def __init__(self, string=""): """ Initialize the exception :param string: The message to append to the error """ message = "[Timeout] %s" % string ModbusException.__init__(self, message)
async def test_service_cover_move(hass, mock_modbus, mock_ha): """Run test for service homeassistant.update_entity.""" mock_modbus.read_holding_registers.return_value = ReadResult([0x01]) await hass.services.async_call("cover", "open_cover", {"entity_id": ENTITY_ID}, blocking=True) assert hass.states.get(ENTITY_ID).state == STATE_OPEN mock_modbus.read_holding_registers.return_value = ReadResult([0x00]) await hass.services.async_call("cover", "close_cover", {"entity_id": ENTITY_ID}, blocking=True) assert hass.states.get(ENTITY_ID).state == STATE_CLOSED mock_modbus.reset() mock_modbus.read_holding_registers.side_effect = ModbusException( "fail write_") await hass.services.async_call("cover", "close_cover", {"entity_id": ENTITY_ID}, blocking=True) assert mock_modbus.read_holding_registers.called assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE mock_modbus.read_coils.side_effect = ModbusException("fail write_") await hass.services.async_call("cover", "close_cover", {"entity_id": ENTITY_ID2}, blocking=True) assert hass.states.get(ENTITY_ID2).state == STATE_UNAVAILABLE
async def test_service_cover_move(hass, mock_pymodbus): """Run test for service homeassistant.update_entity.""" ENTITY_ID2 = f"{ENTITY_ID}2" config = { CONF_COVERS: [ { CONF_NAME: COVER_NAME, CONF_ADDRESS: 1234, CONF_STATUS_REGISTER_TYPE: CALL_TYPE_REGISTER_HOLDING, CONF_SCAN_INTERVAL: 0, }, { CONF_NAME: f"{COVER_NAME}2", CONF_INPUT_TYPE: CALL_TYPE_COIL, CONF_ADDRESS: 1234, CONF_SCAN_INTERVAL: 0, }, ] } mock_pymodbus.read_holding_registers.return_value = ReadResult([0x01]) await prepare_service_update( hass, config, ) await hass.services.async_call( "cover", "open_cover", {"entity_id": ENTITY_ID}, blocking=True ) assert hass.states.get(ENTITY_ID).state == STATE_OPEN mock_pymodbus.read_holding_registers.return_value = ReadResult([0x00]) await hass.services.async_call( "cover", "close_cover", {"entity_id": ENTITY_ID}, blocking=True ) assert hass.states.get(ENTITY_ID).state == STATE_CLOSED mock_pymodbus.reset() mock_pymodbus.read_holding_registers.side_effect = ModbusException("fail write_") await hass.services.async_call( "cover", "close_cover", {"entity_id": ENTITY_ID}, blocking=True ) assert mock_pymodbus.read_holding_registers.called assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE mock_pymodbus.read_coils.side_effect = ModbusException("fail write_") await hass.services.async_call( "cover", "close_cover", {"entity_id": ENTITY_ID2}, blocking=True ) assert hass.states.get(ENTITY_ID2).state == STATE_UNAVAILABLE
def sync_write(self, value: int | float | str, obj: ModbusObj) -> None: """Write value to Modbus object. Args: value: Value to write obj: Object instance. """ if obj.func_write is None: raise ModbusException("Object cannot be overwritten") if isinstance(value, str): raise NotImplementedError( "Modbus expected numbers to write. Got `str`.") payload = self._build_payload(value=value, obj=obj) if obj.func_write is ModbusWriteFunc.WRITE_REGISTER and isinstance( payload, list): # FIXME: hotfix assert len(payload) == 1 payload = payload[0] # type: ignore request = self.write_funcs[obj.func_write]( obj.address, payload, unit=self._device_obj.property_list.rtu.unit, # type: ignore ) if request.isError(): raise ModbusIOException("0x80") # todo: resp.string self._LOG.debug("Successfully write", extra={ "object": obj, "value": value })
async def test_pymodbus_connect_fail(hass, caplog, mock_pymodbus): """Run test for failing pymodbus constructor.""" config = { DOMAIN: [{ CONF_TYPE: "tcp", CONF_HOST: "modbusTestHost", CONF_PORT: 5501, }] } caplog.set_level(logging.ERROR) mock_pymodbus.connect.side_effect = ModbusException("test connect fail") mock_pymodbus.close.side_effect = ModbusException("test connect fail") assert await async_setup_component(hass, DOMAIN, config) is True await hass.async_block_till_done() assert len(caplog.records) == 1 assert caplog.records[0].levelname == "ERROR"
async def test_pb_service_write(hass, do_write, caplog, mock_modbus): """Run test for service write_register.""" func_name = { CALL_TYPE_WRITE_COIL: mock_modbus.write_coil, CALL_TYPE_WRITE_COILS: mock_modbus.write_coils, CALL_TYPE_WRITE_REGISTER: mock_modbus.write_register, CALL_TYPE_WRITE_REGISTERS: mock_modbus.write_registers, } data = { ATTR_HUB: TEST_MODBUS_NAME, ATTR_UNIT: 17, ATTR_ADDRESS: 16, do_write[DATA]: do_write[VALUE], } await hass.services.async_call(DOMAIN, do_write[SERVICE], data, blocking=True) assert func_name[do_write[FUNC]].called assert func_name[do_write[FUNC]].call_args[0] == ( data[ATTR_ADDRESS], data[do_write[DATA]], ) mock_modbus.reset_mock() for return_value in [ ExceptionResponse(0x06), IllegalFunctionRequest(0x06), ModbusException("fail write_"), ]: caplog.set_level(logging.DEBUG) func_name[do_write[FUNC]].return_value = return_value await hass.services.async_call(DOMAIN, do_write[SERVICE], data, blocking=True) assert func_name[do_write[FUNC]].called assert caplog.messages[-1].startswith("Pymodbus:") mock_modbus.reset_mock()
def _helper(self, data): ''' This factory is used to generate the correct response object from a valid response packet. This decodes from a list of the currently implemented request types. :param data: The response packet to decode :returns: The decoded request or an exception response object ''' function_code = ord(data[0]) _logger.debug("Factory Response[%d]" % function_code) response = self.__lookup.get(function_code, lambda: None)() if function_code > 0x80: code = function_code & 0x7f # strip error portion response = ExceptionResponse(code, ecode.IllegalFunction) if not response: raise ModbusException("Unknown response %d" % function_code) response.decode(data[1:]) if hasattr(response, 'sub_function_code'): lookup = self.__sub_lookup.get(response.function_code, {}) subtype = lookup.get(response.sub_function_code, None) if subtype: response.__class__ = subtype return response
async def test_service_cover_move(opp, mock_pymodbus): """Run test for service openpeerpower.update_entity.""" entity_id = "cover.test" entity_id2 = "cover.test2" config = { CONF_COVERS: [ { CONF_NAME: "test", CONF_REGISTER: 1234, CONF_STATUS_REGISTER_TYPE: CALL_TYPE_REGISTER_HOLDING, }, { CONF_NAME: "test2", CALL_TYPE_COIL: 1234, }, ] } mock_pymodbus.read_holding_registers.return_value = ReadResult([0x01]) await prepare_service_update( opp, config, ) await opp.services.async_call("cover", "open_cover", {"entity_id": entity_id}, blocking=True) assert opp.states.get(entity_id).state == STATE_OPEN mock_pymodbus.read_holding_registers.return_value = ReadResult([0x00]) await opp.services.async_call("cover", "close_cover", {"entity_id": entity_id}, blocking=True) assert opp.states.get(entity_id).state == STATE_CLOSED mock_pymodbus.read_holding_registers.side_effect = ModbusException( "fail write_") await opp.services.async_call("cover", "close_cover", {"entity_id": entity_id}, blocking=True) assert opp.states.get(entity_id).state == STATE_UNAVAILABLE mock_pymodbus.read_coils.side_effect = ModbusException("fail write_") await opp.services.async_call("cover", "close_cover", {"entity_id": entity_id2}, blocking=True) assert opp.states.get(entity_id2).state == STATE_UNAVAILABLE
async def mock_pymodbus_exception(hass, do_exception, mock_modbus): """Trigger update call with time_changed event.""" if do_exception: exc = ModbusException("fail read_coils") mock_modbus.read_coils.side_effect = exc mock_modbus.read_discrete_inputs.side_effect = exc mock_modbus.read_input_registers.side_effect = exc mock_modbus.read_holding_registers.side_effect = exc
def _write(self, address, value, **kwargs): """Perform the write, enforcing Defaults.Timeout around the entire transaction. Normally returns None, but may raise a ModbusException or a PlcOffline if there are communications problems. Use a supplied 'unit' ID, or the one specified/deduced at construction. """ self.client.timeout = True if not self.client.connect(): raise PlcOffline( "Modbus Write to PLC %s/%6d failed: Offline; Connect failure" % (self.description, address)) # Use address to deduce Holding Register or Coil (the only writable # entities); Statuses and Input Registers result in a pymodbus # ParameterException multi = hasattr(value, '__iter__') writer = None if 400001 <= address <= 465536: # 400001-465536: Holding Registers writer = (WriteMultipleRegistersRequest if multi or self.multi else WriteSingleRegisterRequest) address -= 400001 elif 40001 <= address <= 99999: # 40001-99999: Holding Registers writer = (WriteMultipleRegistersRequest if multi or self.multi else WriteSingleRegisterRequest) address -= 40001 elif 1 <= address <= 9999: # 1-9999: Coils writer = ( WriteMultipleCoilsRequest if multi # *don't* force multi else WriteSingleCoilRequest) address -= 1 else: # 100001-165536: Statuses (not writable) # 300001-365536: Input Registers (not writable) # 10001-19999: Statuses (not writable) # 30001-39999: Input Registers (not writable) pass if not writer: raise ParameterException("Invalid Modbus address for write: %d" % (address)) if writer is WriteMultipleRegistersRequest: # Overcome bug in 1.2.0/1.3.0 in handling single requests. Also reifies generators. value = list(value) if multi else [value] unit = kwargs.pop('unit', self.unit) result = self.client.execute( writer(address, value, unit=unit, **kwargs)) if isinstance(result, ExceptionResponse): raise ModbusException(str(result)) assert isinstance( result, ModbusResponse), "Unexpected non-ModbusResponse: %r" % result
async def test_light_service_turn(hass, caplog, mock_modbus): """Run test for service turn_on/turn_off.""" assert MODBUS_DOMAIN in hass.config.components assert hass.states.get(ENTITY_ID).state == STATE_OFF await hass.services.async_call("light", "turn_on", service_data={"entity_id": ENTITY_ID}) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID).state == STATE_ON await hass.services.async_call("light", "turn_off", service_data={"entity_id": ENTITY_ID}) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID).state == STATE_OFF mock_modbus.read_holding_registers.return_value = ReadResult([0x01]) assert hass.states.get(ENTITY_ID2).state == STATE_OFF await hass.services.async_call("light", "turn_on", service_data={"entity_id": ENTITY_ID2}) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID2).state == STATE_ON mock_modbus.read_holding_registers.return_value = ReadResult([0x00]) await hass.services.async_call("light", "turn_off", service_data={"entity_id": ENTITY_ID2}) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID2).state == STATE_OFF mock_modbus.write_register.side_effect = ModbusException("fail write_") await hass.services.async_call("light", "turn_on", service_data={"entity_id": ENTITY_ID2}) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID2).state == STATE_UNAVAILABLE mock_modbus.write_coil.side_effect = ModbusException("fail write_") await hass.services.async_call("light", "turn_off", service_data={"entity_id": ENTITY_ID}) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE
async def test_pymodbus_close_fail(hass, caplog, mock_pymodbus): """Run test for failing pymodbus close.""" config = { DOMAIN: [{ CONF_TYPE: "tcp", CONF_HOST: TEST_HOST, CONF_PORT: 5501, }] } caplog.set_level(logging.ERROR) mock_pymodbus.connect.return_value = True mock_pymodbus.close.side_effect = ModbusException("close fail") assert await async_setup_component(hass, DOMAIN, config) is True await hass.async_block_till_done()
def __execute(self, command, *args): ''' Run the supplied command against the currently instantiated client with the supplied arguments. This will make sure to correctly handle resulting errors. :param command: The command to execute against the context :param *args: The arguments for the given command :returns: The result of the operation unless -1 which throws ''' result = command(self.client, *args) if result == -1: message = LIB.modbus_strerror(compiler.errno) raise ModbusException(compiler.string(message)) return result
async def test_pymodbus_connect_fail(hass, caplog, mock_pymodbus): """Run test for failing pymodbus constructor.""" config = { DOMAIN: [{ CONF_NAME: TEST_MODBUS_NAME, CONF_TYPE: TCP, CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, }] } caplog.set_level(logging.WARNING) ExceptionMessage = "test connect exception" mock_pymodbus.connect.side_effect = ModbusException(ExceptionMessage) assert await async_setup_component(hass, DOMAIN, config) is False assert ExceptionMessage in caplog.text
async def test_pymodbus_constructor_fail(hass, caplog): """Run test for failing pymodbus constructor.""" config = { DOMAIN: [{ CONF_TYPE: "tcp", CONF_HOST: "modbusTestHost", CONF_PORT: 5501, }] } with mock.patch("homeassistant.components.modbus.modbus.ModbusTcpClient" ) as mock_pb: caplog.set_level(logging.ERROR) mock_pb.side_effect = ModbusException("test no class") assert await async_setup_component(hass, DOMAIN, config) is True await hass.async_block_till_done() assert len(caplog.records) == 1 assert caplog.records[0].levelname == "ERROR" assert mock_pb.called
async def test_pymodbus_constructor_fail(hass, caplog): """Run test for failing pymodbus constructor.""" config = { DOMAIN: [{ CONF_NAME: TEST_MODBUS_NAME, CONF_TYPE: TCP, CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, }] } with mock.patch("homeassistant.components.modbus.modbus.ModbusTcpClient", autospec=True) as mock_pb: caplog.set_level(logging.ERROR) mock_pb.side_effect = ModbusException("test no class") assert await async_setup_component(hass, DOMAIN, config) is False await hass.async_block_till_done() message = f"Pymodbus: {TEST_MODBUS_NAME}: Modbus Error: test" assert caplog.messages[0].startswith(message) assert caplog.records[0].levelname == "ERROR" assert mock_pb.called
async def test_pb_service_write_coil(hass, caplog, mock_modbus): """Run test for service write_coil.""" # Pymodbus write single, response OK. data = { ATTR_HUB: TEST_MODBUS_NAME, ATTR_UNIT: 17, ATTR_ADDRESS: 16, ATTR_STATE: False, } await hass.services.async_call(DOMAIN, SERVICE_WRITE_COIL, data, blocking=True) assert mock_modbus.write_coil.called assert mock_modbus.write_coil.call_args[0] == ( data[ATTR_ADDRESS], data[ATTR_STATE], ) mock_modbus.reset_mock() # Pymodbus write single, response error or exception caplog.set_level(logging.DEBUG) mock_modbus.write_coil.return_value = ExceptionResponse(0x06) await hass.services.async_call(DOMAIN, SERVICE_WRITE_COIL, data, blocking=True) assert mock_modbus.write_coil.called assert caplog.messages[-1].startswith("Pymodbus:") mock_modbus.reset_mock() mock_modbus.write_coil.return_value = IllegalFunctionRequest(0x06) await hass.services.async_call(DOMAIN, SERVICE_WRITE_COIL, data, blocking=True) assert mock_modbus.write_coil.called assert caplog.messages[-1].startswith("Pymodbus:") mock_modbus.reset_mock() mock_modbus.write_coil.side_effect = ModbusException("fail write_") await hass.services.async_call(DOMAIN, SERVICE_WRITE_COIL, data, blocking=True) assert mock_modbus.write_coil.called assert caplog.messages[-1].startswith("Pymodbus:") mock_modbus.reset_mock() # Pymodbus write multiple, response OK. data[ATTR_STATE] = [True, False, True] await hass.services.async_call(DOMAIN, SERVICE_WRITE_COIL, data, blocking=True) assert mock_modbus.write_coils.called assert mock_modbus.write_coils.call_args[0] == ( data[ATTR_ADDRESS], data[ATTR_STATE], ) mock_modbus.reset_mock() # Pymodbus write multiple, response error or exception mock_modbus.write_coils.return_value = ExceptionResponse(0x06) await hass.services.async_call(DOMAIN, SERVICE_WRITE_COIL, data, blocking=True) assert mock_modbus.write_coils.called assert caplog.messages[-1].startswith("Pymodbus:") mock_modbus.reset_mock() mock_modbus.write_coils.return_value = IllegalFunctionRequest(0x06) await hass.services.async_call(DOMAIN, SERVICE_WRITE_COIL, data, blocking=True) assert mock_modbus.write_coils.called assert caplog.messages[-1].startswith("Pymodbus:") mock_modbus.reset_mock() mock_modbus.write_coils.side_effect = ModbusException("fail write_") await hass.services.async_call(DOMAIN, SERVICE_WRITE_COIL, data, blocking=True) assert mock_modbus.write_coils.called assert caplog.messages[-1].startswith("Pymodbus:") mock_modbus.reset_mock()
await hass.async_block_till_done() now = now + timedelta(seconds=DEFAULT_SCAN_INTERVAL + 60) with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now): async_fire_time_changed(hass, now) await hass.async_block_till_done() @pytest.mark.parametrize( "do_return,do_exception,do_expect", [ [ReadResult([7]), None, "7"], [IllegalFunctionRequest(0x99), None, STATE_UNAVAILABLE], [ExceptionResponse(0x99), None, STATE_UNAVAILABLE], [ReadResult([7]), ModbusException("fail read_"), STATE_UNAVAILABLE], ], ) @pytest.mark.parametrize( "do_type", [CALL_TYPE_REGISTER_HOLDING, CALL_TYPE_REGISTER_INPUT], ) async def test_pb_read_value(hass, caplog, do_type, do_return, do_exception, do_expect, mock_pymodbus): """Run test for different read.""" # the purpose of this test is to test the special # return values from pymodbus: # ExceptionResponse, IllegalResponse # and exceptions. # We "hijiack" binary_sensor and sensor in order
def _raise_exception(_): raise ModbusException('something')
[0xFE], STATE_ON, ), ( [0xFF], STATE_ON, ), ( [0x01], STATE_OFF, ), ( [0x20], STATE_ON, ), (ModbusException("Modbus Exception"), STATE_UNAVAILABLE), (ConnectionException("Modbus Exception"), STATE_UNAVAILABLE), ], ) async def test_register_switch(hass, regs, expected): """Run test for given config.""" switch_name = "modbus_test_switch" state = await base_test( hass, { CONF_NAME: switch_name, CONF_INPUT_TYPE: CALL_TYPE_REGISTER_INPUT, CONF_ADDRESS: 1234, CONF_SLAVE: 1, CONF_COMMAND_BIT_NUMBER: 5, },
"do_return", [ { VALUE: ReadResult([0x0001]), DATA: "" }, { VALUE: ExceptionResponse(0x06), DATA: "Pymodbus:" }, { VALUE: IllegalFunctionRequest(0x06), DATA: "Pymodbus:" }, { VALUE: ModbusException("fail write_"), DATA: "Pymodbus:" }, ], ) async def test_pb_service_write(hass, do_write, do_return, caplog, mock_modbus_with_pymodbus): """Run test for service write_register.""" func_name = { CALL_TYPE_WRITE_COIL: mock_modbus_with_pymodbus.write_coil, CALL_TYPE_WRITE_COILS: mock_modbus_with_pymodbus.write_coils, CALL_TYPE_WRITE_REGISTER: mock_modbus_with_pymodbus.write_register, CALL_TYPE_WRITE_REGISTERS: mock_modbus_with_pymodbus.write_registers, }
with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now): assert await async_setup_component(hass, DOMAIN, config) is True await hass.async_block_till_done() now = now + timedelta(seconds=DEFAULT_SCAN_INTERVAL + 60) with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now): async_fire_time_changed(hass, now) await hass.async_block_till_done() @pytest.mark.parametrize( "do_return,do_exception,do_expect", [ [ReadResult([7]), None, "7"], [IllegalFunctionRequest(0x99), None, STATE_UNAVAILABLE], [ExceptionResponse(0x99), None, STATE_UNAVAILABLE], [ReadResult([7]), ModbusException("fail read_"), STATE_UNAVAILABLE], ], ) @pytest.mark.parametrize( "do_type", [CALL_TYPE_REGISTER_HOLDING, CALL_TYPE_REGISTER_INPUT], ) async def test_pb_read_value( hass, caplog, do_type, do_return, do_exception, do_expect, mock_pymodbus ): """Run test for different read.""" # the purpose of this test is to test the special # return values from pymodbus: # ExceptionResponse, IllegalResponse # and exceptions.
}, { DATA: ATTR_STATE, VALUE: [True, False, True], SERVICE: SERVICE_WRITE_COIL, FUNC: CALL_TYPE_WRITE_COILS, }, ], ) @pytest.mark.parametrize( "do_return", [ {VALUE: ReadResult([0x0001]), DATA: ""}, {VALUE: ExceptionResponse(0x06), DATA: "Pymodbus:"}, {VALUE: IllegalFunctionRequest(0x06), DATA: "Pymodbus:"}, {VALUE: ModbusException("fail write_"), DATA: "Pymodbus:"}, ], ) async def test_pb_service_write( hass, do_write, do_return, caplog, mock_modbus_with_pymodbus ): """Run test for service write_register.""" func_name = { CALL_TYPE_WRITE_COIL: mock_modbus_with_pymodbus.write_coil, CALL_TYPE_WRITE_COILS: mock_modbus_with_pymodbus.write_coils, CALL_TYPE_WRITE_REGISTER: mock_modbus_with_pymodbus.write_register, CALL_TYPE_WRITE_REGISTERS: mock_modbus_with_pymodbus.write_registers, } data = {
async def test_switch_service_turn(hass, caplog, mock_pymodbus): """Run test for service turn_on/turn_off.""" ENTITY_ID2 = f"{SWITCH_DOMAIN}.{SWITCH_NAME}2" config = { MODBUS_DOMAIN: { CONF_TYPE: "tcp", CONF_HOST: "modbusTestHost", CONF_PORT: 5501, CONF_SWITCHES: [ { CONF_NAME: SWITCH_NAME, CONF_ADDRESS: 17, CONF_WRITE_TYPE: CALL_TYPE_REGISTER_HOLDING, }, { CONF_NAME: f"{SWITCH_NAME}2", CONF_ADDRESS: 17, CONF_WRITE_TYPE: CALL_TYPE_REGISTER_HOLDING, CONF_VERIFY: {}, }, ], }, } assert await async_setup_component(hass, MODBUS_DOMAIN, config) is True await hass.async_block_till_done() assert MODBUS_DOMAIN in hass.config.components assert hass.states.get(ENTITY_ID).state == STATE_OFF await hass.services.async_call( "switch", "turn_on", service_data={"entity_id": ENTITY_ID} ) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID).state == STATE_ON await hass.services.async_call( "switch", "turn_off", service_data={"entity_id": ENTITY_ID} ) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID).state == STATE_OFF mock_pymodbus.read_holding_registers.return_value = ReadResult([0x01]) assert hass.states.get(ENTITY_ID2).state == STATE_OFF await hass.services.async_call( "switch", "turn_on", service_data={"entity_id": ENTITY_ID2} ) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID2).state == STATE_ON mock_pymodbus.read_holding_registers.return_value = ReadResult([0x00]) await hass.services.async_call( "switch", "turn_off", service_data={"entity_id": ENTITY_ID2} ) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID2).state == STATE_OFF mock_pymodbus.write_register.side_effect = ModbusException("fail write_") await hass.services.async_call( "switch", "turn_on", service_data={"entity_id": ENTITY_ID2} ) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID2).state == STATE_UNAVAILABLE mock_pymodbus.write_coil.side_effect = ModbusException("fail write_") await hass.services.async_call( "switch", "turn_off", service_data={"entity_id": ENTITY_ID} ) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE
def _read(self, address, count=1, **kwargs): """Perform the read, enforcing Defaults.Timeout around the entire transaction. Returns the result bit(s)/register(s), or raises an Exception; probably a ModbusException or a PlcOffline for communications errors, but could be some other type of Exception. Use a supplied 'unit' ID, or the one specified/deduced at construction. """ self.client.timeout = True if not self.client.connect(): raise PlcOffline( "Modbus Read of PLC %s/%6d failed: Offline; Connect failure" % (self.description, address)) # Use address to deduce Holding/Input Register or Coil/Status. reader = None xformed = address if 400001 <= address <= 465536: reader = ReadHoldingRegistersRequest xformed -= 400001 elif 300001 <= address <= 365536: reader = ReadInputRegistersRequest xformed -= 300001 elif 100001 <= address <= 165536: reader = ReadDiscreteInputsRequest xformed -= 100001 elif 40001 <= address <= 99999: reader = ReadHoldingRegistersRequest xformed -= 40001 elif 30001 <= address <= 39999: reader = ReadInputRegistersRequest xformed -= 30001 elif 10001 <= address <= 19999: reader = ReadDiscreteInputsRequest xformed -= 10001 elif 1 <= address <= 9999: reader = ReadCoilsRequest xformed -= 1 else: # Invalid address pass if not reader: raise ParameterException("Invalid Modbus address for read: %d" % (address)) unit = kwargs.pop('unit', self.unit) request = reader(xformed, count, unit=unit, **kwargs) log.debug("%s/%6d-%6d transformed to %s", self.description, address, address + count - 1, request) result = self.client.execute(request) if isinstance(result, ExceptionResponse): # The remote PLC returned a response indicating it encountered an # error processing the request. Convert it to raise a ModbusException. raise ModbusException(str(result)) assert isinstance( result, ModbusResponse), "Unexpected non-ModbusResponse: %r" % result # The result may contain .bits or .registers, 1 or more values values = result.bits if hasattr(result, 'bits') else result.registers return values if len(values) > 1 else values[0]
async def test_light_service_turn(hass, caplog, mock_pymodbus): """Run test for service turn_on/turn_off.""" config = { MODBUS_DOMAIN: { CONF_TYPE: TCP, CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, CONF_LIGHTS: [ { CONF_NAME: TEST_ENTITY_NAME, CONF_ADDRESS: 17, CONF_WRITE_TYPE: CALL_TYPE_REGISTER_HOLDING, CONF_SCAN_INTERVAL: 0, }, { CONF_NAME: f"{TEST_ENTITY_NAME}2", CONF_ADDRESS: 18, CONF_WRITE_TYPE: CALL_TYPE_REGISTER_HOLDING, CONF_SCAN_INTERVAL: 0, CONF_VERIFY: {}, }, ], }, } assert await async_setup_component(hass, MODBUS_DOMAIN, config) is True await hass.async_block_till_done() assert MODBUS_DOMAIN in hass.config.components assert hass.states.get(ENTITY_ID).state == STATE_OFF await hass.services.async_call("light", "turn_on", service_data={"entity_id": ENTITY_ID}) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID).state == STATE_ON await hass.services.async_call("light", "turn_off", service_data={"entity_id": ENTITY_ID}) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID).state == STATE_OFF mock_pymodbus.read_holding_registers.return_value = ReadResult([0x01]) assert hass.states.get(ENTITY_ID2).state == STATE_OFF await hass.services.async_call("light", "turn_on", service_data={"entity_id": ENTITY_ID2}) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID2).state == STATE_ON mock_pymodbus.read_holding_registers.return_value = ReadResult([0x00]) await hass.services.async_call("light", "turn_off", service_data={"entity_id": ENTITY_ID2}) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID2).state == STATE_OFF mock_pymodbus.write_register.side_effect = ModbusException("fail write_") await hass.services.async_call("light", "turn_on", service_data={"entity_id": ENTITY_ID2}) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID2).state == STATE_UNAVAILABLE mock_pymodbus.write_coil.side_effect = ModbusException("fail write_") await hass.services.async_call("light", "turn_off", service_data={"entity_id": ENTITY_ID}) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE
def register_value(self, a_register_index, a_register_length, a_slave_address): """ Returns a given register value @a_register_length: 1 register is 16 bits (2 bytes = 1 word) """ assert self.is_connected(), 'register_value->device is not connected' assert isinstance(a_register_index, int), 'register_value->Slave address is not an int' assert self.valid_slave_address( a_slave_address ), 'register_value->Slave address is not valid:' + str(a_slave_address) #self._logger.debug('register_value-> _substract_one_to_register_index:%s' % (self._substract_one_to_register_index)) if self._substract_one_to_register_index: l_register_index = a_register_index - 1 l_register_index_s_debug = str(a_register_index) + '-1' else: l_register_index = a_register_index l_register_index_s_debug = str(l_register_index) l_retries_count = 0 while l_retries_count < self.MAX_MODBUS_REGISTER_RETRIES_COUNT: try: #Starting add, num of reg to read, slave unit. self._logger.debug( 'register_value-> index:{} length:{} unit:{} _substract_one_to_register_index:{}' .format(l_register_index, a_register_length, a_slave_address, self._substract_one_to_register_index)) l_result = self._modbus_client.read_holding_registers( l_register_index, a_register_length, unit=a_slave_address) # Average current if l_result is not None: if (hasattr(l_result, 'function_code') and l_result.function_code < 0xFFFFFFFF): self._logger.debug( "register_value-> read register index:%s (%s) length:%s slave_address:%s" % (l_register_index, l_register_index_s_debug, a_register_length, a_slave_address)) #self._logger.debug(l_result) #self._logger.debug("register_value->register 0 value:%s" % l_result.getRegister(1)) #self._logger.debug("register_value-> 0 type:%s" % type(l_result.getRegister(0))) #self._logger.debug(l_result._str_()) else: self._logger.error( "register_value-> returned code is invalid: {}". format(l_result)) else: l_msg = "register_value-> No register received, l_result is None" self._logger.error(l_msg) raise ModbusException(l_msg) if not hasattr(l_result, 'registers'): l_msg = 'register_value-> read register has no registers attribute, slave:{} reading register:{} length:{}'.format( a_slave_address, l_register_index, a_register_length) self._logger.error(l_msg) raise ModbusException(l_msg) return l_result except KeyboardInterrupt: self._logger.exception( "register_value-> Keyboard interruption") except ModbusException as l_e: l_retries_count += 1 if l_retries_count >= self.MAX_MODBUS_REGISTER_RETRIES_COUNT: self._logger.error( 'register_value-> error with ModbusException not retrying but raising' ) raise l_e else: self._logger.error( 'register_value-> error with ModbusException retrying {}' .format(l_retries_count)) self.disconnect time.sleep(0.2) self.connect except Exception as l_e: self._logger.exception( "register_value-> Exception occured, msg:%s" % l_e) raise l_e
async def test_light_service_turn(opp, caplog, mock_pymodbus): """Run test for service turn_on/turn_off.""" entity_id1 = f"{LIGHT_DOMAIN}.light1" entity_id2 = f"{LIGHT_DOMAIN}.light2" config = { MODBUS_DOMAIN: { CONF_TYPE: "tcp", CONF_HOST: "modbusTestHost", CONF_PORT: 5501, CONF_LIGHTS: [ { CONF_NAME: "light1", CONF_ADDRESS: 17, CONF_WRITE_TYPE: CALL_TYPE_REGISTER_HOLDING, }, { CONF_NAME: "light2", CONF_ADDRESS: 17, CONF_WRITE_TYPE: CALL_TYPE_REGISTER_HOLDING, CONF_VERIFY: {}, }, ], }, } assert await async_setup_component(opp, MODBUS_DOMAIN, config) is True await opp.async_block_till_done() assert MODBUS_DOMAIN in opp.config.components assert opp.states.get(entity_id1).state == STATE_OFF await opp.services.async_call("light", "turn_on", service_data={"entity_id": entity_id1}) await opp.async_block_till_done() assert opp.states.get(entity_id1).state == STATE_ON await opp.services.async_call("light", "turn_off", service_data={"entity_id": entity_id1}) await opp.async_block_till_done() assert opp.states.get(entity_id1).state == STATE_OFF mock_pymodbus.read_holding_registers.return_value = ReadResult([0x01]) assert opp.states.get(entity_id2).state == STATE_OFF await opp.services.async_call("light", "turn_on", service_data={"entity_id": entity_id2}) await opp.async_block_till_done() assert opp.states.get(entity_id2).state == STATE_ON mock_pymodbus.read_holding_registers.return_value = ReadResult([0x00]) await opp.services.async_call("light", "turn_off", service_data={"entity_id": entity_id2}) await opp.async_block_till_done() assert opp.states.get(entity_id2).state == STATE_OFF mock_pymodbus.write_register.side_effect = ModbusException("fail write_") await opp.services.async_call("light", "turn_on", service_data={"entity_id": entity_id2}) await opp.async_block_till_done() assert opp.states.get(entity_id2).state == STATE_UNAVAILABLE mock_pymodbus.write_coil.side_effect = ModbusException("fail write_") await opp.services.async_call("light", "turn_off", service_data={"entity_id": entity_id1}) await opp.async_block_till_done() assert opp.states.get(entity_id1).state == STATE_UNAVAILABLE
async def base_test( hass, config_device, device_name, entity_domain, array_name_discovery, array_name_old_config, register_words, expected, method_discovery=False, config_modbus=None, scan_interval=None, expect_init_to_fail=False, expect_setup_to_fail=False, ): """Run test on device for given config.""" if config_modbus is None: config_modbus = { DOMAIN: { CONF_NAME: DEFAULT_HUB, CONF_TYPE: "tcp", CONF_HOST: "modbusTest", CONF_PORT: 5001, }, } mock_sync = mock.MagicMock() with mock.patch( "homeassistant.components.modbus.modbus.ModbusTcpClient", autospec=True, return_value=mock_sync, ): # Setup inputs for the sensor if register_words is None: mock_sync.read_coils.side_effect = ModbusException( "fail read_coils") mock_sync.read_discrete_inputs.side_effect = ModbusException( "fail read_coils") mock_sync.read_input_registers.side_effect = ModbusException( "fail read_coils") mock_sync.read_holding_registers.side_effect = ModbusException( "fail read_coils") else: read_result = ReadResult(register_words) mock_sync.read_coils.return_value = read_result mock_sync.read_discrete_inputs.return_value = read_result mock_sync.read_input_registers.return_value = read_result mock_sync.read_holding_registers.return_value = read_result # mock timer and add old/new config now = dt_util.utcnow() with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now): if method_discovery and config_device is not None: # setup modbus which in turn does setup for the devices config_modbus[DOMAIN].update( {array_name_discovery: [{ **config_device }]}) config_device = None assert (await async_setup_component(hass, DOMAIN, config_modbus) is not expect_setup_to_fail) await hass.async_block_till_done() # setup platform old style if config_device is not None: config_device = { entity_domain: { CONF_PLATFORM: DOMAIN, array_name_old_config: [{ **config_device, }], } } if scan_interval is not None: config_device[entity_domain][ CONF_SCAN_INTERVAL] = scan_interval assert await async_setup_component(hass, entity_domain, config_device) await hass.async_block_till_done() assert (DOMAIN in hass.config.components) is not expect_setup_to_fail if config_device is not None: entity_id = f"{entity_domain}.{device_name}" device = hass.states.get(entity_id) if expect_init_to_fail: assert device is None elif device is None: pytest.fail("CONFIG failed, see output") # Trigger update call with time_changed event now = now + timedelta(seconds=scan_interval + 60) with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now): async_fire_time_changed(hass, now) await hass.async_block_till_done() # Check state entity_id = f"{entity_domain}.{device_name}" return hass.states.get(entity_id).state
"do_return", [ { VALUE: ReadResult([0x0001]), DATA: "" }, { VALUE: ExceptionResponse(0x06), DATA: "Pymodbus:" }, { VALUE: IllegalFunctionRequest(0x06), DATA: "Pymodbus:" }, { VALUE: ModbusException("fail write_"), DATA: "Pymodbus:" }, ], ) async def test_pb_service_write(hass, do_write, do_return, caplog, mock_modbus_with_pymodbus): """Run test for service write_register.""" func_name = { CALL_TYPE_WRITE_COIL: mock_modbus_with_pymodbus.write_coil, CALL_TYPE_WRITE_COILS: mock_modbus_with_pymodbus.write_coils, CALL_TYPE_WRITE_REGISTER: mock_modbus_with_pymodbus.write_register, CALL_TYPE_WRITE_REGISTERS: mock_modbus_with_pymodbus.write_registers, }