--Je souhaite mesurer des données environnementales chronologiques à l'aide de SensorTag --Je joue à un Raspberry Pi3, donc je veux l'utiliser --Je veux essayer des AWS PaaS - DynamoDB、Lambda、API Gateway --Je veux surveiller et utiliser Slack (je ne veux pas écrire UI / UX)
→ En conséquence, nous avons créé quelque chose qui fonctionne, donc je vais le résumer.
La figure a été créée sur www.draw.io. Super pratique.
J'ai utilisé le SensorTag de TI CC2650stk. Il possède de nombreux capteurs tels que l'accélération, la température, l'humidité, l'éclairement, le gyroscope, le géomagnétisme et la pression.
Le SensorTag est essentiellement alimenté par batterie. Cependant, si vous déplacez beaucoup le capteur, la consommation d'énergie sera élevée. Cette fois, je veux obtenir toutes les informations du capteur dans l'ordre chronologique. Attachez Debug Board à SensorTag, connectez-vous à RaspberryPi3 via USB et connectez-vous. L'alimentation était fournie par un câble USB. Cela vous permet de mesurer pendant une longue période sans vous soucier de la batterie.
J'ai fait référence à ce site. http://kinokotimes.com/2017/03/07/usb-control-method-by-raspberry-pi/ [BeagleBoneBlackBox_USB Power Control](http://www.si-linux.co.jp/techinfo/index.php?BeagleBoneBlackBox_USB%E9%9B%BB%E6%BA%90%E5%88%B6%E5%BE% A1)
Je collecte les données du capteur avec l'application côté Raspberry Pi. Parfois, l'application plante et redémarre. À ce moment-là, l'alimentation de SensorTag est également redémarrée au cas où. Que cela soit approprié ou non, cela rendra l'état de démarrage uniforme.
usbrefresh.sh
#! /bin/sh
hub-ctrl -h 0 -P 2 -p 0
sleep 1
hub-ctrl -h 0 -P 2 -p 1
J'ai fait référence à ce site. http://taku-make.blogspot.jp/2015/02/blesensortag.html http://dev.classmethod.jp/hardware/raspberrypi/sensortag-raspberry-pi-2-script/ Il existait diverses bibliothèques liées au BLE pour collecter des informations à partir d'étiquettes de capteur. bluepy a fonctionné le plus facilement, alors maintenant.
[Exemple de code] du kit SDK AWS (https://github.com/aws/aws-iot-device-sdk-python/blob/master/samples/basicPubSub/basicPubSub.py) a été utilisé tel quel. Licence Apache 2.0. Nous avons apporté des modifications telles que la partie à acquérir par BLE et la partie à créer JSON. Fondamentalement, je pense que le code est basé sur l'hypothèse que la communication sera effectuée en utilisant les informations d'identification X.509.
sendMQTT.py
'''
/*
* Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
'''
###-----------------------------------------------------------------------------
from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
import sys
import logging
import time
import argparse
from btle import UUID, Peripheral, DefaultDelegate, AssignedNumbers
import struct
import math
from sensortag import SensorTag, KeypressDelegate
import json
from datetime import datetime
###-----------------------------------------------------------------------------
# Custom MQTT message callback
def customCallback(client, userdata, message):
print("--------------")
print("Received : " + message.payload)
print("from topic: " + message.topic)
print("--------------\n\n")
###-----------------------------------------------------------------------------
# Read in command-line parameters
parser = argparse.ArgumentParser()
### AWS!
parser.add_argument("-e", "--endpoint", action="store", required=True, dest="host", help="Your AWS IoT custom endpoint")
parser.add_argument("-r", "--rootCA", action="store", dest="rootCAPath", default="root-CA.crt", help="Root CA file path")
parser.add_argument("-c", "--cert", action="store", dest="certificatePath", default="certificate.pem.crt",help="Certificate file path")
parser.add_argument("-k", "--key", action="store", dest="privateKeyPath", default="private.pem.key",help="Private key file path")
parser.add_argument("-w", "--websocket", action="store_true", dest="useWebsocket", default=False,
help="Use MQTT over WebSocket")
parser.add_argument("-id", "--clientId", action="store", dest="clientId", default="Raspi_1", help="Targeted client id")
parser.add_argument("-t", "--topic", action="store", dest="topic", default="etl/room", help="Targeted topic")
### SensorTag!
parser.add_argument('-n', action='store', dest='count', default=0,
type=int, help="Number of times to loop data")
parser.add_argument('-T',action='store', dest="sleeptime", type=float, default=5.0, help='time between polling')
parser.add_argument('-H', action='store', dest="taghost", help='MAC of BT device')
parser.add_argument('--all', action='store_true', default=True)
args = parser.parse_args()
host = args.host
rootCAPath = args.rootCAPath
certificatePath = args.certificatePath
privateKeyPath = args.privateKeyPath
useWebsocket = args.useWebsocket
clientId = args.clientId
topic = args.topic
sleeptime = args.sleeptime
deviceID = args.clientId
###=============================================================================
if args.useWebsocket and args.certificatePath and args.privateKeyPath:
parser.error("X.509 cert authentication and WebSocket are mutual exclusive. Please pick one.")
exit(2)
if not args.useWebsocket and (not args.certificatePath or not args.privateKeyPath):
parser.error("Missing credentials for authentication.")
exit(2)
###=============================================================================
# Enabling selected sensors
print('Connecting to ' + args.taghost)
tag = SensorTag(args.taghost)
if args.all:
tag.IRtemperature.enable()
tag.humidity.enable()
tag.barometer.enable()
tag.accelerometer.enable()
tag.magnetometer.enable()
tag.gyroscope.enable()
tag.battery.enable()
tag.keypress.enable()
tag.setDelegate(KeypressDelegate())
tag.lightmeter.enable()
time.sleep(1.0)
###=============================================================================
# Configure logging
logger = logging.getLogger("AWSIoTPythonSDK.core")
logger.setLevel(logging.DEBUG)
streamHandler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
streamHandler.setFormatter(formatter)
logger.addHandler(streamHandler)
###=============================================================================
# Init AWSIoTMQTTClient
myAWSIoTMQTTClient = None
if useWebsocket:
myAWSIoTMQTTClient = AWSIoTMQTTClient(clientId, useWebsocket=True)
myAWSIoTMQTTClient.configureEndpoint(host, 443)
myAWSIoTMQTTClient.configureCredentials(rootCAPath)
else:
myAWSIoTMQTTClient = AWSIoTMQTTClient(clientId)
myAWSIoTMQTTClient.configureEndpoint(host, 8883)
myAWSIoTMQTTClient.configureCredentials(rootCAPath, privateKeyPath, certificatePath)
# AWSIoTMQTTClient connection configuration
myAWSIoTMQTTClient.configureAutoReconnectBackoffTime(1, 32, 20)
myAWSIoTMQTTClient.configureOfflinePublishQueueing(-1) # Infinite offline Publish queueing
myAWSIoTMQTTClient.configureDrainingFrequency(0.5) # Draining: 2 Hz
myAWSIoTMQTTClient.configureConnectDisconnectTimeout(10) # 10 sec
myAWSIoTMQTTClient.configureMQTTOperationTimeout(5) # 5 sec
# Connect and subscribe to AWS IoT
myAWSIoTMQTTClient.connect()
#myAWSIoTMQTTClient.subscribe(topic, 1, customCallback)
time.sleep(2)
###=============================================================================
# Publish to the same topic in a loop forever
loopCount = 0
Payload = {}
while True:
Payload['ID'] = str(deviceID)
ambient_temp, target_temparature = tag.IRtemperature.read()
Payload["AmbientTemp"] = ambient_temp
Payload["TargetTemp"] = target_temparature
ambient_temp, rel_humidity = tag.humidity.read()
Payload["Humidity"] = rel_humidity
ambient_temp, pressure_millibars = tag.barometer.read()
Payload["Barometer"] = pressure_millibars
Acc_x, Acc_y, Acc_z = tag.accelerometer.read()
Payload["AccX"] = Acc_x
Payload["AccY"] = Acc_y
Payload["AccZ"] = Acc_z
magnet_x, magnet_y, magnet_z = tag.magnetometer.read()
Payload["MagnetX"] = magnet_x
Payload["MagnetY"] = magnet_y
Payload["MagnetZ"] = magnet_z
gyro_x, gyro_y, gyro_z = tag.gyroscope.read()
Payload["GyroX"] = gyro_x
Payload["GyroY"] = gyro_y
Payload["GyroZ"] = gyro_z
Payload["Light"] = tag.lightmeter.read()
Payload["Batterys"] = tag.battery.read()
Payload["Count"] = loopCount
Payload["Datetime"] = datetime.now().strftime("%Y/%m/%d %H:%M:%S")
# print("try to send!")
myAWSIoTMQTTClient.publish(topic, json.dumps(Payload), 1)
# print("end!")
loopCount += 1
time.sleep(sleeptime)
Le programme ci-dessus échoue parfois. Il peut être judicieux de débuter sérieusement, C'est une tarte aux râpes et c'est Python, alors j'ai décidé qu'il tomberait. Exécutez un script comme celui ci-dessous avec cron une fois par minute. Vous voudrez peut-être quitter sendMQTT.py à chaque fois et le démarrer avec juste cron, Il est également difficile de démarrer Maikai Python- Il existe également une partie de Python qui compte les répétitions. Je sais aussi que cela fonctionne environ 10 000 fois sans tomber, donc cron essaie de surveiller la vie et la mort.
Au fait, avec crontab -e
sendSensorData.sh
#!/bin/sh
ps | grep python
if [ "$?" -eq 0 ]
then
logger "python is exist. exit..."
exit 0
else
logger "start reset sequence..."
sudo usbrefresh.sh
sleep 3
cd /home/pi/deviceSDK
python ./sendMQTT.py -T 59 -H "AA:BB:CC:DD:EE:FF" -e awsiotnoarn.iot.ap-northeast-1.amazonaws.com >/dev/null 2>&1 &
cd
exit 0
fi
Il y a une partie du script ci-dessus qui dit -T 59. Cela signifie dormir pendant 59 secondes dans une boucle infinie dans le script Python. Il existe des fluctuations subtiles telles que le temps d'acquisition des données avec BLE et le temps de communication avec AWS. Il peut être possible d'utiliser un système d'exploitation en temps réel et d'insérer une interruption toutes les minutes, etc. Puisqu'il est composé de tarte à la râpe, de jessie raspbienne et de Python, il est jugé qu'il est impossible d'atteindre ce point, et il est divisible. Vous pouvez en fait obtenir des données chronologiques, mais pas exactement toutes les minutes. Les données montrent qu'il y a quelques secondes de gigue.
AWS PaaS Le côté AWS est construit sans serveur. Pour étudier.
AWS IoT J'ai fait référence à ce site. http://qiita.com/nsedo/items/c6f33c7cadea7023403f Le reste est toujours le SDK AWS. MQTT crée et lance simplement JSON, je n'ai donc pas à me soucier de l'utilisation d'AWS IoT. Il reçoit les données JSON et les place dans DynamoDB telles quelles.
DynamoDB
La clé est le nom de l'ID et les informations d'heure. J'ai entendu quelque part que cette méthode est bonne pour créer des données de séries chronologiques avec l'IoT. D'autres sont comme organiser les données acquises. Lors de l'analyse de données chronologiques, nous n'organisons que les données non structurées dans la base de données. Comme vous pouvez le voir, il y a un endroit où j'ai créé un JSON et l'ai lancé tel quel.
La clé est uniquement le nom de l'ID. La valeur est --Data: Le dernier JSON reçu tel quel --isNight: indique s'il reconnaît ou non que la lumière est allumée est. Cette fois, nous ne traitons que des changements dans les informations de Lumière, donc nous ne conservons que cet état. Afin de rendre le côté Lambda sans état, l'état est destiné à DynamoDB.
Juste au cas où, si vous l'écrivez en JSON, ce seront de telles données. Pour la base de données de séries chronologiques, seuls les éléments de données suivants sont alignés.
room.json
{
"Data": {
"AccX": {
"N": "0.915283203125"
},
"AccY": {
"N": "-0.129150390625"
},
"AccZ": {
"N": "0.48974609375"
},
"AmbientTemp": {
"N": "31.78125"
},
"Barometer": {
"N": "1006.03"
},
"Batterys": {
"N": "100"
},
"Count": {
"N": "261"
},
"Datetime": {
"S": "2017/09/06 13:31:35"
},
"GyroX": {
"N": "-4.0740966796875"
},
"GyroY": {
"N": "1.85394287109375"
},
"GyroZ": {
"N": "1.983642578125"
},
"Humidity": {
"N": "40.95458984375"
},
"ID": {
"S": "Raspi_1"
},
"Light": {
"N": "57.88"
},
"MagnetX": {
"N": "38.83418803418803"
},
"MagnetY": {
"N": "-19.34212454212454"
},
"MagnetZ": {
"N": "-17.842735042735043"
},
"TargetTemp": {
"N": "23.78125"
}
},
"ID": "Raspi_1",
"isNight": "0"
}
Lambda C'est embarrassant car il y a de nombreux endroits qui ne sont pas écrits correctement. .. .. La partie d'Exception et le contenu arrivent à Slack comme un endroit où je ne peux pas arriver à une conclusion tant que je suis inquiet. Je ne sais pas quoi faire dans un tel cas. Je ne veux pas être exécuté à plusieurs reprises lorsqu'une exception se produit dans Lambda, ou je ne veux pas publier à plusieurs reprises une erreur dans Slack, alors je me demandais quoi faire, mais j'en ai fait une tâche future. Je ne connais pas non plus les spécifications de Lambda.
Construire une table de salle qui contient l'état de la pièce est également presque approprié, Clé: ID Rasppie, ici "Raspi_1" data: Toutes les données de SensorTag, Kita guy Lumière: l'état actuel est-il déterminé comme étant «activé» ou «désactivé»? C'est un dessin.
slackpost C'est là que Lambda reçoit les données du flux dynamoDB. Obtenez des informations Light de la table ETLRoom et comparez-les avec la table actuelle Il détermine si la lumière est allumée ou éteinte et l'affiche pour se détendre en cas de changement.
slackpost.py
# coding : utf-8
import json
import datetime
import requests
import boto3
LIGHT_TAG='Light'
#===============================================================================
# Slack Incomming Webhooks URL
SLACL_POST_URL = "https://hooks.slack.com/services/XXXXX/YYYYY/ZZZZZ"
# Post to Slack
def PostSlack(message, icon=':ghost:', username='ETLBot', channel="#room"):
Dict_Payload = {
"text": message,
"username": username,
"icon_emoji": icon,
"channel": channel,
}
return requests.post(SLACL_POST_URL, data=json.dumps(Dict_Payload))
#-------------------------------------------------------------------------------
def Check_LightChanges(new, old, IsNight):
Change2Night = None
Change2Morining = None
print("new:" , new, ", old:", old, ", IsNight:", IsNight)
if (IsNight=='1') and (new > (old + 50)):
Change2Morining = True
IsNight = '0'
elif (IsNight=='0') and (new < 10):
Change2Night = True
IsNight = '1'
# Down -> UP
if Change2Morining:
message = ":smiley: Light is Turned On. Good Morning! :smiley:"
icon = ":smiley:"
# UP -> Down
elif Change2Night:
message = ":ghost: Light is Turned Down. Good Bye! :ghost:"
icon = ":ghost:"
else:
return IsNight
PostSlack(message, icon=icon)
return IsNight
#-------------------------------------------------------------------------------
table = None
def update_table(data):
ID = data['ID']['S']
# Access to ETLRoom Table
global table
if not table:
table = boto3.resource('dynamodb').Table('ETLRoom')
response = table.get_item(Key={'ID': ID})
if response:
item = response['Item']
#PostSlack(json.dumps(item))
light = round(float(item['Data'][LIGHT_TAG]['N']))
IsNight = item['IsNight']
else:
light = 0
IsNight = Check_LightChanges(round(float(data[LIGHT_TAG]['N'])), light, IsNight)
# Update Room Table
response = table.put_item(
Item={
"ID": ID,
"Data" : data,
"IsNight": IsNight
}
)
return 0
#-------------------------------------------------------------------------------
def lambda_handler(event, context):
try:
for record in event['Records']:
dynamodb = record['dynamodb']
keys = dynamodb['Keys']
data = dynamodb['NewImage']
# Keys are "ID" and "Datetime".
id = keys['ID']['S']
datetime = keys['Datetime']['S']
print("ID:", id, "/ Date:", datetime)
update_table(data)
except Exception as e:
import traceback
message = traceback.format_exc()
print(message)
PostSlack('Meets Exception!\n' + message)
raise e
return 0
#===============================================================================
getroomenv
getroomenv.py
# coding : utf-8
import json
import requests
import boto3
# Slack Incomming Webhooks URL
SLACL_POST_URL = "https://hooks.slack.com/services/XXXXX/YYYYY/ZZZZZ"
#===============================================================================
def MakeStr(data, key, round_n):
return str(round(float(data[key]['N']), round_n))
table = None
def GetRoomEnv(id, isAll):
global table
if not table:
table = boto3.resource('dynamodb').Table('ETLRoom')
response = table.get_item(
Key={
'ID': id
}
)
data = response['Item']['Data']
light = MakeStr(data, 'Light', 1)
temp = MakeStr(data, 'TargetTemp', 1)
humid = MakeStr(data, 'Humidity', 1)
balo = MakeStr(data, 'Barometer', 1)
time = data['Datetime']['S']
message = "" \
+ "La température actuelle est" + temp + "Degré, humidité"+ humid + "Diplôme.\n" \
+ "Luminosité" + light + "En lux, la pression est"+ balo + "C'est hPa.\n" \
+ "(" + time + "Mesuré à:bar_chart:)"
#S'il y a un argument ALL, s'il y a un argument, vider toutes les données
if isAll:
message = ""
for d in data:
s = str(d)
v = data[s]
if "N" in v:
message += s + ":" + v["N"] + "\n"
else:
message += s + ":" + v["S"] + "\n"
return message
#-------------------------------------------------------------------------------
# POST to Slack
def PostSlack(message):
Dict_Payload = {
"text": message,
"username": 'NowBot',
"icon_emoji": ":clock3:",
"channel": '#room',
}
return requests.post(SLACL_POST_URL, data=json.dumps(Dict_Payload))
#-------------------------------------------------------------------------------
def lambda_handler(event, context):
isAll = False
try:
tri1 = 'text='
tri2 = 'trigger_word='
body = event['body']
tag = body[body.find(tri1)+len(tri1):body.find(tri2)-1]
taglist = tag.split("+")
for word in taglist:
if "ALL" in word:
isAll = True
except:
pass
try:
message = GetRoomEnv('Raspi_1', isAll)
except Exception as e:
import traceback
message = traceback.format_exc()
print(message)
PostSlack('Meets Exception!\n' + message)
raise e
PostSlack(message)
return 0
#===============================================================================
API Gateway
Slack Les WebHooks entrants et les WebHooks sortants ont été ajoutés aux intégrations personnalisées. Vous pouvez maintenant utiliser l'interface utilisateur. Super pratique.
Incoming WebHooks https://hooks.slack.com/services/XXXXXXX/YYYYYYY/ZZZZZZ Générez et définissez l'URL de publication sur Slack. Tout ce que vous avez à faire est de publier depuis Lambda. Vous pouvez dire s'il est activé ou désactivé. Ce qui suit est un exemple.
<img width = "556" alt = "Capture d'écran 2017-09-05 21.07.36.png " src = "https://qiita-image-store.s3.amazonaws.com/0/171122/6fd49e45-4271" -642d-f5a4-045833411c15.png ">
En regardant cela, nous pouvons voir ce qui suit.
Outgoing WebHooks Trigger Word Le mot déclencheur est «maintenant». Envoyez "maintenant" et NowBot vous indiquera l'état de la salle.
<img width = "389" alt = "Capture d'écran 2017-09-05 20.07.48.png " src = "https://qiita-image-store.s3.amazonaws.com/0/171122/94beb1e4-359a" -45f4-a6bb-01d02354fd63.png ">
L'envoi de "now ALL" videra toutes les données de la base de données. Pour le débogage.
<img width = "437" alt = "Capture d'écran 2017-09-05 20.11.08.png " src = "https://qiita-image-store.s3.amazonaws.com/0/171122/20954556-c401" -142f-e816-d6a2ade8f29b.png ">
J'ai pensé à traiter le libellé suivant "maintenant" en langage naturel (comme Amazon Polly) et à le renvoyer s'il y avait un paramètre lié (comme la température), mais ce sera la prochaine opportunité. .. ..
URL https://XXXXXX.execute-api.ap-northeast-1.amazonaws.com/prod/getroomenv Est réglé. Il a été créé avec AWS API Gateway, avec Lambda derrière. Le script getroomenv est en cours d'exécution.
Il a fallu environ 3 jours pour se déplacer complètement Après cela, je me suis entretenu pendant environ deux jours et j'ai observé la situation. J'ai besoin d'un peu plus de Kouo pour DynamoDB et Lambda. C'était une bonne étude. Utiliser Slack pour l'interface utilisateur était une excellente réponse. Vraiment pratique.
Depuis que j'ai collecté des données de séries chronologiques, Utilisez ceci pour prendre la composante de fluctuation périodique Si vous pouvez informer Slack en cas de mouvement inhabituel, Je pense que c'est bon comme prochain développement. Indiquez s'il est normal que les gens viennent travailler le samedi. .. ..
Recommended Posts