diff --git a/Modbus Map Clean.pdf b/Modbus Map Clean.pdf new file mode 100644 index 0000000..0c7640f Binary files /dev/null and b/Modbus Map Clean.pdf differ diff --git a/Modbus Map.xlsx b/Modbus Map.xlsx index a50f72c..503c0f0 100644 Binary files a/Modbus Map.xlsx and b/Modbus Map.xlsx differ diff --git a/analog.json b/analog.json index 15dd2f2..5a6507b 100644 --- a/analog.json +++ b/analog.json @@ -124,6 +124,25 @@ {"tag_name": "kWh_Today", "register_type": "ir", "register_number": 231}, {"tag_name": "Max_Theoretical_Fluid_Load", "register_type": "ir", "register_number": 233}, {"tag_name": "Motor_Torque", "register_type": "ir", "register_number": 235}, + {"tag_name": "Pump.Run_Status", "register_type": "ir", "register_number": 237}, + {"tag_name": "Pump.Speed_Actual_SPM", "register_type": "ir", "register_number": 238}, + {"tag_name": "Pump_Intake_Pressure", "register_type": "ir", "register_number": 240}, + {"tag_name": "Card_Past[1].Surface_Position", "register_type": "ir", "register_number": 242, "arr_len": 750}, + {"tag_name": "Card_Past[2].Surface_Position", "register_type": "ir", "register_number": 1742, "arr_len": 750}, + {"tag_name": "Card_Past[3].Surface_Position", "register_type": "ir", "register_number": 3242, "arr_len": 750}, + {"tag_name": "Card_Past[4].Surface_Position", "register_type": "ir", "register_number": 4742, "arr_len": 750}, + {"tag_name": "Card_Past[1].Surface_Load", "register_type": "ir", "register_number": 6242, "arr_len": 750}, + {"tag_name": "Card_Past[2].Surface_Load", "register_type": "ir", "register_number": 7742, "arr_len": 750}, + {"tag_name": "Card_Past[3].Surface_Load", "register_type": "ir", "register_number": 9242, "arr_len": 750}, + {"tag_name": "Card_Past[4].Surface_Load", "register_type": "ir", "register_number": 10742, "arr_len": 750}, + {"tag_name": "Card_Past[1].Downhole_Position", "register_type": "ir", "register_number": 12242, "arr_len": 750}, + {"tag_name": "Card_Past[2].Downhole_Position", "register_type": "ir", "register_number": 13742, "arr_len": 750}, + {"tag_name": "Card_Past[3].Downhole_Position", "register_type": "ir", "register_number": 15242, "arr_len": 750}, + {"tag_name": "Card_Past[4].Downhole_Position", "register_type": "ir", "register_number": 16742, "arr_len": 750}, + {"tag_name": "Card_Past[1].Downhole_Load", "register_type": "ir", "register_number": 18242, "arr_len": 750}, + {"tag_name": "Card_Past[2].Downhole_Load", "register_type": "ir", "register_number": 19742, "arr_len": 750}, + {"tag_name": "Card_Past[3].Downhole_Load", "register_type": "ir", "register_number": 21242, "arr_len": 750}, + {"tag_name": "Card_Past[4].Downhole_Load", "register_type": "ir", "register_number": 22742, "arr_len": 750}, {"tag_name": "_dt", "register_type": "hr", "register_number": 1}, {"tag_name": "Casing_ID", "register_type": "hr", "register_number": 3}, @@ -155,5 +174,65 @@ {"tag_name": "Input_Inclinometer.Channel", "register_type": "hr", "register_number": 47}, {"tag_name": "Input_Inclinometer.Type", "register_type": "hr", "register_number": 48}, {"tag_name": "Input_LoadCell.Channel", "register_type": "hr", "register_number": 49}, - {"tag_name": "Input_LoadCell.Type", "register_type": "hr", "register_number": 50} + {"tag_name": "Input_LoadCell.Type", "register_type": "hr", "register_number": 50}, + {"tag_name": "Taper.Taper[1].Setup.Length", "register_type": "hr", "register_number": 51}, + {"tag_name": "Taper.Taper[1].Setup.Diameter", "register_type": "hr", "register_number": 53}, + {"tag_name": "Taper.Taper[1].Setup.Material", "register_type": "hr", "register_number": 55}, + {"tag_name": "Taper.Taper[2].Setup.Length", "register_type": "hr", "register_number": 56}, + {"tag_name": "Taper.Taper[2].Setup.Diameter", "register_type": "hr", "register_number": 58}, + {"tag_name": "Taper.Taper[2].Setup.Material", "register_type": "hr", "register_number": 60}, + {"tag_name": "Taper.Taper[3].Setup.Length", "register_type": "hr", "register_number": 61}, + {"tag_name": "Taper.Taper[3].Setup.Diameter", "register_type": "hr", "register_number": 63}, + {"tag_name": "Taper.Taper[3].Setup.Material", "register_type": "hr", "register_number": 65}, + {"tag_name": "Taper.Taper[4].Setup.Length", "register_type": "hr", "register_number": 66}, + {"tag_name": "Taper.Taper[4].Setup.Diameter", "register_type": "hr", "register_number": 68}, + {"tag_name": "Taper.Taper[4].Setup.Material", "register_type": "hr", "register_number": 70}, + {"tag_name": "Taper.Taper[5].Setup.Length", "register_type": "hr", "register_number": 71}, + {"tag_name": "Taper.Taper[5].Setup.Diameter", "register_type": "hr", "register_number": 73}, + {"tag_name": "Taper.Taper[5].Setup.Material", "register_type": "hr", "register_number": 75}, + {"tag_name": "Taper.Taper[6].Setup.Length", "register_type": "hr", "register_number": 76}, + {"tag_name": "Taper.Taper[6].Setup.Diameter", "register_type": "hr", "register_number": 78}, + {"tag_name": "Taper.Taper[6].Setup.Material", "register_type": "hr", "register_number": 80}, + {"tag_name": "Taper.Taper[7].Setup.Length", "register_type": "hr", "register_number": 81}, + {"tag_name": "Taper.Taper[7].Setup.Diameter", "register_type": "hr", "register_number": 83}, + {"tag_name": "Taper.Taper[7].Setup.Material", "register_type": "hr", "register_number": 85}, + {"tag_name": "Taper.Taper[8].Setup.Length", "register_type": "hr", "register_number": 86}, + {"tag_name": "Taper.Taper[8].Setup.Diameter", "register_type": "hr", "register_number": 88}, + {"tag_name": "Taper.Taper[8].Setup.Material", "register_type": "hr", "register_number": 90}, + {"tag_name": "Taper.Taper[9].Setup.Length", "register_type": "hr", "register_number": 91}, + {"tag_name": "Taper.Taper[9].Setup.Diameter", "register_type": "hr", "register_number": 93}, + {"tag_name": "Taper.Taper[9].Setup.Material", "register_type": "hr", "register_number": 95}, + {"tag_name": "Taper.Taper[10].Setup.Length", "register_type": "hr", "register_number": 96}, + {"tag_name": "Taper.Taper[10].Setup.Diameter", "register_type": "hr", "register_number": 98}, + {"tag_name": "Taper.Taper[10].Setup.Material", "register_type": "hr", "register_number": 100}, + {"tag_name": "UnitConfig.MotorNameplate.Volts", "register_type": "hr", "register_number": 101}, + {"tag_name": "UnitConfig.MotorNameplate.Amps", "register_type": "hr", "register_number": 103}, + {"tag_name": "UnitConfig.MotorNameplate.Hertz", "register_type": "hr", "register_number": 105}, + {"tag_name": "UnitConfig.MotorNameplate.Poles", "register_type": "hr", "register_number": 107}, + {"tag_name": "UnitConfig.MotorNameplate.RPM", "register_type": "hr", "register_number": 108}, + {"tag_name": "UnitConfig.MotorNameplate.ServiceFactor", "register_type": "hr", "register_number": 110}, + {"tag_name": "UnitConfig.MotorNameplate.Horsepower", "register_type": "hr", "register_number": 112}, + {"tag_name": "UnitConfig.Pump_Diameter", "register_type": "hr", "register_number": 114}, + {"tag_name": "UnitConfig.Anchor_Depth", "register_type": "hr", "register_number": 116}, + {"tag_name": "UnitConfig.Total_Stroke_Length", "register_type": "hr", "register_number": 118}, + {"tag_name": "UnitConfig.Motor_Sheave_Size", "register_type": "hr", "register_number": 120}, + {"tag_name": "UnitConfig.Gearbox_Sheave", "register_type": "hr", "register_number": 122}, + {"tag_name": "UnitConfig.Gearbox_Limit", "register_type": "hr", "register_number": 124}, + {"tag_name": "UnitConfig.Gearbox_Ratio", "register_type": "hr", "register_number": 126}, + {"tag_name": "UnitConfig.Rating_Gearbox", "register_type": "hr", "register_number": 128}, + {"tag_name": "UnitConfig.Rating_Structural", "register_type": "hr", "register_number": 130}, + {"tag_name": "UnitConfig.Well_Type", "register_type": "hr", "register_number": 132}, + {"tag_name": "UnitConfig.Total_Vertical_Depth", "register_type": "hr", "register_number": 133}, + {"tag_name": "UnitConfig.Tubing_Size_ID", "register_type": "hr", "register_number": 135}, + {"tag_name": "UnitConfig.Tubing_Size_OD", "register_type": "hr", "register_number": 137}, + {"tag_name": "UnitConfig.API_Oil", "register_type": "hr", "register_number": 139}, + {"tag_name": "UnitConfig.SG_Water", "register_type": "hr", "register_number": 141}, + {"tag_name": "UnitConfig.Percent_Water", "register_type": "hr", "register_number": 143}, + {"tag_name": "Pump.Speed_Setpoint_SPM", "register_type": "hr", "register_number": 145}, + {"tag_name": "Pump.Speed_Max", "register_type": "hr", "register_number": 147}, + {"tag_name": "Pump.Speed_Min", "register_type": "hr", "register_number": 149}, + {"tag_name": "Pump.POC_Percentage_Off", "register_type": "hr", "register_number": 151}, + {"tag_name": "Pump.Auto_Percentage_RampDown", "register_type": "hr", "register_number": 153}, + {"tag_name": "Pump.Auto_Percentage_RampUp", "register_type": "hr", "register_number": 155}, + {"tag_name": "Pump.Mode", "register_type": "hr", "register_number": 157} ] diff --git a/digital.json b/digital.json index 2acee8e..8eb694d 100644 --- a/digital.json +++ b/digital.json @@ -16,6 +16,9 @@ {"tag_name": "USE_WIRELESS_LOADCELL", "register_type": "co", "register_number": 15}, {"tag_name": "Write_Mode_Data", "register_type": "co", "register_number": 16}, {"tag_name": "Write_Setup_Data", "register_type": "co", "register_number": 17}, + {"tag_name": "Pump.Start", "register_type": "co", "register_number": 18}, + {"tag_name": "Pump.Stop", "register_type": "co", "register_number": 19}, + {"tag_name": "Inclinometer_Calibrating", "register_type": "di", "register_number": 1}, {"tag_name": "Inclinometer_Stale", "register_type": "di", "register_number": 2} ] diff --git a/poc_to_modbus.py b/poc_to_modbus.py index 4fbd434..409493a 100644 --- a/poc_to_modbus.py +++ b/poc_to_modbus.py @@ -52,6 +52,28 @@ def integer_to_byte(integer_val): return unpacked +def lebyte_to_float(word_list): + ''' + Converts list of little-endian bytes to float + ''' + packed_string = struct.pack("HH", *word_list) + unpacked_float = struct.unpack("f", packed_string)[0] + return unpacked_float + + +def lebyte_to_integer(word_list): + ''' + Converts list(size = 1) of little-endian bytes to Integer + ''' + try: + packed_string = struct.pack("H", *word_list) + unpacked_int = struct.unpack("h", packed_string)[0] + except Exception as e: + print("Unable to convert {} to integer".format(word_list)) + return False + return unpacked_int + + def getTagsFromDB(): client = MongoClient() db = client.tag_data @@ -104,14 +126,14 @@ class DigitalTagDataBlock(ModbusSparseDataBlock): super(DigitalTagDataBlock, self).__init__(values) def getValues(self, address, count=1): - # client = MongoClient() - # db = client.tag_data - # tags = db.tag_vals - # - # 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']) + client = MongoClient() + db = client.tag_data + tags = db.tag_vals + + 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']) return super(DigitalTagDataBlock, self).getValues(address, count=count) @@ -124,8 +146,19 @@ class DigitalTagDataBlock(ModbusSparseDataBlock): 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) + tag_found = tags.find_one({'register_number': address, 'register_type': self.register_type}) + tag_name = tag_found['tag_name'] + if value[0]: + plc_value = 1 + else: + plc_value = 0 + print("Writing {} to {}".format(plc_value, tag_name)) + plc.writeTag(PLC_IP_ADDRESS, tag_name, int(plc_value)) + plc_val = plc.readTag(PLC_IP_ADDRESS, tag_found['tag_name']) + if plc_val: + tag_found['val'] = plc_val[0] + tags.update({'tag_name': tag_found['tag_name']}, tag_found) + super(DigitalTagDataBlock, self).setValues(address, value) class AnalogTagDataBlock(ModbusSparseDataBlock): @@ -193,11 +226,26 @@ class AnalogTagDataBlock(ModbusSparseDataBlock): :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) + print("provided values: {}".format(value)) + try: + client = MongoClient() + db = client.tag_data + tags = db.tag_vals + tag_found = tags.find_one({'register_number': address, 'register_type': self.register_type}) + tag_name = tag_found['tag_name'] + if tag_found['tag_type'] == "REAL": + plc_value = lebyte_to_float(value) + else: + plc_value = value[0] + print("Writing {} to {}".format(plc_value, tag_name)) + plc.writeTag(PLC_IP_ADDRESS, tag_name, plc_value) + plc_val = plc.readTag(PLC_IP_ADDRESS, tag_found['tag_name']) + if plc_val: + tag_found['val'] = plc_val[0] + tags.update({'tag_name': tag_found['tag_name']}, tag_found) + super(AnalogTagDataBlock, self).setValues(address, value) + except Exception as e: + print(e) def main(): diff --git a/test.py b/test.py index 556e384..678d442 100644 --- a/test.py +++ b/test.py @@ -9,7 +9,7 @@ from pymodbus.client.sync import ModbusTcpClient as ModbusClient # import python packages # ---------------------------------------------------------------------------# from pymongo import MongoClient -import unittest +import pytest import struct # ---------------------------------------------------------------------------# @@ -33,16 +33,26 @@ mongo_db = mongo_client.tag_data mongo_tags = mongo_db.tag_vals -# ---------------------------------------------------------------------------# -# close enough function -# ---------------------------------------------------------------------------# def close_enough(a, b): + return abs(a - b) < 0.001 + + +def float_to_bytes(float_val): ''' - Determines if two values a and b are close enough to eachother to be considered equal + Converts a float to little-endian bytes ''' - if abs(a - b) < 0.01: - return True - return False + packed_string = struct.pack('f', float_val) + unpacked_list = list(struct.unpack('HH', packed_string)) + return unpacked_list + + +def integer_to_byte(integer_val): + ''' + Converts an integer to its byte + ''' + packed_string = struct.pack('h', integer_val) + unpacked = list(struct.unpack('H', packed_string)) + return unpacked def lebyte_to_float(word_list): @@ -70,8 +80,8 @@ def lebyte_to_integer(word_list): # ---------------------------------------------------------------------------# # specify slave to query # ---------------------------------------------------------------------------# -class TestModbusDatabaseValues(unittest.TestCase): - def test_holding_registers(self): +class TestModbusDatabaseValues: + def test_read_holding_registers(self): modbus_client.connect() tag_in_db = mongo_tags.find({"register_type": 'hr'}) for db_tag in tag_in_db: @@ -104,10 +114,10 @@ class TestModbusDatabaseValues(unittest.TestCase): print(reg) print("Modbus Scaled: {}".format(modbus_value)) print("Database Scaled: {}".format(database_value)) - self.assertTrue(modbus_value == database_value) + assert modbus_value == database_value modbus_client.close() - def test_input_registers(self): + def test_read_input_registers(self): modbus_client.connect() tag_in_db = mongo_tags.find({"register_type": 'ir'}) for db_tag in tag_in_db: @@ -144,10 +154,10 @@ class TestModbusDatabaseValues(unittest.TestCase): print(reg) print("Modbus Scaled: {}".format(modbus_value)) print("Database Scaled: {}".format(database_value)) - self.assertTrue(modbus_value == database_value) + assert modbus_value == database_value modbus_client.close() - def test_arrays(self): + def test_read_arrays(self): modbus_client.connect() tag_in_db = mongo_tags.find({"register_type": 'ir'}) for db_tag in tag_in_db: @@ -184,10 +194,10 @@ class TestModbusDatabaseValues(unittest.TestCase): if len(array_values) > 0: for i in range(0, len(array_values)): print("{} = {} - {}".format(i, array_values[i], db_vals[i])) - self.assertTrue(array_values == db_vals) + assert array_values == db_vals modbus_client.close() - def test_coils(self): + def test_read_coils(self): modbus_client.connect() tag_in_db = mongo_tags.find({"register_type": 'co'}) for db_tag in tag_in_db: @@ -200,13 +210,13 @@ class TestModbusDatabaseValues(unittest.TestCase): reg = modbus_client.read_coils(db_tag['register_number'] - 1, 1).bits if not database_value == reg[0]: print("{} =/= {}".format(database_value, reg[0])) - self.assertTrue(database_value == reg[0]) + assert database_value == reg[0] except AttributeError: print("Could not get register {} for {}".format(db_tag['register_number'], db_tag['tag_name'])) continue - def test_digital_inputs(self): + def test_read_digital_inputs(self): modbus_client.connect() tag_in_db = mongo_tags.find({"register_type": 'di'}) for db_tag in tag_in_db: @@ -219,11 +229,59 @@ class TestModbusDatabaseValues(unittest.TestCase): reg = modbus_client.read_discrete_inputs(db_tag['register_number'] - 1, 1).bits if not database_value == reg[0]: print("{} =/= {}".format(database_value, reg[0])) - self.assertTrue(database_value == reg[0]) + assert database_value == reg[0] except AttributeError: print("Could not get register {} for {}".format(db_tag['register_number'], db_tag['tag_name'])) continue -if __name__ == '__main__': - unittest.main() + def test_write_coils(self): + modbus_client.connect() + tag_in_db = mongo_tags.find({"register_type": 'co'}) + for db_tag in tag_in_db: + db_val_before = db_tag['val'] + if db_val_before == 0: + write_val = 1 + else: + write_val = 0 + modbus_client.write_coil(db_tag['register_number'] - 1, write_val) + reg = modbus_client.read_coils(db_tag['register_number'] - 1, 1).bits + assert write_val == reg[0] + + modbus_client.write_coil(db_tag['register_number'] - 1, db_val_before) + reg = modbus_client.read_coils(db_tag['register_number'] - 1, 1).bits + assert db_val_before == reg[0] + + def test_write_coils(self): + modbus_client.connect() + tag_in_db = mongo_tags.find({"register_type": 'hr'}) + for db_tag in tag_in_db: + db_val_before = db_tag['val'] + if db_tag['tag_type'] == "REAL": + write_val = db_val_before + 1.0 + db_val_before_bytes = float_to_bytes(db_val_before) + write_val_bytes = float_to_bytes(write_val) + + modbus_client.write_registers(db_tag['register_number'] - 1, write_val_bytes) + reg = modbus_client.read_holding_registers(db_tag['register_number'] - 1, 2).registers + read_val = lebyte_to_float(reg) + assert close_enough(write_val, read_val) + + modbus_client.write_registers(db_tag['register_number'] - 1, db_val_before_bytes) + reg = modbus_client.read_holding_registers(db_tag['register_number'] - 1, 2).registers + read_val = lebyte_to_float(reg) + assert close_enough(db_val_before, read_val) + else: + write_val = db_val_before + 1 + db_val_before_bytes = integer_to_byte(db_val_before) + write_val_bytes = integer_to_byte(write_val) + + modbus_client.write_registers(db_tag['register_number'] - 1, write_val_bytes) + reg = modbus_client.read_holding_registers(db_tag['register_number'] - 1, 1).registers + read_val = lebyte_to_integer(reg) + assert write_val == read_val + + modbus_client.write_registers(db_tag['register_number'] - 1, db_val_before_bytes) + reg = modbus_client.read_holding_registers(db_tag['register_number'] - 1, 1).registers + read_val = lebyte_to_integer(reg) + assert db_val_before == read_val