#!/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,%.2f\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()