From 726b00ad9a2bd76045aa5ee8d1d234ece09ae6a3 Mon Sep 17 00:00:00 2001 From: Patrick McDonagh Date: Thu, 5 Jan 2017 17:20:44 -0600 Subject: [PATCH] Working on POC-108. Reading holding registers confirmed working --- Modbus Map.xlsx | Bin 0 -> 67446 bytes README.md | 6 ++ analog.json | 149 +++++++++++++++++++++++++++++++++ arraylist.json | 4 + digital.json | 31 +++++++ plc_to_mongo.py | 90 ++++++++++++++++++++ poc-to-modbus.py | 85 ------------------- poc_to_modbus.py | 209 +++++++++++++++++++++++++++++++++++++++++++++++ test.py | 79 ++++++++++++++++++ 9 files changed, 568 insertions(+), 85 deletions(-) create mode 100644 Modbus Map.xlsx create mode 100644 README.md create mode 100644 analog.json create mode 100644 arraylist.json create mode 100644 digital.json create mode 100644 plc_to_mongo.py delete mode 100644 poc-to-modbus.py create mode 100644 poc_to_modbus.py create mode 100644 test.py diff --git a/Modbus Map.xlsx b/Modbus Map.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..6b20116931317ba94d21428799cb98a29426076b GIT binary patch literal 67446 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 z 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 literal 0 HcmV?d00001 diff --git a/README.md b/README.md new file mode 100644 index 0000000..3fc29e0 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# Modbus Register Map + +## Coils + +Reg. # | Description | Access | +1 | diff --git a/analog.json b/analog.json new file mode 100644 index 0000000..1864a69 --- /dev/null +++ b/analog.json @@ -0,0 +1,149 @@ +[ + {"tag_name": "_Firmware_Rev", "register_type": "ir", "register_number": 1, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "_Program_Firmware", "register_type": "ir", "register_number": 6, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[1].Card_Type", "register_type": "ir", "register_number": 11, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[1].Downhole_AdjustedGrossStroke", "register_type": "ir", "register_number": 16, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[1].Downhole_FluidLoad", "register_type": "ir", "register_number": 21, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[1].Downhole_GrossStroke", "register_type": "ir", "register_number": 26, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[1].Downhole_Max_Load.Load", "register_type": "ir", "register_number": 31, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[1].Downhole_Max_Position.Position", "register_type": "ir", "register_number": 36, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[1].Downhole_Min_Load.Load", "register_type": "ir", "register_number": 41, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[1].Downhole_Min_Position.Position", "register_type": "ir", "register_number": 46, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[1].Downhole_NetStroke", "register_type": "ir", "register_number": 51, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[1].Fillage_Percent", "register_type": "ir", "register_number": 56, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[1].Fluid_Above_Pump", "register_type": "ir", "register_number": 61, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[1].Fluid_Moved", "register_type": "ir", "register_number": 66, "scale_type": 0, "scale_multiplier": 10000}, + {"tag_name": "Card_Past[1].ID", "register_type": "ir", "register_number": 71, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[1].Num_Points", "register_type": "ir", "register_number": 76, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[1].Polished_Rod_HP", "register_type": "ir", "register_number": 81, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[1].Pump_HP", "register_type": "ir", "register_number": 86, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[1].Pump_Intake_Pressure", "register_type": "ir", "register_number": 91, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[1].SPM", "register_type": "ir", "register_number": 96, "scale_type": 0, "scale_multiplier": 1000}, + {"tag_name": "Card_Past[1].Surface_Max.Load", "register_type": "ir", "register_number": 101, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[1].Surface_Max.Position", "register_type": "ir", "register_number": 106, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[1].Surface_Min.Load", "register_type": "ir", "register_number": 111, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[1].Surface_Min.Position", "register_type": "ir", "register_number": 116, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[1].Surface_StrokeLength", "register_type": "ir", "register_number": 121, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[1].Tubing_Movement", "register_type": "ir", "register_number": 126, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[2].Card_Type", "register_type": "ir", "register_number": 131, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[2].Downhole_AdjustedGrossStroke", "register_type": "ir", "register_number": 136, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[2].Downhole_FluidLoad", "register_type": "ir", "register_number": 141, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[2].Downhole_GrossStroke", "register_type": "ir", "register_number": 146, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[2].Downhole_Max_Load.Load", "register_type": "ir", "register_number": 151, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[2].Downhole_Max_Position.Position", "register_type": "ir", "register_number": 156, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[2].Downhole_Min_Load.Load", "register_type": "ir", "register_number": 161, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[2].Downhole_Min_Position.Position", "register_type": "ir", "register_number": 166, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[2].Downhole_NetStroke", "register_type": "ir", "register_number": 171, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[2].Fillage_Percent", "register_type": "ir", "register_number": 176, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[2].Fluid_Above_Pump", "register_type": "ir", "register_number": 181, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[2].Fluid_Moved", "register_type": "ir", "register_number": 186, "scale_type": 0, "scale_multiplier": 10000}, + {"tag_name": "Card_Past[2].ID", "register_type": "ir", "register_number": 191, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[2].Num_Points", "register_type": "ir", "register_number": 196, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[2].Polished_Rod_HP", "register_type": "ir", "register_number": 201, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[2].Pump_HP", "register_type": "ir", "register_number": 206, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[2].Pump_Intake_Pressure", "register_type": "ir", "register_number": 211, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[2].SPM", "register_type": "ir", "register_number": 216, "scale_type": 0, "scale_multiplier": 1000}, + {"tag_name": "Card_Past[2].Surface_Max.Load", "register_type": "ir", "register_number": 221, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[2].Surface_Max.Position", "register_type": "ir", "register_number": 226, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[2].Surface_Min.Load", "register_type": "ir", "register_number": 231, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[2].Surface_Min.Position", "register_type": "ir", "register_number": 236, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[2].Surface_StrokeLength", "register_type": "ir", "register_number": 241, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[2].Tubing_Movement", "register_type": "ir", "register_number": 246, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[3].Card_Type", "register_type": "ir", "register_number": 251, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[3].Downhole_AdjustedGrossStroke", "register_type": "ir", "register_number": 256, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[3].Downhole_FluidLoad", "register_type": "ir", "register_number": 261, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[3].Downhole_GrossStroke", "register_type": "ir", "register_number": 266, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[3].Downhole_Max_Load.Load", "register_type": "ir", "register_number": 271, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[3].Downhole_Max_Position.Position", "register_type": "ir", "register_number": 276, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[3].Downhole_Min_Load.Load", "register_type": "ir", "register_number": 281, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[3].Downhole_Min_Position.Position", "register_type": "ir", "register_number": 286, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[3].Downhole_NetStroke", "register_type": "ir", "register_number": 291, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[3].Fillage_Percent", "register_type": "ir", "register_number": 296, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[3].Fluid_Above_Pump", "register_type": "ir", "register_number": 301, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[3].Fluid_Moved", "register_type": "ir", "register_number": 306, "scale_type": 0, "scale_multiplier": 10000}, + {"tag_name": "Card_Past[3].ID", "register_type": "ir", "register_number": 311, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[3].Num_Points", "register_type": "ir", "register_number": 316, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[3].Polished_Rod_HP", "register_type": "ir", "register_number": 321, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[3].Pump_HP", "register_type": "ir", "register_number": 326, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[3].Pump_Intake_Pressure", "register_type": "ir", "register_number": 331, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[3].SPM", "register_type": "ir", "register_number": 336, "scale_type": 0, "scale_multiplier": 1000}, + {"tag_name": "Card_Past[3].Surface_Max.Load", "register_type": "ir", "register_number": 341, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[3].Surface_Max.Position", "register_type": "ir", "register_number": 346, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[3].Surface_Min.Load", "register_type": "ir", "register_number": 351, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[3].Surface_Min.Position", "register_type": "ir", "register_number": 356, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[3].Surface_StrokeLength", "register_type": "ir", "register_number": 361, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[3].Tubing_Movement", "register_type": "ir", "register_number": 366, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[4].Card_Type", "register_type": "ir", "register_number": 371, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[4].Downhole_AdjustedGrossStroke", "register_type": "ir", "register_number": 376, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[4].Downhole_FluidLoad", "register_type": "ir", "register_number": 381, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[4].Downhole_GrossStroke", "register_type": "ir", "register_number": 386, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[4].Downhole_Max_Load.Load", "register_type": "ir", "register_number": 391, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[4].Downhole_Max_Position.Position", "register_type": "ir", "register_number": 396, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[4].Downhole_Min_Load.Load", "register_type": "ir", "register_number": 401, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[4].Downhole_Min_Position.Position", "register_type": "ir", "register_number": 406, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[4].Downhole_NetStroke", "register_type": "ir", "register_number": 411, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[4].Fillage_Percent", "register_type": "ir", "register_number": 416, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[4].Fluid_Above_Pump", "register_type": "ir", "register_number": 421, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[4].Fluid_Moved", "register_type": "ir", "register_number": 426, "scale_type": 0, "scale_multiplier": 10000}, + {"tag_name": "Card_Past[4].ID", "register_type": "ir", "register_number": 431, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[4].Num_Points", "register_type": "ir", "register_number": 436, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[4].Polished_Rod_HP", "register_type": "ir", "register_number": 441, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[4].Pump_HP", "register_type": "ir", "register_number": 446, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[4].Pump_Intake_Pressure", "register_type": "ir", "register_number": 451, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[4].SPM", "register_type": "ir", "register_number": 456, "scale_type": 0, "scale_multiplier": 1000}, + {"tag_name": "Card_Past[4].Surface_Max.Load", "register_type": "ir", "register_number": 461, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[4].Surface_Max.Position", "register_type": "ir", "register_number": 466, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[4].Surface_Min.Load", "register_type": "ir", "register_number": 471, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Card_Past[4].Surface_Min.Position", "register_type": "ir", "register_number": 476, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[4].Surface_StrokeLength", "register_type": "ir", "register_number": 481, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Card_Past[4].Tubing_Movement", "register_type": "ir", "register_number": 486, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Drive_Speed_Reference_SP", "register_type": "ir", "register_number": 491, "scale_type": 0, "scale_multiplier": 10}, + {"tag_name": "Drive_Torque_Percent", "register_type": "ir", "register_number": 496, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "GAUGEOFF_Average_SPM", "register_type": "ir", "register_number": 501, "scale_type": 0, "scale_multiplier": 1000}, + {"tag_name": "GAUGEOFF_Downhole_GrossStroke", "register_type": "ir", "register_number": 506, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "GAUGEOFF_Downhole_NetStroke", "register_type": "ir", "register_number": 511, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "GAUGEOFF_Electricity_Cost", "register_type": "ir", "register_number": 516, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "GAUGEOFF_Fluid_Above_Pump", "register_type": "ir", "register_number": 521, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "GAUGEOFF_Inflow_Rate", "register_type": "ir", "register_number": 526, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "GAUGEOFF_kWh", "register_type": "ir", "register_number": 531, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "GAUGEOFF_kWh_Regen", "register_type": "ir", "register_number": 536, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "GAUGEOFF_Lifting_Cost", "register_type": "ir", "register_number": 541, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "GAUGEOFF_Max_Load", "register_type": "ir", "register_number": 546, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "GAUGEOFF_Min_Load", "register_type": "ir", "register_number": 551, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "GAUGEOFF_Percent_Run", "register_type": "ir", "register_number": 556, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "GAUGEOFF_Polished_Rod_HP", "register_type": "ir", "register_number": 561, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "GAUGEOFF_Pump_Intake_Pressure", "register_type": "ir", "register_number": 566, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "GAUGEOFF_Surface_StrokeLength", "register_type": "ir", "register_number": 571, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "GAUGEOFF_Tubing_Movement", "register_type": "ir", "register_number": 576, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Gearbox_Loading", "register_type": "ir", "register_number": 581, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Gearbox_Torque", "register_type": "ir", "register_number": 586, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Input_Analog_1_AIN.Val", "register_type": "ir", "register_number": 591, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Input_Analog_2_AIN.Val", "register_type": "ir", "register_number": 596, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Input_Analog_3_AIN.Val", "register_type": "ir", "register_number": 601, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Instantaneous_SPM", "register_type": "ir", "register_number": 606, "scale_type": 0, "scale_multiplier": 10}, + {"tag_name": "kWh_Today", "register_type": "ir", "register_number": 611, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Max_Theoretical_Fluid_Load", "register_type": "ir", "register_number": 616, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Motor_Torque", "register_type": "ir", "register_number": 621, "scale_type": 0, "scale_multiplier": 100}, + + {"tag_name": "_dt", "register_type": "hr", "register_number": 1, "scale_type": 0, "scale_multiplier": 1000}, + {"tag_name": "Casing_ID", "register_type": "hr", "register_number": 6, "scale_type": 0, "scale_multiplier": 1000}, + {"tag_name": "Drive_Start_Speed", "register_type": "hr", "register_number": 11, "scale_type": 0, "scale_multiplier": 10}, + {"tag_name": "Electricity_Cost", "register_type": "hr", "register_number": 16, "scale_type": 0, "scale_multiplier": 1000}, + {"tag_name": "Estimated_Tubing_Movement", "register_type": "hr", "register_number": 21, "scale_type": 0, "scale_multiplier": 100}, + {"tag_name": "Fluid_Gradient", "register_type": "hr", "register_number": 26, "scale_type": 0, "scale_multiplier": 1000}, + {"tag_name": "Friction", "register_type": "hr", "register_number": 31, "scale_type": 0, "scale_multiplier": 10}, + {"tag_name": "Gauge_Off_Time.Hour", "register_type": "hr", "register_number": 36, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Gauge_Off_Time.Min", "register_type": "hr", "register_number": 41, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Input_Analog_1_AIN.Cfg_PVEUMax", "register_type": "hr", "register_number": 46, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Input_Analog_1_AIN.Cfg_PVEUMin", "register_type": "hr", "register_number": 51, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Input_Analog_2_AIN.Cfg_PVEUMax", "register_type": "hr", "register_number": 56, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Input_Analog_2_AIN.Cfg_PVEUMin", "register_type": "hr", "register_number": 61, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Input_Analog_3_AIN.Cfg_PVEUMax", "register_type": "hr", "register_number": 66, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Input_Analog_3_AIN.Cfg_PVEUMin", "register_type": "hr", "register_number": 71, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Input_Inclinometer_AIN.Cfg_PVEUMax", "register_type": "hr", "register_number": 76, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Input_Inclinometer_AIN.Cfg_PVEUMin", "register_type": "hr", "register_number": 81, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Input_LoadCell_AIN.Cfg_PVEUMax", "register_type": "hr", "register_number": 86, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "Input_LoadCell_AIN.Cfg_PVEUMin", "register_type": "hr", "register_number": 91, "scale_type": 0, "scale_multiplier": 1}, + {"tag_name": "K_Factor", "register_type": "hr", "register_number": 96, "scale_type": 0, "scale_multiplier": 1000}, + {"tag_name": "Min_Fluid_Load_Shutdown_Setpoint", "register_type": "hr", "register_number": 101, "scale_type": 0, "scale_multiplier": 1} +] diff --git a/arraylist.json b/arraylist.json new file mode 100644 index 0000000..221a9a5 --- /dev/null +++ b/arraylist.json @@ -0,0 +1,4 @@ + +[ + +] diff --git a/digital.json b/digital.json new file mode 100644 index 0000000..3d1232d --- /dev/null +++ b/digital.json @@ -0,0 +1,31 @@ +[ + {"tag_name": "_TOC_ACCEPTED", "register_type": "co", "register_number": 1}, + {"tag_name": "Autotune_Next_Start", "register_type": "co", "register_number": 2}, + {"tag_name": "Gas_Interference_Override_Enabled", "register_type": "co", "register_number": 3}, + {"tag_name": "Gauge_Off_Manual", "register_type": "co", "register_number": 4}, + {"tag_name": "Initialize", "register_type": "co", "register_number": 5}, + {"tag_name": "Input_Analog_1.Channel", "register_type": "co", "register_number": 6}, + {"tag_name": "Input_Analog_1.Enabled", "register_type": "co", "register_number": 7}, + {"tag_name": "Input_Analog_1.Type", "register_type": "co", "register_number": 8}, + {"tag_name": "Input_Analog_2.Channel", "register_type": "co", "register_number": 9}, + {"tag_name": "Input_Analog_2.Enabled", "register_type": "co", "register_number": 10}, + {"tag_name": "Input_Analog_2.Type", "register_type": "co", "register_number": 11}, + {"tag_name": "Input_Analog_3.Channel", "register_type": "co", "register_number": 12}, + {"tag_name": "Input_Analog_3.Enabled", "register_type": "co", "register_number": 13}, + {"tag_name": "Input_Analog_3.Type", "register_type": "co", "register_number": 14}, + {"tag_name": "Input_Inclinometer_Calibrate", "register_type": "co", "register_number": 15}, + {"tag_name": "Input_Inclinometer.Channel", "register_type": "co", "register_number": 16}, + {"tag_name": "Input_Inclinometer.Enabled", "register_type": "co", "register_number": 17}, + {"tag_name": "Input_Inclinometer.Type", "register_type": "co", "register_number": 18}, + {"tag_name": "Input_LoadCell.Channel", "register_type": "co", "register_number": 19}, + {"tag_name": "Input_LoadCell.Enabled", "register_type": "co", "register_number": 20}, + {"tag_name": "Input_LoadCell.Type", "register_type": "co", "register_number": 21}, + {"tag_name": "Min_Fluid_Load_Shutdown_Enabled", "register_type": "co", "register_number": 22}, + {"tag_name": "Restart_After_Fault_Allowed", "register_type": "co", "register_number": 23}, + {"tag_name": "Update_Tapers", "register_type": "co", "register_number": 24}, + {"tag_name": "USE_WIRELESS_LOADCELL", "register_type": "co", "register_number": 25}, + {"tag_name": "Write_Mode_Data", "register_type": "co", "register_number": 26}, + {"tag_name": "Write_Setup_Data", "register_type": "co", "register_number": 27}, + {"tag_name": "Inclinometer_Calibrating", "register_type": "di", "register_number": 1}, + {"tag_name": "Inclinometer_Stale", "register_type": "di", "register_number": 2} +] diff --git a/plc_to_mongo.py b/plc_to_mongo.py new file mode 100644 index 0000000..8b5ad50 --- /dev/null +++ b/plc_to_mongo.py @@ -0,0 +1,90 @@ +import pymongo +import json +from time import sleep +from time import time as now +from pymongo import MongoClient +import pycomm_helper.utils as plc + +PLC_IP_ADDRESS = '10.20.4.7' + + +def main(): + client = MongoClient() + db = client.tag_data + + tag_vals = db.tag_vals + print("THERE ARE ALREADY {} VALUES IN tag_vals".format(tag_vals.count())) + with open('analog.json', 'rb') as analogfile: + analog_list = json.loads(analogfile.read()) + + with open('digital.json', 'rb') as digitalfile: + digital_list = json.loads(digitalfile.read()) + + with open('arraylist.json', 'rb') as arrayfile: + arr_list = [] # json.loads(arrayfile.read()) + + for entry in analog_list + digital_list + arr_list: + if tag_vals.find({'tag_name': entry['tag_name']}).count() < 1: + tag_vals.insert(entry) + print("NOW THERE ARE {} VALUES IN tag_vals".format(tag_vals.count())) + + while True: + for t in analog_list: + try: + plc_val = plc.readTag(PLC_IP_ADDRESS, t['tag_name']) + if plc_val: + t['tag_type'] = plc_val[1] + t['val_actual'] = plc_val[0] + t['timestamp'] = now() + if int(t['scale_type']) == 0: + t['val'] = int(plc_val[0] * t['scale_multiplier']) + else: + t['val'] = int(plc_val[0] / t['scale_multiplier']) + tag_vals.update({'tag_name': t['tag_name']}, t) + print("Updated: {}".format(tag_vals.find({'tag_name': t['tag_name']})[0])) + except Exception as e: + print("[ERROR] {} - {}".format(t['tag_name'], e)) + + for t in digital_list: + try: + plc_val = plc.readTag(PLC_IP_ADDRESS, t['tag_name']) + if plc_val: + t['tag_type'] = plc_val[1] + t['val'] = plc_val[0] + t['timestamp'] = now() + tag_vals.update({'tag_name': t['tag_name']}, t) + print("Updated: {}".format(tag_vals.find({'tag_name': t['tag_name']})[0])) + except Exception as e: + print("[ERROR] {} - {}".format(t['tag_name'], e)) + + for a in arr_list: + try: + plc_val = plc.readArray(PLC_IP_ADDRESS, str(a['tag_name']), int(a['length'])) + if plc_val: + a['val'] = plc_val + a['tag_type'] = 'ARRAY' + t['timestamp'] = now() + tag_vals.update({'tag_name': a['tag_name']}, a) + print("Updated: {}".format(tag_vals.find({'tag_name': a['tag_name']})[0])) + except Exception as e: + print("[ERROR] {} - {}".format(a['tag_name'], e)) + + sleep(5) + + +def purge(): + client = MongoClient() + db = client.tag_data + + tag_vals = db.tag_vals + tag_vals.delete_many({}) + print("THERE ARE {} VALUES IN tag_vals".format(tag_vals.count())) + +if __name__ == '__main__': + import sys + if len(sys.argv) > 1: + if sys.argv[1] == 'purge': + print("Purging...") + purge() + else: + main() diff --git a/poc-to-modbus.py b/poc-to-modbus.py deleted file mode 100644 index 46a35d7..0000000 --- a/poc-to-modbus.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python3 -''' -Pymodbus Server With Updating Thread --------------------------------------------------------------------------- - -This is an example of having a background thread updating the -context while the server is operating. This can also be done with -a python thread:: - - from threading import Thread - - thread = Thread(target=updating_writer, args=(context,)) - thread.start() -''' -# ---------------------------------------------------------------------------# -# import the modbus libraries we need -# ---------------------------------------------------------------------------# -from pymodbus.server.async import StartTcpServer -from pymodbus.device import ModbusDeviceIdentification -from pymodbus.datastore import ModbusSequentialDataBlock -from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext -# from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer - -# ---------------------------------------------------------------------------# -# import the twisted libraries we need -# ---------------------------------------------------------------------------# -from twisted.internet.task import LoopingCall - -# ---------------------------------------------------------------------------# -# configure the service logging -# ---------------------------------------------------------------------------# -import logging -logging.basicConfig() -log = logging.getLogger() -log.setLevel(logging.DEBUG) - - -# ---------------------------------------------------------------------------# -# define your callback process -# ---------------------------------------------------------------------------# -def updating_writer(a): - ''' A worker process that runs every so often and - updates live values of the context. It should be noted - that there is a race condition for the update. - - :param arguments: The input arguments to the call - ''' - log.debug("updating the context") - context = a[0] - register = 3 - slave_id = 0x00 - address = 0x10 - values = context[slave_id].getValues(register, address, count=5) - values = [v + 1 for v in values] - log.debug("new values: " + str(values)) - context[slave_id].setValues(register, address, values) - -# ---------------------------------------------------------------------------# -# initialize your data store -# ---------------------------------------------------------------------------# -store = ModbusSlaveContext( - di=ModbusSequentialDataBlock(0, [17]*100), - co=ModbusSequentialDataBlock(0, [17]*100), - hr=ModbusSequentialDataBlock(0, [17]*100), - ir=ModbusSequentialDataBlock(0, [17]*100)) -context = ModbusServerContext(slaves=store, single=True) - -# ---------------------------------------------------------------------------# -# initialize the server information -# ---------------------------------------------------------------------------# -identity = ModbusDeviceIdentification() -identity.VendorName = 'pymodbus' -identity.ProductCode = 'PM' -identity.VendorUrl = 'http://github.com/bashwork/pymodbus/' -identity.ProductName = 'pymodbus Server' -identity.ModelName = 'pymodbus Server' -identity.MajorMinorRevision = '1.0' - -# ---------------------------------------------------------------------------# -# run the server you want -# ---------------------------------------------------------------------------# -time = 5 # 5 seconds delay -loop = LoopingCall(f=updating_writer, a=(context,)) -loop.start(time, now=False) # initially delay by time -StartTcpServer(context, identity=identity, address=("localhost", 5020)) diff --git a/poc_to_modbus.py b/poc_to_modbus.py new file mode 100644 index 0000000..a8aff9e --- /dev/null +++ b/poc_to_modbus.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python +''' +Pymodbus Server With Callbacks +-------------------------------------------------------------------------- + +This is an example of adding callbacks to a running modbus server +when a value is written to it. In order for this to work, it needs +a device-mapping file. +''' +# ---------------------------------------------------------------------------# +# import the modbus libraries we need +# ---------------------------------------------------------------------------# +from pymodbus.server.async import StartTcpServer +from pymodbus.device import ModbusDeviceIdentification +from pymodbus.datastore import ModbusSparseDataBlock +from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext +from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer + +# ---------------------------------------------------------------------------# +# import the python libraries we need +# ---------------------------------------------------------------------------# +import pymongo +from pymongo import MongoClient +from pycomm_helper import utils as plc +from time import time as now +# ---------------------------------------------------------------------------# +# configure the service logging +# ---------------------------------------------------------------------------# +import logging +logging.basicConfig() +log = logging.getLogger() +log.setLevel(logging.DEBUG) + +PLC_IP_ADDRESS = '10.20.4.7' + + +def getTagsFromDB(): + client = MongoClient() + db = client.tag_data + tags = db.tag_vals + print("Found {} tags in the database".format(tags.count())) + di_tags_cur = tags.find({'register_type': 'di'}) + di_tags = list(di_tags_cur) + di_tags_num = di_tags_cur.count() + print("{} Digital Inputs".format(di_tags_num)) + + co_tags_cur = tags.find({'register_type': 'co'}) + co_tags = list(co_tags_cur) + co_tags_num = co_tags_cur.count() + print("{} Coils".format(co_tags_num)) + + ir_tags_cur = tags.find({'register_type': 'ir'}) + ir_tags = list(ir_tags_cur) + ir_tags_num = ir_tags_cur.count() + print("{} Input Registers".format(ir_tags_num)) + + hr_tags_cur = tags.find({'register_type': 'hr'}) + hr_tags = list(hr_tags_cur) + hr_tags_num = hr_tags_cur.count() + print("{} Holding Registers".format(hr_tags_num)) + + return {'di': di_tags, 'co': co_tags, 'ir': ir_tags, 'hr': hr_tags} + + +# ---------------------------------------------------------------------------# +# create your custom data block with callbacks +# ---------------------------------------------------------------------------# +class DigitalTagDataBlock(ModbusSparseDataBlock): + ''' A datablock that stores the new value in memory + and passes the operation to a message queue for further + processing. + ''' + + def __init__(self, register_type, tag_list): + ''' + ''' + values = {} + self.register_type = register_type + + for t in tag_list: + try: + values[t['register_number']] = t['val'] + except KeyError: + values[t['register_number']] = 911 + # print("Initialized DigitalTagDataBlock for {} with values {}".format(self.register_type, values)) + super(DigitalTagDataBlock, self).__init__(values) + + def getValues(self, address, count=1): + client = MongoClient() + db = client.tag_data + tags = db.tag_vals + + if count > 1: + for i in range(address, address + count): + tag_found = tags.find_one({'register_number': i, 'register_type': self.register_type}) + print("{} = {}".format(tag_found['tag_name'], tag_found['val'])) + super(DigitalTagDataBlock, self).setValues(address, tag_found['val']) + else: + tag_found = tags.find_one({'register_number': address, 'register_type': self.register_type}) + print("{} = {}".format(tag_found['tag_name'], tag_found['val'])) + super(DigitalTagDataBlock, self).setValues(address, tag_found['val']) + + return super(DigitalTagDataBlock, self).getValues(address, count=count) + + def setValues(self, address, value): + ''' Sets the requested values of the datastore + + :param address: The starting address + :param values: The new values to be set + ''' + client = MongoClient() + db = client.tag_data + tags = db.tag_vals + tag_name = tags.find_one({'register_number': address, 'register_type': self.register_type})['tag_name'] + plc.writeTag(PLC_IP_ADDRESS, tag_name, value) + + +class AnalogTagDataBlock(ModbusSparseDataBlock): + ''' A datablock that stores the new value in memory + and passes the operation to a message queue for further + processing. + ''' + + def __init__(self, register_type, tag_list): + ''' + ''' + values = {} + self.register_type = register_type + + for t in tag_list: + try: + values[t['register_number']] = t['val'] + values[t['register_number'] + 1] = t['scale_type'] + values[t['register_number'] + 2] = t['scale_multiplier'] + values[t['register_number'] + 3] = 0 + except KeyError: + values[t['register_number']] = 911 + values[t['register_number'] + 1] = 2 + values[t['register_number'] + 2] = 0 + values[t['register_number'] + 3] = 0 + values[t['register_number'] + 4] = 123 + + # print("Initialized AnalogTagDataBlock for {} with values {}".format(self.register_type, values)) + super(AnalogTagDataBlock, self).__init__(values) + + def getValues(self, address, count=1): + client = MongoClient() + db = client.tag_data + tags = db.tag_vals + + if count > 1: + for i in range(address, address + count): + tag_found = tags.find_one({'register_number': i, 'register_type': self.register_type}) + if tag_found: + print("{} = {}".format(tag_found['tag_name'], tag_found['val'])) + super(AnalogTagDataBlock, self).setValues(address, tag_found['val']) + super(AnalogTagDataBlock, self).setValues(address + 3, 0) + else: + tag_found = tags.find_one({'register_number': address, 'register_type': self.register_type}) + if tag_found: + print("{} = {}".format(tag_found['tag_name'], tag_found['val'])) + super(AnalogTagDataBlock, self).setValues(address, tag_found['val']) + super(AnalogTagDataBlock, self).setValues(address + 3, 0) + + return super(AnalogTagDataBlock, self).getValues(address, count=count) + + def setValues(self, address, value): + ''' Sets the requested values of the datastore + + :param address: The starting address + :param values: The new values to be set + ''' + client = MongoClient() + db = client.tag_data + tags = db.tag_vals + tag_name = tags.find_one({'register_number': address, 'register_type': self.register_type})['tag_name'] + plc.writeTag(PLC_IP_ADDRESS, tag_name, value) + + +def main(): + # ---------------------------------------------------------------------------# + # initialize your data store + # ---------------------------------------------------------------------------# + tags_in_db = getTagsFromDB() + di_block = DigitalTagDataBlock('di', tags_in_db['di']) + co_block = DigitalTagDataBlock('co', tags_in_db['co']) + hr_block = AnalogTagDataBlock('hr', tags_in_db['hr']) + ir_block = AnalogTagDataBlock('ir', tags_in_db['ir']) + store = ModbusSlaveContext(di=di_block, co=co_block, hr=hr_block, ir=ir_block) + context = ModbusServerContext(slaves=store, single=True) + + # ---------------------------------------------------------------------------# + # initialize the server information + # ---------------------------------------------------------------------------# + identity = ModbusDeviceIdentification() + identity.VendorName = 'pymodbus' + identity.ProductCode = 'PM' + identity.VendorUrl = 'http://github.com/bashwork/pymodbus/' + identity.ProductName = 'pymodbus Server' + identity.ModelName = 'pymodbus Server' + identity.MajorMinorRevision = '1.0' + + # ---------------------------------------------------------------------------# + # run the server you want + # ---------------------------------------------------------------------------# + StartTcpServer(context, identity=identity, address=("localhost", 5020)) + +if __name__ == '__main__': + main() diff --git a/test.py b/test.py new file mode 100644 index 0000000..ebb85dd --- /dev/null +++ b/test.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +''' +Pymodbus Synchronous Client Examples +-------------------------------------------------------------------------- +The following is an example of how to use the synchronous modbus client +implementation from pymodbus. +It should be noted that the client can also be used with +the guard construct that is available in python 2.5 and up:: + with ModbusClient('127.0.0.1') as client: + result = client.read_coils(1,10) + print result +''' +# ---------------------------------------------------------------------------# +# import the various server implementations +# ---------------------------------------------------------------------------# +from pymodbus.client.sync import ModbusTcpClient as ModbusClient +# from pymodbus.client.sync import ModbusUdpClient as ModbusClient +# from pymodbus.client.sync import ModbusSerialClient as ModbusClient + +# ---------------------------------------------------------------------------# +# configure the client logging +# ---------------------------------------------------------------------------# +import logging +logging.basicConfig() +log = logging.getLogger() +log.setLevel(logging.INFO) +# ---------------------------------------------------------------------------# +# choose the client you want +# ---------------------------------------------------------------------------# +# make sure to start an implementation to hit against. For this +# you can use an existing device, the reference implementation in the tools +# directory, or start a pymodbus server. +# +# If you use the UDP or TCP clients, you can override the framer being used +# to use a custom implementation (say RTU over TCP). By default they use the +# socket framer:: +# +# client = ModbusClient('localhost', port=5020, framer=ModbusRtuFramer) +# +# It should be noted that you can supply an ipv4 or an ipv6 host address for +# both the UDP and TCP clients. +# +# There are also other options that can be set on the client that controls +# how transactions are performed. The current ones are: +# +# * retries - Specify how many retries to allow per transaction (default = 3) +# * retry_on_empty - Is an empty response a retry (default = False) +# * source_address - Specifies the TCP source address to bind to +# +# Here is an example of using these options:: +# +# client = ModbusClient('localhost', retries=3, retry_on_empty=True) +# ---------------------------------------------------------------------------# +client = ModbusClient('localhost', port=5020) +# client = ModbusClient(method='ascii', port='/dev/pts/2', timeout=1) +# client = ModbusClient(method='rtu', port='/dev/pts/2', timeout=1) +client.connect() + +# ---------------------------------------------------------------------------# +# specify slave to query +# ---------------------------------------------------------------------------# +# The slave to query is specified in an optional parameter for each +# individual request. This can be done by specifying the `unit` parameter +# which defaults to `0x00` +# ---------------------------------------------------------------------------# +print("HOLDING REGISTERS") +for i in range(0, 21): + rd = client.read_holding_registers(i * 5, 5) + print rd.registers + +print("INPUT REGISTERS") +for i in range(2, 125): + rd = client.read_input_registers(i * 5, 5) + print rd.registers + +# ---------------------------------------------------------------------------# +# close the client +# ---------------------------------------------------------------------------# +client.close()