diff --git a/POCloud/Direct/poc.py b/POCloud/Direct/poc.py index 276c79e..a4fe549 100644 --- a/POCloud/Direct/poc.py +++ b/POCloud/Direct/poc.py @@ -13,8 +13,7 @@ import time # import minimalmodbus import pickle from device_base import deviceBase -# from datetime import datetime -from pycomm.ab_comm.clx import Driver as ClxDriver +from datetime import datetime import logging from collections import deque @@ -25,191 +24,85 @@ except: import simplejson as json import calendar -data_source = "PLC" -plc_ip = '192.168.1.20' - - -def readTag(addr, tag): - time.sleep(0.01) - c = ClxDriver() - if c.open(addr): - try: - v = c.read_tag(tag) - # print(v) - return v - except Exception: - err = c.get_status() - c.close() - print err - pass - c.close() - - -def writeTag(addr, tag, val): - c = ClxDriver() - - if c.open(addr): - try: - # typ = getTagType(addr, tag) - cv = c.read_tag(tag) - wt = c.write_tag(tag, val, cv[1]) - # print(wt) - return wt - except Exception: - err = c.get_status() - c.close() - print err - pass - c.close() - - class Channel(): - global plc_ip + def __init__(self, ch_name, chg_threshold, max_age): + self.channel = ch_name + self.chg_treshold = chg_treshold, + self.max_age = max_age + self.last_sent = 0 + self.value = float('inf') - def __init__(self, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec): - self.mesh_name = mesh_name - self.plc_tag = plc_tag - self.data_type = data_type - self.last_value = None - self.value = None - self.last_send_time = 0 - self.chg_threshold = chg_threshold - self.guarantee_sec = guarantee_sec - - def read(self, forceSend): - if self.plc_tag: - v = readTag(plc_ip, self.plc_tag) - if v: - if self.data_type == 'boolean' or self.data_type == 'str': - if (self.last_send_time == 0) or (self.value is None) or not (self.value == v[0]) or ((time.time() - self.last_send_time) > self.guarantee_sec) or (forceSend): - self.last_value = self.value - self.value = v[0] - return True - else: - return False - else: - if (self.last_send_time == 0) or (self.value is None) or (abs(self.value - v[0]) > self.chg_threshold) or ((time.time() - self.last_send_time) > self.guarantee_sec) or (forceSend): - self.last_value = self.value - self.value = v[0] - return True - else: - return False - else: - return False + def check(self, val, force=False): + if (abs(self.value - val) > self.chg_treshold) or ((time.time() - self.last_sent) > self.max_age) or force: + return True return False -go_channels = { - "percent_run": Channel('go_percent_run', 'GAUGEOFF_Percent_Run', 'float', 0, 0), - "kWh": Channel('go_kwh', 'GAUGEOFF_kWh', 'float', 0, 0), - 'kWh_regen': Channel('go_kwh_regen', 'GAUGEOFF_kWh_regen', 'float', 0, 0), - "electricity_cost": Channel('go_electricity_cost', 'GAUGEOFF_Electricity_Cost', 'float', 0, 0), - 'peak_load': Channel('go_peak_load', 'GAUGEOFF_Max_Load', 'float', 0, 0), - 'min_load': Channel('go_min_load', 'GAUGEOFF_Min_Load', 'float', 0, 0), - 'polished_rod_HP': Channel('go_polished_rod_hp', 'GAUGEOFF_Polished_Rod_HP', 'float', 0, 0), - 'average_SPM': Channel('go_average_spm', "GAUGEOFF_Average_SPM", 'float', 0, 0), - 'lifting_cost': Channel('go_lifting_cost', "GAUGEOFF_Lifting_Cost", 'float', 0, 0), - 'full_card_production': Channel('go_full_card_production', "GAUGEOFF_Full_Card_Production", 'float', 0, 0), - 'fluid_above_pump': Channel('go_fluid_above_pump', 'GAUGEOFF_Fluid_Above_Pump', 'float', 0, 0), - 'production_calculated': Channel('go_production_calculated', 'GAUGEOFF_Production_Calculated', 'float', 0, 0), - 'inflow_rate': Channel('go_inflow_rate', 'GAUGEOFF_Inflow_Rate', 'float', 0, 0), - 'pump_intake_pressure': Channel('go_pump_intake_pressure', 'GAUGEOFF_pump_intake_pressure', 'float', 0, 0) -} - -statusCh = Channel('status', 'Pump.Run_Status', 'str', 0, 0) -gaugeOffCh = Channel('go','Gauge_Off_Command', 'boolean', 0, 0) - -channels = { - 'downhole_adjusted_gross_stroke': Channel('downhole_adjusted_gross_stroke', 'Card_Past[1].Downhole_AdjustedGrossStroke', 'float', 5.0, 3600), - 'downhole_fluid_load': Channel('downhole_fluid_load', 'Card_Past[1].Downhole_FluidLoad', 'float', 100.0, 3600), - 'downhole_gross_stroke': Channel('downhole_gross_stroke', 'Card_Past[1].Downhole_GrossStroke', 'float', 1.0, 3600), - 'downhole_max_position': Channel('downhole_max_position', 'Card_Past[1].Downhole_Max_Position.Position', 'float', 1.0, 3600), - 'downhole_min_position': Channel('downhole_min_position', 'Card_Past[1].Downhole_Min_Position.Position', 'float', 1.0, 3600), - 'downhole_net_stroke': Channel('downhole_net_stroke', 'Card_Past[1].Downhole_NetStroke', 'float', 1.0, 3600), - 'fillage_percent': Channel('fillage_percent', 'Card_Past[1].Fillage_Percent', 'float', 1.0, 3600), - 'fluid_above_pump': Channel('fluid_above_pump', 'Card_Past[1].Fluid_Above_Pump', 'float', 10.0, 3600), - 'fluid_gradient': Channel('fluid_gradient', 'Card_Past[1].Params.Fluid_Gradient', 'float', 0, 3600), - 'polished_rod_hp': Channel('polished_rod_hp', 'Card_Past[1].Polished_Rod_HP', 'float', 0.5, 3600), - 'pump_hp': Channel('pump_hp', 'Card_Past[1].Pump_HP', 'float', 0.5, 3600), - 'pump_intake_pressure': Channel('pump_intake_pressure', 'Card_Past[1].Pump_Intake_Pressure', 'float', 10.0, 3600), - 'stroke_production': Channel('stroke_production', 'Stroke_Production', 'float', 0.0005, 3600), - 'surface_max_load': Channel('surface_max_load', 'Card_Past[1].Surface_Max.Load', 'float', 100.0, 3600), - 'surface_min_load': Channel('surface_min_load', 'Card_Past[1].Surface_Min.Load', 'float', 100.0, 3600), - 'surface_stroke_length': Channel('surface_stroke_length', 'Card_Past[1].Surface_StrokeLength', 'float', 1.0, 3600), - 'tubing_movement': Channel('tubing_movement', 'Card_Past[1].Tubing_Movement', 'float', 1.0, 3600), - 'SPM': Channel('SPM', 'Card_Past[1].SPM', 'float', 0.5, 3600), - # 'drive_torque_mode': Channel('drive_torque_mode', 'DriveTorqueMode', 'boolean', 0, 3600), - 'dt': Channel('dt', 'Card_Past[1].Params.dt', 'float', 0.001, 3600), - # 'speed_reference': Channel('speed_reference', 'Pump_PF755.PSet_SpeedRef', 'float', 5.0, 3600), - 'stuffing_box_friction': Channel('stuffing_box_friction', 'Card_Past[1].Params.Stuffing_Box_Friction', 'float', 1.0, 3600), - # 'torque_reference': Channel('torque_reference', 'PF755_Drive:O.TrqRefAStpt', 'float', 1.0, 3600), - 'tubing_head_pressure': Channel('tubing_head_pressure', 'Card_Past[1].Params.Tubing_Head_Pressure', 'float', 5.0, 3600), -} - -dt_channels = { # Current Daily Totals - 'Average_SPM': Channel('dt_average_spm', 'TODAY_Average_SPM', 'float', 0.5, 3600), - 'Calculated_Production': Channel('dt_calculated_production', 'TODAY_Production_Calculated', 'float', 10.0, 3600), - 'Downhole_Net_Stroke': Channel('dt_downhole_net_stroke', 'TODAY_Downhole_NetStroke', 'float', 1.0, 3600), - 'Electricity_Cost': Channel('dt_electricity_cost', 'TODAY_Electricity_Cost', 'float', 0.1, 3600), - 'Fluid_Level': Channel('dt_fluid_level', 'TODAY_Fluid_Above_Pump', 'float', 10.0, 3600), - 'Full_Card_Production': Channel('dt_full_card_production', 'TODAY_Full_Card_Production', 'float', 10.0, 3600), - 'Inflow_Rate': Channel('dt_inflow_rate', 'TODAY_Inflow_Rate', 'float', 10.0, 3600), - 'Lifting_Cost': Channel('dt_lifting_cost', 'TODAY_Lifting_Cost', 'float', 0.01, 3600), - 'Min_Load': Channel('dt_min_load', 'TODAY_Min_Load', 'float', 100.0, 3600), - 'Peak_Load': Channel('dt_peak_load', 'TODAY_Max_Load', 'float', 100.0, 3600), - 'Percent_Run': Channel('dt_percent_run', 'TODAY_Percent_Run', 'float', 1.0, 3600), - 'Polished_Rod_HP': Channel('dt_polished_rod_hp', 'TODAY_Polished_Rod_HP', 'float', 1.0, 3600), - 'Projected_Production': Channel('dt_projected_production', 'TODAY_Production_Projected', 'float', 5.0, 3600), - 'Pump_HP': Channel('dt_pump_hp', 'TODAY_Pump_HP', 'float', 1.0, 3600), - 'Pump_Intake_Presure': Channel('dt_pump_intake_pressure', 'TODAY_Pump_Intake_Pressure', 'float', 10.0, 3600), - 'Surface_Stroke_Length': Channel('dt_surface_stroke_length', 'TODAY_Surface_StrokeLength', 'float', 1.0, 3600), - 'Tubing_Movement': Channel('dt_tubing_movement', 'TODAY_Tubing_Movement', 'float', 1.00, 3600), - 'kWh': Channel('dt_kWh', 'TODAY_kWh', 'float', 10.0, 3600), - 'kWh_Regen': Channel('dt_kWh_regen', 'TODAY_kWh_Regen', 'float', 1.0, 3600) +card_data_channels = { + 'Downhole Adjusted Gross Stroke': {channel:'downhole_adjusted_gross_stroke', chg_threshold: 5.0, max_age: 3600}, + 'Downhole Fluid Load': {channel:'downhole_fluid_load', chg_threshold: 100.0, max_age: 3600}, + 'Downhole Gross Stroke': {channel:'downhole_gross_stroke', chg_threshold: 1.0, max_age: 3600}, + 'Downhole Max Position': {channel:'downhole_max_position', chg_threshold: 1.0, max_age: 3600}, + 'Downhole Min Position': {channel:'downhole_min_position', chg_threshold: 1.0, max_age: 3600}, + 'Downhole Net Stroke': {channel:'downhole_net_stroke', chg_threshold: 1.0, max_age: 3600}, + 'Fill Percentage': {channel:'fillage_percent', chg_threshold: 1.0, max_age: 3600}, + 'Fluid Level': {channel:'fluid_above_pump', chg_threshold: 10.0, max_age: 3600}, + 'Fluid Gradient': {channel:'fluid_gradient', chg_threshold: 1.0, max_age: 3600}, + 'Polished Rod HP': {channel:'polished_rod_hp', chg_threshold: 0.5, max_age: 3600}, + 'Pump HP': {channel:'pump_hp', chg_threshold: 0.5, max_age: 3600}, + 'Pump Intake Pressure': {channel:'pump_intake_pressure', chg_threshold: 10.0, max_age: 3600}, + 'Stroke Production': {channel:'stroke_production', chg_threshold: 0.005, max_age: 3600}, + 'Surface Max Load': {channel:'surface_max_load', chg_threshold: 100.0, max_age: 3600}, + 'Surface Min Load': {channel:'surface_min_load', chg_threshold: 100.0, max_age: 3600}, + 'Surface Stroke Length': {channel:'surface_stroke_length', chg_threshold: 1.0, max_age: 3600}, + 'Tubing Movement': {channel:'tubing_movement', chg_threshold: 1.0, max_age: 3600}, + 'SPM': {channel:'SPM', chg_threshold: 0.5, max_age: 3600}, + 'dt': {channel:'dt', chg_threshold: 0.01, max_age: 3600}, + 'Stuffing Box Friction': {channel:'stuffing_box_friction', chg_threshold: 1.0, max_age: 3600}, + 'Tubing Head Pressure': {channel:'tubing_head_pressure', chg_threshold: 5.0, max_age: 3600} } -class Card(): - global plc_ip - - def __init__(self, unified_time): - self.sc = [] - self.dc = [] - self.sent = False - self.read_time = unified_time - self.readCard() - - def readCard(self): - self.card_id = readTag(plc_ip, "Card_Past[1].ID")[0] - self.num_points = int(readTag(plc_ip, "Card_Past[1].Num_Points")[0]) - print("reading {} from card ID {}".format(self.num_points, self.card_id)) - for i in range(0, self.num_points): - try: - surf_pos = round(float(readTag(plc_ip, 'Card_Past[1].Surface_Position[{}]'.format(i))[0]), 3) - surf_lod = round(float(readTag(plc_ip, 'Card_Past[1].Surface_Load[{}]'.format(i))[0]), 3) - if not (surf_pos == 0.0) and not (surf_lod == 0.0): - self.sc.append([surf_pos, surf_lod]) - except: - print("Unable to read surface point {}".format(i)) - - try: - down_pos = round(float(readTag(plc_ip, 'Card_Past[1].Downhole_Position[{}]'.format(i))[0]), 3) - down_lod = round(float(readTag(plc_ip, 'Card_Past[1].Downhole_Load[{}]'.format(i))[0]), 3) - if not (down_pos == 0.0) and not (down_lod == 0.0): - self.dc.append([down_pos, down_lod]) - except: - print("Unable to read downhole point {}".format(i)) +# go_channels = { +# "percent_run": Channel('go_percent_run', 'GAUGEOFF_Percent_Run', 'float', 0, 0), +# "kWh": Channel('go_kwh', 'GAUGEOFF_kWh', 'float', 0, 0), +# 'kWh_regen': Channel('go_kwh_regen', 'GAUGEOFF_kWh_regen', 'float', 0, 0), +# "electricity_cost": Channel('go_electricity_cost', 'GAUGEOFF_Electricity_Cost', 'float', 0, 0), +# 'peak_load': Channel('go_peak_load', 'GAUGEOFF_Max_Load', 'float', 0, 0), +# 'min_load': Channel('go_min_load', 'GAUGEOFF_Min_Load', 'float', 0, 0), +# 'polished_rod_HP': Channel('go_polished_rod_hp', 'GAUGEOFF_Polished_Rod_HP', 'float', 0, 0), +# 'average_SPM': Channel('go_average_spm', "GAUGEOFF_Average_SPM", 'float', 0, 0), +# 'lifting_cost': Channel('go_lifting_cost', "GAUGEOFF_Lifting_Cost", 'float', 0, 0), +# 'full_card_production': Channel('go_full_card_production', "GAUGEOFF_Full_Card_Production", 'float', 0, 0), +# 'fluid_above_pump': Channel('go_fluid_above_pump', 'GAUGEOFF_Fluid_Above_Pump', 'float', 0, 0), +# 'production_calculated': Channel('go_production_calculated', 'GAUGEOFF_Production_Calculated', 'float', 0, 0), +# 'inflow_rate': Channel('go_inflow_rate', 'GAUGEOFF_Inflow_Rate', 'float', 0, 0), +# 'pump_intake_pressure': Channel('go_pump_intake_pressure', 'GAUGEOFF_pump_intake_pressure', 'float', 0, 0) +# } +# +# +# dt_channels = { # Current Daily Totals +# 'Average_SPM': Channel('dt_average_spm', 'TODAY_Average_SPM', 'float', 0.5, 3600), +# 'Calculated_Production': Channel('dt_calculated_production', 'TODAY_Production_Calculated', 'float', 10.0, 3600), +# 'Downhole_Net_Stroke': Channel('dt_downhole_net_stroke', 'TODAY_Downhole_NetStroke', 'float', 1.0, 3600), +# 'Electricity_Cost': Channel('dt_electricity_cost', 'TODAY_Electricity_Cost', 'float', 0.1, 3600), +# 'Fluid_Level': Channel('dt_fluid_level', 'TODAY_Fluid_Above_Pump', 'float', 10.0, 3600), +# 'Full_Card_Production': Channel('dt_full_card_production', 'TODAY_Full_Card_Production', 'float', 10.0, 3600), +# 'Inflow_Rate': Channel('dt_inflow_rate', 'TODAY_Inflow_Rate', 'float', 10.0, 3600), +# 'Lifting_Cost': Channel('dt_lifting_cost', 'TODAY_Lifting_Cost', 'float', 0.01, 3600), +# 'Min_Load': Channel('dt_min_load', 'TODAY_Min_Load', 'float', 100.0, 3600), +# 'Peak_Load': Channel('dt_peak_load', 'TODAY_Max_Load', 'float', 100.0, 3600), +# 'Percent_Run': Channel('dt_percent_run', 'TODAY_Percent_Run', 'float', 1.0, 3600), +# 'Polished_Rod_HP': Channel('dt_polished_rod_hp', 'TODAY_Polished_Rod_HP', 'float', 1.0, 3600), +# 'Projected_Production': Channel('dt_projected_production', 'TODAY_Production_Projected', 'float', 5.0, 3600), +# 'Pump_HP': Channel('dt_pump_hp', 'TODAY_Pump_HP', 'float', 1.0, 3600), +# 'Pump_Intake_Presure': Channel('dt_pump_intake_pressure', 'TODAY_Pump_Intake_Pressure', 'float', 10.0, 3600), +# 'Surface_Stroke_Length': Channel('dt_surface_stroke_length', 'TODAY_Surface_StrokeLength', 'float', 1.0, 3600), +# 'Tubing_Movement': Channel('dt_tubing_movement', 'TODAY_Tubing_Movement', 'float', 1.00, 3600), +# 'kWh': Channel('dt_kWh', 'TODAY_kWh', 'float', 10.0, 3600), +# 'kWh_Regen': Channel('dt_kWh_regen', 'TODAY_kWh_Regen', 'float', 1.0, 3600) +# } - def stringify(self): - ''' returns a list of two strings [surface card, downhole card]''' - sc_str = "[" - dc_str = "[" - for i in range(0, self.num_points): - sc_str = sc_str + "[{},{}],".format(self.sc[i][0], self.sc[i][1]) - dc_str = dc_str + "[{},{}],".format(self.dc[i][0], self.dc[i][1]) - sc_str = sc_str + "[{},{}]]".format(self.sc[0][0], self.sc[0][1]) - dc_str = dc_str + "[{},{}]]".format(self.dc[0][0], self.dc[0][1]) - return[sc_str, dc_str] class start(threading.Thread, deviceBase): @@ -221,17 +114,13 @@ class start(threading.Thread, deviceBase): self.daemon = True self.forceSend = True self.version = "3" - self.device_address = "http://192.168.1.30/" - # self.device_address = "http://localhost/" - self.cardLoopTimer = 600 + self.device_address = "https://192.168.1.30:3000" + self.cardLoopTimer = 60 self.finished = threading.Event() threading.Thread.start(self) + self.last_status = "" self.statusChanged = False - self.al_status_last = False - self.dl_status_last = False - self.card_storage = deque([]) # array of the last x cards - self.card_storage_limit = 5 - self.last_card_sent_time = 0 + self.last_stored_card_date = 0 # load stored event ID's try: @@ -261,10 +150,25 @@ class start(threading.Thread, deviceBase): def register(self): channels["status"]["last_value"] = "" - def channelCheck(self, c, force): - if c.read(force): - self.sendtodbJSON(c.mesh_name, c.value, time.time()) - c.last_send_time = time.time() + def sendCard(self, card): + card_date = int(datetime.strptime(card['createdAt'], '%Y-%m-%dT%H:%M:%S.%fZ').strftime('%s')) + self.sendtodb("card_history", card['card_id'], card_date) + s_p = map(float, card['surface_position'].split(", ")) + s_l = map(float, card['surface_load'].split(", ")) + d_p = map(float, card['downhole_position'].split(", ")) + d_l = map(float, card['downhole_load'].split(", ")) + + surf_string = "[" + for i in range(0,min(len(s_p), len(s_l))): + surf_string += "[{},{}],".format(round(s_p[i],3), round(s_l[i],3)) + surf_string = surf_string[:-1] + "]" + self.sendtodb('sc', surf_string, card_date) + + down_string = "[" + for i in range(0,min(len(d_p), len(d_l))): + down_string += "[{},{}],".format(round(d_p[i],3), round(d_l[i],3)) + down_string = down_string[:-1] + "]" + self.sendtodb('dc', down_string, card_date) def run(self): self.runLoopStatus = "" @@ -276,50 +180,28 @@ class start(threading.Thread, deviceBase): self.statusChanged = True # TODO Add event logic here - runLoopStatus = "Daily Total Loop" - for dt in dt_channels: - self.channelCheck(dt_channels[dt], self.forceSend) + # runLoopStatus = "Daily Total Loop" + # for dt in dt_channels: + # self.channelCheck(dt_channels[dt], self.forceSend) - runLoopStatus = "checkGaugeOffData" - if gaugeOffCh.read(self.forceSend): - for go in go_channels: - self.channelCheck(go_channels[go], self.forceSend) + # runLoopStatus = "checkGaugeOffData" + # if gaugeOffCh.read(self.forceSend): + # for go in go_channels: + # self.channelCheck(go_channels[go], self.forceSend) - runLoopStatus = "Stroke Parameter Data" - for ch in channels: - self.channelCheck(channels[ch], self.forceSend) + # runLoopStatus = "Stroke Parameter Data" + # for ch in channels: + # self.channelCheck(channels[ch], self.forceSend) runLoopStatus = "Reading Cards" - if len(self.card_storage) > 0: - if not readTag(plc_ip, "Card_Past[1].ID")[0] == self.card_storage[0].card_id: - current_time = time.time() - current_card = Card(current_time) - self.sendtodbJSON("card_history", current_card.card_id, current_time) - if (current_card.read_time - self.last_card_sent_time) > self.cardLoopTimer or self.forceSend: - cards = current_card.stringify() - self.sendtodbJSON("sc", cards[0], current_time) - self.sendtodbJSON("dc", cards[1], current_time) - current_card.sent = True - self.card_storage.appendleft(current_card) - while len(self.card_storage) > self.card_storage_limit: - self.card_storage.pop() - if self.statusChanged: - for c in self.card_storage: - if not c.sent: - cstr = c.stringify() - self.sendtodbJSON("sc", cstr[0], c.read_time) - self.sendtodbJSON("dc", cstr[1], c.read_time) - else: - current_time = time.time() - current_card = Card(current_time) - self.sendtodbJSON("card_history", current_card.card_id, current_time) - if (current_card.read_time - self.last_card_sent_time) > self.cardLoopTimer or self.forceSend: - cards = current_card.stringify() - self.sendtodbJSON("sc", cards[0], current_time) - self.sendtodbJSON("dc", cards[1], current_time) - current_card.sent = True - self.card_storage.appendleft(current_card) + c_req = requests.get('{}/card/latest'.format(self.device_address), verify=False) + latest_card = json.loads(c_req.text)[0] + card_date = int(datetime.strptime(latest_card['createdAt'], '%Y-%m-%dT%H:%M:%S.%fZ').strftime('%s')) + if ((card_date - self.last_stored_card_date) > self.cardLoopTimer) or self.statusChanged or self.forceSend: + self.sendCard(latest_card) + self.last_stored_card_date = card_date + runLoopStatus = "Complete" time.sleep(3) self.forceSend = False @@ -330,28 +212,16 @@ class start(threading.Thread, deviceBase): time.sleep(sleep_timer) def checkStatus(self): - statusMap = { - 0: 'Stopped', - 1: 'Running', - 2: 'Pumped Off', - 3: 'Faulted', - 4: 'Starting', - 5: 'Recovering', - 100: 'Read Error', - 1000: 'PLC Error', - 9999: 'No Response' - } - s = int(readTag(plc_ip, "Pump.Run_Status")[0]) - try: - status = statusMap[s] - except: - print("Could not map status for {}".format(s)) + s_req = requests.get('{}/run_status/current'.format(self.device_address), verify=False) + status_result = json.loads(s_req.text)[0] + status = status_result['status'] if status: date = time.time() - if statusCh.last_value != status: + if self.last_status != status: self.statusChanged = True - print "Status has changed from {0} to {1} @ {2}".format(statusCh.last_value, status, time.time()) + print "Status has changed from {0} to {1} @ {2}".format(self.last_status, status, time.time()) + self.last_status = status else: self.statusChanged = False return False @@ -359,7 +229,7 @@ class start(threading.Thread, deviceBase): if self.statusChanged or self.forceSend: self.status = status self.sendtodb("status", status, date) - statusCh.last_value = status + self.last_status = status return status def checkEvents(self):