Sunday, 9 December 2018

Driving Inductive loads with the Sonoff Basic

The Sonoff Basic Smart Home power switch is very handy and has a claimed output rating of 10A at 230Vac. The basis for this is the output relay rating, a SONGLE SRD-05VDC-SL-C.

SONGLE SRD-05VDC-SL-C Miniature Relay

Now in tropical Malaysia, one of the most handy home appliances is a fan. There is usually quietly ruthless jostling for fans so there is usually at least one in use.

Poppy the dog usually wins pole position

One of my favourites is a Pensonic PFF-20B, rated at a modest 200W or  0.87A.

Pensonic PFF-20B 230V 200W

Trouble is the Sonoff Basic does not last long switching the PFF-20B, even at a modest 1A load. The contacts often get stuck, ie the relay might turn on but fail to turn off. A sharp knock on the Sonoff Basic will often jar the contacts back into position, but it will invariably fail again. Inductive loads cause arcing of the contacts resulting in damaging micro-welds.

On the other end of the spectrum is a magnetic contactor, like the LSIS MC-9b. Modestly rated at 9A for inductive loads (AC3), it is a veritable King Kong next to the SONGLE SRD-05VDC-SL-C.

LSIS MC-9b 230V 9A (AC3)

The obvious answer is to use the Sonoff Basic to drive the MC-9b. The MC-9b has a 230Vac input relay coil. I picked one up at RM36 from my local store, but you can find them online for as low as RM28. The wiring is straightforward, so I'll dispense with the schematics.

The contactor jumps about quite a bit, so it is necessary to mount it securely and to ensure the jolting does not transfer too much to the Sonoff Basic.

Here's a youtube video of a voice controlled Pensonic PFF-20B. The MC-9b did not faze Poppy at all. In Malaysia, it takes a lot to give up one's spot in front of the fan.

Happy Trails.

Friday, 30 November 2018

Autogate Adventures: Voice Control via Google Home. Part 2 of 2: ESP8266, MQTT and IFTTT

In Part 1, the autogate became a smarthome device, controlled by voice via my Google Home speaker or Google Assistant on my phone. However every time we went out, it needed four voice commands to open and close the gate.

I can also use an ESP8266 relay board, MQTT and IFTTT like I did for my ancient lantern, but there were a couple of problems. First the MQTT code would stop working after a few days. Secondly I had not figured out how to share the device on Google Home with my wife.

Top: 230Vac to 5V@1A power supply. Bottom: ESP-01S with 1-channel relay adapter

It did not take long to find out why the program stopped running. The routine MQTT_connect() is run in a loop and normally returns non-zero for a valid connection to MQTT server. However if the connection should drop (maybe WiFi interference, power outtage affecting the router, it tries to reconnect 3 times and then goes into an infinite loop waiting for a watchdog timer. Trouble is there is no such code so it stays in that infinite loop:

  while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected
    Serial.println("Retrying MQTT connection in 5 seconds...");
    delay(5000);  // wait 5 seconds
    if (retries == 0) {
      // basically die and wait for WDT to reset me
      while (1);

That is easy to get around. I merely removed the decrement statement to make it keep trying:

//    retries--;

Getting Google Home to share the device took a little more time. On the face of it, IFTTT allows me to share: I can publish my Applet for unlimited sharing. But that is too much sharing: letting a random user open my gate from the Internet is a not good idea.

I get my MQTT service from Adafruit, (which IFTTT needs to link the ESP8266 to Google Home) and I can share just my Adafruit feed for the autogate with my wife. Indeed she can activate the autogate from the Adafruit App, but Google Home will not link to a shared feed. Furthermore the Adafruit share turned out to be a global share as well, again an unacceptable security risk.

It turned out that if I got the wife to register with IFTTT and Adafruit, and if I duplicated the ESP8266 code using my wife's Adafruit account and secret key (AIO) the device will support both Adafruit feeds.

Adafruit_MQTT_Client wife_mqtt(&wife_client, AIO_SERVER, AIO_SERVERPORT, AIO_WIFENAME, AIO_WIFEKEY);

// Setup feeds Adafruit_MQTT_Subscribe lantern = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME"/feeds/frontgate");
Adafruit_MQTT_Subscribe wife_autogate = Adafruit_MQTT_Subscribe(&wife_mqtt, AIO_WIFENAME"/feeds/frontgate");

Converting the Open/Close Google Home commands to autogate pulses is now easy:

while ((subscription = mqtt.readSubscription(2000))) {
    if (subscription == &gate) {
      Serial.print(F("Got: "));
      Serial.println((char *)gate.lastread);
      int gate_State = atoi((char *)gate.lastread);
      Serial.print("Autogate openclose ");
      digitalWrite(0, 1); // Issue a command to toggle gate
      digitalWrite(0, 0); // Autogate CPU input is monostable. Issue command to reset    

Tight fit: clockwise from top left: autogate control board, RF remote control module, 12V battery and ESP8266 relay module

Here's a little youtube video of the result.

I am still a little unhappy with it: I would it like to feedback (ie 'publish' in MQTT-speak) actual gate open/close state, and I would have to manually dismantle and reprogram it should my parents come to visit. Also it would be nice to keep a log of the autogate usage, but perhaps that is a future project, an MQTT server. But in the spirit of DevOps, let's just put it up and look for those all-important gotchas that are so critical to a product's eventual success.

There you have it- a voice-controlled autogate. I have not looked very hard, but with a little luck, this should be one of the first homebrew Google Home autogate smart remotes.

Happy Trails.

Thursday, 29 November 2018

Autogate Adventures: Voice Control via Google Home. Part 1 of 2: Sonoff Basic

About a year ago I decided to maintain the autogate myself, and naturally could not resist adding a Bluetooth remote function, in addition to its keychain RF remote control. Later I added a Raspberry Pi Zero W IoT gateway, which let me control it from my smartphone. The next step would be to make it a smarthome device - voice control via my Google Home speaker or Google Assistant on my phone.

The obvious way would be to use an ESP8266 relay board, MQTT and IFTTT like I did for my ancient lantern, but there were a couple of problems. First the MQTT code would stop working after a few days. Secondly I had not figured out how to share the device on Google Home with my wife. So I though I would use a commercially-available IoT device, a Sonoff Basic.

Sonoff Basic was RM23 from Lazada
There was no trouble sharing the Sonoff Basic with home members and it was a lot more reliable. I had been using it to control some of the lights in the house. The Basic takes 230Vac input and puts out 230Vac when it turns on. But the autogate needed a dry (ie voltage-free) contact, so I simply added a 230Vac relay , an Omron MY2N.

Omron MY2N 230Vac coil relay, RM10 
I found difficult to mount in the available space in the autogate enclosure, so eventually replaced it with a solid-state relay, the Omron G3TA-IAZR02S.

The OMRON G3TA-IAZR025 was a whopping RM45
Sonoff Basic converted to output dry contact
It worked ... here's a video ... sort of. Trouble was the autogate controller needed a pulse to toggle the gate open and shut. The Sonoff Basic puts out a steady on or off command. Opening the gate was easy:

"OK Google Open autogate"

Now I need to deactivate the signal to the autogate controller, so

"OK Google Close autogate"

But the gate is still open, so now it is time to close it:

"OK Google Open autogate"

And then

"OK Google Close autogate"

It took all of 15 minutes before I got tired of it. I guess it is time for Part 2.

Saturday, 27 October 2018

Voice-controlled ESP8266 IoT AC Mains Power Extension Part 3 of 3

230Vac Fluorescent battery-backed lantern  
I have had this lantern for over 20 years. It is obsolete- dual 230Vac fluorescent lamps running from a 6V 7Ah lead-acid battery. Yet it has never failed - I replaced the battery many times but the lamps never blew. It is always plugged, in float-charging the battery and when the mains power goes the lamps automatically comes on.

It would be nice to have a bit of extra light sometimes during normal conditions, so why not mount it on the ESP8266 IoT Extension? And it would even be nicer if I could control it by voice command.

I do not have Google Home or Amazon Echo yet, but my Android smartphone does have Google Assistant which will capture my voice commands. I just need to forward the command words to the ESP8266 IoT device.

Robosapien has a great writeup on using the ESP8266 with Google Assistant, IFTTT and Adafruit IO. I really wish I could improve on it, but I believe Robosapien has had the last word on the subject. I simply followed all the steps and replaced his hardware with my ESP8266 IoT AC Mains Power Extension.

The process just took half a day, and I now have a voice controlled 20-year old lantern!

Update (2018-11-27): after running the IoT lantern a few days, I noticed a new WiFi access point with an extremely strong signal:

iwlist wlan0 scan
          Cell 04 - Address: xx:xx:xx:xx:xx:xx
                    Frequency:2.457 GHz (Channel 10)
                    Quality=70/70  Signal level=-34 dBm
                    Encryption key:off
                    Bit Rates:5.5 Mb/s; 11 Mb/s; 1 Mb/s; 2 Mb/s; 6 Mb/s
                              12 Mb/s; 24 Mb/s; 48 Mb/s
                    Bit Rates:54 Mb/s; 9 Mb/s; 18 Mb/s; 36 Mb/s
                    Extra: Last beacon: 4442ms ago
                    IE: Unknown: 000A4553505F314343363044
                    IE: Unknown: 01088B9682840C183060
                    IE: Unknown: 03010A
                    IE: Unknown: 0706534720010D14
                    IE: Unknown: 32046C122448
                    IE: Unknown: DD0918FE34030100000000

A signal level of -34dBm was stronger than my own access point! In fact I can detect and log into it two houses down, some 150m away through 2 concrete walls.

It turned out to come from the lantern code, and while it does not interfere with its operation, it is also a security risk as it is an open Access Point. As usual the Internet provided an answer; there is a bug in the ESP8266 Arduino library. There is a workaround: you just need to insert WiFi.mode(WIFI_STA) after calling WiFi.begin().

My setup code now looks like:

void setup() {

  pinMode(0, OUTPUT); // 2018-10-26 set gpio to o/p

  // Connect to WiFi access point.
  Serial.println(); Serial.println();
  Serial.print("Connecting to ");

  WiFi.mode(WIFI_STA); // 2018-11-27 remove rogue AP
  while (WiFi.status() != WL_CONNECTED) {

  Serial.println("WiFi connected");
  Serial.println("IP address: ");

  // Setup MQTT subscription for onoff feed.

Lantern with IoT Power Extension
Now the logic is reversed for the lantern as it comes on when there is no mains power. This I did by entering a data '0' when the IFTTT trigger is on.

Note reversed (data zero) logic
Google Assistant, IFTTT and Adafruit IO all worked first time. Here is a youtube video of it:

Youtube video
There you have it, a voice-controlled IoT lantern. It is not ideal and much work needs to be done on it, but it still works as before without the Internet, and in the spirit of DevOps, just put it out there as fast as we can ...  Happy Trails.

Sunday, 7 October 2018

ESP8266 IoT AC Mains Power Extension Part 2 of 3

ESP8288 ESP-01S IoT AC Mains Power Extension, shown with light bulb in E27 adapter
Rather than building separate Internet of Things (IoT) lamps, TVs, speakers, etc I found it much more flexible to make an IoT power extension. This lets me test the various IoT devices at leisure.

In Part 1, half the space is taken up by the IoT power supply. Here, in Part 2, is a proper 1-gang IoT AC mains power extension using the ESP8266 NodeMCU ESP-01S and a very handy 700mA 5V AC mains power supply.

5VDC 700mA adapter from 220VAC 
I bought mine from autobotic at for about RM7 each. They were so handy I actually bought up all the available stock in Malaysia (sorry). The 5V 1A version is over twice in size and not as useful.

Assembly is straightforward. The power socket fits nicely over the power module and the ESP-01S relay modle without coming into contact. Note the cable ties to hold the modules in place
Assemble is simple. Note the cable ties used to hold the modules in place. This is advisable as there is Live voltages on the power board and you do not want them in contact with the other bits. The modules should not be exposed from the bottom either, as some of the holes there are quite large.

You may need to drill a hole in the side for the power cord, and maybe enlarge the various holes in the bottom enclosure to fit your cable ties.

Assembled IoT mains power extension
There you have it, a proper IoT AC mains power extension. In Part 3 of this series I hope to detail how to remotely control it from your smartphone or your Google Home smart speaker.

Happy Trails.

Thursday, 27 September 2018

ESP8266 IoT AC Mains Power Extension Part 1 of 3

Right socket:: regular UK 230V power socket with builtin USB charger. Left socket: ESP-01S controllerd
Using the same parts as the IoT Porchlight, it is possible to make a remotely-switched AC power extension. This lets you switch almost any low-power electrical device without having to worry about fitting the ESP8266 bits. All you need is a stack of IoT Power Extensions ...

Warning- this project involves hazardous AC mains voltages. Do not attempt this if you are not a qualified electrician/engineer. 

Luckily here in Malaysia we use the UK 230V standard. This means it is easy to buy AC wall socket parts.

Two-gang UK Mains AC wall socket
I salvaged a two-gang wall socket enclosure, and swapped one of the wall sockets with a new type (SIRIM approved) with a built-in USB charger. This solved the problem of the 5V power supply for ESP-01S Relay Module. The charger is rated for 1500mA at 5V. The ESP-01S Relay Module required 60mA on standby and 130mA when the relay is on. That leaves plenty of wiggle room for I noticed that these built-in chargers tend to get hot at full load, probably due to the lack of ventilation.

AC mains wall socket with built-in USB charger. I paid RM25 for mine.

It is not difficult to fit the ESP-01S Relay Module into the socket enclosure. By fitting it face-down over the opening in the middle, this makes sure the AC mains on the PCB cannot be accessed from outside the enclosure. It also makes the ESP-01S's blinkenlights visible and gives its antenna an unobstructed view of the WiFi modem router.
Fitting in the ESP-01S Relay Module
A cheap (RM2) phone charger cable from Alibaba is used to connect the Relay Module to the charger output.

Wired thus, the right socket is a normal, manually switched AC mains socket at full 13A rating. Only the left socket can be remotely switched and although the load of 10A is claimed on the little 5V relay, it is prudent to switch no more than a 2A load maximum. Anything more the little relay tends to get stuck, especially with inductive loads, and most switched-mode power supplies (even little USB chargers) are inductive loads.

There you have it: an IoT (ie remotely-switched) AC mains power socket. Happy Trails.

Monday, 6 August 2018

RS-485 Modbus IoT Gateway using ESP8266 NodeMCU ESP-12E: TCP/IP Slave Part 2 of 3

Clockwise from top: USB3 externally-powered hub, RS-485 dongle, RS-485 to TTL serial PCB and ESP-12E ESP8266 NodeMCU module

Modbus TCP/IP to RS-485 passthrough gateway for RM25.

Part 1 describes an ESP-12E Modbus RTU Master using RS-485 interface. It can read and write RS-485 Modbus devices but it uses the ESP-12E debug serial port to do so. This works if the ESP-12E itself is the host controller, but Modbus masters usually have a lot more horsepower.

To be really useful, we can also make the ESP-12E a TCP/IP Modbus Slave. It still can work as a host, but this will make it a Modbus TCP/IP to RS485 "passthrough" gateway. A real Modbus host, say a desktop or Industrial PC can then orchestrate a whole bunch of Modbus devices to control a whole buildings' services.

A real TCP/IP to RS-485 Modbus gateway is some RM680

Of course the ESP-12E is not as powerful as a regular Modbus TCP/IP to RS485 passthrough but at RM25 it is an ideal way of retrofitting IoT functionality to Modbus devices.

If we replace the desktop with a cloud-based server we can scale  
As usual someone has already provided the required code. yaacov's ModbusSlaveTCP made an excellent template. I downloaded it as a zip file and copied yaacov's files from ArduinoModbusSlaveTCP-master/src into my Arduino IDE directory <your Linux account>/Arduino/libraries/ModbusSlaveTCP/

Then I expanded the sketch in Part 1:

#include <ESP8266WiFi.h>
#include <ModbusSlaveTCP.h>

const char* ssid = "YourAccessPoint";
const char* pass = "StrongPassword";
IPAddress staticIP(10,0,0,100);
IPAddress gateway(10,0,0,1);
IPAddress subnet(255,255,255,0);

// slave id = 1, rs485 control-pin = 8, baud = 9600
#define SLAVE_ID 1
// Modbus object declaration
ModbusTCP slave(SLAVE_ID);

#include <ModbusMaster232.h>
#include <SoftwareSerial.h>  // Modbus RTU pins   D7(13),D8(15)   RX,TX
// MAX485 half duplex control lines
#define not_RE 14 // D5. Enable receiver, active low
#define DE 12 // D6  Enable Transmitter, active high

// Instantiate ModbusMaster object as slave ID 1
  ModbusMaster232 node(1);

void setup() {
  pinMode(not_RE, OUTPUT);
  pinMode(DE, OUTPUT);
  // default to transmit mode to reduce noise
  digitalWrite(not_RE, HIGH); // disable receiver
  digitalWrite(DE, HIGH); // enable transmitter
  node.begin(9600);  // Modbus RTU
    /* Connect WiFi to the network
    Serial.print("Connecting to ");
    WiFi.begin(ssid, pass);
    WiFi.config(staticIP, gateway, subnet); // Static IP. Not required for dhcp

    int wifi_loop = 0;
    while (WiFi.status() != WL_CONNECTED) {
        if (wifi_loop++ == 10)
            Serial.println("Reconnecting ...");
            WiFi.begin(ssid, pass);
            wifi_loop = 0;
    /* register handler functions
     * into the modbus slave callback vector.
    slave.cbVector[CB_WRITE_COIL] = writeDigitlOut;
    slave.cbVector[CB_READ_DISCRETE_INPUT] = readDigitalIn; //    
    slave.cbVector[CB_READ_COILS] = readDigitalIn;
    slave.cbVector[CB_READ_REGISTERS] = readAnalogIn;
    slave.cbVector[CB_WRITE_MULTIPLE_REGISTERS] = writeAnalogOut; // cmheong
    /* start slave and listen to TCP port 502
    // log to serial port
    Serial.print("Modbus ready, listen on ");
    Serial.println(" : 502");

int loop_i = 0;
uint16_t readDiscreteInputs[10];
int Mdelay = 10; // from 5

void loop() {

  node.readDiscreteInputs(loop_i, 1);
  readDiscreteInputs[loop_i] = node.getResponseBuffer(0);
  Serial.print("] ");
  if (++loop_i >= 10)
    loop_i = 0;
  // delay(Mdelay); // no need for delay(5) since we print 5 char at 9600

    /* listen for modbus commands con serial port
     * on a request, handle the request.
     * if the request has a user handler function registered in cbVector
     * call the user handler function.

 * Handel Force Single Coil (FC=05)
 * set digital output pins (coils) on and off
void writeDigitlOut(uint8_t fc, uint16_t address, uint16_t status) {
    digitalWrite(address, status);

 * Handel Read Input Status (FC=02/01)
 * write back the values from digital in pins (input status).
 * handler functions must return void and take:
 *      uint8_t  fc - function code
 *      uint16_t address - first register/coil address
 *      uint16_t length/status - length of data / coil status
void readDigitalIn(uint8_t fc, uint16_t address, uint16_t length)
    int data = 0;
    // read digital input
    Serial.printf("digital input bytes fc %02x at address %04x length %d data ", fc, address, length);
    node.readDiscreteInputs(address, length);

    for (int i = 0; i <= (length-1)/8; i++) // cmheong 2018-08-06
      data = node.getResponseBuffer(i);
      slave.writeCoilsToBuffer(i, (uint8_t) data); // digitalRead(address + i));
      Serial.printf(" %x", node.getResponseBuffer(i));


 * Handel Read Input Registers (FC=04/03)
 * write back the values from analog in pins (input registers).
void readAnalogIn(uint8_t fc, uint16_t address, uint16_t length) {
    // read analog input
    for (int i = 0; i < length; i++) {
        slave.writeRegisterToBuffer(i, analogRead(address + i));

// cmheong 2018-07-31 write_registers()
void writeAnalogOut(uint8_t fc, uint16_t address, uint16_t length)
    Serial.printf("analog output bytes at address %04x length %d data ", address, length);
    for (int i = 0; i < length; i++)
        Serial.printf("%x ", slave.readRegisterFromBuffer(i));
        // node.writeSingleRegister(address, slave.readRegisterFromBuffer(i));
    node.writeMultipleRegisters(address, length);
I only tested 'Read Discrete Registers' (function code 2) and 'Write Multiple Registers' (function code 16) on a real Modbus RTU device, but you get the idea. yaakov's code did not process function code 2 properly, so I modifiled ModbusSlaveTCP.cpp of his library:

        case FC_READ_DISCRETE_INPUT: // read input state (digital in)
            address = word(bufIn[MLEN + 2], bufIn[MLEN + 3]); // coil to set.
            length = word(bufIn[MLEN + 4], bufIn[MLEN + 5]);

            // sanity check.
            if (length > MAX_BUFFER) return 0;

            // check command length.
            if (lengthIn != (MLEN + 6)) return 0;

            // build valid empty answer.
            lengthOut = MLEN + 3 + (length - 1) / 8 + 1; // cmheong 2018-08-06
            bufOut[MLEN + 2] = length;  // cmheong 2018-08-06

            // clear data out.
            memset(MLEN + bufOut + 2, 0, bufOut[2]);  // cmheong 2018-08-06

            if (cbVector[CB_READ_DISCRETE_INPUT]) // cmheong 2018-08-02
                cbVector[CB_READ_DISCRETE_INPUT](fc, address, length);

And added a new function:

void ModbusTCP::writeCoilsToBuffer(int offset, uint8_t state)
    int address = MLEN + 3 + offset;

    bufOut[address] = state;

 The ESP-12E will connect to your WiFi and use a fixed IP address (change it to suit your own address assignments) To test it, I used my laptop to connect to the same WiFi access point. I then modified pymodbus's excellent thus:

from pymodbus.client.sync import ModbusTcpClient as ModbusClient
client = ModbusClient('', method='rtu', port=502) # 2018-07-29

The test code is:
rr = client.read_discrete_inputs(1,1,unit=0x01)
if rr != None :
    print "\nread discrete inputs from", coils, rr.bits, '\n'

rq = client.write_registers(0x1001, [0x001f]*1, unit=0x01)
if rq != None :
    print "\write holding_registers from", 10, rq, '\n'

A sample working output is:

root@aspireF15:/home/heong/EMS/pymodbus/pymodbus-master/examples/current$python ./ 0x01
DEBUG:pymodbus.transaction:Current transaction state - IDLE
DEBUG:pymodbus.transaction:Running transaction 1
DEBUG:pymodbus.transaction:SEND: 0x0 0x1 0x0 0x0 0x0 0x6 0x1 0x2 0x0 0x1 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 0x1 0x2 0x1 0x7
DEBUG:pymodbus.framer.socket_framer:Processing: 0x0 0x1 0x0 0x0 0x0 0x4 0x1 0x2 0x1 0x7
DEBUG:pymodbus.factory:Factory Response[ReadDiscreteInputsResponse: 2]
DEBUG:pymodbus.transaction:Adding transaction 1
DEBUG:pymodbus.transaction:Getting transaction 1
DEBUG:pymodbus.transaction:Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'

read discrete inputs from 1 [True, True, True, False, False, False, False, False] 

DEBUG:pymodbus.transaction:Current transaction state - TRANSCATION_COMPLETE
DEBUG:pymodbus.transaction:Running transaction 2
DEBUG:pymodbus.transaction:SEND: 0x0 0x2 0x0 0x0 0x0 0x9 0x1 0x10 0x10 0x1 0x0 0x1 0x2 0x0 0x1f
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 0x2 0x0 0x0 0x0 0x6 0x1 0x10 0x10 0x1 0x0 0x1
DEBUG:pymodbus.framer.socket_framer:Processing: 0x0 0x2 0x0 0x0 0x0 0x6 0x1 0x10 0x10 0x1 0x0 0x1
DEBUG:pymodbus.factory:Factory Response[WriteMultipleRegistersResponse: 16]
DEBUG:pymodbus.transaction:Adding transaction 2
DEBUG:pymodbus.transaction:Getting transaction 2
DEBUG:pymodbus.transaction:Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
Modbus device replied!

There you have it, a Modbus TCP/IP to RS485 passthrough gateway for less than RM25. Slap on an AWS or Google Cloud server and you are ready for a free docker microservice demon Modbus host.

Happy Trails!