Initial
This commit is contained in:
commit
71e70571d9
6 changed files with 212 additions and 0 deletions
11
README.md
Normal file
11
README.md
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
This is the aquastat program. It uses ssocr to read a boiler's water temperature display and publish to MQTT.
|
||||||
|
|
||||||
|
Tested on FreeBSD. Requires raspberrypi-userland (for libbcm_host.so)
|
||||||
|
|
||||||
|
1. Create a venv.
|
||||||
|
2. Enter it.
|
||||||
|
3. `pip install -r requirements.txt`
|
||||||
|
4. Install the aquastat script to rc.d.
|
||||||
|
5. `aquastat_enable="YES"`
|
||||||
|
6. Make sure `ssocr` is installed.
|
||||||
|
7. Edit `aquastat.yaml`.
|
56
aquastat
Executable file
56
aquastat
Executable file
|
@ -0,0 +1,56 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# PROVIDE: aquastat
|
||||||
|
# REQUIRE: NETWORKING
|
||||||
|
#
|
||||||
|
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf
|
||||||
|
# to enable this service:
|
||||||
|
#
|
||||||
|
# aquastat (bool): Set to NO by default.
|
||||||
|
# Set it to YES to enable aquastat.
|
||||||
|
#
|
||||||
|
|
||||||
|
. /etc/rc.subr
|
||||||
|
|
||||||
|
name="aquastat"
|
||||||
|
rcvar=aquastat_enable
|
||||||
|
pidfile=/var/run/aquastat.pid
|
||||||
|
procname=python3
|
||||||
|
|
||||||
|
start_cmd=aquastat_start
|
||||||
|
stop_cmd=aquastat_stop
|
||||||
|
status_cmd=aquastat_status
|
||||||
|
|
||||||
|
command="/home/dponte/aquastat/start.sh"
|
||||||
|
|
||||||
|
aquastat_start() {
|
||||||
|
echo "Starting ${name}."
|
||||||
|
/usr/sbin/daemon -p ${pidfile} -f -u dponte $command
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
aquastat_stop() {
|
||||||
|
if [ -e "${pidfile}" ]; then
|
||||||
|
echo "Stopping ${name}."
|
||||||
|
kill -s TERM `cat ${pidfile}`
|
||||||
|
else
|
||||||
|
echo "${name} is not running"
|
||||||
|
fi
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
aquastat_status() {
|
||||||
|
if [ -e "${pidfile}" ]; then
|
||||||
|
echo "${name} is running as pid `cat ${pidfile}`"
|
||||||
|
else
|
||||||
|
echo "${name} is not running"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
load_rc_config $name
|
||||||
|
|
||||||
|
: ${aquastat_enable="NO"}
|
||||||
|
: ${aquastat_user="dponte"}
|
||||||
|
: ${aquastat_group="dponte"}
|
||||||
|
|
||||||
|
run_rc_command "$1"
|
114
aquastat.py
Normal file
114
aquastat.py
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
#!/usr/bin/env python3.10
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
def __init__(self, configfile):
|
||||||
|
with open(configfile, 'r') as f:
|
||||||
|
conf = yaml.safe_load(f)
|
||||||
|
|
||||||
|
self.outfile = conf["outfile"]
|
||||||
|
self.broker = conf["mqtt"]["broker"]
|
||||||
|
self.port = conf["mqtt"]["port"]
|
||||||
|
self.username = conf["mqtt"]["username"]
|
||||||
|
self.password = conf["mqtt"]["password"]
|
||||||
|
self.temptopic = conf["topic"]["temp"]
|
||||||
|
self.lwttopic = conf["topic"]["lwt"]
|
||||||
|
self.thresh = conf["threshold"]
|
||||||
|
self.coords = conf["coords"]
|
||||||
|
self.sleepfast = conf["sleep"]["fast"]
|
||||||
|
self.sleepmedium = conf["sleep"]["medium"]
|
||||||
|
self.sleepslow = conf["sleep"]["slow"]
|
||||||
|
self.fastthresh = conf["fastThreshold"]
|
||||||
|
self.slowthresh = conf["slowThreshold"]
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
from time import sleep
|
||||||
|
from picamera import PiCamera
|
||||||
|
import subprocess
|
||||||
|
import signal, os
|
||||||
|
import sys
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
cfg = Config('/usr/local/etc/aquastat.yaml')
|
||||||
|
SSOCR_ARGS = ['ssocr', '--number-digits=-1', '--foreground=white', '--background=black', '-t', str(cfg.thresh), 'crop', str(cfg.coords[0]), str(cfg.coords[1]), str(cfg.coords[2]), str(cfg.coords[3]), cfg.outfile]
|
||||||
|
|
||||||
|
|
||||||
|
def mysleep(t):
|
||||||
|
# print("sleeping for {}".format(t))
|
||||||
|
sleep(t)
|
||||||
|
|
||||||
|
def on_connect(client, userdata, flags, rc):
|
||||||
|
client.publish(cfg.lwttopic, "Online")
|
||||||
|
pass
|
||||||
|
|
||||||
|
# The callback for when a PUBLISH message is received from the server.
|
||||||
|
def on_message(client, userdata, msg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def handler(signum, frame):
|
||||||
|
client.publish(cfg.lwttopic, "Offline")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
signal.signal(signal.SIGTERM, handler)
|
||||||
|
signal.signal(signal.SIGINT, handler)
|
||||||
|
|
||||||
|
client = mqtt.Client()
|
||||||
|
client.on_connect = on_connect
|
||||||
|
client.on_message = on_message
|
||||||
|
client.username_pw_set(cfg.username, cfg.password)
|
||||||
|
client.connect(cfg.broker, cfg.port, 60)
|
||||||
|
client.loop_start()
|
||||||
|
|
||||||
|
camera = PiCamera()
|
||||||
|
camera.resolution = (1024, 768)
|
||||||
|
mysleep(2)
|
||||||
|
temp = 0
|
||||||
|
prev_temp = 0
|
||||||
|
offline = False
|
||||||
|
sleep_count = 0
|
||||||
|
fast = False
|
||||||
|
slow = False
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
prev_temp = temp
|
||||||
|
camera.capture(cfg.outfile, resize=(800, 600))
|
||||||
|
result = subprocess.run(SSOCR_ARGS, stdout=subprocess.PIPE)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
client.publish(cfg.lwttopic, "Offline")
|
||||||
|
offline = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if offline:
|
||||||
|
offline = False
|
||||||
|
client.publish(cfg.lwttopic, "Online")
|
||||||
|
|
||||||
|
temp = int(result.stdout.decode('utf-8'))
|
||||||
|
if temp > 200 or temp < 50:
|
||||||
|
mysleep(cfg.sleepfast)
|
||||||
|
continue
|
||||||
|
if temp != prev_temp:
|
||||||
|
client.publish(cfg.temptopic, temp)
|
||||||
|
sleep_count = 0
|
||||||
|
fast = True
|
||||||
|
slow = False
|
||||||
|
mysleep(cfg.sleepfast)
|
||||||
|
else:
|
||||||
|
if fast and sleep_count > cfg.fastthresh:
|
||||||
|
fast = False
|
||||||
|
sleep_count = 0
|
||||||
|
mysleep(cfg.sleepmedium)
|
||||||
|
elif slow or sleep_count > cfg.slowthresh:
|
||||||
|
sleep_count = 0
|
||||||
|
slow = True
|
||||||
|
mysleep(cfg.sleepslow)
|
||||||
|
else:
|
||||||
|
sleep_count += 1
|
||||||
|
if fast:
|
||||||
|
mysleep(cfg.sleepfast)
|
||||||
|
else:
|
||||||
|
mysleep(cfg.sleepmedium)
|
||||||
|
except:
|
||||||
|
mysleep(cfg.sleepfast)
|
||||||
|
continue
|
23
aquastat.yaml.sample
Normal file
23
aquastat.yaml.sample
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
outfile: /tmp/cam.jpg
|
||||||
|
mqtt:
|
||||||
|
broker: xenon-iot
|
||||||
|
port: 1883
|
||||||
|
username: aquastat
|
||||||
|
password: "password"
|
||||||
|
topic:
|
||||||
|
temp: "aquastat/WATERTEMP"
|
||||||
|
lwt: "aquastat/LWT"
|
||||||
|
sleep:
|
||||||
|
fast: 1
|
||||||
|
medium: 5
|
||||||
|
slow: 15
|
||||||
|
|
||||||
|
fastThreshold: 3
|
||||||
|
slowThreshold: 10
|
||||||
|
|
||||||
|
threshold: 75
|
||||||
|
coords:
|
||||||
|
- 360
|
||||||
|
- 170
|
||||||
|
- 162
|
||||||
|
- 109
|
4
requirements.txt
Normal file
4
requirements.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
colorzero==2.0
|
||||||
|
paho-mqtt==1.6.1
|
||||||
|
picamera==1.13
|
||||||
|
PyYAML==6.0
|
4
start.sh
Executable file
4
start.sh
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
. /home/dponte/env/bin/activate
|
||||||
|
exec python3 /home/dponte/aquastat/aquastat.py
|
Loading…
Reference in a new issue