arendst / Tasmota

Alternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at
https://tasmota.github.io/docs
GNU General Public License v3.0
21.85k stars 4.75k forks source link

ModBus RTU to TCP gateway #9586

Closed TheChatty closed 3 years ago

TheChatty commented 3 years ago

Have you looked for this feature in other issues and in the docs?
I did. It's not implemented yet. Here is an existing gateway in C or stripped down in python. This is the basic idea behind a RTU/TCP gateway.

Is your feature request related to a problem? Please describe.
I want to be able to poll certain registers from my modbus device regularly via MQTT and be able to configure it via a TCP-based Windows app.

Describe the solution you'd like
Implement a RTU/TCP gateway in Tasmota which allows concurrent access to a modbus device on Tx/Rx.

Describe alternatives you've considered
Alternatively I can either connect Tasmota for MQTT-based access or my PC directly for TCP-based access.

Additional context
This gateway is a litte bit more than a simple serial TCP bridge in the sense that it adds and also strips a few bytes from each request/response.

(Please, remember to close the issue when the problem has been addressed)

s-hadinger commented 3 years ago

Tasmota always prefers MQTT protocol wherever it's possible. We strongly suggest to use the Mqtt gateway instead.

TheChatty commented 3 years ago

At least I need Tasmota to act as TCP gateway because my heating regulator Trovis 5573 can only be configured using a software called Trovis View which only supports communication via TCP. My idea is to enhance your code with an option "TCP_BRIDGE_MODBUS" for according byte mangling (see python solution linked in description).

Jason2866 commented 3 years ago

Tasmota already has a TCP gateway. See config example used for the ZbBridge for connecting with HomeAssistant (ZHA) https://zigbee.blakadder.com/Sonoff_ZBBridge.html Command TCPStart <Port> Sorry a implementation for all different special use cases for a TCP bridge will not be done

TheChatty commented 3 years ago

I linked to Tasmota TCP bridge already in the first post. But I need the gateway to mangle the bits a little.

Another option would be to implement the gateway in the SML interface. The meter is already defined (baudrate, protocol, pins, etc) and thus the gateway only needs to insert its TCP commands along the other commands from the MQTT/console stream.

Since a meter can only be connected to one host it would be great if continous meter value logging could be combined with (rare) meter configuration stuff (in my case with a TCP-based Windows app, a TCP-based smartphone app is available as well).

Jason2866 commented 3 years ago

The use case is very specific. So it will be probably not done from the main contributors. Maybe you are lucky and someone has the need too and provides a PR. With the solution as it is now, the needed manipulations can be done on receiver side with a little (Python?) tool and providing the data to your Application.

ascillato2 commented 3 years ago

Closing this request as there is no active development on this. Please, ask to reopen if you want to work on this. Thanks.

jeroenst commented 2 years ago

@TheChatty Can you try this?

I have added the modbustcpstart en modbustcpconnect functions.

I also removed the (u)int8 option for modbus send command, because modbus registers are always 16 bits.

The data received from the tcp client is not checked btw, it's just send to the modbus client.

I will also write a small tcpclient to test this added feature.

https://github.com/jeroenst/Tasmota/tree/ModbusTCP

TheChatty commented 2 years ago

I flashed the .gz you provided but are unable to select "ModBr .." in Configuration? It's not listed.

jeroenst commented 2 years ago

I will look at it tomorrow. Otherwise compile it from my branch.

TheChatty commented 2 years ago

@Jason2866: A ModBus TCP gateway is a widely requested feature I'd say. Enhancing driver 41 or 63 is not really important. The code impact is rather small, the usage gain is much bigger. If implemented in driver 41 TCP/RTU protocol conversion could be handled by a new option for instance.

TheChatty commented 2 years ago

@jeroenst: I compiled db4f710 myself and it offered ModBr Tx/Rx. But I don't receive anything yet (trying to read outside temp like in my SML script):

# ModBusBaudrate 19200
# ModBusSerialConfig 3
# ModBusSend {"deviceaddress": 247, "functioncode": 3, "startaddress": 9, "type":"int16", "count":1}

I need to check via plain serial bridge if communication is a problem atm... tomorrow or so...

jeroenst commented 2 years ago

@TheChatty I did some improvements in my latest branch, try that instead.

I was confused with the registers which are not 8 but 16 bits.

https://github.com/jeroenst/Tasmota/tree/ModbusTCP

Also have a look at the logging when using weblog 4

I will also have a look at it tomorrow if it still doesn't work.

jeroenst commented 2 years ago

@TheChatty Here is a bin file based on my latest commit in the modbustcp branch.

Can you give it a try?

tasmota.bin.gz

jeroenst commented 2 years ago

Here are the PHP tools that I use for simulaton. modbus tools.zip

TheChatty commented 2 years ago

I could now use plain serial bridge (again) like this:

baudrate 19200
serialsend5 f7 03 00 09 00 01 40 9e
RESULT = {"SerialSend":"Done"}
RESULT = {"SerialReceived":"F703020115B00E"}

0x0115 --> 227 --> /10 --> 27.7° degrees outside

But still no response via modbus bridge:

ModBusBaudrate 19200
ModBusSend {"deviceaddress": 247, "functioncode": 3, "startaddress": 9, "type":"int16", "count":1}
RESULT = {"ModbusSend":"Done"}
jeroenst commented 2 years ago

Something goes wrong with setting the baudrate.

On 9600 I receive from esp8266: 0xf7 0x03 0x00 0x09 0x00 0x01 0x40 0x9e On 19200 I receive from esp8266: 0x7e 0x1e 0x00 0x86 0x00 0x06 0x00 0x1e

Investigation in progress..

jeroenst commented 2 years ago

For some reason modbusbaudrate is not working right and it also causes modbusbridge to become deaf to incomming data.

Have to dig further for this to find out why this is happening.

jeroenst commented 2 years ago

@TheChatty This is a hard thing, in code everything seems right, I even thing serialbridge encounters the same issue.

Can you maybe do a test at 9600 baud (without setting baudrate in tasmota) ?

TheChatty commented 2 years ago

At 9600 baud I cannot test with my modbus client as it is fixed to 19200.

What do you want me to test?

jeroenst commented 2 years ago

It seems that data from every slave address greater than 4 is ignored by the modbus driver. Can you test it with address 1 or 2?

jeroenst commented 2 years ago

19200 works fine below slave address 5, debugging now.

TheChatty commented 2 years ago

I cannot change any modbus setting at the client side - sorry. Good look with the debugging.

jeroenst commented 2 years ago

ok, almost there I think

jeroenst commented 2 years ago

Bug found, and solved, here is the firmware without bug

tasmota.bin.gz

TheChatty commented 2 years ago

3156b06 does not change anything for me - still no response. weblog 4 does not give any hint.

jeroenst commented 2 years ago

Do you execute ModBusBaudrate 19200 ?

jeroenst commented 2 years ago

Here it seems to work:

19:51:30.028 CMD: ModBusSend {"deviceaddress": 247, "functioncode": 3, "startaddress": 9, "type":"int16", "count":1}
19:51:30.035 CMD: Grp 0, Cmd 'MODBUSSEND', Idx 1, Len 87, Pld -99, Data '{"deviceaddress": 247, "functioncode": 3, "startaddress": 9, "type":"int16", "count":1}'
19:51:30.043 RSL: RESULT = {"ModbusSend":"Done"}
19:51:30.353 MBS: DATA_RECEIVED1 Address=247
19:51:30.357 RSL: RESULT = {"ModbusReceived":{"DeviceAddress":247,"FunctionCode":3,"StartAddress":9,"Length":7,"Count":1,"Values":[277]}}
jeroenst commented 2 years ago

did you use the provided .bin.gz file?

TheChatty commented 2 years ago

No... I compiled 3156b06 myself. I get no response regardless of whether I issue ModBusBaudrate 19200 upfront or not.

jeroenst commented 2 years ago

Please try the bin.gz file maybe some defines are different.

jeroenst commented 2 years ago

When building you have to define USE_MODBUS_BRIDGE and USE_MODBUS_TCP_BRIDGE

TheChatty commented 2 years ago

Now flashed you bin.gz with same results. Did ModBusSend ... and ModBusBaudrate ... and another ModBusSend .... No response. I will test ModBus_TCP_Bridge once the other is working ;-)

jeroenst commented 2 years ago

Over here it is working fine, I even used the exact data you provided.

Maybe something is incorrectly configured like parity?

20:05:22.714 CMD: ModBusSend {"deviceaddress": 247, "functioncode": 3, "startaddress": 9, "type":"int16", "count":1}
20:05:22.719 CMD: Grp 0, Cmd 'MODBUSSEND', Idx 1, Len 87, Pld -99, Data '{"deviceaddress": 247, "functioncode": 3, "startaddress": 9, "type":"int16", "count":1}'
20:05:22.725 RSL: RESULT = {"ModbusSend":"Done"}
20:05:23.038 RSL: RESULT = {"ModbusReceived":{"DeviceAddress":247,"FunctionCode":3,"StartAddress":9,"Length":7,"Count":1,"Values":[277]}}
20:05:32.011 WIF: Checking connection...
TheChatty commented 2 years ago

That was it. After redoing ModBusSerialConfig 3 it worked. ... I am testing now.

ModBusSerialConfig is not persisted... it's always 8E1 after reboot. ModBusBaudrate looks like it's persisted but probably not run during boot up. I still have to set it manually.

ModBusTCPStart 90
# running tcp modbus 'server' asking for same request as above
TCP: MBS: MBRTCP Got connection from 192.168.178.53
RSL: RESULT = {"SerialReceived":"F7030200F5B016"}

But the 'server' does not get the correct result. Modbus Error: [Invalid Message] No response received, expected at least 8 bytes (0 received) (timeout is a whopping 10s) Maybe you need to flush the response?

jeroenst commented 2 years ago

Ok, perfect. In the meantime I bricked my esp which is communicating with an SDM120, that's a thing to fix tomorrow.

TheChatty commented 2 years ago

Sorry to hear about your loss.

jeroenst commented 2 years ago

I fixed it this morning so everything is ok, I'm glad it starts working!

jeroenst commented 2 years ago

@TheChatty Ok, I found the problem there seems to be a TasmotaModbus->begin() and a TasmotaModbus->Begin() function. The first one calls the function from TasmotaSerial->begin which returns different value. Now using Begin the serialclaim function is called and al data received by serial is parsed to the tasmotamodbus driver.

Please give it a try.

TheChatty commented 2 years ago

Settings are still not persisted as described above.

MQTT with serial@19200b still works.

TCP 'server' (strange definition ModBus uses here) receives something but not understood.

RSL: RESULT = {"ModbusTCPStart":"Done"}
TCP: MBS: MBRTCP Got connection from 192.168.178.53
MBS: Serial data ready 5
MBS: Serial data received buffer = 4
MBS: MBRTCP from Modbus deviceAddress 247, writing 11 bytes to client

DEBUG:pymodbus.client.sync:Connection to Modbus server established. Socket ('192.168.178.53', 64995)
DEBUG:pymodbus.transaction:Current transaction state - IDLE
DEBUG:pymodbus.transaction:Running transaction 1
DEBUG:pymodbus.transaction:SEND: 0x0 0x1 0x0 0x0 0x0 0x6 0xf7 0x3 0x0 0x9 0x0 0x1
DEBUG:pymodbus.client.sync:New Transaction state 'SENDING'
DEBUG:pymodbus.transaction:Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
DEBUG:pymodbus.transaction:Changing transaction state from 'WAITING FOR REPLY' to 'PROCESSING REPLY'
DEBUG:pymodbus.transaction:RECV: 0x0 0x1 0x0 0x0 0x0 0x4 0xf7 0x3 0x0 0xde
DEBUG:pymodbus.framer.socket_framer:Processing: 0x0 0x1 0x0 0x0 0x0 0x4 0xf7 0x3 0x0 0xde
DEBUG:pymodbus.factory:Factory Response[ReadHoldingRegistersResponse: 3]
DEBUG:pymodbus.transaction:Adding transaction 1
DEBUG:pymodbus.transaction:Getting transaction 1
DEBUG:pymodbus.transaction:Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
Traceback (most recent call last):
  File "minimal_PyModbus_tcp_tasmota.py", line 9, in <module>
    print('Temp. outside %s°C' % (client.read_holding_registers(  9,1,unit=247).registers[0]/10))
IndexError: list index out of range
jeroenst commented 2 years ago

Can you add python logging from a successful request? As far as I can see the data is correct according to the tcp modbus documentation. Maybe I missed something...

jeroenst commented 2 years ago

I think I had to add a byte count register in the tcp reply message according to the documentation found here: https://www.manualslib.com/manual/1708763/Accuenergy-Axm-Web2.html?page=27#manual

Please try latest commit in my ModbusTCP branch.

jeroenst commented 2 years ago

These settings are not persistent indeed, you can create a small rule System#Boot

Rule1 On System#Boot DO ModbusBaudrate 19200 ENDON ON System#Boot DO ModbusSerialConfig 3 ENDON ON System#Boot DO ModbusTCPStart 8000 ENDON

Rule1 on

@arendst Should these settings be persistent or is using a boot rule to set modbus parameters the way to go?

TheChatty commented 2 years ago

I wrote a small ModBus server using pymodbus. That's the log output:

# served by your tcp bridge
DEBUG:pymodbus.transaction:RECV: 0x0 0x1 0x0 0x0 0x0 0x4 0xf7 0x3 0x0 0xde

# served by pymodbus
DEBUG:pymodbus.transaction:RECV: 0x0 0x1 0x0 0x0 0x0 0x5 0xf7 0x3 0x2 0x0 0xde

That looks like a byte counter... will now try your latest code.

TheChatty commented 2 years ago

MQTT and TCP via pymodbus-client works smoothly in (quasi) parallel. And I can read several registers with a single command or with several consecutive commands. Well done!

Plain serial bridge settings are persisted across boots. Here and with modbus bridge you can see the "Saved conf in flash" message after setting e.g. baudrate.

What I still have to analyze: the Windows and iOS tool suite for my heating regulator still cannot communicate via modbus tcp bridge.

jeroenst commented 2 years ago

I wrote a small ModBus server using pymodbus. That's the log output:

# served by your tcp bridge
DEBUG:pymodbus.transaction:RECV: 0x0 0x1 0x0 0x0 0x0 0x4 0xf7 0x3 0x0 0xde

# served by pymodbus
DEBUG:pymodbus.transaction:RECV: 0x0 0x1 0x0 0x0 0x0 0x5 0xf7 0x3 0x2 0x0 0xde

That looks like a byte counter... will now try your latest code.

I'm missing the register count bytes in this example, is that on purpose?

TheChatty commented 2 years ago

You got the chronological order wrong. Your cited message was created BEFORE you updated your code. The last line of the message was: will now try your latest code. To be specific: 56c07ec inserts the once missing byte count and works absolutely fine with MQTT and TCP as long as using pymodbus. But TrovisView is probably not sticking to modbus standard and communicates slightly different which the heating regulator (also) understands. As I said: I will try to analyze this later.

jeroenst commented 2 years ago

Sorry, I now see it are replies, not requests, you can ignore my message

Btw I tested the modbussend command with an sdm120 modbus electricity meter and it worked like it should.

jeroenst commented 2 years ago

If TrovisView is not according to the standard and the change is minimal I can take a look if I can add an extra define for it.

But you have to provide a description for the bytes to send and the bytes to receive for the TCP socket.

Please open a new feature request for Modbus RTU to Trovis TCP gateway as this Modbus RTU to TCP gateway is created according the Modbus TCP specifications.

TheChatty commented 2 years ago

I'm still in the works with figuring out what TrovisView does.

But to the modbus TCP bridge standard:

jeroenst commented 2 years ago

I will check the persistence, I inherited this code from other modules, so possible persistence is not supported. You can use the Rules I stated earlier instead. (https://github.com/arendst/Tasmota/issues/9586#issuecomment-1192466036)

I will also check the ModbusBaudrate command with no parameters.

For your TrovisView TCP you should open a new change request because that is a different protocol than Modbus TCP specifies.