[PYTHON] Verwirklichen Sie ein Super-IoT-Haus, indem Sie mit Raspberry Pi Sensordaten im Haus erfassen

Einführung

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 **.

DashBoard.png

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! ** **.

Dinge notwendig

** ・ RaspberryPi ** (diesmal wird Pi3 Modell B verwendet) ** ・ Python-Ausführungsumgebung ** (diesmal wird voreingestelltes Python 3.7.3 verwendet)

Liste der verwendeten Sensoren

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-HygrometerNature Remo

Systemkonfiguration

Gesamtkonfigurationsdiagramm

Die Gesamtstruktur ist in der folgenden Abbildung dargestellt. Die Pfeile repräsentieren den Datenfluss. センサ値取得システム.png

Charakteristisch

Periodische Ausführung

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.

Drahtlose Kommunikation zwischen Sensor und Raspberry Pi

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.

Datenspeicherungsziel

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.

Verfahren

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

Einzelheiten

① Grundeinstellung des Sensors und Funktionsprüfung

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 miniInkbird IBS-TH1SwitchBot-Thermo-HygrometerNature Remo

② Sensordatenerfassungsklasse erstellen

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.

Omron Umgebungssensor

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

SwitchBot Thermo-Hygrometer

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

③ Hauptskript erstellen

Gesamtstruktur

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. maincode.png

Einstellungsdatei

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.

Aktuelles Skript

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)}]')

④ Drücken Sie die GAS-API von Python, um Daten in die Tabelle zu schreiben

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.

Daten-POST-Verarbeitung von Python-Seite (RaspberryPi-Seite)

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.)

Erstellen einer Zieltabelle

Gehen Sie zu Google Spreadsheets und gehen Sie zu Erstellen Sie eine Tabelle wie unten gezeigt. sensor_spreadsheet.png

Der Blattname lautet "SensorData". Im nächsten Abschnitt erstellen wir ein Skript, das Daten auf diesem Blatt gleichzeitig an alle Sensoren ausgibt.

Erstellung von GAS-Skripten (Datenempfang und Schreiben von Tabellenkalkulationen)

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.

GAS-Skript-API-Version

[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).

Beschreibung der Ausgabe-Tabelle

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. spreadsheet_sensor.png

⑤ Regelmäßige Ausführung des Skripts

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.

Cron aktivieren

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.)

Planen Sie die Ausführung mit cron

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!

⑥ Visualisierung durch Google Data Portal

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.

Neuen Bericht erstellen

1_dataportal.png ** Geben Sie eine Google-Tabelle als Datenverbindungsziel an ** 2_connect_spreadsheet.png ** Wenn Sie um Genehmigung gebeten werden, drücken Sie die Genehmigungstaste ** 3_allow_spreadsheet.png ** Sie werden nach dem Referenzblatt gefragt. Geben Sie daher die in ④ ** erstellte Tabelle an. 4_add_spreadsheet.png ** Drücken Sie "Zum Bericht hinzufügen" ** 5_confirm_spreadsheet.png ** Benennen Sie den Bericht um ** 6_rename.png

Erstellen eines täglichen Durchschnittsdiagramms

Erstellen Sie ein Diagramm, um langfristige Änderungen anzuzeigen

** Klicken Sie auf Ressourcen → Hinzugefügte Datenquellen verwalten ** 7.png ** Klicken Sie auf "Bearbeiten" für die Zieldatenquelle ** 8.png ** Ä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 9.png ** Wechsel zum Zeitreihendiagramm ** 10.png ** Geben Sie die Abmessung (horizontale Achse = Date_Master_Day) und den Index (vertikale Achse = Temperatur verschiedener Sensoren) an ** 11.png ** Ändern Sie die Temperaturstatistik von gesamt auf durchschnittlich ** 12.png ** Ändern, um fehlende Werte auszublenden ** 13.png

Erstellen eines Diagramms für jede Messung (alle 5 Minuten)

Erstellen Sie ein Diagramm, um kurzfristige Änderungen anzuzeigen

** Zeitreihendiagramm hinzufügen ** 15.png ** Geben Sie die Abmessung (horizontale Achse = Date_Master_Day) und den Index (vertikale Achse = Temperatur verschiedener Sensoren) an ** 16.png ** Ändern Sie die Temperaturstatistik von gesamt auf durchschnittlich ** 17_.png ** Ändern Sie fehlende Werte, um sie linear zu vervollständigen ** 18.png Damit ist der Temperaturübergangsgraph abgeschlossen!

Um das Dashboard zu vervollständigen

Zu diesem Zeitpunkt ist nur der Teil des Öffnungs-Dashboards abgeschlossen, der von dem roten Rahmen darunter umgeben ist. DashBoard_part.png

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. DashBoard.png

** 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!

Korrespondenz zu verschiedenen Fehlern

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 **!

Fehler 1: Beim Initialisieren des Peripheriegeräts tritt ein Fehler auf

Phänomen, das auftritt

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).

Bewältigung

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.

Fehler 2: Fehler beim Scannen im Broadcast-Modus

Phänomen, das auftritt

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.)

Bewältigung

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!

Fehler 3: Wenn der Wert nicht abgerufen werden kann, kann die Ursache nicht verfolgt werden.

Phänomen, das auftritt

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.

Ansatz

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.

Was ich fühlte, als ich es versuchte

Schwierigkeit des langzeitstabilen Betriebs

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.

Bedeutung der Erweiterbarkeit

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!

Tiefe Wurzel von Sicherheitsproblemen

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)

Impressionen für jeden Sensor

Zeichen einer leeren Batterie

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. InkbirdMini_異常値.png

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!

Recommended Posts

Verwirklichen Sie ein Super-IoT-Haus, indem Sie mit Raspberry Pi Sensordaten im Haus erfassen
Ein Memo zur einfachen Verwendung des Beleuchtungsstärkesensors TSL2561 mit Raspberry Pi 2
Verwendung des digitalen Beleuchtungsstärkesensors TSL2561 mit Raspberry Pi
Überprüfen Sie! Erhalten Sie Sensordaten über Bluetooth mit Raspberry Pi ~ Preparation
Notieren Sie sich, was Sie in Zukunft mit Razpai machen möchten
Verarbeitung natürlicher Sprache (Originaldaten) mit Word2Vec, entwickelt von US-amerikanischen Google-Forschern
Steuern Sie den Motor mit einem Motortreiber mit Python auf Raspberry Pi 3!
Raspberry Pi --1 - Zum ersten Mal (Schließen Sie einen Temperatursensor an, um die Temperatur anzuzeigen)
Verwenden einer Webkamera mit Raspberry Pi
Protokollieren Sie die Omron-Umgebungssensorwerte regelmäßig mit Raspberry Pi
Erstellen einer verteilten Umgebung mit der Raspberry PI-Serie (Teil 1: Zusammenfassung der Verfügbarkeit von plattenlosen Clients nach Modell)
Die Geschichte eines Parksensors in 10 Minuten mit dem GrovePi + Starter Kit
Ein Memo, das durch Umbenennen der Dateinamen im Ordner mit Python organisiert wird
Erfassen Sie den Sensorwert von Grove Pi + mit Raspberry Pi und speichern Sie ihn in Kintone
Protokollierung der Omron-Umgebungssensorwerte mit Raspberry Pi (USB-Typ)
Erstellen Sie eine Tensorflow-Umgebung mit Raspberry Pi [2020]
Verwenden Sie den Grove-Sensor mit Raspberry Pi
Verbesserter menschlicher Sensor mit Raspberry Pi
Verwenden Sie einen PIR-Bewegungssensor mit Himbeer-Pi
Machen Sie einen Waschtrocknungs-Timer mit Raspberry Pi
Bedienen Sie das Oszilloskop mit dem Raspberry Pi
Erstellen Sie eine Auto-Anzeige mit Himbeer-Pi
Machen Sie ein Thermometer mit Raspberry Pi und machen Sie es im Browser Teil 3 sichtbar
Ich habe die Beleuchtungsstärke des Raumes mit Raspberry Pi, Arduino und einem optischen Sensor getwittert
[Für Anfänger] Ich habe mit Raspberry Pi einen menschlichen Sensor erstellt und LINE benachrichtigt!
Verwendung des digitalen 1-Draht-Temperatursensors DS18B20 mit Raspberry Pi von Python
[Python] Holen Sie sich die Dateien mit Python in den Ordner
Löschen Sie Daten in einem Muster mit Redis Cluster
Senden Sie Daten von Raspberry Pi mit AWS IOT
CSV-Ausgabe von Impulsdaten mit Raspberry Pi (CSV-Ausgabe)
Beobachten Sie die Futago-Meteorgruppe mit Raspberry Pi4
Bearbeiten und debuggen Sie den Code in Raspberry Pi mit der SSH-Verbindungsfunktion von VSCode
[Siehe nicht 04.02.17] Zeigen Sie den Temperatursensor in Echtzeit in einem Diagramm mit Himbeer-Pi 3 an
[Raspberry Pi] Wenn der menschliche Sensor dies erkennt, speichern Sie den Zeitstempel in der Firebase-Echtzeitdatenbank.
Erstellen Sie einen Farbsensor mit einem Raspeltorte und einer Kamera
Trainieren Sie MNIST-Daten mit PyTorch mithilfe eines neuronalen Netzwerks
Einfaches IoT, um mit Raspeye und MESH zu beginnen
Stellen wir uns den Raum mit Raspeltorte vor, Teil 1
Versuchen Sie es mit dem Temperatursensor (LM75B) mit Raspeye.
Nehmen Sie den Wert des SwitchBot-Thermo-Hygrometers mit Raspberry Pi
Umschalten der Bot-Thermo-Hygrometer-Werte mit Raspberry Pi
Ein Memorandum bei der Herstellung einer Überwachungskamera mit Raspeye
Ich habe eine Klasse erstellt, um das Analyseergebnis von MeCab in ndarray mit Python zu erhalten
Es war großartig, die Python-Datei in Raspberry Pi mit der Remote-Funktion von Atom zu bearbeiten