Först incheckning av scripten
This commit is contained in:
515
LYWSD03MMC.py
Executable file
515
LYWSD03MMC.py
Executable file
@@ -0,0 +1,515 @@
|
||||
#!/usr/bin/python3 -u
|
||||
#!/home/openhabian/Python3/Python-3.7.4/python -u
|
||||
#-u to unbuffer output. Otherwise when calling with nohup or redirecting output things are printed very lately or would even mixup
|
||||
|
||||
from bluepy import btle
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from collections import deque
|
||||
import threading
|
||||
import time
|
||||
import signal
|
||||
import traceback
|
||||
import math
|
||||
import logging
|
||||
|
||||
@dataclass
|
||||
class Measurement:
|
||||
temperature: float
|
||||
humidity: int
|
||||
voltage: float
|
||||
calibratedHumidity: int = 0
|
||||
battery: int = 0
|
||||
timestamp: int = 0
|
||||
sensorname: str = ""
|
||||
rssi: int = 0
|
||||
|
||||
def __eq__(self, other): #rssi may be different
|
||||
if self.temperature == other.temperature and self.humidity == other.humidity and self.calibratedHumidity == other.calibratedHumidity and self.battery == other.battery and self.voltage == other.voltage and self.sensorname == other.sensorname:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
measurements=deque()
|
||||
#globalBatteryLevel=0
|
||||
previousMeasurement=Measurement(0,0,0,0,0,0,0,0)
|
||||
identicalCounter=0
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
if args.atc:
|
||||
disable_le_scan(sock)
|
||||
os._exit(0)
|
||||
|
||||
def watchDog_Thread():
|
||||
global unconnectedTime
|
||||
global connected
|
||||
global pid
|
||||
while True:
|
||||
logging.debug("watchdog_Thread")
|
||||
logging.debug("unconnectedTime : " + str(unconnectedTime))
|
||||
logging.debug("connected : " + str(connected))
|
||||
logging.debug("pid : " + str(pid))
|
||||
now = int(time.time())
|
||||
if (unconnectedTime is not None) and ((now - unconnectedTime) > 60): #could also check connected is False, but this is more fault proof
|
||||
pstree=os.popen("pstree -p " + str(pid)).read() #we want to kill only bluepy from our own process tree, because other python scripts have there own bluepy-helper process
|
||||
logging.debug("PSTree: " + pstree)
|
||||
try:
|
||||
bluepypid=re.findall(r'bluepy-helper\((.*)\)',pstree)[0] #Store the bluepypid, to kill it later
|
||||
except IndexError: #Should not happen since we're now connected
|
||||
logging.debug("Couldn't find pid of bluepy-helper")
|
||||
os.system("kill " + bluepypid)
|
||||
logging.debug("Killed bluepy with pid: " + str(bluepypid))
|
||||
unconnectedTime = now #reset unconnectedTime to prevent multiple killings in a row
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
def thread_SendingData():
|
||||
global previousMeasurement
|
||||
global measurements
|
||||
path = os.path.dirname(os.path.abspath(__file__))
|
||||
while True:
|
||||
try:
|
||||
mea = measurements.popleft()
|
||||
if (mea == previousMeasurement and identicalCounter < args.skipidentical): #only send data when it has changed or X identical data has been skipped, ~10 pakets per minute, 50 pakets --> writing at least every 5 minutes
|
||||
print("Measurements are identical don't send data\n")
|
||||
identicalCounter+=1
|
||||
continue
|
||||
identicalCounter=0
|
||||
fmt = "sensorname,temperature,humidity,voltage" #don't try to seperate by semicolon ';' os.system will use that as command seperator
|
||||
if ' ' in mea.sensorname:
|
||||
sensorname = '"' + mea.sensorname + '"'
|
||||
else:
|
||||
sensorname = mea.sensorname
|
||||
params = sensorname + " " + str(mea.temperature) + " " + str(mea.humidity) + " " + str(mea.voltage)
|
||||
if (args.TwoPointCalibration or args.offset): #would be more efficient to generate fmt only once
|
||||
fmt +=",humidityCalibrated"
|
||||
params += " " + str(mea.calibratedHumidity)
|
||||
if (args.battery):
|
||||
fmt +=",batteryLevel"
|
||||
params += " " + str(mea.battery)
|
||||
if (args.rssi):
|
||||
fmt +=",rssi"
|
||||
params += " " + str(mea.rssi)
|
||||
params += " " + str(mea.timestamp)
|
||||
fmt +=",timestamp"
|
||||
cmd = path + "/" + args.callback + " " + fmt + " " + params
|
||||
print(cmd)
|
||||
ret = os.system(cmd)
|
||||
if (ret != 0):
|
||||
measurements.appendleft(mea) #put the measurement back
|
||||
print ("Data couln't be send to Callback, retrying...")
|
||||
time.sleep(5) #wait before trying again
|
||||
else: #data was sent
|
||||
previousMeasurement=Measurement(mea.temperature,mea.humidity,mea.voltage,mea.calibratedHumidity,mea.battery,0) #using copy or deepcopy requires implementation in the class definition
|
||||
|
||||
except IndexError:
|
||||
#print("Keine Daten")
|
||||
time.sleep(1)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print(traceback.format_exc())
|
||||
|
||||
sock = None #from ATC
|
||||
lastBLEPaketReceived = 0
|
||||
BLERestartCounter = 1
|
||||
def keepingLEScanRunning(): #LE-Scanning gets disabled sometimes, especially if you have a lot of BLE connections, this thread periodically enables BLE scanning again
|
||||
global BLERestartCounter
|
||||
while True:
|
||||
time.sleep(1)
|
||||
now = time.time()
|
||||
if now - lastBLEPaketReceived > args.watchdogtimer:
|
||||
print("Watchdog: Did not receive any BLE Paket within", int(now - lastBLEPaketReceived), "s. Restarting BLE scan. Count:", BLERestartCounter)
|
||||
disable_le_scan(sock)
|
||||
enable_le_scan(sock, filter_duplicates=False)
|
||||
BLERestartCounter += 1
|
||||
print("")
|
||||
time.sleep(5) #give some time to take effect
|
||||
|
||||
|
||||
def calibrateHumidity2Points(humidity, offset1, offset2, calpoint1, calpoint2):
|
||||
#offset1=args.offset1
|
||||
#offset2=args.offset2
|
||||
#p1y=args.calpoint1
|
||||
#p2y=args.calpoint2
|
||||
p1y=calpoint1
|
||||
p2y=calpoint2
|
||||
p1x=p1y - offset1
|
||||
p2x=p2y - offset2
|
||||
m = (p1y - p2y) * 1.0 / (p1x - p2x) # y=mx+b
|
||||
#b = (p1x * p2y - p2x * p1y) * 1.0 / (p1y - p2y)
|
||||
b = p2y - m * p2x #would be more efficient to do this calculations only once
|
||||
humidityCalibrated=m*humidity + b
|
||||
if (humidityCalibrated > 100 ): #with correct calibration this should not happen
|
||||
humidityCalibrated = 100
|
||||
elif (humidityCalibrated < 0):
|
||||
humidityCalibrated = 0
|
||||
humidityCalibrated=int(round(humidityCalibrated,0))
|
||||
return humidityCalibrated
|
||||
|
||||
|
||||
mode="round"
|
||||
class MyDelegate(btle.DefaultDelegate):
|
||||
def __init__(self, params):
|
||||
btle.DefaultDelegate.__init__(self)
|
||||
# ... initialise here
|
||||
|
||||
def handleNotification(self, cHandle, data):
|
||||
global measurements
|
||||
try:
|
||||
measurement = Measurement(0,0,0,0,0,0,0,0)
|
||||
if args.influxdb == 1:
|
||||
measurement.timestamp = int((time.time() // 10) * 10)
|
||||
else:
|
||||
measurement.timestamp = int(time.time())
|
||||
temp=int.from_bytes(data[0:2],byteorder='little',signed=True)/100
|
||||
#print("Temp received: " + str(temp))
|
||||
if args.round:
|
||||
#print("Temperatur unrounded: " + str(temp
|
||||
|
||||
if args.debounce:
|
||||
global mode
|
||||
temp*=10
|
||||
intpart = math.floor(temp)
|
||||
fracpart = round(temp - intpart,1)
|
||||
#print("Fracpart: " + str(fracpart))
|
||||
if fracpart >= 0.7:
|
||||
mode="ceil"
|
||||
elif fracpart <= 0.2: #either 0.8 and 0.3 or 0.7 and 0.2 for best even distribution
|
||||
mode="trunc"
|
||||
#print("Modus: " + mode)
|
||||
if mode=="trunc": #only a few times
|
||||
temp=math.trunc(temp)
|
||||
elif mode=="ceil":
|
||||
temp=math.ceil(temp)
|
||||
else:
|
||||
temp=round(temp,0)
|
||||
temp /=10.
|
||||
#print("Debounced temp: " + str(temp))
|
||||
else:
|
||||
temp=round(temp,1)
|
||||
humidity=int.from_bytes(data[2:3],byteorder='little')
|
||||
print("Temperature: " + str(temp))
|
||||
print("Humidity: " + str(humidity))
|
||||
voltage=int.from_bytes(data[3:5],byteorder='little') / 1000.
|
||||
print("Battery voltage:",voltage,"V")
|
||||
measurement.temperature = temp
|
||||
measurement.humidity = humidity
|
||||
measurement.voltage = voltage
|
||||
measurement.sensorname = args.name
|
||||
if args.battery:
|
||||
#measurement.battery = globalBatteryLevel
|
||||
batteryLevel = min(int(round((voltage - 2.1),2) * 100), 100) #3.1 or above --> 100% 2.1 --> 0 %
|
||||
measurement.battery = batteryLevel
|
||||
print("Battery level:",batteryLevel)
|
||||
|
||||
|
||||
if args.offset:
|
||||
humidityCalibrated = humidity + args.offset
|
||||
print("Calibrated humidity: " + str(humidityCalibrated))
|
||||
measurement.calibratedHumidity = humidityCalibrated
|
||||
|
||||
if args.TwoPointCalibration:
|
||||
humidityCalibrated= calibrateHumidity2Points(humidity,args.offset1,args.offset2, args.calpoint1, args.calpoint2)
|
||||
print("Calibrated humidity: " + str(humidityCalibrated))
|
||||
measurement.calibratedHumidity = humidityCalibrated
|
||||
|
||||
if(args.callback):
|
||||
measurements.append(measurement)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print("Fehler")
|
||||
print(e)
|
||||
print(traceback.format_exc())
|
||||
|
||||
# Initialisation -------
|
||||
|
||||
def connect():
|
||||
#print("Interface: " + str(args.interface))
|
||||
p = btle.Peripheral(adress,iface=args.interface)
|
||||
val=b'\x01\x00'
|
||||
p.writeCharacteristic(0x0038,val,True) #enable notifications of Temperature, Humidity and Battery voltage
|
||||
p.writeCharacteristic(0x0046,b'\xf4\x01\x00',True)
|
||||
p.withDelegate(MyDelegate("abc"))
|
||||
return p
|
||||
|
||||
# Main loop --------
|
||||
parser=argparse.ArgumentParser(allow_abbrev=False)
|
||||
parser.add_argument("--device","-d", help="Set the device MAC-Address in format AA:BB:CC:DD:EE:FF",metavar='AA:BB:CC:DD:EE:FF')
|
||||
parser.add_argument("--battery","-b", help="Get estimated battery level", metavar='', type=int, nargs='?', const=1)
|
||||
parser.add_argument("--count","-c", help="Read/Receive N measurements and then exit script", metavar='N', type=int)
|
||||
parser.add_argument("--interface","-i", help="Specifiy the interface number to use, e.g. 1 for hci1", metavar='N', type=int, default=0)
|
||||
parser.add_argument("--unreachable-count","-urc", help="Exit after N unsuccessful connection tries", metavar='N', type=int, default=0)
|
||||
|
||||
|
||||
rounding = parser.add_argument_group("Rounding and debouncing")
|
||||
rounding.add_argument("--round","-r", help="Round temperature to one decimal place",action='store_true')
|
||||
rounding.add_argument("--debounce","-deb", help="Enable this option to get more stable temperature values, requires -r option",action='store_true')
|
||||
|
||||
offsetgroup = parser.add_argument_group("Offset calibration mode")
|
||||
offsetgroup.add_argument("--offset","-o", help="Enter an offset to the reported humidity value",type=int)
|
||||
|
||||
complexCalibrationGroup=parser.add_argument_group("2 Point Calibration")
|
||||
complexCalibrationGroup.add_argument("--TwoPointCalibration","-2p", help="Use complex calibration mode. All arguments below are required",action='store_true')
|
||||
complexCalibrationGroup.add_argument("--calpoint1","-p1", help="Enter the first calibration point",type=int)
|
||||
complexCalibrationGroup.add_argument("--offset1","-o1", help="Enter the offset for the first calibration point",type=int)
|
||||
complexCalibrationGroup.add_argument("--calpoint2","-p2", help="Enter the second calibration point",type=int)
|
||||
complexCalibrationGroup.add_argument("--offset2","-o2", help="Enter the offset for the second calibration point",type=int)
|
||||
|
||||
callbackgroup = parser.add_argument_group("Callback related arguments")
|
||||
callbackgroup.add_argument("--callback","-call", help="Pass the path to a program/script that will be called on each new measurement")
|
||||
callbackgroup.add_argument("--name","-n", help="Give this sensor a name reported to the callback script")
|
||||
callbackgroup.add_argument("--skipidentical","-skip", help="N consecutive identical measurements won't be reported to callbackfunction",metavar='N', type=int, default=0)
|
||||
callbackgroup.add_argument("--influxdb","-infl", help="Optimize for writing data to influxdb,1 timestamp optimization, 2 integer optimization",metavar='N', type=int, default=0)
|
||||
|
||||
atcgroup = parser.add_argument_group("ATC mode related arguments")
|
||||
atcgroup.add_argument("--atc","-a", help="Read the data of devices with custom ATC firmware flashed",action='store_true')
|
||||
atcgroup.add_argument("--watchdogtimer","-wdt",metavar='X', type=int, help="Re-enable scanning after not receiving any BLE packet after X seconds")
|
||||
atcgroup.add_argument("--devicelistfile","-df",help="Specify a device list file giving further details to devices")
|
||||
atcgroup.add_argument("--onlydevicelist","-odl", help="Only read devices which are in the device list file",action='store_true')
|
||||
atcgroup.add_argument("--rssi","-rs", help="Report RSSI via callback",action='store_true')
|
||||
|
||||
|
||||
args=parser.parse_args()
|
||||
if args.device:
|
||||
if re.match("[0-9a-fA-F]{2}([:]?)[0-9a-fA-F]{2}(\\1[0-9a-fA-F]{2}){4}$",args.device):
|
||||
adress=args.device
|
||||
else:
|
||||
print("Please specify device MAC-Address in format AA:BB:CC:DD:EE:FF")
|
||||
os._exit(1)
|
||||
elif not args.atc:
|
||||
parser.print_help()
|
||||
os._exit(1)
|
||||
|
||||
if args.TwoPointCalibration:
|
||||
if(not(args.calpoint1 and args.offset1 and args.calpoint2 and args.offset2)):
|
||||
print("In 2 Point calibration you have to enter 4 points")
|
||||
os._exit(1)
|
||||
elif(args.offset):
|
||||
print("Offset calibration and 2 Point calibration can't be used together")
|
||||
os._exit(1)
|
||||
if not args.name:
|
||||
args.name = args.device
|
||||
|
||||
if args.callback:
|
||||
dataThread = threading.Thread(target=thread_SendingData)
|
||||
dataThread.start()
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
if args.device:
|
||||
|
||||
p=btle.Peripheral()
|
||||
cnt=0
|
||||
|
||||
|
||||
connected=False
|
||||
#logging.basicConfig(level=logging.DEBUG)
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
logging.debug("Debug: Starting script...")
|
||||
pid=os.getpid()
|
||||
bluepypid=None
|
||||
unconnectedTime=None
|
||||
connectionLostCounter=0
|
||||
|
||||
watchdogThread = threading.Thread(target=watchDog_Thread)
|
||||
watchdogThread.start()
|
||||
logging.debug("watchdogThread started")
|
||||
|
||||
while True:
|
||||
try:
|
||||
if not connected:
|
||||
#Bluepy sometimes hangs and makes it even impossible to connect with gatttool as long it is running
|
||||
#on every new connection a new bluepy-helper is called
|
||||
#we now make sure that the old one is really terminated. Even if it hangs a simple kill signal was sufficient to terminate it
|
||||
# if bluepypid is not None:
|
||||
# os.system("kill " + bluepypid)
|
||||
# print("Killed possibly remaining bluepy-helper")
|
||||
# else:
|
||||
# print("bluepy-helper couldn't be determined, killing not allowed")
|
||||
|
||||
print("Trying to connect to " + adress)
|
||||
p=connect()
|
||||
# logging.debug("Own PID: " + str(pid))
|
||||
# pstree=os.popen("pstree -p " + str(pid)).read() #we want to kill only bluepy from our own process tree, because other python scripts have there own bluepy-helper process
|
||||
# logging.debug("PSTree: " + pstree)
|
||||
# try:
|
||||
# bluepypid=re.findall(r'bluepy-helper\((.*)\)',pstree)[0] #Store the bluepypid, to kill it later
|
||||
# except IndexError: #Should not happen since we're now connected
|
||||
# logging.debug("Couldn't find pid of bluepy-helper")
|
||||
connected=True
|
||||
unconnectedTime=None
|
||||
|
||||
# if args.battery:
|
||||
# if(cnt % args.battery == 0):
|
||||
# print("Warning the battery option is deprecated, Aqara device always reports 99 % battery")
|
||||
# batt=p.readCharacteristic(0x001b)
|
||||
# batt=int.from_bytes(batt,byteorder="little")
|
||||
# print("Battery-Level: " + str(batt))
|
||||
# globalBatteryLevel = batt
|
||||
|
||||
|
||||
if p.waitForNotifications(2000):
|
||||
# handleNotification() was called
|
||||
|
||||
cnt += 1
|
||||
if args.count is not None and cnt >= args.count:
|
||||
print(str(args.count) + " measurements collected. Exiting in a moment.")
|
||||
p.disconnect()
|
||||
time.sleep(5)
|
||||
#It seems that sometimes bluepy-helper remains and thus prevents a reconnection, so we try killing our own bluepy-helper
|
||||
pstree=os.popen("pstree -p " + str(pid)).read() #we want to kill only bluepy from our own process tree, because other python scripts have there own bluepy-helper process
|
||||
bluepypid=0
|
||||
try:
|
||||
bluepypid=re.findall(r'bluepy-helper\((.*)\)',pstree)[0] #Store the bluepypid, to kill it later
|
||||
except IndexError: #Should normally occur because we're disconnected
|
||||
logging.debug("Couldn't find pid of bluepy-helper")
|
||||
if bluepypid != 0:
|
||||
os.system("kill " + bluepypid)
|
||||
logging.debug("Killed bluepy with pid: " + str(bluepypid))
|
||||
os._exit(0)
|
||||
print("")
|
||||
continue
|
||||
except Exception as e:
|
||||
print("Connection lost")
|
||||
connectionLostCounter +=1
|
||||
if connected is True: #First connection abort after connected
|
||||
unconnectedTime=int(time.time())
|
||||
connected=False
|
||||
if args.unreachable_count != 0 and connectionLostCounter >= args.unreachable_count:
|
||||
print("Maximum numbers of unsuccessful connections reaches, exiting")
|
||||
os._exit(0)
|
||||
time.sleep(1)
|
||||
logging.debug(e)
|
||||
logging.debug(traceback.format_exc())
|
||||
|
||||
print ("Waiting...")
|
||||
# Perhaps do something else here
|
||||
|
||||
elif args.atc:
|
||||
print("Script started in ATC Mode")
|
||||
print("----------------------------")
|
||||
print("In this mode all devices within reach are read out, unless a namefile and --namefileonlydevices is specified.")
|
||||
print("Also --name Argument is ignored, if you require names, please use --namefile.")
|
||||
print("In this mode rounding and debouncing are not available, since ATC firmware sends out only one decimal place.")
|
||||
print("ATC mode usually requires root rights. If you want to use it with normal user rights, \nplease execute \"sudo setcap cap_net_raw,cap_net_admin+eip $(eval readlink -f `which python3`)\"")
|
||||
print("You have to redo this step if you upgrade your python version.")
|
||||
print("----------------------------")
|
||||
|
||||
import sys
|
||||
import bluetooth._bluetooth as bluez
|
||||
|
||||
from bluetooth_utils import (toggle_device,
|
||||
enable_le_scan, parse_le_advertising_events,
|
||||
disable_le_scan, raw_packet_to_str)
|
||||
|
||||
advCounter=dict()
|
||||
sensors = dict()
|
||||
if args.devicelistfile:
|
||||
import configparser
|
||||
if not os.path.exists(args.devicelistfile):
|
||||
print ("Error specified device list file '",args.devicelistfile,"' not found")
|
||||
os._exit(1)
|
||||
sensors = configparser.ConfigParser()
|
||||
sensors.read(args.devicelistfile)
|
||||
|
||||
if args.onlydevicelist and not args.devicelistfile:
|
||||
print("Error: --onlydevicelist requires --devicelistfile <devicelistfile>")
|
||||
os._exit(1)
|
||||
|
||||
dev_id = args.interface # the bluetooth device is hci0
|
||||
toggle_device(dev_id, True)
|
||||
|
||||
try:
|
||||
sock = bluez.hci_open_dev(dev_id)
|
||||
except:
|
||||
print("Cannot open bluetooth device %i" % dev_id)
|
||||
raise
|
||||
|
||||
enable_le_scan(sock, filter_duplicates=False)
|
||||
|
||||
try:
|
||||
prev_data = None
|
||||
|
||||
def le_advertise_packet_handler(mac, adv_type, data, rssi):
|
||||
global lastBLEPaketReceived
|
||||
if args.watchdogtimer:
|
||||
lastBLEPaketReceived = time.time()
|
||||
lastBLEPaketReceived = time.time()
|
||||
#print("reveived BLE packet")
|
||||
data_str = raw_packet_to_str(data)
|
||||
ATCPaketMAC = data_str[10:22].upper()
|
||||
macStr = mac.replace(":","").upper()
|
||||
atcIdentifier = data_str[6:10].upper()
|
||||
if(atcIdentifier == "1A18" and ATCPaketMAC == macStr) and not args.onlydevicelist or (atcIdentifier == "1A18" and mac in sensors): #only Data from ATC devices, double checked
|
||||
advNumber = data_str[-2:]
|
||||
if macStr in advCounter:
|
||||
lastAdvNumber = advCounter[macStr]
|
||||
else:
|
||||
lastAdvNumber = None
|
||||
if lastAdvNumber == None or lastAdvNumber != advNumber:
|
||||
advCounter[macStr] = advNumber
|
||||
print("BLE packet: %s %02x %s %d" % (mac, adv_type, data_str, rssi))
|
||||
#print("AdvNumber: ", advNumber)
|
||||
#temp = data_str[22:26].encode('utf-8')
|
||||
#temperature = int.from_bytes(bytearray.fromhex(data_str[22:26]),byteorder='big') / 10.
|
||||
global measurements
|
||||
measurement = Measurement(0,0,0,0,0,0,0,0)
|
||||
if args.influxdb == 1:
|
||||
measurement.timestamp = int((time.time() // 10) * 10)
|
||||
else:
|
||||
measurement.timestamp = int(time.time())
|
||||
|
||||
|
||||
#temperature = int(data_str[22:26],16) / 10.
|
||||
temperature = int.from_bytes(bytearray.fromhex(data_str[22:26]),byteorder='big',signed=True) / 10.
|
||||
print("Temperature: ", temperature)
|
||||
humidity = int(data_str[26:28], 16)
|
||||
print("Humidity: ", humidity)
|
||||
batteryVoltage = int(data_str[30:34], 16) / 1000
|
||||
print ("Battery voltage:", batteryVoltage,"V")
|
||||
print ("RSSI:", rssi, "dBm")
|
||||
|
||||
if args.battery:
|
||||
batteryPercent = int(data_str[28:30], 16)
|
||||
print ("Battery:", batteryPercent,"%")
|
||||
measurement.battery = batteryPercent
|
||||
measurement.humidity = humidity
|
||||
measurement.temperature = temperature
|
||||
measurement.voltage = batteryVoltage
|
||||
measurement.rssi = rssi
|
||||
|
||||
if mac in sensors:
|
||||
try:
|
||||
measurement.sensorname = sensors[mac]["sensorname"]
|
||||
except:
|
||||
measurement.sensorname = mac
|
||||
if "offset1" in sensors[mac] and "offset2" in sensors[mac] and "calpoint1" in sensors[mac] and "calpoint2" in sensors[mac]:
|
||||
measurement.humidity = calibrateHumidity2Points(humidity,int(sensors[mac]["offset1"]),int(sensors[mac]["offset2"]),int(sensors[mac]["calpoint1"]),int(sensors[mac]["calpoint2"]))
|
||||
print ("Humidity calibrated (2 points calibration): ", measurement.humidity)
|
||||
elif "humidityOffset" in sensors[mac]:
|
||||
measurement.humidity = humidity + int(sensors[mac]["humidityOffset"])
|
||||
print ("Humidity calibrated (offset calibration): ", measurement.humidity)
|
||||
else:
|
||||
measurement.sensorname = mac
|
||||
if(args.callback):
|
||||
measurements.append(measurement)
|
||||
#print("Length:", len(measurements))
|
||||
print("")
|
||||
|
||||
if args.watchdogtimer:
|
||||
keepingLEScanRunningThread = threading.Thread(target=keepingLEScanRunning)
|
||||
keepingLEScanRunningThread.start()
|
||||
logging.debug("keepingLEScanRunningThread started")
|
||||
|
||||
|
||||
|
||||
# Blocking call (the given handler will be called each time a new LE
|
||||
# advertisement packet is detected)
|
||||
parse_le_advertising_events(sock,
|
||||
handler=le_advertise_packet_handler,
|
||||
debug=False)
|
||||
except KeyboardInterrupt:
|
||||
disable_le_scan(sock)
|
||||
421
bluetooth_utils.py
Normal file
421
bluetooth_utils.py
Normal file
@@ -0,0 +1,421 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# This file is from https://github.com/colin-guyon/py-bluetooth-utils
|
||||
# published under MIT License
|
||||
|
||||
# MIT License
|
||||
|
||||
# Copyright (c) 2020 Colin GUYON
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
"""
|
||||
Module containing some bluetooth utility functions (linux only).
|
||||
|
||||
It either uses HCI commands using PyBluez, or does ioctl calls like it's
|
||||
done in Bluez tools such as hciconfig.
|
||||
|
||||
Main functions:
|
||||
- toggle_device : enable or disable a bluetooth device
|
||||
- set_scan : set scan type on a device ("noscan", "iscan", "pscan", "piscan")
|
||||
- enable/disable_le_scan : enable BLE scanning
|
||||
- parse_le_advertising_events : parse and read BLE advertisements packets
|
||||
- start/stop_le_advertising : advertise custom data using BLE
|
||||
|
||||
Bluez : http://www.bluez.org/
|
||||
PyBluez : http://karulis.github.io/pybluez/
|
||||
|
||||
The module was in particular inspired from 'iBeacon-Scanner-'
|
||||
https://github.com/switchdoclabs/iBeacon-Scanner-/blob/master/blescan.py
|
||||
and sometimes directly from the Bluez sources.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
import sys
|
||||
import struct
|
||||
import fcntl
|
||||
import array
|
||||
import socket
|
||||
from errno import EALREADY
|
||||
|
||||
# import PyBluez
|
||||
import bluetooth._bluetooth as bluez
|
||||
|
||||
__all__ = ('toggle_device', 'set_scan',
|
||||
'enable_le_scan', 'disable_le_scan', 'parse_le_advertising_events',
|
||||
'start_le_advertising', 'stop_le_advertising',
|
||||
'raw_packet_to_str')
|
||||
|
||||
LE_META_EVENT = 0x3E
|
||||
LE_PUBLIC_ADDRESS = 0x00
|
||||
LE_RANDOM_ADDRESS = 0x01
|
||||
|
||||
OGF_LE_CTL = 0x08
|
||||
OCF_LE_SET_SCAN_PARAMETERS = 0x000B
|
||||
OCF_LE_SET_SCAN_ENABLE = 0x000C
|
||||
OCF_LE_CREATE_CONN = 0x000D
|
||||
OCF_LE_SET_ADVERTISING_PARAMETERS = 0x0006
|
||||
OCF_LE_SET_ADVERTISE_ENABLE = 0x000A
|
||||
OCF_LE_SET_ADVERTISING_DATA = 0x0008
|
||||
|
||||
SCAN_TYPE_PASSIVE = 0x00
|
||||
SCAN_FILTER_DUPLICATES = 0x01
|
||||
SCAN_DISABLE = 0x00
|
||||
SCAN_ENABLE = 0x01
|
||||
|
||||
# sub-events of LE_META_EVENT
|
||||
EVT_LE_CONN_COMPLETE = 0x01
|
||||
EVT_LE_ADVERTISING_REPORT = 0x02
|
||||
EVT_LE_CONN_UPDATE_COMPLETE = 0x03
|
||||
EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE = 0x04
|
||||
|
||||
# Advertisement event types
|
||||
ADV_IND = 0x00
|
||||
ADV_DIRECT_IND = 0x01
|
||||
ADV_SCAN_IND = 0x02
|
||||
ADV_NONCONN_IND = 0x03
|
||||
ADV_SCAN_RSP = 0x04
|
||||
|
||||
# Allow Scan Request from Any, Connect Request from Any
|
||||
FILTER_POLICY_NO_WHITELIST = 0x00
|
||||
# Allow Scan Request from White List Only, Connect Request from Any
|
||||
FILTER_POLICY_SCAN_WHITELIST = 0x01
|
||||
# Allow Scan Request from Any, Connect Request from White List Only
|
||||
FILTER_POLICY_CONN_WHITELIST = 0x02
|
||||
# Allow Scan Request from White List Only, Connect Request from White List Only
|
||||
FILTER_POLICY_SCAN_AND_CONN_WHITELIST = 0x03
|
||||
|
||||
|
||||
def toggle_device(dev_id, enable):
|
||||
"""
|
||||
Power ON or OFF a bluetooth device.
|
||||
|
||||
:param dev_id: Device id.
|
||||
:type dev_id: ``int``
|
||||
:param enable: Whether to enable of disable the device.
|
||||
:type enable: ``bool``
|
||||
"""
|
||||
hci_sock = socket.socket(socket.AF_BLUETOOTH,
|
||||
socket.SOCK_RAW,
|
||||
socket.BTPROTO_HCI)
|
||||
print("Power %s bluetooth device %d" % ('ON' if enable else 'OFF', dev_id))
|
||||
# di = struct.pack("HbBIBBIIIHHHH10I", dev_id, *((0,) * 22))
|
||||
# fcntl.ioctl(hci_sock.fileno(), bluez.HCIGETDEVINFO, di)
|
||||
req_str = struct.pack("H", dev_id)
|
||||
request = array.array("b", req_str)
|
||||
try:
|
||||
fcntl.ioctl(hci_sock.fileno(),
|
||||
bluez.HCIDEVUP if enable else bluez.HCIDEVDOWN,
|
||||
request[0])
|
||||
except IOError as e:
|
||||
if e.errno == EALREADY:
|
||||
print("Bluetooth device %d is already %s" % (
|
||||
dev_id, 'enabled' if enable else 'disabled'))
|
||||
else:
|
||||
raise
|
||||
finally:
|
||||
hci_sock.close()
|
||||
|
||||
|
||||
# Types of bluetooth scan
|
||||
SCAN_DISABLED = 0x00
|
||||
SCAN_INQUIRY = 0x01
|
||||
SCAN_PAGE = 0x02
|
||||
|
||||
|
||||
def set_scan(dev_id, scan_type):
|
||||
"""
|
||||
Set scan type on a given bluetooth device.
|
||||
|
||||
:param dev_id: Device id.
|
||||
:type dev_id: ``int``
|
||||
:param scan_type: One of
|
||||
``'noscan'``
|
||||
``'iscan'``
|
||||
``'pscan'``
|
||||
``'piscan'``
|
||||
:type scan_type: ``str``
|
||||
"""
|
||||
hci_sock = socket.socket(socket.AF_BLUETOOTH,
|
||||
socket.SOCK_RAW,
|
||||
socket.BTPROTO_HCI)
|
||||
if scan_type == "noscan":
|
||||
dev_opt = SCAN_DISABLED
|
||||
elif scan_type == "iscan":
|
||||
dev_opt = SCAN_INQUIRY
|
||||
elif scan_type == "pscan":
|
||||
dev_opt = SCAN_PAGE
|
||||
elif scan_type == "piscan":
|
||||
dev_opt = SCAN_PAGE | SCAN_INQUIRY
|
||||
else:
|
||||
raise ValueError("Unknown scan type %r" % scan_type)
|
||||
|
||||
req_str = struct.pack("HI", dev_id, dev_opt)
|
||||
print("Set scan type %r to bluetooth device %d" % (scan_type, dev_id))
|
||||
try:
|
||||
fcntl.ioctl(hci_sock.fileno(), bluez.HCISETSCAN, req_str)
|
||||
finally:
|
||||
hci_sock.close()
|
||||
|
||||
|
||||
def raw_packet_to_str(pkt):
|
||||
"""
|
||||
Returns the string representation of a raw HCI packet.
|
||||
"""
|
||||
if sys.version_info > (3, 0):
|
||||
return ''.join('%02x' % struct.unpack("B", bytes([x]))[0] for x in pkt)
|
||||
else:
|
||||
return ''.join('%02x' % struct.unpack("B", x)[0] for x in pkt)
|
||||
|
||||
|
||||
def enable_le_scan(sock, interval=0x0800, window=0x0800,
|
||||
filter_policy=FILTER_POLICY_NO_WHITELIST,
|
||||
filter_duplicates=True):
|
||||
"""
|
||||
Enable LE passive scan (with filtering of duplicate packets enabled).
|
||||
|
||||
:param sock: A bluetooth HCI socket (retrieved using the
|
||||
``hci_open_dev`` PyBluez function).
|
||||
:param interval: Scan interval.
|
||||
:param window: Scan window (must be less or equal than given interval).
|
||||
:param filter_policy: One of
|
||||
``FILTER_POLICY_NO_WHITELIST`` (default value)
|
||||
``FILTER_POLICY_SCAN_WHITELIST``
|
||||
``FILTER_POLICY_CONN_WHITELIST``
|
||||
``FILTER_POLICY_SCAN_AND_CONN_WHITELIST``
|
||||
|
||||
.. note:: Scan interval and window are to multiply by 0.625 ms to
|
||||
get the real time duration.
|
||||
"""
|
||||
print("Enable LE scan")
|
||||
own_bdaddr_type = LE_PUBLIC_ADDRESS # does not work with LE_RANDOM_ADDRESS
|
||||
cmd_pkt = struct.pack("<BHHBB", SCAN_TYPE_PASSIVE, interval, window,
|
||||
own_bdaddr_type, filter_policy)
|
||||
bluez.hci_send_cmd(sock, OGF_LE_CTL, OCF_LE_SET_SCAN_PARAMETERS, cmd_pkt)
|
||||
print("scan params: interval=%.3fms window=%.3fms own_bdaddr=%s "
|
||||
"whitelist=%s" %
|
||||
(interval * 0.625, window * 0.625,
|
||||
'public' if own_bdaddr_type == LE_PUBLIC_ADDRESS else 'random',
|
||||
'yes' if filter_policy in (FILTER_POLICY_SCAN_WHITELIST,
|
||||
FILTER_POLICY_SCAN_AND_CONN_WHITELIST)
|
||||
else 'no'))
|
||||
cmd_pkt = struct.pack("<BB", SCAN_ENABLE, SCAN_FILTER_DUPLICATES if filter_duplicates else 0x00)
|
||||
bluez.hci_send_cmd(sock, OGF_LE_CTL, OCF_LE_SET_SCAN_ENABLE, cmd_pkt)
|
||||
|
||||
|
||||
def disable_le_scan(sock):
|
||||
"""
|
||||
Disable LE scan.
|
||||
|
||||
:param sock: A bluetooth HCI socket (retrieved using the
|
||||
``hci_open_dev`` PyBluez function).
|
||||
"""
|
||||
print("Disable LE scan")
|
||||
cmd_pkt = struct.pack("<BB", SCAN_DISABLE, 0x00)
|
||||
bluez.hci_send_cmd(sock, OGF_LE_CTL, OCF_LE_SET_SCAN_ENABLE, cmd_pkt)
|
||||
|
||||
|
||||
def start_le_advertising(sock, min_interval=1000, max_interval=1000,
|
||||
adv_type=ADV_NONCONN_IND, data=()):
|
||||
"""
|
||||
Start LE advertising.
|
||||
|
||||
:param sock: A bluetooth HCI socket (retrieved using the
|
||||
``hci_open_dev`` PyBluez function).
|
||||
:param min_interval: Minimum advertising interval.
|
||||
:param max_interval: Maximum advertising interval.
|
||||
:param adv_type: Advertisement type (``ADV_NONCONN_IND`` by default).
|
||||
:param data: The advertisement data (maximum of 31 bytes).
|
||||
:type data: iterable
|
||||
"""
|
||||
own_bdaddr_type = 0
|
||||
direct_bdaddr_type = 0
|
||||
direct_bdaddr = (0,) * 6
|
||||
chan_map = 0x07 # All channels: 37, 38, 39
|
||||
filter = 0
|
||||
|
||||
struct_params = [min_interval, max_interval, adv_type, own_bdaddr_type,
|
||||
direct_bdaddr_type]
|
||||
struct_params.extend(direct_bdaddr)
|
||||
struct_params.extend((chan_map, filter))
|
||||
|
||||
cmd_pkt = struct.pack("<HHBBB6BBB", *struct_params)
|
||||
bluez.hci_send_cmd(sock, OGF_LE_CTL, OCF_LE_SET_ADVERTISING_PARAMETERS,
|
||||
cmd_pkt)
|
||||
|
||||
cmd_pkt = struct.pack("<B", 0x01)
|
||||
bluez.hci_send_cmd(sock, OGF_LE_CTL, OCF_LE_SET_ADVERTISE_ENABLE, cmd_pkt)
|
||||
|
||||
data_length = len(data)
|
||||
if data_length > 31:
|
||||
raise ValueError("data is too long (%d but max is 31 bytes)",
|
||||
data_length)
|
||||
cmd_pkt = struct.pack("<B%dB" % data_length, data_length, *data)
|
||||
bluez.hci_send_cmd(sock, OGF_LE_CTL, OCF_LE_SET_ADVERTISING_DATA, cmd_pkt)
|
||||
print("Advertising started data_length=%d data=%r" % (data_length, data))
|
||||
|
||||
|
||||
def stop_le_advertising(sock):
|
||||
"""
|
||||
Stop LE advertising.
|
||||
|
||||
:param sock: A bluetooth HCI socket (retrieved using the
|
||||
``hci_open_dev`` PyBluez function).
|
||||
"""
|
||||
cmd_pkt = struct.pack("<B", 0x00)
|
||||
bluez.hci_send_cmd(sock, OGF_LE_CTL, OCF_LE_SET_ADVERTISE_ENABLE, cmd_pkt)
|
||||
print("Advertising stopped")
|
||||
|
||||
|
||||
def parse_le_advertising_events(sock, mac_addr=None, packet_length=None,
|
||||
handler=None, debug=False):
|
||||
"""
|
||||
Parse and report LE advertisements.
|
||||
|
||||
This is a blocking call, an infinite loop is started and the
|
||||
given handler will be called each time a new LE advertisement packet
|
||||
is detected and corresponds to the given filters.
|
||||
|
||||
.. note:: The :func:`.start_le_advertising` function must be
|
||||
called before calling this function.
|
||||
|
||||
:param sock: A bluetooth HCI socket (retrieved using the
|
||||
``hci_open_dev`` PyBluez function).
|
||||
:param mac_addr: list of filtered mac address representations
|
||||
(uppercase, with ':' separators).
|
||||
If not specified, the LE advertisement of any device will be reported.
|
||||
Example: mac_addr=('00:2A:5F:FF:25:11', 'DA:FF:12:33:66:12')
|
||||
:type mac_addr: ``list`` of ``string``
|
||||
:param packet_length: Filter a specific length of LE advertisement packet.
|
||||
:type packet_length: ``int``
|
||||
:param handler: Handler that will be called each time a LE advertisement
|
||||
packet is available (in accordance with the ``mac_addr``
|
||||
and ``packet_length`` filters).
|
||||
:type handler: ``callable`` taking 4 parameters:
|
||||
mac (``str``), adv_type (``int``), data (``bytes``) and rssi (``int``)
|
||||
:param debug: Enable debug prints.
|
||||
:type debug: ``bool``
|
||||
"""
|
||||
if not debug and handler is None:
|
||||
raise ValueError("You must either enable debug or give a handler !")
|
||||
|
||||
old_filter = sock.getsockopt(bluez.SOL_HCI, bluez.HCI_FILTER, 14)
|
||||
|
||||
flt = bluez.hci_filter_new()
|
||||
bluez.hci_filter_set_ptype(flt, bluez.HCI_EVENT_PKT)
|
||||
# bluez.hci_filter_all_events(flt)
|
||||
bluez.hci_filter_set_event(flt, LE_META_EVENT)
|
||||
sock.setsockopt(bluez.SOL_HCI, bluez.HCI_FILTER, flt)
|
||||
|
||||
print("socket filter set to ptype=HCI_EVENT_PKT event=LE_META_EVENT")
|
||||
print("Listening ...")
|
||||
|
||||
try:
|
||||
while True:
|
||||
pkt = full_pkt = sock.recv(255)
|
||||
ptype, event, plen = struct.unpack("BBB", pkt[:3])
|
||||
|
||||
if event != LE_META_EVENT:
|
||||
# Should never occur because we filtered with this type of event
|
||||
print("Not a LE_META_EVENT !")
|
||||
continue
|
||||
|
||||
sub_event, = struct.unpack("B", pkt[3:4])
|
||||
if sub_event != EVT_LE_ADVERTISING_REPORT:
|
||||
if debug:
|
||||
print("Not a EVT_LE_ADVERTISING_REPORT !")
|
||||
continue
|
||||
|
||||
pkt = pkt[4:]
|
||||
adv_type = struct.unpack("b", pkt[1:2])[0]
|
||||
mac_addr_str = bluez.ba2str(pkt[3:9])
|
||||
|
||||
if packet_length and plen != packet_length:
|
||||
# ignore this packet
|
||||
if debug:
|
||||
print("packet with non-matching length: mac=%s adv_type=%02x plen=%s" %
|
||||
(mac_addr_str, adv_type, plen))
|
||||
print(raw_packet_to_str(pkt))
|
||||
continue
|
||||
|
||||
data = pkt[9:-1]
|
||||
|
||||
rssi = struct.unpack("b", full_pkt[len(full_pkt)-1:len(full_pkt)])[0]
|
||||
|
||||
if mac_addr and mac_addr_str not in mac_addr:
|
||||
if debug:
|
||||
print("packet with non-matching mac %s adv_type=%02x data=%s RSSI=%s" %
|
||||
(mac_addr_str, adv_type, raw_packet_to_str(data), rssi))
|
||||
continue
|
||||
|
||||
if debug:
|
||||
print("LE advertisement: mac=%s adv_type=%02x data=%s RSSI=%d" %
|
||||
(mac_addr_str, adv_type, raw_packet_to_str(data), rssi))
|
||||
|
||||
if handler is not None:
|
||||
try:
|
||||
handler(mac_addr_str, adv_type, data, rssi)
|
||||
except Exception as e:
|
||||
print('Exception when calling handler with a BLE advertising event: %r' % (e,))
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\nRestore previous socket filter")
|
||||
sock.setsockopt(bluez.SOL_HCI, bluez.HCI_FILTER, old_filter)
|
||||
raise
|
||||
|
||||
"""
|
||||
def hci_le_add_white_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to)
|
||||
{
|
||||
struct hci_request {
|
||||
uint16_t ogf;
|
||||
uint16_t ocf;
|
||||
int event;
|
||||
void *cparam;
|
||||
int clen;
|
||||
void *rparam;
|
||||
int rlen;
|
||||
};
|
||||
|
||||
struct hci_request rq;
|
||||
le_add_device_to_white_list_cp cp;
|
||||
uint8_t status;
|
||||
|
||||
memset(&cp, 0, sizeof(cp));
|
||||
cp.bdaddr_type = type;
|
||||
bacpy(&cp.bdaddr, bdaddr);
|
||||
|
||||
memset(&rq, 0, sizeof(rq));
|
||||
rq.ogf = OGF_LE_CTL;
|
||||
rq.ocf = OCF_LE_ADD_DEVICE_TO_WHITE_LIST;
|
||||
rq.cparam = &cp;
|
||||
rq.clen = LE_ADD_DEVICE_TO_WHITE_LIST_CP_SIZE;
|
||||
rq.rparam = &status;
|
||||
rq.rlen = 1;
|
||||
|
||||
if (hci_send_req(dd, &rq, to) < 0)
|
||||
return -1;
|
||||
|
||||
if (status) {
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}"""
|
||||
20
sendToMQTT.sh
Executable file
20
sendToMQTT.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
#This script is provided by Chiunownow https://github.com/Chiunownow
|
||||
#Thank you very much for providing this script
|
||||
#This script is
|
||||
|
||||
#use e.g with that script: MySensor.sh
|
||||
#!/bin/bash
|
||||
#DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
#$DIR/LYWSD03MMC.py -d <device> -b 1000 -r --debounce --skipidentical 50 --name MySensor --callback sendToMQTT
|
||||
|
||||
#mosquitto_pub -h mqtt.host -t "MiTemperature2/$2/temp" -u mqtt.username -P mqtt.passwd -i "mibridge" -m "$3"
|
||||
#mosquitto_pub -h mqtt.host -t "MiTemperature2/$2/humidity" -u mqtt.username -P mqtt.passwd -i "mibridge" -m "$4"
|
||||
#mosquitto_pub -h mqtt.host -t "MiTemperature2/$2/batteryvoltage" -u mqtt.username -P mqtt.passwd -i "mibridge" -m "$5"
|
||||
#mosquitto_pub -h mqtt.host -t "MiTemperature2/$2/batterylevel" -u mqtt.username -P mqtt.passwd -i "mibridge" -m "$6"
|
||||
|
||||
mosquitto_pub -h 192.168.0.114 -t "MiTemperature2/$2/temp" -u hasse -P casablanca -i "mibridge" -m "$3"
|
||||
mosquitto_pub -h 192.168.0.114 -t "MiTemperature2/$2/humidity" -u hasse -P casablanca -i "mibridge" -m "$4"
|
||||
mosquitto_pub -h 192.168.0.114 -t "MiTemperature2/$2/batteryvoltage" -u hasse -P casablanca -i "mibridge" -m "$5"
|
||||
mosquitto_pub -h 192.168.0.114 -t "MiTemperature2/$2/batterylevel" -u hasse -P casablanca -i "mibridge" -m "$6"
|
||||
11
sensorer.ini
Normal file
11
sensorer.ini
Normal file
@@ -0,0 +1,11 @@
|
||||
[A4:C1:38:98:7B:B6]
|
||||
sensorname=mi_temp_1
|
||||
|
||||
[A4:C1:38:29:03:0D]
|
||||
sensorname=mi_temp_2
|
||||
|
||||
[A4:C1:38:62:CA:83]
|
||||
sensorname=mi_temp_3
|
||||
|
||||
[A4:C1:38:D5:EA:63]
|
||||
sensorname=mi_temp_4
|
||||
5
sensorer.sh
Executable file
5
sensorer.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
|
||||
tmux new-session -d -s sensorer '/home/pi/LYWSD03MMC.py -a -wdt 5 --devicelistfile sensorer.ini --callback sendToMQTT.sh'
|
||||
tmux detach -s sensorer
|
||||
45
temperatur_koksfonstret.py
Executable file
45
temperatur_koksfonstret.py
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/python3
|
||||
# Copyright (c) 2014 Adafruit Industries
|
||||
# Author: Tony DiCola
|
||||
|
||||
import sys
|
||||
import Adafruit_DHT
|
||||
import paho.mqtt.client as mqtt
|
||||
|
||||
mqttserver = "192.168.0.114"
|
||||
client = mqtt.Client("koksfonstret")
|
||||
client.connect(mqttserver)
|
||||
|
||||
# Parse command line parameters.
|
||||
sensor_args = { '11': Adafruit_DHT.DHT11,
|
||||
'22': Adafruit_DHT.DHT22,
|
||||
'2302': Adafruit_DHT.AM2302 }
|
||||
if len(sys.argv) == 3 and sys.argv[1] in sensor_args:
|
||||
sensor = sensor_args[sys.argv[1]]
|
||||
pin = sys.argv[2]
|
||||
else:
|
||||
print('Usage: sudo ./Adafruit_DHT.py [11|22|2302] <GPIO pin number>')
|
||||
print('Example: sudo ./Adafruit_DHT.py 2302 4 - Read from an AM2302 connected to GPIO pin #4')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Try to grab a sensor reading. Use the read_retry method which will retry up
|
||||
# to 15 times to get a sensor reading (waiting 2 seconds between each retry).
|
||||
humidity, temperature = Adafruit_DHT.read_retry(sensor, pin)
|
||||
|
||||
# Un-comment the line below to convert the temperature to Fahrenheit.
|
||||
# temperature = temperature * 9/5.0 + 32
|
||||
|
||||
# Note that sometimes you won't get a reading and
|
||||
# the results will be null (because Linux can't
|
||||
# guarantee the timing of calls to read the sensor).
|
||||
# If this happens try again!
|
||||
|
||||
if humidity is not None and temperature is not None:
|
||||
print('Temp={0:0.1f}* Humidity={1:0.1f}%'.format(temperature, humidity))
|
||||
client.publish("casablanca/koksfonstret/temperature", temperature)
|
||||
client.publish("casablanca/koksfonstret/humidity", humidity)
|
||||
|
||||
else:
|
||||
print('Failed to get reading. Try again!')
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user