def __init__(self, output): """ Initialize a new instance of the logger :param output: The output file to save to """ self.output = output self.context = ModbusSlaveContext( di = ModbusSequentialDataBlock.create(), co = ModbusSequentialDataBlock.create(), hr = ModbusSequentialDataBlock.create(), ir = ModbusSequentialDataBlock.create())
def __init__(self, output): """ Initialize a new instance of the logger :param output: The output file to save to """ self.output = output self.context = ModbusSlaveContext( di=ModbusSequentialDataBlock.create(), co=ModbusSequentialDataBlock.create(), hr=ModbusSequentialDataBlock.create(), ir=ModbusSequentialDataBlock.create())
def run_updating_server(): store = ModbusSlaveContext(hr=ModbusSequentialDataBlock.create()) context = ModbusServerContext(slaves=store, single=True) loop = LoopingCall(f=updating_writer, a=(context, )) loop.start(1, now=True) # initially delay by time StartSerialServer(context, framer=ModbusRtuFramer, port='/dev/ttyACM0', timeout=0.3, baudrate=9600)
def test_combo(self): test_store = ModbusSlaveContext(t=ModbusSequentialDataBlock.create()) test_env = {'fields': {'scale_8_int': {'reg_type': CO, 'address': 1, 'encode': {'e_type': SCALE, 's_factor': 100, 'd_type': INT8} }, 'scale_8_uint': {'reg_type': CO, 'address': 2, 'encode': {'e_type': SCALE, 's_factor': 10, 'd_type': UINT8} }, 'scale_16_int': {'reg_type': CO, 'address': 3, 'encode': {'e_type': SCALE, 's_factor': 100, 'd_type': INT16} }, 'scale_16_uint': {'reg_type': CO, 'address': 4, 'encode': {'e_type': SCALE, 's_factor': 100, 'd_type': UINT16} }, 'scale_32_int': {'reg_type': CO, 'address': 5, 'encode': {'e_type': SCALE, 'd_type': INT32} }, 'scale_32_uint': {'reg_type': CO, 'address': 6, 'encode': {'e_type': SCALE, 'd_type': UINT32} }, 'scale_32_float': {'reg_type': CO, 'address': 7, 'encode': {'e_type': SCALE, 's_factor': 10, 'd_type': FLOAT32} }, 'comb_32_float': {'reg_type': CO, 'address': 8, 'encode': {'e_type': COMB, 'd_type': FLOAT32} }}, 'byte_order': '<', 'word_order': '<', 'default_scaling_factor': 1000} test_fields = test_env['fields'] test_handler = PayloadHandler(test_env, test_store) test_store.setValues(1, 1, test_handler.encode(1, test_fields['scale_8_int']['encode'])) self.assertEqual(1, test_handler.decode(1, 1, test_fields['scale_8_int']['encode'])) test_store.setValues(1, 2, test_handler.encode(10, test_fields['scale_8_uint']['encode'])) self.assertEqual(10, test_handler.decode(1, 2, test_fields['scale_8_uint']['encode'])) test_store.setValues(1, 3, test_handler.encode(12.5, test_fields['scale_16_int']['encode'])) self.assertEqual(12.5, test_handler.decode(1, 3, test_fields['scale_16_int']['encode'])) test_store.setValues(1, 4, test_handler.encode(32.991, test_fields['scale_16_uint']['encode'])) self.assertEqual(32.99, test_handler.decode(1, 4, test_fields['scale_16_uint']['encode'])) test_store.setValues(1, 5, test_handler.encode(1000, test_fields['scale_32_int']['encode'])) self.assertEqual(1000, test_handler.decode(1, 5, test_fields['scale_32_int']['encode'])) test_store.setValues(1, 6, test_handler.encode(63.2, test_fields['scale_32_uint']['encode'])) self.assertEqual(63.2, test_handler.decode(1, 6, test_fields['scale_32_uint']['encode'])) test_store.setValues(1, 7, test_handler.encode(1.333, test_fields['scale_32_float']['encode'])) self.assertEqual(1.3, test_handler.decode(1, 7, test_fields['scale_32_float']['encode'])) test_store.setValues(1, 8, test_handler.encode(789.4375, test_fields['comb_32_float']['encode'])) self.assertEqual(789.4375, test_handler.decode(1, 8, test_fields['comb_32_float']['encode']))
def __init__(self, port): self.port = port self.block = ModbusSequentialDataBlock.create() self.store = ModbusSlaveContext(ir=self.block) self.context = ModbusServerContext(slaves=self.store, single=True) self.scheduler = BackgroundScheduler() params = { 'hour': os.environ.get("VIRTUAL_SERVER_UPDATE_CRON_HOUR_TRIGGER"), 'minute': os.environ.get("VIRTUAL_SERVER_UPDATE_CRON_MINUTE_TRIGGER"), 'second': os.environ.get("VIRTUAL_SERVER_UPDATE_CRON_SECOND_TRIGGER"), } self.main_job = self.scheduler.add_job(**params, func=self.set_datastore, trigger='cron')
def initiate_server(identity, inteval, host, port): """ Start a Modbus server using the information on the configuration block above, and set a recurring call to a function in order to update the values. """ # initialize your data store store = ModbusSlaveContext( di=ModbusSequentialDataBlock.create(), co=ModbusSequentialDataBlock.create(), hr=ModbusSequentialDataBlock.create(), ir=ModbusSequentialDataBlock.create(), ) context = ModbusServerContext(slaves=store, single=True) # Start the Looping callback loop = LoopingCall(f=updating_writer, context=context) loop.start(inteval, now=False) # Start the Modbus server print("Modbus Server started. Press Cntl + C to stop...") StartTcpServer(context, identity=identity, address=(host, port))
def run_server(): block = ModbusSequentialDataBlock.create() store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block) context = ModbusServerContext(slaves=store, single=True) identity = ModbusDeviceIdentification() identity.VendorName = 'Pymodbus' identity.ProductCode = 'PM' identity.VendorUrl = 'http://github.com/riptideio/pymodbus/' identity.ProductName = 'Pymodbus Server' identity.ModelName = 'Pymodbus Server' identity.MajorMinorRevision = '1.5' StartTcpServer(context, identity=identity, address=("0.0.0.0", 502))
def test_decode(self): test_store = ModbusSlaveContext(t=ModbusSequentialDataBlock.create()) test_env = {'fields': {'scale_8_int': {'reg_type': CO, 'address': 1, 'encode': {'e_type': SCALE, 's_factor': 10, 'd_type': INT8} }, 'scale_8_uint': {'reg_type': CO, 'address': 4, 'encode': {'e_type': SCALE, 's_factor': 10, 'd_type': UINT8} }, 'scale_16_int': {'reg_type': CO, 'address': 6, 'encode': {'e_type': SCALE, 's_factor': 100, 'd_type': INT16} }, 'scale_16_uint': {'reg_type': CO, 'address': 8, 'encode': {'e_type': SCALE, 's_factor': 100, 'd_type': UINT16} }}, 'byte_order': '<', 'word_order': '<', 'default_scaling_factor': 1000} test_fields = test_env['fields'] test_handler = PayloadHandler(test_env, test_store) test_builder = BinaryPayloadBuilder('<', '<') test_builder.reset() test_builder.add_8bit_int(100) test_store.setValues(1, 1, test_builder.to_registers()) self.assertEqual(10, test_handler.decode(1, 1, test_fields['scale_8_int']['encode'])) test_builder.reset() test_builder.add_8bit_uint(100) test_store.setValues(1, 4, test_builder.to_registers()) self.assertEqual(10, test_handler.decode(1, 4, test_fields['scale_8_uint']['encode'])) test_builder.reset() test_builder.add_16bit_int(1350) test_store.setValues(1, 6, test_builder.to_registers()) self.assertEqual(13.5, test_handler.decode(1, 6, test_fields['scale_16_int']['encode'])) test_builder.reset() test_builder.add_16bit_uint(3287) test_store.setValues(1, 8, test_builder.to_registers()) self.assertEqual(32.87, test_handler.decode(1, 8, test_fields['scale_16_uint']['encode'])) test_builder.reset()
def test_no_e_type(self): test_store = ModbusSlaveContext(t=ModbusSequentialDataBlock.create()) test_env = {'fields': {'8_int': {'reg_type': CO, 'address': 1, 'encode': {'d_type': INT8} }, '8_uint': {'reg_type': CO, 'address': 2, 'encode': {'d_type': UINT8} }, '16_int': {'reg_type': CO, 'address': 3, 'encode': {'d_type': INT16} }, '16_uint': {'reg_type': CO, 'address': 4, 'encode': {'d_type': UINT16} }, '32_int': {'reg_type': CO, 'address': 5, 'encode': {'d_type': INT32} }, '32_uint': {'reg_type': CO, 'address': 6, 'encode': {'d_type': UINT32} }}, 'byte_order': '<', 'word_order': '<', 'default_scaling_factor': 1000} test_fields = test_env['fields'] test_handler = PayloadHandler(test_env, test_store) test_store.setValues(1, 1, test_handler.encode(1, test_fields['8_int']['encode'])) self.assertEqual(1, test_handler.decode(1, 1, test_fields['8_int']['encode'])) test_store.setValues(1, 2, test_handler.encode(10, test_fields['8_uint']['encode'])) self.assertEqual(10, test_handler.decode(1, 2, test_fields['8_uint']['encode'])) test_store.setValues(1, 3, test_handler.encode(12, test_fields['16_int']['encode'])) self.assertEqual(12, test_handler.decode(1, 3, test_fields['16_int']['encode'])) test_store.setValues(1, 4, test_handler.encode(32, test_fields['16_uint']['encode'])) self.assertEqual(32, test_handler.decode(1, 4, test_fields['16_uint']['encode'])) test_store.setValues(1, 5, test_handler.encode(1000, test_fields['32_int']['encode'])) self.assertEqual(1000, test_handler.decode(1, 5, test_fields['32_int']['encode'])) test_store.setValues(1, 6, test_handler.encode(63, test_fields['32_uint']['encode'])) self.assertEqual(63, test_handler.decode(1, 6, test_fields['32_uint']['encode']))
def test_encode(self): test_store = ModbusSlaveContext(t=ModbusSequentialDataBlock.create()) test_env = {'fields': {'scale_8_int': {'reg_type': CO, 'address': 1, 'encode': {'e_type': SCALE, 's_factor': 10, 'd_type': INT8} }, 'scale_8_uint': {'reg_type': CO, 'address': 2, 'encode': {'e_type': SCALE, 's_factor': 10, 'd_type': UINT8} }, 'scale_16_int': {'reg_type': CO, 'address': 3, 'encode': {'e_type': SCALE, 's_factor': 100, 'd_type': INT16} }, 'scale_16_uint': {'reg_type': CO, 'address': 4, 'encode': {'e_type': SCALE, 's_factor': 100, 'd_type': UINT16} }, 'scale_32_int': {'reg_type': CO, 'address': 5, 'encode': {'e_type': SCALE, 'd_type': INT32} }, 'scale_32_uint': {'reg_type': CO, 'address': 6, 'encode': {'e_type': SCALE, 'd_type': UINT32} }, 'scale_32_float': {'reg_type': CO, 'address': 7, 'encode': {'e_type': SCALE, 's_factor': 10, 'd_type': FLOAT32} }, 'comb_32_float': {'reg_type': CO, 'address': 8, 'encode': {'e_type': COMB, 'd_type': FLOAT32} }}, 'byte_order': '<', 'word_order': '<', 'default_scaling_factor': 1000} test_fields = test_env['fields'] test_handler = PayloadHandler(test_env, test_store) self.assertEqual(100, BinaryPayloadDecoder.fromRegisters( test_handler.encode(10, test_fields['scale_8_int']['encode']), byteorder='<', wordorder='<').decode_8bit_int()) self.assertEqual(100, BinaryPayloadDecoder.fromRegisters( test_handler.encode(10, test_fields['scale_8_uint']['encode']), byteorder='<', wordorder='<').decode_8bit_uint()) self.assertEqual(1250, BinaryPayloadDecoder.fromRegisters( test_handler.encode(12.5, test_fields['scale_16_int']['encode']), byteorder='<', wordorder='<').decode_16bit_int()) self.assertEqual(3299, BinaryPayloadDecoder.fromRegisters( test_handler.encode(32.991, test_fields['scale_16_uint']['encode']), byteorder='<', wordorder='<').decode_16bit_uint()) self.assertEqual(1000000, BinaryPayloadDecoder.fromRegisters( test_handler.encode(1000, test_fields['scale_32_int']['encode']), byteorder='<', wordorder='<').decode_32bit_int()) self.assertEqual(63200, BinaryPayloadDecoder.fromRegisters( test_handler.encode(63.2, test_fields['scale_32_uint']['encode']), byteorder='<', wordorder='<').decode_32bit_uint()) self.assertEqual(13, BinaryPayloadDecoder.fromRegisters( test_handler.encode(1.333, test_fields['scale_32_float']['encode']), byteorder='<', wordorder='<').decode_32bit_float()) self.assertEqual(789.4375, BinaryPayloadDecoder.fromRegisters( test_handler.encode(789.4375, test_fields['comb_32_float']['encode']), byteorder='<', wordorder='<').decode_32bit_float())
def run_server(): # ----------------------------------------------------------------------- # # initialize your data store # ----------------------------------------------------------------------- # # The datastores only respond to the addresses that they are initialized to # Therefore, if you initialize a DataBlock to addresses of 0x00 to 0xFF, a # request to 0x100 will respond with an invalid address exception. This is # because many devices exhibit this kind of behavior (but not all):: # # block = ModbusSequentialDataBlock(0x00, [0]*0xff) # # Continuing, you can choose to use a sequential or a sparse DataBlock in # your data context. The difference is that the sequential has no gaps in # the data while the sparse can. Once again, there are devices that exhibit # both forms of behavior:: # # block = ModbusSparseDataBlock({0x00: 0, 0x05: 1}) # block = ModbusSequentialDataBlock(0x00, [0]*5) # # Alternately, you can use the factory methods to initialize the DataBlocks # or simply do not pass them to have them initialized to 0x00 on the full # address range:: # # store = ModbusSlaveContext(di = ModbusSequentialDataBlock.create()) # store = ModbusSlaveContext() # # Finally, you are allowed to use the same DataBlock reference for every # table or you may use a separate DataBlock for each table. # This depends if you would like functions to be able to access and modify # the same data or not:: # # block = ModbusSequentialDataBlock(0x00, [0]*0xff) # store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block) # # The server then makes use of a server context that allows the server to # respond with different slave contexts for different unit ids. By default # it will return the same context for every unit id supplied (broadcast # mode). # However, this can be overloaded by setting the single flag to False and # then supplying a dictionary of unit id to context mapping:: # # slaves = { # 0x01: ModbusSlaveContext(...), # 0x02: ModbusSlaveContext(...), # 0x03: ModbusSlaveContext(...), # } # context = ModbusServerContext(slaves=slaves, single=False) # # The slave context can also be initialized in zero_mode which means that a # request to address(0-7) will map to the address (0-7). The default is # False which is based on section 4.4 of the specification, so address(0-7) # will map to (1-8):: # # store = ModbusSlaveContext(..., zero_mode=True) # ----------------------------------------------------------------------- # # store = ModbusSlaveContext( # di=ModbusSequentialDataBlock(0, [17]*100), # co=ModbusSequentialDataBlock(0, [17]*100), # hr=ModbusSequentialDataBlock(0, [17]*100), # ir=ModbusSequentialDataBlock(0, [17]*100)) store = ModbusSlaveContext(di=ModbusSequentialDataBlock.create()) store = ModbusSlaveContext() context = ModbusServerContext(slaves=store, single=True) # ----------------------------------------------------------------------- # # initialize the server information # ----------------------------------------------------------------------- # # If you don't set this or any fields, they are defaulted to empty strings. # ----------------------------------------------------------------------- # identity = ModbusDeviceIdentification() identity.VendorName = 'Pymodbus' identity.ProductCode = 'PM' identity.VendorUrl = 'http://github.com/riptideio/pymodbus/' identity.ProductName = 'Pymodbus Server' identity.ModelName = 'Pymodbus Server' identity.MajorMinorRevision = '2.3.0' # ----------------------------------------------------------------------- # # run the server you want # ----------------------------------------------------------------------- # # Tcp: StartTcpServer(context, identity=identity, address=("", 502))
class Battery: """ Battery represents the Server/Slave. Basically is a modbus server wrapper. """ """ Initialize the store and the context """ store = ModbusSlaveContext(di=ModbusSequentialDataBlock.create(), co=ModbusSequentialDataBlock.create(), hr=ModbusSequentialDataBlock.create(), ir=ModbusSequentialDataBlock.create()) context = ModbusServerContext(slaves=store, single=True) def __init__(self, env): self.fields = env['fields'] self.id = env['id'] self.max_capacity = env['battery_capacity'] self.payload_handler = PayloadHandler(env['float_store'], self.store) self.set_initial_values() self.run_server(self.context, env['server_address']) def set_initial_values(self): """ setting initial values to fields which have 'init' keyword in their dictionary """ for field_name in self.fields: if 'init' in self.fields[field_name]: self.set_value(self.fields[field_name], self.fields[field_name]['init']) def set_value(self, field, value): """ sets the value to the given field; ALL data which is assigned to 16-bit registers (meaning fx > 2) will be encoded by payload_handler :param field: a field from the the dictionary (fields[field_name]) :param value: value to set """ fx = field['reg_type'] addr = field['address'] if fx > 2: mode = field['encode'] value = self.payload_handler.encode(value, mode) self.store.setValues(fx, addr, value) else: self.store.setValues(fx, addr, [value]) def get_value(self, field): """ gets the value of a given field; ALL data from 16-bit registers (meaning fx > 2) will be decoded by payload_handler :param field: a field from the the dictionary (fields[field_name]) :return: value of the given field """ fx = field['reg_type'] addr = field['address'] if fx <= 2: value = int(self.store.getValues(fx, addr, 1)[0]) elif fx > 2: mode = field['encode'] value = self.payload_handler.decode(fx, addr, mode) return value def is_input_connected(self): return self.get_value(self.fields['input_connected']) def is_converter_started(self): return self.get_value(self.fields['converter_started']) def run_server(self, context, env): """ starts the servers with filled in context runs in separate thread """ t = threading.Thread(target=StartTcpServer, kwargs={ 'context': context, 'address': tuple(env) }, daemon=True) t.start() print("SERVER: is running")