La gestion des données par la méthode supercity est un sujet brûlant dans les rues, En tant que secte d'intérieur, surfons sur la vague du temps à la maison en face de la ville! J'ai pensé, ** Nous avons créé un mécanisme pour afficher une grande quantité de données de capteurs sur un tableau de bord en temps réel **.
** À partir de la conclusion, je pense que nous avons construit un système capable de visualiser les données des capteurs de manière stable **.
Je voudrais essayer de créer un article sans un saut d'explication afin que même les débutants puissent le comprendre facilement. ** Je vous serais reconnaissant si vous pouviez signaler des sauts ou des erreurs! ** **
** ・ RaspberryPi ** (Pi3 Model B est utilisé cette fois) ** ・ Environnement d'exécution Python ** (Python 3.7.3 prédéfini est utilisé cette fois)
・ Type de sac de capteur d'environnement Omron (2JCIE-BL01) ・ Type USB du capteur d'environnement Omron (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? 4e16-8524-c984893af9bd & pd_rd_w = Ip0MD & pd_rd_wg = lcDuD & pf_rd_p = bff3a3a6-0f6e-4187-bd60-25e75d4c1c8f & pf_rd_r = 939W116VQ9B4AY ・ Thermo-hygromètre SwitchBot ・ Nature Remo
La structure générale est celle illustrée dans la figure ci-dessous. Les flèches représentent le flux de données.
Le fonctionnement à long terme étant difficile avec l'exécution manuelle de scripts, nous avons créé un mécanisme d'exécution périodique automatique. Le script sensor_to_spreadsheet.py qui contrôle l'ensemble est périodiquement exécuté par le cron de Raspberry Pi pour accumuler des données.
Fondamentalement, il communique avec Bluetooth Low Energy (BLE). BLE est une norme qui devient la norme de facto pour les appareils IoT à domicile, et les notes sont résumées dans cet article d'une manière facile à comprendre. [Détails ci-dessous](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), mais la stabilisation de la BLE était le plus grand obstacle à ce développement. De plus, seul Nature Remo est connecté par WiFi au lieu de BLE.
La destination de stockage des données est divisée en deux systèmes: le stockage CSV local et Google Spreadsheets. CSV: utilisé ultérieurement pour l'analyse Feuilles de calcul Google: utilisées pour la visualisation sur le portail de données (je voulais visualiser sur le cloud) Je l'ai divisé en supposant qu'il sera utilisé, mais je pense qu'il est redondant, alors je pense qu'il est normal de l'unifier en un seul système. Lors de l'unification, je pense qu'il est idéal de pouvoir enregistrer dans une base de données accessible sur le cloud.
Le système ci-dessus a été construit selon la procédure suivante ** ① Réglage initial du capteur et contrôle de fonctionnement ** ** ② Créer une classe d'acquisition de données de capteur ** ** ③ Créer le script principal ** ** ④ Accédez à l'API GAS de Python pour écrire des données dans la feuille de calcul ** ** ⑤ Exécution périodique du script ** ** ⑥ Visualisation par Google Data Portal **
Les détails seront expliqués dans le chapitre suivant
Depuis que j'ai créé l'article individuellement, veuillez effectuer le réglage initial et le contrôle de fonctionnement pour chaque capteur.
・ Type de sac de capteur d'environnement Omron (2JCIE-BL01) ・ Type USB du capteur d'environnement Omron (2JCIE-BU01) ・ Inkbird IBS-TH1 mini ・ Inkbird IBS-TH1 ・ Thermo-hygromètre SwitchBot ・ Nature Remo
Créez une classe Python pour obtenir des données de capteur pour chaque type de capteur. (Inkbird IBS-TH1 et IBS-TH1 mini sont acquis dans la même classe. ")
De plus, les modules (fichiers .py) sont organisés par fabricant. Ci-dessous, nous expliquerons chaque module (par fabricant) dans des sections distinctes.
Créez une classe d'acquisition de données pour Omron 2JCIE-BL01 & 2JCIE-BU01. Le contenu est généralement [Type de sac](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) [Type USB](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) Est une combinaison de
** ・ Méthode d'appel de l'extérieur Type de BAG: ** Créez une instance de la classe OmniBroadcastScanDelegate et
Nom de l'instance.scan(timeout)
Vous pouvez obtenir les données du capteur avec. Pour l'argument «timeout», spécifiez la valeur de timeout pour l'acquisition de données en secondes (environ 3 à 5 secondes semblent être bonnes).
** Type USB: ** Vous pouvez obtenir les données du capteur par la méthode suivante
GetOmronConnectModeData().get_env_usb_data(macaddr)
Pour l'argument "macaddr", spécifiez l'adresse Mac du capteur.
** - Script réel **
omron_env.py
# coding: UTF-8
from bluepy import btle
import struct
#Délégué d'acquisition de données de diffusion (type BAG)
class OmronBroadcastScanDelegate(btle.DefaultDelegate):
#constructeur
def __init__(self):
btle.DefaultDelegate.__init__(self)
#Variables pour conserver les données du capteur
self.sensorValue = None
#Gestionnaire de scan
def handleDiscovery(self, dev, isNewDev, isNewData):
#Lorsqu'un nouvel appareil est trouvé
if isNewDev or isNewData:
#Extraire les données publicitaires
for (adtype, desc, value) in dev.getScanData():
#Effectuer la récupération des données lorsque le capteur d'environnement
if desc == 'Manufacturer' and value[0:4] == 'd502':
#Retirez le type de capteur (EP ou IM)
sensorType = dev.scanData[dev.SHORT_LOCAL_NAME].decode(encoding='utf-8')
#Extraction des données du capteur pendant EP
if sensorType == 'EP':
self._decodeSensorData_EP(value)
#Extraction des données du capteur pendant la messagerie instantanée
if sensorType == 'IM':
self._decodeSensorData_IM(value)
#Extraire les données du capteur et les convertir au format dict (en mode EP)
def _decodeSensorData_EP(self, valueStr):
#Données du capteur de la chaîne de caractères(6ème et suivants caractères)Sortir et convertir uniquement en binaire
valueBinary = bytes.fromhex(valueStr[6:])
#Convertir les données binaires du capteur en nombre entier Taple
(temp, humid, light, uv, press, noise, discomf, wbgt, rfu, batt) = struct.unpack('<hhhhhhhhhB', valueBinary)
#Stocker dans le type de dict après la conversion d'unité
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
}
#Extraire les données du capteur et les convertir au format dict (en mode IM)
def _decodeSensorData_IM(self, valueStr):
#Données du capteur de la chaîne de caractères(6ème et suivants caractères)Sortir et convertir uniquement en binaire
valueBinary = bytes.fromhex(valueStr[6:])
#Convertir les données binaires du capteur en nombre entier Taple
(temp, humid, light, uv, press, noise, accelX, accelY, accelZ, batt) = struct.unpack('<hhhhhhhhhB', valueBinary)
#Stocker dans le type de dict après la conversion d'unité
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
}
#Classe d'acquisition de données en mode connexion (type USB)
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 Créez une classe d'acquisition de données pour Inkbird IBS-TH1 et IBS-TH1 mini. Le contenu est généralement IBS-TH1 IBS-TH1 mini Est une combinaison de
** ・ Méthode d'appel de l'extérieur ** Inkbird_IBSTH1 et Inkbird_IBSTH1mini, les deux peuvent acquérir des données de capteur par la méthode suivante
GetIBSTH1Data().get_ibsth1_data(macaddr, sensortype)
Pour l'argument "macaddr", spécifiez l'adresse Mac du capteur, et pour "sensortype", spécifiez'Inkbird_IBSTH1mini'ou'Inkbird_IBSTH1 'en fonction du type de capteur.
** - Script réel **
inkbird_ibsth1.py
from bluepy import btle
import struct
#Inkbird IBS-Classe d'acquisition de données TH1
class GetIBSTH1Data():
def get_ibsth1_data(self, macaddr, sensortype):
#Connectez-vous à l'appareil
peripheral = btle.Peripheral(macaddr)
#IBS-Lorsque TH1 mini
if sensortype == 'Inkbird_IBSTH1mini':
characteristic = peripheral.readCharacteristic(0x002d)
return self._decodeSensorData_mini(characteristic)
#IBS-Au 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
Créez une classe d'acquisition de données pour le thermo-hygromètre SwitchBot. Le contenu est [ici](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 Identique à% 88% 90)
** ・ Méthode d'appel de l'extérieur ** Créer une instance de la classe SwitchbotScanDelegate
Nom de l'instance.scan(timeout)
Vous pouvez obtenir les données du capteur avec. Pour l'argument «timeout», spécifiez la valeur de timeout pour l'acquisition de données en secondes (environ 5 secondes semblent être bonnes).
** - Script réel **
switchbot.py
from bluepy import btle
import struct
#Délégué d'acquisition de données de diffusion
class SwitchbotScanDelegate(btle.DefaultDelegate):
#constructeur
def __init__(self, macaddr):
btle.DefaultDelegate.__init__(self)
#Variables pour conserver les données du capteur
self.sensorValue = None
self.macaddr = macaddr
#Gestionnaire de scan
def handleDiscovery(self, dev, isNewDev, isNewData):
#Si vous trouvez un appareil avec l'adresse Mac cible
if dev.addr == self.macaddr:
#Extraire les données publicitaires
for (adtype, desc, value) in dev.getScanData():
#Effectuer la récupération des données lorsque le capteur d'environnement
if desc == '16b Service Data':
#Extraire les données du capteur
self._decodeSensorData(value)
#Extraire les données du capteur et les convertir au format dict
def _decodeSensorData(self, valueStr):
#Données du capteur de la chaîne de caractères(4e et suivants caractères)Sortir et convertir uniquement en binaire
valueBinary = bytes.fromhex(valueStr[4:])
#Convertir les données binaires du capteur en valeur numérique
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
#Stocker dans le type de dict
self.sensorValue = {
'SensorType': 'SwitchBot',
'Temperature': temp,
'Humidity': humid,
'BatteryVoltage': batt
}
Nature Remo En plus des données du capteur, les données du climatiseur sont également acquises via la télécommande.
** ・ Méthode d'appel de l'extérieur Acquisition des données du capteur: ** Vous pouvez obtenir les données du capteur par la méthode suivante
get_sensor_data(Token, API_URL)
L'argument «Token» est le jeton d'accès Remo (référence), et «API_URL» est «https://api.nature.global/» (corrigé). Valeur)
** Acquisition des données du climatiseur **: Vous pouvez obtenir les données du capteur par la méthode suivante
get_aircon_data(Token, API_URL)
Les arguments sont les mêmes que lors de l'acquisition des données du capteur
** - Script réel **
remo.py
import json
import requests
import glob
import pandas as pd
#Classe d'acquisition de données Remo
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
#Extraire les données du capteur et les convertir au format dict
def _decodeSensorData(self, rjson):
for device in rjson:
#Sélectionnez Remo (pour éviter de sélectionner Remo E par erreur)
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
#Retirez les données du climatiseur et convertissez-les au format dict
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
Appelez la classe (2) et créez un script principal pour acquérir les données du capteur. La structure est divisée en 5 types de méthodes et de structures suivants
** 1) Méthode pour exécuter la classe ② ** getdata_omron_bag (device): obtenir la valeur du capteur du type de BAG du capteur d'environnement Omron getdata_omron_usb (périphérique): Capteur d'environnement Omron Acquisition de valeur de capteur de type USB getdata_ibsth1 (périphérique): obtenir la valeur du capteur de Inkbird IBS-TH1 et IBS-TH1 mini getdata_switchbot_thermo (appareil): obtenir la valeur du capteur du thermo-hygromètre SwitchBot getdata_remo (appareil, csvpath): obtenir la valeur du capteur Nature Remo et les données du climatiseur La signification de l'argument est la suivante périphérique: spécifiez la ligne lue depuis DeviceList.csv. csvpath: chemin enregistré CSV (le capteur humain de Remo doit être comparé aux données antérieures enregistrées CSV)
** 2) Méthode de sortie CSV ** output_csv (data, csvpath): génère les données de valeur mesurée pour chaque capteur au format CSV. La signification de l'argument est la suivante data: Données acquises du capteur par la méthode de 1 csvpath: chemin de sauvegarde CSV
** 3) Méthode de sortie SpreadSheet ** output_spreadsheet (all_values_dict): affiche les données de mesure de tous les capteurs dans la feuille de calcul. La signification de l'argument est la suivante all_values_dict: Données de tous les capteurs au format dict (en supposant la structure ci-dessous)
all_values_dict = {
Nom du capteur 1:{
Nom de colonne 1:Valeur 1,
Nom de colonne 2:Valeur 2,
:
},
Nom du capteur 2:{
Nom de colonne 1:Valeur 1,
Nom de colonne 2:Valeur 2,
:
},
:
}
** 4) Méthode de redémarrage de l'adaptateur Bluetooth ** restart_hci0 (devicename): C'est la méthode de contre-mesure "Bug 2" décrite en bas. La signification de l'argument est la suivante devicename: nom du périphérique (utilisé pour la sortie du journal)
** 5) Partie de traitement principale if name == After'main ': Exécute une série de processus comme indiqué dans le flux ci-dessous.
À mesure que le nombre de capteurs augmentait et que je sentais qu'il y avait une limite à la gestion avec le code, j'ai créé les deux types de fichiers de paramètres suivants. ** ・ DeviceList.csv: Décrivez les informations nécessaires pour chaque capteur **
DeviceList.csv
DeviceName,SensorType,MacAddress,Timeout,Retry,Offset_Temp,Offset_Humid,API_URL,Token
SwitchBot_Thermo1,SwitchBot_Thermo,[Adresse Mac SwitchBot],4,3,0,0,,
Inkbird_IBSTH1_Mini1,Inkbird_IBSTH1mini,[IBS-Adresse mini Mac TH1],0,2,0,0,,
Inkbird_IBSTH1_1,Inkbird_IBSTH1,[IBS-Adresse Mac TH1],0,2,0,0,,
Remo1,Nature_Remo,,0,2,0,0,https://api.nature.global/,[Jeton d'accès Nature Remo]
Omron_USB1,Omron_USB_EP,[Adresse Mac de type USB Omron],0,2,0,0,,
Omron_BAG1,Omron_BAG_EP,[Adresse Mac de type Omron BAG],3,2,0,0,,
La signification des colonnes est la suivante DeviceName: gère les noms de périphériques et identifie lorsqu'il y a plusieurs capteurs du même type SensorType: Le type de capteur. Séparez les classes d'acquisition de données de capteur à exécuter en fonction de cette valeur MacAddress: adresse MAC du capteur Timeout: valeur du délai au moment de la recherche (uniquement pour les capteurs en mode diffusion) Réessayer: nombre maximal de tentatives [Détails](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: valeur de décalage de température (actuellement inutilisée) Offset_Humid: valeur de décalage d'humidité (actuellement inutilisée) API_URL: URL de l'API (utilisée uniquement pour Nature Remo) Jeton: jeton d'accès (utilisé uniquement pour Nature Remo)
** ・ config.ini: Spécifiez le répertoire de sortie CSV et journal **
config.ini
[Path]
CSVOutput = /share/Data/Sensor
LogOutput = /share/Log/Sensor
Si les deux sont affichés dans le dossier partagé créé par samba, il est pratique d'accéder depuis l'extérieur du Raspberry Pi.
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
#Variables globales
global masterdate
######Acquisition de valeurs pour capteur d'environnement OMRON (type BAG)######
def getdata_omron_bag(device):
#Appareil maximum lorsqu'aucune valeur n'est disponible.Réessayer de répéter l'analyse
for i in range(device.Retry):
#omron_Définit le délégué d'acquisition de la valeur du capteur d'env pour qu'il s'exécute au moment du scan
scanner = btle.Scanner().withDelegate(OmronBroadcastScanDelegate())
#Scannez pour obtenir la valeur du capteur
try:
scanner.scan(device.Timeout)
#Si vous obtenez une erreur lors de l'analyse, redémarrez l'adaptateur Bluetooth
except:
restart_hci0(device.DeviceName)
#Terminer la boucle lorsque la valeur peut être obtenue
if scanner.delegate.sensorValue is not None:
break
#Si la valeur ne peut pas être obtenue, écrivez-la dans le journal
else:
logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}, timeout{device.Timeout}]')
#Si la valeur peut être obtenue, stockez les données POST dans dict
if scanner.delegate.sensorValue is not None:
#Données à 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
#Si la valeur n'a pas pu être obtenue, enregistrez la sortie et redémarrez l'adaptateur Bluetooth
else:
logging.error(f'cannot get data [date{str(masterdate)}, device{device.DeviceName}, timeout{device.Timeout}]')
restart_hci0(device.DeviceName)
return None
######Acquisition de données du capteur d'environnement OMRON (type USB)######
def getdata_omron_usb(device):
#Appareil maximum lorsqu'aucune valeur n'est disponible.Réessayer de répéter l'analyse
for i in range(device.Retry):
try:
sensorValue = GetOmronConnectModeData().get_env_usb_data(device.MacAddress)
#Sortie de journal si une erreur se produit
except:
logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}]')
sensorValue = None
continue
else:
break
#Si la valeur peut être obtenue, stockez les données POST dans dict
if sensorValue is not None:
#Données à 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
#Si la valeur n'a pas pu être obtenue, enregistrez la sortie et redémarrez l'adaptateur Bluetooth
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-Acquisition de données TH1######
def getdata_ibsth1(device):
#Appareil maximum lorsqu'aucune valeur n'est disponible.Réessayer de répéter l'analyse
for i in range(device.Retry):
try:
sensorValue = GetIBSTH1Data().get_ibsth1_data(device.MacAddress, device.SensorType)
#Sortie de journal si une erreur se produit
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:
#Données à POST
data = {
'DeviceName': device.DeviceName,
'Date_Master': str(masterdate),
'Date': str(datetime.today()),
'Temperature': str(sensorValue['Temperature']),
'Humidity': str(sensorValue['Humidity']),
}
return data
#Si la valeur n'a pas pu être obtenue, enregistrez la sortie et redémarrez l'adaptateur Bluetooth
else:
logging.error(f'cannot get data [loop{str(device.Retry)}, date{str(masterdate)}, device{device.DeviceName}]')
restart_hci0(device.DeviceName)
return None
######Acquisition des données du thermo-hygromètre SwitchBot######
def getdata_switchbot_thermo(device):
#Appareil maximum lorsqu'aucune valeur n'est disponible.Réessayer de répéter l'analyse
for i in range(device.Retry):
#Définir le délégué d'acquisition de la valeur du capteur Switchbot
scanner = btle.Scanner().withDelegate(SwitchbotScanDelegate(str.lower(device.MacAddress)))
#Scannez pour obtenir la valeur du capteur
try:
scanner.scan(device.Timeout)
#Si vous obtenez une erreur lors de l'analyse, redémarrez l'adaptateur Bluetooth
except:
restart_hci0(device.DeviceName)
#Terminer la boucle lorsque la valeur peut être obtenue
if scanner.delegate.sensorValue is not None:
break
#Si la valeur ne peut pas être obtenue, écrivez-la dans le journal
else:
logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}, timeout{device.Timeout}]')
#Si la valeur peut être obtenue, stockez les données POST dans dict
if scanner.delegate.sensorValue is not None:
#Données à 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
#Sinon, sortez le journal et redémarrez l'adaptateur Bluetooth
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
######Acquisition de données Nature Remo######
def getdata_remo(device, csvpath):
#Appareil maximum lorsque la valeur des données du capteur n'est pas disponible.Réessayer de répéter l'analyse
for i in range(device.Retry):
try:
sensorValue = GetRemoData().get_sensor_data(device.Token, device.API_URL)
#Sortie de journal si une erreur se produit
except:
logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}, sensor]')
sensorValue = None
continue
else:
break
#Appareil maximum lorsque la valeur des données du climatiseur n'est pas disponible.Réessayer de répéter l'analyse
for i in range(device.Retry):
try:
airconValue = GetRemoData().get_aircon_data(device.Token, device.API_URL)
#Sortie de journal si une erreur se produit
except:
logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}, aircon]')
sensorValue = None
continue
else:
break
#Si la valeur peut être obtenue, stockez les données POST dans dict
if sensorValue is not None:
#Données du capteur
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}')
}
#Données du climatiseur
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
#S'il ne peut pas être obtenu, enregistrez la sortie (comme c'est via WiFi, l'adaptateur Bluetooth ne sera pas redémarré)
else:
logging.error(f'cannot get data [loop{str(device.Retry)}, date{str(masterdate)}, device{device.DeviceName}]')
return None
######Sortie CSV des données######
def output_csv(data, csvpath):
dvname = data['DeviceName']
monthstr = masterdate.strftime('%Y%m')
#Nom du dossier de destination de sortie
outdir = f'{csvpath}/{dvname}/{masterdate.year}'
#Lorsque le dossier de destination de sortie n'existe pas, créez-en un nouveau
os.makedirs(outdir, exist_ok=True)
#Chemin du fichier de sortie
outpath = f'{outdir}/{dvname}_{monthstr}.csv'
#Créer un nouveau fichier de sortie lorsqu'il n'existe pas
if not os.path.exists(outpath):
with open(outpath, 'w') as f:
writer = csv.DictWriter(f, data.keys())
writer.writeheader()
writer.writerow(data)
#Ajouter une ligne lorsque le fichier de sortie existe
else:
with open(outpath, 'a') as f:
writer = csv.DictWriter(f, data.keys())
writer.writerow(data)
######Processus de téléchargement sur la feuille de calcul Google######
def output_spreadsheet(all_values_dict):
#URL de l'API
url = 'URL de l'API GAS répertoriée ici'
#POST des données vers l'API
response = requests.post(url, json=all_values_dict)
print(response.text)
######Redémarrage de l'adaptateur Bluetooth######
def restart_hci0(devicename):
passwd = 'Entrez le mot de passe RaspberryPi'
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}]')
######Principale######
if __name__ == '__main__':
#Obtenir l'heure de début
startdate = datetime.today()
#Arrondissez l'heure de début en minutes
masterdate = startdate.replace(second=0, microsecond=0)
if startdate.second >= 30:
masterdate += timedelta(minutes=1)
#Lire le fichier de configuration et la liste des appareils
cfg = configparser.ConfigParser()
cfg.read('./config.ini', encoding='utf-8')
df_devicelist = pd.read_csv('./DeviceList.csv')
#Nombre total de capteurs et acquisition de données réussie
sensor_num = len(df_devicelist)
success_num = 0
#Initialisation du journal
logname = f"/sensorlog_{str(masterdate.strftime('%y%m%d'))}.log"
logging.basicConfig(filename=cfg['Path']['LogOutput'] + logname, level=logging.INFO)
#Dict pour conserver toutes les données acquises
all_values_dict = None
######Acquisition de données pour chaque appareil######
for device in df_devicelist.itertuples():
#Type de sac du capteur d'environnement Omron (connexion BroadCast)
if device.SensorType in ['Omron_BAG_EP','Omron_BAG_IM']:
data = getdata_omron_bag(device)
#Type USB du capteur d'environnement Omron (connexion en mode Connect)
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)
#Thermo-hygromètre SwitchBot
elif device.SensorType == 'SwitchBot_Thermo':
data = getdata_switchbot_thermo(device)
#remo
elif device.SensorType == 'Nature_Remo':
data = getdata_remo(device, cfg['Path']['CSVOutput'])
#Autre que ceux ci-dessus
else:
data = None
#Lorsque des données existent, ajoutez-les à Dict pour contenir toutes les données et générer le CSV
if data is not None:
#all_values_Créer un nouveau dictionnaire lorsque dict est None
if all_values_dict is None:
all_values_dict = {data['DeviceName']: data}
#all_values_Ajouter au dictionnaire existant lorsque dict n'est pas None
else:
all_values_dict[data['DeviceName']] = data
#Sortie CSV
output_csv(data, cfg['Path']['CSVOutput'])
#Numéro de réussite plus
success_num+=1
######Processus de téléchargement sur la feuille de calcul Google######
output_spreadsheet(all_values_dict)
#Sortie du journal de la fin du traitement
logging.info(f'[masterdate{str(masterdate)} startdate{str(startdate)} enddate{str(datetime.today())} success{str(success_num)}/{str(sensor_num)}]')
Écrivez les données de Raspberry Pi dans une feuille de calcul Google.
Du côté RaspberryPi, utilisez la classe d'exécution d'API de Python pour publier des données et Du côté de la feuille de calcul, les données ci-dessus sont reçues à l'aide d'un script Google Apps Script.
Il semble que seule la fonction nommée doPost puisse être utilisée pour publier l'API, vous devez donc publier toutes les données de Python vers GAS en même temps. Pour cela, j'ai fait un script à publier au format JSON.
sensors_to_spreadsheet.Partie de py
######Processus de téléchargement sur la feuille de calcul Google######
def output_spreadsheet(all_values_dict):
#URL de l'API
url = 'URL de l'API GAS répertoriée ici'
#POST des données vers l'API
response = requests.post(url, json=all_values_dict)
print(response.text)
Le fait est que la valeur passée après "requests.post (url, json =" est dict. (Ne convertissez pas la chaîne de caractères avec json.dumps (). C'est un point addictif de la passer sans la convertir en json même si le nom est json = ...)
Accédez à Google Spreadsheets et accédez à Créez une feuille de calcul comme indiqué ci-dessous.
Le nom de la feuille est "SensorData". Dans la section suivante, nous allons créer un script qui envoie des données à tous les capteurs à la fois sur cette feuille.
Sélectionnez "Outils" -> "Éditeur de script" sur la feuille de calcul Créez un script GAS comme celui ci-dessous
postSensorData.gs
var spreadsheetId = '******'//← Entrez l'ID de la feuille de calcul
//Une méthode qui reçoit les données publiées et les écrit dans une feuille de calcul
function doPost(e){
//Obtenir des informations sur la feuille de calcul (instance de feuille, liste de noms d'en-tête, nombre de lignes)
var sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName('SensorData');
var headerList = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
var lastRow = sheet.getLastRow();
//Convertir les données reçues en JSON
var json = JSON.parse(e.postData.getDataAsString());
//Tenez le nombre d'appareils
var devCnt = 0;
for(var device in json) {
//Ajouter 1 appareil
devCnt++;
//Scanner toutes les mesures du capteur
for(var colName in json[device]){
//Le nom de la colonne est"DeviceName"Ignorez la valeur lorsque"device"(Acquis comme)
if(colName == "DeviceName"){
//Le nom de la colonne est"Date_Master"Quand, ignorez la valeur sauf pour le premier appareil (évitez la description en double)
}else if(colName == "Date_Master" && devCnt >= 2){
//Autre que ce qui précède, écrivez des données dans la feuille de calcul
}else{
headerList = addData(sheet, lastRow, headerList, device, colName, json[device][colName]);
}
}
}
//Renvoie le succès à l'exécuteur d'API
return ContentService.createTextOutput(JSON.stringify({content:"post ok", endtime:new Date()})).setMimeType(ContentService.MimeType.JSON);
}
//Écrire des données dans une feuille de calcul
function addData(sheet, lastRow, headerList, deviceName, colName, cell){
//Le nom de la colonne est Date_Lorsque Master, écrivez des données et écrivez des données divisées uniquement en partie de date
if (colName == 'Date_Master'){
sheet.getRange(lastRow + 1, 1).setValue(cell);
sheet.getRange(lastRow + 1, 2).setValue(cell.substring(0, 10));
//Le nom de la colonne est Date_Quand autre que Master
}else{
//Nom de la colonne (combine le nom de l'appareil et le nom de la colonne d'origine)
var newColName = deviceName + "_" + colName;
//Analyser les noms d'en-tête existants
for (i=0; i<headerList.length; i++) {
//Écrire les données du capteur dans cette colonne s'il existe un nom d'en-tête existant égal au nom de la colonne
if(newColName == headerList[i]){
sheet.getRange(lastRow + 1, i + 1).setValue(cell);
break;
}
//Si le nom de la colonne n'est pas inclus dans le nom de l'en-tête existant, ajoutez un nouvel en-tête et écrivez les données du capteur
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
}
** * ID de la feuille de calcul ** L'URL de la feuille de calcul est "https://docs.google.com/spreadsheets/d/AAAAAA/edit#gid=0" Si c'est le cas, 「AAAAAA」 La partie de est l'ID de la feuille de calcul.
[Ici](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% Veuillez publier le script GAS en tant qu'API en vous référant à BC% E3% 81% BF). Si vous collez l'URL qui apparaît à la fin dans la partie suivante du script principal sensor_to_spreadsheet.py,
sensors_to_spreadsheet.Partie de py
def output_spreadsheet(all_values_dict):
#URL de l'API
url = 'URL de l'API GAS répertoriée ici'
Le script GAS est appelé lors de l'exécution de Sensor_to_spreadsheet.py La valeur est automatiquement écrite dans la feuille de calcul (l'en-tête est également ajouté automatiquement comme indiqué dans la section suivante).
Comme le montre la figure ci-dessous Nom de l'en-tête: [Nom de l'appareil_ signification de la valeur] → Ajouté automatiquement s'il n'existe pas Données: ajoutez une ligne à la fin de la valeur mesurée correspondant à l'en-tête ci-dessus. Nous avons créé un script GAS afin que les en-têtes puissent être générés et que les données puissent être écrites automatiquement.
C'est un problème d'exécuter le script à chaque fois pour obtenir la valeur du capteur, donc Automatisez en utilisant le package périodique "cron". Cela ressemble à un planificateur de tâches Windows.
Il peut être désactivé par défaut, alors activez-le en vous référant à here.
** ・ Point de contrôle 1: Vérifiez rsyslog.conf ** Cela ne fonctionnera pas si "cron" dans /etc/rsyslog.conf est commenté. Dans mon cas, il a été commenté comme ci-dessous, donc
###############
#### 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
Après avoir décommenté comme ci-dessous,
cron.* /var/log/cron.log
Redémarrez rsyslog avec la commande suivante.
sudo /etc/init.d/rsyslog restart
** ・ Point de contrôle 2: Modifier le niveau de journal ** Dans / etc / default / cron, spécifiez les éléments à décrire dans le journal lorsque cron est exécuté. Par défaut, il semble que le journal ne soit pas sorti comme indiqué ci-dessous, donc
# 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=""
Supprimer les commentaires de EXTRA_OPTS comme indiqué ci-dessous
EXTRA_OPTS='-L 15'
ça ira. Cela signifie que toutes les sorties sont 1 + 2 + 4 + 8 = 15.
Redémarrez cron avec la commande suivante.
sudo /etc/init.d/cron restart
Il réussit si cron.log est généré dans / var / log. (Veuillez cocher ici si vous voulez voir l'historique d'exécution de cron)
Enregistrer l'exécution périodique avec cron
** - Modifier crontab ** Ouvrez crontab avec la commande suivante
crontab -e
Lorsqu'on vous demande quel éditeur ouvrir, sélectionnez celui que vous aimez (nano recommandé pour les débutants)
# 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
: Diverses choses continuent
Il y a divers commentaires écrits comme ci-dessus, À la toute fin, décrivez le timing et la commande que vous souhaitez exécuter. Le format de synchronisation est [ici](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 Veuillez vous référer à% 9A.html)
Cette fois, puisqu'elle est exécutée toutes les 5 minutes, le contenu suivant est décrit.
*/5 * * * * [Chemin complet de Python] [sensors_to_spreadSheet.chemin complet de py] >/dev/null 2>&1
** ・ Démarrer cron ** Démarrez cron avec la commande suivante
sudo /etc/init.d/cron start
Attendez un peu ci-dessus et si la feuille de calcul est mise à jour toutes les 5 minutes, vous avez terminé!
Google Data Portal est un tableau de bord qui peut être modifié et affiché sur le cloud. Dans cette section, nous expliquerons comment créer la partie graphique de transition de température du tableau de bord au début.
** Spécifiez une feuille de calcul Google comme destination de la connexion de données ** ** Si vous êtes invité à approuver, appuyez sur le bouton d'approbation ** ** Il vous sera demandé la feuille de référence, alors spécifiez la feuille de calcul créée en ④ ** ** Appuyez sur "Ajouter au rapport" ** ** Renommer le rapport **
Créez un graphique pour voir les changements à long terme
** Cliquez sur Ressources → Gérer les sources de données ajoutées ** ** Cliquez sur "Modifier" pour la source de données cible ** ** Changez la date et l'heure dans un format reconnaissable ** (Le portail de données a un format strict de reconnaissance de la date et de l'heure) Si vous souhaitez obtenir les statistiques correctes telles que la moyenne: AAAAMMJJ Si vous souhaitez afficher la valeur mesurée pour chaque ligne: AAAAMMJJhhmm ** Modification du graphique de série chronologique ** ** Spécifiez la dimension (axe horizontal = Date_Master_Day) et l'indice (axe vertical = température des différents capteurs) ** ** Changer les statistiques de température du total à la moyenne ** ** Modifier pour masquer les valeurs manquantes **
Créez un graphique pour voir les changements à court terme
** Ajouter un graphique de série chronologique ** ** Spécifiez la dimension (axe horizontal = Date_Master_Day) et l'indice (axe vertical = température des différents capteurs) ** ** Changer les statistiques de température du total à la moyenne ** ** Modifier les valeurs manquantes pour terminer linéairement ** Ceci complète le graphique de transition de température!
À ce stade, seule la partie du tableau de bord d'ouverture entourée par le cadre rouge ci-dessous est terminée.
En plus de ce qui précède, il est nécessaire de faire un «graphique de transition d'humidité» et un «graphique circulaire» pour compléter. Je vais l'omettre car ce sera long si je vous explique tout.
Le graphique de transition d'humidité ne modifie que "l'indice" du graphique de transition de température de la température à l'humidité, donc je pense qu'il peut être créé sans colmatage particulier.
Comment créer un graphe circulaire est [ici](https://qiita.com/t-chi/items/01b9a9b98fbccef880c3#%E6%B9%BF%E5%BA%A6%E3%81%AE%E5%86% 86% E3% 82% B0% E3% 83% A9% E3% 83% 95) se résume de manière simple à comprendre.
Je suis désolé d'avoir fait un vol à la cuisine pendant 3 minutes en disant "Je ne sauterai pas!" Au début. Si vous faites de votre mieux pour compléter le graphique et organiser la mise en page, vous pouvez créer le tableau de bord suivant.
** C'est tout! !! ** ** Il y a peut-être des gens qui veulent se précipiter dans "Où est le supermarché!" Sans aucune pièce étrange. Personnellement, j'ai été impressionné par la combinaison de gadgets à bas prix et de services gratuits.
Après la cuisson, l'humidité monte et la température dessine une belle courbe ascendante avec les saisons, et il y a de nouvelles découvertes, donc je pense que la finition ne se lassera pas de regarder!
Comme pour tout système, le fonctionnement continu révèle une grande variété de problèmes qui n'ont pas été remarqués en un seul coup. Dans ce cas, j'ai été particulièrement agacé par ** l'instabilité de la réception Bluetooth **.
Après avoir traité chaque problème individuellement, il se reflète dans divers scripts. Avant les contre-mesures, d'innombrables arrêts et valeurs manquantes se sont produits, mais avec les contre-mesures suivantes, bien que les valeurs manquantes apparaissent rarement, il a été amélioré à ** un niveau où un fonctionnement continu peut être effectué sans presque aucune maintenance **!
inkbird_ibsth1.Partie de py
def get_ibsth1_data(self, macaddr, sensortype):
#Connectez-vous à l'appareil
peripheral = btle.Peripheral(macaddr)
#IBS-Lorsque TH1 mini
if sensortype == 'Inkbird_IBSTH1mini':
characteristic = peripheral.readCharacteristic(0x002d)
au dessus de peripheral = btle.Peripheral(macaddr) Au fait
BTLEException: failed to connect to peripheral addr type: public
Une erreur apparaît ou n'apparaît pas (il est gênant qu'il n'y ait pas de reproductibilité)
Si vous réexécutez, cela fonctionne souvent comme si de rien n'était, donc L'appelant prend l'exception avec try except et la réexécute jusqu'à un certain nombre de fois.
sensors_to_spreadsheet.py getdata_Fait partie de la méthode ibsth1
def getdata_ibsth1(device):
#Appareil maximum lorsqu'aucune valeur n'est disponible.Réessayer de répéter l'analyse
for i in range(device.Retry):
try:
sensorValue = GetIBSTH1Data().get_ibsth1_data(device.MacAddress, device.SensorType)
#Sortie de journal si une erreur se produit
except:
logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}]')
sensorValue = None
continue
else:
break
Le nombre de réexécutions est spécifié pour chaque périphérique dans la colonne "Réessayer" du fichier de paramètres "DeviceList.csv".
getdata_switchbot_Une partie de la méthode thermique
#Scannez pour obtenir la valeur du capteur
try:
scanner.scan(device.Timeout)
#Si vous obtenez une erreur lors de l'analyse, redémarrez l'adaptateur Bluetooth
except:
restart_hci0(masterdate, device.DeviceName)
Si vous répétez le scanner.scan () ci-dessus, il sera exécuté environ 20 fois.
bluepy.btle.BTLEManagementError: Failed to execute management command 'scan' (code: 11, error: Rejected)
Une erreur se produit et quel que soit le nombre de réexécutions, vous ne pourrez pas acquérir de données en raison d'une erreur. Parfois cela arrive 10 fois, parfois cela arrive 34 fois, et il n'y a pas de reproductibilité du nombre de fois, ce qui est très gênant. (La cause est inconnue, mais il semble qu'un comportement de type section soit accumulé)
Reportez-vous à here et utilisez la commande suivante (redémarrez l'adaptateur Bluetooth)
sudo hciconfig hci0 down
sudo hciconfig hci0 up
J'ai trouvé qu'il récupère de l'erreur lorsque j'exécute, alors référez-vous à ici
python
def restart_hci0(masterdate, devicename):
passwd = 'Entrer le mot de passe'
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}]')
J'ai fait une méthode appelée.
cette,
getdata_switchbot_Une partie de la méthode thermique
#Scannez pour obtenir la valeur du capteur
try:
scanner.scan(device.Timeout)
#Si vous obtenez une erreur lors de l'analyse, redémarrez l'adaptateur Bluetooth
except:
restart_hci0(masterdate, device.DeviceName)
C'est un mécanisme qui peut être exécuté lorsqu'une erreur se produit et redémarré et récupéré automatiquement.
La commande ci-dessus semble être efficace pour améliorer le bogue 1, j'ai donc essayé de l'exécuter même si la valeur ne pouvait pas être obtenue dans le nombre de tentatives. ** Redémarrer en cas de problème ** est un chapeau polyvalent!
Même si vous répétez la ré-exécution pour le bogue 1, vous ne pourrez peut-être pas obtenir la valeur dans le nombre de tentatives.
En outre, en mode de diffusion comme SwitchBot, les erreurs liées aux périphériques ne se produisent pas, mais le bogue 2 se produit, la valeur n'est pas renvoyée dans le délai d'expiration et la valeur peut ne pas être acquise dans le nombre de tentatives.
Si vous ne faites rien, vous ne vous rendrez pas compte que vous ne pouvez pas obtenir la valeur, et si vous ne pouvez pas obtenir la valeur tout le temps en raison d'un échec, vous ne pouvez pas suivre quand elle a échoué.
Enregistrez la valeur n'a pas pu être obtenue et affichez les 4 modèles de journaux suivants en vous référant à ici afin que la cause puisse être retracée plus tard. Je l'ai fait. ** Motif 1: journal de la partie de sortie lorsque le défaut 1 se produit **
sensors_to_spreadsheet.py getdata_omron_Fait partie de la méthode USB
#Sortie de journal si une erreur se produit
except:
logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}]')
** Motif 2: journal de la partie de sortie lorsque le défaut 2 se produit **
sensors_to_spreadsheet.redémarrage py_Fait partie de la méthode hci0
logging.error(f'restart bluetooth adapter [date{str(masterdate)}, device{devicename}]')
** Modèle 3: journal de la partie de sortie lorsque la valeur n'a pas pu être acquise même après une nouvelle tentative le nombre de fois spécifié **
sensors_to_spreadsheet.py getdata_omron_Fait partie de la méthode USB
#Si la valeur n'a pas pu être obtenue, enregistrez la sortie et redémarrez l'adaptateur Bluetooth
else:
logging.error(f'cannot get data [loop{str(device.Retry)}, date{str(masterdate)}, device{device.DeviceName}]')
** Modèle 4: journal de la partie sortie lorsqu'une série de traitements est terminée **
sensors_to_spreadsheet.La fin du code principal de py
#Sortie du journal de la fin du traitement
logging.info(f'[masterdate{str(masterdate)} startdate{str(startdate)} enddate{str(datetime.today())} success{str(success_num)}/{str(sensor_num)}]')
La destination de sortie du journal est spécifiée dans "Sortie du journal" du fichier de paramètres "config.ini".
Il était plus difficile d'assurer un fonctionnement stable à long terme que d'acquérir des données. En particulier, l'environnement de la réception Bluetooth est instable et les erreurs qui ne sont pas reproductibles sont impitoyablement déchirantes (rires) Le processus try-except est très utile pour résoudre le problème ci-dessus, mais si vous en abusez, vous ne remarquerez pas le problème, vous devez donc l'utiliser avec prudence.
En répertoriant la gestion des appareils dans un fichier de configuration et en automatisant la génération de colonnes de feuille de calcul Il est devenu beaucoup plus facile de réagir lors de l'augmentation du nombre de capteurs. Les exigences d'évolutivité sont souvent repoussées, mais j'ai réalisé que ce ne serait pas une douleur plus tard si vous y réfléchissiez dès les premiers stades de la conception!
Si vous essayez d'assurer la sécurité comme le masquage des mots de passe, la mise en œuvre et le fonctionnement seront compliqués, donc J'ai trouvé difficile d'équilibrer la relation de compromis avec la commodité. Il y a également un problème de confidentialité, car les données des capteurs peuvent indiquer dans une certaine mesure l'absence de personnes et ce qu'elles font. (C'est exactement la partie qui pose également problème dans la loi Super City)
L'Inkbird IBS-TH1 mini a montré beaucoup de mou, la batterie s'épuisant en moins d'un mois. Environ 2 jours avant l'épuisement de la batterie, des valeurs anormales (température = -46,2 ° C, humidité = 0%) comme la ligne rose de la figure ci-dessous ont commencé à se produire fréquemment.
S'il s'agit d'un signe reproductible d'une batterie déchargée, il s'agit d'une information très utile car elle peut être remplacée avant qu'elle ne soit complètement morte pour éviter les valeurs manquantes. Même si la batterie s'épuise la prochaine fois, j'observerai attentivement la situation juste avant.
Au fait, le niveau de batterie restant obtenu avec l'application Inkbird était d'environ 40% restant à ce stade, j'ai donc trouvé qu'il n'était pas du tout fiable (rires).
J'aimerais utiliser les connaissances ci-dessus pour un développement futur!
Recommended Posts