→ Als Ergebnis haben wir etwas erstellt, das funktioniert, also werde ich es zusammenfassen.
Die Figur wurde unter www.draw.io erstellt. Super praktisch.
Ich habe den SensorTag [CC2650stk] von TI verwendet (http://www.tij.co.jp/tool/jp/cc2650stk). Es verfügt über viele Sensoren wie Beschleunigung, Temperatur, Luftfeuchtigkeit, Beleuchtungsstärke, Kreisel, Geomagnetismus und Druck.
Der SensorTag ist grundsätzlich batteriebetrieben. Wenn Sie den Sensor jedoch stark bewegen, ist der Stromverbrauch hoch. Dieses Mal möchte ich alle Sensorinformationen in chronologischer Reihenfolge abrufen. Schließen Sie Debug Board an SensorTag an, stellen Sie über USB eine Verbindung zu RaspberryPi3 her und stellen Sie eine Verbindung her. Die Stromversorgung erfolgte über ein USB-Kabel. Auf diese Weise können Sie lange messen, ohne sich Gedanken über den Akku machen zu müssen.
Ich habe auf diese Seite verwiesen. 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)
Ich sammle Sensordaten mit der App auf der Raspberry Pi-Seite. Gelegentlich stürzt die App ab und wird neu gestartet. Zu diesem Zeitpunkt wird auch die Stromversorgung von SensorTag für alle Fälle neu gestartet. Unabhängig davon, ob dies angemessen ist oder nicht, wird der Startstatus dadurch einheitlich.
usbrefresh.sh
#! /bin/sh
hub-ctrl -h 0 -P 2 -p 0
sleep 1
hub-ctrl -h 0 -P 2 -p 1
Ich habe auf diese Seite verwiesen. http://taku-make.blogspot.jp/2015/02/blesensortag.html http://dev.classmethod.jp/hardware/raspberrypi/sensortag-raspberry-pi-2-script/ Es gab verschiedene BLE-bezogene Bibliotheken zum Sammeln von Informationen von Sensor-Tags. bluepy hat am einfachsten funktioniert, also jetzt.
Der [Beispielcode] des AWS SDK (https://github.com/aws/aws-iot-device-sdk-python/blob/master/samples/basicPubSub/basicPubSub.py) wurde unverändert verwendet. Apache 2.0 Lizenz. Wir haben Änderungen vorgenommen, z. B. das von BLE zu erwerbende Teil und das zu erstellende Teil von JSON. Grundsätzlich denke ich, dass der Code auf der Annahme basiert, dass die Kommunikation mit den X.509-Anmeldeinformationen durchgeführt wird.
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)
Das obige Programm schlägt manchmal fehl. Es kann sinnvoll sein, ernsthaft zu debuckeln, Es ist eine Raspeltorte und es ist Python, also habe ich beschlossen, dass es fallen würde. Führen Sie einmal pro Minute ein Skript wie das folgende mit cron aus. Möglicherweise möchten Sie sendMQTT.py jedes Mal beenden und mit nur cron starten. Es ist auch schwer, Maikai Python zu starten. Es gibt auch einen Teil von Python, der Wiederholungen zählt. Ich weiß auch, dass es ungefähr 10.000 Mal funktioniert, ohne zu fallen, also versucht cron, Leben und Tod zu überwachen.
Übrigens mit 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
Es gibt einen Teil des obigen Skripts, der -T 59 sagt. Dies bedeutet, 59 Sekunden in einer Endlosschleife im Python-Skript zu schlafen. Es gibt geringfügige Schwankungen wie die Zeit zum Erfassen von Daten mit BLE und die Kommunikationszeit mit AWS. Es kann möglich sein, ein Echtzeit-Betriebssystem zu verwenden und jede Minute einen Interrupt usw. einzufügen. Da es aus Raspetorte, Raspbian Jessie und Python besteht, wird beurteilt, dass es unmöglich ist, diesen Punkt zu erreichen, und es ist teilbar. Sie können tatsächlich Zeitreihendaten abrufen, jedoch nicht genau jede Minute. Die Daten zeigen, dass es einige Sekunden Jitter gibt.
AWS PaaS Die AWS-Seite wird ohne Server erstellt. Zum Lernen.
AWS IoT Ich habe auf diese Seite verwiesen. http://qiita.com/nsedo/items/c6f33c7cadea7023403f Der Rest ist immer noch das AWS SDK. MQTT erstellt und wirft nur JSON, sodass ich mir keine Gedanken über die Verwendung von AWS IoT machen musste. Es empfängt die JSON-Daten und legt sie unverändert in DynamoDB ab.
DynamoDB
Schlüssel ist ID-Name und Zeitinformation. Ich habe irgendwo gehört, dass diese Methode beim Erstellen von Zeitreihendaten mit IoT gut ist. Andere sind wie das Anordnen der erfassten Daten. Bei der Analyse von Zeitreihendaten ordnen wir nur unstrukturierte Daten in der DB an. Wie Sie sehen können, gibt es einen Ort, an dem ich einen JSON erstellt und ihn so geworfen habe, wie er ist.
Der Schlüssel ist nur der ID-Name. Wert ist --Data: Der zuletzt empfangene JSON wie er ist --isNight: Geben Sie an, ob das Licht eingeschaltet ist oder nicht ist. Dieses Mal beschäftigen wir uns nur mit Änderungen der Lichtinformationen, also halten wir nur diesen Zustand. Um die Lambda-Seite zustandslos zu machen, richtet sich der Staat an DynamoDB.
Nur für den Fall, wenn Sie es in JSON schreiben, werden es solche Daten sein. Für die Zeitreihen-DB werden nur die folgenden Datenelemente ausgerichtet.
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 Es ist peinlich, weil es viele Orte gibt, die nicht richtig geschrieben sind. .. .. Der Teil von Exception und der Inhalt kommen zu Slack als einem Ort, an dem ich keine Schlussfolgerung ziehen kann, während ich mir Sorgen mache. Ich bin mir nicht sicher, was ich in einem solchen Fall tun soll. Ich möchte nicht wiederholt ausgeführt werden, wenn eine Ausnahme in Lambda auftritt, oder ich möchte Slack nicht wiederholt einen Fehler senden, also habe ich mich gefragt, was ich tun soll, aber ich habe es zu einer zukünftigen Aufgabe gemacht. Ich kenne auch die Spezifikationen von Lambda nicht.
Es ist auch fast angemessen, einen Raumtisch zu bauen, der den Zustand des Raumes festhält. Schlüssel: Rasppie ID, hier "Raspi_1" Daten: Alle Daten von SensorTag, Kita Licht: Wird der aktuelle Status als "Ein" oder "Aus" bestimmt? Es ist ein Design.
slackpost Hier empfängt Lambda die Daten aus dem dynamoDB-Stream. Holen Sie sich Light-Informationen aus der ETLRoom-Tabelle und vergleichen Sie sie mit der aktuellen Es bestimmt, ob das Licht ein- oder ausgeschaltet ist, und lässt es nach, wenn es sich ändert.
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 = "" \
+ "Die aktuelle Temperatur ist" + temp + "Grad, Luftfeuchtigkeit"+ humid + "Grad.\n" \
+ "Helligkeit" + light + "In Lux ist der Druck"+ balo + "Es ist hPa.\n" \
+ "(" + time + "Gemessen an:bar_chart:)"
#Wenn es ein ALL-Argument gibt, wenn es ein Argument gibt, sichern Sie alle Daten
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 Eingehende WebHooks und ausgehende WebHooks wurden zu benutzerdefinierten Integrationen hinzugefügt. Jetzt können Sie die Benutzeroberfläche verwenden. Super praktisch.
Incoming WebHooks https://hooks.slack.com/services/XXXXXXX/YYYYYYY/ZZZZZZ Generieren Sie die URL für die Veröffentlichung in Slack und legen Sie sie fest. Alles was Sie tun müssen, ist Post von Lambda. Sie können erkennen, ob es ein- oder ausgeschaltet ist. Das Folgende ist ein Beispiel.
<img width = "556" alt = "Screenshot 2017-09-05 21.07.36.png " src = "https://qiita-image-store.s3.amazonaws.com/0/171122/6fd49e45-4271" -642d-f5a4-045833411c15.png ">
Wenn wir uns das ansehen, können wir Folgendes sehen.
Outgoing WebHooks Trigger Word Das Auslösewort ist "jetzt". Senden Sie "jetzt" und NowBot teilt Ihnen den Status des Raums mit.
<img width = "389" alt = "Screenshot 2017-09-05 20.07.48.png " src = "https://qiita-image-store.s3.amazonaws.com/0/171122/94beb1e4-359a" -45f4-a6bb-01d02354fd63.png ">
Wenn Sie "now ALL" senden, werden alle Daten in der Datenbank gespeichert. Zum Debuggen.
<img width = "437" alt = "Screenshot 2017-09-05 20.11.08.png " src = "https://qiita-image-store.s3.amazonaws.com/0/171122/20954556-c401" -142f-e816-d6a2ade8f29b.png ">
Ich habe darüber nachgedacht, den Wortlaut nach "jetzt" in natürlicher Sprache (wie Amazon Polly) zu verarbeiten und zurückzugeben, wenn es einen verwandten Parameter gibt (wie z. B. Temperatur), aber das wird die nächste Gelegenheit sein. .. ..
URL https://XXXXXX.execute-api.ap-northeast-1.amazonaws.com/prod/getroomenv Ist eingestellt. Es wurde mit AWS API Gateway erstellt, dahinter Lambda. Das getroomenv-Skript wird ausgeführt.
Es dauerte ungefähr 3 Tage, um sich den ganzen Weg zu bewegen Danach habe ich ungefähr zwei Tage lang aufgefrischt und die Situation beobachtet. Ich brauche etwas mehr Kouo für DynamoDB und Lambda. Es war eine gute Studie. Die Verwendung von Slack für die Benutzeroberfläche war eine gute Antwort. Wirklich praktisch.
Da ich Zeitreihendaten gesammelt habe, Verwenden Sie diese Option, um die periodische Fluktuationskomponente zu erfassen Wenn Sie Slack benachrichtigen können, wenn ungewöhnliche Bewegungen auftreten, Ich denke, es ist gut als nächste Entwicklung. Legen Sie fest, ob es normal ist, dass Menschen samstags zur Arbeit kommen. .. ..
Recommended Posts