async def _write_register_value( self, key: str, value: Union[str, float, int]) -> WriteMultipleRegistersResponse: """Write a single value to the holding registers. Currently registers are written one at a time to avoid issues with discontinuous modbus addresses. """ start_address = self.tags[key]['address']['start'] - 400001 builder = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) data_type = self.tags[key]['type'] if data_type == 'float': builder.add_32bit_float(value) elif data_type == 'str': chars = self.tags[key]['length'] if len(value) > chars: raise ValueError(f'{value} is too long for {key}. ' f'Max: {chars} chars') builder.add_string(value.ljust(chars)) elif data_type == 'int16': builder.add_16bit_int(value) elif data_type == 'int32': builder.add_32bit_int(value) else: raise ValueError("Missing data type.") resp = await self.write_registers(start_address, builder.build(), skip_encode=True) return resp[0]
def encode_field(self, value, mb_type='unit16'): builder = BinaryPayloadBuilder(endian=self.endian) if mb_type == 'bit' or mb_type == 'bits': builder.add_bits(value) elif mb_type == 'uint8': builder.add_8bit_uint(value) elif mb_type == 'uint16': builder.add_16bit_uint(value) elif mb_type == 'uint32': builder.add_32bit_uint(value) elif mb_type == 'uint64': builder.add_64bit_uint(value) elif mb_type == 'int8': builder.add_8bit_int(value) elif mb_type == 'int16': builder.add_16bit_int(value) elif mb_type == 'int32': builder.add_32bit_int(value) elif mb_type == 'int64': builder.add_64bit_int(value) elif mb_type == 'float32': builder.add_32bit_float(value) elif mb_type == 'float64': builder.add_64bit_float(value) elif mb_type == 'string' or mb_type == 'str': builder.add_string(value) else: log.warn('Not supported DataType: "%s"' % mb_type) return builder.build()
def set_value(self, value): if value is not None: builder = BinaryPayloadBuilder(byteorder=self.byteorder, wordorder=self.wordorder) if self.encoding in ['int8', 'uint8']: builder.add_8bit_int( value ) if self.encoding == 'int8' else builder.add_8bit_uint( value) elif self.encoding in ['int16', 'uint16']: builder.add_16bit_int( value ) if self.encoding == 'int16' else builder.add_16bit_uint( value) elif self.encoding in ['int32', 'uint32']: builder.add_32bit_int( value ) if self.encoding == 'int32' else builder.add_32bit_uint( value) elif self.encoding in ['float32', 'float64']: builder.add_32bit_float( value ) if self.encoding == 'float32' else builder.add_64bit_float( value) elif self.encoding in ['int64', 'uint64']: builder.add_64bit_int( value ) if self.encoding == 'int64' else builder.add_64bit_uint( value) elif self.encoding == 'boolean': builder.add_16bit_uint(value) elif self.encoding == 'string': builder.add_string(value) else: log.error("Unhandled encoding exception {enc}".format( enc=self.encoding)) payload = builder.to_registers() log.info( "Setting {type} {addr} to {enc} {val} as {list}".format( type=self.type, addr=self.address, enc=self.encoding, val=value, list=str(payload))) self.parent.context.setValues(self.get_function_code(), self.address, payload) self.value = value else: log.warning( "Attempt to set {type} {addr} to None (default={default})". format(type=self.type, addr=self.address, default=self.default))
def writeValue(self, varname, value): if CONST_MAP_VARIABLES_TO_ID[varname]["write"] != True: self.logger.error( "Helios: Variable {0} may not be written!".format(varname)) return False success = False self._lock.acquire() try: # if we have got to write a single bit, we need the current (byte) value to # reproduce the other bits... # if CONST_MAP_VARIABLES_TO_ID[varname]["type"] == "bit": # currentval = None # # Send poll request # # Read response # # CONST_MAP_VARIABLES_TO_ID[varname]["variable"] # value = value # if currentval == None: # self.logger.error("Helios: Sending value to ventilation system failed. Can not read current variable value '{0}'." # .format(varname)) # return False # rawvalue = self._convertFromValue(varname, value, currentval) # else: # #rawvalue = self._convertFromValue(varname, value, None) rawvalue = str(value) # send the new value if rawvalue is not None: # Writing value builder = BinaryPayloadBuilder() builder.add_string( CONST_MAP_VARIABLES_TO_ID[varname]["variable"] + '=' + rawvalue + '\0') payload = builder.build() self._port.write_registers(FIRST_REGISTER_ADDR, payload, skip_encode=True, unit=SLAVE_ID) success = True else: self.logger.error( "Helios: Sending value to ventilation system failed. Can not convert value '{0}' for variable '{1}'." .format(value, varname)) success = False except Exception as e: self.logger.error( "Helios: Exception in writeValue() occurred: {0}".format(e)) finally: self._lock.release() return success
def make_block(section, test_val, test_params): #print("Format", section) builder = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Big) # TYPES TO BE ADDED value = 0 data_size = 0 for s in section: field_name = test_params["field_name"].format(source=s[4], block=s[5], field=s[6] ) #print(field_name, test_val[field_name], s) print("Add: ", data_size, s, end="") value = test_val[field_name] #value += 1 if s[0] == "float16": builder.add_16bit_uint(round(value) ) data_size +=1 elif s[0] == "uint16": #print(value, float(value), s[2] , s[3] ) #print("values:", s, value) val = int( ( float(value) - float(s[3])) / float(s[2]) ) #print("values:", s, value, val) builder.add_16bit_uint(val) data_size+=1 elif s[0] == "uint32": val = int(float(value) / float(s[2]) - float(s[3]) ) builder.add_32bit_uint(val) data_size += 2 elif s[0] in ["int32", "sint32"]: val = int(float(value) / float(s[2]) - float(s[3]) ) builder.add_32bit_int(val) data_size +=2 elif s[0] == "float32": val = float(value) / float(s[2]) - float(s[3]) builder.add_32bit_float(val) data_size +=2 elif s[0] == "bit16": builder.add_bits([0, 0, 0, 0, 0, 0, 0, 0] ) builder.add_bits([0, 0, 0, 0, 0, 0, 0, 0] ) val = 0 data_size += 1 elif s[0][:3] == "str": data_length = int(s[1]) val = " " * (data_length - len(str(value))) + str(value) builder.add_string(val) data_size += data_length/2 else: print(" ------ ", s) print(val) #value += 1 block = ModbusSequentialDataBlock(1, builder.to_registers()) return block
def prefil_registers(a): context=a[0] # Values to be filled: # 4000 SERIAL NUMBER, length 2, 13070001 HEX # 4002 MeterCode, length 1, 0102 HEX # 4003 Meter ID, Length 1, 0001 # 4004 Baud, Length 1, 9600 # 4005 Protocol Version, Lenth 2, 3.2 # 4007 Software Version, Length 2, 1.18 # 4009 Hardware Version, Length 2, 1.03 # 400B Meter Amps, Length 1, 45 # 400D S0 Rate, Length 2, 1000 # 400F Combination Code, Length 1, 10 (Forward - Reverse) # 4010 LCD LifeCycle, Lenght 1, 01 HEX # 4011 Parity Setting, Length 1, 01 # 4012 Current Direction, Lenght 1, FW ASCII builder = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Big) builder.add_32bit_float(0x13070001) builder.add_16bit_int(0x0102) builder.add_16bit_int(1) builder.add_16bit_int(9600) builder.add_32bit_float(3.2) builder.add_32bit_float(1.18) builder.add_32bit_float(1.03) builder.add_16bit_int(45) # builder.add_16bit_int(0) payload = builder.to_registers() context[0x01].setValues(0x10, 0x4000, payload) # Skip Address builder2 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Big) builder2.add_32bit_float(1000) builder2.add_16bit_int(10) builder2.add_16bit_int(0x10) builder2.add_16bit_int(01) builder2.add_string('FW') payload2 = builder2.to_registers() context[0x01].setValues(0x10, 0x400D, payload2) # 5000 builder3 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Big) builder3.add_32bit_float(230) builder3.add_32bit_float(230) builder3.add_32bit_float(230) builder3.add_32bit_float(230) builder3.add_32bit_float(50) payload3 = builder3.to_registers() context[0x01].setValues(0x10, 0x5000, payload3)
def run_payload_server(): # ----------------------------------------------------------------------- # # build your payload # ----------------------------------------------------------------------- # builder = BinaryPayloadBuilder(byteorder=Endian.Little, wordorder=Endian.Little) builder.add_string('abcdefgh') builder.add_bits([0, 1, 0, 1, 1, 0, 1, 0]) builder.add_8bit_int(-0x12) builder.add_8bit_uint(0x12) builder.add_16bit_int(-0x5678) builder.add_16bit_uint(0x1234) builder.add_32bit_int(-0x1234) builder.add_32bit_uint(0x12345678) builder.add_16bit_float(12.34) builder.add_16bit_float(-12.34) builder.add_32bit_float(22.34) builder.add_32bit_float(-22.34) builder.add_64bit_int(-0xDEADBEEF) builder.add_64bit_uint(0x12345678DEADBEEF) builder.add_64bit_uint(0xDEADBEEFDEADBEED) builder.add_64bit_float(123.45) builder.add_64bit_float(-123.45) # ----------------------------------------------------------------------- # # use that payload in the data store # ----------------------------------------------------------------------- # # Here we use the same reference block for each underlying store. # ----------------------------------------------------------------------- # block = ModbusSequentialDataBlock(1, builder.to_registers()) store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block) 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 = version.short() # ----------------------------------------------------------------------- # # run the server you want # ----------------------------------------------------------------------- # StartTcpServer(context, identity=identity, address=("localhost", 5020))
def update_register(context, param): values = [] fn_code = HOLDING_REGISTER_FN_CODE if (param == "temperature"): address = TEMPERATURE_REGISTER newvalue = random.randint(TEMP_LO, TEMP_HI) log.debug("new temperatue value: " + str(newvalue)) elif (param == "humidity"): address = HUMIDITY_REGISTER newvalue = random.randint(HUMID_LO, HUMID_HI) log.debug("new humidity value: " + str(newvalue)) elif (param == "pressure"): address = PRESSURE_REGISTER newvalue = random.randint(PRESSURE_LO, PRESSURE_HI) log.debug("new pressure value: " + str(newvalue)) elif (param == "geolati"): address = GEO_LATI_REGISTER newvalue = random.uniform(LATI_LO, LATI_HI) log.debug("new latitude value = " + str(newvalue)) builder = BinaryPayloadBuilder(endian=Endian.Big) builder.add_32bit_float(newvalue) payload = builder.to_registers() context.setValues(fn_code, address, payload) return elif (param == "geolongi"): address = GEO_LONGI_REGISTER newvalue = random.uniform(LONGI_LO, LONGI_HI) log.debug("new longitude value = " + str(newvalue)) builder = BinaryPayloadBuilder(endian=Endian.Big) builder.add_32bit_float(newvalue) payload = builder.to_registers() context.setValues(fn_code, address, payload) return elif (param == "keyop"): address = KEY_OP_REGISTER context.setValues(fn_code, address, [0] * 8) newvalue = random.choice(operations) newvalue = newvalue log.debug("new key operation = " + newvalue) builder = BinaryPayloadBuilder(endian=Endian.Big) builder.add_string(newvalue) payload = builder.to_registers() context.setValues(fn_code, address, payload) return else: return values.append(newvalue) context.setValues(fn_code, address, values)
def readValue(self, varname): if CONST_MAP_VARIABLES_TO_ID[varname]["read"] != True: self.logger.error("Variable {0} may not be read!".format(varname)) return False value = None self._lock.acquire() try: self.logger.debug("Helios: Reading value: {0}".format(varname)) # Send poll request builder = BinaryPayloadBuilder() builder.add_string(CONST_MAP_VARIABLES_TO_ID[varname]["variable"] + '\0') payload = builder.build() self._port.write_registers(FIRST_REGISTER_ADDR, payload, skip_encode=True, unit=SLAVE_ID) # Read response rtr = CONST_MAP_VARIABLES_TO_ID[varname]["count"] + 3 if rtr < 8: rtr = 8 result = self._port.read_holding_registers(FIRST_REGISTER_ADDR, rtr, unit=SLAVE_ID) output = BinaryPayloadDecoder.fromRegisters( result.registers).decode_string(rtr) output = re.sub(u'([\x00])', "", output.decode('utf-8')) reg, value = output.split('=') if value is not None: raw_value = value value = self._convertFromRawValue(varname, value) self.logger.debug( "Value for {0} ({1}) received: {2} --> converted = {3}". format(varname, CONST_MAP_VARIABLES_TO_ID[varname]["variable"], raw_value, value)) else: # logging in debug only, so we stop spamming log file (noise on the bus seems to be normal) self.logger.debug( "Helios: No valid value for '{0}' from ventilation system received." .format(varname)) except Exception as e: self.logger.error( "Helios: Exception in readValue() occurred: {0}".format(e)) finally: self._lock.release() return value
def run_payload_server(): # ----------------------------------------------------------------------- # # build your payload # ----------------------------------------------------------------------- # builder = BinaryPayloadBuilder(byteorder=Endian.Little, wordorder=Endian.Little) builder.add_string('abcdefgh') builder.add_bits([0, 1, 0, 1, 1, 0, 1, 0]) builder.add_8bit_int(-0x12) builder.add_8bit_uint(0x12) builder.add_16bit_int(-0x5678) builder.add_16bit_uint(0x1234) builder.add_32bit_int(-0x1234) builder.add_32bit_uint(0x12345678) builder.add_32bit_float(22.34) builder.add_32bit_float(-22.34) builder.add_64bit_int(-0xDEADBEEF) builder.add_64bit_uint(0x12345678DEADBEEF) builder.add_64bit_uint(0xDEADBEEFDEADBEED) builder.add_64bit_float(123.45) builder.add_64bit_float(-123.45) # ----------------------------------------------------------------------- # # use that payload in the data store # ----------------------------------------------------------------------- # # Here we use the same reference block for each underlying store. # ----------------------------------------------------------------------- # block = ModbusSequentialDataBlock(1, builder.to_registers()) store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block) 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/bashwork/pymodbus/' identity.ProductName = 'Pymodbus Server' identity.ModelName = 'Pymodbus Server' identity.MajorMinorRevision = '1.5' # ----------------------------------------------------------------------- # # run the server you want # ----------------------------------------------------------------------- # StartTcpServer(context, identity=identity, address=("localhost", 5020))
def testBigEndianPayloadBuilder(self): ''' Test basic bit message encoding/decoding ''' builder = BinaryPayloadBuilder(endian=Endian.Big) builder.add_8bit_uint(1) builder.add_16bit_uint(2) builder.add_32bit_uint(3) builder.add_64bit_uint(4) builder.add_8bit_int(-1) builder.add_16bit_int(-2) builder.add_32bit_int(-3) builder.add_64bit_int(-4) builder.add_32bit_float(1.25) builder.add_64bit_float(6.25) builder.add_string(b'test') builder.add_bits(self.bitstring) self.assertEqual(self.big_endian_payload, builder.to_string())
def testLittleEndianPayloadBuilder(self): ''' Test basic bit message encoding/decoding ''' builder = BinaryPayloadBuilder(endian=Endian.Little) builder.add_8bit_uint(1) builder.add_16bit_uint(2) builder.add_32bit_uint(3) builder.add_64bit_uint(4) builder.add_8bit_int(-1) builder.add_16bit_int(-2) builder.add_32bit_int(-3) builder.add_64bit_int(-4) builder.add_32bit_float(1.25) builder.add_64bit_float(6.25) builder.add_string('test') builder.add_bits(self.bitstring) self.assertEqual(self.little_endian_payload, str(builder))
def testLittleEndianPayloadBuilder(self): """ Test basic bit message encoding/decoding """ builder = BinaryPayloadBuilder(byteorder=Endian.Little, wordorder=Endian.Little) builder.add_8bit_uint(1) builder.add_16bit_uint(2) builder.add_32bit_uint(3) builder.add_64bit_uint(4) builder.add_8bit_int(-1) builder.add_16bit_int(-2) builder.add_32bit_int(-3) builder.add_64bit_int(-4) builder.add_32bit_float(1.25) builder.add_64bit_float(6.25) builder.add_16bit_uint(1) # placeholder builder.add_string(b'test') builder.add_bits(self.bitstring) self.assertEqual(self.little_endian_payload, builder.to_string())
def build_args(self, arg, kwargs): builder = BinaryPayloadBuilder( byteorder=Endian.Big) #, wordorder=Endian.Big) if 'arg_type' in kwargs: if kwargs['arg_type'] == 'float': builder.add_32bit_float(arg) elif kwargs['arg_type'] == 'uint64': builder.add_64bit_uint(arg) elif kwargs['arg_type'] == 'int64': builder.add_64bit_int(arg) elif kwargs['arg_type'] == 'uint32': builder.add_32bit_uint(arg) elif kwargs['arg_type'] == 'int32': builder.add_32bit_int(arg) elif kwargs['arg_type'] == 'uint16': builder.add_16bit_uint(arg) elif kwargs['arg_type'] == 'int16': builder.add_16bit_int(arg) elif kwargs['arg_type'] == 'int8': builder.add_8bit_int(arg) elif kwargs['arg_type'] == 'uint8': builder.add_8bit_uint(arg) elif kwargs['arg_type'] == 'string': builder.add_string(arg) else: raise Exception("unknown parameter type given: %s" % (kwargs['arg_type'])) else: builder.add_16bit_uint(arg) val = builder.build() return val
def convert_string_to_register(value): # currently not used because no need to write string in register """ Convert a string value to a list of registers :param value: A string :type value: str :return: registers value :rtype: list[int] """ if isinstance(value, str): if pymodbus.__version__ == '1.3.2': # by default, on Version 1.3.2, endian is Endian.Little, # so registers were written in the reverse order. # For now, we use this version on the raspberry pi # and v 2.4.0 in simulation builder = BinaryPayloadBuilder(endian=Endian.Big) else: builder = BinaryPayloadBuilder() builder.add_string(value) payload = builder.to_registers() return payload else: return None
def write_registers(self, reg_id: int, value=None): if reg_id is None: raise ValueError reg_str = "v{:05d}".format(reg_id) builder = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) builder.add_string(reg_str) if value is not None: builder.add_string("=" + str(value)) # terminate string with NUL byte builder.add_string('\0') payload = builder.to_registers() self.client.write_registers(address=self.ADDRESS, values=payload, unit=self.UNIT)
def convert(self, config, data): byte_order = config["byteOrder"] if config.get("byteOrder") else "LITTLE" if byte_order == "LITTLE": builder = BinaryPayloadBuilder(byteorder=Endian.Little) elif byte_order == "BIG": builder = BinaryPayloadBuilder(byteorder=Endian.Big) else: log.warning("byte order is not BIG or LITTLE") return reg_count = config.get("registerCount", 1) value = config["value"] if config.get("tag") is not None: tags = (findall('[A-Z][a-z]*', config["tag"])) if "Coil" in tags: builder.add_bits(value) elif "String" in tags: builder.add_string(value) elif "Double" in tags: if reg_count == 4: builder.add_64bit_float(value) else: log.warning("unsupported amount of registers with double type for device %s in Downlink converter", self.__config["deviceName"]) return elif "Float" in tags: if reg_count == 2: builder.add_32bit_float(value) else: log.warning("unsupported amount of registers with float type for device %s in Downlink converter", self.__config["deviceName"]) return elif "Integer" in tags or "DWord" in tags or "DWord/Integer" in tags or "Word" in tags: if reg_count == 1: builder.add_16bit_int(value) elif reg_count == 2: builder.add_32bit_int(value) elif reg_count == 4: builder.add_64bit_int(value) else: log.warning("unsupported amount of registers with integer/word/dword type for device %s in Downlink converter", self.__config["deviceName"]) return else: log.warning("unsupported hardware data type for device %s in Downlink converter", self.__config["deviceName"]) if config.get("bit") is not None: bits = [0 for _ in range(8)] bits[config["bit"]-1] = int(value) log.debug(bits) builder.add_bits(bits) return builder.to_string() if config["functionCode"] in [5, 15]: return builder.to_coils() elif config["functionCode"] in [6, 16]: return builder.to_registers() else: log.warning("Unsupported function code, for device %s in Downlink converter", self.__config["deviceName"]) return
def write(self, name, value): client = self.mbclient(host=self.host, port=self.port, retries=self.retries, backoff=self.backoff, timeout=self.timeout, framer=self.mbframer, retry_on_empty=True, retry_on_invalid=True) if not client.connect(): logger.error("Cannot connect to bridge %s" % (self.host)) return False row = list(filter(lambda r: r[0] == name, self.mapping)) if len(row): (name, descr, unit, datatype, rw, scale, offset, register) = row[0] if not rw: logger.error( "Error writing to bridge %s slave %d register %d: read only" % (self.host, self.slaveid, register)) client.close() return False register = int(register) try: builder = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=self.endian) if datatype == 'b': builder.add_8bit_int(value) if datatype == 'B': builder.add_8bit_uint(value) if datatype == 'h': builder.add_16bit_int(value) if datatype == 'H': builder.add_16bit_uint(value) if datatype == 'i': builder.add_32bit_int(value) if datatype == 'I': builder.add_32bit_uint(value) if datatype == 'q': builder.add_64bit_int(value) if datatype == 'Q': builder.add_64bit_uint(value) if datatype == 'f': builder.add_32bit_float(value) if datatype == 'd': builder.add_64bit_float(value) if re.match(r'^s(\d*)$', datatype): builder.add_string(value) registers = builder.to_registers() except (AttributeError, ValueError, struct.error) as e: logger.error( "Error writing to bridge %s slave %d register %d: %s" % (self.host, self.slaveid, register, str(e))) client.close() return False try: if register > 40000: addr = register - 40001 if len(registers) > 1: result = client.write_registers(addr, registers, unit=self.slaveid) else: result = client.write_register(addr, value, unit=self.slaveid) else: addr = register - 1 result = client.write_coil(addr, bool(value), unit=self.slaveid) except ConnectionException as e: logger.error( "Error writing to bridge %s slave %d register %d: %s" % (self.host, self.slaveid, register, str(e))) client.close() return False if type(result) == ExceptionResponse: logger.error( "Error writing to bridge %s slave %d register %d: %s" % (self.host, self.slaveid, register, result)) client.close() return False if result.isError(): logger.error( "Error writing to bridge %s slave %d register %d" % (self.host, self.slaveid, register)) client.close() return False logger.debug( 'Modbus bridge: %s slave: %s register: %s (%s) value: %s' % (self.host, self.slaveid, register, name, value)) client.close() return True
def string_to_register(string): # code a string to 16 bits hex value to store in register builder = BinaryPayloadBuilder() builder.add_string(string) payload = builder.to_registers() return payload
#---------------------------------------------------------------------------# # If you need to build a complex message to send, you can use the payload # builder to simplify the packing logic. # # Here we demonstrate packing a random payload layout, unpacked it looks # like the following: # # - a 8 byte string 'abcdefgh' # - a 32 bit float 22.34 # - a 16 bit unsigned int 0x1234 # - an 8 bit int 0x12 # - an 8 bit bitstring [0,1,0,1,1,0,1,0] #---------------------------------------------------------------------------# builder = BinaryPayloadBuilder(endian=Endian.Little) builder.add_string('abcdefgh') builder.add_32bit_float(22.34) builder.add_16bit_uint(0x1234) builder.add_8bit_int(0x12) builder.add_bits([0,1,0,1,1,0,1,0]) payload = builder.build() address = 0x01 result = client.write_registers(address, payload, skip_encode=True) #---------------------------------------------------------------------------# # If you need to decode a collection of registers in a weird layout, the # payload decoder can help you as well. # # Here we demonstrate decoding a random register layout, unpacked it looks # like the following: #
def run_binary_payload_ex(): # ----------------------------------------------------------------------- # # We are going to use a simple client to send our requests # ----------------------------------------------------------------------- # client = ModbusClient('127.0.0.1', port=5020) client.connect() # ----------------------------------------------------------------------- # # If you need to build a complex message to send, you can use the payload # builder to simplify the packing logic. # # Here we demonstrate packing a random payload layout, unpacked it looks # like the following: # # - a 8 byte string 'abcdefgh' # - a 32 bit float 22.34 # - a 16 bit unsigned int 0x1234 # - another 16 bit unsigned int 0x5678 # - an 8 bit int 0x12 # - an 8 bit bitstring [0,1,0,1,1,0,1,0] # - an 32 bit uint 0x12345678 # - an 32 bit signed int -0x1234 # - an 64 bit signed int 0x12345678 # The packing can also be applied to the word (wordorder) and bytes in each # word (byteorder) # The wordorder is applicable only for 32 and 64 bit values # Lets say we need to write a value 0x12345678 to a 32 bit register # The following combinations could be used to write the register # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # # Word Order - Big Byte Order - Big # word1 =0x1234 word2 = 0x5678 # Word Order - Big Byte Order - Little # word1 =0x3412 word2 = 0x7856 # Word Order - Little Byte Order - Big # word1 = 0x5678 word2 = 0x1234 # Word Order - Little Byte Order - Little # word1 =0x7856 word2 = 0x3412 # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # # ----------------------------------------------------------------------- # combos = [(wo, bo) for wo in [Endian.Big, Endian.Little] for bo in [Endian.Big, Endian.Little]] for wo, bo in combos: print("-" * 60) print("Word Order: {}".format(ORDER_DICT[wo])) print("Byte Order: {}".format(ORDER_DICT[bo])) print() builder = BinaryPayloadBuilder(byteorder=bo, wordorder=wo) strng = "abcdefgh" builder.add_string(strng) builder.add_bits([0, 1, 0, 1, 1, 0, 1, 0]) builder.add_8bit_int(-0x12) builder.add_8bit_uint(0x12) builder.add_16bit_int(-0x5678) builder.add_16bit_uint(0x1234) builder.add_32bit_int(-0x1234) builder.add_32bit_uint(0x12345678) builder.add_16bit_float(12.34) builder.add_16bit_float(-12.34) builder.add_32bit_float(22.34) builder.add_32bit_float(-22.34) builder.add_64bit_int(-0xDEADBEEF) builder.add_64bit_uint(0x12345678DEADBEEF) builder.add_64bit_uint(0x12345678DEADBEEF) builder.add_64bit_float(123.45) builder.add_64bit_float(-123.45) payload = builder.to_registers() print("-" * 60) print("Writing Registers") print("-" * 60) print(payload) print("\n") payload = builder.build() address = 0 # Can write registers # registers = builder.to_registers() # client.write_registers(address, registers, unit=1) # Or can write encoded binary string client.write_registers(address, payload, skip_encode=True, unit=1) # ----------------------------------------------------------------------- # # If you need to decode a collection of registers in a weird layout, the # payload decoder can help you as well. # # Here we demonstrate decoding a random register layout, unpacked it looks # like the following: # # - a 8 byte string 'abcdefgh' # - a 32 bit float 22.34 # - a 16 bit unsigned int 0x1234 # - another 16 bit unsigned int which we will ignore # - an 8 bit int 0x12 # - an 8 bit bitstring [0,1,0,1,1,0,1,0] # ----------------------------------------------------------------------- # address = 0x0 count = len(payload) result = client.read_holding_registers(address, count, unit=1) print("-" * 60) print("Registers") print("-" * 60) print(result.registers) print("\n") decoder = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=bo, wordorder=wo) assert decoder._byteorder == builder._byteorder, \ "Make sure byteorder is consistent between BinaryPayloadBuilder and BinaryPayloadDecoder" assert decoder._wordorder == builder._wordorder, \ "Make sure wordorder is consistent between BinaryPayloadBuilder and BinaryPayloadDecoder" decoded = OrderedDict([ ('string', decoder.decode_string(len(strng))), ('bits', decoder.decode_bits()), ('8int', decoder.decode_8bit_int()), ('8uint', decoder.decode_8bit_uint()), ('16int', decoder.decode_16bit_int()), ('16uint', decoder.decode_16bit_uint()), ('32int', decoder.decode_32bit_int()), ('32uint', decoder.decode_32bit_uint()), ('16float', decoder.decode_16bit_float()), ('16float2', decoder.decode_16bit_float()), ('32float', decoder.decode_32bit_float()), ('32float2', decoder.decode_32bit_float()), ('64int', decoder.decode_64bit_int()), ('64uint', decoder.decode_64bit_uint()), ('ignore', decoder.skip_bytes(8)), ('64float', decoder.decode_64bit_float()), ('64float2', decoder.decode_64bit_float()), ]) print("-" * 60) print("Decoded Data") print("-" * 60) for name, value in iteritems(decoded): print("%s\t" % name, hex(value) if isinstance(value, int) else value) # ----------------------------------------------------------------------- # # close the client # ----------------------------------------------------------------------- # client.close()
from pymodbus.payload import BinaryPayloadDecoder from pymodbus.payload import BinaryPayloadBuilder #---------------------------------------------------------------------------# # configure the service logging #---------------------------------------------------------------------------# import logging logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) #---------------------------------------------------------------------------# # build your payload #---------------------------------------------------------------------------# builder = BinaryPayloadBuilder(endian=Endian.Little) builder.add_string('abcdefgh') builder.add_32bit_float(22.34) builder.add_16bit_uint(0x1234) builder.add_8bit_int(0x12) builder.add_bits([0, 1, 0, 1, 1, 0, 1, 0]) #---------------------------------------------------------------------------# # use that payload in the data store #---------------------------------------------------------------------------# # Here we use the same reference block for each underlying store. #---------------------------------------------------------------------------# block = ModbusSequentialDataBlock(1, builder.to_registers()) store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block) context = ModbusServerContext(slaves=store, single=True)
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 """ try: #apiurl = 'http://192.168.0.216/json/apiV1p1data.php?order=desc&limit=1&outputmode=object' apiurl = 'http://192.168.0.216/api/v1/smartmeter?order=desc&limit=1&json=object' url = urllib.urlopen(apiurl).read() result_temp = json.loads(url) # result is now a dict except: print("Cannot Open Api URL") else: result = result_temp[0] currentimport = float(result['CONSUMPTION_W']) currentexport = float(result['PRODUCTION_W']) forwardactiveenergy = float(result['CONSUMPTION_KWH_HIGH']) + float(result['CONSUMPTION_KWH_LOW']) reverseactiveenergy = float(result['PRODUCTION_KWH_LOW']) + float(result['PRODUCTION_KWH_LOW']) totalcurrent = currentimport - currentexport totalactiveenergy = forwardactiveenergy - reverseactiveenergy log.debug('Current Importing: ' + str(currentimport)) log.debug('Current Exporting: ' + str(currentexport)) log.debug('updating the context') context = a[0] readregister = 0x03 writeregister = 0x10 slave_id = 0x01 address1 = 0x4012 # Flow of the Current RV FV (ASCII) address2 = 0x500A # Current of Flow (FLOAT ABCD) address3 = 0x5012 # Total active power KW Float ABCD) address4 = 0x6000 # Total active energy kWh Float address5 = 0x600c # Forward Active Energy kWH Float address6 = 0x6018 # Reverse Active Energy kWh Float #values1 = context[slave_id].getValues(register, address1, count=1) if currentimport > currentexport: values1 = 'FW' else: values1 = 'RV' # Build payload builder1 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Big) builder1.add_string(values1) payload1 = builder1.to_registers() context[slave_id].setValues(writeregister, address1, payload1) log.debug("new values: " + str(values1)) log.debug("new imp values: " + str(round(currentimport/230,2))) log.debug("new exp values: " + str(round(currentexport/230,2))) #values2 = context[slave_id].getValues(register, address2, count=2) if currentimport > currentexport: values2 = round(currentimport / 230,2) # Calculate A from Kwh assume 230 volts else: values2 = round(currentexport / 230,2) # Calculate A from Kwh, assume 230 volts log.debug("new values: " + str(values2)) # Build payload builder2 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Big) builder2.add_32bit_float(values2) payload2 = builder2.to_registers() context[slave_id].setValues(writeregister, address2, payload2) builder3 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Big) builder3.add_32bit_float(round(totalcurrent/1000,2)) payload3 = builder3.to_registers() context[slave_id].setValues(writeregister, address3, payload3) builder4 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Big) builder4.add_32bit_float(round(totalactiveenergy,2)) payload4 = builder4.to_registers() context[slave_id].setValues(writeregister, address4, payload4) builder5 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Big) builder5.add_32bit_float(round(forwardactiveenergy,2)) payload5 = builder5.to_registers() context[slave_id].setValues(writeregister, address5, payload5) builder6 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Big) builder6.add_32bit_float(round(reverseactiveenergy,2)) payload6 = builder6.to_registers() context[slave_id].setValues(writeregister, address6, payload6) readvalues = context[slave_id].getValues(0x03,0x4000,0x12) log.debug("Values from datastore: " + str(readvalues))
def run_binary_payload_ex(): # ----------------------------------------------------------------------- # # We are going to use a simple client to send our requests # ----------------------------------------------------------------------- # client = ModbusClient('127.0.0.1', port=5440) client.connect() # ----------------------------------------------------------------------- # # If you need to build a complex message to send, you can use the payload # builder to simplify the packing logic. # # Here we demonstrate packing a random payload layout, unpacked it looks # like the following: # # - a 8 byte string 'abcdefgh' # - a 32 bit float 22.34 # - a 16 bit unsigned int 0x1234 # - another 16 bit unsigned int 0x5678 # - an 8 bit int 0x12 # - an 8 bit bitstring [0,1,0,1,1,0,1,0] # - an 32 bit uint 0x12345678 # - an 32 bit signed int -0x1234 # - an 64 bit signed int 0x12345678 # The packing can also be applied to the word (wordorder) and bytes in each # word (byteorder) # The wordorder is applicable only for 32 and 64 bit values # Lets say we need to write a value 0x12345678 to a 32 bit register # The following combinations could be used to write the register # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # # Word Order - Big Byte Order - Big # word1 =0x1234 word2 = 0x5678 # Word Order - Big Byte Order - Little # word1 =0x3412 word2 = 0x7856 # Word Order - Little Byte Order - Big # word1 = 0x5678 word2 = 0x1234 # Word Order - Little Byte Order - Little # word1 =0x7856 word2 = 0x3412 # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # # ----------------------------------------------------------------------- # builder = BinaryPayloadBuilder(byteorder=Endian.Little, wordorder=Endian.Big) builder.add_string('abcdefgh') builder.add_32bit_float(22.34) builder.add_16bit_uint(0x1234) builder.add_16bit_uint(0x5678) builder.add_8bit_int(0x12) builder.add_bits([0, 1, 0, 1, 1, 0, 1, 0]) builder.add_32bit_uint(0x12345678) builder.add_32bit_int(-0x1234) builder.add_64bit_int(0x1234567890ABCDEF) payload = builder.build() address = 0 client.write_registers(address, payload, skip_encode=True, unit=1) # ----------------------------------------------------------------------- # # If you need to decode a collection of registers in a weird layout, the # payload decoder can help you as well. # # Here we demonstrate decoding a random register layout, unpacked it looks # like the following: # # - a 8 byte string 'abcdefgh' # - a 32 bit float 22.34 # - a 16 bit unsigned int 0x1234 # - another 16 bit unsigned int which we will ignore # - an 8 bit int 0x12 # - an 8 bit bitstring [0,1,0,1,1,0,1,0] # ----------------------------------------------------------------------- # address = 0x00 count = len(payload) result = client.read_holding_registers(address, count, unit=1) print("-" * 60) print("Registers") print("-" * 60) print(result.registers) print("\n") decoder = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=Endian.Little, wordorder=Endian.Big) decoded = { 'string': decoder.decode_string(8), 'float': decoder.decode_32bit_float(), '16uint': decoder.decode_16bit_uint(), 'ignored': decoder.skip_bytes(2), '8int': decoder.decode_8bit_int(), 'bits': decoder.decode_bits(), "32uints": decoder.decode_32bit_uint(), "32ints": decoder.decode_32bit_int(), "64ints": decoder.decode_64bit_int(), } print("-" * 60) print("Decoded Data") print("-" * 60) for name, value in iteritems(decoded): print("%s\t" % name, hex(value) if isinstance(value, int) else value) # ----------------------------------------------------------------------- # # close the client # ----------------------------------------------------------------------- # client.close()
class MbVariable: def __init__(self, name, var_type, encoding, scale, offset): self.name = name # do not allow invalid encoding if encoding not in [ "BB", "LB", "BL", "LL", "ABCD", "CDAB", "BADC", "DCBA" ]: logging.error("invalid Encoding:" + encoding + " For " + name) raise KeyError self.encoding = encoding self.type, self.size = compute_size(var_type) self.value = 0 self.bpb = None self.bpd = None self.scale = float(scale) self.offset = float(offset) self.registers = [0] * self.size self.set_builder() self.register = None def __str__(self): return self.name def set_var_register(self, reg): # print("decoding ", self.name, self.type, reg) if self.encoding in ['BB', 'ABCD']: self.bpd = BinaryPayloadDecoder.fromRegisters(reg, byteorder=Endian.Big, wordorder=Endian.Big) elif self.encoding in ['BL', 'CDAB']: self.bpd = BinaryPayloadDecoder.fromRegisters( reg, byteorder=Endian.Big, wordorder=Endian.Little) elif self.encoding in ['LL', 'DCBA']: self.bpd = BinaryPayloadDecoder.fromRegisters( reg, byteorder=Endian.Little, wordorder=Endian.Little) elif self.encoding in ['LB', 'BADC']: self.bpd = BinaryPayloadDecoder.fromRegisters( reg, byteorder=Endian.Little, wordorder=Endian.Big) else: raise KeyError self.register = reg if self.type == "uint16": val = self.bpd.decode_16bit_uint() * self.scale + int(self.offset) elif self.type in ["sint16", "int16"]: val = self.bpd.decode_16bit_int() * self.scale + int(self.offset) elif self.type in ["uint32"]: val = self.bpd.decode_32bit_uint() * self.scale + int(self.offset) elif self.type in ["sint32", "int32"]: val = self.bpd.decode_32bit_int() * self.scale + int(self.offset) elif self.type in ["float32"]: val = self.bpd.decode_32bit_float() * self.scale + int(self.offset) elif self.type == "str": val1 = self.bpd.decode_string(self.size) val = val1.decode() else: logging.error("Unknown data type" + self.type) raise KeyError if math.isnan(val): self.value = 0 else: self.value = val def set_builder(self): if self.encoding in ['BB', 'ABCD']: self.bpb = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Big) elif self.encoding in ['BL', 'CDAB']: self.bpb = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) elif self.encoding in ['LL', 'DCBA']: self.bpb = BinaryPayloadBuilder(byteorder=Endian.Little, wordorder=Endian.Little) elif self.encoding in ['LB', 'BADC']: self.bpb = BinaryPayloadBuilder(byteorder=Endian.Little, wordorder=Endian.Big) else: raise KeyError def set_value(self, value): self.set_builder() self.set_reg_value(value) self.value = value def set_reg_value(self, value): if self.type == "uint16": v_raw = int((float(value) - self.offset) / self.scale) if v_raw < 0: logging.warning("Negative value for " + self.name + ":" + str(v_raw)) v_raw = 0 self.bpb.add_16bit_uint(v_raw) elif self.type == "int16": self.bpb.add_16bit_int( int((float(value) - self.offset) / self.scale)) elif self.type == "uint32": v_raw = int((float(value) - self.offset) / self.scale) if v_raw < 0: logging.warning("Negative value for " + self.name + ":" + str(v_raw)) v_raw = 0 self.bpb.add_32bit_uint(v_raw) elif self.type == "int32": self.bpb.add_32bit_int( int((float(value) - self.offset) / self.scale)) elif self.type == "float32": self.bpb.add_32bit_float( int((float(value) - self.offset) / self.scale)) elif self.type == "str": value_str = " " * (self.size - len(str(value))) + str(value) self.bpb.add_string(value_str) else: logging.error("Unknown format:" + self.type + " for " + self.name) raise ValueError self.registers = self.bpb.to_registers() logging.debug("Updated var register " + str(self.registers))
# ---------------------------------------------------------------------------# # If you need to build a complex message to send, you can use the payload # builder to simplify the packing logic. # # Here we demonstrate packing a random payload layout, unpacked it looks # like the following: # # - a 8 byte string 'abcdefgh' # - a 32 bit float 22.34 # - a 16 bit unsigned int 0x1234 # - an 8 bit int 0x12 # - an 8 bit bitstring [0,1,0,1,1,0,1,0] # ---------------------------------------------------------------------------# builder = BinaryPayloadBuilder(endian=Endian.Little) builder.add_string("abcdefgh") builder.add_32bit_float(22.34) builder.add_16bit_uint(0x1234) builder.add_8bit_int(0x12) builder.add_bits([0, 1, 0, 1, 1, 0, 1, 0]) payload = builder.build() address = 0x01 result = client.write_registers(address, payload, skip_encode=True) # ---------------------------------------------------------------------------# # If you need to decode a collection of registers in a weird layout, the # payload decoder can help you as well. # # Here we demonstrate decoding a random register layout, unpacked it looks # like the following: #
def __write_Registers(self, regPara, value): objectType = regPara['objectType'] address = regPara['regAddr'] slaveUnit = regPara['slaveUnit'] bo = regPara['byteOrder'] wo = regPara['wordOrder'] dataTypeStr = regPara['dataType'] dataType = ''.join( filter(str.isalpha, dataTypeStr )) # vom dataType die Ziffen entfernen z.B. uint16 = uint registerCount = 0 # Anzahl der zu schreibenden Register (Words) try: bits = int(''.join(filter( str.isdigit, dataTypeStr))) # bit-Zahl aus aus dataType z.B. uint16 = 16 except: bits = 16 if dataType.lower() == 'string': registerCount = int( bits / 2 ) # bei string: bits = bytes !! string16 -> 16Byte - 8 registerCount else: registerCount = int(bits / 16) if regPara['factor'] != 1: #self.logger.debug("value {0} divided by: {1}".format(value, regPara['factor'])) value = value * (1 / regPara['factor']) self.logger.debug( "write {0} to {1}.{2}.{3} (address.slaveUnit) dataType:{4}".format( value, objectType, address, slaveUnit, dataTypeStr)) builder = BinaryPayloadBuilder(byteorder=bo, wordorder=wo) if dataType.lower() == 'uint': if bits == 16: builder.add_16bit_uint(int(value)) elif bits == 32: builder.add_32bit_uint(int(value)) elif bits == 64: builder.add_64bit_uint(int(value)) else: self.logger.error( "Number of bits or datatype not supportet : {0}".format( typeStr)) elif dataType.lower() == 'int': if bits == 16: builder.add_16bit_int(int(value)) elif bits == 32: builder.add_32bit_int(int(value)) elif bits == 64: builder.add_64bit_int(int(value)) else: self.logger.error( "Number of bits or datatype not supportet : {0}".format( typeStr)) elif dataType.lower() == 'float': if bits == 32: builder.add_32bit_float(value) if bits == 64: builder.add_64bit_float(value) else: self.logger.error( "Number of bits or datatype not supportet : {0}".format( typeStr)) elif dataType.lower() == 'string': builder.add_string(value) elif dataType.lower() == 'bit': if objectType == 'Coil' or objectType == 'DiscreteInput': if not type(value) == type(True): # test is boolean self.logger.error( "Value is not boolean: {0}".format(value)) return else: if set(bitstr).issubset({ '0', '1' }) and bool(bitstr): # test is bit-string '00110101' builder.add_bits(value) else: self.logger.error( "Value is not a bitstring: {0}".format(value)) else: self.logger.error( "Number of bits or datatype not supportet : {0}".format( typeStr)) return None if objectType == 'Coil': result = self._Mclient.write_coil(address, value, unit=slaveUnit) elif objectType == 'HoldingRegister': registers = builder.to_registers() result = self._Mclient.write_registers(address, registers, unit=slaveUnit) elif objectType == 'DiscreteInput': self.logger.warning( "this object type cannot be written {0}:{1} slaveUnit:{2}". format(objectType, address, slaveUnit)) return elif objectType == 'InputRegister': self.logger.warning( "this object type cannot be written {0}:{1} slaveUnit:{2}". format(objectType, address, slaveUnit)) return else: return if result.isError(): self.logger.error( "write error: {0} {1}.{2}.{3} (address.slaveUnit)".format( result, objectType, address, slaveUnit)) return None if 'write_dt' in regPara: regPara['last_write_dt'] = regPara['write_dt'] regPara['write_dt'] = datetime.now() else: regPara.update({'write_dt': datetime.now()}) if 'write_value' in regPara: regPara['last_write_value'] = regPara['write_value'] regPara['write_value'] = value else: regPara.update({'write_value': value})