Rebuilding home system with ESP-now

Place your projects here
Post Reply
lyizb
Posts: 241
Joined: Fri Feb 12, 2021 8:23 pm
Has thanked: 121 times
Been thanked: 84 times

Rebuilding home system with ESP-now

Post by lyizb »

I've been cobbling up my house monitoring and control system for 22 years. The aging out of some parts has made me think that I might do well to rebuild from the ground up, with a Raspberry Pi 4B as the aggregator/master, and ESP32s as the nodes, using ESP-Now.

My messages from sensors and to actuators have always been in the form of an ID code followed by three values (of which only the first is often valid--e.g. for a temperature sensor). The ID has been a capital letter, A-Z. I will be expanding that to include "a-z".

The central part of this is the Pi 4B, which needs to talk to ESP32 nodes. My solution looks like this:
Pi4B_ESPs labelled.jpg
My first pass was to plug an ESP32-S2Mini into the 5V, 0V, Tx and Rx pins on the Pi. These align perfectly with VBus (5V), GND, 16, and 18 on the S2Mini. This worked after serial was enabled on the pi, creating the serial port address, /dev/serial0. I could talk back and forth.

But I wanted a way to toggle the power if communication was lost. It turns out that by installing a program called uhubctl, you can turn off and on the 4 USB ports (they can't be switched individually): "sudo apt install uhubctl". Now you can turn off power to the USB ports with "sudo uhubctl -l 2 -a 0", and turn it back on with "sudo uhubctl -l 2 -a 1".

I wired up a little perf-board adaptor to connect 5V, Tx, Rx, GND on a USB/serial adaptor to 5V, G, 4, 3 on an ESP32-C3Supermini. This is on the port /dev/ttyUSB0. Now I can talk to the ESP32, and power cycle it if communication is lost.

My first project for this is to control the zones in my 3-zone hot water system by being able to bypass (short out) the relays in the zone thermostats. I'm using ESP32-S2Minis which have the D1Mini relay modules plugged in (all the D1 modules plug into the outer rows of the S2Mini).
ESP32 S2Mini relays.jpg
The S2Mini+Relay combination presents a web page which allows you to toggle the relay, turning on heat to the zone.
DR_Toggle.jpg
In addition to toggling the relay, a message, "DR Thermostat Relay ON" or "OFF" is sent with ESP-Now to the C3Supermini, which passes it via serial to the Pi (in the form, "p 1 0 0" or "p 0 0 0"), which logs it to a file with a timestamp. Control can also go the other way. From the pi, the command echo -n "p 1" >/dev/ttyUSB0 will turn the relay on, and "p 0" will turn it off.

This bash code running on the Pi handles the incoming serial:

Code: [Local Link Removed for Guests]

#!/bin/bash
stty -F /dev/serial0 115200 clocal cread cs8 -cstopb -parenb -crtscts -echo
cat /dev/serial0 2>/dev/null | while read v1 v2 v3 v4; do
 if [ "$v1" \> "@" -a "$v1" \< "[" ] ; then  # A-Z
  echo "$v1 $v2 $v3 $v4 $(date +%y%m%d%H%M%S)"  >> /home/lb/signals.txt
 fi
 if [ "$v1" \> "\`" -a "$v1" \< "{" ] ; then # a-z
  echo "$v1 $v2 $v3 $v4 $(date +%y%m%d%H%M%S)"  >> /home/lb/signals.txt
 fi
#  echo "$v1 $v2 $v3 $v4 $(date +%y%m%d%H%M%S)" >> /signals.txt
done
This saves an incoming serial string to the file, signals.txt, if the first charactor is A-Z or a-z. When I first ran this, I was baffled for some hours because the command I sent got sent back 4-15 times. I finally realized that the bash stty command was echoing back charactors it received. The "-echo" parameter fixed that by turning echo off.

Here is the Annex code on the relay node:

Code: [Local Link Removed for Guests]

' DR_zone.bas
' CC:8D:A2:8C:A1:A8
  dim wifi_APs$(2)="omnibus6_EXT","Omnibus_N"
  dim RECEIVER_MAC$(2) = "F0:F5:BD:FD:02:50","CC:8D:A2:8C:A1:A8" ' MAC address of the receiver
  currentWIFI=0
'  WIFI.CONNECT wifi_APs$(currentWIFI),"amber1977"  
  
pin.mode 35,output ' D2
led1=0
ID$="p"
cls
autorefresh 1000
wlog "ESP-Now init " ; espnow.begin  ' should print 0 if all OK
espnow.add_peer RECEIVER_MAC$(currentWIFI)    ' set the address of the receiver
'espnow.add_peer RECEIVER_MAC$(1)    ' set the address of the receiver
bcast$ = "FF:FF:FF:FF:FF:FF" 'broadcast address
espnow.add_peer bcast$
onEspNowError status ' set the place where jump in case of TX error
onEspNowMsg espnowRX 'branch here with incoming msg
gosub make
wait

make:
  if led1=1 then
    state$="ON"
  else
    state$="OFF"
  endif
  cls
  a$=""
  a$=a$ + "<table>"
  a$=a$ + "<tr style='vertical-align:baseline'><td>" + "DR Thermostat Relay " + "</td><td>" + Led$(led1) + "</td><td>" + button$("Toggle",toggle1) + "</td></tr>"
  a$=a$ + "</table>"
  html a$
  pin(35)=led1
  espnow.write "DR Thermostat Relay " + state$ ' send the message
  return

toggle1:
  led1=1-led1
  gosub make
  return

status:
  print "ESP-Now TX error on ";wifi_APs$(currentWIFI);", ch ";WIFI.CHANNEL;": "; espnow.error$  ' print the error
  wlog "ESP-Now TX error on ";wifi_APs$(currentWIFI);", ch ";WIFI.CHANNEL;": "; espnow.error$  ' print the error
  return
  
espnowRX:    ' set/reset relay if message is for this node
  RXmsg$ = espnow.read$
  sender$ = espnow.remote$
  if sender$<>MAC$(0) and sender$<>MAC$(1) then ' don't respond to self
    if instr(RXmsg$,chr$(13)) then
      RXmsg$=mid$(RXmsg$,1,instr(RXmsg$,chr$(13))-1) ' should strip CR & LF
    endif
    wlog "ESP_Now message :"+RXmsg$+": from "+sender$+" "+time$
    ledVal=-1
    if mid$(Rxmsg$,1,1)= ID$ then ' message is for us
      if mid$(Rxmsg$,3,1)= "1" then ' turn relay on
        ledVal=1
      elseif mid$(Rxmsg$,3,1)= "0" then ' turn relay off
        ledVal=0
      endif
    endif
    if ledVal <> -1 then
      led1=ledVal
      gosub make
    endif
  endif
  return
And here is the receiving node--the C3Supermini plugged into the Pi:

Code: [Local Link Removed for Guests]

' log_now.bas ' ESP-Now receiver
SERIAL2.MODE 115200,3,4
' print2 "a 23 1 0"
MAC_adr$="CC:8D:A2:8C:A1:A8"
wlog "ESP-Now init " ; espnow.begin 'should print 0 if all OK
bcast$ = "FF:FF:FF:FF:FF:FF" 'broadcast address
espnow.add_peer bcast$
onserial2 serialRX2
onEspNowMsg espnowRX 'branch here with incoming msg
RXmsg$ = "b 1 0 0 " ' starting now
sender$="00:00:00:00:00" ' for startup message only
gosub RXnow
wait

espnowRX:
  RXmsg$ = espnow.read$
  sender$ = espnow.remote$
  if sender$ <> bcast$ then
    if sender$ <> MAC$(0) and sender$ <> MAC$(1) then ' ignore messages from ourselves
      if RXmsg$<>"" then ' ignore empty
        gosub RXnow
      endif
    endif
  endif
  return

RXnow:
wlog "!"+RXmsg$+"!",sender$
if mid$(Rxmsg$,1,2)= "DR" then
  if instr(RXmsg$,"ON") then
    RXmsg$="p 1 0 0"   ' DR Zone on
  else
    RXmsg$="p 0 0 0"   ' DR Zone off
  endif
elseif mid$(Rxmsg$,1,2)= "BR" then
  if instr(RXmsg$,"ON") then
    RXmsg$="q 1 0 0"   ' BR Zone on
  else
    RXmsg$="q 0 0 0"   ' BR Zone off
  endif
endif
print2 RXmsg$ ' send to RPi4 MHome
return

serialRX2:
  pause 20 ' allow characters to arrive
  serRXmsg$ = serial2.input$
  if len(serRXmsg$)>2 then
    wlog "*"+serRXmsg$+"*"
    espnow.write serRXmsg$+chr$(13), "ff:ff:ff:ff:ff:ff" ' broadcast the text
  endif
  return
There is undoubtedly some unneeded code here, left over from trying to debug the repetiton caused by the echoed commands.

There's lots more to be done to build this out to redo my entire setup.
You do not have the required permissions to view the files attached to this post.
Post Reply