Datenmanagement nach der Supercity-Methode ist ein heißes Thema auf der Straße. Lassen Sie uns als Indoor-Sekte die Welle der Zeit im Haus vor der Stadt reiten! Ich dachte, ** Wir haben einen Mechanismus erstellt, mit dem eine große Menge von Sensordaten in Echtzeit auf einem Dashboard angezeigt werden kann **.
** Ich bin der Meinung, dass wir ein System entwickelt haben, das Sensordaten auf stabile Weise visualisieren kann **.
Ich möchte versuchen, einen Artikel ohne einen Erklärungssprung zu erstellen, damit auch Anfänger ihn leicht verstehen können. ** Ich würde mich freuen, wenn Sie auf Sprünge oder Fehler hinweisen könnten! ** **.
** ・ RaspberryPi ** (diesmal wird Pi3 Modell B verwendet) ** ・ Python-Ausführungsumgebung ** (diesmal wird voreingestelltes Python 3.7.3 verwendet)
・ BAG-Typ des Omron-Umgebungssensors (2JCIE-BL01) ・ Omron-Umgebungssensor-USB-Typ (2JCIE-BU01) ・ [Inkbird IBS-TH1 mini](https://www.amazon.co.jp/Inkbird-%E3%83%96%E3%83%AB%E3%83%BC%E3%83%88%E3% 82% A5% E3% 83% BC% E3% 82% B9-% E6% B8% A9% E6% B9% BF% E5% BA% A6% E3% 82% BB% E3% 83% B3% E3% 82 % B5% E3% 83% BC-Android-IBS-TH1 / dp / B07D74CQLJ) ・ [Inkbird IBS-TH1](https://www.amazon.co.jp/Inkbird-IBS-TH1-Bluetooth-%E5%A4%96%E9%83%A8%E3%83%97%E3%83 % AD% E3% 83% BC% E3% 83% 96% E4% BB% 98% E3% 81% 8D-% E3% 82% B9% E3% 83% 9E% E3% 83% 9B% E3% 81% A8% E9% 80% A3% E6% 8E% A5% E3% 81% A7% E3% 81% 8D / dp / B07D75H734 / ref = pd_aw_sbs_201_3 / 356-7389076-5979439? Encoding = UTF8 & pd_rd_i = B07D_H 4e16-8524-c984893af9bd & pd_rd_w = Ip0MD & pd_rd_wg = lcDuD & pf_rd_p = bff3a3a6-0f6e-4187-bd60-25e75d4c1c8f & pf_rd_r = 939W116VQ9B4 ・ SwitchBot-Thermo-Hygrometer ・ Nature Remo
Die Gesamtstruktur ist in der folgenden Abbildung dargestellt. Die Pfeile repräsentieren den Datenfluss.
Da die langfristige Ausführung mit der manuellen Skriptausführung schwierig ist, haben wir einen Mechanismus für die automatische periodische Ausführung erstellt. Das Skript sensor_to_spreadsheet.py, das das Ganze steuert, wird regelmäßig vom Cron von Raspberry Pi ausgeführt, um Daten zu sammeln.
Grundsätzlich kommuniziert es mit Bluetooth Low Energy (BLE). BLE ist ein Standard, der zum De-facto-Standard für IoT-Heimgeräte wird. Hinweise werden in diesem Artikel auf leicht verständliche Weise zusammengefasst. [Details unten](https://qiita.com/c60evaporator/items/283d0569eba58830f86e#%E5%90%84%E7%A8%AE%E3%82%A8%E3%83%A9%E3%83%BC % E5% AF% BE% E5% BF% 9C), aber die Stabilisierung von BLE war das größte Hindernis für diese Entwicklung. Außerdem ist anstelle von BLE nur Nature Remo über WLAN verbunden.
Das Datenspeicherziel ist in zwei Systeme unterteilt: lokaler CSV-Speicher und Google Spreadsheets. CSV: Wird später zur Analyse verwendet Google Spreadsheets: Wird zur Visualisierung im Datenportal verwendet (ich wollte in der Cloud visualisieren) Ich habe es unter der Annahme geteilt, dass es verwendet wird, aber ich denke, es ist redundant, also denke ich, dass es in Ordnung ist, es in einem System zu vereinheitlichen. Beim Vereinheitlichen halte ich es für ideal, in einer Datenbank speichern zu können, auf die in der Cloud zugegriffen werden kann.
Das obige System wurde nach dem folgenden Verfahren aufgebaut ** ① Grundeinstellung des Sensors und Funktionsprüfung ** ** ② Sensordatenerfassungsklasse erstellen ** ** ③ Hauptskript erstellen ** ** ④ Drücken Sie die GAS-API von Python, um Daten in die Tabelle zu schreiben ** ** ⑤ Regelmäßige Ausführung des Skripts ** ** ⑥ Visualisierung durch Google Data Portal **
Details werden im nächsten Kapitel erklärt
Da ich den Artikel einzeln erstellt habe, führen Sie bitte die Ersteinstellung und Funktionsprüfung für jeden Sensor durch.
・ BAG-Typ des Omron-Umgebungssensors (2JCIE-BL01) ・ USB-Typ des Omron-Umgebungssensors (2JCIE-BU01) ・ Inkbird IBS-TH1 mini ・ Inkbird IBS-TH1 ・ SwitchBot-Thermo-Hygrometer ・ Nature Remo
Erstellen Sie eine Python-Klasse, um Sensordaten für jeden Sensortyp abzurufen. (Inkbird IBS-TH1 und IBS-TH1 mini werden in derselben Klasse erworben. ")
Darüber hinaus werden Module (.py-Dateien) vom Hersteller organisiert. Im Folgenden werden wir jedes Modul (nach Hersteller) in separaten Abschnitten erläutern.
Erstellen Sie eine Datenerfassungsklasse für Omron 2JCIE-BL01 und 2JCIE-BU01. Der Inhalt ist in der Regel [BAG-Typ](https://qiita.com/c60evaporator/items/ed2ffde4c87001111c12#%E3%82%BB%E3%83%B3%E3%82%B5%E6%B8%AC%E5%AE%9A% E5% 80% A4% E5% 8F% 96% E5% BE% 97% E7% 94% A8% E3% 82% AF% E3% 83% A9% E3% 82% B9% E3% 82% 92% E4% BD% 9C% E6% 88% 90) [USB-Typ](https://qiita.com/c60evaporator/items/ced01c241031c6c9c379#%E3%82%BB%E3%83%B3%E3%82%B5%E5%80%A4%E5%8F%96% E5% BE% 97% E3% 82% B9% E3% 82% AF% E3% 83% AA% E3% 83% 97% E3% 83% 88% E3% 81% AE% E4% BD% 9C% E6% 88% 90) Ist eine Kombination von
** ・ Aufrufmethode von außen BAG-Typ: ** Erstellen Sie eine Instanz der OmniBroadcastScanDelegate-Klasse und
Instanzname.scan(timeout)
Sie können die Sensordaten mit abrufen. Geben Sie für das Argument "Zeitüberschreitung" den Zeitüberschreitungswert für die Datenerfassung in Sekunden an (ungefähr 3 bis 5 Sekunden scheinen gut zu sein).
** USB-Typ: ** Sie können die Sensordaten mit der folgenden Methode abrufen
GetOmronConnectModeData().get_env_usb_data(macaddr)
Geben Sie für das Argument "macaddr" die Mac-Adresse des Sensors an.
** - Aktuelles Skript **
omron_env.py
# coding: UTF-8
from bluepy import btle
import struct
#Erfassungsdelegierter für Broadcast-Daten (BAG-Typ)
class OmronBroadcastScanDelegate(btle.DefaultDelegate):
#Konstrukteur
def __init__(self):
btle.DefaultDelegate.__init__(self)
#Variablen zum Speichern von Sensordaten
self.sensorValue = None
#Scan-Handler
def handleDiscovery(self, dev, isNewDev, isNewData):
#Wenn ein neues Gerät gefunden wird
if isNewDev or isNewData:
#Werbedaten extrahieren
for (adtype, desc, value) in dev.getScanData():
#Führen Sie den Datenabruf beim Umgebungssensor durch
if desc == 'Manufacturer' and value[0:4] == 'd502':
#Nehmen Sie den Sensortyp (EP oder IM) heraus.
sensorType = dev.scanData[dev.SHORT_LOCAL_NAME].decode(encoding='utf-8')
#Extraktion von Sensordaten während der EP
if sensorType == 'EP':
self._decodeSensorData_EP(value)
#Extraktion von Sensordaten während der IM
if sensorType == 'IM':
self._decodeSensorData_IM(value)
#Extrahieren Sie Sensordaten und konvertieren Sie sie in das Diktatformat (im EP-Modus).
def _decodeSensorData_EP(self, valueStr):
#Sensordaten aus der Zeichenfolge(6. und nachfolgende Zeichen)Nur herausnehmen und in Binär konvertieren
valueBinary = bytes.fromhex(valueStr[6:])
#Konvertieren Sie binäre Sensordaten in ganzzahliges Taple
(temp, humid, light, uv, press, noise, discomf, wbgt, rfu, batt) = struct.unpack('<hhhhhhhhhB', valueBinary)
#Nach Einheitenumrechnung im Diktattyp speichern
self.sensorValue = {
'SensorType': 'Omron_BAG_EP',
'Temperature': temp / 100,
'Humidity': humid / 100,
'Light': light,
'UV': uv / 100,
'Pressure': press / 10,
'Noise': noise / 100,
'Discomfort': discomf / 100,
'WBGT': wbgt / 100,
'BatteryVoltage': (batt + 100) / 100
}
#Extrahieren Sie Sensordaten und konvertieren Sie sie in das Diktatformat (im IM-Modus).
def _decodeSensorData_IM(self, valueStr):
#Sensordaten aus der Zeichenfolge(6. und nachfolgende Zeichen)Nur herausnehmen und in Binär konvertieren
valueBinary = bytes.fromhex(valueStr[6:])
#Konvertieren Sie binäre Sensordaten in ganzzahliges Taple
(temp, humid, light, uv, press, noise, accelX, accelY, accelZ, batt) = struct.unpack('<hhhhhhhhhB', valueBinary)
#Nach Einheitenumrechnung im Diktattyp speichern
self.sensorValue = {
'SensorType': 'Omron_BAG_IM',
'Temperature': temp / 100,
'Humidity': humid / 100,
'Light': light,
'UV': uv / 100,
'Pressure': press / 10,
'Noise': noise / 100,
'AccelerationX': accelX / 10,
'AccelerationY': accelY / 10,
'AccelerationZ': accelZ / 10,
'BatteryVoltage': (batt + 100) / 100
}
#Datenerfassungsklasse im Verbindungsmodus (USB-Typ)
class GetOmronConnectModeData():
def get_env_usb_data(self, macaddr):
peripheral = btle.Peripheral(macaddr, addrType=btle.ADDR_TYPE_RANDOM)
characteristic = peripheral.readCharacteristic(0x0059)
return self._decodeSensorData_EP(characteristic)
def _decodeSensorData_EP(self, valueBinary):
(seq, temp, humid, light, press, noise, eTVOC, eCO2) = struct.unpack('<Bhhhlhhh', valueBinary)
sensorValue = {
'SensorType': 'Omron_USB_EP',
'Temperature': temp / 100,
'Humidity': humid / 100,
'Light': light,
'Pressure': press / 1000,
'Noise': noise / 100,
'eTVOC': eTVOC,
'eCO2': eCO2
}
return sensorValue
Inkbird IBS-TH1 & IBS-TH1 mini Erstellen Sie eine Datenerfassungsklasse für Inkbird IBS-TH1 und IBS-TH1 mini. Der Inhalt ist in der Regel IBS-TH1 IBS-TH1 mini Ist eine Kombination von
** ・ Aufrufmethode von außen ** Inkbird_IBSTH1 und Inkbird_IBSTH1mini können beide Sensordaten mit der folgenden Methode erfassen
GetIBSTH1Data().get_ibsth1_data(macaddr, sensortype)
Geben Sie für das Argument "macaddr" die Mac-Adresse des Sensors und für "sensortype" "Inkbird_IBSTH1mini'or'Inkbird_IBSTH1" entsprechend dem Sensortyp an.
** - Aktuelles Skript **
inkbird_ibsth1.py
from bluepy import btle
import struct
#Inkbird IBS-TH1 Datenerfassungsklasse
class GetIBSTH1Data():
def get_ibsth1_data(self, macaddr, sensortype):
#Mit Gerät verbinden
peripheral = btle.Peripheral(macaddr)
#IBS-Wenn TH1 mini
if sensortype == 'Inkbird_IBSTH1mini':
characteristic = peripheral.readCharacteristic(0x002d)
return self._decodeSensorData_mini(characteristic)
#IBS-Bei TH1
elif sensortype == 'Inkbird_IBSTH1':
characteristic = peripheral.readCharacteristic(0x28)
return self._decodeSensorData(characteristic)
else:
return None
#IBS-TH1 mini
def _decodeSensorData_mini(self, valueBinary):
(temp, humid, unknown1, unknown2, unknown3) = struct.unpack('<hhBBB', valueBinary)
sensorValue = {
'SensorType': 'Inkbird_IBSTH1mini',
'Temperature': temp / 100,
'Humidity': humid / 100,
'unknown1': unknown1,
'unknown2': unknown2,
'unknown3': unknown3,
}
return sensorValue
#IBS-TH1
def _decodeSensorData(self, valueBinary):
(temp, humid, unknown1, unknown2, unknown3) = struct.unpack('<hhBBB', valueBinary)
sensorValue = {
'SensorType': 'Inkbird_IBSTH1',
'Temperature': temp / 100,
'Humidity': humid / 100,
'unknown1': unknown1,
'unknown2': unknown2,
'unknown3': unknown3,
}
return sensorValue
Erstellen Sie eine Datenerfassungsklasse für das SwitchBot-Thermohygrometer. Der Inhalt ist [hier](https://qiita.com/c60evaporator/items/7c3156a6bbb7c6c59052#%E3%82%BB%E3%83%B3%E3%82%B5%E5%80%A4%E5%8F%96 % E5% BE% 97% E3% 82% B9% E3% 82% AF% E3% 83% AA% E3% 83% 97% E3% 83% 88% E3% 81% AE% E4% BD% 9C% E6 Gleich wie% 88% 90)
** ・ Aufrufmethode von außen ** Erstellen Sie eine Instanz der SwitchbotScanDelegate-Klasse
Instanzname.scan(timeout)
Sie können die Sensordaten mit abrufen. Geben Sie für das Argument "Zeitüberschreitung" den Zeitüberschreitungswert für die Datenerfassung in Sekunden an (ungefähr 5 Sekunden scheinen gut zu sein).
** - Aktuelles Skript **
switchbot.py
from bluepy import btle
import struct
#Delegierter für Broadcast-Datenerfassung
class SwitchbotScanDelegate(btle.DefaultDelegate):
#Konstrukteur
def __init__(self, macaddr):
btle.DefaultDelegate.__init__(self)
#Variablen zum Speichern von Sensordaten
self.sensorValue = None
self.macaddr = macaddr
#Scan-Handler
def handleDiscovery(self, dev, isNewDev, isNewData):
#Wenn Sie ein Gerät mit der Ziel-Mac-Adresse finden
if dev.addr == self.macaddr:
#Werbedaten extrahieren
for (adtype, desc, value) in dev.getScanData():
#Führen Sie den Datenabruf beim Umgebungssensor durch
if desc == '16b Service Data':
#Sensordaten extrahieren
self._decodeSensorData(value)
#Extrahieren Sie Sensordaten und konvertieren Sie sie in das Diktatformat
def _decodeSensorData(self, valueStr):
#Sensordaten aus der Zeichenfolge(4. und nachfolgende Zeichen)Nur herausnehmen und in Binär konvertieren
valueBinary = bytes.fromhex(valueStr[4:])
#Konvertieren Sie binäre Sensordaten in numerische Werte
batt = valueBinary[2] & 0b01111111
isTemperatureAboveFreezing = valueBinary[4] & 0b10000000
temp = ( valueBinary[3] & 0b00001111 ) / 10 + ( valueBinary[4] & 0b01111111 )
if not isTemperatureAboveFreezing:
temp = -temp
humid = valueBinary[5] & 0b01111111
#In Diktat speichern
self.sensorValue = {
'SensorType': 'SwitchBot',
'Temperature': temp,
'Humidity': humid,
'BatteryVoltage': batt
}
Nature Remo Neben Sensordaten werden auch Klimadaten über die Fernbedienung erfasst.
** ・ Aufrufmethode von außen Sensordatenerfassung: ** Sie können die Sensordaten mit der folgenden Methode abrufen
get_sensor_data(Token, API_URL)
Das Argument "Token" ist das Remo-Zugriffstoken (Referenz) und das "API_URL" ist "https://api.nature.global/" (behoben). Wert)
** Datenerfassung der Klimaanlage **: Sie können die Sensordaten mit der folgenden Methode abrufen
get_aircon_data(Token, API_URL)
Die Argumente sind dieselben wie beim Erfassen von Sensordaten
** - Aktuelles Skript **
remo.py
import json
import requests
import glob
import pandas as pd
#Remo Datenerfassungsklasse
class GetRemoData():
def get_sensor_data(self, Token, API_URL):
headers = {
'accept': 'application/json',
'Authorization': 'Bearer ' + Token,
}
response = requests.get(f"{API_URL}/1/devices", headers=headers)
rjson = response.json()
return self._decodeSensorData(rjson)
def get_aircon_data(self, Token, API_URL):
headers = {
'accept': 'application/json',
'Authorization': 'Bearer ' + Token,
}
response = requests.get(f"{API_URL}/1/appliances", headers=headers)
rjson = response.json()
return self._decodeAirconData(rjson)
def calc_human_motion(self, Human_last, csvdir):
filelist = glob.glob(f"{csvdir}/*/*.csv")
if len(filelist) == 0:
return 0
filelist.sort()
df = pd.read_csv(filelist[-1])
if df.Human_last[len(df) - 1] != Human_last:
return 1
else:
return 0
#Extrahieren Sie Sensordaten und konvertieren Sie sie in das Diktatformat
def _decodeSensorData(self, rjson):
for device in rjson:
#Wählen Sie Remo (um zu vermeiden, dass Sie versehentlich Remo E auswählen).
if device['firmware_version'].split('/')[0] == 'Remo':
sensorValue = {
'SensorType': 'Remo_Sensor',
'Temperature': device['newest_events']['te']['val'],
'Humidity': device['newest_events']['hu']['val'],
'Light': device['newest_events']['il']['val'],
'Human_last': device['newest_events']['mo']['created_at']
}
return sensorValue
#Nehmen Sie die Daten der Klimaanlage heraus und konvertieren Sie sie in das Diktatformat
def _decodeAirconData(self, rjson):
for appliance in rjson:
if appliance['type'] == 'AC':
Value = {
'TempSetting': appliance['settings']['temp'],
'Mode': appliance['settings']['mode'],
'AirVolume': appliance['settings']['vol'],
'AirDirection': appliance['settings']['dir'],
'Power': appliance['settings']['button']
}
break
return Value
Rufen Sie die Klasse (2) auf und erstellen Sie ein Hauptskript, um Sensordaten zu erfassen. Die Struktur ist in die folgenden 5 Arten von Methoden und Strukturen unterteilt
** 1) Methode zur Ausführung der Klasse ② ** getdata_omron_bag (Gerät): Ermittelt den Sensorwert des BAG-Typs des Omron-Umgebungssensors getdata_omron_usb (Gerät): Omron-Umgebungssensor USB-Typ-Sensorwerterfassung getdata_ibsth1 (Gerät): Liefert den Sensorwert von Inkbird IBS-TH1 & IBS-TH1 mini getdata_switchbot_thermo (Gerät): Sensorwert des SwitchBot-Thermo-Hygrometers abrufen getdata_remo (Gerät, csvpath): Abrufen des Nature Remo-Sensorwerts und der Daten der Klimaanlage Die Bedeutung des Arguments ist wie folgt Gerät: Geben Sie die aus DeviceList.csv gelesene Zeile an. csvpath: CSV-gespeicherter Pfad (Remos menschlicher Sensor muss mit CSV-gespeicherten früheren Daten verglichen werden)
** 2) CSV-Ausgabemethode ** output_csv (data, csvpath): Gibt die Messwertdaten für jeden Sensor im CSV-Format aus. Die Bedeutung des Arguments ist wie folgt Daten: Vom Sensor nach der Methode von 1 erfasste Daten csvpath: CSV-Speicherpfad
** 3) SpreadSheet-Ausgabemethode ** output_spreadsheet (all_values_dict): Sendet die Messdaten aller Sensoren in das Arbeitsblatt. Die Bedeutung des Arguments ist wie folgt all_values_dict: Daten aller Sensoren im Diktatformat (unter der Annahme der folgenden Struktur)
all_values_dict = {
Sensorname 1:{
Spaltenname 1:Wert 1,
Spaltenname 2:Wert 2,
:
},
Sensorname 2:{
Spaltenname 1:Wert 1,
Spaltenname 2:Wert 2,
:
},
:
}
** 4) Neustartmethode des Bluetooth-Adapters ** restart_hci0 (Gerätename): Dies ist die unten beschriebene Gegenmaßnahmenmethode "Bug 2". Die Bedeutung des Arguments ist wie folgt Gerätename: Gerätename (wird für die Protokollausgabe verwendet)
** 5) Hauptverarbeitungsteil if name == After'main ': Führt eine Reihe von Prozessen aus, wie im folgenden Ablauf gezeigt.
Als die Anzahl der Sensoren zunahm und ich das Gefühl hatte, dass die Verwaltung mit In-Code begrenzt ist, erstellte ich die folgenden zwei Arten von Einstellungsdateien. ** ・ DeviceList.csv: Beschreiben Sie die erforderlichen Informationen für jeden Sensor **
DeviceList.csv
DeviceName,SensorType,MacAddress,Timeout,Retry,Offset_Temp,Offset_Humid,API_URL,Token
SwitchBot_Thermo1,SwitchBot_Thermo,[SwitchBot Mac-Adresse],4,3,0,0,,
Inkbird_IBSTH1_Mini1,Inkbird_IBSTH1mini,[IBS-TH1 Mini-Mac-Adresse],0,2,0,0,,
Inkbird_IBSTH1_1,Inkbird_IBSTH1,[IBS-TH1 Mac-Adresse],0,2,0,0,,
Remo1,Nature_Remo,,0,2,0,0,https://api.nature.global/,[Nature Remo Zugriffstoken]
Omron_USB1,Omron_USB_EP,[Omron USB-Typ Mac-Adresse],0,2,0,0,,
Omron_BAG1,Omron_BAG_EP,[Omron BAG Typ Mac Adresse],3,2,0,0,,
Die Bedeutung der Spalten ist wie folgt Gerätename: Verwaltet Gerätenamen und identifiziert, wenn mehrere Sensoren desselben Typs vorhanden sind Sensortyp: Der Sensortyp. Trennen Sie die auszuführenden Sensordatenerfassungsklassen nach diesem Wert MacAddress: MAC-Adresse des Sensors Timeout: Timeout-Wert zum Zeitpunkt des Scannens (nur für Sensoren im Broadcast-Modus) Wiederholung: Maximale Anzahl von Wiederholungen [Details](https://qiita.com/c60evaporator/items/283d0569eba58830f86e#%E4%B8%8D%E5%85%B7%E5%90%881peripheral%E3%81%AE%E5 % 88% 9D% E6% 9C% 9F% E5% 8C% 96% E6% 99% 82% E3% 81% AB% E3% 82% A8% E3% 83% A9% E3% 83% BC% E3% 81 % 8C% E5% 87% BA% E3% 82% 8B) Offset_Temp: Temperaturversatzwert (derzeit nicht verwendet) Offset_Humid: Feuchtigkeitsoffsetwert (derzeit nicht verwendet) API_URL: API-URL (nur für Nature Remo verwendet) Token: Zugriffstoken (nur für Nature Remo verwendet)
** ・ config.ini: CSV angeben und Ausgabeverzeichnis protokollieren **
config.ini
[Path]
CSVOutput = /share/Data/Sensor
LogOutput = /share/Log/Sensor
Beide sind praktisch, da von außerhalb des Raspberry Pi auf sie zugegriffen werden kann, indem sie in dem von [samba] erstellten freigegebenen Ordner (https://qiita.com/k-Mata/items/8bee9e02e74565b6c147) ausgegeben werden.
sensors_to_spreadsheet.py
from bluepy import btle
from omron_env import OmronBroadcastScanDelegate, GetOmronConnectModeData
from inkbird_ibsth1 import GetIBSTH1Data
from switchbot import SwitchbotScanDelegate
from remo import GetRemoData
from datetime import datetime, timedelta
import os
import csv
import configparser
import pandas as pd
import requests
import logging
import subprocess
#Globale Variablen
global masterdate
######Erfassung von Werten für den OMRON-Umgebungssensor (BAG-Typ)######
def getdata_omron_bag(device):
#Maximales Gerät, wenn kein Wert verfügbar ist.Wiederholen Wiederholen Sie den Scan
for i in range(device.Retry):
#omron_Stellen Sie den Delegierten für die Sensorwerterfassung von env so ein, dass er zur Scan-Zeit ausgeführt wird
scanner = btle.Scanner().withDelegate(OmronBroadcastScanDelegate())
#Scannen, um den Sensorwert zu erhalten
try:
scanner.scan(device.Timeout)
#Wenn beim Scannen eine Fehlermeldung angezeigt wird, starten Sie den Bluetooth-Adapter neu
except:
restart_hci0(device.DeviceName)
#Beenden Sie die Schleife, wenn der Wert erhalten werden kann
if scanner.delegate.sensorValue is not None:
break
#Wenn der Wert nicht abgerufen werden kann, schreiben Sie ihn in das Protokoll
else:
logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}, timeout{device.Timeout}]')
#Wenn der Wert erhalten werden kann, speichern Sie die POST-Daten in dict
if scanner.delegate.sensorValue is not None:
#Daten zu POST
data = {
'DeviceName': device.DeviceName,
'Date_Master': str(masterdate),
'Date': str(datetime.today()),
'Temperature': str(scanner.delegate.sensorValue['Temperature']),
'Humidity': str(scanner.delegate.sensorValue['Humidity']),
'Light': str(scanner.delegate.sensorValue['Light']),
'UV': str(scanner.delegate.sensorValue['UV']),
'Pressure': str(scanner.delegate.sensorValue['Pressure']),
'Noise': str(scanner.delegate.sensorValue['Noise']),
'BatteryVoltage': str(scanner.delegate.sensorValue['BatteryVoltage'])
}
return data
#Wenn der Wert nicht abgerufen werden konnte, protokollieren Sie die Ausgabe und starten Sie den Bluetooth-Adapter neu
else:
logging.error(f'cannot get data [date{str(masterdate)}, device{device.DeviceName}, timeout{device.Timeout}]')
restart_hci0(device.DeviceName)
return None
######Datenerfassung des OMRON-Umgebungssensors (USB-Typ)######
def getdata_omron_usb(device):
#Maximales Gerät, wenn kein Wert verfügbar ist.Wiederholen Wiederholen Sie den Scan
for i in range(device.Retry):
try:
sensorValue = GetOmronConnectModeData().get_env_usb_data(device.MacAddress)
#Protokollausgabe, wenn ein Fehler auftritt
except:
logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}]')
sensorValue = None
continue
else:
break
#Wenn der Wert erhalten werden kann, speichern Sie die POST-Daten in dict
if sensorValue is not None:
#Daten zu POST
data = {
'DeviceName': device.DeviceName,
'Date_Master': str(masterdate),
'Date': str(datetime.today()),
'Temperature': str(sensorValue['Temperature']),
'Humidity': str(sensorValue['Humidity']),
'Light': str(sensorValue['Light']),
'Pressure': str(sensorValue['Pressure']),
'Noise': str(sensorValue['Noise']),
'eTVOC': str(sensorValue['eTVOC']),
'eCO2': str(sensorValue['eCO2'])
}
return data
#Wenn der Wert nicht abgerufen werden konnte, protokollieren Sie die Ausgabe und starten Sie den Bluetooth-Adapter neu
else:
logging.error(f'cannot get data [loop{str(device.Retry)}, date{str(masterdate)}, device{device.DeviceName}]')
restart_hci0(device.DeviceName)
return None
######Inkbird IBS-TH1 Datenerfassung######
def getdata_ibsth1(device):
#Maximales Gerät, wenn kein Wert verfügbar ist.Wiederholen Wiederholen Sie den Scan
for i in range(device.Retry):
try:
sensorValue = GetIBSTH1Data().get_ibsth1_data(device.MacAddress, device.SensorType)
#Protokollausgabe, wenn ein Fehler auftritt
except:
logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}]')
sensorValue = None
continue
else:
break
if sensorValue is not None:
#Daten zu POST
data = {
'DeviceName': device.DeviceName,
'Date_Master': str(masterdate),
'Date': str(datetime.today()),
'Temperature': str(sensorValue['Temperature']),
'Humidity': str(sensorValue['Humidity']),
}
return data
#Wenn der Wert nicht abgerufen werden konnte, protokollieren Sie die Ausgabe und starten Sie den Bluetooth-Adapter neu
else:
logging.error(f'cannot get data [loop{str(device.Retry)}, date{str(masterdate)}, device{device.DeviceName}]')
restart_hci0(device.DeviceName)
return None
######SwitchBot-Thermohygrometer-Datenerfassung######
def getdata_switchbot_thermo(device):
#Maximales Gerät, wenn kein Wert verfügbar ist.Wiederholen Wiederholen Sie den Scan
for i in range(device.Retry):
#Stellen Sie den Switchbot-Sensorwert-Erfassungsdelegierten ein
scanner = btle.Scanner().withDelegate(SwitchbotScanDelegate(str.lower(device.MacAddress)))
#Scannen, um den Sensorwert zu erhalten
try:
scanner.scan(device.Timeout)
#Wenn beim Scannen eine Fehlermeldung angezeigt wird, starten Sie den Bluetooth-Adapter neu
except:
restart_hci0(device.DeviceName)
#Beenden Sie die Schleife, wenn der Wert erhalten werden kann
if scanner.delegate.sensorValue is not None:
break
#Wenn der Wert nicht abgerufen werden kann, schreiben Sie ihn in das Protokoll
else:
logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}, timeout{device.Timeout}]')
#Wenn der Wert erhalten werden kann, speichern Sie die POST-Daten in dict
if scanner.delegate.sensorValue is not None:
#Daten zu POST
data = {
'DeviceName': device.DeviceName,
'Date_Master': str(masterdate),
'Date': str(datetime.today()),
'Temperature': str(scanner.delegate.sensorValue['Temperature']),
'Humidity': str(scanner.delegate.sensorValue['Humidity']),
'BatteryVoltage': str(scanner.delegate.sensorValue['BatteryVoltage'])
}
return data
#Wenn nicht, geben Sie das Protokoll aus und starten Sie den Bluetooth-Adapter neu
else:
logging.error(f'cannot get data [loop{str(device.Retry)}, date{str(masterdate)}, device{device.DeviceName}, timeout{device.Timeout}]')
restart_hci0(device.DeviceName)
return None
######Nature Remo Datenerfassung######
def getdata_remo(device, csvpath):
#Maximales Gerät, wenn der Sensordatenwert nicht verfügbar ist.Wiederholen Wiederholen Sie den Scan
for i in range(device.Retry):
try:
sensorValue = GetRemoData().get_sensor_data(device.Token, device.API_URL)
#Protokollausgabe, wenn ein Fehler auftritt
except:
logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}, sensor]')
sensorValue = None
continue
else:
break
#Maximales Gerät, wenn der Datenwert der Klimaanlage nicht verfügbar ist.Wiederholen Wiederholen Sie den Scan
for i in range(device.Retry):
try:
airconValue = GetRemoData().get_aircon_data(device.Token, device.API_URL)
#Protokollausgabe, wenn ein Fehler auftritt
except:
logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}, aircon]')
sensorValue = None
continue
else:
break
#Wenn der Wert erhalten werden kann, speichern Sie die POST-Daten in dict
if sensorValue is not None:
#Sensordaten
data = {
'DeviceName': device.DeviceName,
'Date_Master': str(masterdate),
'Date': str(datetime.today()),
'Temperature': str(sensorValue['Temperature']),
'Humidity': str(sensorValue['Humidity']),
'Light': str(sensorValue['Light']),
'Human_last': str(sensorValue['Human_last']),
'HumanMotion': GetRemoData().calc_human_motion(sensorValue['Human_last'], f'{csvpath}/{device.DeviceName}')
}
#Daten der Klimaanlage
if airconValue is not None:
data['TempSetting'] = airconValue['TempSetting']
data['AirconMode'] = airconValue['Mode']
data['AirVolume'] = airconValue['AirVolume']
data['AirDirection'] = airconValue['AirDirection']
data['AirconPower'] = airconValue['Power']
return data
#Wenn nicht, Protokollausgabe (Da dies über WLAN erfolgt, wird der Bluetooth-Adapter nicht neu gestartet.)
else:
logging.error(f'cannot get data [loop{str(device.Retry)}, date{str(masterdate)}, device{device.DeviceName}]')
return None
######CSV-Ausgabe von Daten######
def output_csv(data, csvpath):
dvname = data['DeviceName']
monthstr = masterdate.strftime('%Y%m')
#Name des Ausgabeordnerordners
outdir = f'{csvpath}/{dvname}/{masterdate.year}'
#Wenn der Ausgabezielordner nicht vorhanden ist, erstellen Sie einen neuen
os.makedirs(outdir, exist_ok=True)
#Pfad der Ausgabedatei
outpath = f'{outdir}/{dvname}_{monthstr}.csv'
#Erstellen Sie eine neue Ausgabedatei, wenn diese nicht vorhanden ist
if not os.path.exists(outpath):
with open(outpath, 'w') as f:
writer = csv.DictWriter(f, data.keys())
writer.writeheader()
writer.writerow(data)
#Fügen Sie eine Zeile hinzu, wenn die Ausgabedatei vorhanden ist
else:
with open(outpath, 'a') as f:
writer = csv.DictWriter(f, data.keys())
writer.writerow(data)
######Prozess des Hochladens in eine Google-Tabelle######
def output_spreadsheet(all_values_dict):
#API-URL
url = 'Hier aufgeführte GAS-API-URL'
#POST-Daten an API
response = requests.post(url, json=all_values_dict)
print(response.text)
######Neustart des Bluetooth-Adapters######
def restart_hci0(devicename):
passwd = 'Geben Sie das RaspberryPi-Passwort ein'
subprocess.run(('sudo','-S','hciconfig','hci0','down'), input=passwd, check=True)
subprocess.run(('sudo','-S','hciconfig','hci0','up'), input=passwd, check=True)
logging.error(f'restart bluetooth adapter [date{str(masterdate)}, device{devicename}]')
######Maine######
if __name__ == '__main__':
#Startzeit bekommen
startdate = datetime.today()
#Runden Sie die Startzeit in Minuten ab
masterdate = startdate.replace(second=0, microsecond=0)
if startdate.second >= 30:
masterdate += timedelta(minutes=1)
#Konfigurationsdatei und Geräteliste lesen
cfg = configparser.ConfigParser()
cfg.read('./config.ini', encoding='utf-8')
df_devicelist = pd.read_csv('./DeviceList.csv')
#Gesamtzahl der Sensoren und erfolgreiche Datenerfassung
sensor_num = len(df_devicelist)
success_num = 0
#Protokollinitialisierung
logname = f"/sensorlog_{str(masterdate.strftime('%y%m%d'))}.log"
logging.basicConfig(filename=cfg['Path']['LogOutput'] + logname, level=logging.INFO)
#Diktat für das Speichern aller erfassten Daten
all_values_dict = None
######Datenerfassung für jedes Gerät######
for device in df_devicelist.itertuples():
#BAG-Typ des Omron-Umgebungssensors (BroadCast-Verbindung)
if device.SensorType in ['Omron_BAG_EP','Omron_BAG_IM']:
data = getdata_omron_bag(device)
#Omron-Umgebungssensor USB-Typ (Verbindung im Verbindungsmodus)
elif device.SensorType in ['Omron_USB_EP','Omron_USB_IM']:
data = getdata_omron_usb(device)
#Inkbird IBS-TH1
elif device.SensorType in ['Inkbird_IBSTH1mini','Inkbird_IBSTH1']:
data = getdata_ibsth1(device)
#SwitchBot Thermo-Hygrometer
elif device.SensorType == 'SwitchBot_Thermo':
data = getdata_switchbot_thermo(device)
#remo
elif device.SensorType == 'Nature_Remo':
data = getdata_remo(device, cfg['Path']['CSVOutput'])
#Andere als die oben genannten
else:
data = None
#Wenn Daten vorhanden sind, fügen Sie sie zu Dict hinzu, um alle Daten zu speichern und CSV auszugeben
if data is not None:
#all_values_Erstellen Sie ein neues Wörterbuch, wenn dict Keine ist
if all_values_dict is None:
all_values_dict = {data['DeviceName']: data}
#all_values_Zum vorhandenen Wörterbuch hinzufügen, wenn dict nicht None ist
else:
all_values_dict[data['DeviceName']] = data
#CSV-Ausgabe
output_csv(data, cfg['Path']['CSVOutput'])
#Erfolgszahl plus
success_num+=1
######Prozess des Hochladens in eine Google-Tabelle######
output_spreadsheet(all_values_dict)
#Protokollausgabe des Verarbeitungsende
logging.info(f'[masterdate{str(masterdate)} startdate{str(startdate)} enddate{str(datetime.today())} success{str(success_num)}/{str(sensor_num)}]')
Schreiben Sie Daten von Raspberry Pi in eine Google-Tabelle.
Verwenden Sie auf der RaspberryPi-Seite die API-Ausführungsklasse von Python, um Daten und zu veröffentlichen Auf der Tabellenkalkulationsseite werden die oben genannten Daten mithilfe eines Google Apps Script-Skripts empfangen.
Es scheint, dass nur die Funktion doPost zum Veröffentlichen der API verwendet werden kann. Sie müssen also alle Daten von Python auf einmal an GAS senden. Zu diesem Zweck habe ich ein Skript zum Posten im JSON-Format erstellt.
sensors_to_spreadsheet.Ein Teil von py
######Prozess des Hochladens in eine Google-Tabelle######
def output_spreadsheet(all_values_dict):
#API-URL
url = 'Hier aufgeführte GAS-API-URL'
#POST-Daten an API
response = requests.post(url, json=all_values_dict)
print(response.text)
Der Punkt ist, dass der Wert, der nach "request.post (url, json =" übergeben wird, diktiert ist. (Konvertieren Sie die Zeichenfolge nicht mit json.dumps (). Es macht süchtig, sie zu übergeben, ohne sie in json zu konvertieren, obwohl der Name json = ... lautet.)
Gehen Sie zu Google Spreadsheets und gehen Sie zu Erstellen Sie eine Tabelle wie unten gezeigt.
Der Blattname lautet "SensorData". Im nächsten Abschnitt erstellen wir ein Skript, das Daten auf diesem Blatt gleichzeitig an alle Sensoren ausgibt.
Wählen Sie in der Tabelle "Extras" -> "Skript-Editor" Erstellen Sie ein GAS-Skript wie das folgende
postSensorData.gs
var spreadsheetId = '******'//← Geben Sie die Tabellenkalkulations-ID ein
//Eine Methode, die gebuchte Daten empfängt und in eine Tabelle schreibt
function doPost(e){
//Informationen zur Tabelle abrufen (Blattinstanz, Liste der Headernamen, Anzahl der Zeilen)
var sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName('SensorData');
var headerList = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
var lastRow = sheet.getLastRow();
//Konvertieren Sie empfangene Daten in JSON
var json = JSON.parse(e.postData.getDataAsString());
//Halten Sie die Anzahl der Geräte
var devCnt = 0;
for(var device in json) {
//1 Gerät hinzufügen
devCnt++;
//Scannen Sie alle Sensormessungen
for(var colName in json[device]){
//Der Spaltenname lautet"DeviceName"Ignorieren Sie den Wert, wenn"device"(Erworben als)
if(colName == "DeviceName"){
//Der Spaltenname lautet"Date_Master"Wenn, ignorieren Sie den Wert mit Ausnahme des ersten Geräts (doppelte Beschreibung verhindern)
}else if(colName == "Date_Master" && devCnt >= 2){
//Schreiben Sie anders als oben beschrieben Daten in die Tabelle
}else{
headerList = addData(sheet, lastRow, headerList, device, colName, json[device][colName]);
}
}
}
//Gibt den Erfolg an den API-Executor zurück
return ContentService.createTextOutput(JSON.stringify({content:"post ok", endtime:new Date()})).setMimeType(ContentService.MimeType.JSON);
}
//Schreiben Sie Daten in eine Tabelle
function addData(sheet, lastRow, headerList, deviceName, colName, cell){
//Der Spaltenname lautet Datum_Schreiben Sie beim Master Daten und Daten, die nur in Datumsangaben unterteilt sind
if (colName == 'Date_Master'){
sheet.getRange(lastRow + 1, 1).setValue(cell);
sheet.getRange(lastRow + 1, 2).setValue(cell.substring(0, 10));
//Der Spaltenname lautet Datum_Wenn anders als Meister
}else{
//Spaltenname (kombiniert Gerätenamen und ursprünglichen Spaltennamen)
var newColName = deviceName + "_" + colName;
//Scannen Sie vorhandene Headernamen
for (i=0; i<headerList.length; i++) {
//Schreiben Sie Sensordaten in diese Spalte, wenn ein Headername vorhanden ist, der dem Spaltennamen entspricht
if(newColName == headerList[i]){
sheet.getRange(lastRow + 1, i + 1).setValue(cell);
break;
}
//Wenn der Spaltenname nicht im vorhandenen Headernamen enthalten ist, fügen Sie einen neuen Header hinzu und schreiben Sie Sensordaten
if(i == headerList.length - 1){
sheet.getRange(1, i + 2).setValue(newColName);
sheet.getRange(lastRow + 1, i + 2).setValue(cell);
headerList.push(newColName);
}
}
}
return headerList
}
** * Tabellenkalkulations-ID ** Die URL der Tabelle lautet "https://docs.google.com/spreadsheets/d/AAAAAA/edit#gid=0" Wenn ja, 「AAAAAA」 Der Teil von ist die ID der Tabelle.
[Hier](https://qiita.com/c60evaporator/items/ed2ffde4c87001111c12#python%E3%81%8B%E3%82%89gas%E3%81%AEapi%E3%82%92%E5%8F%A9% E3% 81% 84% E3% 81% A6% E3% 82% B9% E3% 83% 97% E3% 83% AC% E3% 83% 83% E3% 83% 89% E3% 82% B7% E3% 83% BC% E3% 83% 88% E3% 81% AB% E3% 83% 87% E3% 83% BC% E3% 82% BF% E6% 9B% B8% E3% 81% 8D% E8% BE% Bitte veröffentlichen Sie das GAS-Skript als API unter Bezugnahme auf BC% E3% 81% BF). Wenn Sie die am Ende angezeigte URL in den folgenden Teil des Hauptskripts sensor_to_spreadsheet.py einfügen,
sensors_to_spreadsheet.Ein Teil von py
def output_spreadsheet(all_values_dict):
#API-URL
url = 'Hier aufgeführte GAS-API-URL'
Das GAS-Skript wird aufgerufen, wenn sensor_to_spreadsheet.py ausgeführt wird Werte werden automatisch in die Tabelle geschrieben (die Überschrift wird ebenfalls automatisch hinzugefügt, wie im nächsten Abschnitt).
Wie in der folgenden Abbildung gezeigt Headername: [Gerätename_Bedeutung des Wertes] → Wird automatisch hinzugefügt, wenn er nicht vorhanden ist Daten: Fügen Sie am Ende des Messwerts, der dem obigen Header entspricht, eine Zeile hinzu. Wir haben ein GAS-Skript erstellt, damit Header generiert und Daten automatisch geschrieben werden können.
Es ist mühsam, das Skript jedes Mal auszuführen, um den Sensorwert zu erhalten Automatisieren Sie mit dem periodischen Paket "cron". Es sieht aus wie ein Windows-Taskplaner.
Es ist möglicherweise standardmäßig deaktiviert. Aktivieren Sie es daher unter hier.
** ・ Checkpoint 1: Überprüfen Sie die rsyslog.conf ** Es wird nicht funktionieren, wenn "cron" in /etc/rsyslog.conf auskommentiert ist. In meinem Fall wurde es wie folgt auskommentiert
###############
#### RULES ####
###############
#
# First some standard log files. Log by facility.
#
auth,authpriv.* /var/log/auth.log
*.*;auth,authpriv.none -/var/log/syslog
#cron.* /var/log/cron.log
daemon.* -/var/log/daemon.log
Nach dem Kommentieren wie unten,
cron.* /var/log/cron.log
Starten Sie rsyslog mit dem folgenden Befehl neu.
sudo /etc/init.d/rsyslog restart
** ・ Prüfpunkt 2: Protokollstufe ändern ** Geben Sie in / etc / default / cron die Elemente an, die im Protokoll beschrieben werden sollen, wenn cron ausgeführt wird. Standardmäßig scheint das Protokoll nicht wie unten gezeigt ausgegeben zu werden
# For quick reference, the currently available log levels are:
# 0 no logging (errors are logged regardless)
# 1 log start of jobs
# 2 log end of jobs
# 4 log jobs with exit status != 0
# 8 log the process identifier of child process (in all logs)
#
#EXTRA_OPTS=""
Kommentieren Sie EXTRA_OPTS wie unten gezeigt aus
EXTRA_OPTS='-L 15'
Wird besorgt. Dies bedeutet, dass alle Ausgaben 1 + 2 + 4 + 8 = 15 sind.
Starten Sie cron mit dem folgenden Befehl neu.
sudo /etc/init.d/cron restart
Es ist erfolgreich, wenn cron.log in / var / log generiert wird. (Bitte überprüfen Sie hier, ob Sie den Ausführungsverlauf von cron sehen möchten.)
Registrieren Sie die regelmäßige Ausführung bei cron
** - crontab bearbeiten ** Öffnen Sie crontab mit dem folgenden Befehl
crontab -e
Wenn Sie gefragt werden, welcher Editor geöffnet werden soll, wählen Sie den gewünschten aus (Nano für Anfänger empfohlen).
# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
: Verschiedene Dinge gehen weiter
Es gibt verschiedene Kommentare wie oben geschrieben, Beschreiben Sie ganz am Ende das Timing und den Befehl, den Sie ausführen möchten. Das Timing-Format ist [hier](http://tech.junkpot.net/archives/721/crontab-%E3%81%A7%E7%B4%B0%E3%81%8B%E3%81%84%E3 % 82% B9% E3% 82% B1% E3% 82% B8% E3% 83% A5% E3% 83% BC% E3% 83% AB% E3% 81% AE% E8% A8% AD% E5% AE Bitte beziehen Sie sich auf% 9A.html)
Dieses Mal werden die folgenden Inhalte beschrieben, da sie alle 5 Minuten ausgeführt werden.
*/5 * * * * [Python voller Pfad] [sensors_to_spreadSheet.voller Weg von py] >/dev/null 2>&1
** ・ Cron starten ** Starten Sie cron mit dem folgenden Befehl
sudo /etc/init.d/cron start
Warten Sie eine Weile oben und wenn die Tabelle alle 5 Minuten aktualisiert wird, sind Sie fertig!
Google Data Portal ist ein Dashboard, das in der Cloud bearbeitet und angezeigt werden kann. In diesem Abschnitt wird am Anfang erläutert, wie der Teil des Temperaturübergangsdiagramms des Dashboards erstellt wird.
** Geben Sie eine Google-Tabelle als Datenverbindungsziel an ** ** Wenn Sie um Genehmigung gebeten werden, drücken Sie die Genehmigungstaste ** ** Sie werden nach dem Referenzblatt gefragt. Geben Sie daher die in ④ ** erstellte Tabelle an. ** Drücken Sie "Zum Bericht hinzufügen" ** ** Benennen Sie den Bericht um **
Erstellen Sie ein Diagramm, um langfristige Änderungen anzuzeigen
** Klicken Sie auf Ressourcen → Hinzugefügte Datenquellen verwalten ** ** Klicken Sie auf "Bearbeiten" für die Zieldatenquelle ** ** Ändern Sie Datum und Uhrzeit in ein erkennbares Format ** (Das Datenportal hat ein striktes Format zur Erkennung von Datum und Uhrzeit.) Wenn Sie die richtigen Statistiken wie den Durchschnitt erhalten möchten: JJJJMMTT Wenn Sie den Messwert für jede Zeile anzeigen möchten: JJJJMMTThhmm ** Wechsel zum Zeitreihendiagramm ** ** Geben Sie die Abmessung (horizontale Achse = Date_Master_Day) und den Index (vertikale Achse = Temperatur verschiedener Sensoren) an ** ** Ändern Sie die Temperaturstatistik von gesamt auf durchschnittlich ** ** Ändern, um fehlende Werte auszublenden **
Erstellen Sie ein Diagramm, um kurzfristige Änderungen anzuzeigen
** Zeitreihendiagramm hinzufügen ** ** Geben Sie die Abmessung (horizontale Achse = Date_Master_Day) und den Index (vertikale Achse = Temperatur verschiedener Sensoren) an ** ** Ändern Sie die Temperaturstatistik von gesamt auf durchschnittlich ** ** Ändern Sie fehlende Werte, um sie linear zu vervollständigen ** Damit ist der Temperaturübergangsgraph abgeschlossen!
Zu diesem Zeitpunkt ist nur der Teil des Öffnungs-Dashboards abgeschlossen, der von dem roten Rahmen darunter umgeben ist.
Zusätzlich zu dem Obigen ist es notwendig, ein "Feuchtigkeitsübergangsdiagramm" und ein "Kreisdiagramm" zu erstellen, um es zu vervollständigen. Ich werde es weglassen, weil es lange dauern wird, wenn ich alles erkläre.
Das Feuchtigkeitsübergangsdiagramm ändert nur den "Index" des Temperaturübergangsdiagramms von Temperatur zu Feuchtigkeit, daher denke ich, dass es ohne besondere Verstopfung erstellt werden kann.
Das Erstellen eines Kreisdiagramms finden Sie hier 86% E3% 82% B0% E3% 83% A9% E3% 83% 95) ist leicht verständlich zusammengefasst.
Es tut mir leid, dass ich 3 Minuten lang wie beim Kochen geflogen bin und am Anfang gesagt habe: "Ich werde nicht springen!". Wenn Sie Ihr Bestes tun, um das Diagramm zu vervollständigen und das Layout anzuordnen, können Sie das folgende Dashboard erstellen.
** Das ist es! !! ** **. Es kann einige Leute geben, die in "Wo ist der Supermarkt!" Anstürmen wollen, ohne irgendwelche seltsamen Teile. Persönlich war ich beeindruckt von der Kombination aus preisgünstigen Geräten und kostenlosen Diensten.
Es gab neue Entdeckungen wie den Anstieg der Luftfeuchtigkeit nach dem Kochen und die Temperatur, die mit den Jahreszeiten eine schöne Steigungskurve zeichnet. Ich denke also, dass das Finish nicht müde wird, zuzusehen!
Wie bei jedem System zeigt der Dauerbetrieb eine Vielzahl von Problemen, die bei einem einzigen Schuss nicht bemerkt wurden. In diesem Fall hat mich die ** Instabilität des Bluetooth-Empfangs ** besonders geärgert.
Nachdem jedes Problem einzeln behandelt wurde, wird es in verschiedenen Skripten wiedergegeben. Vor den Gegenmaßnahmen traten unzählige Stopps und fehlende Werte auf, aber mit den folgenden Gegenmaßnahmen wurde, obwohl fehlende Werte selten auftreten, diese auf ein Niveau verbessert, das nahezu wartungsfrei betrieben werden kann **!
inkbird_ibsth1.Ein Teil von py
def get_ibsth1_data(self, macaddr, sensortype):
#Mit Gerät verbinden
peripheral = btle.Peripheral(macaddr)
#IBS-Wenn TH1 mini
if sensortype == 'Inkbird_IBSTH1mini':
characteristic = peripheral.readCharacteristic(0x002d)
über peripheral = btle.Peripheral(macaddr) Apropos
BTLEException: failed to connect to peripheral addr type: public
Der Fehler wird angezeigt oder nicht (es ist problematisch, dass keine Reproduzierbarkeit vorliegt).
Wenn Sie erneut ausführen, funktioniert dies häufig so, als wäre nichts passiert Der Anrufer nimmt die Ausnahme mit try exception auf und führt sie bis zu einer bestimmten Anzahl von Malen erneut aus.
sensors_to_spreadsheet.py getdata_Teil der ibsth1-Methode
def getdata_ibsth1(device):
#Maximales Gerät, wenn kein Wert verfügbar ist.Wiederholen Wiederholen Sie den Scan
for i in range(device.Retry):
try:
sensorValue = GetIBSTH1Data().get_ibsth1_data(device.MacAddress, device.SensorType)
#Protokollausgabe, wenn ein Fehler auftritt
except:
logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}]')
sensorValue = None
continue
else:
break
Die Anzahl der erneuten Ausführungen wird für jedes Gerät in der Spalte "Wiederholen" der Einstellungsdatei "DeviceList.csv" angegeben.
getdata_switchbot_Teil der Thermomethode
#Scannen, um den Sensorwert zu erhalten
try:
scanner.scan(device.Timeout)
#Wenn beim Scannen eine Fehlermeldung angezeigt wird, starten Sie den Bluetooth-Adapter neu
except:
restart_hci0(masterdate, device.DeviceName)
Wenn Sie den obigen Vorgang scanner.scan () wiederholen, wird er ungefähr 20 Mal ausgeführt.
bluepy.btle.BTLEManagementError: Failed to execute management command 'scan' (code: 11, error: Rejected)
Es tritt ein Fehler auf, und unabhängig davon, wie oft Sie ihn erneut ausführen, können Sie aufgrund eines Fehlers keine Daten erfassen. Manchmal passiert es 10 Mal, manchmal 34 Mal, und es gibt keine Reproduzierbarkeit der Häufigkeit, was sehr problematisch ist. (Die Ursache ist unbekannt, aber es sieht aus wie ein abschnittsähnliches Verhalten.)
Lesen Sie hier und verwenden Sie den folgenden Befehl (Starten Sie den Bluetooth-Adapter neu).
sudo hciconfig hci0 down
sudo hciconfig hci0 up
Ich habe festgestellt, dass der Fehler bei der Ausführung behoben wird. Weitere Informationen finden Sie unter hier.
python
def restart_hci0(masterdate, devicename):
passwd = 'Passwort eingeben'
subprocess.run(('sudo','-S','hciconfig','hci0','down'), input=passwd, check=True)
subprocess.run(('sudo','-S','hciconfig','hci0','up'), input=passwd, check=True)
logging.error(f'restart bluetooth adapter [date{str(masterdate)}, device{devicename}]')
Ich habe eine Methode namens aufgerufen.
Dies,
getdata_switchbot_Teil der Thermomethode
#Scannen, um den Sensorwert zu erhalten
try:
scanner.scan(device.Timeout)
#Wenn beim Scannen eine Fehlermeldung angezeigt wird, starten Sie den Bluetooth-Adapter neu
except:
restart_hci0(masterdate, device.DeviceName)
Dieser Mechanismus kann ausgeführt werden, wenn ein Fehler auftritt, und automatisch neu gestartet und wiederhergestellt werden.
Der obige Befehl scheint bei der Verbesserung von Fehler 1 wirksam zu sein, daher habe ich versucht, ihn auszuführen, auch wenn der Wert nicht innerhalb der Anzahl der Wiederholungsversuche erhalten werden konnte. ** Bei Problemen neu starten ** ist ein vielseitiger Hut!
Selbst wenn Sie die erneute Ausführung für Fehler 1 wiederholen, können Sie den Wert möglicherweise nicht innerhalb der Anzahl der Wiederholungsversuche erhalten.
In einem Broadcast-Modus wie SwitchBot treten keine peripheren Fehler auf, aber Fehler 2 tritt auf, der Wert wird nicht innerhalb des Timeout-Werts zurückgegeben und der Wert wird möglicherweise nicht innerhalb der Wiederholungsanzahl erfasst.
Wenn Sie nichts tun, werden Sie nicht feststellen, dass Sie den Wert nicht erhalten konnten, und wenn Sie den Wert aufgrund eines Fehlers nicht immer erhalten können, können Sie nicht verfolgen, wann er fehlgeschlagen ist.
Notieren Sie, dass der Wert nicht abgerufen werden konnte, und geben Sie die folgenden 4 Protokollmuster unter Bezugnahme auf hier aus, damit die Ursache später zurückverfolgt werden kann. Ich habe es gemacht. ** Muster 1: Protokollausgabeteil, wenn Fehler 1 auftritt **
sensors_to_spreadsheet.py getdata_omron_Teil der USB-Methode
#Protokollausgabe, wenn ein Fehler auftritt
except:
logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}]')
** Muster 2: Protokollausgabeteil, wenn Fehler 2 auftritt **
sensors_to_spreadsheet.py Neustart_Teil der hci0-Methode
logging.error(f'restart bluetooth adapter [date{str(masterdate)}, device{devicename}]')
** Muster 3: Protokollieren Sie den Ausgabeteil, wenn der Wert auch nach einem erneuten Versuch der angegebenen Anzahl nicht erfasst werden konnte. **
sensors_to_spreadsheet.py getdata_omron_Teil der USB-Methode
#Wenn der Wert nicht abgerufen werden konnte, protokollieren Sie die Ausgabe und starten Sie den Bluetooth-Adapter neu
else:
logging.error(f'cannot get data [loop{str(device.Retry)}, date{str(masterdate)}, device{device.DeviceName}]')
** Muster 4: Protokollausgabeteil, wenn eine Reihe von Verarbeitungen abgeschlossen ist **
sensors_to_spreadsheet.Das Ende des Hauptcodes von py
#Protokollausgabe des Verarbeitungsende
logging.info(f'[masterdate{str(masterdate)} startdate{str(startdate)} enddate{str(datetime.today())} success{str(success_num)}/{str(sensor_num)}]')
Das Protokollausgabeziel wird in "Protokollausgabe" der Einstellungsdatei "config.ini" angegeben.
Es war schwieriger, einen langfristig stabilen Betrieb sicherzustellen, als Daten zu erfassen. Insbesondere die Umgebung des Bluetooth-Empfangs ist instabil und Fehler, die nicht reproduzierbar sind, sind gnadenlos herzzerreißend (lacht). Der Try-Except-Prozess ist sehr nützlich, um das oben genannte Problem zu lösen. Wenn Sie ihn jedoch missbrauchen, werden Sie das Problem nicht bemerken. Verwenden Sie ihn daher mit Vorsicht.
Durch Auflisten der Geräteverwaltung in einer Konfigurationsdatei und Automatisieren der Generierung von Tabellenkalkulationsspalten Es ist viel einfacher geworden, auf die Anzahl der Sensoren zu reagieren. Die Anforderungen an die Skalierbarkeit werden oft verschoben, aber mir wurde klar, dass es später kein Problem sein würde, wenn Sie in den frühen Phasen des Designs darüber nachdenken würden!
Wenn Sie versuchen, die Sicherheit zu gewährleisten, z. B. das Ausblenden von Kennwörtern, sind Implementierung und Betrieb kompliziert Ich fand es schwierig, die Kompromissbeziehung mit der Bequemlichkeit in Einklang zu bringen. Es gibt auch Bedenken hinsichtlich der Privatsphäre, da die Sensordaten in gewissem Maße Aufschluss über die Abwesenheit von Personen und deren Aktivitäten geben können. (Es ist genau der Teil, der auch im Super City Law ein Problem darstellt)
Der Inkbird IBS-TH1 mini war sehr locker, und der Akku war in weniger als einem Monat leer. Ungefähr 2 Tage vor dem Entladen der Batterie traten häufig abnormale Werte (Temperatur = -46,2 ° C, Luftfeuchtigkeit = 0%) wie die rosa Linie in der folgenden Abbildung auf.
Wenn dies ein reproduzierbares Zeichen für eine leere Batterie ist, sind dies sehr nützliche Informationen, da sie ersetzt werden können, bevor sie vollständig leer ist, um fehlende Werte zu vermeiden. Selbst wenn die Batterie beim nächsten Mal leer wird, werde ich die Situation unmittelbar zuvor sorgfältig beobachten.
Übrigens lag der verbleibende Akkuladestand mit der Inkbird-App zu diesem Zeitpunkt bei etwa 40%, sodass ich feststellte, dass er völlig unzuverlässig war (lacht).
Ich möchte das oben genannte Wissen für die zukünftige Entwicklung nutzen!