Sunday, March 3, 2024

Hot Tub Monitor, Part 1

 Today I am working on a better hot tub monitor.  My hot tub has three separate pumps. I want to be able to monitor its energy consumption in greater detail, and to have it send me alerts in the event that the heater fails.

To make it fun (and cheap) I decided to use the Shelly I4 Plus module coupled with the I/O Addon. To this I added three DS18B20 temperature sensors and a couple of AC current switches (two shown below, two more to be added later).  The current switches are self-powered and act like a relay contact that closes when the current to each motor is above a few hundred milliamps.  I already have a current monitor, so I don't need to know the actual current - all I'm looking for here is state monitoring.

You can run Tasmota on these Shelly devices, but for this I don't need it.  I do, however, want to be able to control the update rate.  Initially I am just going to report state changes on a fixed interval, but the intent is to implement reporting on state change with a minimum reporting rate if temperature or state has not changed.  This will allow me to get an immediate report when the motors start, but not overwhelm my time-series database with a lot of duplicate data.  To accomplish this I will use the built-in scripting that the Shelly Gen 3 modules support.  I will improve this script later to implement the report-by-exception but to keep it simple I want to show an example of how the scripts work.

function updateTemps() {"Temperature.Getstatus", { id: 100 },
    function (response) {
       MQTT.publish("hottub/t1", JSON.stringify(response.tF), 0, false);
  )"Temperature.Getstatus", { id: 101 },
    function (response) {
       MQTT.publish("hottub/t2", JSON.stringify(response.tF), 0, false);
  )"Temperature.Getstatus", { id: 102 },
    function (response) {
       MQTT.publish("hottub/t3", JSON.stringify(response.tF), 0, false);

let tempTimer = null;

function start() {
    tempTimer = Timer.set(1000, true, updateTemps, null);
function stop() {

The device is already configured to connect to my MQTT broker.  Updates are done by making an RPC call to the Temperature.GetStatus function in the Shelly.  Each sensor has a unique ID; the three external sensors are Inumbers 100, 101, and 102.  You can get a sensor list by calling SensorAddon.GetPeripherals which returns a little bit of JSON:

    "digital_in": {},
    "ds18b20": {
        "temperature:100": {
            "addr": "40:118:243:67:212:9:96:228"
        "temperature:101": {
            "addr": "40:204:240:67:212:47:73:182"
        "temperature:102": {
            "addr": "40:242:0:67:212:231:49:22"
    "dht22": {},
    "analog_in": {},
    "voltmeter": {}

All of these RPC calls are accessible through the either your browser or curl, so it's easy to figure out the returned message structure (but the documentation is also very clear).

Happy hacking!

Wednesday, February 7, 2024

IPv6 Only Ubuntu instance on Amazon Web Services

Pay Per IPv4 Address?

You're probably aware of the AWS plan charge for IPv4 Addresses.  The costs for an address is$0.005 per hour, which amounts to about $3.65/month.  That's not going to break the bank, but if you use a few tiny instances (t3.micro or even t3.nano) you might pay more for the IP address than for the virtual machine it's attached to.  

Secondarily, I wondered if I can create an EC2 instance that has only IPv6, and would there be problems?  I decided to try.

Configuring IPv6 on EC2

Setting up IPv6 on EC2 takes a little work, most of it being done in the VPC.  Amazon has documentation that explains it.  The gist is:

  1. Associate a public IPv6 border address block to your VPC.  To do this, open the VPC console, select the VPC your instances are on, and edit the CIDR blocks.  Add an "Amazon-provided IPv6 CIDR block" associated with your ec2 region or AZ
  2. Create at least one subnet within that block. Typically this will be a subset of the block (for example a /64).  Your instances will choose an address from within this subnet.
  3. Make sure your EC2 instance belongs to this VPC/subnet (if it doesn't already) and auto-assign it an address
  4. Update the routing tables to include an IPv6 default rout

My instructions here are very incomplete, but the Amazon instructions are good (though long), so follow those.

APT Updates

The first pain point I ran into was breaking apt.  There are two reasons for this.  One is that you have to specifically configure apt to use IPv6.  You can do this by adding a file:

root# cat /etc/apt/apt.conf.d/1000-force-ipv6-transport
Acquire::ForceIPv6 "true";

Second, if you built your Ubuntu instance from one of the EC2 templates, it's going to have apt repositories listed that don't support IPv6.  In my case this was  It surprises me that there would be such a repository, obviously within AWS, but without an IPv6 address.

To fix this, I changed /etc/apt/sources.list to point toward a more generic Ubuntu package source:

deb jammy main restricted
deb jammy-updates main restricted
deb jammy universe
deb jammy-updates universe

This probably has some speed implications, and most likely some cost implications as well as the package repository is no longer within the AWS region as your instance, but that is likely to cost you pennies at most and the speed isn't that important for regular patching and updates.

Is it connectable?

All the testing I have done so far has been fine.  I work at a place that has good IPv6 infrastructure, as does my cable internet service (Spectrum).  Mobile devices seem to be ahead of the curve with respect to IPv6.  So, for my use case, IPv6 only seems fine.  I do have concerns, though, about connectivity - how many IPv4-only clients are out there?  No idea.  I don't think I would risk it for a truly "production" application until I understand the answer to that question.


Docker containers running within the EC2 instance are a little more problematic.  If those containers need to reach out to the internet for any reason, they won't work unless you specifically enable IPv6 (or set up some kind of proxy) for them.  That's an issue I was abled to solve, and I will explain how another day.