import requests from threading import Thread from time import time, sleep from datetime import datetime # from itertools import groupby from tango import AttrWriteType, DevState, DispLevel, DeviceProxy from tango.server import run, Device, attribute, device_property class ArduinoWeatherStation(Device): address = device_property( dtype=str, doc="IP address", ) port = device_property( dtype=int, default_value=5209, doc="Port", ) polling_time = device_property( dtype=int, default_value=2, doc="Polling time", ) timeout = device_property( dtype=int, default_value=5, doc="Data request timeout", ) stale_time = device_property( dtype=int, default_value=60, doc="Stale time", ) # buffer_size = device_property( # dtype=int, # default_value=30, # doc="Data buffer size", # ) fw_version = attribute( label="FW version", dtype=str, access=AttrWriteType.READ, display_level=DispLevel.EXPERT, fget="get_fw_version" ) eeprom_slots = attribute( label="EEPROM slots", dtype=int, access=AttrWriteType.READ, display_level=DispLevel.EXPERT, fget="get_eeprom_slots" ) eeprom_writes = attribute( label="EEPROM write count", dtype=int, access=AttrWriteType.READ, display_level=DispLevel.EXPERT, fget="get_eeprom_writes" ) sht30_status = attribute( label="SHT30 status", dtype=int, access=AttrWriteType.READ, display_level=DispLevel.EXPERT, fget="get_sht30_status" ) t_outA = attribute( label="Outside temperature A", dtype=float, unit="C", access=AttrWriteType.READ, fget="get_outA_temp" ) t_outB = attribute( label="Outside temperature B", dtype=float, unit="C", access=AttrWriteType.READ, fget="get_outB_temp" ) t_out_avg = attribute( label="Average outside temperature", dtype=float, unit="C", access=AttrWriteType.READ, fget="get_out_avg_temp" ) t_box = attribute( label="Box temperature", dtype=float, unit="C", access=AttrWriteType.READ, fget="get_box_temp" ) t_chip = attribute( label="Chip temperature", dtype=float, unit="C", access=AttrWriteType.READ, fget="get_chip_temp" ) rh = attribute( label="Relative humidity", dtype=float, unit="%RH", access=AttrWriteType.READ, fget="get_rh" ) ah = attribute( label="Absolute humidity", dtype=float, unit="g/m3", access=AttrWriteType.READ, fget="get_ah" ) dew = attribute( label="Dew point", dtype=float, unit="C", access=AttrWriteType.READ, fget="get_dew" ) bat1ADC = attribute( label="Battery1 ADC", dtype=int, access=AttrWriteType.READ, display_level=DispLevel.EXPERT, fget="get_bat1ADC" ) bat2ADC = attribute( label="Battery2 ADC", dtype=int, access=AttrWriteType.READ, display_level=DispLevel.EXPERT, fget="get_bat2ADC" ) pv1ADC = attribute( label="PV1 ADC", dtype=int, access=AttrWriteType.READ, display_level=DispLevel.EXPERT, fget="get_pv1ADC" ) pv2ADC = attribute( label="PV2 ADC", dtype=int, access=AttrWriteType.READ, display_level=DispLevel.EXPERT, fget="get_pv2ADC" ) bat1V = attribute( label="Battery1 Voltage", dtype=float, unit="V", access=AttrWriteType.READ, fget="get_bat1V" ) bat2V = attribute( label="Battery2 Voltage", dtype=float, unit="V", access=AttrWriteType.READ, fget="get_bat2V" ) pv1V = attribute( label="PV1 Voltage", dtype=float, unit="V", access=AttrWriteType.READ, fget="get_pv1V" ) pv2V = attribute( label="PV2 Voltage", dtype=float, unit="V", access=AttrWriteType.READ, fget="get_pv2V" ) rdADC = attribute( label="Rain detector ADC", dtype=int, access=AttrWriteType.READ, display_level=DispLevel.EXPERT, fget="get_rdADC" ) piezoADC = attribute( label="Piezo sensor ADC", dtype=int, access=AttrWriteType.READ, display_level=DispLevel.EXPERT, fget="get_piezoADC" ) rd = attribute( label="Rain detector", dtype=float, unit="%", access=AttrWriteType.READ, fget="get_rd" ) piezo = attribute( label="Piezo sensor", dtype=float, access=AttrWriteType.READ, fget="get_piezo" ) bat1Coef = attribute( label="Bat1Coef", dtype=float, access=AttrWriteType.READ_WRITE, display_level=DispLevel.EXPERT, fget="get_bat1coef", fset="set_bat1coef" ) bat2Coef = attribute( label="Bat2Coef", dtype=float, access=AttrWriteType.READ_WRITE, display_level=DispLevel.EXPERT, fget="get_bat2coef", fset="set_bat2coef" ) pv1Coef = attribute( label="PV1Coef", dtype=float, access=AttrWriteType.READ_WRITE, display_level=DispLevel.EXPERT, fget="get_pv1coef", fset="set_pv1coef" ) pv2Coef = attribute( label="PV2Coef", dtype=float, access=AttrWriteType.READ_WRITE, display_level=DispLevel.EXPERT, fget="get_pv2coef", fset="set_pv2coef" ) rdCoef = attribute( label="RDCoef", dtype=float, access=AttrWriteType.READ_WRITE, display_level=DispLevel.EXPERT, fget="get_rdcoef", fset="set_rdcoef" ) piezoCoef = attribute( label="PiezoCoef", dtype=float, access=AttrWriteType.READ_WRITE, display_level=DispLevel.EXPERT, fget="get_piezocoef", fset="set_piezocoef" ) bat1Off = attribute( label="Bat1Off", dtype=int, access=AttrWriteType.READ_WRITE, display_level=DispLevel.EXPERT, fget="get_bat1off", fset="set_bat1off" ) bat2Off = attribute( label="Bat2Off", dtype=int, access=AttrWriteType.READ_WRITE, display_level=DispLevel.EXPERT, fget="get_bat2off", fset="set_bat2off" ) pv1Off = attribute( label="PV1Off", dtype=int, access=AttrWriteType.READ_WRITE, display_level=DispLevel.EXPERT, fget="get_pv1off", fset="set_pv1off" ) pv2Off = attribute( label="PV2Off", dtype=int, access=AttrWriteType.READ_WRITE, display_level=DispLevel.EXPERT, fget="get_pv2off", fset="set_pv2off" ) rdOff = attribute( label="rdOff", dtype=int, access=AttrWriteType.READ_WRITE, display_level=DispLevel.EXPERT, fget="get_rdoff", fset="set_rdoff" ) piezoOff = attribute( label="PiezoOff", dtype=int, access=AttrWriteType.READ_WRITE, display_level=DispLevel.EXPERT, fget="get_piezooff", fset="set_piezooff" ) def init_device(self): Device.init_device(self) self.set_state(DevState.INIT) self.url = "http://%s:%d/" % (self.address, self.port) self.data = {} self.comm_thread = CommunicationThread(self) self.comm_thread.start() self.set_state(DevState.ON) def delete_device(self): self.comm_thread.stop() def get_fw_version(self): return self.data["status"]["fw_version"] def get_eeprom_slots(self): return self.data["status"]["eeprom_slots"] def get_eeprom_writes(self): return self.data["status"]["eeprom_writes"] def get_sht30_status(self): return self.data["status"]["sht30_status"] def get_outA_temp(self): return self.data["temperature"]["out_a"] def get_outB_temp(self): return self.data["temperature"]["out_b"] def get_out_avg_temp(self): return self.data["temperature"]["out_avg"] def get_box_temp(self): return self.data["temperature"]["box"] def get_chip_temp(self): return self.data["temperature"]["chip"] def get_rh(self): return self.data["humidity"]["rel"] def get_ah(self): return self.data["humidity"]["abs"] def get_dew(self): return self.data["humidity"]["dew"] def get_bat1ADC(self): return self.data["adc"]["bat1"] def get_bat2ADC(self): return self.data["adc"]["bat2"] def get_pv1ADC(self): return self.data["adc"]["pv1"] def get_pv2ADC(self): return self.data["adc"]["pv2"] def get_bat1V(self): return self.data["power"]["bat1"] def get_bat2V(self): return self.data["power"]["bat2"] def get_pv1V(self): return self.data["power"]["pv1"] def get_pv2V(self): return self.data["power"]["pv2"] def get_rdADC(self): return self.data["adc"]["rd"] def get_piezoADC(self): return self.data["adc"]["piezo"] def get_rd(self): return self.data["rain"]["rd"] def get_piezo(self): return self.data["rain"]["piezo"] def get_bat1coef(self): return self.data["config"]["bat1_coef"] def set_bat1coef(self, v): self.comm_thread.write({"bat1_coef": v}) def get_bat2coef(self): return self.data["config"]["bat2_coef"] def set_bat2coef(self, v): self.comm_thread.write({"bat2_coef": v}) def get_pv1coef(self): return self.data["config"]["pv1_coef"] def set_pv1coef(self, v): self.comm_thread.write({"pv1_coef": v}) def get_pv2coef(self): return self.data["config"]["pv2_coef"] def set_pv2coef(self, v): self.comm_thread.write({"pv2_coef": v}) def get_bat1off(self): return self.data["config"]["bat1_off"] def set_bat1off(self, v): self.comm_thread.write({"bat1_off": v}) def get_bat2off(self): return self.data["config"]["bat2_off"] def set_bat2off(self, v): self.comm_thread.write({"bat2_off": v}) def get_pv1off(self): return self.data["config"]["pv1_off"] def set_pv1off(self, v): self.comm_thread.write({"pv1_off": v}) def get_pv2off(self): return self.data["config"]["pv2_off"] def set_pv2off(self, v): self.comm_thread.write({"pv2_off": v}) def get_rdcoef(self): return self.data["config"]["rd_coef"] def set_rdcoef(self, v): self.comm_thread.write({"rd_coef": v}) def get_piezocoef(self): return self.data["config"]["piezo_coef"] def set_piezocoef(self, v): self.comm_thread.write({"piezo_coef": v}) def get_rdoff(self): return self.data["config"]["rd_off"] def set_rdoff(self, v): self.comm_thread.write({"rd_off": v}) def get_piezooff(self): return self.data["config"]["piezo_off"] def set_piezooff(self, v): self.comm_thread.write({"piezo_off": v}) class CommunicationThread(Thread): def __init__(self, dev): Thread.__init__(self) self.dev = dev self.quit = False self.write_buffer = None self.last_read = None self.data_buffer = [] def run(self): while not self.quit: if self.write_buffer is not None: self._write() self._read() self._check_stale_data() sleep(self.dev.polling_time) def _read(self): try: r = requests.get(self.dev.url, timeout=self.dev.timeout) self.dev.data = r.json() except Exception as e: print("Read generated exception: %s" % str(e)) else: self.last_read = time() # self.data_buffer.append(data) # if len(self.data_buffer) > self.dev.buffer_size: # self.data_buffer.pop(0) def _write(self): requests.post(self.dev.url, json=self.write_buffer) self.write_buffer = None def _check_stale_data(self): print("checking for stale data") if self.last_read is not None: if time() - self.last_read >= self.dev.stale_time: print("all data stale, resetting") DeviceProxy(self.dev.get_name()).Init() self.dev.set_status("Device reset at %s due to stale data." % datetime.now().strftime("%Y-%m-%d/%H:%M:%S.%f/%Z")) else: print("not stale") else: print("no read yet") # stale = [] # if len(self.data_buffer) >= self.dev.buffer_size: # for k, v in self.data_buffer[0].items(): # if k in ("status", "config"): # continue # print("processing section %s" % k) # for kk, vv in v.items(): # print("processing value %s" % kk) # data = [s[k][kk] for s in self.data_buffer] # print(data) # r = self.all_equal(data) # print("is stale? %s" % str(r)) # stale.append(r) # if all(stale): # def all_equal(self, iterable): # g = groupby(iterable) # return next(g, True) and not next(g, False) def write(self, data): self.write_buffer = data def stop(self): self.quit = True def start(): run((ArduinoWeatherStation,)) if __name__ == "__main__": start()