Dieser Artikel ist Kapitel 4 eines Artikels mit vier Kapiteln.
Sie können das Operationsbild auf YouTube überprüfen.
Ich wollte in der Lage sein, die von SORACOM-Geräten gesammelten Daten interaktiv zu überprüfen und Haushaltsgeräte mit LINE zu betreiben, das von 80 Millionen Menschen in Japan verwendet wird und mit der Benutzeroberfläche vertraut ist.
$ pip install flask
$ pip install line-bot-sdk
$ pip install firebase-admin
$ pip install pillow
$ pip install paramiko
Ich werde jeden von ihnen zusammen mit dem Quellcode erklären.
Es wird die gesamte Quelle sein. Bitte beachten Sie, dass die Import- und Zugriffstoken-Teile des Moduls danach nicht mehr veröffentlicht werden.
line_bot.py
from flask import Flask, request, abort
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, TextSendMessage, FlexSendMessage
import subprocess
import os
import json
import time
import datetime
import base64
import requests
import ast
import paramiko
import firebase_admin
from firebase_admin import credentials
from firebase_admin import db
from PIL import ImageFont, Image, ImageDraw
from image import add_text_to_image #Selbstgemachtes Modul
app = Flask(__name__)
# LINE Messaging API Settings
LINE_BOT_ACCESS_TOKEN = os.environ["LINE_BOT_ACCESS_TOKEN"]
LINE_BOT_CHANNEL_SECRET = os.environ["LINE_BOT_CHANNEL_SECRET"]
line_bot_api = LineBotApi(LINE_BOT_ACCESS_TOKEN)
handler = WebhookHandler(LINE_BOT_CHANNEL_SECRET)
user_id = "U0..." #ID des Benutzers, der die Nachricht sendet
FQDN = 'https://xxx.ngrok.io' #ngrok URL
# Firebase Settings
cred = credentials.Certificate("<secret key file>.json")
firebase_admin.initialize_app(cred, {
'databaseURL': 'https://xxx.firebaseio.com/'
})
ref = db.reference('data')
# ssh settings
HOST = '192.168.11.xxx'
PORT = 22
USER = 'username'
KEY_FILE = '../.ssh/<secret_key_file>' #Relativer Pfad
@app.route("/webhook", methods=['POST'])
def webhook():
print(json.dumps(request.get_json(), indent=2))
object = request.get_json()
if object['title'] == "[Alerting] Emergency alert":
json_message = {
"type": "bubble",
"hero": {
"type": "image",
"url": "https://xxxx.ngrok.io/static/sos.png ",
"size": "full",
"aspectRatio": "16:9",
"aspectMode": "cover"
},
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": "Der Notfallknopf wurde gedrückt",
"weight": "bold",
"size": "lg",
"color": "#E9462B",
"align": "center"
},
{
"type": "box",
"layout": "vertical",
"margin": "lg",
"spacing": "sm",
"contents": [
{
"type": "box",
"layout": "baseline",
"spacing": "sm",
"contents": [
{
"type": "text",
"text": "Ort",
"color": "#aaaaaa",
"size": "sm",
"flex": 1
},
{
"type": "text",
"text": "Umkleideraum",
"wrap": True,
"color": "#666666",
"size": "sm",
"flex": 5
}
]
},
{
"type": "box",
"layout": "vertical",
"margin": "lg",
"spacing": "sm",
"contents": [
{
"type": "box",
"layout": "baseline",
"spacing": "sm",
"contents": [
{
"type": "text",
"text": "Ich habe die Haustür als Notfallmaßnahme aufgeschlossen",
"color": "#4764a6",
"size": "md",
"flex": 1,
"wrap": True
}
]
}
]
}
]
}
]
},
"footer": {
"type": "box",
"layout": "vertical",
"spacing": "sm",
"contents": [
{
"type": "button",
"style": "primary",
"height": "sm",
"action": {
"type": "message",
"label": "Erste Hilfe",
"text": "Erste Hilfe"
},
"color": "#E9462B"
},
{
"type": "spacer",
"size": "sm"
}
],
"flex": 0
}
}
messages = FlexSendMessage(alt_text='[SOS]Der Notfallknopf wurde gedrückt', contents=json_message)
line_bot_api.push_message(user_id, messages=messages)
key = paramiko.ECDSAKey.from_private_key_file(KEY_FILE)
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(HOST, PORT, USER, pkey=key)
ssh.exec_command('python3 key_open.py')
elif object['title'] == "[Alerting] Temperature & Humidity alert":
current_time = int(time.time()*1000)
fifteen_minutes_ago = current_time - 900000
data = ref.order_by_key().limit_to_last(1).get()
for key, val in data.items():
if val['timestamp'] >= fifteen_minutes_ago:
json_message = {
"type": "bubble",
"hero": {
"type": "image",
"url": "https://xxx.ngrok.io/static/aircon.png ",
"size": "full",
"aspectRatio": "16:9",
"aspectMode": "cover"
},
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": "Ich schaltete die Klimaanlage ein",
"weight": "bold",
"size": "xl",
"color": "#7077BE"
},
{
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": "Temperatur und Luftfeuchtigkeit mit hohem Hitzschlagrisiko.",
"size": "xs",
"wrap": True
},
{
"type": "text",
"text": "Da der menschliche Sensor innerhalb von 15 Minuten reagierte, entschied ich, dass ich zu Hause war und schaltete die Klimaanlage ein.",
"size": "xs",
"wrap": True
}
],
"margin": "sm"
}
]
},
"footer": {
"type": "box",
"layout": "vertical",
"spacing": "sm",
"contents": [
{
"type": "button",
"style": "primary",
"height": "sm",
"action": {
"type": "message",
"label": "Siehe aktuelle Temperatur / Luftfeuchtigkeit",
"text": "Temperatur Feuchtigkeit"
},
"color": "#6fb1bf"
},
{
"type": "button",
"style": "primary",
"height": "sm",
"action": {
"type": "uri",
"label": "Erkundigen Sie sich bei SORACOM Lagoon",
"uri": "https://jp.lagoon.soracom.io/"
},
"color": "#34CDD7"
},
{
"type": "button",
"style": "secondary",
"height": "sm",
"action": {
"type": "message",
"label": "Schalten Sie die Klimaanlage aus",
"text": "Schalten Sie die Klimaanlage aus"
},
"color": "#DDDDDD"
},
{
"type": "spacer",
"size": "sm"
}
],
"flex": 0
}
}
messages = FlexSendMessage(alt_text='Ich schaltete die Klimaanlage ein', contents=json_message)
line_bot_api.push_message(user_id, messages=messages)
subprocess.run("python3 IR-remocon02-commandline.py t `cat filename4.dat`", shell = True, cwd="/home/pi/I2C0x52-IR")
return request.get_data()
@app.route("/callback", methods=['POST'])
def callback():
signature = request.headers['X-Line-Signature']
body = request.get_data(as_text=True)
app.logger.info("Request body: " + body)
try:
handler.handle(body, signature)
except InvalidSignatureError:
abort(400)
return 'OK'
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
password = os.environ["soracom_pass"]
if event.message.text == "Temperatur Feuchtigkeit":
headers = {
'Content-Type': 'application/json',
}
data = '{"email": "[email protected]", "password": "' + password + '"}'
response = requests.post('https://api.soracom.io/v1/auth', headers=headers, data=data)
apikey = response.json()['apiKey']
token = response.json()['token']
current_time = int(time.time()*1000)
headers = {
'Accept': 'application/json',
'X-Soracom-API-Key': apikey,
'X-Soracom-Token': token,
}
params = (
('to', current_time),
('sort', 'desc'),
('limit', '1'),
)
response = requests.get('https://api.soracom.io/v1/data/Subscriber/44xxxxxxxxxxxxx', headers=headers, params=params)
request_body = response.json()
content = [d.get('content') for d in request_body]
payload = content[0]
payload_dic = ast.literal_eval(payload)
message = base64.b64decode(payload_dic['payload']).decode()
temp = ast.literal_eval(message)['temp']
humi = ast.literal_eval(message)['humi']
base_image_path = './image.png'
base_img = Image.open(base_image_path).copy()
base_img = base_img.convert('RGB')
temperature = str(temp)
font_path = "/usr/share/fonts/downloadfonts/DSEG7-Classic/DSEG7Classic-Regular.ttf"
font_size = 80
font_color = (255, 255, 255)
height = 90
width = 180
img = add_text_to_image(base_img, temperature, font_path, font_size, font_color, height, width)
humidity = str(humi)
height = 330
img = add_text_to_image(base_img, humidity, font_path, font_size, font_color, height, width)
img_path = 'static/{}.png'.format(datetime.datetime.now().strftime('%H-%M-%S'))
img.save(img_path)
json_message = {
"type": "bubble",
"hero": {
"type": "image",
"url": FQDN + '/' + img_path,
"size": "full",
"aspectRatio": "1:1",
"aspectMode": "fit",
},
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": "Temperatur&Feuchtigkeit",
"weight": "bold",
"size": "xl",
"color": "#6fb1bf"
},
{
"type": "box",
"layout": "vertical",
"margin": "lg",
"spacing": "sm",
"contents": [
{
"type": "box",
"layout": "baseline",
"spacing": "sm",
"contents": [
{
"type": "text",
"text": "Temperatur",
"color": "#aaaaaa",
"size": "sm",
"flex": 1
},
{
"type": "text",
"text": temperature + '℃',
"wrap": True,
"color": "#666666",
"size": "sm",
"flex": 5
}
]
},
{
"type": "box",
"layout": "baseline",
"spacing": "sm",
"contents": [
{
"type": "text",
"text": "Feuchtigkeit",
"color": "#aaaaaa",
"size": "sm",
"flex": 1
},
{
"type": "text",
"text": humidity + "%",
"wrap": True,
"color": "#666666",
"size": "sm",
"flex": 5
}
]
}
]
}
]
},
"footer": {
"type": "box",
"layout": "vertical",
"spacing": "sm",
"contents": [
{
"type": "button",
"style": "primary",
"height": "sm",
"action": {
"type": "uri",
"label": "Erkundigen Sie sich bei SORACOM Lagoon",
"uri": "https://jp.lagoon.soracom.io/"
},
"color": "#34CDD7"
},
{
"type": "button",
"style": "secondary",
"height": "sm",
"action": {
"type": "message",
"label": "Menschlicher Sensor",
"text": "Menschlicher Sensor"
},
"color": "#DDDDDD"
},
{
"type": "spacer",
"size": "sm"
}
],
"flex": 0
}
}
messages = FlexSendMessage(alt_text='Temperatur&Feuchtigkeit', contents=json_message)
line_bot_api.reply_message(event.reply_token, messages)
elif event.message.text == "Menschlicher Sensor":
current_time = int(time.time()*1000)
one_hour_ago = current_time - 3600000
data = ref.order_by_key().limit_to_last(1).get()
for key, val in data.items():
timestamp = datetime.datetime.fromtimestamp(int(val['timestamp']/1000))
last_time = timestamp.strftime('%m Monat%d Tag%Uhr%M Minuten')
count = 0
data = ref.order_by_key().get()
for key, val in data.items():
timestamp = val['timestamp']
if timestamp >= one_hour_ago:
count += 1
json_message = {
"type": "bubble",
"hero": {
"type": "image",
"url": "https://xxx.ngrok.io/static/sensors.png ",
"size": "full",
"aspectRatio": "16:9",
"aspectMode": "cover"
},
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": "Menschlicher Sensor",
"weight": "bold",
"size": "xl",
"color": "#72D35B"
},
{
"type": "box",
"layout": "vertical",
"margin": "lg",
"spacing": "sm",
"contents": [
{
"type": "box",
"layout": "baseline",
"spacing": "sm",
"contents": [
{
"type": "text",
"text": "Anzahl der Erkennungen innerhalb von 1 Stunde",
"color": "#aaaaaa",
"size": "sm",
"flex": 10
},
{
"type": "text",
"text": str(count) + "Mal",
"wrap": True,
"color": "#666666",
"size": "sm",
"flex": 4,
"align": "end"
}
]
},
{
"type": "box",
"layout": "baseline",
"spacing": "sm",
"contents": [
{
"type": "text",
"text": "Letzte erkannte Zeit",
"color": "#aaaaaa",
"size": "sm",
"flex": 5
},
{
"type": "text",
"text": last_time,
"wrap": True,
"color": "#666666",
"size": "sm",
"flex": 5,
"align": "end"
}
]
}
]
}
]
},
"footer": {
"type": "box",
"layout": "vertical",
"spacing": "sm",
"contents": [
{
"type": "spacer",
"size": "sm"
}
],
"flex": 0
}
}
messages = FlexSendMessage(alt_text='Menschlicher Sensor', contents=json_message)
line_bot_api.reply_message(event.reply_token, messages)
elif event.message.text == "Erste Hilfe":
messages = "Beruhige dich und rufe 119 an"
line_bot_api.reply_message(event.reply_token, TextSendMessage(text=messages))
elif event.message.text == "Schalten Sie die Klimaanlage aus":
messages = "Ich habe die Klimaanlage ausgeschaltet"
line_bot_api.reply_message(event.reply_token, TextSendMessage(text=messages))
subprocess.run("python3 IR-remocon02-commandline.py t `cat filename5.dat`", shell = True, cwd="/home/pi/I2C0x52-IR")
if __name__ == "__main__":
port = int(os.getenv("PORT", 6000))
app.run(host="0.0.0.0", port=port)
Holen Sie sich den Schlüssel und das Token, die für die Verwendung der API im folgenden Teil erforderlich sind. Das Passwort wird für alle Fälle als Umgebungsvariable festgelegt.
line_bot.py
password = os.environ["soracom_pass"]
headers = {
'Content-Type': 'application/json',
}
data = '{"email": "[email protected]", "password": "' + password + '"}'
response = requests.post('https://api.soracom.io/v1/auth', headers=headers, data=data)
apikey = response.json()['apiKey']
token = response.json()['token']
Und erhalten Sie die neueste Temperatur und Luftfeuchtigkeit. Wenn Sie andere APIs verwenden möchten, können Sie unter API-Referenz darauf verweisen. Da der Befehl cURL geschrieben ist, konvertieren Sie ihn unter dieser Site in das Python-Format.
Da request_body
als Liste zurückgegeben wird, nehmen Sie content
heraus und konvertieren payload
in ein Wörterbuch mit einem Modul namens ast
. Sie können die Temperatur und Luftfeuchtigkeit ermitteln, indem Sie die Nachricht mit base64 dekodieren.
line_bot.py
current_time = int(time.time()*1000)
headers = {
'Accept': 'application/json',
'X-Soracom-API-Key': apikey,
'X-Soracom-Token': token,
}
params = (
('to', current_time),
('sort', 'desc'),
('limit', '1'),
)
response = requests.get('https://api.soracom.io/v1/data/Subscriber/44xxxxxxxxxxxxx', headers=headers, params=params)
request_body = response.json()
content = [d.get('content') for d in request_body]
payload = content[0]
payload_dic = ast.literal_eval(payload)
message = base64.b64decode(payload_dic['payload']).decode()
temp = ast.literal_eval(message)['temp']
humi = ast.literal_eval(message)['humi']
Sobald Sie die Daten haben, verwenden Sie Pillow, um ein Bild mit der Temperatur und Luftfeuchtigkeit zu erstellen. Ich habe auf [diesen Artikel] verwiesen (https://qiita.com/xKxAxKx/items/2599006005098dc2e299). Erstellen Sie das folgende Basisbild mit PowerPoint und schreiben Sie die Temperatur und Luftfeuchtigkeit mit der Schriftart DSEG hinein.
line_bot.py
temperature = str(temp)
font_path = "/usr/share/fonts/downloadfonts/DSEG7-Classic/DSEG7Classic-Regular.ttf"
font_size = 80
font_color = (255, 255, 255)
height = 90
width = 180
img = add_text_to_image(base_img, temperature, font_path, font_size, font_color, height, width)
humidity = str(humi)
height = 330
img = add_text_to_image(base_img, humidity, font_path, font_size, font_color, height, width)
img_path = 'static/{}.png'.format(datetime.datetime.now().strftime('%H-%M-%S'))
img.save(img_path)
image.py
from PIL import ImageFont, Image, ImageDraw
def add_text_to_image(img, text, font_path, font_size, font_color, height, width, max_length=740):
position = (width, height)
font = ImageFont.truetype(font_path, font_size)
draw = ImageDraw.Draw(img)
if draw.textsize(text, font=font)[0] > max_length:
while draw.textsize(text + '…', font=font)[0] > max_length:
text = text[:-1]
text = text + '…'
draw.text(position, text, font_color, font=font)
return img
Senden Sie abschließend eine Flex-Nachricht. Mit dem Flex Message Simulator (https://developers.line.biz/flex-simulator/) ist das Erstellen einfach.
line_bot.py
json_message = {
"type": "bubble",
"hero": {
"type": "image",
"url": FQDN + '/' + img_path,
"size": "full",
"aspectRatio": "1:1",
"aspectMode": "fit",
},
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": "Temperatur&Feuchtigkeit",
"weight": "bold",
"size": "xl",
"color": "#6fb1bf"
},
{
"type": "box",
"layout": "vertical",
"margin": "lg",
"spacing": "sm",
"contents": [
{
"type": "box",
"layout": "baseline",
"spacing": "sm",
"contents": [
{
"type": "text",
"text": "Temperatur",
"color": "#aaaaaa",
"size": "sm",
"flex": 1
},
{
"type": "text",
"text": temperature + '℃',
"wrap": True,
"color": "#666666",
"size": "sm",
"flex": 5
}
]
},
{
"type": "box",
"layout": "baseline",
"spacing": "sm",
"contents": [
{
"type": "text",
"text": "Feuchtigkeit",
"color": "#aaaaaa",
"size": "sm",
"flex": 1
},
{
"type": "text",
"text": humidity + "%",
"wrap": True,
"color": "#666666",
"size": "sm",
"flex": 5
}
]
}
]
}
]
},
"footer": {
"type": "box",
"layout": "vertical",
"spacing": "sm",
"contents": [
{
"type": "button",
"style": "primary",
"height": "sm",
"action": {
"type": "uri",
"label": "Erkundigen Sie sich bei SORACOM Lagoon",
"uri": "https://jp.lagoon.soracom.io/"
},
"color": "#34CDD7"
},
{
"type": "button",
"style": "secondary",
"height": "sm",
"action": {
"type": "message",
"label": "Menschlicher Sensor",
"text": "Menschlicher Sensor"
},
"color": "#DDDDDD"
},
{
"type": "spacer",
"size": "sm"
}
],
"flex": 0
}
}
messages = FlexSendMessage(alt_text='Temperatur&Feuchtigkeit', contents=json_message)
line_bot_api.reply_message(event.reply_token, messages)
line_bot.py
current_time = int(time.time()*1000)
one_hour_ago = current_time - 3600000
data = ref.order_by_key().limit_to_last(1).get()
for key, val in data.items():
timestamp = datetime.datetime.fromtimestamp(int(val['timestamp']/1000))
last_time = timestamp.strftime('%m Monat%d Tag%Uhr%M Minuten')
count = 0
data = ref.order_by_key().get()
for key, val in data.items():
timestamp = val['timestamp']
if timestamp >= one_hour_ago:
count += 1
Der Flex Message-Teil ist lang, daher habe ich ihn weggelassen, aber er ist der gleiche wie ➊.
Ich habe auch Paramiko
verwendet, um in einen anderen Raspberry Pi zu SSH und Befehle in Python auszuführen.
Ändern Sie den Teil ECDSAKey
in den Schlüsseltyp, den Sie entsprechend festgelegt haben. Sie sollten entweder RSA oder Ed25519 verwenden können.
line_bot.py
key = paramiko.ECDSAKey.from_private_key_file(KEY_FILE)
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(HOST, PORT, USER, pkey=key)
ssh.exec_command('python3 key_open.py')
key_open.py
ist ein einfaches Programm, das nur den Servomotor dreht.
key_open.py
import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(4, GPIO.OUT)
p = GPIO.PWM(4, 50)
p.start(0.0)
p.ChangeDutyCycle(3.0)
time.sleep(0.4)
p.ChangeDutyCycle(0.0)
GPIO.cleanup()
line_bot.py
current_time = int(time.time()*1000)
fifteen_minutes_ago = current_time - 900000
data = ref.order_by_key().limit_to_last(1).get()
for key, val in data.items():
if val['timestamp'] >= fifteen_minutes_ago:
#Flex-Nachrichtenteil weggelassen
subprocess.run("python3 IR-remocon02-commandline.py t cat `filename4.dat`", shell = True, cwd="/home/pi/I2C0x52-IR")
Ich konnte Daten durchsuchen und Haushaltsgeräte bedienen, indem ich die Dienste von LINE Bot und SORACOM, zwei Raspberry Pis und anderen externen Modulen voll ausnutzte. Ich habe nicht viele Artikel über das Berühren der SORACOM-API in Python oder das Verknüpfen von SORACOM mit LINE Bot gesehen, daher hoffe ich, dass es jemandem hilft. Wenn Sie Anregungen oder Fragen haben, können Sie diese gerne kommentieren.
Recommended Posts