Initial
This commit is contained in:
commit
71e70571d9
|
@ -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`.
|
|
@ -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"
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,4 @@
|
|||
colorzero==2.0
|
||||
paho-mqtt==1.6.1
|
||||
picamera==1.13
|
||||
PyYAML==6.0
|
Loading…
Reference in New Issue