diff --git a/Pub_Sub/fk_plcpond/thingsboard/pub/sendData.py b/Pub_Sub/fk_plcpond/thingsboard/pub/sendData.py index f956b1f..6970e9e 100644 --- a/Pub_Sub/fk_plcpond/thingsboard/pub/sendData.py +++ b/Pub_Sub/fk_plcpond/thingsboard/pub/sendData.py @@ -129,6 +129,7 @@ def sendData(message): payload = {} payload["ts"] = (round(dt.timestamp(dt.now())/600)*600)*1000 payload["values"] = {} + attribute_payload = {{"latestReportTime": (round(dt.timestamp(dt.now())/600)*600)*1000}} try: checkCredentialConfig() except Exception as e: @@ -137,9 +138,11 @@ def sendData(message): try: logger.debug(measure) payload["values"][measure["name"]] = measure["value"] + if "_spt" in measure["name"]: + attribute_payload[measure["name"]] = measure["value"] except Exception as e: logger.error(e) for chunk in chunk_payload(payload=payload): publish(__topic__, json.dumps(chunk), __qos__) time.sleep(2) - publish("v1/devices/me/attributes", json.dumps({"latestReportTime": (round(dt.timestamp(dt.now())/600)*600)*1000}), __qos__) \ No newline at end of file + publish("v1/devices/me/attributes", json.dumps(attribute_payload), __qos__) \ No newline at end of file diff --git a/Pub_Sub/fk_plcpond_gateway/thingsboard/fk_plcpond_tb_v6.cfg b/Pub_Sub/fk_plcpond_gateway/thingsboard/fk_plcpond_tb_v6.cfg new file mode 100644 index 0000000..09d2a50 --- /dev/null +++ b/Pub_Sub/fk_plcpond_gateway/thingsboard/fk_plcpond_tb_v6.cfg @@ -0,0 +1,2909 @@ +{ + "controllers": [ + { + "enable": 1, + "protocol": "AllenBradley MicroCip", + "name": "plcpond", + "samplePeriod": 30, + "desc": "", + "expired": 10000, + "args": { + "slot": 0, + "connectTimeOut": 30000 + }, + "enableDebug": 0, + "enablePerOnchange": 0, + "endpoint": "192.168.1.12:44818" + }, + { + "enable": 1, + "protocol": "AllenBradley MicroCip", + "name": "overflow_pump", + "samplePeriod": 30, + "desc": "", + "expired": 10000, + "args": { + "slot": 0, + "connectTimeOut": 10000 + }, + "enableDebug": 0, + "enablePerOnchange": 0, + "endpoint": "192.168.1.20:44818" + }, + { + "enable": 1, + "protocol": "AllenBradley MicroCip", + "name": "leak_detection", + "samplePeriod": 10, + "desc": "", + "expired": 1000, + "args": { + "slot": 0, + "connectTimeOut": 10000 + }, + "enableDebug": 0, + "enablePerOnchange": 0, + "endpoint": "192.168.1.30:44818" + } + ], + "measures": [ + { + "name": "pond_1_level", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Pond_1_Lev", + "decimal": 2, + "len": 1, + "readWrite": "ro", + "unit": "", + "desc": "", + "transformType": 0, + "maxValue": "", + "minValue": "", + "maxScaleValue": "", + "minScaleValue": "", + "gain": "", + "offset": "", + "storageLwTSDB": 0 + }, + { + "name": "pond_1_total_bbls", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Pond_1_Total_Barrels", + "decimal": 2, + "len": 1, + "readWrite": "ro", + "unit": "", + "desc": "", + "transformType": 0, + "maxValue": "", + "minValue": "", + "maxScaleValue": "", + "minScaleValue": "", + "gain": "", + "offset": "", + "storageLwTSDB": 0 + }, + { + "name": "pond_1_hi_alm", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Pond_1_Hi_Alarm", + "decimal": 2, + "len": 1, + "readWrite": "ro", + "unit": "", + "desc": "", + "transformType": 0, + "maxValue": "", + "minValue": "", + "maxScaleValue": "", + "minScaleValue": "", + "gain": "", + "offset": "", + "bitMap": 0, + "reverseBit": 0, + "storageLwTSDB": 0 + }, + { + "name": "pond_1_hi_spt", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Pond_1_Hi_Setpoint", + "decimal": 2, + "len": 1, + "readWrite": "rw", + "unit": "", + "desc": "", + "transformType": 0, + "maxValue": "", + "minValue": "", + "maxScaleValue": "", + "minScaleValue": "", + "gain": "", + "offset": "", + "storageLwTSDB": 0 + }, + { + "name": "pond_1_hi_clr_spt", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Pond_1_Hi_Clr_Setpoint", + "decimal": 2, + "len": 1, + "readWrite": "rw", + "unit": "", + "desc": "", + "transformType": 0, + "maxValue": "", + "minValue": "", + "maxScaleValue": "", + "minScaleValue": "", + "gain": "", + "offset": "", + "storageLwTSDB": 0 + }, + { + "name": "pond_1_lo_alm", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Pond_1_Lo_Alarm", + "decimal": 2, + "len": 1, + "readWrite": "ro", + "unit": "", + "desc": "", + "transformType": 0, + "maxValue": "", + "minValue": "", + "maxScaleValue": "", + "minScaleValue": "", + "gain": "", + "offset": "", + "bitMap": 0, + "reverseBit": 0, + "storageLwTSDB": 0 + }, + { + "name": "pond_1_lo_spt", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Pond_1_Lo_Setpoint", + "decimal": 2, + "len": 1, + "readWrite": "rw", + "unit": "", + "desc": "", + "transformType": 0, + "maxValue": "", + "minValue": "", + "maxScaleValue": "", + "minScaleValue": "", + "gain": "", + "offset": "", + "storageLwTSDB": 0 + }, + { + "name": "pond_1_lo_clr_spt", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Pond_1_Lo_Clr_Setpoint", + "decimal": 2, + "len": 1, + "readWrite": "rw", + "unit": "", + "desc": "", + "transformType": 0, + "maxValue": "", + "minValue": "", + "maxScaleValue": "", + "minScaleValue": "", + "gain": "", + "offset": "", + "storageLwTSDB": 0 + }, + { + "name": "pond_2_level", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Pond_2_Lev", + "decimal": 2, + "len": 1, + "readWrite": "ro", + "unit": "", + "desc": "", + "transformType": 0, + "maxValue": "", + "minValue": "", + "maxScaleValue": "", + "minScaleValue": "", + "gain": "", + "offset": "", + "storageLwTSDB": 0 + }, + { + "name": "pond_2_total_bbls", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Pond_2_Total_Barrels", + "decimal": 2, + "len": 1, + "readWrite": "ro", + "unit": "", + "desc": "", + "transformType": 0, + "maxValue": "", + "minValue": "", + "maxScaleValue": "", + "minScaleValue": "", + "gain": "", + "offset": "", + "storageLwTSDB": 0 + }, + { + "name": "pond_2_hi_alm", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Pond_2_Hi_Alarm", + "decimal": 2, + "len": 1, + "readWrite": "ro", + "unit": "", + "desc": "", + "transformType": 0, + "maxValue": "", + "minValue": "", + "maxScaleValue": "", + "minScaleValue": "", + "gain": "", + "offset": "", + "bitMap": 0, + "reverseBit": 0, + "storageLwTSDB": 0 + }, + { + "name": "pond_2_hi_spt", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Pond_2_Hi_Setpoint", + "decimal": 2, + "len": 1, + "readWrite": "rw", + "unit": "", + "desc": "", + "transformType": 0, + "maxValue": "", + "minValue": "", + "maxScaleValue": "", + "minScaleValue": "", + "gain": "", + "offset": "", + "storageLwTSDB": 0 + }, + { + "name": "pond_2_hi_clr_spt", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Pond_2_Hi_Clr_Setpoint", + "decimal": 2, + "len": 1, + "readWrite": "rw", + "unit": "", + "desc": "", + "transformType": 0, + "maxValue": "", + "minValue": "", + "maxScaleValue": "", + "minScaleValue": "", + "gain": "1.0", + "offset": "0.0", + "storageLwTSDB": 0 + }, + { + "name": "pond_2_lo_alm", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Pond_2_Lo_Alarm", + "decimal": 2, + "len": 1, + "readWrite": "ro", + "unit": "", + "desc": "", + "transformType": 0, + "maxValue": "", + "minValue": "", + "maxScaleValue": "", + "minScaleValue": "", + "gain": "", + "offset": "", + "bitMap": 0, + "reverseBit": 0, + "storageLwTSDB": 0 + }, + { + "name": "pond_2_lo_spt", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Pond_2_Lo_Setpoint", + "decimal": 2, + "len": 1, + "readWrite": "rw", + "unit": "", + "desc": "", + "transformType": 0, + "maxValue": "", + "minValue": "", + "maxScaleValue": "", + "minScaleValue": "", + "gain": "", + "offset": "", + "storageLwTSDB": 0 + }, + { + "name": "pond_2_lo_clr_spt", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Pond_2_Lo_Clr_Setpoint", + "decimal": 2, + "len": 1, + "readWrite": "rw", + "unit": "", + "desc": "", + "transformType": 0, + "maxValue": "", + "minValue": "", + "maxScaleValue": "", + "minScaleValue": "", + "gain": "", + "offset": "", + "storageLwTSDB": 0 + }, + { + "name": "air_comp_low_alm", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "AL0_Air_Comp_Low", + "readWrite": "ro", + "unit": "", + "desc": "", + "transformType": 0, + "storageLwTSDB": 0, + "reverseBit": 0, + "bitMap": 0 + }, + { + "name": "air_comp_val", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Val_Air_Comp_Out", + "readWrite": "ro", + "unit": "", + "desc": "", + "transformType": 0, + "storageLwTSDB": 0, + "decimal": 2 + }, + { + "name": "air_comp_spt", + "ctrlName": "plcpond", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "SPT_Low_Air_PSI", + "readWrite": "rw", + "unit": "", + "desc": "", + "transformType": 0, + "storageLwTSDB": 0, + "decimal": 2 + }, + { + "name": "level_override_spt", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Level_Override ", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "rw", + "unit": "", + "desc": "1= runs regardless of level, 0= runs based on start/stop levels", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pond_1_hi_alm", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Pond_1_Hi_Alarm", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pond_1_hi_clr_spt", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Pond_1_Hi_Clr_Setpoint", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pond_1_hi_spt", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Pond_1_Hi_Setpoint", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pond_1_level", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Pond_1_Lev", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pond_1_lo_alm", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Pond_1_Lo_Alarm", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pond_1_lo_clr_spt", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Pond_1_Lo_Clr_Setpoint", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pond_1_lo_spt", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Pond_1_Lo_Setpoint", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pond_1_total_bbls", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Pond_1_Total_Barrels", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pump_1_auto", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P001_AUTO_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": " HOA auto status", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pump_1_hand", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P001_HAND_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": " HOA hand status", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pump_1_local_start", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P001_START_BTN_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "Local start status", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pump_1_overload_status", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P001_OVERLOAD_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pump_1_run_fail_alm", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P001_RUN_FAIL_ALARM", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pump_1_run_status", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P001_RUN_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": " Pump running status", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pump_2_auto", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P002_AUTO_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": " HOA auto status", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pump_2_hand", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P002_HAND_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": " HOA hand status", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pump_2_local_start", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P002_START_BTN_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "Local start status", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pump_2_overload_status", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P002_OVERLOAD_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pump_2_run_fail_alm", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P002_RUN_FAIL_ALARM", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "pump_2_run_status", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P002_RUN_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": " Pump running status", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "run_level_perm", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Run_Level_Perm", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "start_level_spt", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Start_Level_spt ", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": " start level input", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "stop_level_spt", + "ctrlName": "overflow_pump", + "group": "default", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Stop_Level_spt ", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": " stop level input", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "fm_1_flowrate", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "FIT1_FLOWRATE", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "fm_1_lolo_alm", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "FIT1_LL_ALARM", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "fm_1_lolo_reset_spt", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "FIT1_LL_RESET_SPT", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "fm_1_lolo_spt", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "FIT1_LL_SET_SPT", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "fm_1_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "FIT1_TOTAL_FLOWRATE", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "fm_2_flowrate", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "FIT2_FLOWRATE", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "fm_2_lolo_alm", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "FIT2_LL_ALARM", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "fm_2_lolo_reset_spt", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "FIT2_LL_RESET_SPT", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "fm_2_lolo_spt", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "FIT2_LL_SET_SPT", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "fm_2_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "FIT2_TOTAL_FLOWRATE", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_0ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_0FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_10ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_10FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_11ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_11FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_12ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_12FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_13ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_13FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_14ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_14FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_15ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_15FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_16ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_16FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_1ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_1FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_2ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_2FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_3ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_3FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_4ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_4FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_5ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_5FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_6ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_6FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_7ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_7FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_8ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_8FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_9ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_9FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_cu_ft", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_CU_Ft", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_cubic_feet_to_barrels", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_Cubic_Feet_To_Barrels", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_hi_alm", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Leak_1_Hi_Alarm", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_hi_alm_enable", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Leak_1_Hi_Alarm_Enable", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_hi_alm_enabled", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Leak_1_Hi_Alarm_Enabled", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_hi_clr_spt", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_Hi_Clr_Setpoint", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_hi_reset", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Leak_1_Hi_Reset", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_hi_set", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Leak_1_Hi_Set", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_hi_spt", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_Hi_Setpoint", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_level", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_Lev", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_level_psi", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_Lev_Psi", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_lo_alm", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Leak_1_Lo_Alarm", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_lo_clr_spt", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_Lo_Clr_Setpoint", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_lo_reset", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Leak_1_Lo_Reset", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_lo_set", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Leak_1_Lo_Set", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_lo_spt", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_Lo_Setpoint", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_pump_off_spt", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_Pump_Off_Spt", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_pump_on_spt", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_Pump_On_Spt", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_raw_max", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_Raw_Max", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_raw_min", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_Raw_Min", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_rise_multiplier", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_Rise_Multiplier", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_scaled_max", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_ScaledMax", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_scaled_min", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_ScaledMin", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_stage_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_Stage_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_1_total_barrels", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_1_Total_Barrels", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_0ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_0FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_10ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_10FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_11ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_11FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_12ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_12FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_13ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_13FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_14ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_14FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_15ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_15FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_16ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_16FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_1ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_1FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_2ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_2FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_3ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_3FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_4ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_4FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_5ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_5FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_6ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_6FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_7ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_7FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_8ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_8FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_9ft_volume_total", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_9FT_Volume_Total", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_cu_ft", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_CU_Ft", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_cubic_feet_to_barrels", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_Cubic_Feet_To_Barrels", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_hi_alm", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Leak_2_Hi_Alarm", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_hi_clr_spt", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_Hi_Clr_Setpoint", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_hi_reset", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Leak_2_Hi_Reset", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_hi_set", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Leak_2_Hi_Set", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_hi_spt", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_Hi_Setpoint", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_level", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_Lev", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_level_psi", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_Lev_Psi", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_lo_alm", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Leak_2_Lo_Alarm", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_lo_clr_spt", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_Lo_Clr_Setpoint", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_lo_reset", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Leak_2_Lo_Reset", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_lo_set", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "Leak_2_Lo_Set", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_lo_spt", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_Lo_Setpoint", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_pump_off_spt", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_Pump_Off_Spt", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_pump_on_spt", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_Pump_On_Spt", + "decimal": 2, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_raw_max", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_Raw_Max", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_raw_min", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_Raw_Min", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_rise_multiplier", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_Rise_Multiplier", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_scaled_max", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_ScaledMax", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_scaled_min", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_ScaledMin", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "leak_2_total_barrels", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "FLOAT", + "addr": "Leak_2_Total_Barrels", + "decimal": 2, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "p001_auto", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P001_AUTO_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "p001_hand", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P001_HAND_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "p001_overload", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P001_OVERLOAD_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "p001_overload_alm", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P001_OVERLOAD_ALARM", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "p001_run", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P001_RUN_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "p001_run_cmd", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P001_RUN_CMD", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "p001_run_fail_alm", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P001_RUN_FAIL_ALARM", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "p001_start", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P001_START_BTN_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "p002_auto", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P002_AUTO_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "p002_hand", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P002_HAND_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "p002_overload", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P002_OVERLOAD_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "p002_overload_alm", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P002_OVERLOAD_ALARM", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "p002_run", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P002_RUN_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "p002_run_cmd", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P002_RUN_CMD", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "rw", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "p002_run_fail_alm", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P002_RUN_FAIL_ALARM", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "p002_start", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "P002_START_BTN_FBK", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + }, + { + "name": "reset", + "ctrlName": "leak_detection", + "group": "fastReport", + "uploadType": "periodic", + "dataType": "BIT", + "addr": "RESET", + "bitMap": 0, + "reverseBit": 0, + "readWrite": "ro", + "unit": "", + "desc": "", + "storageLwTSDB": 1, + "transformType": 0 + } + ], + "alarmLables": [ + "default", + "gateway" + ], + "alarms": [ + { + "name": "air_comp_low_alm", + "ctrlName": "plcpond", + "measureName": "air_comp_low_alm", + "alarmLevel": 5, + "cond1": { + "op": "eq", + "value": "1.0" + }, + "condOp": "none", + "cond2": { + "op": "eq", + "value": "" + }, + "content": "Alarm Triggered", + "alarmLable": "default" + }, + { + "name": "pond_1_hi_alm", + "ctrlName": "overflow_pump", + "measureName": "pond_1_hi_alm", + "alarmLevel": 5, + "cond1": { + "op": "eq", + "value": "1.0" + }, + "condOp": "none", + "cond2": { + "op": "eq", + "value": "" + }, + "content": "Pond 2 Lo", + "alarmLable": "gateway" + }, + { + "name": "pond_1_lo_alm", + "ctrlName": "overflow_pump", + "measureName": "pond_1_lo_alm", + "alarmLevel": 5, + "cond1": { + "op": "eq", + "value": "1.0" + }, + "condOp": "none", + "cond2": { + "op": "eq", + "value": "" + }, + "content": "Pond 1 Lo", + "alarmLable": "gateway" + }, + { + "name": "pump_1_run_fail_alm", + "ctrlName": "overflow_pump", + "measureName": "pump_1_run_fail_alm", + "alarmLevel": 5, + "cond1": { + "op": "eq", + "value": "1.0" + }, + "condOp": "none", + "cond2": { + "op": "eq", + "value": "" + }, + "content": "Alarm Triggered", + "alarmLable": "gateway" + }, + { + "name": "pump_2_run_fail_alm", + "ctrlName": "overflow_pump", + "measureName": "pump_2_run_fail_alm", + "alarmLevel": 5, + "cond1": { + "op": "eq", + "value": "1.0" + }, + "condOp": "none", + "cond2": { + "op": "eq", + "value": "" + }, + "content": "Alarm Triggered", + "alarmLable": "gateway" + }, + { + "name": "leak_1_hi_alm", + "ctrlName": "leak_detection", + "measureName": "leak_1_hi_alm", + "alarmLevel": 5, + "cond1": { + "op": "eq", + "value": "1.0" + }, + "condOp": "none", + "cond2": { + "op": "eq", + "value": "" + }, + "content": "Alarm Triggered", + "alarmLable": "gateway" + }, + { + "name": "leak_1_lo_alm", + "ctrlName": "leak_detection", + "measureName": "leak_1_lo_alm", + "alarmLevel": 5, + "cond1": { + "op": "eq", + "value": "1.0" + }, + "condOp": "none", + "cond2": { + "op": "eq", + "value": "" + }, + "content": "Alarm Triggered", + "alarmLable": "gateway" + }, + { + "name": "leak_2_hi_alm", + "ctrlName": "leak_detection", + "measureName": "leak_2_hi_alm", + "alarmLevel": 5, + "cond1": { + "op": "eq", + "value": "1.0" + }, + "condOp": "none", + "cond2": { + "op": "eq", + "value": "" + }, + "content": "Alarm Triggered", + "alarmLable": "gateway" + }, + { + "name": "leak_2_lo_alm", + "ctrlName": "leak_detection", + "measureName": "leak_2_lo_alm", + "alarmLevel": 5, + "cond1": { + "op": "eq", + "value": "1.0" + }, + "condOp": "none", + "cond2": { + "op": "eq", + "value": "" + }, + "content": "Alarm Triggered", + "alarmLable": "gateway" + }, + { + "name": "p001_overload_alm", + "ctrlName": "leak_detection", + "measureName": "p001_overload_alm", + "alarmLevel": 5, + "cond1": { + "op": "eq", + "value": "1.0" + }, + "condOp": "none", + "cond2": { + "op": "eq", + "value": "" + }, + "content": "Alarm Triggered", + "alarmLable": "gateway" + }, + { + "name": "p001_run_fail_alm", + "ctrlName": "leak_detection", + "measureName": "p001_run_fail_alm", + "alarmLevel": 5, + "cond1": { + "op": "eq", + "value": "1.0" + }, + "condOp": "none", + "cond2": { + "op": "eq", + "value": "" + }, + "content": "Alarm Triggered", + "alarmLable": "gateway" + }, + { + "name": "p002_overload_alm", + "ctrlName": "leak_detection", + "measureName": "p002_overload_alm", + "alarmLevel": 5, + "cond1": { + "op": "eq", + "value": "1.0" + }, + "condOp": "none", + "cond2": { + "op": "eq", + "value": "" + }, + "content": "Alarm Triggered", + "alarmLable": "gateway" + }, + { + "name": "p002_run_fail_alm", + "ctrlName": "leak_detection", + "measureName": "p002_run_fail_alm", + "alarmLevel": 5, + "cond1": { + "op": "eq", + "value": "1.0" + }, + "condOp": "none", + "cond2": { + "op": "eq", + "value": "" + }, + "content": "Alarm Triggered", + "alarmLable": "gateway" + }, + { + "name": "fm_1_lolo_alm", + "ctrlName": "leak_detection", + "alarmLevel": 5, + "content": "Alarm Triggered", + "alarmLable": "gateway", + "measureName": "fm_1_lolo_alm", + "cond1": { + "op": "eq", + "value": "1" + }, + "cond2": { + "op": "eq", + "value": "" + }, + "condOp": "none" + }, + { + "name": "fm_2_lolo_alm", + "ctrlName": "leak_detection", + "alarmLevel": 5, + "content": "Alarm Triggered", + "alarmLable": "gateway", + "measureName": "fm_2_lolo_alm", + "cond1": { + "op": "eq", + "value": "1" + }, + "cond2": { + "op": "eq", + "value": "" + }, + "condOp": "none" + } + ], + "groups": [ + { + "name": "default", + "uploadInterval": 600, + "reference": 16, + "LwTSDBSize": 1000, + "strategy": 1, + "historyDataPath": "/var/user/data/dbhome/device_supervisor/LwTSDB", + "enablePerOnchange": 1, + "onchangePeriod": 600 + }, + { + "historyDataPath": "/var/user/data/dbhome/device_supervisor/LwTSDB", + "name": "fastReport", + "uploadInterval": 60, + "enablePerOnchange": 0, + "LwTSDBSize": 1000, + "strategy": 1 + } + ], + "misc": { + "maxAlarmRecordSz": 2000, + "logLvl": "INFO", + "coms": [ + { + "name": "rs232", + "baud": 9600, + "bits": 8, + "stopbits": 1, + "parityChk": "n" + }, + { + "name": "rs485", + "baud": 9600, + "bits": 8, + "stopbits": 1, + "parityChk": "n" + } + ], + "cachePath": "/var/user/data/dbhome/device_supervisor/offlinedata", + "cacheSize": 10000, + "debugLogPath": "/var/user/data/dbhome/device_supervisor/debugLog", + "debugLogSize": 2000 + }, + "clouds": [ + { + "cacheSize": 10000, + "enable": 1, + "name": "default", + "type": "Standard MQTT", + "args": { + "host": "hp.henrypump.cloud", + "port": 1883, + "clientId": "faskens-xx-pond", + "auth": 1, + "tls": 0, + "cleanSession": 1, + "mqttVersion": "v3.1.1", + "keepalive": 600, + "key": "", + "cert": "", + "rootCA": "", + "verifyServer": 0, + "verifyClient": 0, + "username": "faskensmqtt", + "passwd": "faskensmqtt@1903", + "authType": 1, + "willQos": 0, + "willRetain": 0, + "willTopic": "", + "willPayload": "", + "tlsAuth": "caSelfSigned" + }, + "uploadRules": [] + } + ], + "quickfaas": { + "genericFuncs": [], + "uploadFuncs": [ + { + "name": "sendData", + "trigger": "measure_event", + "topic": "v1/devices/me/telemetry", + "qos": 1, + "groups": [ + "default" + ], + "funcName": "sendData", + "script": "import json\nimport os\nimport time\nfrom datetime import datetime as dt\nfrom common.Logger import logger\nfrom quickfaas.remotebus import publish\nfrom quickfaas.global_dict import get as get_params\nfrom quickfaas.global_dict import _set_global_args\nfrom mobiuspi_lib.gps import GPS\n\n\ndef reboot():\n # basic = Basic()\n logger.info(\"!\" * 10 + \"REBOOTING DEVICE\" + \"!\"*10)\n r = os.popen(\"kill -s SIGHUP `cat /var/run/python/supervisord.pid`\").read()\n logger.info(f\"REBOOT : {r}\")\n\n\ndef checkFileExist(filename):\n path = \"/var/user/files\"\n if not os.path.exists(path):\n logger.info(\"no folder making files folder in var/user\")\n os.makedirs(path)\n with open(path + \"/\" + filename, \"a\") as f:\n json.dump({}, f)\n if not os.path.exists(path + \"/\" + filename):\n logger.info(\"no creds file making creds file\")\n with open(path + \"/\" + filename, \"a\") as f:\n json.dump({}, f)\n\n\ndef convertDStoJSON(ds):\n j = dict()\n for x in ds:\n j[x[\"key\"]] = x[\"value\"]\n return j\n\n\ndef convertJSONtoDS(j):\n d = []\n for key in j.keys():\n d.append({\"key\": key, \"value\": j[key]})\n return d\n\n\ndef checkCredentialConfig():\n logger.info(\"CHECKING CONFIG\")\n cfgpath = \"/var/user/cfg/device_supervisor/device_supervisor.cfg\"\n credspath = \"/var/user/files/creds.json\"\n cfg = dict()\n with open(cfgpath, \"r\") as f:\n cfg = json.load(f)\n clouds = cfg.get(\"clouds\")\n logger.info(clouds)\n # if not configured then try to configure from stored values\n if clouds[0][\"args\"][\"clientId\"] == \"unknown\" or clouds[0][\"args\"][\"username\"] == \"unknown\" or not clouds[0][\"args\"][\"passwd\"] or clouds[0][\"args\"][\"passwd\"] == \"unknown\":\n checkFileExist(\"creds.json\")\n with open(credspath, \"r\") as c:\n creds = json.load(c)\n if creds:\n logger.info(\"updating config with stored data\")\n clouds[0][\"args\"][\"clientId\"] = creds[\"clientId\"]\n clouds[0][\"args\"][\"username\"] = creds[\"userName\"]\n clouds[0][\"args\"][\"passwd\"] = creds[\"password\"]\n cfg[\"clouds\"] = clouds\n cfg = checkParameterConfig(cfg)\n with open(cfgpath, \"w\", encoding='utf-8') as n:\n json.dump(cfg, n, indent=1, ensure_ascii=False)\n reboot()\n else:\n # assuming clouds is filled out, if data is different then assume someone typed in something new and store it, if creds is empty fill with clouds' data\n checkFileExist(\"creds.json\")\n with open(credspath, \"r\") as c:\n logger.info(\"updating stored file with new data\")\n cfg = checkParameterConfig(cfg)\n with open(cfgpath, \"w\", encoding='utf-8') as n:\n json.dump(cfg, n, indent=1, ensure_ascii=False)\n creds = json.load(c)\n if creds:\n if creds[\"clientId\"] != clouds[0][\"args\"][\"clientId\"]:\n creds[\"clientId\"] = clouds[0][\"args\"][\"clientId\"]\n if creds[\"userName\"] != clouds[0][\"args\"][\"username\"]:\n creds[\"userName\"] = clouds[0][\"args\"][\"username\"]\n if creds[\"password\"] != clouds[0][\"args\"][\"passwd\"]:\n creds[\"password\"] = clouds[0][\"args\"][\"passwd\"]\n else:\n creds[\"clientId\"] = clouds[0][\"args\"][\"clientId\"]\n creds[\"userName\"] = clouds[0][\"args\"][\"username\"]\n creds[\"password\"] = clouds[0][\"args\"][\"passwd\"]\n with open(credspath, \"w\") as cw:\n json.dump(creds, cw)\n\n\ndef checkParameterConfig(cfg):\n logger.info(\"Checking Parameters!!!!\")\n paramspath = \"/var/user/files/params.json\"\n cfgparams = convertDStoJSON(cfg.get(\"labels\"))\n # check stored values\n checkFileExist(\"params.json\")\n with open(paramspath, \"r\") as f:\n logger.info(\"Opened param storage file\")\n params = json.load(f)\n if params:\n if cfgparams != params:\n # go through each param\n # if not \"unknown\" and cfg and params aren't the same take from cfg likely updated manually\n # if key in cfg but not in params copy to params\n logger.info(\"equalizing params between cfg and stored\")\n for key in cfgparams.keys():\n try:\n if cfgparams[key] != params[key] and cfgparams[key] != \"unknown\":\n params[key] = cfgparams[key]\n except:\n params[key] = cfgparams[key]\n cfg[\"labels\"] = convertJSONtoDS(params)\n _set_global_args(convertJSONtoDS(params))\n with open(paramspath, \"w\") as p:\n json.dump(params, p)\n else:\n with open(paramspath, \"w\") as p:\n logger.info(\"initializing param file with params in memory\")\n json.dump(convertDStoJSON(get_params()), p)\n cfg[\"labels\"] = get_params()\n\n return cfg\n\n\ndef getGPS():\n # Create a gps instance\n gps = GPS()\n\n # Retrieve GPS information\n position_status = gps.get_position_status()\n logger.debug(\"position_status: \")\n logger.debug(position_status)\n latitude = position_status[\"latitude\"].split(\" \")\n longitude = position_status[\"longitude\"].split(\" \")\n lat_dec = int(latitude[0][:-1]) + (float(latitude[1][:-1])/60)\n lon_dec = int(longitude[0][:-1]) + (float(longitude[1][:-1])/60)\n if latitude[2] == \"S\":\n lat_dec = lat_dec * -1\n if longitude[2] == \"W\":\n lon_dec = lon_dec * -1\n # lat_dec = round(lat_dec, 7)\n # lon_dec = round(lon_dec, 7)\n logger.info(\"HERE IS THE GPS COORDS\")\n logger.info(f\"LATITUDE: {lat_dec}, LONGITUDE: {lon_dec}\")\n speedKnots = position_status[\"speed\"].split(\" \")\n speedMPH = float(speedKnots[0]) * 1.151\n return (f\"{lat_dec:.8f}\", f\"{lon_dec:.8f}\", f\"{speedMPH:.2f}\")\n\n\ndef chunk_payload(payload, chunk_size=20):\n if \"values\" in payload:\n # Original format: {\"ts\": ..., \"values\": {...}}\n chunked_values = list(payload[\"values\"].items())\n for i in range(0, len(chunked_values), chunk_size):\n yield {\n \"ts\": payload[\"ts\"],\n \"values\": dict(chunked_values[i:i+chunk_size])\n }\n else:\n # New format: {\"key1\": \"value1\", \"key2\": \"value2\"}\n chunked_keys = list(payload.keys())\n for i in range(0, len(chunked_keys), chunk_size):\n yield {k: payload[k] for k in chunked_keys[i:i+chunk_size]}\n\n\ndef chunk_payload_devices(payload, chunk_size=20, is_attributes_payload=False):\n if is_attributes_payload:\n # For attributes payload, chunk the controllers\n controllers = list(payload.items())\n for i in range(0, len(controllers), chunk_size):\n yield dict(controllers[i:i + chunk_size])\n else:\n # For data payload, chunk the values within each controller\n for controller, data in payload.items():\n for entry in data:\n ts = entry['ts']\n values = entry['values']\n chunked_values = list(values.items())\n for i in range(0, len(chunked_values), chunk_size):\n yield {\n controller: [{\n \"ts\": ts,\n \"values\": dict(chunked_values[i:i + chunk_size])\n }]\n }\n\n\ndef controlName(name):\n logger.debug(name)\n params = convertDStoJSON(get_params())\n logger.debug(params)\n nameMap = {\n \"overflow_pump\": f\"{params['overflow_pump']}\",\n \"leak_detection\": f\"{params['leak_detection']}\"\n }\n return nameMap.get(name, \"Gateway\")\n\n\ndef sendData(message):\n # logger.info(message)\n grouped_data = {}\n grouped_attributes = {}\n now = (round(dt.timestamp(dt.now())/600)*600)*1000\n payload = {\"ts\": now, \"values\": {}}\n attributes_payload = {}\n logger.debug(message)\n for measure in message[\"measures\"]:\n try:\n logger.debug(measure)\n ctrlName = controlName(measure[\"ctrlName\"])\n logger.debug(ctrlName)\n if ctrlName == \"Gateway\":\n # send to gateway with v1/devices/me/telemetry\n if measure[\"health\"] == 1:\n if \"_spt\" in measure[\"name\"]:\n attributes_payload[measure[\"name\"]] = measure[\"value\"]\n else:\n payload[\"values\"][measure[\"name\"]] = measure[\"value\"]\n else:\n name = measure['name']\n value = measure['value']\n health = measure['health']\n # Add controller for telemetry if it doesn't exist\n if ctrlName not in grouped_data:\n grouped_data[ctrlName] = {}\n # Add controller for attributes if it doesn't exist\n if ctrlName not in grouped_attributes:\n grouped_attributes[ctrlName] = {}\n grouped_attributes[ctrlName][\"latestReportTime\"] = now\n # Add data to temp payload if datapoint health is good\n if health:\n if \"_spt\" in name:\n grouped_attributes[ctrlName][name] = value\n else:\n grouped_data[ctrlName][name] = value\n except Exception as e:\n logger.error(e)\n\n # Transform the grouped data to desired structure\n payload_devices = {}\n #logger.info(grouped_data)\n for key, value in grouped_data.items():\n if value:\n payload_devices[key] = [{\"ts\": now, \"values\": value}]\n\n attributes_payload_devices = {}\n for key, value in grouped_attributes.items():\n if value:\n attributes_payload_devices[key] = value\n #logger.info(payload_devices)\n #logger.info(attributes_payload_devices)\n #logger.info(payload)\n #logger.info(attributes)\n # Send data belonging to Gateway\n for chunk in chunk_payload(payload=payload):\n publish(__topic__, json.dumps(chunk), __qos__)\n time.sleep(2)\n\n attributes_payload[\"latestReportTime\"] = (\n round(dt.timestamp(dt.now())/600)*600)*1000\n for chunk in chunk_payload(payload=attributes_payload):\n publish(\"v1/devices/me/attributes\", json.dumps(chunk), __qos__)\n time.sleep(2)\n\n # Send gateway devices data\n for chunk in chunk_payload_devices(payload=payload_devices):\n publish(\"v1/gateway/telemetry\", json.dumps(chunk), __qos__)\n time.sleep(2)\n\n for chunk in chunk_payload_devices(payload=attributes_payload_devices, is_attributes_payload=True):\n publish(\"v1/gateway/attributes\",\n json.dumps(attributes_payload_devices), __qos__)\n time.sleep(2)\n", + "cloudName": "default", + "msgType": 0 + }, + { + "qos": 2, + "funcName": "sendAlarm", + "script": "import json, time\nfrom common.Logger import logger\nfrom quickfaas.remotebus import publish\n\n\ndef sendAlarm(message):\n logger.info(message)\n payload = {}\n payload[\"ts\"] = time.time()*1000\n payload[\"values\"] = {message[\"measureName\"]: message[\"value\"]}\n publish(__topic__, json.dumps(payload), __qos__,cloud_name=\"default\")", + "name": "sendAlarm", + "trigger": "warning_event", + "topic": "v1/devices/me/telemetry", + "cloudName": "default", + "alarms": [ + "default" + ], + "msgType": 0 + }, + { + "qos": 1, + "funcName": "sendData", + "script": "import json, os, time, shutil, math\nfrom datetime import datetime as dt\nfrom common.Logger import logger\nfrom quickfaas.remotebus import publish\nfrom quickfaas.global_dict import get as get_params\nfrom quickfaas.global_dict import _set_global_args\nfrom mobiuspi_lib.gps import GPS\n\nclass RuntimeStats:\n def __init__(self):\n self.runs = {}\n self.currentRun = 0\n self.today = \"\"\n self.todayString = \"\"\n self.filePath = \"/var/user/files/runtimestats.json\"\n\n def __init__(self, filePath:str):\n self.runs = {}\n self.currentRun = 0\n self.today = \"\"\n self.todayString = \"\"\n self.filePath = filePath\n\n def manageTime(self):\n if self.todayString != dt.strftime(dt.today(), \"%Y-%m-%d\"):\n if self.runs[self.todayString][\"run_\" + str(self.currentRun)][\"start\"] and not self.runs[self.todayString][\"run_\" + str(self.currentRun)][\"end\"]:\n self.runs[self.todayString][\"run_\" + str(self.currentRun)][\"end\"] = time.mktime(dt.strptime(self.todayString + \" 23:59:59\", \"%Y-%m-%d %H:%M:%S\").timetuple())\n self.addDay()\n self.today = dt.today()\n self.todayString = dt.strftime(self.today, \"%Y-%m-%d\")\n days = list(self.runs.keys())\n days.sort()\n while (dt.strptime(days[-1],\"%Y-%m-%d\") - dt.strptime(days[0], \"%Y-%m-%d\")).days > 40:\n self.removeDay(day=days[0])\n days = list(self.runs.keys())\n days.sort()\n\n def addHertzDataPoint(self, frequency):\n if frequency > 0:\n self.manageTime()\n try:\n self.runs[self.todayString][\"run_\" + str(self.currentRun)][\"frequencies\"].append(frequency)\n except:\n self.runs[self.todayString][\"run_\" + str(self.currentRun)][\"frequencies\"] = [frequency]\n\n def startRun(self):\n if self.checkRunning():\n self.endRun()\n self.runs[self.todayString][\"run_\" + str(self.currentRun)][\"start\"] = time.time()\n\n def endRun(self):\n self.runs[self.todayString][\"run_\" + str(self.currentRun)][\"end\"] = time.time()\n self.currentRun += 1\n self.runs[self.todayString][\"run_\" + str(self.currentRun)] = {\"start\":0, \"end\": 0, \"frequencies\":[]} \n\n def checkRunning(self):\n if self.runs[self.todayString][\"run_\" + str(self.currentRun)][\"start\"] and not self.runs[self.todayString][\"run_\" + str(self.currentRun)][\"end\"]:\n return True\n return False\n\n def addDay(self):\n self.today = dt.today()\n self.todayString = dt.strftime(self.today, \"%Y-%m-%d\")\n self.currentRun = 1\n self.runs[self.todayString] = {}\n self.runs[self.todayString][\"run_\" + str(self.currentRun)] = {\"start\":0, \"end\": 0, \"frequencies\":[]}\n\n def countRunsDay(self, day=None):\n if not day:\n day = self.todayString\n return len(self.runs[day].keys())\n\n def countRunsMultiDay(self, numDays=30):\n total_runs = 0\n for day in list(self.runs.keys()):\n total_runs += self.countRunsDay(day=day)\n return total_runs\n\n def calculateAverageHertzDay(self, day=None, returnArray=False):\n dayFrequencies = []\n if not day:\n day = self.todayString\n for run in list(self.runs[day].keys()):\n try:\n dayFrequencies += self.runs[day][run][\"frequencies\"]\n except Exception as e:\n logger.debug(\"{} missing frequency data for {}\".format(day,run))\n if returnArray:\n return dayFrequencies\n return round(math.fsum(dayFrequencies)/len(dayFrequencies),2)\n\n def calculateAverageHertzMultiDay(self, numDays=30):\n self.manageTime()\n frequencies = []\n for day in list(self.runs.keys()):\n if not day == self.todayString and (dt.strptime(self.todayString, \"%Y-%m-%d\") - dt.strptime(day, \"%Y-%m-%d\")).days <= numDays:\n try:\n frequencies += self.calculateAverageHertzDay(day=day, returnArray=True)\n except Exception as e:\n logger.debug(\"{} missing frequency data\".format(day))\n if len(frequencies):\n return round(math.fsum(frequencies)/len(frequencies), 2)\n return 0\n \n def calculateRunTimeDay(self, day=None, convertToHours=True):\n self.manageTime()\n total_time = 0\n if not day:\n day = self.todayString\n for run in list(self.runs.get(day,{}).keys()):\n if self.runs[day][run][\"end\"] == 0 and self.runs[day][run][\"start\"] != 0:\n total_time = time.time() - self.runs[day][run][\"start\"] + total_time\n else:\n total_time = self.runs[day][run][\"end\"] - self.runs[day][run][\"start\"] + total_time\n if convertToHours:\n return self.convertSecondstoHours(total_time)\n return total_time\n\n def calculateRunTimeMultiDay(self, numDays=30, convertToHours=True):\n total_time = 0\n for day in list(self.runs.keys()):\n if (dt.strptime(self.todayString, \"%Y-%m-%d\") - dt.strptime(day, \"%Y-%m-%d\")).days <= numDays:\n total_time += self.calculateRunTimeDay(day=day, convertToHours=False)\n if convertToHours:\n return self.convertSecondstoHours(total_time)\n return total_time\n \n def calculateRunPercentDay(self, day=None, precise=False):\n if not day:\n day = self.todayString\n if precise:\n return (self.calculateRunTimeDay(day=day)/24) * 100\n return round((self.calculateRunTimeDay(day=day)/24) * 100, 2)\n \n\n def calculateRunPercentMultiDay(self, numDays=30, precise=False):\n self.manageTime()\n if precise:\n return (self.calculateRunTimeMultiDay()/(24*numDays)) * 100\n return round((self.calculateRunTimeMultiDay()/(24*numDays)) * 100,2)\n\n def removeDay(self, day=None):\n if not day:\n raise Exception(\"Day can not be None\")\n logger.debug(\"removing day {}\".format(day))\n del self.runs[day]\n self.saveDataToFile(filePath=self.filePath)\n \n def convertSecondstoHours(self, seconds):\n return round(seconds / (60*60),2)\n\n def resetData(self):\n logger.debug(\"clearing database\")\n try:\n for day in list(self.runs.keys()):\n self.removeDay(day=day, filePath=self.filePath)\n except Exception as e:\n logger.error(e)\n return False\n self.runs = {}\n self.currentRun = 0\n self.today = \"\"\n self.todayString = \"\"\n self.manageTime()\n return True\n\n def loadDataFromFile(self):\n try:\n with open(self.filePath, \"r\") as f:\n temp = json.load(f)\n self.runs = temp[\"data\"]\n self.currentRun = temp[\"current_run\"]\n self.today = dt.strptime(temp[\"current_day\"], \"%Y-%m-%d\")\n self.todayString = temp[\"current_day\"]\n self.manageTime()\n except:\n logger.debug(\"Could not find file at {}\".format(self.filePath))\n logger.debug(\"creating file\")\n self.addDay()\n try:\n with open(self.filePath, \"w\") as f:\n d = {\n \"current_run\": self.currentRun,\n \"current_day\": self.todayString,\n \"data\": self.runs\n }\n json.dump(d, f, indent=4)\n except Exception as e:\n logger.error(e)\n\n def saveDataToFile(self):\n try:\n logger.debug(\"Saving Runs\")\n with open(self.filePath, \"w\") as f:\n d = {\n \"current_run\": self.currentRun,\n \"current_day\": self.todayString,\n \"data\": self.runs\n }\n json.dump(d, f, indent=4)\n except Exception as e:\n logger.error(e)\n\nrts1 = RuntimeStats(\"/var/user/files/runtimestats_p001.json\")\nrts1.loadDataFromFile()\nrts1.saveDataToFile()\n\nrts2 = RuntimeStats(\"/var/user/files/runtimestats_p002.json\")\nrts2.loadDataFromFile()\nrts2.saveDataToFile()\n\ndef reboot():\n # basic = Basic()\n logger.info(\"!\" * 10 + \"REBOOTING DEVICE\" + \"!\"*10)\n r = os.popen(\"kill -s SIGHUP `cat /var/run/python/supervisord.pid`\").read()\n logger.info(f\"REBOOT : {r}\")\n\n\ndef checkFileExist(filename):\n path = \"/var/user/files\"\n if not os.path.exists(path):\n logger.info(\"no folder making files folder in var/user\")\n os.makedirs(path)\n with open(path + \"/\" + filename, \"a\") as f:\n json.dump({}, f)\n if not os.path.exists(path + \"/\" + filename):\n logger.info(\"no creds file making creds file\")\n with open(path + \"/\" + filename, \"a\") as f:\n json.dump({}, f)\n\n\ndef convertDStoJSON(ds):\n j = dict()\n for x in ds:\n j[x[\"key\"]] = x[\"value\"]\n return j\n\n\ndef convertJSONtoDS(j):\n d = []\n for key in j.keys():\n d.append({\"key\": key, \"value\": j[key]})\n return d\n\n\ndef checkCredentialConfig():\n logger.info(\"CHECKING CONFIG\")\n cfgpath = \"/var/user/cfg/device_supervisor/device_supervisor.cfg\"\n credspath = \"/var/user/files/creds.json\"\n cfg = dict()\n with open(cfgpath, \"r\") as f:\n cfg = json.load(f)\n clouds = cfg.get(\"clouds\")\n logger.info(clouds)\n # if not configured then try to configure from stored values\n if clouds[0][\"args\"][\"clientId\"] == \"unknown\" or clouds[0][\"args\"][\"username\"] == \"unknown\" or not clouds[0][\"args\"][\"passwd\"] or clouds[0][\"args\"][\"passwd\"] == \"unknown\":\n checkFileExist(\"creds.json\")\n with open(credspath, \"r\") as c:\n creds = json.load(c)\n if creds:\n logger.info(\"updating config with stored data\")\n clouds[0][\"args\"][\"clientId\"] = creds[\"clientId\"]\n clouds[0][\"args\"][\"username\"] = creds[\"userName\"]\n clouds[0][\"args\"][\"passwd\"] = creds[\"password\"]\n cfg[\"clouds\"] = clouds\n cfg = checkParameterConfig(cfg)\n with open(cfgpath, \"w\", encoding='utf-8') as n:\n json.dump(cfg, n, indent=1, ensure_ascii=False)\n reboot()\n else:\n # assuming clouds is filled out, if data is different then assume someone typed in something new and store it, if creds is empty fill with clouds' data\n checkFileExist(\"creds.json\")\n with open(credspath, \"r\") as c:\n logger.info(\"updating stored file with new data\")\n cfg = checkParameterConfig(cfg)\n with open(cfgpath, \"w\", encoding='utf-8') as n:\n json.dump(cfg, n, indent=1, ensure_ascii=False)\n creds = json.load(c)\n if creds:\n if creds[\"clientId\"] != clouds[0][\"args\"][\"clientId\"]:\n creds[\"clientId\"] = clouds[0][\"args\"][\"clientId\"]\n if creds[\"userName\"] != clouds[0][\"args\"][\"username\"]:\n creds[\"userName\"] = clouds[0][\"args\"][\"username\"]\n if creds[\"password\"] != clouds[0][\"args\"][\"passwd\"]:\n creds[\"password\"] = clouds[0][\"args\"][\"passwd\"]\n else:\n creds[\"clientId\"] = clouds[0][\"args\"][\"clientId\"]\n creds[\"userName\"] = clouds[0][\"args\"][\"username\"]\n creds[\"password\"] = clouds[0][\"args\"][\"passwd\"]\n with open(credspath, \"w\") as cw:\n json.dump(creds, cw)\n\n\ndef checkParameterConfig(cfg):\n logger.info(\"Checking Parameters!!!!\")\n paramspath = \"/var/user/files/params.json\"\n cfgparams = convertDStoJSON(cfg.get(\"labels\"))\n # check stored values\n checkFileExist(\"params.json\")\n with open(paramspath, \"r\") as f:\n logger.info(\"Opened param storage file\")\n params = json.load(f)\n if params:\n if cfgparams != params:\n # go through each param\n # if not \"unknown\" and cfg and params aren't the same take from cfg likely updated manually\n # if key in cfg but not in params copy to params\n logger.info(\"equalizing params between cfg and stored\")\n for key in cfgparams.keys():\n try:\n if cfgparams[key] != params[key] and cfgparams[key] != \"unknown\":\n params[key] = cfgparams[key]\n except:\n params[key] = cfgparams[key]\n cfg[\"labels\"] = convertJSONtoDS(params)\n _set_global_args(convertJSONtoDS(params))\n with open(paramspath, \"w\") as p:\n json.dump(params, p)\n else:\n with open(paramspath, \"w\") as p:\n logger.info(\"initializing param file with params in memory\")\n json.dump(convertDStoJSON(get_params()), p)\n cfg[\"labels\"] = get_params()\n\n return cfg\n\n\ndef getGPS():\n # Create a gps instance\n gps = GPS()\n\n # Retrieve GPS information\n position_status = gps.get_position_status()\n logger.debug(\"position_status: \")\n logger.debug(position_status)\n latitude = position_status[\"latitude\"].split(\" \")\n longitude = position_status[\"longitude\"].split(\" \")\n lat_dec = int(latitude[0][:-1]) + (float(latitude[1][:-1])/60)\n lon_dec = int(longitude[0][:-1]) + (float(longitude[1][:-1])/60)\n if latitude[2] == \"S\":\n lat_dec = lat_dec * -1\n if longitude[2] == \"W\":\n lon_dec = lon_dec * -1\n # lat_dec = round(lat_dec, 7)\n # lon_dec = round(lon_dec, 7)\n logger.info(\"HERE IS THE GPS COORDS\")\n logger.info(f\"LATITUDE: {lat_dec}, LONGITUDE: {lon_dec}\")\n speedKnots = position_status[\"speed\"].split(\" \")\n speedMPH = float(speedKnots[0]) * 1.151\n return (f\"{lat_dec:.8f}\", f\"{lon_dec:.8f}\", f\"{speedMPH:.2f}\")\n\ndef initialize_totalizers():\n return {\n \"day\": 0,\n \"week\": 0,\n \"month\": 0,\n \"year\": 0,\n \"lifetime\": 0,\n \"dayHolding\": 0,\n \"weekHolding\": 0,\n \"monthHolding\": 0,\n \"yearHolding\": 0,\n \"rolloverCounter\": 0\n }\n\ndef getTotalizers(file_path=\"/var/user/files/totalizers.json\"):\n \"\"\"\n Retrieves totalizer data from a JSON file.\n\n :param file_path: Path to the JSON file storing totalizer data.\n :return: Dictionary containing totalizer values.\n \"\"\"\n try:\n with open(file_path, \"r\") as t:\n totalizers = json.load(t)\n if not totalizers or not isinstance(totalizers, dict):\n logger.info(\"Invalid data format in the file. Initializing totalizers.\")\n totalizers = initialize_totalizers()\n except FileNotFoundError:\n logger.info(\"File not found. Initializing totalizers.\")\n totalizers = initialize_totalizers()\n except json.JSONDecodeError:\n timestamp = dt.now().strftime(\"%Y%m%d_%H%M%S\")\n # Split the file path and insert the timestamp before the extension\n file_name, file_extension = os.path.splitext(file_path)\n backup_file_path = f\"{file_name}_{timestamp}{file_extension}\"\n shutil.copyfile(file_path, backup_file_path)\n logger.error(f\"Error decoding JSON. A backup of the file is created at {backup_file_path}. Initializing totalizers.\")\n totalizers = initialize_totalizers()\n return totalizers\n\ndef saveTotalizers(totalizers, file_path=\"/var/user/files/totalizers.json\"):\n \"\"\"\n Saves totalizer data to a JSON file.\n\n :param totalizers: Dictionary containing totalizer values to be saved.\n :param file_path: Path to the JSON file where totalizer data will be saved.\n \"\"\"\n try:\n with open(file_path, \"w\") as t:\n json.dump(totalizers, t)\n except (IOError, OSError, json.JSONEncodeError) as e:\n logger.error(f\"Error saving totalizers to {file_path}: {e}\")\n raise # Optionally re-raise the exception if it should be handled by the caller\n\ndef chunk_payload(payload, chunk_size=20):\n if \"values\" in payload:\n # Original format: {\"ts\": ..., \"values\": {...}}\n chunked_values = list(payload[\"values\"].items())\n for i in range(0, len(chunked_values), chunk_size):\n yield {\n \"ts\": payload[\"ts\"],\n \"values\": dict(chunked_values[i:i+chunk_size])\n }\n else:\n # New format: {\"key1\": \"value1\", \"key2\": \"value2\"}\n chunked_keys = list(payload.keys())\n for i in range(0, len(chunked_keys), chunk_size):\n yield {k: payload[k] for k in chunked_keys[i:i+chunk_size]}\n\n\ndef chunk_payload_devices(payload, chunk_size=20, is_attributes_payload=False):\n if is_attributes_payload:\n # For attributes payload, chunk the controllers\n controllers = list(payload.items())\n for i in range(0, len(controllers), chunk_size):\n yield dict(controllers[i:i + chunk_size])\n else:\n # For data payload, chunk the values within each controller\n for controller, data in payload.items():\n for entry in data:\n ts = entry['ts']\n values = entry['values']\n chunked_values = list(values.items())\n for i in range(0, len(chunked_values), chunk_size):\n yield {\n controller: [{\n \"ts\": ts,\n \"values\": dict(chunked_values[i:i + chunk_size])\n }]\n }\n\n\ndef controlName(name):\n logger.debug(name)\n params = convertDStoJSON(get_params())\n logger.debug(params)\n nameMap = {\n \"overflow_pump\": f\"{params['overflow_pump']}\",\n \"leak_detection\": f\"{params['leak_detection']}\"\n }\n return nameMap.get(name, \"Gateway\")\n\n\ndef sendData(message):\n # logger.info(message)\n rts1.loadDataFromFile()\n rts2.loadDataFromFile()\n grouped_data = {}\n grouped_attributes = {}\n now = (round(dt.timestamp(dt.now())/60)*60)*1000\n payload = {\"ts\": now, \"values\": {}}\n attributes_payload = {}\n logger.debug(message)\n resetPayloadLD = {}\n totalizerHolding = {}\n sendResetData = False\n for measure in message[\"measures\"]:\n try:\n logger.debug(measure)\n ctrlName = controlName(measure[\"ctrlName\"])\n logger.debug(ctrlName)\n if ctrlName == \"Gateway\":\n # send to gateway with v1/devices/me/telemetry\n if measure[\"health\"] == 1:\n if \"_spt\" in measure[\"name\"]:\n attributes_payload[measure[\"name\"]] = measure[\"value\"]\n else:\n payload[\"values\"][measure[\"name\"]] = measure[\"value\"]\n else:\n name = measure['name']\n value = measure['value']\n health = measure['health']\n # Add controller for telemetry if it doesn't exist\n if ctrlName not in grouped_data:\n grouped_data[ctrlName] = {}\n # Add controller for attributes if it doesn't exist\n if ctrlName not in grouped_attributes:\n grouped_attributes[ctrlName] = {}\n grouped_attributes[ctrlName][\"latestReportTime\"] = now\n # Add data to temp payload if datapoint health is good\n if health:\n if \"_spt\" in name:\n grouped_attributes[ctrlName][name] = value\n else:\n grouped_data[ctrlName][name] = value\n if name in [\"p001_run\"]:\n rts1.manageTime()\n if value == 1 and not rts1.runs[rts1.todayString][\"run_\" + str(rts1.currentRun)][\"start\"]:\n rts1.startRun()\n rts1.saveDataToFile()\n elif value == 0 and rts1.runs[rts1.todayString][\"run_\" + str(rts1.currentRun)][\"start\"] and not rts1.runs[rts1.todayString][\"run_\" + str(rts1.currentRun)][\"end\"]:\n rts1.endRun()\n rts1.saveDataToFile()\n grouped_data[ctrlName][name + \"_today_running_hours\"] = rts1.calculateRunTimeDay()\n grouped_data[ctrlName][name + \"_month_running_hours\"] = rts1.calculateRunTimeMultiDay(numDays=dt.today().day)\n if name in [\"p002_run\"]:\n rts2.manageTime()\n if value == 1 and not rts2.runs[rts2.todayString][\"run_\" + str(rts2.currentRun)][\"start\"]:\n rts2.startRun()\n rts2.saveDataToFile()\n elif value == 0 and rts2.runs[rts2.todayString][\"run_\" + str(rts2.currentRun)][\"start\"] and not rts2.runs[rts2.todayString][\"run_\" + str(rts2.currentRun)][\"end\"]:\n rts2.endRun()\n rts2.saveDataToFile()\n grouped_data[ctrlName][name + \"_today_running_hours\"] = rts2.calculateRunTimeDay()\n grouped_data[ctrlName][name + \"_month_running_hours\"] = rts2.calculateRunTimeMultiDay(numDays=dt.today().day)\n if name in [\"fm_1_total\"]:\n if not totalizerHolding.get(ctrlName):\n totalizerHolding[ctrlName] = {\"fm_1\":{\"dayReset\": False, \"weekReset\": False, \"monthReset\": False, \"yearReset\": False}}\n if not totalizerHolding[ctrlName].get(\"fm_1\"):\n totalizerHolding[ctrlName][\"fm_1\"] = {\"dayReset\": False, \"weekReset\": False, \"monthReset\": False, \"yearReset\": False}\n totalizerHolding[ctrlName][\"fm_1\"][\"totalizer_1\"] = value\n if name in [\"fm_2_total\"]:\n if not totalizerHolding.get(ctrlName):\n totalizerHolding[ctrlName] = {\"fm_2\":{\"dayReset\": False, \"weekReset\": False, \"monthReset\": False, \"yearReset\": False}}\n if not totalizerHolding[ctrlName].get(\"fm_2\"):\n totalizerHolding[ctrlName][\"fm_2\"] = {\"dayReset\": False, \"weekReset\": False, \"monthReset\": False, \"yearReset\": False}\n totalizerHolding[ctrlName][\"fm_2\"][\"totalizer_1\"] = value\n except Exception as e:\n logger.error(e)\n for controller, meters in totalizerHolding.items():\n resetPayloadLD[controller] = [{\"ts\": now + 1, \"values\": {}}]\n for meter, values in meters.items():\n if meter == \"fm_1\":\n file_path = \"/var/user/files/totalizers_ld_fm_1.json\"\n elif meter == \"fm_2\":\n file_path = \"/var/user/files/totalizers_ld_fm_2.json\"\n else:\n file_path = \"/var/user/files/totalizers.json\"\n \n grouped_data[controller][f\"{meter}_year_total\"], values[\"yearReset\"] = totalizeYear(values[\"totalizer_1\"],file_path=file_path)\n grouped_data[controller][f\"{meter}_month_total\"], values[\"monthReset\"] = totalizeMonth(values[\"totalizer_1\"],file_path=file_path)\n grouped_data[controller][f\"{meter}_week_total\"], values[\"weekReset\"] = totalizeWeek(values[\"totalizer_1\"],file_path=file_path)\n grouped_data[controller][f\"{meter}_today_total\"], values[\"dayReset\"] = totalizeDay(values[\"totalizer_1\"],file_path=file_path)\n\n if values[\"dayReset\"]:\n resetPayloadLD[controller][0][\"values\"][f\"{meter}_yesterday_total\"] = grouped_data[controller][f\"{meter}_today_total\"]\n resetPayloadLD[controller][0][\"values\"][f\"{meter}_today_total\"] = 0\n sendResetData = True\n if values[\"weekReset\"]:\n resetPayloadLD[controller][0][\"values\"][f\"{meter}_last_week_total\"] = grouped_data[controller][f\"{meter}_week_total\"]\n resetPayloadLD[controller][0][\"values\"][f\"{meter}_week_total\"] = 0\n sendResetData = True\n if values[\"monthReset\"]:\n resetPayloadLD[controller][0][\"values\"][f\"{meter}_last_month_total\"] = grouped_data[controller][f\"{meter}_month_total\"]\n resetPayloadLD[controller][0][\"values\"][f\"{meter}_month_total\"] = 0\n sendResetData = True\n if values[\"yearReset\"]:\n resetPayloadLD[controller][0][\"values\"][f\"{meter}_last_year_total\"] = grouped_data[controller][f\"{meter}_year_total\"]\n resetPayloadLD[controller][0][\"values\"][f\"{meter}_year_total\"] = 0\n sendResetData = True\n\n \n\n # Transform the grouped data to desired structure\n payload_devices = {}\n #logger.info(grouped_data)\n for key, value in grouped_data.items():\n if value:\n payload_devices[key] = [{\"ts\": now, \"values\": value}]\n\n mergedPayload = {\n k: payload_devices.get(k, []) + [item for item in resetPayloadLD.get(k, []) if item.get('values')]\n for k in set(payload_devices) | set(resetPayloadLD)\n }\n attributes_payload_devices = {}\n for key, value in grouped_attributes.items():\n if value:\n attributes_payload_devices[key] = value\n #logger.info(payload_devices)\n #logger.info(attributes_payload_devices)\n #logger.info(payload)\n #logger.info(attributes)\n # Send data belonging to Gateway\n for chunk in chunk_payload(payload=payload):\n publish(__topic__, json.dumps(chunk), __qos__)\n time.sleep(2)\n\n attributes_payload[\"latestReportTime\"] = (\n round(dt.timestamp(dt.now())/60)*60)*1000\n for chunk in chunk_payload(payload=attributes_payload):\n publish(\"v1/devices/me/attributes\", json.dumps(chunk), __qos__)\n time.sleep(2)\n\n # Send gateway devices data\n for chunk in chunk_payload_devices(payload=mergedPayload):\n publish(\"v1/gateway/telemetry\", json.dumps(chunk), __qos__)\n time.sleep(2)\n\n for chunk in chunk_payload_devices(payload=attributes_payload_devices, is_attributes_payload=True):\n publish(\"v1/gateway/attributes\",\n json.dumps(attributes_payload_devices), __qos__)\n time.sleep(2)\n \n\ndef totalizeDay(lifetime, max_retries=3, retry_delay=2, file_path=\"/var/user/files/totalizers.json\"):\n \"\"\"\n Update and save daily totalizers based on the lifetime value.\n\n :param lifetime: The current lifetime total.\n :param max_retries: Maximum number of save attempts.\n :param retry_delay: Delay in seconds between retries.\n :return: A tuple containing the calculated value and a boolean indicating if a reset occurred, or (None, False) if save fails.\n \"\"\"\n totalizers = getTotalizers(file_path=file_path)\n now = dt.fromtimestamp(round(dt.timestamp(dt.now())/600)*600)\n reset = False\n value = lifetime - totalizers[\"dayHolding\"]\n\n if not int(now.strftime(\"%d\")) == int(totalizers[\"day\"]):\n totalizers[\"dayHolding\"] = lifetime\n totalizers[\"day\"] = int(now.strftime(\"%d\"))\n\n for attempt in range(max_retries):\n try:\n saveTotalizers(totalizers, file_path=file_path)\n reset = True\n return (value, reset)\n except Exception as e:\n logger.error(f\"Attempt {attempt + 1} failed to save totalizers: {e}\")\n if attempt < max_retries - 1:\n time.sleep(retry_delay)\n else:\n logger.error(\"All attempts to save totalizers failed.\")\n return (None, False)\n\n return (value, reset)\n\ndef totalizeWeek(lifetime, max_retries=3, retry_delay=2, file_path=\"/var/user/files/totalizers.json\"):\n \"\"\"\n Update and save weekly totalizers based on the lifetime value.\n\n :param lifetime: The current lifetime total.\n :param max_retries: Maximum number of save attempts.\n :param retry_delay: Delay in seconds between retries.\n :return: A tuple containing the calculated value and a boolean indicating if a reset occurred, or (None, False) if save fails.\n \"\"\"\n totalizers = getTotalizers(file_path=file_path)\n now = dt.fromtimestamp(round(dt.timestamp(dt.now())/600)*600)\n reset = False\n value = lifetime - totalizers[\"weekHolding\"]\n\n if (not now.strftime(\"%U\") == totalizers[\"week\"] and now.strftime(\"%a\") == \"Sun\") or totalizers[\"week\"] == 0:\n totalizers[\"weekHolding\"] = lifetime\n totalizers[\"week\"] = now.strftime(\"%U\")\n\n for attempt in range(max_retries):\n try:\n saveTotalizers(totalizers, file_path=file_path)\n reset = True\n return (value, reset)\n except Exception as e:\n logger.error(f\"Attempt {attempt + 1} failed to save totalizers: {e}\")\n if attempt < max_retries - 1:\n time.sleep(retry_delay)\n else:\n logger.error(\"All attempts to save totalizers failed.\")\n return (None, False)\n return (value, reset)\n\ndef totalizeMonth(lifetime, max_retries=3, retry_delay=2, file_path=\"/var/user/files/totalizers.json\"):\n \"\"\"\n Update and save monthly totalizers based on the lifetime value.\n\n :param lifetime: The current lifetime total.\n :param max_retries: Maximum number of save attempts.\n :param retry_delay: Delay in seconds between retries.\n :return: A tuple containing the calculated value and a boolean indicating if a reset occurred, or (None, False) if save fails.\n \"\"\"\n totalizers = getTotalizers(file_path=file_path)\n now = dt.fromtimestamp(round(dt.timestamp(dt.now())/600)*600)\n reset = False\n value = lifetime - totalizers[\"monthHolding\"]\n\n if not int(now.strftime(\"%m\")) == int(totalizers[\"month\"]):\n totalizers[\"monthHolding\"] = lifetime\n totalizers[\"month\"] = now.strftime(\"%m\")\n\n for attempt in range(max_retries):\n try:\n saveTotalizers(totalizers, file_path=file_path)\n reset = True\n return (value, reset)\n except Exception as e:\n logger.error(f\"Attempt {attempt + 1} failed to save totalizers: {e}\")\n if attempt < max_retries - 1:\n time.sleep(retry_delay)\n else:\n logger.error(\"All attempts to save totalizers failed.\")\n return (None, False)\n\n return (value,reset)\n\ndef totalizeYear(lifetime, max_retries=3, retry_delay=2, file_path=\"/var/user/files/totalizers.json\"):\n \"\"\"\n Update and save yearly totalizers based on the lifetime value.\n\n :param lifetime: The current lifetime total.\n :param max_retries: Maximum number of save attempts.\n :param retry_delay: Delay in seconds between retries.\n :return: A tuple containing the calculated value and a boolean indicating if a reset occurred, or (None, False) if save fails.\n \"\"\"\n totalizers = getTotalizers(file_path=file_path)\n now = dt.fromtimestamp(round(dt.timestamp(dt.now())/600)*600)\n reset = False\n value = lifetime - totalizers[\"yearHolding\"]\n\n if not int(now.strftime(\"%Y\")) == int(totalizers[\"year\"]):\n totalizers[\"yearHolding\"] = lifetime\n totalizers[\"year\"] = now.strftime(\"%Y\")\n \n for attempt in range(max_retries):\n try:\n saveTotalizers(totalizers, file_path=file_path)\n reset = True\n return (value, reset)\n except Exception as e:\n logger.error(f\"Attempt {attempt + 1} failed to save totalizers: {e}\")\n if attempt < max_retries - 1:\n time.sleep(retry_delay)\n else:\n logger.error(\"All attempts to save totalizers failed.\")\n return (None, False)", + "name": "sendDataFast", + "trigger": "measure_event", + "topic": "v1/devices/me/telemetry", + "cloudName": "default", + "groups": [ + "fastReport" + ], + "msgType": 0 + }, + { + "qos": 1, + "funcName": "sendAlarmGateway", + "script": "# Enter your python code.\nimport json, time\nfrom common.Logger import logger\nfrom quickfaas.remotebus import publish\nfrom quickfaas.global_dict import get as get_params\n\ndef convertDStoJSON(ds):\n j = dict()\n for x in ds:\n j[x[\"key\"]] = x[\"value\"]\n return j \n\ndef controlName(name):\n logger.debug(name)\n params = convertDStoJSON(get_params())\n logger.debug(params)\n nameMap = {\n \"overflow_pump\": f\"{params['overflow_pump']}\",\n \"leak_detection\": f\"{params['leak_detection']}\"\n }\n return nameMap.get(name, \"Gateway\")\n \ndef sendAlarmGateway(message, wizard_api):\n logger.info(message)\n for measure, data in message[\"values\"].items():\n ctrlName = controlName(data[\"ctrlName\"])\n payload = {ctrlName: [{\"ts\": message[\"timestamp\"]*1000, \"values\": {data[\"measureName\"]: data[\"value\"]}}]}\n publish(__topic__, json.dumps(payload), __qos__, cloud_name=\"default\")", + "name": "sendAlarmGateway", + "trigger": "warning_event", + "topic": "v1/gateway/telemetry", + "cloudName": "default", + "alarms": [ + "gateway" + ], + "msgType": 0 + } + ], + "downloadFuncs": [ + { + "name": "receiveAttributes", + "topic": "v1/devices/me/attributes", + "qos": 1, + "funcName": "receiveAttribute", + "payload_type": "Plaintext", + "script": "import json, time\nfrom datetime import datetime as dt\nfrom quickfaas.measure import recall, write\nfrom quickfaas.remotebus import publish\nfrom common.Logger import logger\nfrom quickfaas.global_dict import get as get_params\n\ndef convertDStoJSON(ds):\n j = dict()\n for x in ds:\n j[x[\"key\"]] = x[\"value\"]\n return j\n\ndef chunk_payload(payload, chunk_size=20):\n if \"values\" in payload:\n # Original format: {\"ts\": ..., \"values\": {...}}\n chunked_values = list(payload[\"values\"].items())\n for i in range(0, len(chunked_values), chunk_size):\n yield {\n \"ts\": payload[\"ts\"],\n \"values\": dict(chunked_values[i:i+chunk_size])\n }\n else:\n # New format: {\"key1\": \"value1\", \"key2\": \"value2\"}\n chunked_keys = list(payload.keys())\n for i in range(0, len(chunked_keys), chunk_size):\n yield {k: payload[k] for k in chunked_keys[i:i+chunk_size]}\n\ndef chunk_payload_gateway(payload, chunk_size=20, is_attributes_payload=False):\n if is_attributes_payload:\n # For attributes payload, chunk the controllers\n controllers = list(payload.items())\n for i in range(0, len(controllers), chunk_size):\n yield dict(controllers[i:i + chunk_size])\n else:\n # For data payload, chunk the values within each controller\n for controller, data in payload.items():\n for entry in data:\n ts = entry['ts']\n values = entry['values']\n chunked_values = list(values.items())\n for i in range(0, len(chunked_values), chunk_size):\n yield {\n controller: [{\n \"ts\": ts,\n \"values\": dict(chunked_values[i:i + chunk_size])\n }]\n }\n\n\ndef controlName(name):\n logger.debug(name)\n params = convertDStoJSON(get_params())\n logger.debug(params)\n nameMap = {\n \"overflow_pump\": f\"{params['overflow_pump']}\"\n }\n return nameMap.get(name, \"Gateway\")\n\n\n# Filter payloads based on device_filter\ndef filter_payload(payload, device_filter):\n if not device_filter: # If filter is empty, include all devices\n return payload\n return {key: value for key, value in payload.items() if key in device_filter}\n\ndef sync(device_filter=[]):\n #get new values and send\n now = round(dt.timestamp(dt.now()))*1000\n topic = \"v1/gateway/telemetry\"\n try:\n data = recall()#json.loads(recall().decode(\"utf-8\"))\n except Exception as e:\n logger.error(e)\n logger.debug(data)\n logger.info(\"SYNCING\")\n grouped_data = {}\n grouped_attributes = {}\n payload = {\"ts\": now, \"values\":{}}\n attributes_payload = {}\n try:\n for controller in data:\n for measure in controller[\"measures\"]:\n ctrlName = controlName(measure[\"ctrlName\"])\n if ctrlName == \"Gateway\":\n #send to gateway with v1/devices/me/telemetry\n if measure[\"health\"] == 1:\n if \"_spt\" in measure[\"name\"]:\n attributes_payload[measure[\"name\"]] = measure[\"value\"]\n else:\n payload[\"values\"][measure[\"name\"]] = measure[\"value\"]\n else:\n name = \"_\".join(measure['name'].split(\"_\")[2:])\n value = measure['value']\n health = measure['health']\n #Add controller for telemetry if it doesn't exist\n if ctrlName not in grouped_data:\n grouped_data[ctrlName] = {}\n #Add controller for attributes if it doesn't exist\n if ctrlName not in grouped_attributes:\n grouped_attributes[ctrlName] = {}\n grouped_attributes[ctrlName][\"latestReportTime\"] = now\n #Add data to temp payload if datapoint health is good\n if health:\n if \"_spt\" in name:\n grouped_attributes[ctrlName][name] = value\n else:\n grouped_data[ctrlName][name] = value\n except Exception as e:\n logger.error(e)\n try:\n # Transform the grouped data to desired structure\n payload_gateway = {}\n\n for key, value in grouped_data.items():\n if value:\n payload_gateway[key] = [{\"ts\": now ,\"values\": value}]\n\n attributes_payload_gateway = {}\n for key, value in grouped_attributes.items():\n if value:\n attributes_payload_gateway[key] = value\n\n # Apply the filter\n filtered_payload_gateway = filter_payload(payload_gateway, device_filter)\n filtered_attributes_payload_gateway = filter_payload(attributes_payload_gateway, device_filter)\n\n #Send data belonging to Gateway\n for chunk in chunk_payload(payload=payload):\n publish(\"v1/devices/me/telemetry\", json.dumps(chunk), qos=1, cloud_name=\"default\")\n time.sleep(2)\n \n attributes_payload[\"latestReportTime\"] = (round(dt.timestamp(dt.now())/600)*600)*1000\n for chunk in chunk_payload(payload=attributes_payload):\n publish(\"v1/devices/me/attributes\", json.dumps(chunk), qos=1, cloud_name=\"default\")\n time.sleep(2)\n \n #Send gateway devices data\n for chunk in chunk_payload_gateway(payload=filtered_payload_gateway):\n publish(\"v1/gateway/telemetry\", json.dumps(chunk), qos=1, cloud_name=\"default\")\n time.sleep(2)\n \n for chunk in chunk_payload_gateway(payload=filtered_attributes_payload_gateway, is_attributes_payload=True):\n publish(\"v1/gateway/attributes\", json.dumps(chunk), qos=1, cloud_name=\"default\")\n time.sleep(2)\n \n except Exception as e:\n logger.error(e)\n\ndef writeplctag(value):\n #value in the form {\"measurement\": , \"value\": }\n try:\n #value = json.loads(value.replace(\"'\",'\"'))\n logger.info(value)\n #payload format: [{\"name\": \"advvfdipp\", \"measures\": [{\"name\": \"manualfrequencysetpoint\", \"value\": 49}]}]\n message = [{\"name\": \"plcpond\", \"measures\":[{\"name\":value[\"measurement\"], \"value\": value[\"value\"]}]}]\n resp = write(message) \n logger.info(\"RETURN FROM WRITE: {}\".format(resp))\n return True\n except Exception as e:\n logger.error(e)\n return False\n \ndef receiveAttribute(topic, payload):\n try:\n logger.debug(topic)\n logger.info(json.loads(payload))\n p = json.loads(payload)\n \n for key, value in p.items():\n try:\n result = writeplctag({\"measurement\": key, \"value\": value})\n logger.debug(result)\n except Exception as e:\n logger.error(e)\n #logger.debug(command)\n time.sleep(5)\n try:\n sync(p[\"device\"])\n except Exception as e:\n logger.error(f\"Could not sync: {e}\")\n \n except Exception as e:\n logger.debug(e)\n", + "msgType": 0, + "cloudName": "default", + "trigger": "command_event" + }, + { + "name": "receiveAttributeGateway", + "topic": "v1/gateway/attributes", + "qos": 1, + "funcName": "receiveAttribute", + "payload_type": "Plaintext", + "script": "import json, time\nfrom datetime import datetime as dt\nfrom quickfaas.measure import recall, write\nfrom quickfaas.remotebus import publish\nfrom common.Logger import logger\nfrom quickfaas.global_dict import get as get_params\n\ndef convertDStoJSON(ds):\n j = dict()\n for x in ds:\n j[x[\"key\"]] = x[\"value\"]\n return j\n\ndef formatPLCPayload(device, key, value):\n params = convertDStoJSON(get_params())\n nameMap = {\n f\"{params['overflow_pump']}\": \"overflow_pump\",\n f\"{params['leak_detection']}\": \"leak_detection\"\n }\n measure = key\n device = nameMap.get(device, \"\")\n output = {\"measurement\": measure, \"value\": value, \"device\": device}\n return output\n\n\ndef chunk_payload(payload, chunk_size=20):\n if \"values\" in payload:\n # Original format: {\"ts\": ..., \"values\": {...}}\n chunked_values = list(payload[\"values\"].items())\n for i in range(0, len(chunked_values), chunk_size):\n yield {\n \"ts\": payload[\"ts\"],\n \"values\": dict(chunked_values[i:i+chunk_size])\n }\n else:\n # New format: {\"key1\": \"value1\", \"key2\": \"value2\"}\n chunked_keys = list(payload.keys())\n for i in range(0, len(chunked_keys), chunk_size):\n yield {k: payload[k] for k in chunked_keys[i:i+chunk_size]}\n\ndef chunk_payload_gateway(payload, chunk_size=20, is_attributes_payload=False):\n if is_attributes_payload:\n # For attributes payload, chunk the controllers\n controllers = list(payload.items())\n for i in range(0, len(controllers), chunk_size):\n yield dict(controllers[i:i + chunk_size])\n else:\n # For data payload, chunk the values within each controller\n for controller, data in payload.items():\n for entry in data:\n ts = entry['ts']\n values = entry['values']\n chunked_values = list(values.items())\n for i in range(0, len(chunked_values), chunk_size):\n yield {\n controller: [{\n \"ts\": ts,\n \"values\": dict(chunked_values[i:i + chunk_size])\n }]\n }\n\n\ndef controlName(name):\n logger.debug(name)\n params = convertDStoJSON(get_params())\n logger.debug(params)\n nameMap = {\n \"overflow_pump\": f\"{params['overflow_pump']}\",\n \"leak_detection\": f\"{params['leak_detection']}\"\n }\n return nameMap.get(name, \"Gateway\")\n\n# Filter payloads based on device_filter\ndef filter_payload(payload, device_filter):\n if not device_filter: # If filter is empty, include all devices\n return payload\n return {key: value for key, value in payload.items() if key in device_filter}\n\n\ndef sync(device_filter=[]):\n #get new values and send\n now = round(dt.timestamp(dt.now()))*1000\n topic = \"v1/gateway/telemetry\"\n try:\n data = recall()#json.loads(recall().decode(\"utf-8\"))\n except Exception as e:\n logger.error(e)\n logger.debug(data)\n logger.info(\"SYNCING\")\n grouped_data = {}\n grouped_attributes = {}\n payload = {\"ts\": now, \"values\":{}}\n attributes_payload = {}\n try:\n for controller in data:\n for measure in controller[\"measures\"]:\n ctrlName = controlName(measure[\"name\"])\n if ctrlName == \"Gateway\":\n #send to gateway with v1/devices/me/telemetry\n if measure[\"health\"] == 1:\n if \"_spt\" in measure[\"name\"]:\n attributes_payload[measure[\"name\"]] = measure[\"value\"]\n else:\n payload[\"values\"][measure[\"name\"]] = measure[\"value\"]\n else:\n name = \"_\".join(measure['name'].split(\"_\")[2:])\n value = measure['value']\n health = measure['health']\n #Add controller for telemetry if it doesn't exist\n if ctrlName not in grouped_data:\n grouped_data[ctrlName] = {}\n #Add controller for attributes if it doesn't exist\n if ctrlName not in grouped_attributes:\n grouped_attributes[ctrlName] = {}\n grouped_attributes[ctrlName][\"latestReportTime\"] = now\n #Add data to temp payload if datapoint health is good\n if health:\n if \"_spt\" in name:\n grouped_attributes[ctrlName][name] = value\n else:\n grouped_data[ctrlName][name] = value\n except Exception as e:\n logger.error(e)\n try:\n # Transform the grouped data to desired structure\n payload_gateway = {}\n\n for key, value in grouped_data.items():\n if value:\n payload_gateway[key] = [{\"ts\": now ,\"values\": value}]\n\n attributes_payload_gateway = {}\n for key, value in grouped_attributes.items():\n if value:\n attributes_payload_gateway[key] = value\n\n # Apply the filter\n filtered_payload_gateway = filter_payload(payload_gateway, device_filter)\n filtered_attributes_payload_gateway = filter_payload(attributes_payload_gateway, device_filter)\n\n #Send data belonging to Gateway\n for chunk in chunk_payload(payload=payload):\n publish(\"v1/devices/me/telemetry\", json.dumps(chunk), qos=1, cloud_name=\"default\")\n time.sleep(2)\n \n attributes_payload[\"latestReportTime\"] = (round(dt.timestamp(dt.now())/600)*600)*1000\n for chunk in chunk_payload(payload=attributes_payload):\n publish(\"v1/devices/me/attributes\", json.dumps(chunk), qos=1, cloud_name=\"default\")\n time.sleep(2)\n \n #Send gateway devices data\n for chunk in chunk_payload_gateway(payload=filtered_payload_gateway):\n publish(\"v1/gateway/telemetry\", json.dumps(chunk), qos=1, cloud_name=\"default\")\n time.sleep(2)\n \n for chunk in chunk_payload_gateway(payload=filtered_attributes_payload_gateway, is_attributes_payload=True):\n publish(\"v1/gateway/attributes\", json.dumps(chunk), qos=1, cloud_name=\"default\")\n time.sleep(2)\n \n except Exception as e:\n logger.error(e)\n\n\n\ndef writeplctag(value):\n #value in the form {\"measurement\": , \"value\": }\n try:\n logger.info(value)\n #payload format: [{\"name\": \"advvfdipp\", \"measures\": [{\"name\": \"manualfrequencysetpoint\", \"value\": 49}]}]\n message = [{\"name\": value[\"device\"], \"measures\": [{\"name\": value[\"measurement\"], \"value\": value[\"value\"]}]}]\n logger.info(message)\n resp = write(message) \n logger.debug(\"RETURN FROM WRITE: {}\".format(resp))\n return True\n except Exception as e:\n logger.error(e)\n return False\n\n\ndef receiveAttribute(topic, payload):\n try:\n logger.debug(topic)\n logger.info(json.loads(payload))\n p = json.loads(payload)\n device = p[\"device\"]\n for key, value in p[\"data\"].items():\n try:\n if key == 'pond_1_lo_spt': \n measure = formatPLCPayload(device, 'pond_1_lo_clr_spt', value + 0.5)\n result = writeplctag(measure)\n elif key == 'pond_2_lo_spt':\n measure = formatPLCPayload(device, 'pond_2_lo_clr_spt', value + 0.5)\n result = writeplctag(measure)\n measure = formatPLCPayload(device, key, value)\n result = writeplctag(measure)\n logger.debug(result)\n except Exception as e:\n logger.error(e)\n #logger.debug(command)\n time.sleep(5)\n try:\n sync(p[\"device\"])\n except Exception as e:\n logger.error(f\"Could not sync: {e}\")\n except Exception as e:\n logger.debug(e)\n \n", + "msgType": 0, + "cloudName": "default", + "trigger": "command_event" + }, + { + "name": "receiveGatewayCommand", + "topic": "v1/gateway/rpc", + "qos": 1, + "funcName": "receiveCommand", + "payload_type": "Plaintext", + "script": "import json, time\nfrom datetime import datetime as dt\nfrom quickfaas.measure import recall, write\nfrom quickfaas.remotebus import publish\nfrom common.Logger import logger\nfrom quickfaas.global_dict import get as get_params\n\ndef convertDStoJSON(ds):\n j = dict()\n for x in ds:\n j[x[\"key\"]] = x[\"value\"]\n return j\n\n\ndef formatPLCPayload(device, key, value):\n params = convertDStoJSON(get_params())\n nameMap = {\n f\"{params['overflow_pump']}\": \"overflow_pump\"\n }\n measure = key\n device = nameMap.get(device, \"\")\n output = {\"measurement\": measure, \"value\": value, \"device\": device}\n return output\n\n\ndef chunk_payload(payload, chunk_size=20):\n if \"values\" in payload:\n # Original format: {\"ts\": ..., \"values\": {...}}\n chunked_values = list(payload[\"values\"].items())\n for i in range(0, len(chunked_values), chunk_size):\n yield {\n \"ts\": payload[\"ts\"],\n \"values\": dict(chunked_values[i:i+chunk_size])\n }\n else:\n # New format: {\"key1\": \"value1\", \"key2\": \"value2\"}\n chunked_keys = list(payload.keys())\n for i in range(0, len(chunked_keys), chunk_size):\n yield {k: payload[k] for k in chunked_keys[i:i+chunk_size]}\n\ndef chunk_payload_gateway(payload, chunk_size=20, is_attributes_payload=False):\n if is_attributes_payload:\n # For attributes payload, chunk the controllers\n controllers = list(payload.items())\n for i in range(0, len(controllers), chunk_size):\n yield dict(controllers[i:i + chunk_size])\n else:\n # For data payload, chunk the values within each controller\n for controller, data in payload.items():\n for entry in data:\n ts = entry['ts']\n values = entry['values']\n chunked_values = list(values.items())\n for i in range(0, len(chunked_values), chunk_size):\n yield {\n controller: [{\n \"ts\": ts,\n \"values\": dict(chunked_values[i:i + chunk_size])\n }]\n }\n\n\ndef controlName(name):\n logger.debug(name)\n params = convertDStoJSON(get_params())\n logger.debug(params)\n nameMap = {\n \"overflow_pump\": f\"{params['overflow_pump']}\"\n }\n return nameMap.get(name, \"Gateway\")\n\n# Filter payloads based on device_filter\ndef filter_payload(payload, device_filter):\n if not device_filter: # If filter is empty, include all devices\n return payload\n return {key: value for key, value in payload.items() if key in device_filter}\n\n\ndef sync(device_filter=[]):\n #get new values and send\n now = round(dt.timestamp(dt.now()))*1000\n topic = \"v1/gateway/telemetry\"\n try:\n data = recall()#json.loads(recall().decode(\"utf-8\"))\n except Exception as e:\n logger.error(e)\n logger.debug(data)\n logger.info(\"SYNCING\")\n grouped_data = {}\n grouped_attributes = {}\n payload = {\"ts\": now, \"values\":{}}\n attributes_payload = {}\n try:\n for controller in data:\n for measure in controller[\"measures\"]:\n ctrlName = controlName(measure[\"name\"])\n if ctrlName == \"Gateway\":\n #send to gateway with v1/devices/me/telemetry\n if measure[\"health\"] == 1:\n if \"_spt\" in measure[\"name\"]:\n attributes_payload[measure[\"name\"]] = measure[\"value\"]\n else:\n payload[\"values\"][measure[\"name\"]] = measure[\"value\"]\n else:\n name = \"_\".join(measure['name'].split(\"_\")[2:])\n value = measure['value']\n health = measure['health']\n #Add controller for telemetry if it doesn't exist\n if ctrlName not in grouped_data:\n grouped_data[ctrlName] = {}\n #Add controller for attributes if it doesn't exist\n if ctrlName not in grouped_attributes:\n grouped_attributes[ctrlName] = {}\n grouped_attributes[ctrlName][\"latestReportTime\"] = now\n #Add data to temp payload if datapoint health is good\n if health:\n if \"_spt\" in name:\n grouped_attributes[ctrlName][name] = value\n else:\n grouped_data[ctrlName][name] = value\n except Exception as e:\n logger.error(e)\n try:\n # Transform the grouped data to desired structure\n payload_gateway = {}\n\n for key, value in grouped_data.items():\n if value:\n payload_gateway[key] = [{\"ts\": now ,\"values\": value}]\n\n attributes_payload_gateway = {}\n for key, value in grouped_attributes.items():\n if value:\n attributes_payload_gateway[key] = value\n\n # Apply the filter\n filtered_payload_gateway = filter_payload(payload_gateway, device_filter)\n filtered_attributes_payload_gateway = filter_payload(attributes_payload_gateway, device_filter)\n\n #Send data belonging to Gateway\n for chunk in chunk_payload(payload=payload):\n publish(\"v1/devices/me/telemetry\", json.dumps(chunk), qos=1, cloud_name=\"default\")\n time.sleep(2)\n \n attributes_payload[\"latestReportTime\"] = (round(dt.timestamp(dt.now())/600)*600)*1000\n for chunk in chunk_payload(payload=attributes_payload):\n publish(\"v1/devices/me/attributes\", json.dumps(chunk), qos=1, cloud_name=\"default\")\n time.sleep(2)\n \n #Send gateway devices data\n for chunk in chunk_payload_gateway(payload=filtered_payload_gateway):\n publish(\"v1/gateway/telemetry\", json.dumps(chunk), qos=1, cloud_name=\"default\")\n time.sleep(2)\n \n for chunk in chunk_payload_gateway(payload=filtered_attributes_payload_gateway, is_attributes_payload=True):\n publish(\"v1/gateway/attributes\", json.dumps(chunk), qos=1, cloud_name=\"default\")\n time.sleep(2)\n \n except Exception as e:\n logger.error(e)\n\n\ndef writeplctag(value):\n # value in the form {\"measurement\": , \"value\": }\n try:\n logger.debug(value)\n # payload format: [{\"name\": \"advvfdipp\", \"measures\": [{\"name\": \"manualfrequencysetpoint\", \"value\": 49}]}]\n message = [{\"name\": value[\"device\"], \"measures\": [\n {\"name\": value[\"measurement\"], \"value\": value[\"value\"]}]}]\n resp = write(message)\n logger.debug(\"RETURN FROM WRITE: {}\".format(resp))\n return True\n except Exception as e:\n logger.error(e)\n return False\n\n\ndef receiveCommand(topic, payload):\n try:\n logger.debug(topic)\n logger.info(json.loads(payload))\n p = json.loads(payload)\n #logger.info(p)\n command = p[\"data\"][\"method\"]\n commands = {\n \"sync\": sync,\n \"writeplctag\": writeplctag,\n } \n if command == \"setPLCTag\":\n try:\n params = formatPLCPayload(p[\"device\"], p[\"data\"][\"params\"][\"measurement\"], p[\"data\"][\"params\"][\"value\"])\n #logger.info(params)\n result = commands[\"writeplctag\"](params)\n logger.debug(result)\n except Exception as e:\n logger.error(e)\n elif command == \"startWW\":\n try:\n params = formatPLCPayload({\"device\": p[\"device\"], \"data\": {\"params\": {\"measurement\": \"auto_cmd\", \"value\": 0}}})\n result = commands[\"writeplctag\"](params)\n time.sleep(1)\n params = formatPLCPayload({\"device\": p[\"device\"], \"data\": {\"params\": {\"measurement\": \"manual_run_cmd\", \"value\": 1}}})\n result = commands[\"writeplctag\"](params)\n except Exception as e:\n logger.error(f\"Error in startWW: {e}\")\n elif command == \"manualAutoSwitch\":\n try:\n if p[\"data\"][\"params\"][\"direction\"] == \"manualToAuto\":\n params = formatPLCPayload({\"device\": p[\"device\"], \"data\": {\"params\": {\"measurement\": \"manual_run_cmd\", \"value\": 0}}})\n result = commands[\"writeplctag\"](params)\n time.sleep(1)\n params = formatPLCPayload({\"device\": p[\"device\"], \"data\": {\"params\": {\"measurement\": \"auto_cmd\", \"value\": 1}}})\n result = commands[\"writeplctag\"](params)\n elif p[\"data\"][\"params\"][\"direction\"] == \"autoToManual\":\n params = formatPLCPayload({\"device\": p[\"device\"], \"data\": {\"params\": {\"measurement\": \"auto_cmd\", \"value\": 0}}})\n result = commands[\"writeplctag\"](params)\n else:\n logger.error(f'Invalid input in manualAutoSwitch: {p[\"data\"][\"params\"][\"direction\"]}')\n except Exception as e:\n logger.error(f\"Error in manualToAuto: {e}\")\n ackPayload = {\"device\": p[\"device\"], \"id\": p[\"data\"][\"id\"], \"data\": {\"success\": True}}\n ack(ackPayload)\n time.sleep(5)\n try:\n sync(p[\"device\"])\n except Exception as e:\n logger.error(f\"Could not sync: {e}\")\n except Exception as e:\n logger.error(e)\n \n\ndef ack(message):\n publish(\"v1/gateway/rpc\", json.dumps(message), 1, cloud_name=\"default\")\n", + "msgType": 0, + "cloudName": "default", + "trigger": "command_event" + } + ] + }, + "labels": [ + { + "key": "SN", + "value": "GF5022311031664" + }, + { + "key": "MAC", + "value": "00:18:05:28:4a:40" + }, + { + "key": "overflow_pump", + "value": "BP Overflow Pump" + }, + { + "key": "leak_detection", + "value": "BP Leak Detection" + } + ], + "modbusSlave": { + "enable": 0, + "protocol": "Modbus-TCP", + "port": 502, + "slaveAddr": 1, + "int16Ord": "ab", + "int32Ord": "abcd", + "float32Ord": "abcd", + "maxConnection": 5, + "mapping_table": [] + }, + "modbusRTUSlave": { + "enable": 0, + "protocol": "Modbus-RTU", + "coms": "rs485", + "slaveAddr": 1, + "int16Ord": "ab", + "int32Ord": "abcd", + "float32Ord": "abcd", + "mapping_table": [] + }, + "iec104Server": { + "enable": 0, + "cotSize": 2, + "port": 2404, + "serverList": [ + { + "asduAddr": 1 + } + ], + "kValue": 12, + "wValue": 8, + "t0": 30, + "t1": 15, + "t2": 10, + "t3": 20, + "maximumLink": 5, + "timeSet": 1, + "byteOrder": "abcd", + "mapping_table": [] + }, + "iec101Server": { + "enable": 0, + "coms": "rs485", + "mode": "UnBalance", + "linkLen": 2, + "linkAddr": 1, + "asduLen": 2, + "ioaLen": 3, + "cotLen": 2, + "serverList": [ + { + "asduAddr": 1 + } + ], + "linkTimeOut": 2000, + "timeSet": 1, + "idleTimeOut": 10000, + "byteOrder": "abcd", + "mapping_table": { + "YX": [], + "YC": [], + "YK": [] + } + }, + "iec104Client": { + "enable": 0, + "connectType": 2, + "serverAddr": "ipower.inhandcloud.cn", + "serverPort": 2404, + "communicationCode": "", + "protocol": 1, + "asduAddr": 1, + "tls": 0, + "mapping_table": { + "YX": [], + "YC": [], + "YK": [] + } + }, + "opcuaServer": { + "enable": 0, + "port": 4840, + "maximumLink": 5, + "securityMode": 0, + "identifierType": "String", + "certificate": "None", + "privateKey": "None", + "pubsub": 0, + "mapping_table": [] + }, + "sl651Slave": { + "enable": 0, + "centerAaddr": 1, + "remoteAddr": "", + "addrCode": "", + "password": "", + "platform_list": [], + "mapping_table": [] + }, + "hj212Client": { + "enable": 0, + "platform_list": [], + "block_list": [], + "mapping_table": [] + }, + "southMetadata": {}, + "bindMetadata": { + "version": "", + "timestamp": "" + }, + "bindConfig": { + "enable": 0, + "bind": { + "modelId": "", + "modelName": "", + "srcId": "", + "srcName": "", + "devId": "", + "devName": "" + }, + "varGroups": [], + "variables": [], + "alerts": [] + }, + "templates": {}, + "version": "2.7.1" +} \ No newline at end of file