beielipi/bin/beielimon.py

268 lines
9.1 KiB
Python
Executable File

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# vim: expandtab sw=4 ts=4 sts=4:
#
# Beehive-Monitoring with SMS Alerts and Data-Upload to
# Webservice.
#
# Author: Joerg Lehmann, nbit Informatik GmbH
#
"""Beehive Monitoring"""
from __future__ import print_function
import os
import sys
import serial
import time
import yaml
import random
import string
import glob
import re
import json
import datetime
# Root Path
APP_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
# Read Configuration from YAML-File
with open("%s/bin/beielimon-config.yaml" % (APP_ROOT), 'r') as stream:
try:
config_data = yaml.load(stream)
except yaml.YAMLError as exc:
print(exc)
# Constants
INVALID_VALUE = -999
# Wegen der Dummy-Waage moechten wir den letzten Messwert holen koennen
def GetLastStoredValue(scale_uuid):
files = glob.glob("%s/data/weight-%s-????????.log" % (APP_ROOT,scale_uuid))
if len(files) > 0:
with open(sorted(files)[-1], "r") as f:
for line in f: pass
result = line
# Beispiel: 2017-11-07 08:23,0
m = re.match("(\d{4})-(\d{2})-(\d{2}) (\d\d:\d\d),(\d+)", result)
if m:
return int("%s" % (m.group(5)))
else:
return INVALID_VALUE
else:
return INVALID_VALUE
class Scale(object):
def __init__(self, scale_config):
self.last_values = []
self.scale_config = scale_config
self.accu = INVALID_VALUE
self.hum = INVALID_VALUE
self.temp = INVALID_VALUE
def __del__(self):
pass
def LogValue(self,weigh_in_gram,swarm_alarm):
cur_time = time.localtime()
timestamp = time.strftime("%Y-%m-%d %H:%M", cur_time)
year = time.strftime("%Y", cur_time)
month = time.strftime("%m", cur_time)
day = time.strftime("%d", cur_time)
if swarm_alarm:
prefix = 'swarmalarm'
else:
prefix = 'weight'
datafilename = "%s/data/%s-%s-%s%s%s.log" % (APP_ROOT,prefix,self.scale_config['scale_uuid'],year,month,day)
with open(datafilename, 'a') as file:
file.write('%s,%d\n' % (timestamp,weigh_in_gram))
if self.accu != INVALID_VALUE and not(swarm_alarm):
prefix = 'accu'
datafilename = "%s/data/%s-%s-%s%s%s.log" % (APP_ROOT,prefix,self.scale_config['scale_uuid'],year,month,day)
with open(datafilename, 'a') as file:
file.write('%s,%.1f\n' % (timestamp,self.accu / 100.0))
if self.hum != INVALID_VALUE and not(swarm_alarm):
prefix = 'humidity'
datafilename = "%s/data/%s-%s-%s%s%s.log" % (APP_ROOT,prefix,self.scale_config['scale_uuid'],year,month,day)
with open(datafilename, 'a') as file:
file.write('%s,%d\n' % (timestamp,self.hum / 10))
if self.temp != INVALID_VALUE and not(swarm_alarm):
prefix = 'temp'
datafilename = "%s/data/%s-%s-%s%s%s.log" % (APP_ROOT,prefix,self.scale_config['scale_uuid'],year,month,day)
with open(datafilename, 'a') as file:
file.write('%s,%d\n' % (timestamp,self.temp / 10))
def AppendReading(self,weigh_in_gram):
self.last_values.append(weigh_in_gram)
if len(self.last_values) > config_data['number_of_samples']:
self.last_values = self.last_values[1:]
# Wir loggen den Wert noch
self.LogValue(weigh_in_gram,False)
def Read(self):
pass
def CalibrateToZero(self):
pass
def SwarmAlarm(self):
return (self.GetWeighLoss() > config_data['swarm_alarm_threshold_gram'])
def ResetValues(self):
self.last_values = []
def GetLastValue(self):
return (self.last_values or [0])[-1]
def GetWeighLoss(self):
last_value = self.GetLastValue()
max_value = max(self.last_values or [0])
return (max_value - last_value)
def GetScaleConfig(self):
return self.scale_config
class ScaleUSB_PCE(Scale):
def __init__(self,serial_int, scale_config):
Scale.__init__(self, scale_config)
self.ser = serial_int
def Read(self):
res = INVALID_VALUE
self.ser.write('Sx\r\n')
weight_string = self.ser.readline()
if len(weight_string) == 16:
if (weight_string[12:13] == 'g'):
try:
res = int(weight_string[2:11])
except:
print('Problem with Read Scale: weight string is not an integer: %s' % (weight_string[2:11]))
else:
print('Problem with Read Scale: weigth should be in grams, but is [%s]' % (weight_string[12:13]))
else:
print('Problem with Read Scale: value should be 16 digits: but is %d' % (len(weight_string)))
if res != INVALID_VALUE:
self.AppendReading(res)
class ScaleBT(Scale):
def __init__(self,scale_config):
Scale.__init__(self, scale_config)
def Read(self):
res = INVALID_VALUE
filename = "/home/beieli/bt-readings/%s.json" % (self.scale_config['mac'])
try:
data = json.load(open(filename))
except:
return
# Wenn die Daten aelter als 15 Minuten sind loeschen wir das File
delta_in_seconds = (datetime.datetime.now() - datetime.datetime.strptime(data['datetime'],'%d.%m.%Y %H:%M')).total_seconds()
print(delta_in_seconds)
if ( delta_in_seconds > 15*60):
print('DEBUG [%s,%s]' % (datetime.datetime.now(),data['datetime']))
print('Readings are older than 15 minutes, removing them [%s]' % (self.scale_config['mac']))
os.remove(filename)
else:
res = ((data['w1'] - self.scale_config['offset1']) / self.scale_config['ratio1']) + ((data['w2'] - self.scale_config['offset2']) / self.scale_config['ratio2'])
if res < 0:
print('DEBUG: Read Bluetooth Scale; Value less than 0, set it to 0: %d' % (res))
res = 0
self.accu = data['accu']
self.hum = data['hum']
self.temp = data['temp']
print('DEBUG: Read Bluetooth Scale: %d' % (res))
if res != INVALID_VALUE:
self.AppendReading(res)
class ScaleDummy(Scale):
def __init__(self,scale_config):
Scale.__init__(self, scale_config)
# Wir versuchen, den letzten Wert wieder zu holen
last_value = GetLastStoredValue(self.scale_config['scale_uuid'])
if last_value != INVALID_VALUE:
self.last_values = [ last_value ]
else:
self.last_values = [ random.randint(500,1000) ]
def Read(self):
# Gewichts- Zu/Abnahme ist Random, manchmal gibt es einen
# Zufaelligen Schwarmalarm
delta = random.randint(-2, 4)
if (random.randint(0,1000) == 500):
delta = random.randint(-1000, - 501)
last_value = self.GetLastValue()
# Wir duerfen nicht negativ werden...
if (last_value + delta < 0):
delta = random.randint(0,4)
print('DEBUG: Read Dummy Scale: %d' % (last_value + delta))
self.AppendReading(last_value + delta)
def send_sms(phonenumbers , text):
data = {}
data['phonenumbers'] = phonenumbers
data['text'] = text
# wird von smsmon verarbeitet...
print("Send SMS to %s, Text: %s" % (phonenumbers, text))
randomstr = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(7))
with open("%s/send_sms_queue/%s.yml" % (APP_ROOT,randomstr), "w") as outfile:
yaml.dump(data, outfile, default_flow_style=False)
def main():
scales = []
for scale_config in config_data['scales']:
if scale_config['interface_type'] == 'usb_pce':
ser = serial.Serial(port='/dev/' + scale_config['interface_name'],
baudrate=9600,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_EVEN,
timeout=20)
scale=ScaleUSB_PCE(ser, scale_config)
scales.append(scale)
elif scale_config['interface_type'] == 'btscale':
scale=ScaleBT(scale_config)
scales.append(scale)
elif scale_config['interface_type'] == 'dummy':
scale=ScaleDummy(scale_config)
scales.append(scale)
# Main Loop
while True:
for scale in scales:
scale.Read()
if scale.SwarmAlarm():
date_time = time.strftime("%d.%m.%Y %H:%M")
last_value = scale.GetLastValue()
weigh_loss = scale.GetWeighLoss()
# Wir loggen den Wert noch
scale.LogValue(last_value,True)
sms_message = '*** Schwarmalarm ***\nDatum/Zeit: %s\nWaage: %s\nLetztes Gewicht [g]: %d\nGewichtsverlust [g]: %d' % (date_time,scale.GetScaleConfig()['alias'],last_value,weigh_loss)
send_sms(scale.GetScaleConfig()['sms_alert_phonenumbers'],sms_message)
scale.ResetValues()
sys.stdout.flush()
time.sleep(config_data['read_scale_interval_sec'])
if __name__ == "__main__":
main()