The wrong way to use a serial port

Monday, January 20th, 2020 at 10:40pm

Up until yesterday the last change I made to my script that pulls usage information from my smart meter was to make it use a serial port library. At the time I grabbed the example and apart from adding in a hack to remove the null characters I was getting it worked fine.

Inspired by some things at LCA2020 last week I decided that I would change how I was getting the usage data.

The adapter I am using appears as a serial port and while you can send commands, if you just read from the serial port it will output an InstantaneousDemand XML payload every few seconds. The script I was using would connect to the serial port and read until it got one of these payloads (erroring if it didn’t see one in 30 seconds), writing the data to MQTT for Home Assistant to pick up.

This didn’t seem to be the best way, it sometimes wasn’t able to open or read from the serial port (one hung process could give hours of errors) and it was also skipping over information.

My replacement version would instead run as a daemon, connecting to the serial port once and then publishing MQTT messages whenever an InstantaneousDemand payload appears on the serial port. I still had it die if no payload had been seen for 30 seconds, but it would be restarted as I set it up as a service.

This was working quite nicely for a few hours, but then I noticed the load value was high, checking with top I found that this process was taking 100% of CPU. This wasn’t right…

Upon checking the documentation for Device::SerialPort I found that while I had used examples from the SYNOPSIS section, there was a full EXAMPLE that was much better.

  • In my loop I was trying to read() a single byte each time, instead I should be reading more bytes. The example used 255, so I went with that.
  • I wasn’t checking the count of bytes returned from read(), this explained the null data I had been seeing. I was reading when no data was available, if the count was zero there was no data.
  • There is a timeout value that controls how long the read will wait before returning zero data. If not set then the loop in my code would be calling read() as fast as it could, setting this to one second meant it was handled within the serial port library.

After making these small changes (aka using the library correctly) the CPU usage of this process dropped to nothing. While it wasn’t actually impacting the performance, it is good to do things properly.

Something else I an trying with this process is setting an MQTT Last Will and Testament message. This is a message that you set up when connecting to your MQTT server, but it will only get sent if there is an ungraceful disconnect. Coupled with a startup message I can get a notification when the process is starting and a notification if the process crashes. Time will tell how useful this will be, but it is interesting to learn about.

Tagged with: ,