summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorfishsoupisgood <github@madingley.org>2020-09-09 11:53:37 +0100
committerfishsoupisgood <github@madingley.org>2020-09-09 11:53:37 +0100
commit9d87c925a9eaa4fc256be3173c14a20d1469472d (patch)
tree50d63f87a47a0eac3f5b8058850184bcd4e6ee95
parentdafd8cf2fdcdd637cc06f760d318cf8391b1a294 (diff)
downloadheating-9d87c925a9eaa4fc256be3173c14a20d1469472d.tar.gz
heating-9d87c925a9eaa4fc256be3173c14a20d1469472d.tar.bz2
heating-9d87c925a9eaa4fc256be3173c14a20d1469472d.zip
everything, mostly, working
-rw-r--r--.gitmodules6
-rw-r--r--.otmonitor.config130
-rw-r--r--boiler-monster/DOCS/IMG_20200514_1758578.jpgbin0 -> 3034508 bytes
-rw-r--r--boiler-monster/DOCS/Opentherm Protocol v2-2.pdf (renamed from DOCS/Opentherm Protocol v2-2.pdf)bin285534 -> 285534 bytes
-rw-r--r--boiler-monster/mr3020/.gitignore1
-rw-r--r--boiler-monster/mr3020/Makefile5
-rwxr-xr-xboiler-monster/mr3020/etc/init.d/stm327
l---------boiler-monster/mr3020/etc/rc.d/S99stm321
-rwxr-xr-xboiler-monster/mr3020/etc/stm32/startup50
-rw-r--r--boiler-monster/mr3020/stamp0
-rwxr-xr-xboiler-monster/mr3020/usr/bin/thermostat70
-rw-r--r--boiler-monster/original-pic-4.2.5/.gitignore6
-rw-r--r--boiler-monster/original-pic-4.2.5/16f88.lkr (renamed from pic/16f88.lkr)0
-rw-r--r--boiler-monster/original-pic-4.2.5/Makefile22
-rw-r--r--boiler-monster/original-pic-4.2.5/build.asm (renamed from pic/build.asm)0
-rw-r--r--boiler-monster/original-pic-4.2.5/build.vbs77
-rw-r--r--boiler-monster/original-pic-4.2.5/ds1820.asm198
-rw-r--r--boiler-monster/original-pic-4.2.5/gateway.asm4524
-rw-r--r--boiler-monster/original-pic-4.2.5/license.txt58
-rw-r--r--boiler-monster/original-pic-4.2.5/ot-gateway.brdbin0 -> 37424 bytes
-rw-r--r--boiler-monster/original-pic-4.2.5/ot-gateway.schbin0 -> 210692 bytes
-rw-r--r--boiler-monster/original-pic-4.2.5/readme.txt43
-rw-r--r--boiler-monster/original-pic-4.2.5/selfprog.asm (renamed from pic/selfprog.asm)0
-rw-r--r--boiler-monster/pic/.gitignore (renamed from pic/.gitignore)0
-rw-r--r--boiler-monster/pic/16f88.lkr76
-rw-r--r--boiler-monster/pic/Makefile (renamed from pic/Makefile)0
-rw-r--r--boiler-monster/pic/build.asm2
-rw-r--r--boiler-monster/pic/docs/otgw-big-sch.png (renamed from pic/docs/otgw-big-sch.png)bin77271 -> 77271 bytes
-rw-r--r--boiler-monster/pic/docs/pinout.txt (renamed from pic/docs/pinout.txt)0
-rw-r--r--boiler-monster/pic/gateway.asm (renamed from pic/gateway.asm)0
-rw-r--r--boiler-monster/pic/selfprog.asm391
-rw-r--r--boiler-monster/stm32/.gitignore (renamed from stm32/.gitignore)1
-rw-r--r--boiler-monster/stm32/Makefile (renamed from stm32/Makefile)0
-rw-r--r--boiler-monster/stm32/Makefile.include (renamed from stm32/Makefile.include)0
-rw-r--r--boiler-monster/stm32/Makefile.rules (renamed from stm32/Makefile.rules)0
-rw-r--r--boiler-monster/stm32/app/1wire.c (renamed from stm32/app/1wire.c)0
-rw-r--r--boiler-monster/stm32/app/1wire.h (renamed from stm32/app/1wire.h)0
-rw-r--r--boiler-monster/stm32/app/Makefile (renamed from stm32/app/Makefile)21
-rw-r--r--boiler-monster/stm32/app/adc.c (renamed from stm32/app/adc.c)33
-rw-r--r--boiler-monster/stm32/app/boiler.ld (renamed from stm32/app/boiler.ld)0
-rw-r--r--boiler-monster/stm32/app/cmd.c (renamed from stm32/app/cmd.c)0
-rw-r--r--boiler-monster/stm32/app/commit.c (renamed from stm32/app/commit.c)0
-rw-r--r--boiler-monster/stm32/app/ds1820.c (renamed from stm32/app/ds1820.c)0
-rw-r--r--boiler-monster/stm32/app/gdb.script (renamed from stm32/app/gdb.script)0
-rw-r--r--boiler-monster/stm32/app/led.c (renamed from stm32/app/led.c)0
-rw-r--r--boiler-monster/stm32/app/main.c (renamed from stm32/app/main.c)1
-rw-r--r--boiler-monster/stm32/app/ot.c (renamed from stm32/app/ot.c)31
-rw-r--r--boiler-monster/stm32/app/ot_phy_rx.c (renamed from stm32/app/ot_phy_rx.c)0
-rw-r--r--boiler-monster/stm32/app/ot_phy_tx.c (renamed from stm32/app/ot_phy_tx.c)0
-rw-r--r--boiler-monster/stm32/app/pic.c (renamed from stm32/app/pic.c)0
-rw-r--r--boiler-monster/stm32/app/pins.h (renamed from stm32/app/pins.h)0
-rw-r--r--boiler-monster/stm32/app/pressure.c164
-rw-r--r--boiler-monster/stm32/app/project.h (renamed from stm32/app/project.h)0
-rw-r--r--boiler-monster/stm32/app/prototypes.h (renamed from stm32/app/prototypes.h)5
-rw-r--r--boiler-monster/stm32/app/ring.c (renamed from stm32/app/ring.c)0
-rw-r--r--boiler-monster/stm32/app/ring.h (renamed from stm32/app/ring.h)0
-rw-r--r--boiler-monster/stm32/app/stdio.c (renamed from stm32/app/stdio.c)0
-rw-r--r--boiler-monster/stm32/app/temp.c (renamed from stm32/app/temp.c)0
-rw-r--r--boiler-monster/stm32/app/ticker.c (renamed from stm32/app/ticker.c)2
-rw-r--r--boiler-monster/stm32/app/usart.c (renamed from stm32/app/usart.c)0
-rw-r--r--boiler-monster/stm32/app/util.c (renamed from stm32/app/util.c)0
-rw-r--r--boiler-monster/stm32/docs/pinout.txt (renamed from stm32/docs/pinout.txt)0
-rw-r--r--boiler-monster/stm32/docs/pm0056.pdf (renamed from stm32/docs/pm0056.pdf)bin2098835 -> 2098835 bytes
-rw-r--r--boiler-monster/stm32/docs/rm0008.pdf (renamed from stm32/docs/rm0008.pdf)bin13016697 -> 13016697 bytes
-rw-r--r--boiler-monster/stm32/docs/stm32f103c8.pdf (renamed from stm32/docs/stm32f103c8.pdf)bin1697666 -> 1697666 bytes
-rw-r--r--boiler-monster/stm32/docs/stm32f103c8t6_pinout_voltage01.png (renamed from stm32/docs/stm32f103c8t6_pinout_voltage01.png)bin679565 -> 679565 bytes
m---------boiler-monster/stm32/libopencm3 (renamed from stm32/libopencm3)0
-rw-r--r--boiler/41hpa-warranty-registration.pdf (renamed from DOCS/41hpa-warranty-registration.pdf)bin37952 -> 37952 bytes
-rw-r--r--boiler/773447801-baxi-platinum-40-user.pdfbin0 -> 508408 bytes
-rw-r--r--boiler/baxi-platinum-installation-and-service-manual.pdfbin0 -> 6386210 bytes
-rw-r--r--munin/.gitignore1
-rwxr-xr-xmunin/boiler552
-rwxr-xr-xmunin/boiler_rx65
m---------otmonitor0
-rw-r--r--radiator-plc/DOCS/DS18B20.pdfbin0 -> 392186 bytes
-rw-r--r--radiator-plc/DOCS/plc_notes.txt93
-rw-r--r--radiator-plc/DOCS/radiator-numbering.txt8
-rw-r--r--radiator-plc/DOCS/valve_01213210.pdfbin0 -> 341645 bytes
-rw-r--r--radiator-plc/DOCS/valve_wiring.txt15
-rw-r--r--radiator-plc/mt300n-v2/.gitignore1
-rw-r--r--radiator-plc/mt300n-v2/Makefile6
-rwxr-xr-xradiator-plc/mt300n-v2/usr/bin/mqtt_if.pl76
-rwxr-xr-xradiator-plc/mt300n-v2/usr/bin/run_mqtt_if6
-rw-r--r--radiator-plc/stm32/.gitignore15
-rw-r--r--radiator-plc/stm32/Makefile8
-rw-r--r--radiator-plc/stm32/Makefile.include44
-rw-r--r--radiator-plc/stm32/Makefile.rules261
-rw-r--r--radiator-plc/stm32/app/1wire.c384
-rw-r--r--radiator-plc/stm32/app/1wire.h9
-rw-r--r--radiator-plc/stm32/app/Makefile59
-rw-r--r--radiator-plc/stm32/app/control.c114
-rw-r--r--radiator-plc/stm32/app/ds18b20.c128
-rw-r--r--radiator-plc/stm32/app/gdb.script2
-rw-r--r--radiator-plc/stm32/app/hexdump.c57
-rw-r--r--radiator-plc/stm32/app/inputs.c26
-rw-r--r--radiator-plc/stm32/app/led.c53
-rw-r--r--radiator-plc/stm32/app/logic.c158
-rw-r--r--radiator-plc/stm32/app/main.c150
-rw-r--r--radiator-plc/stm32/app/outputs.c47
-rw-r--r--radiator-plc/stm32/app/pins.h53
-rw-r--r--radiator-plc/stm32/app/project.h29
-rw-r--r--radiator-plc/stm32/app/prototypes.h80
-rw-r--r--radiator-plc/stm32/app/radiator-plc.ld38
-rw-r--r--radiator-plc/stm32/app/rain.c100
-rw-r--r--radiator-plc/stm32/app/ring.c69
-rw-r--r--radiator-plc/stm32/app/ring.h6
-rw-r--r--radiator-plc/stm32/app/ticker.c97
-rw-r--r--radiator-plc/stm32/app/usart.c149
-rw-r--r--radiator-plc/stm32/app/watchdog.c16
m---------radiator-plc/stm32/libopencm30
-rwxr-xr-xrun_ot_monitor4
-rwxr-xr-xset_radiator_target10
-rw-r--r--stm32/app/pressure.c79
-rwxr-xr-xtasmota-config/configure-radiators74
114 files changed, 8955 insertions, 112 deletions
diff --git a/.gitmodules b/.gitmodules
index 4d99ada..6011582 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,9 @@
[submodule "stm32/libopencm3"]
path = stm32/libopencm3
url = git://git.panaceas.org/stm32/libopencm3
+[submodule "radiator-plc/stm32/libopencm3"]
+ path = radiator-plc/stm32/libopencm3
+ url = git://git.panaceas.org/stm32/libopencm3
+[submodule "boiler-monster/stm32/libopencm3"]
+ path = boiler-monster/stm32/libopencm3
+ url = git://git.panaceas.org/stm32/libopencm3
diff --git a/.otmonitor.config b/.otmonitor.config
new file mode 100644
index 0000000..c426c03
--- /dev/null
+++ b/.otmonitor.config
@@ -0,0 +1,130 @@
+datalog {
+ append false
+ file /root/otdata.txt
+ enable false
+ itemlist {
+ flame
+ dhwmode
+ chmode
+ dhwenable
+ diag
+ fault
+ outside
+ roomtemp
+ setpoint
+ modulation
+ boilertemp
+ returntemp
+ controlsp
+ dhwsetpoint
+ chwsetpoint
+ timestamp
+ feedtemp
+ }
+ interval 30000
+}
+web {
+ sslport 0
+ graphlegend false
+ theme default
+ enable false
+ sslprotocols tls1,tls1.1,tls1.2
+ nopass true
+ certonly false
+ port 8080
+}
+clock {
+ year false
+ date false
+ auto false
+}
+sms {
+ enable false
+ ventilationfault false
+ phonenumber {}
+ route {}
+ sender {}
+ password c2VjcmV0
+ provider VoipPlanet
+ account {}
+ pressure false
+ solarfault false
+ roomcold true
+ watchdogtimer false
+ boilerfault true
+ commproblem false
+}
+email {
+ password c2VjcmV0
+ server {}
+ pressure true
+ roomcold true
+ boilerfault true
+ watchdogtimer true
+ solarfault true
+ commproblem true
+ port 25
+ enable false
+ secure TLS
+ recipient {}
+ user {}
+ sender {}
+ ventilationfault true
+}
+mqtt {
+ retransmit 10
+ format json2
+ port 1883
+ qos 1
+ keepalive 120
+ eventtopic events/central_heating/otmonitor
+ username {}
+ actiontopic actions/otmonitor
+ password {}
+ enable false
+ messages false
+ client new-circe-otmon
+ broker localhost
+}
+logfile {
+ pattern otlog-%Y%m%d.txt
+ directory /root
+ enable false
+}
+tspeak {
+ key {}
+ sync false
+ enable false
+ interval 120
+ field1 roomtemp
+ field2 setpoint
+ field3 boilertemp
+ field4 returntemp
+ field5 controlsp
+ field6 modulation
+ field7 {}
+ field8 {}
+}
+alert {
+ pressurelow 1.5
+ roomcold 14
+ pressurehigh 2.0
+}
+connection {
+ device /dev/ttyUSB0
+ type tcp
+ port 2001
+ host 10.32.139.18
+ enable true
+}
+server {
+ relay false
+ enable true
+ port 7686
+}
+view {
+ tab graph
+ order increasing
+ sort name
+ bitflags false
+}
diff --git a/boiler-monster/DOCS/IMG_20200514_1758578.jpg b/boiler-monster/DOCS/IMG_20200514_1758578.jpg
new file mode 100644
index 0000000..5f159c5
--- /dev/null
+++ b/boiler-monster/DOCS/IMG_20200514_1758578.jpg
Binary files differ
diff --git a/DOCS/Opentherm Protocol v2-2.pdf b/boiler-monster/DOCS/Opentherm Protocol v2-2.pdf
index 47a7f59..47a7f59 100644
--- a/DOCS/Opentherm Protocol v2-2.pdf
+++ b/boiler-monster/DOCS/Opentherm Protocol v2-2.pdf
Binary files differ
diff --git a/boiler-monster/mr3020/.gitignore b/boiler-monster/mr3020/.gitignore
new file mode 100644
index 0000000..859afb1
--- /dev/null
+++ b/boiler-monster/mr3020/.gitignore
@@ -0,0 +1 @@
+stamp
diff --git a/boiler-monster/mr3020/Makefile b/boiler-monster/mr3020/Makefile
new file mode 100644
index 0000000..26efbc3
--- /dev/null
+++ b/boiler-monster/mr3020/Makefile
@@ -0,0 +1,5 @@
+STUFF=$(shell find etc usr \! -type d -print)
+
+stamp: ${STUFF}
+ tar cf - ${STUFF} | ssh boiler-monster "cd / && tar xvpf -"
+ touch $@
diff --git a/boiler-monster/mr3020/etc/init.d/stm32 b/boiler-monster/mr3020/etc/init.d/stm32
new file mode 100755
index 0000000..6dce9bf
--- /dev/null
+++ b/boiler-monster/mr3020/etc/init.d/stm32
@@ -0,0 +1,7 @@
+#!/bin/sh /etc/rc.common
+# Copyright (C) 2006 OpenWrt.org
+
+START=99
+boot() {
+ /etc/stm32/startup > /var/log/stm32.log
+}
diff --git a/boiler-monster/mr3020/etc/rc.d/S99stm32 b/boiler-monster/mr3020/etc/rc.d/S99stm32
new file mode 120000
index 0000000..a3b0774
--- /dev/null
+++ b/boiler-monster/mr3020/etc/rc.d/S99stm32
@@ -0,0 +1 @@
+../init.d/stm32 \ No newline at end of file
diff --git a/boiler-monster/mr3020/etc/stm32/startup b/boiler-monster/mr3020/etc/stm32/startup
new file mode 100755
index 0000000..9422745
--- /dev/null
+++ b/boiler-monster/mr3020/etc/stm32/startup
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+logger -t stm32 "startup"
+
+LOCK=/var/lock/LCK..ttyATH0
+echo $$ > ${LOCK}.tmp
+
+#killall -9 ser2net
+
+FW="/etc/stm32/boiler.fw"
+TFW="/tmp/stm32.fw"
+
+PORT="/dev/ttyATH0"
+
+echo 5 > /sys/class/gpio/export
+echo out > /sys/class/gpio/gpio5/direction
+
+for i in $(seq 0 20); do
+ echo 1 > /sys/class/leds/tp-link\:green\:wps/brightness
+ echo 0 > /sys/class/leds/tp-link\:green\:wps/brightness
+
+ if stm32flash "${PORT}"; then
+ break
+ fi
+ sleep 1
+done
+
+if [ -z "$1" ]; then
+ logger -t stm32 "reading ROM"
+ if ! stm32flash -r "${TFW}" "${PORT}" ; then
+ exit 1
+ fi
+
+ if diff -q "${TFW}" "${FW}"; then
+ logger -t stm32 "Code in ROM matches file"
+ echo "Code in ROM matches file"
+ else
+ logger -t stm32 "Flashing"
+ echo "Code in ROM doesn't match file, flashing"
+ stm32flash -w "${FW}" "${PORT}"
+ fi
+else
+ echo "Flashing..."
+ stm32flash -w "$1" "${PORT}"
+fi
+
+logger -t stm32 "Booting"
+stm32flash -g 0 "${PORT}"
+
+rm -f ${LOCK}
diff --git a/boiler-monster/mr3020/stamp b/boiler-monster/mr3020/stamp
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/boiler-monster/mr3020/stamp
diff --git a/boiler-monster/mr3020/usr/bin/thermostat b/boiler-monster/mr3020/usr/bin/thermostat
new file mode 100755
index 0000000..1689964
--- /dev/null
+++ b/boiler-monster/mr3020/usr/bin/thermostat
@@ -0,0 +1,70 @@
+#!/bin/sh
+
+LOCK=/var/lock/LCK..ttyATH0
+M=10.32.139.1
+
+WATERS=0
+MAX=70
+
+# conventions
+# POWER is power to valve coil
+# OPEN is valve state
+# DELTA is temp difference
+# var1 is low set point
+# var2 is high set point
+# var3 is manual override
+
+# towel radiators
+
+for i in laundry_radiator bathroom_radiator; do
+ O="$(mosquitto_sub -t stat/$i/OPEN -h ${M} -W 1 -C 1)"
+
+ W=0
+ if [ "$O" == "1" ]; then
+ W=$MAX
+ fi
+ WATERS="$WATERS $W"
+
+ logger -t thermostat " $i O=$O W=$W"
+done
+
+#... others where we care about the delta
+
+for i in kstudy_radiator bedroom_radiator spare_bedroom_radiator dd_radiator1 dd_radiator2 dd_radiator3 hall_radiator kitchen_radiator music_room_radiator; do
+ O="$(mosquitto_sub -t stat/$i/OPEN -h ${M} -W 1 -C 1)"
+ D="$(mosquitto_sub -t stat/$i/DELTA -h ${M} -W 1 -C 1 | sed -e 's/\..*$//g') "
+
+ W=0
+ if [ "$O" == "1" ]; then
+ W=$[ $D * 3 + 40]
+ fi
+
+ logger -t thermostat " $i O=$O D=$D W=$W"
+
+ WATERS="$WATERS $W"
+done
+
+
+R=0
+for w in ${WATERS}; do
+ if [ $w -gt $R ]; then
+ R=$w
+ fi
+done
+
+if [ $R -gt $MAX ]; then
+ R=$MAX
+fi
+
+
+
+
+if [ ! -f "${LOCK}" ]; then
+ logger -t thermostat "Requesting water temp of $R"
+ printf "\nCH=%d\n" $R > /dev/ttyATH0
+else
+ logger -t thermostat "Interface is locked, so cant request water temp of $R"
+fi
+
+
+
diff --git a/boiler-monster/original-pic-4.2.5/.gitignore b/boiler-monster/original-pic-4.2.5/.gitignore
new file mode 100644
index 0000000..86351a7
--- /dev/null
+++ b/boiler-monster/original-pic-4.2.5/.gitignore
@@ -0,0 +1,6 @@
+*.err
+*.lst
+*.o
+*.hex
+*.map
+*.cod
diff --git a/pic/16f88.lkr b/boiler-monster/original-pic-4.2.5/16f88.lkr
index b2b3e95..b2b3e95 100644
--- a/pic/16f88.lkr
+++ b/boiler-monster/original-pic-4.2.5/16f88.lkr
diff --git a/boiler-monster/original-pic-4.2.5/Makefile b/boiler-monster/original-pic-4.2.5/Makefile
new file mode 100644
index 0000000..ec282b1
--- /dev/null
+++ b/boiler-monster/original-pic-4.2.5/Makefile
@@ -0,0 +1,22 @@
+AFLAGS=-k -p 16F88 -k
+LDFLAGS=-m -s 16f88.lkr
+
+PROG=gateway
+ASRCS=${PROG}.asm ds1820.asm selfprog.asm
+OBJS=${ASRCS:%.asm=%.o}
+LSTS=${ASRCS:%.asm=%.err}
+ERRS=${ASRCS:%.asm=%.lst}
+
+default:${PROG}.hex
+
+%.o:%.asm
+ gpasm ${AFLAGS} -c -o $@ $<
+
+${PROG}.hex:${OBJS}
+ gplink ${LDFLAGS} -o $@ ${OBJS}
+
+
+clean:
+ /bin/rm -f ${PROG}.hex ${PROG}.map ${PROG}.cod ${OBJS} ${LSTS} ${ERRS}
+
+
diff --git a/pic/build.asm b/boiler-monster/original-pic-4.2.5/build.asm
index 67d0747..67d0747 100644
--- a/pic/build.asm
+++ b/boiler-monster/original-pic-4.2.5/build.asm
diff --git a/boiler-monster/original-pic-4.2.5/build.vbs b/boiler-monster/original-pic-4.2.5/build.vbs
new file mode 100644
index 0000000..989fbf1
--- /dev/null
+++ b/boiler-monster/original-pic-4.2.5/build.vbs
@@ -0,0 +1,77 @@
+'Build.vbs
+'Generate MPLAB include script
+'-----------------------------
+'
+Option Explicit
+
+Dim sh, fso, src, fd
+Dim ts, name, timestr
+Dim proj, regpath, build
+
+Const incfile = "build.asm"
+
+Set sh = CreateObject("WScript.Shell")
+Set fso = CreateObject("Scripting.FileSystemObject")
+
+'Make sure to be working in the project directory
+sh.CurrentDirectory = fso.GetParentFolderName(Wscript.ScriptFullName)
+
+'Determine the last modification time of the sources
+ts = DateValue("1/1/1970")
+For Each name In fso.GetFolder(".").Files
+ If LCase(fso.GetExtensionName(name)) = "asm" Then
+ If fso.GetFileName(name) <> incfile Then
+ Set src = fso.GetFile(name)
+ If DateDiff("S", ts, src.DateLastModified) > 0 Then
+ ts = src.DateLastModified
+ End If
+ End If
+ End If
+Next
+
+'Check if the include file needs to be regenerated
+If fso.FileExists(incfile) Then
+ Set src = fso.GetFile(incfile)
+ If DateDiff("S", ts, src.DateLastModified) >= 0 Then
+ Wscript.StdOut.WriteLine "Source was not changed"
+ Wscript.Quit
+ End If
+End If
+
+'Format the last modification time
+timestr = right("0" & hour(ts), 2) & ":" & _
+ right("0" & minute(ts), 2) & " " & _
+ right("0" & day(ts), 2) & "-" & _
+ right("0" & month(ts), 2) & "-" & year(ts)
+
+'Determine the project name
+If WScript.Arguments.Count > 0 Then
+ 'The project name was specified on the command line
+ proj = WScript.Arguments.item(0)
+Else
+ 'Derive the project name from the current directory
+ proj = fso.GetFileName(fso.GetAbsolutePathName("."))
+End If
+
+'Get the build revision from the registry
+regpath = "HKLM\SOFTWARE\Microchip\MPLAB IDE\Projects\" & proj & "\Revision"
+On Error Resume Next
+build = sh.RegRead(regpath)
+If Hex(Err.number) = "80070002" Then
+ build = 1
+Else
+ build = build + 1
+End If
+On Error GoTo 0
+'Update the registry
+sh.RegWrite regpath, build, "REG_DWORD"
+
+'Create the build.asm file
+Set fd = fso.CreateTextFile(incfile)
+fd.WriteLine "#define build """ & build & """"
+fd.WriteLine "#define tstamp """ & timestr & """"
+fd.Close
+
+'Report the build revision to standard output as well
+Wscript.StdOut.WriteLine "Build revision: " & build
+Wscript.StdOut.WriteLine "Build time & date: " & timestr
diff --git a/boiler-monster/original-pic-4.2.5/ds1820.asm b/boiler-monster/original-pic-4.2.5/ds1820.asm
new file mode 100644
index 0000000..b9b06c1
--- /dev/null
+++ b/boiler-monster/original-pic-4.2.5/ds1820.asm
@@ -0,0 +1,198 @@
+ title "Outside sensor"
+ list p=16F88, b=8, r=dec
+
+#include "p16f88.inc"
+ errorlevel -302, -306
+
+#define IOPORT PORTA,7 ;I/O port used for 1-wire line
+
+;1-wire ROM commands
+#define READROM 0x33
+#define SKIPROM 0xCC
+;1-wire FUNCTION commands
+#define CONVERTT 0x44
+#define READSCRATCHPAD 0xBE
+;1-wire family codes for supported temperature sensors
+#define DS_FAM_DS18S20 0x10
+#define DS_FAM_DS18B20 0x28
+;Return codes, controlling when to perform the next step
+#define DONE 0 ;Next step after one second
+#define CONTINUE 1 ;Next step as soon as possible
+
+ extern float1, float2, flags, temp, StoreOutTemp
+
+ UDATA
+sensorstage res 1
+#define ds18b20 sensorstage,7
+#define restart sensorstage,6
+lsbstorage res 1
+
+
+#define OutsideTemp flags,0 ;Keep in sync with gateway.asm
+#define OutsideInvalid flags,1 ;Keep in sync with gateway.asm
+
+ global Temperature
+Temperature CODE
+Temperature skpz ;Zero bit indicates no message in prog
+ goto Restart ;Sequence interrupted by OT message
+ btfsc restart
+ clrf sensorstage
+ ;Split up the temperature measurement into several short steps,
+ ;each less than 1ms. That way the main loop is allowed to run
+ ;frequently enough for it to be able to do all the necessary
+ ;processing in time, like clearing out the serial receive
+ ;register.
+ movlw high StageTable
+ movwf PCLATH
+ movfw sensorstage
+ andlw b'11111'
+ addlw low StageTable
+ skpnc
+ incf PCLATH,F
+ incf sensorstage,F ;Determine next step of the process
+ movwf PCL ;Jump to the current step
+StageTable goto PresencePulse ;Step 0 - Transmit a reset pulse
+ goto SkipRom ;Step 1 - Address all devices on the bus
+ goto Measure ;Step 2 - Start a measurement
+ retlw DONE ;Step 3 - Allow time for measurement
+ goto PresencePulse ;Step 4 - Transmit a reset pulse
+ goto ReadRom ;Step 5 - Send Read ROM command
+ goto ReadFamily ;Step 6 - Read device family code
+ goto PresencePulse ;Step 7 - Transmit a reset pulse
+ goto SkipRom ;Step 8 - Address all devices
+ goto ReadCommand ;Step 9 - Send Read Scratchpad command
+ goto ReadLSB ;Step 10 - Read temperature value, LSB
+ goto ReadMSB ;Step 11 - Read temperature value, MSB
+ goto Restart ;Step 12 - Start over (never reached)
+
+PresencePulse bcf IOPORT ;Prepare the output latch
+ bsf STATUS,RP0
+ bcf IOPORT ;Drive the output port low
+ call Delay480 ;Wait for the minimum reset duration
+ bsf STATUS,RP0
+ bsf IOPORT ;Release the I/O port
+ bcf STATUS,RP0
+ call Delay60 ;Allow the device time to respond
+ btfsc IOPORT ;Check the 1-wire line
+ goto NoDevice
+ call Delay240 ;Wait for the presence pulse to end
+ retlw CONTINUE
+NoDevice bsf OutsideInvalid ;Outside temperature is not valid
+ goto Restart ;Start the sequence from the top
+
+Measure movlw CONVERTT ;Initiate temperature conversion
+ call WriteByte ;Send the command on the 1-wire bus
+ retlw DONE ;Give devices time to do the conversion
+
+ReadFamily call ReadByte ;Read family code from the 1-wire bus
+ movfw temp
+ sublw DS_FAM_DS18S20 ;Check if the sensor is a DS18S20
+ skpnz
+ retlw CONTINUE ;Found a DS18S20
+ movfw temp
+ sublw DS_FAM_DS18B20 ;Check if the sensor is a DS18B20
+ skpz
+ goto NoDevice ;Not a supported temperature sensor
+ bsf ds18b20 ;Found a DS18B20
+ retlw CONTINUE
+
+ReadCommand movlw READSCRATCHPAD ;The scratchpad contains the temerature
+ goto WriteByte ;Send the command on the 1-wire bus
+
+ReadLSB call ReadByte ;Read the first scratchpad byte
+ movfw temp
+ movwf lsbstorage ;Save the byte for later use
+ retlw CONTINUE
+
+ReadMSB call ReadByte ;Read the second scratchpad byte
+ btfsc ds18b20 ;Check which sensor type was found
+ goto HighPrecision ;A DS18B20 stores the value differently
+ rrf temp,W ;Sign of the temperature value
+ rrf lsbstorage,W ;Determine the full degrees
+ movwf float1
+ clrf float2
+ rrf float2,F ;Determine the half degrees
+ goto Success
+HighPrecision swapf lsbstorage,W ;Get the 1/16 degrees in the high nibble
+ andlw b'11110000'
+ movwf float2 ;Store as outside temperture low byte
+ swapf temp,W ;Get the upper nibble of the degrees
+ andlw b'11110000'
+ movwf temp ;Save for later use
+ swapf lsbstorage,W
+ andlw b'1111' ;Get the lower nibble of the degrees
+ iorwf temp,W ;Combine with the saved upper nibble
+ movwf float1 ;Store as outside temperature high byte
+Success bsf OutsideTemp ;Temperature measurement completed
+ bsf OutsideInvalid ;Invalid until validity is checked
+ xorlw 85 ;Test against powerup value
+ skpnz
+ goto Restart ;Not a valid measurement
+ lcall StoreOutTemp ;Store the measurement
+ retlw CONTINUE
+
+Restart bsf restart ;Don't clear sensorstage for debugging
+ retlw DONE
+
+Delay480 call Delay240 ;480 = 2 * 240
+Delay240 movlw 240 - 8 ;Value for 240uS delay (compensated)
+ goto Delay ;Wait for the specified amount of time
+Delay60 movlw 60 - 6 ;Value for 60uS delay (compensated)
+Delay bsf STATUS,RP0 ;Bank 1
+ bcf PIE1,TMR2IE ;Disable interrupt from timer 2
+ subwf PR2,W ;Calculate the start value
+ bcf STATUS,RP0 ;Bank 0
+ addlw 10 ;Overhead for starting and stopping
+ movwf TMR2 ;Load the timer
+ bsf T2CON,TMR2ON ;Start timer 2
+WaitTimer2 btfss PIR1,TMR2IF ;Check if timer 2 expired
+ goto WaitTimer2 ;Wait some more
+ bcf T2CON,TMR2ON ;Stop timer 2
+ bcf PIR1,TMR2IF ;Prevent unwanted interrupt
+ return
+
+Delay15 call Delay5 ;15 = 3 * 5
+Delay10 call Delay5 ;10 = 2 * 5
+Delay5 nop ;Wait 1 us
+ return ;Subroutine Call + Return = 2us + 2us
+
+ReadByte call ReadNibble ;Read 4 bits from 1-wire bus
+ReadNibble call ReadBit ;Read 1 bit from 1-wire bus
+ call ReadBit ;Read 1 bit from 1-wire bus
+ call ReadBit ;Read 1 bit from 1-wire bus
+ReadBit bcf IOPORT ;Prepare output latch
+ bsf STATUS,RP0 ;TRIS register is in Bank 1
+ bcf IOPORT ;Drive the 1-wire line low
+ clrc ;Prepare carry while waiting
+ bsf IOPORT ;Release the 1-wire bus
+ bcf STATUS,RP0 ;Back to bank 0
+ call Delay10 ;Allow device time to write the bit
+ btfsc IOPORT ;Read the 1-wire bus
+ setc ;Device wrote a "1"
+ rrf temp,F ;Shift bit into the result varaible
+ movlw 45 ;Maximum time a device drives the bus
+ call Delay ;Wait for the device to release the bus
+ return
+
+ReadRom movlw READROM ;Command to read a device's ROM code
+ goto WriteByte ;Send the command on the 1-wire bus
+SkipRom movlw SKIPROM ;Address all devices on the 1-wire bus
+WriteByte movwf temp ;Temporarily store the 1-wire command
+ call WriteNibble ;Write 4 bits to the 1-wire bus
+WriteNibble call WriteBit ;Write 1 bit to the 1-wire bus
+ call WriteBit ;Write 1 bit to the 1-wire bus
+ call WriteBit ;Write 1 bit to the 1-wire bus
+WriteBit rrf temp,F ;Set carry to least significant bit
+WriteSlot bcf IOPORT ;Prepare the output latch
+ bsf STATUS,RP0
+ bcf IOPORT ;Drive the 1-wire line low
+ skpnc ;Carry contains the bit to send
+ bsf IOPORT ;Release the 1-wire line
+ movlw 60 - 10 ;Value for 60uS delay (compensated)
+ call Delay ;Bit duration
+ bsf STATUS,RP0 ;Bank 1
+ bsf IOPORT ;Float the 1-wire line, if necessary
+ bcf STATUS,RP0 ;Bank 0
+ retlw CONTINUE
+
+ end
diff --git a/boiler-monster/original-pic-4.2.5/gateway.asm b/boiler-monster/original-pic-4.2.5/gateway.asm
new file mode 100644
index 0000000..da214b1
--- /dev/null
+++ b/boiler-monster/original-pic-4.2.5/gateway.asm
@@ -0,0 +1,4524 @@
+ title "OpenTherm Gateway"
+ list p=16F88, b=8, r=dec
+
+;Copyright (c) 2009 Schelte Bron
+
+#define version "4.2"
+#define phase "." ;a=alpha, b=beta, .=production
+#define patch "5" ;Comment out when not applicable
+;#define bugfix "1" ;Comment out when not applicable
+#include build.asm
+
+;See the file "license.txt" for information on usage and redistribution of
+;this file, and for a DISCLAIMER OF ALL WARRANTIES.
+
+#ifndef LVP
+ __config H'2007', B'10111101110100'
+#else
+ __config H'2007', B'10111111110100'
+#endif
+ __config H'2008', B'11111111111111'
+
+ errorlevel -302
+
+;The OpenTherm Gateway is located between the thermostat and the boiler of a
+;central heating system. In monitor mode it passes its inputs unmodified to
+;the outputs using only hardware functions. In back to back mode the messages
+;from the thermostat are passed to the boiler under firmware control. In some
+;cases the responses from the boiler may be overridden by the gateway and
+;external data is injected into the messages. If no thermostat, or a simple
+;on/off thermostat is connected, the gateway will generate its own opentherm
+;messages to communicate with the Opentherm boiler.
+;
+;The gateway starts in back to back mode. It switches to monitor mode if for
+;some reason the firmware locks up, resulting in a watchdog timeout.
+;
+;Interaction with the gateway takes place via a serial interface. The interface
+;uses 9600 baud, 8 bits, no parity.
+;
+;Messages on the OpenTherm link are reported as a letter follwed by 8 hex
+;digits. The letter indicates if the message was received from the boiler (B),
+;or from the thermostat (T). If the gateway modifies the message to the boiler
+;in any way, the modified request is also reported (R), as is a modified answer
+;back to the thermostat (A). The hex digits represent the message contents.
+;
+;The serial interface also allows commands to be sent to the gateway. Commands
+;consist of two letters, an equals-sign, and an argument. A command must be
+;terminated by a carriage-return character (13, 0xd). The gateway understands
+;the following commands:
+;OT=+15.4 Specify the outside temperature
+;TT=18.5 Change the temperature setpoint (temporary). Use 0 (+0.0) to
+; return to the thermostat's normal programming.
+;TC=+16.0 Change the temperature setpoint (continuously). Use +0.0 to
+; return to the thermostat's normal programming.
+;SB=15.5 Configure the setback temperature to use in combination with
+; GPIO functions HOME (5) and AWAY (6).
+;CS=10.0 Override the control setpoint from the thermostat
+;VS=25 Override the ventilation setpoint from the thermostat
+;MM=40 Override the maximum relative modulation from the thermostat
+;SH=72.0 Set the maximum central heating setpoint.
+;SW=60.0 Set the domestic hot water setpoint.
+;SC=14:45/6 Set the clock and the day of the week, 1 = Monday, 7 = Sunday
+;VR=3 Adjust the reference voltage between 0.6V and 2.5V
+;HW=1 Control DHW. Use T or A to let the thermostat control DHW.
+;LA=F Configure the function for LED A, defaults to Flame on
+;LB=X Configure the function for LED B, defaults to Transmitting
+;LC=O Configure the function for LED C, defaults to Setpoint Override
+;LD=M Configure the function for LED D, defaults to Maintenance
+;LE=P Configure the function for LED E, defaults to Raised Power
+;LF=C Configure the function for LED F, defaults to Comfort Mode
+; Possible function codes to use for the LEDs are:
+; R: Receiving an Opentherm message from the thermostat or boiler
+; X: Transmitting an Opentherm message to the thermostat or boiler
+; T: Transmitting or receiving a message on the master interface
+; B: Transmitting or receiving a message on the slave interface
+; O: Remote setpoint override is active
+; F: Flame is on
+; H: Flame is on for central heating
+; W: Flame is on for hot water
+; C: Comfort mode (Domestic Hot Water Enable) is on
+; E: Communication error has been detected
+; M: Boiler requires maintenance
+; P: Raised power mode
+;GA=3 Configure the function for GPIO pin A, defaults to No Function
+;GB=4 Configure the function for GPIO pin B, defaults to No Function
+; Possible GPIO functions are:
+; 0 (input): NONE: No GPIO function.
+; 1 (output): GND: Port is constantly pulled low.
+; 2 (output): VCC: Port is constantly pulled high.
+; 3 (output): LEDE: Port outputs the LED E function.
+; 4 (output): LEDF: Port outputs the LED F function.
+; 5 (input): HOME: High - Cancel remote room setpoint override
+; Low - Sets thermostat to setback temperature
+; 6 (input): AWAY: High - Sets thermostat to setback temperature
+; Low - Cancel remote room setpoint override
+; 7 (in/out): DS1820: Drive DS18[SB]20 for outside temperature
+;PR=L Print selected information. Supported information sources are:
+; A: About opentherm gateway (prints the welcome message)
+; B: Build date and time
+; C: Clock speed of the PIC
+; G: Report the GPIO pin configuration
+; I: Report the GPIO pin input states
+; L: Configured LED functions. This prints a string like FXOMPC
+; M: Report the gateway mode
+; O: Setpoint override
+; P: Current Smart-Power mode.
+; R: The state of the automatic Remeha thermostat detection.
+; S: Print the configured setback temperature
+; T: Tweak settings: Ignore Transition and Override in High Byte
+; V: Report the reference voltage setting
+; W: Domestic hot water setting
+;PS=1 Select whether to only print a summary line (1) on request, or
+; every received/transmitted opentherm message (0).
+;AA=42 Add alternative message to send to the boiler instead of a
+; message that is known not to be supported by the boiler.
+;DA=42 Delete a single occurrence from the list of alternative messages
+; to send to the boiler
+;SR=18:1,230 Set the response for a DataID. Either one or two data bytes
+; must be provided.
+;CR=18 Clear the response for a DataID.
+;UI=9 Tell the gateway which DataID's are not supported by the boiler,
+; so the gateway can inject its own message.
+;KI=9 Remove a DataID from the list of ID's that have been configured
+; as unsupported by the boiler.
+;RS=HBS Reset boiler counter. Supported counters are:
+; HBS: Central heating burner starts
+; HBH: Central heating burner operation hours
+; HPS: Central heating pump starts
+; HPH: Central heating pump operation hours
+; WBS: Domestic hot water burner starts
+; WBH: Domestic hot water burner operation hours
+; WPS: Domestic hot water pump starts
+; WPH: Domestic hot water pump operation hours
+;GW=1 Switch between monitor (0) and gateway (1) mode
+;IT=1 Control whether multiple mid-bit line transitions are ignored.
+; Normally more than one such transition results in Error 01.
+;OH=0 Copy the low byte of MsgID 100 also into the high byte to work
+; with thermostats that look at the wrong byte (Honeywell Vision)
+;FT=D Force thermostat model to Remeha Celcia 20 (C) or iSense (I).
+;DP=1F Set the debug pointer to the specified file register
+
+;#######################################################################
+; Peripheral use
+;#######################################################################
+;Comparator 1 is used for requests. The input is connected to the thermostat.
+;In monitor mode, the output is connected to the boiler.
+;Comparator 2 is used for responses. The input is connected to the Boiler.
+;In monitor mode, the output is connected to the thermostat.
+
+;Timer 0 uses a 1:64 prescaler. It is used to measure the 10ms line idle-time
+;and is the time base for lighting up the error LED for 2 seconds
+;Timer 1 has a 1:8 prescaler, which gives it a period of 524288us, or roughly
+;half a second. It is used for flashing the override LED.
+;Timer 2 is used to create a 250uS time base. When the first low to high
+;transition on one of the comparators is detected the timer is started with
+;a 100us head start. A variable is used to count timer 2 roll-overs. Any
+;transition of the comparator while the lowest two bits of the rollover
+;counter are binary 10 marks a real bit. These bits are shifted into a 32 bit
+;message buffer. Also timer 2 is resynchronized. When 34 bits have been
+;received, the gateway will send a report to the serial interface. The report
+;consists of either a 'T' or a 'B' and eight hex digits. The first letter
+;indicates whether the message was received from the thermostat or the
+;boiler. The hex digits represent the value of the 32-bits of data in the
+;OpenTherm message.
+
+;The AUSART is configured for full duplex asynchronous serial communication at
+;9600 baud, 8 bits, no parity. It is used for reporting about opentherm messages
+;and receiving commands.
+
+;Analog input 0 is used to measure the voltage level on the opentherm line to
+;the thermostat. This way the code can distinguish between a real opentherm
+;thermostat and a simple on/off thermostat. An opentherm thermostat will keep
+;the line at a few volts (low) or between 15 and 18 volts (high). An on/off
+;thermostat will either short-circuit the line (0 volts) or leave the line open
+;(20+ volts).
+
+#include "p16f88.inc"
+
+;Define the speed of the PIC clock
+#define mhzstr "4"
+
+;BAUD contains the SPBRG value for 9600 baud
+ constant BAUD=25
+
+;PERIOD is the value to be loaded into PR2 to obtain a 250uS period for TMR2
+ constant PERIOD=249
+
+;PRELOADT0 gives TMR0 a head start so the next rollover (with a 1:64 prescaler)
+;will happen after 10 ms. This is the time the line must be stable to consider
+;it idle after an error was detected.
+ constant PRELOADT0=100
+
+;ONESEC contains the number of timer 0 overflows (with a 1:64 prescaler)
+;that happen in roughly 1 second. This controls the frequency for generating
+;Opentherm requests to the boiler when no Opentherm thermostat is connected.
+ constant ONESEC=61 ;61 * 64 * 256 = 999424
+;TWOSEC contains the number of timer 0 overflows (with a 1:64 prescaler)
+;that happen in roughly 2 seconds. This is the time the error LED will stay
+;lit after an error has been detected.
+ constant TWOSEC=2*ONESEC
+
+;Opentherm message types
+ constant T_READ=0x00
+ constant T_WRITE=0x10
+ constant T_INV=0x20
+ constant B_RACK=0x40
+ constant B_WACK=0x50
+ constant B_INV=0x60
+ constant B_UNKN=0x70
+
+;Opentherm Data IDs
+ constant MSG_STATUS=0
+ constant MSG_CTRLSETPT=1
+ constant MSG_REMOTECMD=4
+ constant MSG_FAULTCODE=5
+ constant MSG_REMOTEPARAM=6
+ constant MSG_SETPTOVERRIDE=9
+ constant MSG_MAXMODULATION=14
+ constant MSG_ROOMSETPOINT=16
+ constant MSG_MODULATION=17
+ constant MSG_CHPRESSURE=18
+ constant MSG_DAYTIME=20
+ constant MSG_BOILERTEMP=25
+ constant MSG_OUTSIDETEMP=27
+ constant MSG_RETURNTEMP=28
+ constant MSG_DHWBOUNDS=48
+ constant MSG_MAXCHBOUNDS=49
+ constant MSG_DHWSETPOINT=56
+ constant MSG_MAXCHSETPOINT=57
+ constant MSG_FUNCOVERRIDE=100
+
+;Opentherm remote requests
+ constant RCMD_TELEFOON=170
+
+;Some default values to use if the boiler hasn't specified its actual limits.
+ constant MIN_DHWSETPOINT=20
+ constant MAX_DHWSETPOINT=80
+ constant MIN_CHSETPOINT=10
+ constant MAX_CHSETPOINT=90
+
+;Line voltage thresholds
+ constant V_SHORT=6 ;6 / 256 * 5 * 37.7 / 4.7 = 0.94V
+ constant V_LOW=58 ;58 / 256 * 5 * 37.7 / 4.7 = 9.09V
+ constant V_OPEN=122 ;122 / 256 * 5 * 37.7 / 4.7 = 19.11V
+
+ extern SelfProg, Temperature
+
+;Variables accessible from all RAM banks
+ UDATA_SHR
+s_wreg res 1 ;Used in interrupt routine
+s_status res 1 ;Used in interrupt routine
+s_fsr res 1 ;Used in interrupt routine
+
+temp res 1
+temp2 res 1
+
+byte1 res 1
+byte2 res 1
+byte3 res 1
+byte4 res 1
+#define MsgParity byte1,7
+#define MsgResponse byte1,6
+
+originaltype res 1
+originalreq res 1
+databyte1 res 1
+databyte2 res 1
+loopcounter res 1
+
+mode res 1
+#define MonitorMode mode,0 ;The code expects this to be bit 0
+#define ChangeMode mode,1
+#define Overrun mode,2
+#define FailSafeMode mode,3
+
+Bank0data UDATA 0x20
+rxbuffer res 16 ;Serial receive buffer
+
+;Variables for temporary storage
+s_pclath res 1 ;Used in interrupt routine
+tempvar0 res 1
+tempvar1 res 1
+tempvar2 res 1
+float1 res 1
+float2 res 1
+
+;Variables for longer lasting storage
+rxpointer res 1
+txpointer res 1
+bitcount res 1 ;Count of bits remaining in a message
+celciasetpoint res 1 ;Setpoint times 5 as needed for a Celcia 20
+setpoint1 res 1
+setpoint2 res 1
+outside1 res 1
+outside2 res 1
+clock1 res 1
+clock2 res 1
+dhwsetpoint1 res 1
+dhwsetpoint2 res 1
+maxchsetpoint1 res 1
+maxchsetpoint2 res 1
+controlsetpt1 res 1
+controlsetpt2 res 1
+ventsetpoint res 1
+debug res 1
+outputmask res 1
+messagetype res 1
+alternativecmd res 1
+valueindex res 1
+txavailable res 1
+resetflags res 1
+prioritymsgid res 1
+dhwsetpointmin res 1
+dhwsetpointmax res 1
+chsetpointmin res 1
+chsetpointmax res 1
+minutetimer res 1
+#define ChangeTime minutetimer,7
+
+SecCounter res 1
+MessagePtr res 1
+RemOverrideFunc res 1
+#define ManChangePrio RemOverrideFunc,0
+#define ProgChangePrio RemOverrideFunc,1
+#define TempChangePrio RemOverrideFunc,5
+
+LineVoltage res 1
+MaxModLevel res 1
+GPIOFunction res 1 ;Funtions assigned to GPIO pins
+
+flags res 1 ;General bit flags
+#define OutsideTemp flags,0 ;Keep in sync with ds1820.asm
+#define OutsideInvalid flags,1 ;Keep in sync with ds1820.asm
+#define SummaryRequest flags,2
+#define BoilerAlive flags,3
+#define BoilerFault flags,4
+#define NegativeTemp flags,6
+#define LeadingZero flags,7
+
+stateflags res 1 ;More bit flags
+#define MessageRcvd stateflags,0
+#define Unsolicited stateflags,1
+#define HideReports stateflags,2
+#define CommandComplete stateflags,3
+#define NoAlternative stateflags,4
+#define AlternativeUsed stateflags,5
+#define OverrideUsed stateflags,6
+#define BoilerResponse stateflags,7
+
+msgflags res 1 ;Used in interrupt routine
+#define NextBit msgflags,0
+#define Transition msgflags,1
+#define Comparator1 msgflags,2 ;Must be bit 2
+#define Comparator2 msgflags,3 ;Must be bit 3
+#define Transmit msgflags,4
+#define Intermission msgflags,5
+#define MidBit msgflags,6
+#define Parity msgflags,7
+#define NextBitMask 1 << 0
+
+quarter res 1 ;Count of 1/4 ms, used in interrupt routine
+#define LineStable quarter,0
+#define RealBit quarter,1
+#define BitClkCarry quarter,3
+
+tstatflags res 1 ;Used in- and outside interrupt routine
+#define SmartPower tstatflags,7 ;Thermostat supports Smart Power
+#define DisconnectEvent tstatflags,5 ;Long high without Smart Power
+#define OneSecond tstatflags,4 ;Time to send the next message
+#define Request tstatflags,3 ;Event on the thermostat line
+#define PowerReport tstatflags,2
+#define HighPower tstatflags,1
+#define RaisedPower tstatflags,0
+
+override res 1
+#define OverrideBlink override,7 ;Keep track of blinking LED
+#define OverrideWait override,6 ;Override has not yet been sent
+#define OverrideFunc override,5 ;ID100 has not yet been sent
+#define OverrideClr override,4 ;Cancel override request
+#define OverrideReq override,3 ;Override temp was requested
+#define OverrideAct override,2 ;Override accepted by thermostat
+;Bits 0&1 are used as a counter to allow 3 attempts for the override to stick
+
+initflags res 1
+#define InitParameter initflags,0
+#define InitHotWater initflags,1
+#define InitHeating initflags,2
+#define WaterSetpoint initflags,3
+#define MaxHeatSetpoint initflags,4
+#define NoResetUnknown initflags,6
+#define PriorityMsg initflags,7
+
+onoffflags res 1
+#define dhwupdate onoffflags,0 ;Must be bit 0
+#define maxchupdate onoffflags,1 ;Must be bit 1
+#define NoThermostat onoffflags,2
+#define HeatDemand onoffflags,3
+#define HotWaterSwitch onoffflags,4
+#define HotWaterEnable onoffflags,5
+#define UserMaxModLevel onoffflags,6
+#define CHModeOff onoffflags,7
+
+gpioflags res 1
+#define gpioaway gpioflags,0
+#define VirtualLedE gpioflags,1
+#define VirtualLedF gpioflags,2
+#define gpio_port1 gpioflags,6
+#define gpio_port2 gpioflags,7
+#define gpio_mask gpioflags
+
+;Flags to work around quirks of remeha thermostats
+remehaflags res 1
+#define TStatRemeha remehaflags,7 ;Auto-detected Remeha Thermostat
+#define TStatISense remehaflags,6 ;Thermostat is Remeha iSense
+#define TStatCelcia remehaflags,5 ;Thermostat is Remeha Celcia 20
+#define TStatManual remehaflags,4 ;Thermostat model set manually
+
+s_timer2 res 1 ;Store timer 2 value, used in interrupt routine
+
+errornum res 1
+errortimer res 1
+
+repeatcount res 1 ;Count same message from the thermostat
+
+TSPCount res 1
+TSPIndex res 1
+
+;If the serial receive pin is disconnected and it picks up some 50Hz noise, the
+;AUSART would detect a BREAK every 20ms. To prevent the gateway from repeatedly
+;resetting in this situation, wait for 32 TMR0 overflows (slightly over 0.5s)
+;without a BREAK before acting on a BREAK again.
+BreakTimer res 1
+#define NoBreak BreakTimer,5 ;Set after 32 TMR0 overflows
+
+settings res 1 ;Bits 0-4: voltage reference
+#define IgnoreErr1 settings,5 ;For bouncing high<->low changes
+#define OverrideHigh settings,6 ;Copy MsgID100 data to high byte
+
+resetreason res 1
+boilercom res 1
+
+ global float1, float2, flags, temp, StoreOutTemp
+
+Bank1data UDATA 0xA0
+;The functions that can be assigned to a LED are referred to by an uppercase
+;letter. One byte is reserved for each possible function. Bits 3, 4, 6, and 7
+;indicate if the function is assigned to one or more of the four LED outputs.
+;Bit 0 is used to keep track of the current state of the function. That makes
+;it possible to immediately set a LED to the correct state upon assignment of
+;the function.
+functions res 26
+
+;The gateway keeps track of which messages are not supported by the boiler.
+;The code counts the times the boiler responds with an Unknown-DataID in two
+;bits per DataID. If the counter for a specific DataID reaches 3, the gateway
+;stops sending the DataID to the boiler. The next time the thermostat requests
+;that information again, the gateway will actually send a different request to
+;the boiler. This allows gathering of information that the thermostat does not
+;normally request.
+unknownmap res 32 ;2 bits per DataID for 128 DataID's
+
+; 6 bytes remaining in Bank 1
+
+Bank1values UDATA 0xE0
+ResponseValues1 res 16 ;DataID's of fake responses
+
+
+Bank2data UDATA 0x110
+;Keep track of the data values of up to 40 DataID's
+valuestorage res 80 ;2 bytes per DataID for up to 40 DataID's
+Bank2values UDATA 0x160
+ResponseValues2 res 16 ;High byte of fake responses
+
+Bank3data UDATA 0x190
+;Use all available RAM in bank 3 for the transmit buffer
+ constant TXBUFSIZE=80
+txbuffer res TXBUFSIZE
+Bank3values UDATA 0x1E0
+ResponseValues3 res 16 ;Low byte of fake responses
+
+;I/O map
+#define MasterIn CMCON,C1OUT
+#define SlaveIn CMCON,C2OUT
+#define SlaveOut PORTA,3
+#define MasterOut PORTA,4
+#define RXD PORTB,2
+#define TXD PORTB,5
+#define CCP1 PORTB,0
+#define LED_A PORTB,3
+#define LED_B PORTB,4
+#define LED_C PORTB,6
+#define LED_D PORTB,7
+#define RSET PORTB,1 ;Used by self-programming
+
+#define SlaveMask b'00001000'
+#define MasterMask b'00010000'
+
+package macro pkg
+pkg code
+pkg
+ endm
+
+pcall macro rtn
+ lcall rtn
+ pagesel $
+ endm
+
+;Skip the next instruction (bit 7 of PCLATH is always 0)
+skip macro
+ btfsc PCLATH,7
+ endm
+
+;Get the output of the active comparator into the carry bit
+getcompout macro
+ bsf STATUS,RP0
+ rlf CMCON,W ;Get the output of comparator 2
+ bcf STATUS,RP0
+ btfsc Request ;A request goes through comparator 1
+ addlw b'10000000' ;Cause a carry if C1OUT is high
+ endm
+
+;The first thing to do upon a reset is to allow the firmware to be updated.
+;So no matter how buggy freshly loaded firmware is, if the first two command
+;have not been messed up, the device can always be recovered without the need
+;for a PIC programmer. The self-programming code times out and returns after
+;a second when no firmware update is performed.
+;
+
+ResetVector code 0x0000
+ lcall SelfProg ;Always allow a firmware update on boot
+ lgoto Start ;Start the opentherm gateway program
+
+;Handle interrupts. This piece of code saves the important registers and finds
+;out which interrupt fired. It then calls a subroutine to handle that specific
+;interrupt. Upon return from the subroutine, it restores the saved registers.
+;
+InterruptVector code 0x0004
+ movwf s_wreg ;Save the work register
+ swapf STATUS,W ;Don't change any status flags
+ clrf STATUS ;Start with a known state
+ movwf s_status ;Save the (swapped) status register
+ movfw FSR
+ movwf s_fsr ;Save the FSR register
+ movfw PCLATH
+ movwf s_pclath
+ pagesel Interrupt
+ movfw TMR2
+ movwf s_timer2 ;Save the current value of timer2
+ ;Process the timer 2 interrupt before the comparator
+ ;interrupt to get the correct overflow count
+ btfsc PIR1,TMR2IF ;Check if timer 2 matched
+ call timer2int
+ btfsc PIR2,CMIF ;Check if an input changed
+ call inputint
+ ;Restore the registers and return from the interrupt
+ movfw s_pclath
+ movwf PCLATH
+ movfw s_fsr ;Get the old value of the FSR register
+ movwf FSR ;Restore the indirect addressing pointer
+ swapf s_status,W ;Swap the saved status register back
+ movwf STATUS ;Restore the status register
+ swapf s_wreg,F ;Don't change any status flags
+ swapf s_wreg,W ;Restore the work register
+ retfie ;End of interrupt routine
+
+;########################################################################
+;Interrupt routines
+;########################################################################
+;
+;The meaning of events on the opentherm interface depends on the logical levels
+;and timing. The state is tracked via a number of bits and variables.
+;
+;TMR2ON==0 && CXOUT==1: Line idle
+;TMR2ON==0 && CXOUT:=0: Start T2
+;bitcount==0 && quarter<=3 && CXOUT:=1: Start of opentherm message. Restart T2
+;bitcount>0 && quarter<=3 && CXOUT:=0: Midbit transition, ignore.
+;bitcount>0 && quarter<=3 && CXOUT:=1: Midbit transition, ignore.
+;bitcount>0 && quarter>3 && quarter<=6 && CXOUT:=0: Bit: 0. Restart T2
+;bitcount>0 && quarter>3 && quarter<=6 && CXOUT:=1: Bit: 1. Restart T2
+;quarter:=20 && C1OUT==0 && C1INV==0: Smartpower on.
+;quarter>20 && quarter<80 &&
+
+ package Interrupt
+
+;Comparator interrupt routine
+inputint bsf STATUS,RP0
+ swapf CMCON,W ;Read to end the mismatch condition
+ bcf STATUS,RP0
+ bcf PIR2,CMIF ;Clear the interrupt
+ xorwf msgflags,W ;Determine which comparator has changed
+ andlw b'00001100' ;Filter out the comparator bits
+ skpnz ;Make sure there actually was a change
+ return ;False trigger
+ xorwf msgflags,F ;Update the saved state
+ andlw b'00000100' ;Check for a change from the thermostat
+ skpnz ;Thermostat wants attention
+ btfss Request ;Or not receiving a request
+ goto inputmark ;then continue
+ movlw -1 ;Suppress errors for a while
+ movwf errornum
+ return
+
+;The thermostat may try to change power levels while the gateway is in the
+;process of transmitting a message, or receiving a message from the boiler.
+;The same timer is used for all opentherm communication, so the gateway can't
+;handle the two things at the same time. Ignoring the thermostat may result in
+;incorrect power levels leading to user-visible artefacts (like a flashing
+;backlight of the thermostat screen). So, for a smoother user experience, we
+;abort the current operation when the thermostat asks for attention.
+inputmark skpz ;Z bit is cleared if comparator1 changed
+ btfsc Request ;Already listening to the thermostat
+ goto inputboiler ;Not a new event from the thermostat
+ bsf Request ;Receiving from the thermostat
+ tstf bitcount ;Message in progress?
+ skpnz
+ btfsc Transmit ;Transmission in progress?
+ bcf Request
+ goto activity ;End of the interrupt routine
+inputboiler tstf bitcount ;Is a message in progress?
+ skpnz
+ goto InputEvent ;Line state change outside message
+ movlw 3 ;3 overflows of timer 2 = 750 us
+ subwf quarter,W ;Check the time since the last bit
+ skpc ;More than 750 us
+ goto midbitchange
+ getcompout ;Get the comparator out
+ decfsz bitcount,F ;Is this the stop bit?
+ goto storebit ;If not, store the bit in the buffer
+ skpc ;Carry is set when the line is low
+ goto error02 ;A stop bit must be logical 1
+ btfsc Parity ;Check for even parity
+ goto error04 ;Parity error
+ bsf MessageRcvd ;Indicate that a message was received
+ pagesel Message
+ movlw 'R' ;Receive function
+ call SwitchOffLED ;Switch off the receive LED
+ movlw 'T' ;Thermostat function
+ call SwitchOffLED ;Switch off the thermostat LED
+ movlw 'B' ;Boiler function
+ call SwitchOffLED ;Switch off the boiler LED
+ pagesel Interrupt
+ bcf Request ;Thermostat is done
+inputspike bcf T2CON,TMR2ON ;Stop timer 2
+ goto activity ;End of the interrupt routine
+
+inputabort clrf bitcount ;Abort message
+ bcf Transmit ;Clear flag
+ movlw b'00011000' ;Default state of the outputs
+ btfsc RaisedPower
+ movlw b'00001000' ;Idle state of port 4 is high
+ xorwf PORTA,W ;Check against the current state
+ andlw b'00011000' ;Only consider port 3 and 4
+ xorwf PORTA,F ;Update the ports
+ pagesel Message
+ movlw 'X'
+ call SwitchOffLED ;Switch off transmit LED
+ movlw 'B'
+ call SwitchOffLED ;Switch off boiler LED
+ pagesel Interrupt
+ goto restarttimer ;Reset T2 and 1/4 ms counter
+
+storebit ;Store the current bit in the message buffer
+ movlw 1 << 7 ;Mask for the bit flag tracking parity
+ skpnc
+ xorwf msgflags,F ;Update the parity
+ rlf byte4,F ;Shift the 32 bits receive buffer
+ rlf byte3,F ;Shift bits 8 through 15
+ rlf byte2,F ;Shift bits 16 through 23
+ rlf byte1,F ;Shift bits 24 through 31
+ bcf MidBit ;Mid-bit transitions are allowed again
+restarttimer clrf quarter ;Restart 1/4 ms counter
+ movfw s_timer2 ;Timer 2 value just after the interrupt
+ subwf TMR2,F ;Adjust the timer value
+ movlw 6 ;Difference between 255 and PR2
+ skpc
+ subwf TMR2,F ;Compensate for early reset of T2
+ bcf PIR1,TMR2IF ;Interrupt routine is less than 250us.
+activity movlw PRELOADT0 ;Idle time preload value
+ movwf TMR0 ;Restart idle timer
+ return ;End of the interrupt routine
+
+InputEvent ;The line state changed while not receiving a message. Need to
+ ;figure out what is going on.
+ btfsc T2CON,TMR2ON ;Timer2 is stopped when the line is idle
+ goto InputProcess
+ ;If an unreported error has happened, we're still waiting for
+ ;the line to become idle again.
+ tstf errornum ;Is there an unreported error?
+ skpz
+ goto activity ;Restart the non-idle timer
+ ;Change of a previously idle line
+ movlw 42 ;Time since start of interrupt routine
+ movwf TMR2 ;Initialize timer 2
+ bsf T2CON,TMR2ON ;Start timer2
+ clrf quarter ;Reset the 1/4 ms counter
+ bsf STATUS,RP0
+ bsf PIE1,TMR2IE ;Enable timer2 interrupts
+ bcf STATUS,RP0
+ goto activity ;The change has no meaning
+
+InputProcess ;A second line change happened shortly after the previous one.
+ movlw 10
+ subwf quarter,W
+ skpnc ;Less than 2.5 ms since the last change
+ goto restarttimer
+InputFast getcompout ;Get the comparator output
+ skpc ;Carry is set when the line is low
+ goto activity ;Not a start bit
+ bsf STATUS,RP0
+ btfss CMCON,C1INV ;Carry is guaranteed to be set here
+ clrc ;Clear carry if not inverting
+ bcf STATUS,RP0
+ skpnc ;Thermostat line not inverted?
+ btfsc HighPower ;High power mode not yet established?
+ goto StartBit
+ bsf HighPower ;Start of message on inverted line
+ bsf PowerReport ;Report high power mode
+StartBit tstf quarter ;Check if pulse was less than 250us
+ skpnz
+ goto inputspike ;Ignore spikes
+ pagesel Message
+ movlw 'R' ;Receive function
+ call SwitchOnLED ;Turn on the receive LED
+ movlw 'B' ;Boiler function
+ btfsc Request
+ movlw 'T' ;Thermostat function
+ call SwitchOnLED ;Switch on the boiler or thermostat LED
+ pagesel Interrupt
+ movlw 33 ;Message length: 32 bits + stop bit - 1
+ movwf bitcount ;Initialize the counter
+ bcf Parity ;Initialize the parity check flag
+ bcf MidBit ;No mid-bit transitions yet
+ goto restarttimer ;Significant transition of the start bit
+
+;Timer 2 interrupt routine
+
+timer2int bcf PIR1,TMR2IF ;Clear the interrupt flag
+ incf quarter,F ;Update the 1/4 ms counter
+ btfsc Transmit ;Transmitting a message?
+ goto timer2xmit
+ tstf bitcount ;Message in progress?
+ skpnz
+ goto timer2idle
+ btfsc BitClkCarry ;Bit transition happened on time?
+ goto error03 ;Report missing transition
+ return
+
+timer2xmit btfsc LineStable ;Nothing to do in a stable interval
+ return
+ tstf bitcount
+ skpnz
+ goto timer2xmitend
+ clrw ;Initialize work register
+ btfss NextBit ;Find out the desired output level
+ movlw b'00011000' ;Set bits matching the opentherm outputs
+ btfsc RaisedPower ;Using raised power to the thermostat?
+ xorlw b'00010000' ;Invert the bit for the thermostat line
+ xorwf PORTA,W ;Compare with the current port levels
+ andwf outputmask,W ;Only apply for the relevant output
+ xorwf PORTA,F ;Update the output port
+ btfsc RealBit ;Check if this was the start of a bit
+ goto InvertBit ;Next time send the inverted value
+ bcf NextBit ;Start with assumption next bit is 0
+ setc ;Prepare to shift in a stop bit
+ rlf byte4,F ;Shift the full 32 bit message buffer
+ rlf byte3,F ;Shift bits 8 through 15
+ rlf byte2,F ;Shift bits 16 through 23
+ rlf byte1,F ;Shift bits 24 through 31
+ skpnc ;Test the bit shifted out of the buffer
+ bsf NextBit ;Next time, send a logical 1
+ clrf quarter
+ decf bitcount,F ;Test for the end of the message
+ return ;Return from the interrupt routine
+
+timer2xmitend bcf T2CON,TMR2ON ;Stop timer 2
+ bcf Transmit ;Finished transmitting
+ btfsc outputmask,4 ;Transmission to the thermostat?
+ bsf Intermission ;Message exchange is finished
+ pagesel Message
+ movlw 'X' ;Transmit function
+ call SwitchOffLED ;Switch off the transmit LED
+ movlw 'T' ;Thermostat function
+ call SwitchOffLED ;Switch off the thermostat LED
+ movlw 'B' ;Boiler function
+ call SwitchOffLED ;Switch off the boiler LED
+ pagesel Interrupt
+ return ;Return from the interrupt routine
+
+InvertBit movlw NextBitMask ;Load a mask for the NextBit flag
+ xorwf msgflags,F ;Invert the bit
+ return ;Return from the interrupt routine
+
+timer2idle btfss Request ;Event was on the thermostat line?
+ return ;Other events only apply to thermostat
+ movfw quarter
+ sublw 20 ;New state persisted for 5ms?
+ skpnz
+ goto timer2invert
+ movlw 80
+ subwf quarter,W ;20ms since last line event?
+ skpc
+ return
+ bcf T2CON,TMR2ON ;No line event for 20ms
+ bcf Request ;Thermostat is done
+ bsf STATUS,RP0
+ btfss CMCON,C1INV ;Carry is guaranteed to be set here
+ clrc ;Clear carry if not inverting
+ bcf STATUS,RP0
+ skpc ;Thermostat line inverted?
+ return
+ bsf HighPower ;low/medium => high power transition
+ bsf PowerReport ;Power levels have changed
+ return
+
+timer2invert btfsc Comparator1 ;Line high (according to current mode)?
+ return ;Not interresting
+ btfss SmartPower ;Thermostat supports Smart Power?
+ goto ExtendedHigh ;Long high level without Smart Power
+ bsf STATUS,RP0 ;Switch to bank 1
+ movlw 1 << C1INV ;Bit mask for the comparator1 invert bit
+ xorwf CMCON,F ;Toggle the invert bit
+ btfss CMCON,C1INV ;Carry is guaranteed to be set here
+ clrc ;Clear carry if not inverting
+ swapf CMCON,W ;Read the new comparator states
+ bcf STATUS,RP0
+ bcf PIR2,CMIF ;Clear the interrupt caused by inverting
+ xorwf msgflags,W ;Get the saved comparator state
+ andlw b'00001100' ;Filter out the comparator bits
+ ;Always expect W to be 0x04 here.
+ xorwf msgflags,F ;Update the saved state
+ skpnc ;Inverting
+ goto SmartPowerOn
+ bsf PowerReport ;Power levels have changed
+ btfss HighPower
+ return ;Medium power
+ bcf RaisedPower ;High/Medium => low transition
+ bcf HighPower ;Clear bit for next round
+ bsf MasterOut ;Make the thermostat line low
+ movlw 'P'
+ pcall SwitchOffLED ;Switch off the raised power mode LED
+ return
+ExtendedHigh bsf DisconnectEvent ;Simulated disconnect
+ return
+SmartPowerOn btfsc RaisedPower ;Already in raised power mode?
+ goto MaximumPower
+ bsf RaisedPower ;In raised power mode
+ bcf MasterOut ;Make the thermostat line high
+ movlw 'P'
+ pcall SwitchOnLED ;Switch on the raised power mode LED
+ return
+MaximumPower bsf HighPower ;This will produce the desired behavior
+ return
+
+midbitfirst bsf MidBit ;Remember a mid-bit transition happened
+ goto activity
+midbitchange btfsc MidBit ;First mid-bit transition?
+ btfsc IgnoreErr1 ;Or multiple transitions are ignored?
+ goto midbitfirst ;Register mid-bit transition
+
+;Error 01: More than one mid-bit transition
+error01 movlw 1
+ goto errorcleanup
+
+;Error 02: Wrong stop bit
+error02 movlw 2
+ goto errorcleanup
+
+;Error 03: Bit not received in time
+error03 movlw 3
+ goto errorcleanup
+
+;Error 04: Parity error
+error04 movlw 4
+
+errorcleanup tstf errornum
+ skpnz
+ movwf errornum
+ bcf T2CON,TMR2ON ;Stop timer 2
+ clrf bitcount
+ clrf quarter
+ bcf Transmit
+ pagesel Message
+ movlw 'X'
+ call SwitchOffLED ;Switch off the transmit LED
+ movlw 'R'
+ call SwitchOffLED ;Switch off the receive LED
+ movlw 'B'
+ call SwitchOffLED ;Switch off the boiler LED
+ movlw 'T'
+ call SwitchOffLED ;Switch off the thermostat LED
+ comf errornum,W
+ skpnz
+ goto errorskip ;Not a real error
+ movlw 'E'
+ call SwitchOnLED ;Switch on the error LED
+ movlw TWOSEC ;Leave the error LED on for 2 seconds
+ movwf errortimer
+errorskip lgoto activity ;Wait for the line to go idle
+
+;########################################################################
+; Main program
+;########################################################################
+
+ package Main
+Break tstf RCREG ;Clear the RCIF interrupt flag
+ btfsc NoBreak
+ goto Restart2
+ clrf BreakTimer ;Reset break-free timer
+ goto MainLoop ;continue running the mainloop
+Restart1 bsf resetreason,0 ;Reset because we're stuck in a loop
+Restart2 bsf resetreason,1 ;Reset due to BREAK on serial line
+ pcall SelfProg ;Jump to the self-programming code
+Start bcf STATUS,RP1
+ bcf STATUS,RP0
+ clrf PORTB ;Flash the LED's on startup
+ bsf STATUS,RP0
+ movlw b'01100000' ;Internal oscillator set to 4MHz
+ movwf OSCCON ;Configure the oscillator
+
+ ;Configure I/O pins
+ ;Power on defaults all pins to inputs
+ ;Port A
+ ;Pins 0 and 1 are used as comparator inputs
+ ;Pin 2 is used as VREF (must be configured as input!)
+ ;Pins 3 and 4 are (comparator) ouputs
+ ;Pin 5 is master reset input
+ ;Pins 6 and 7 are GPIO
+ movlw b'11100111'
+ movwf TRISA
+
+ ;Port B
+ ;Pins 2 and 5 are used by the USART and don't need to be configured
+ ;Pins 3, 4, 6, and 7 are used to indicate events for debugging
+#ifndef LVP
+ movlw b'00100111'
+#else
+ ;Can't use RB3 if LVP is enabled
+ movlw b'00101111'
+#endif
+ movwf TRISB
+
+ ;Check for reset reason
+ movlw b'11'
+ xorwf PCON,W
+ movlw 4 << 4
+ btfsc PCON,NOT_POR
+ movlw 5 << 4
+ bcf STATUS,RP0
+ skpnz
+ swapf resetreason,W
+ movwf mode
+ btfss STATUS,NOT_TO
+ bsf FailSafeMode
+ bsf STATUS,RP0
+ movlw b'11'
+ movwf PCON
+
+ ;Configure Timer 0 & Watchdog Timer
+ ;Bit 7 RBPU = 1 - Port B pull up disabled
+ ;Bit 6 INTEDG = 1 - Interrupt on rising edge
+ ;Bit 5 T0CS = 0 - Internal clock
+ ;Bit 4 T0SE = 1 - High to low transition
+ ;Bit 3 PSA = 0 - Prescaler assigned to Timer 0
+ ;Bit 2-0 PS = 5 - 1:64 Prescaler
+ movlw b'11010101'
+ movwf OPTION_REG
+
+ ;Configure Timer 2
+ ;Generate an interrupt every 250uS (one fourth of a bit)
+ ;Bit 6-3 TOUTPS = 0 - 1:1 Postscaler
+ ;Bit 2 TMR2ON = 0 - Timer disabled
+ ;Bit 1-0 T2CKPS = 0 - 1:1 Prescaler
+ movlw PERIOD ;Fosc / 16000 - 1
+ movwf PR2
+ bcf STATUS,RP0
+#ifndef T2PRESCALER
+ movlw b'00000000'
+#else
+ movlw b'00000001'
+#endif
+ movwf T2CON
+
+ clrf TMR1L
+ clrf TMR1H
+ ;Configure Timer 1
+ ;Used to blink LEDs approximately once per second
+ ;Bit 5-4 T1CKP = 3 - 1:8 Prescale value
+ ;Bit 3 T1OSCEN = 0 - Oscillator is shut off
+ ;Bit 2 T1SYNC = 0 - Bit is ignored because TMR1CS=0
+ ;Bit 1 TMR1CS = 0 - Internal clock (Fosc/4)
+ ;Bit 0 TMR1ON = 1 - Enable Timer1
+ movlw b'110001'
+ movwf T1CON
+
+ ;Delay a few milliseconds to show the lit LEDs
+ clrf TMR0
+ bcf INTCON,TMR0IF
+WaitTimer0 clrwdt
+ btfss INTCON,TMR0IF
+ goto WaitTimer0
+
+ ;Configure comparator module
+ ;Bit 7 C2OUT = R/O
+ ;Bit 6 C1OUT = R/O
+ ;Bit 5 C2INV = 0 - Not inverted
+ ;Bit 4 C1INV = 0 - Not inverted
+ ;Bit 3 CIS = 0 - Irrelevant
+ ;Bit 2-0 = 3 - Common reference / 6 - with outputs
+ bsf STATUS,RP0
+ movlw b'00000011' ;Common reference mode
+ btfsc FailSafeMode
+ movlw b'00000110' ;Common reference with outputs
+ movwf CMCON
+ movlw b'00000111' ;A0 through A2 are used for analog I/O
+ movwf ANSEL
+
+ ;Configure the serial interface
+ movlw BAUD
+ movwf SPBRG
+ bsf TXSTA,BRGH ;9600 baud
+ bcf TXSTA,SYNC ;Asynchronous
+ bsf TXSTA,TXEN ;Enable transmit
+ bcf STATUS,RP0
+ bsf RCSTA,SPEN ;Enable serial port
+ btfss FailSafeMode
+ bsf RCSTA,CREN ;Enable receive
+
+ ;Configure A/D converter
+ movlw b'01000001' ;A/D on, channel 0, Fosc/8
+ movwf ADCON0
+
+ ;Clear RAM in bank 0 & 1
+ movlw 0x20
+ movwf FSR
+ movlw 80
+ movwf loopcounter
+ movlw 0x80
+InitLoop0 clrf INDF
+ xorwf FSR,F
+ clrf INDF
+ incf FSR,F
+ decfsz loopcounter,F
+ goto InitLoop0
+ ;Clear RAM in bank 2
+ movlw low 0x110
+ bsf STATUS,IRP
+ movwf FSR
+ movlw 96
+ movwf loopcounter
+InitLoop1 clrf INDF
+ incf FSR,F
+ decfsz loopcounter,F
+ goto InitLoop1
+
+ ;Configure the voltage reference module
+ ;The reference voltage must be set to 1.3V
+ ;Bit 7 VREN = 1 - VREF Enabled
+ ;Bit 6 VROE = 1 - Output on pin RA2
+ ;Bit 5 VRR = 1 - Low range
+ ;Bit 3-0 VR = 6 - 1.25V (7 - 1.46V)
+ movlw SavedSettings
+ call ReadEpromData ;Get the setting from EEPROM
+ movwf settings ;Store a copy in RAM
+ iorlw b'11100000'
+ bsf STATUS,RP0
+ movwf CVRCON ;Set the reference voltage
+
+ ;Setup interrupt sources
+ bsf PIE2,CMIE ;Enable comparator interrupt
+ bsf PIE1,TMR2IE ;Enable timer 2 interrupts
+
+ swapf CMCON,W
+ clrf STATUS
+ bcf PIR2,CMIF ;Clear any comparator interrupt
+ andlw b'00001100'
+ movwf msgflags
+ rlf msgflags,W
+ iorlw b'11100111'
+ movwf PORTA
+ movlw b'11111111'
+ movwf PORTB
+ bsf INTCON,PEIE
+ btfss FailSafeMode
+ bsf INTCON,GIE
+ comf alternativecmd,F;Compensate for initial increment
+ movlw b'111'
+ movwf initflags ;Nothing has been initialized yet
+ movlw MIN_DHWSETPOINT
+ movwf dhwsetpointmin
+ movlw MAX_DHWSETPOINT
+ movwf dhwsetpointmax
+ movlw MIN_CHSETPOINT
+ movwf chsetpointmin
+ movlw MAX_CHSETPOINT
+ movwf chsetpointmax
+
+ movlw 100
+ movwf MaxModLevel ;Initialize Max Modulation Level at 100%
+
+ movlw ONESEC
+ movwf SecCounter ;Initialize second counter
+
+ movlw TStatModel
+ call ReadEpromData
+ movwf remehaflags
+
+ ;Reinstall manually configured unsupported MsgID's
+ movlw 16 ;Size of flag data: 16 * 8 = 128 msgs
+ movwf loopcounter ;Initialize loop counter
+ clrf float1 ;Message number
+ movlw UnknownFlags ;Start of bitmap in data EEPROM
+ movwf tempvar0 ;Pointer to byte of bitmap
+UnknownLoop1 movfw tempvar0 ;Get the bitmap pointer
+ call ReadEpromData ;Read the flags for 8 MsgID's
+ movwf float2 ;Temporary storage of bit flags
+ movfw float1 ;Number of the first of 8 MsgID's
+UnknownLoop2 tstf float2 ;Check if any remaining flag is set
+ skpnz
+ goto UnknownJump2 ;Skip over the rest
+ movwf float1 ;Update the message number
+ clrc
+ rrf float2,F ;Shift a flag into the carry bit
+ skpc
+ goto UnknownJump1 ;Current message is not flagged
+ pcall UnknownMask2 ;Get mask and pointer into unknownmap
+ iorwf INDF,F ;Mark message as unsupported
+UnknownJump1 incf float1,W ;Next message
+ goto UnknownLoop2 ;Repeat the loop
+UnknownJump2 movlw b'111'
+ iorwf float1,F ;The last of the current 8 MsgID's
+ incf float1,F ;The first of the next 8 MsgID's
+ incf tempvar0,F ;Also advance the bitmap pointer
+ decfsz loopcounter,F
+ goto UnknownLoop1 ;Repeat the loop
+
+ movlw GreetingStr
+ pcall PrintStringNL
+
+ ;Initialize the LED functions
+ clrf temp
+ movlw FunctionLED1
+ call ReadEpromData
+ pcall SetLEDFunction
+ incf temp,F
+ movlw FunctionLED2
+ call ReadEpromData
+ pcall SetLEDFunction
+ incf temp,F
+ movlw FunctionLED3
+ call ReadEpromData
+ pcall SetLEDFunction
+ incf temp,F
+ movlw FunctionLED4
+ call ReadEpromData
+ pcall SetLEDFunction
+ incf temp,F
+ movlw FunctionLED5
+ call ReadEpromData
+ pcall SetLEDFunction
+ incf temp,F
+ movlw FunctionLED6
+ call ReadEpromData
+ pcall SetLEDFunction
+
+ ;Initialize the GPIO ports
+ movlw FunctionGPIO
+ call ReadEpromData
+ movwf GPIOFunction
+ pcall gpio_init
+
+ btfss FailSafeMode
+ goto MainLoop
+ movlw 'M'
+ lcall SwitchOnLED
+ movlw TimeoutStr
+ pcall PrintStringNL
+
+;************************************************************************
+
+MainLoop clrwdt
+
+ tstf txavailable ;Check if there is data to transmit
+ skpz
+ call ProcessOutput
+
+ btfsc FailSafeMode
+ goto MainLoop
+
+ ;Check if an OpenTherm message was received
+ btfsc MessageRcvd
+ call OpenThermMsg
+
+ btfsc PowerReport
+ call ReportPower ;Report a power level change
+
+ btfsc INTCON,TMR0IF
+ call IdleTimer
+
+ btfsc PIR1,TMR1IF
+ call FlashTimer
+
+ btfsc PIR1,ADIF
+ call CheckThermostat
+
+ pcall gpio_common
+
+ btfsc SummaryRequest ;Summary report requested/in progress?
+ call SummaryReport
+
+ btfsc repeatcount,6 ;Reset after same message 64 times
+ goto Restart1
+
+ btfss PIR1,RCIF ;Activity on the serial receiver?
+ goto MainLoop
+
+ ;FERR is only relevant if RCIF is set
+ btfsc RCSTA,FERR ;Check for framing error (break)
+ goto Break
+
+ call SerialEvent
+ goto MainLoop
+
+;Callers depend on this function never setting the carry bit
+SerialReceive movfw rxpointer
+ addlw rxbuffer
+ movwf FSR
+ movfw RCREG
+ btfsc CommandComplete
+ return ;Not ready to accept a command now
+ movwf INDF
+ xorlw 0x0a ;Ignore linefeed characters
+ skpnz
+ return
+ xorlw 0x0a ^ 0x0d ;Check for end of command
+ skpnz
+ goto SerialCmdEnd
+ movfw rxpointer
+ addlw 1
+ skpdc ;All commands are less than 15 chars
+ movwf rxpointer
+ return ;User is still typing the command
+SerialCmdEnd tstf rxpointer ;Check command length
+ skpnz
+ return ;Ignore empty commands
+ clrf INDF ;Indicate end of command
+ bsf CommandComplete
+ return
+
+SerialEvent btfss RCSTA,OERR ;Check for overrun error
+ goto SerialEventJmp1
+ bcf RCSTA,CREN ;Procedure to clear an overrun error
+ bsf RCSTA,CREN ;Re-enable the serial interface
+ bsf Overrun ;Remember for later reporting
+SerialEventJmp1 call SerialReceive
+ btfss CommandComplete
+ return
+ lcall SerialCommand
+ clrf rxpointer ;Prepare for a new command
+ bcf CommandComplete
+ pagesel Print
+ iorlw 0
+ skpz
+ call PrintString ;Print the result
+ call NewLine
+ pagesel $
+ goto PrtDebugPointer
+
+OpenThermMsg pcall PrintRcvdMsg ;Report the received message
+ bcf MessageRcvd ;Message has been reported
+ btfsc MonitorMode ;In monitor mode there is no need ...
+ goto MonModeCommOK ;... to explicitly transmit the message
+ pcall HandleMessage ;Apply special treatment
+ btfss NoThermostat
+ goto SendMessage
+ bsf Intermission ;Message exchange is finished
+ movfw byte2
+ xorwf originalreq,W
+ skpz
+ goto PrtDebugPointer ;Didn't get the expected answer
+ incf MessagePtr,F
+ bcf MessagePtr,4
+MonModeCommOK clrf repeatcount ;Communication is working
+ goto PrtDebugPointer ;No need to send the message
+SendMessage movlw 1
+ movwf quarter ;Initialize the state counter
+ bsf NextBit ;Start bit is 1
+ bsf Transmit ;Starting to transmit a message
+ movlw 'X' ;Transmit function
+ pagesel Message
+ call SwitchOnLED ;Switch on the transmit LED
+ movlw 'B' ;Boiler function
+ btfsc MsgResponse
+ movlw 'T' ;Thermostat function
+ ; In package Message
+ call SwitchOnLED ;Switch on the boiler or thermostat LED
+ movlw SlaveMask ;Transmitting to the boiler
+ btfsc MsgResponse ;Check message direction
+ movlw MasterMask ;Transmitting to the thermostat
+ movwf outputmask
+ movlw 34 ;A message is 32 bits + start & stop bit
+ movwf bitcount ;Load the counter
+ bsf STATUS,RP0
+ bsf PIE1,TMR2IE ;Enable timer 2 interrupts
+ bcf STATUS,RP0
+ movlw PERIOD
+ movwf TMR2 ;Prepare timer 2 to overflow asap
+ bsf T2CON,TMR2ON ;Start timer 2
+PrtDebugPointer pcall PrintDebug ;Report the debug value, if necessary
+ return
+
+ReportPower bcf PowerReport ;Print the report only once
+ movlw NormalPowerStr ;Normal Power
+ btfss RaisedPower
+ goto ReportPowerJ2
+ movlw MediumPowerStr ;Medium Power
+ btfss HighPower
+ goto ReportPowerJ1
+ movfw LineVoltage ;Check the line voltage
+ sublw V_OPEN ;Thermostat disconnected?
+ skpc
+ return ;Don't report the power change
+ movlw HighPowerStr ;High Power
+ReportPowerJ1 lcall PrintString
+ movlw PowerStr
+ReportPowerJ2 pcall PrintStringNL ;Print the new power setting
+ return
+
+; IdleTimer is called whenever timer 0 overflows
+IdleTimer bcf INTCON,TMR0IF ;Clear Timer 0 overflow flag
+ decfsz SecCounter,F ;Decrease second counter
+ goto IdleTimerJ1
+ btfsc BoilerAlive ;Ever received a message from boiler?
+ incf repeatcount,F ;Keep track of repeated messages
+ bsf OneSecond ;Trigger new message in stand-alone mode
+ movlw ONESEC
+ movwf SecCounter ;Reload second counter
+ incfsz boilercom,W ;Increment variable without rolling over
+ movwf boilercom
+ movlw 5
+ subwf boilercom,W ;Check for 5 seconds without a message
+ skpnc
+ bcf SlaveOut ;Prevent unwanted heating
+IdleTimerJ1 btfss NoBreak
+ incf BreakTimer,F
+ btfss T2CON,TMR2ON
+ bsf ADCON0,GO ;Start A/D conversion
+ pagesel Serial
+ btfsc ChangeMode ;Is a request to change mode pending?
+ call SetMonitorMode
+ pagesel Print
+ comf errornum,W ;Suppressing errors?
+ skpnz
+ clrf errornum ;Re-enable errordetection
+ tstf errornum ;Check if any errors were detected
+ skpz
+ call PrintError ;Report the error on the serial intf
+ pagesel Main
+ tstf errortimer
+ skpz
+ decfsz errortimer,F
+ return
+ movlw 'E'
+ pcall SwitchOffLED ;Switch off the error LED
+ return
+
+FlashTimer bcf PIR1,TMR1IF ;Clear timer overrun flag
+ btfsc ChangeTime
+ decf minutetimer,F
+ btfsc OverrideReq
+ btfsc OverrideAct
+ return ;No override request pending
+ ;Override requested, but not active. Flash the override LED
+ movlw 1 << 7 ;Mask for the OverrideBlink bit
+ xorwf override,F
+ andwf override,W
+ movlw 'O'
+ pcall SwitchLED
+ return
+
+SummaryReport btfsc txavailable,6 ;Buffer filled less than 80%?
+ return ;Not enough space in the transmit buffer
+ pcall PrintStoredVal
+ movlw SUMMARYFIELDS
+ subwf valueindex,W
+ skpnc ;Check if the summary is complete
+ goto SummaryDone
+ movlw ',' ;Separator
+ pcall PrintChar
+ return
+SummaryDone bcf SummaryRequest
+ pcall NewLine
+ return
+
+ProcessOutput btfss PIR1,TXIF ;Check if the transmit buffer is empty
+ return
+ movfw txpointer ;Get the offset of the next character
+ addlw txbuffer ;Add the start of the buffer
+ movwf FSR ;Setup indirect access
+ bsf STATUS,IRP ;The buffer is in bank 2
+ movfw INDF ;Get the character from the buffer
+ bcf STATUS,IRP ;Restore the status register to normal
+ movwf TXREG ;Load the serial transmitter
+ incf txpointer,F ;Move to the next character
+ movlw TXBUFSIZE
+ xorwf txpointer,W ;Check for the end of the buffer
+ skpnz
+ clrf txpointer ;Wrap around to the start
+ decf txavailable,F ;Adjust character count
+ return
+
+;This code should not set the carry bit, it may clear the carry
+EepromWait bsf STATUS,RP1 ;EEPROM registers are in bank 2&3
+ bsf STATUS,RP0
+ btfss EECON1,WR ;Check if a write is in progress
+ return ;Safe to modify the EEPROM registers
+ bcf STATUS,RP0 ;Switch back to bank 0
+ bcf STATUS,RP1
+ btfss PIR1,RCIF ;Check for serial receiver activity
+ goto EepromWait ;Wait some more
+ movwf temp2 ;Preserve value in W register
+ call SerialReceive ;Prevent receiver overrun
+ movfw temp2 ;Restore original W register value
+ goto EepromWait ;Wait some more
+
+ReadEpromData call EepromWait ;Wait for any pending EEPROM activity
+ clrf EECON1 ;Read from DATA EEPROM
+ bcf STATUS,RP0
+ movwf EEADR ;Setup the EEPROM data address
+ bsf STATUS,RP0
+ bsf EECON1,RD ;Start the EEPROM read action
+ bcf STATUS,RP0
+ movfw EEDATA ;Read the data from EEPROM
+ bcf STATUS,RP1 ;Switch back to bank 0
+ return
+
+;The Opentherm specification mentions the following special circumstance:
+;"The boiler unit must support an important installation feature which allows
+;the terminals at the boiler to be short-circuited to simulate a heat demand
+;such as can be done with existing on/off boilers. The boiler unit should
+;interpret the short-circuit as a heat demand within15 secs of the short-
+;circuit being applied. This must be supported by both OT/+ and OT/- boilers.
+;
+;It is allowable that this can implemented by a software-detection method. The
+;software short-circuit condition is defined as a low-voltage state (Vlow) with
+;no valid communications frame for at least 5 seconds."
+;
+;Without special precautions, this situation would occur when the thermostat is
+;disconnected while the gateway is still powered up. Without a thermostat, the
+;gateway does not generate any messages towards the boiler and the default idle
+;level of the Opentherm signal is the mentioned low-voltage state.
+;
+;To prevent an unintended heat demand when the thermostat is disconnected, the
+;thermostat interface is monitored periodically between messages. If the master
+;interface is found to be at a high-voltage state, the slave interface is also
+;put to the high-voltage state.
+
+;Measure the voltage on the thermostat line. A value between 10 and 45 is the
+;normal idle line value. Between 95 and 115 is the opentherm high level. We'll
+;check for a value below 6 (short circuit) or above 122 (open) to detect an
+;on/off thermostat (or no thermostat at all).
+
+CheckThermostat bcf PIR1,ADIF ;Clear flag for next round
+ movfw ADRESH ;Get the A/D result
+ xorwf LineVoltage,W ;Swap values of W and LineVoltage
+ xorwf LineVoltage,F
+ xorwf LineVoltage,W ;--
+ subwf LineVoltage,W ;Calculate the difference
+ skpc
+ sublw 0 ;Turn into absolute value
+ andlw b'11111100' ;Check for limited deviation
+ skpz
+ goto Unstable
+ movfw LineVoltage ;Get the A/D result again
+ sublw V_OPEN ;Line open?
+ skpnc
+ btfsc DisconnectEvent ;Interrupt routine detected disconnect
+ goto OpenCircuit ;No opentherm thermostat connected
+ sublw V_OPEN - V_SHORT ;Line shorted?
+ skpc
+ goto ShortCircuit ;Heat demand from on/off thermostat
+ btfss NoThermostat
+ return
+;Any opentherm thermostat will always start in low power mode, so we want to
+;see a low line voltage before considering the thermostat to be reconnected.
+ sublw V_LOW - V_SHORT ;No short, but logical low level?
+ skpc
+ goto ThermostatEnd ;Not a logical low opentherm level
+Connect bcf NoThermostat ;Thermostat was reconnected
+ movlw TConnect
+ pcall PrintStringNL
+ return
+ShortCircuit bsf HeatDemand
+ goto DumbThermostat
+OpenCircuit bcf HeatDemand
+DumbThermostat btfsc NoThermostat
+ goto ThermostatEnd
+Disconnect bsf NoThermostat ;Thermostat has been disconnected
+ movlw TDisconnect ;Report the thermostat was disconnected
+ pcall PrintStringNL
+ bcf T2CON,TMR2ON ;Stop timer 2
+ bcf INTCON,PEIE ;Prevent peripheral interrupts
+ bsf STATUS,RP0
+ bcf CMCON,C1INV ;Make sure the line is in low power mode
+ swapf CMCON,W ;Read the new comparator states
+ bcf STATUS,RP0
+ bcf PIR2,CMIF ;Clear the interrupt caused by inverting
+ bsf INTCON,PEIE ;Re-enable peripheral interrupts
+ xorwf msgflags,W ;Get the saved comparator state
+ andlw b'00001100' ;Filter out the comparator bits
+ xorwf msgflags,F ;Update the saved state
+ clrf tstatflags ;Forget all thermostat related info
+ btfss TStatManual ;Manually configure thermostat model
+ clrf remehaflags
+ clrf override
+ clrf setpoint1
+ clrf setpoint2
+ movlw 'P'
+ pcall SwitchOffLED ;Switch off Raised Power LED
+ return
+Unstable btfss NoThermostat
+ return
+ThermostatEnd btfss MonitorMode ;Can't send messages in monitor mode
+ btfss OneSecond ;Time to send the next message?
+ return
+ movlw T_READ
+ movwf byte1
+ clrf byte3
+ clrf byte4
+ call SelectMessage ;Get a message to send from the table
+ movwf byte2
+ movwf originalreq
+ pcall UnknownMask
+ skpz ;Check if the boiler supports the msg
+ goto NoAltMessage
+ call UserMsg ;Get a msg from the list of alternatives
+ movwf byte2
+ movwf originalreq
+NoAltMessage pcall SetParity ;Calculate the parity
+ bcf AlternativeUsed ;Prevent unnecessary processing
+ call SendMessage ;Start sending the message
+ clrf tstatflags ;Message queued - clear all flags
+ clrf boilercom ;Sent a message to the boiler
+ return
+SelectMessage movlw high MessageTable
+ movwf PCLATH
+ movfw MessagePtr
+ addlw low MessageTable
+ skpnc
+ incf PCLATH,F
+ movwf PCL
+MessageTable goto StatusMsg
+ retlw MSG_BOILERTEMP
+ goto CtrlSetptMsg
+ goto UserMsg
+ retlw MSG_MODULATION
+ retlw MSG_OUTSIDETEMP
+ retlw MSG_RETURNTEMP
+ goto MaxModulation
+ goto UserMsg
+ goto StatusMsg
+ retlw MSG_BOILERTEMP
+ goto CtrlSetptMsg
+ retlw MSG_DHWSETPOINT
+ retlw MSG_MAXCHSETPOINT
+ goto UserMsg
+ retlw MSG_CHPRESSURE
+
+UserMsg pcall Alternative ;Find a user defined message ID
+ btfss NoAlternative ;Check if any message was found
+ return
+ clrf byte1 ;Clear out the fall-back message
+ clrf byte3
+ clrf byte4
+ ;Fall through so a status message is used as filler
+StatusMsg tstf controlsetpt1 ;User specified setpoint?
+ skpz
+ btfsc CHModeOff
+ goto StatusMsgJmp1
+ bsf byte3,0 ;Controlsetpt set and CHModeOff == 0
+StatusMsgJmp1 skpnz
+ btfss HeatDemand
+ goto StatusMsgJmp2
+ bsf byte3,0 ;Controlsetpt clear and HeatDemand == 1
+StatusMsgJmp2 btfsc HotWaterEnable ;Check hot water user setting
+ bsf byte3,1 ;Enable domestic hot water
+ retlw MSG_STATUS ;Send a status message
+
+CtrlSetptMsg bsf byte1,4 ;This is a write-data request
+ movfw controlsetpt2
+ movwf byte4
+ movfw controlsetpt1 ;Check the user defined ctrl setpoint
+ skpz ;No user defined control setpoint
+ goto CtrlSetptMsgRet
+ movlw 10 ;Default control setpoint
+ btfss HeatDemand ;Check if lines are short-circuited
+ goto CtrlSetptSimple ;Send control setpoint for heating off
+ movfw maxchsetpoint2
+ movwf byte4
+ movfw maxchsetpoint1 ;Did user specify a max ch setpoint?
+ skpz
+ goto CtrlSetptMsgRet
+ movfw chsetpointmax ;Use the maximum control setpoint
+CtrlSetptSimple clrf byte4
+CtrlSetptMsgRet movwf byte3 ;Specify the desired control setpoint
+ retlw MSG_CTRLSETPT ;Send a control setpoint message
+
+MaxModulation bsf byte1,4 ;This is a write-data request
+ movfw MaxModLevel ;Get user specified level (default 100%)
+ movwf byte3
+ retlw MSG_MAXMODULATION
+
+;************************************************************************
+
+ package Print
+PrintString pcall EepromWait ;Wait for any pending EEPROM activity
+ bcf STATUS,RP0
+ movwf EEADR
+ goto PrintJump
+PrintLoop call PrintChar
+ bsf STATUS,RP1
+ incf EEADR,F
+PrintJump bsf STATUS,RP0
+ bsf EECON1,RD
+ bcf STATUS,RP0
+ movfw EEDATA
+ bcf STATUS,RP1
+ skpz
+ goto PrintLoop
+ return
+
+PrintStringNL call PrintString
+NewLine movlw 0x0d
+ call PrintChar
+ movlw 0x0a
+ goto PrintChar
+
+PrintHex movwf temp2 ;Temporarily store the byte
+ swapf temp2,W ;Get the high nibble
+ call PrintXChar ;And print it
+ movf temp2,W ;Next, get the low nibble
+ ;Fall through to the subroutine to print a hex character
+PrintXChar andlw B'00001111' ;Extract the low nible
+ addlw -10 ;Check if is a digit or alpha char
+ skpnc
+ addlw 'A' - '9' - 1 ;Bridge the gap in the ASCII table
+ addlw 10 ;Re-add the amount subtracted earlier
+PrintDigit bsf LeadingZero ;Don't skip any future zeroes
+ addlw '0' ;Convert to ASCII digit
+PrintChar tstf txavailable ;Check if some data is already waiting
+ skpnz
+ btfss PIR1,TXIF ;Check the transmit register
+ goto PrintBuffer ;Need to put the byte into the buffer
+ movwf TXREG ;Can transmit immediately
+ retlw 0
+PrintBuffer movwf FSR ;Abuse FSR for temporary storage
+ movfw txpointer ;Current position in the transmit buffer
+ addwf txavailable,W ;Add number of pending characters
+ addlw -TXBUFSIZE ;Check for wrap-around
+ skpc
+ addlw TXBUFSIZE ;No wrap, recalculate original value
+ addlw txbuffer ;Next free position in transmit buffer
+ xorwf FSR,W ;Exchange FSR and W
+ xorwf FSR,F
+ xorwf FSR,W ;--
+ bsf STATUS,IRP ;Buffer is in bank 2
+ movwf INDF ;Store byte in the buffer
+ bcf STATUS,IRP ;Restore status register to normal
+ movfw txavailable ;Get number of pending characters
+ xorlw TXBUFSIZE ;Compare against buffer size
+ skpz ;Buffer full?
+ incf txavailable,F ;One more character in the buffer
+ retlw 0
+
+PrintRcvdMsg movlw 'T'
+ btfsc MsgResponse
+ movlw 'B'
+PrintMessage btfsc HideReports
+ return ;Don't print the message
+ call PrintChar
+ movfw byte1
+ call PrintHex
+ movfw byte2
+ call PrintHex
+ movfw byte3
+ call PrintHex
+ movfw byte4
+ call PrintHex
+ goto NewLine
+
+PrintError btfsc PowerReport ;Pending power change report?
+ return ;Error probably due to the power change
+ movlw ErrorStr
+ call PrintString
+ movfw errornum
+ call PrintHex
+ call NewLine
+ movlw 'E'
+ btfss errornum,0
+ call PrintMessage ;Print message for error 2 and 4.
+ clrf errornum
+PrintDebug movfw debug
+ skpnz
+ return
+ movwf FSR
+ movfw INDF
+ movwf temp
+ movfw debug
+ call PrintHex
+ movlw '='
+ call PrintChar
+ movfw temp
+ call PrintHex
+ goto NewLine
+
+PrintStoredVal movfw valueindex
+ addwf valueindex,W
+ addlw valuestorage
+ movwf FSR
+ bsf STATUS,IRP
+ movfw INDF
+ movwf float1
+ incf FSR,F
+ movfw INDF
+ movwf float2
+ bcf STATUS,IRP
+ movlw high StoreProcTable
+ movwf PCLATH
+ movfw valueindex
+ incf valueindex,F
+ addlw low StoreProcTable
+ skpnc
+ incf PCLATH,F
+ movwf PCL
+StoreProcTable goto PrintStatus ;Message ID 0
+ goto PrintFloat ;Message ID 1
+ goto PrintStatus ;Message ID 6
+ goto PrintFloat ;Message ID 14
+ goto PrintBytes ;Message ID 15
+ goto PrintFloat ;Message ID 16
+ goto PrintFloat ;Message ID 17
+ goto PrintFloat ;Message ID 18
+ goto PrintFloat ;Message ID 24
+ goto PrintFloat ;Message ID 25
+ goto PrintFloat ;Message ID 26
+ goto PrintSigned ;Message ID 27
+ goto PrintFloat ;Message ID 28
+ goto PrintBytes ;Message ID 48
+ goto PrintBytes ;Message ID 49
+ goto PrintFloat ;Message ID 56
+ goto PrintFloat ;Message ID 57
+ goto PrintShort ;Message ID 116
+ goto PrintShort ;Message ID 117
+ goto PrintShort ;Message ID 118
+ goto PrintShort ;Message ID 119
+ goto PrintShort ;Message ID 120
+ goto PrintShort ;Message ID 121
+ goto PrintShort ;Message ID 122
+ goto PrintShort ;Message ID 123
+
+PrintStatus movfw float1
+ call PrintBinary
+ movlw '/'
+ call PrintChar
+ movfw float2
+PrintBinary movwf temp
+ movlw 8
+ movwf loopcounter
+PrintBinaryLoop rlf temp,F
+ call PrintBoolean
+ decfsz loopcounter,F
+ goto PrintBinaryLoop
+ return
+
+ errorlevel -219 ;Ignore warning about invalid RAM addr
+PrintBoolean rlf 7,W ;Trick: Address 7 reads as all 0's
+ errorlevel +219 ;Turn warning back on
+ goto PrintDigit
+
+PrintSigned btfss float1,7 ;Check if value is negative
+ goto PrintFloat
+ comf float1,F ;Negate the value
+ comf float2,F
+ incf float2,F
+ skpnz
+ incf float1,F ;--
+ movlw '-'
+ call PrintChar ;Print a minus sign
+ ;Fall through to print the absolute value
+;If the low byte of the floating point value is 0xff, it would translate to a
+;fraction of .996, which should be rounded up to the next full number. This
+;needs to be checked before the integer part of the value can be printed.
+PrintFloat incfsz float2,W ;Check if the 2nd byte is 0xff
+ goto PrintFloatJ1
+ incf float1,F ;Round up to the next integral number
+ clrf float2 ;--
+PrintFloatJ1 movfw float1
+ call PrintByte
+ movlw 100
+ movwf temp
+ movfw float2
+ call Multiply
+ btfsc float2,7
+ incf float1,F ;Rounding
+ movfw float1
+ movwf temp
+ movlw '.'
+ goto PrintFraction
+
+PrintBytes movfw float1
+ call PrintByte
+ movlw '/'
+ call PrintChar
+ movfw float2
+PrintByte bcf LeadingZero
+ movwf temp
+ addlw -100
+ skpc
+ goto PrintByteJ1
+ movwf temp
+ addlw -100
+ skpc
+ goto Print100
+ movwf temp
+ movlw '2'
+ goto PrintFraction
+Print100 movlw '1'
+PrintFraction call PrintChar
+PrintDecimal bsf LeadingZero
+PrintByteJ1 clrf loopcounter
+ movfw temp
+PrintByteL1 movwf temp ;BCD
+ addlw -10
+ skpc
+ goto PrintByteJ2
+ incf loopcounter,F
+ goto PrintByteL1
+PrintByteJ2 movfw loopcounter
+ skpnz
+ btfsc LeadingZero
+ call PrintLDigit
+ movfw temp
+PrintLDigit bsf LeadingZero
+ goto PrintDigit
+
+PrintShort movlw 16
+ movwf loopcounter
+ clrf tempvar0
+ clrf tempvar1
+ clrf tempvar2
+ goto PrintShortJump
+PrintShortLoop movfw tempvar2
+ call AdjustBCD
+ movwf tempvar2
+ movfw tempvar1
+ call AdjustBCD
+ movwf tempvar1
+PrintShortJump rlf float2,F
+ rlf float1,F
+ rlf tempvar2,F
+ rlf tempvar1,F
+ rlf tempvar0,F
+ decfsz loopcounter,F
+ goto PrintShortLoop
+ bcf LeadingZero
+ movfw tempvar0
+ call PrintBCD
+ movfw tempvar1
+ call PrintBCD
+ movfw tempvar2
+ call PrintBCD
+ btfsc LeadingZero
+ return
+ goto PrintDigit ;W is always 0 here
+AdjustBCD addlw 0x33
+ movwf temp
+ btfss temp,3
+ addlw -0x03
+ btfss temp,7
+ addlw -0x30
+ return
+PrintBCD movwf temp
+ swapf temp,W
+ call PrintBCDDigit
+ movfw temp
+PrintBCDDigit andlw b'1111'
+ skpnz
+ btfsc LeadingZero
+ goto PrintDigit
+ return
+
+PrintSetting movfw rxpointer ;Check that the command is 4 chars long
+ sublw 4
+ skpz
+ retlw SyntaxError
+ movlw high PrintTable
+ movwf PCLATH
+ movfw rxbuffer + 3
+ sublw 'Z'
+ sublw 'Z' - 'A'
+ skpc
+ retlw BadValue
+ addlw low PrintTable
+ skpnc
+ incf PCLATH,F
+ movwf PCL
+PrintTable retlw PrintSettingA ;PR=A
+ retlw PrintSettingB ;PR=B
+ retlw PrintSettingC ;PR=C
+ retlw BadValue ;PR=D
+ retlw BadValue ;PR=E
+ retlw BadValue ;PR=F
+ goto PrintSettingG ;PR=G
+ retlw BadValue ;PR=H
+ goto PrintSettingI ;PR=I
+ retlw BadValue ;PR=J
+ retlw BadValue ;PR=K
+ retlw PrintSettingL ;PR=L
+ goto PrintSettingM ;PR=M
+ retlw BadValue ;PR=N
+ goto PrintSettingO ;PR=O
+ goto PrintSettingP ;PR=P
+ goto PrintSettingQ ;PR=Q
+ goto PrintSettingR ;PR=R
+ goto PrintSettingS ;PR=S
+ goto PrintSettingT ;PR=T
+ retlw BadValue ;PR=U
+ goto PrintSettingV ;PR=V
+ goto PrintSettingW ;PR=W
+ retlw BadValue ;PR=X
+ retlw BadValue ;PR=Y
+ retlw BadValue ;PR=Z
+
+PrintSettingID movfw rxbuffer + 3
+ call PrintChar
+ movlw '='
+ goto PrintChar
+
+PrintSettingV call PrintSettingID
+PrintRefVoltage movfw settings
+ addlw -3
+ goto PrintXChar
+
+PrintSettingO call PrintSettingID
+PrintOverride tstf override ;Any override flags set?
+ movlw 'N'
+ skpnz
+ goto PrintChar ;No override
+ movfw setpoint1 ;Override setpoint set?
+ xorwf setpoint2,W
+ movlw 'n'
+ skpnz
+ goto PrintChar ;Waiting to clear the override
+PrintOvrSetPt swapf RemOverrideFunc,W ;Get the current bits in lower nibble
+ xorwf RemOverrideFunc,W ;Determine the changed bits
+ andlw b'1111' ;Only consider the lower nibble
+ btfss OverrideFunc ;Lower nibble contains pending request
+ xorwf RemOverrideFunc,F ;Harmless to overwrite lower nibble
+ movlw 'T'
+ btfss ProgChangePrio ;Either pending or current state
+ movlw 'C'
+ btfss OverrideAct ;Override accepted by the thermostat?
+ iorlw 0x20 ;Switch to lowercase
+ call PrintChar ;Print the override type
+ movfw setpoint1 ;Copy the setpoint
+ movwf float1
+ movfw setpoint2
+ movwf float2 ;--
+ goto PrintFloat ;Print as a floating point value
+
+PrintSettingM call PrintSettingID
+PrintGateway movlw 'G'
+ btfsc MonitorMode
+ movlw 'M'
+ goto PrintChar
+
+PrintSettingQ call PrintSettingID
+ swapf mode,W
+ andlw b'111'
+ addlw ResetReasonStr
+ pcall ReadEpromData
+ goto PrintChar
+
+PrintSettingW call PrintSettingID
+PrintHotWater movlw '0'
+ btfsc HotWaterEnable
+ movlw '1'
+ btfss HotWaterSwitch
+ movlw 'A'
+ goto PrintChar
+
+PrintSettingP call PrintSettingID
+PrintPowerlevel btfss RaisedPower
+ retlw NormalPowerStr
+ movlw MediumPowerStr
+ btfsc HighPower
+ movlw HighPowerStr
+ call PrintString
+ retlw PowerStr
+
+PrintSettingR call PrintSettingID
+PrintRemeha movlw 'D'
+ btfsc TStatRemeha
+ movlw 'R'
+ btfsc TStatCelcia
+ movlw 'C'
+ btfsc TStatISense
+ movlw 'I'
+ goto PrintChar
+
+PrintSettingT call PrintSettingID
+PrintTweaks swapf settings,W
+ movwf temp
+ rrf temp,F
+ rrf temp,F
+ call PrintBoolean
+ rrf temp,F
+ goto PrintBoolean
+
+PrintSettingI call PrintSettingID
+ rlf PORTA,W
+ addlw b'10000000'
+ call PrintBoolean
+ rlf PORTA,W
+ goto PrintBoolean
+
+PrintSettingG call PrintSettingID
+PrintGPIO swapf GPIOFunction,W
+ goto PrintHex
+
+PrintSettingS call PrintSettingID
+PrintSetback movlw AwaySetpoint1
+ pcall ReadEpromData
+ movwf float1
+ movlw AwaySetpoint2
+ pcall ReadEpromData
+ movwf float2
+ goto PrintFloat
+
+PrintSummary movfw rxpointer
+ sublw 4
+ skpz
+ retlw SyntaxError
+ movfw rxbuffer + 3
+ sublw '1'
+ sublw '1' - '0'
+ skpc
+ retlw BadValue
+ skpnz
+ goto PrintSummaryOff
+ clrf valueindex
+ bsf SummaryRequest
+ bsf HideReports
+ goto PrintSummaryOn
+PrintSummaryOff bcf SummaryRequest
+ bcf HideReports
+PrintSummaryOn lgoto PrintDigit
+
+;Multiply W by temp - result in float1:float2
+Multiply clrf float1
+ clrf float2
+ xorlw 8
+ movwf loopcounter
+ xorlw 8
+ xorwf loopcounter,F
+MultiplyLoop clrc
+ rlf float2,F
+ rlf float1,F
+ rlf temp,F
+ skpnc
+ addwf float2,F
+ skpnc
+ incf float1,F
+ decfsz loopcounter,F
+ goto MultiplyLoop
+ return
+
+;************************************************************************
+;Process an incoming OpenTherm message
+;************************************************************************
+ package Message
+HandleMessage btfsc MsgResponse ;Treat requests and responses differently
+ goto HandleResponse
+
+HandleRequest btfsc byte1,4 ;For Write-Data messages, ...
+ call StoreValue ;... store the value from the thermostat
+ movfw byte1
+ movwf originaltype ;Save the original message type
+ movfw byte2
+ xorwf originalreq,W ;Compare against previous message
+ skpz
+ clrf repeatcount ;Message is different -> reset counter
+ xorwf originalreq,F ;Save the original message ID
+ movfw byte3
+ movwf databyte1 ;Save original data byte #1
+ movfw byte4
+ movwf databyte2 ;Save original data byte #2
+ bcf AlternativeUsed
+ bsf OverrideUsed ;Assume only data values will be changed
+ call TreatMessage ;Process the request from the thermostat
+ clrf boilercom ;Going to send a message to the boiler
+ call UnknownMask ;Check if ID is unsupported by boiler
+ skpnz ;Data ID is not known to be unsupported
+ goto SendAltRequest
+ btfss AlternativeUsed ;Was the request modified in some way?
+ return ;Send message unmodified to the boiler
+ goto SetParity ;Calculate the parity of the new message
+SendAltRequest bsf AlternativeUsed ;Probably going to send a different msg
+ bcf OverrideUsed ;Changes will be more drastic
+ call Alternative ;Get the alternative message to send
+ btfss AlternativeUsed
+ return ;There were no other candidates
+ movwf byte2 ;Fill in the alternative Message ID
+ goto SetParity ;Calculate the parity of the new message
+
+;Vendor specific ID's can go through the code below without causing any trouble
+;because the mask returned by UnknownMask will be 0.
+HandleResponse bsf BoilerAlive ;Received a message from the boiler
+ movfw byte2
+ xorwf prioritymsgid,W ;Response matches ID of priority msg?
+ skpnz
+ bcf PriorityMsg ;Priority message answered
+ btfss byte1,4 ;For Read-Ack messages, ...
+ call StoreValue ;... store the value from the boiler
+ call UnknownMask ;Check boiler support for the DataID
+ btfsc byte1,5
+ btfss byte1,4
+ goto MsgSupported ;No Unknown-DataID response
+ skpnz
+ goto MsgUnsupported ;Already found 3 consecutive failures
+ andlw b'01010101' ;Get lower bit of the mask
+ addwf INDF,F ;Increase failure count
+ goto HandleRespJump
+MsgUnsupported movfw byte2 ;Get the message ID
+ pcall DeleteAlt ;Remove from the list of alternatives
+ goto HandleRespJump
+MsgSupported xorlw -1 ;Invert the mask
+ btfss NoResetUnknown ;Do nothing for administrative requests
+ andwf INDF,F ;Reset the failure count
+HandleRespJump bcf NoResetUnknown
+ bsf BoilerResponse
+ btfsc Unsolicited
+ goto RemoteCommand
+ btfsc AlternativeUsed
+ goto SendAltResponse
+ call TreatMessage ;Process the received response
+ btfsc AlternativeUsed ;Was the response modified in some way?
+ btfsc NoThermostat ;And a thermostat is connected?
+ return
+ goto SetParity
+SendAltResponse call TreatMessage ;Process the received response
+ movlw b'01000000' ;Response bit
+ btfss OverrideUsed
+ movlw b'01110000' ;Boiler does not support the message
+ call CreateMessage ;Turn the request into a response
+ bcf BoilerResponse
+ btfss OverrideUsed ;Does the replacement need processing?
+ call TreatMessage ;Process the message to return
+SetParity movfw byte4 ;Get the last byte
+ xorwf byte3,W ;Calculate the sum of the last 2 bytes
+ xorwf byte2,W ;Calculate the sum of the last 3 bytes
+ bcf MsgParity ;Make sure the parity bit is cleared
+ xorwf byte1,W ;Calculate the sum of all four bytes
+ movwf temp ;Save in temporary storage space
+ swapf temp,F ;Swap the nibbles of the checksum
+ xorwf temp,W ;Calculate the sum of the two nibbles
+ movwf temp ;Save the result again
+ rrf temp,F ;Shift the checksum 2 bits
+ rrf temp,F
+ xorwf temp,F ;Combine two sets of two bits
+ movlw 1 ;Setup a mask to be used later
+ btfsc temp,1 ;Check if bit 1 is set
+ xorwf temp,F ;If so, invert bit 0
+ btfsc temp,0 ;Check the calculated parity
+ bsf MsgParity ;Fill in the parity bit for even parity
+ movlw 'R'
+ btfsc MsgResponse
+ movlw 'A'
+ lgoto PrintMessage
+
+RemoteCommand call TreatMessage ;Process the received message
+ movlw B_WACK
+ movwf byte1
+ movlw MSG_REMOTECMD
+ movwf byte2
+ movlw RCMD_TELEFOON
+ movwf byte3
+ movfw celciasetpoint
+ movwf byte4
+ bcf Unsolicited
+ goto SetParity
+
+;Define special treatment based on the message ID. This code will be called in
+;three situations:
+;1. A request was received from the thermostat.
+;2. A response was received from the boiler.
+;3. An alternative message has been sent to the boiler and a fake response has
+; been created to be sent back to the thermostat.
+;Which of the three situations applies can be determined by checking the
+;MsgResponse and BoilerResponse flags. The flags will have the following states:
+;1. MsgResponse = 0, BoilerResponse = X
+;2. MsgResponse = 1, BoilerResponse = 1
+;3. MsgResponse = 1, BoilerResponse = 0
+;If the code modifies the message, it must set the AlternativeUsed flag. This
+;will cause the parity to be recalculated and the modified message is reported
+;on the serial interface.
+
+TreatMessage btfsc byte2,7
+ goto FakeResponse ;Limited support for Vender specific ID
+ movlw high messagetable
+ movwf PCLATH
+ movfw byte2
+ addlw low messagetable
+ skpnc
+ incf PCLATH,F
+ movwf PCL
+messagetable goto MessageID0 ;Data ID 0
+ goto MessageID1 ;Data ID 1
+ goto MessageID2 ;Data ID 2
+ goto MandatoryWord ;Data ID 3
+ goto MessageID4 ;Data ID 4
+ goto MessageID5 ;Data ID 5
+ goto MessageID6 ;Data ID 6
+ goto FakeResponse ;Data ID 7
+ goto FakeResponse ;Data ID 8
+ goto MessageID9 ;Data ID 9
+ goto TSPBufferSize ;Data ID 10
+ goto TSPReadEntry ;Data ID 11
+ goto TSPBufferSize ;Data ID 12
+ goto TSPReadEntry ;Data ID 13
+ goto MessageID14 ;Data ID 14
+ goto WordResponse ;Data ID 15
+ goto MessageID16 ;Data ID 16
+ goto MandatoryWord ;Data ID 17
+ goto WordResponse ;Data ID 18
+ goto WordResponse ;Data ID 19
+ goto MessageID20 ;Data ID 20
+ goto WordResponse ;Data ID 21
+ goto WordResponse ;Data ID 22
+ goto FakeResponse ;Data ID 23
+ goto WordResponse ;Data ID 24
+ goto MandatoryWord ;Data ID 25
+ goto WordResponse ;Data ID 26
+ goto MessageID27 ;Data ID 27
+ goto WordResponse ;Data ID 28
+ goto WordResponse ;Data ID 29
+ goto WordResponse ;Data ID 30
+ goto WordResponse ;Data ID 31
+ goto WordResponse ;Data ID 32
+ goto WordResponse ;Data ID 33
+ goto WordResponse ;Data ID 34
+ goto WordResponse ;Data ID 35
+ goto FakeResponse ;Data ID 36
+ goto FakeResponse ;Data ID 37
+ goto FakeResponse ;Data ID 38
+ goto FakeResponse ;Data ID 39
+ goto FakeResponse ;Data ID 40
+ goto FakeResponse ;Data ID 41
+ goto FakeResponse ;Data ID 42
+ goto FakeResponse ;Data ID 43
+ goto FakeResponse ;Data ID 44
+ goto FakeResponse ;Data ID 45
+ goto FakeResponse ;Data ID 46
+ goto FakeResponse ;Data ID 47
+ goto MessageID48 ;Data ID 48
+ goto MessageID49 ;Data ID 49
+ goto WordResponse ;Data ID 50
+ goto WordResponse ;Data ID 51
+ goto WordResponse ;Data ID 52
+ goto WordResponse ;Data ID 53
+ goto WordResponse ;Data ID 54
+ goto WordResponse ;Data ID 55
+ goto MessageID56 ;Data ID 56
+ goto MessageID57 ;Data ID 57
+ goto WordResponse ;Data ID 58
+ goto WordResponse ;Data ID 59
+ goto WordResponse ;Data ID 60
+ goto WordResponse ;Data ID 61
+ goto WordResponse ;Data ID 62
+ goto WordResponse ;Data ID 63
+ goto FakeResponse ;Data ID 64
+ goto FakeResponse ;Data ID 65
+ goto FakeResponse ;Data ID 66
+ goto FakeResponse ;Data ID 67
+ goto FakeResponse ;Data ID 68
+ goto FakeResponse ;Data ID 69
+ goto ByteResponse ;Data ID 70
+ goto MessageID71 ;Data ID 71
+ goto WordResponse ;Data ID 72
+ goto WordResponse ;Data ID 73
+ goto WordResponse ;Data ID 74
+ goto WordResponse ;Data ID 75
+ goto WordResponse ;Data ID 76
+ goto WordResponse ;Data ID 77
+ goto WordResponse ;Data ID 78
+ goto WordResponse ;Data ID 79
+ goto WordResponse ;Data ID 80
+ goto WordResponse ;Data ID 81
+ goto WordResponse ;Data ID 82
+ goto WordResponse ;Data ID 83
+ goto WordResponse ;Data ID 84
+ goto WordResponse ;Data ID 85
+ goto WordResponse ;Data ID 86
+ goto WordResponse ;Data ID 87
+ goto TSPBufferSize ;Data ID 88
+ goto TSPReadEntry ;Data ID 89
+ goto TSPBufferSize ;Data ID 90
+ goto TSPReadEntry ;Data ID 91
+ goto FakeResponse ;Data ID 92
+ goto FakeResponse ;Data ID 93
+ goto FakeResponse ;Data ID 94
+ goto FakeResponse ;Data ID 95
+ goto FakeResponse ;Data ID 96
+ goto FakeResponse ;Data ID 97
+ goto FakeResponse ;Data ID 98
+ goto FakeResponse ;Data ID 99
+ goto MessageID100 ;Data ID 100
+ goto ByteResponse ;Data ID 101
+ goto WordResponse ;Data ID 102
+ goto WordResponse ;Data ID 103
+ goto WordResponse ;Data ID 104
+ goto TSPBufferSize ;Data ID 105
+ goto TSPReadEntry ;Data ID 106
+ goto TSPBufferSize ;Data ID 107
+ goto TSPReadEntry ;Data ID 108
+ goto FakeResponse ;Data ID 109
+ goto FakeResponse ;Data ID 110
+ goto FakeResponse ;Data ID 111
+ goto FakeResponse ;Data ID 112
+ goto WordResponse ;Data ID 113
+ goto WordResponse ;Data ID 114
+ goto WordResponse ;Data ID 115
+ goto WordResponse ;Data ID 116
+ goto WordResponse ;Data ID 117
+ goto WordResponse ;Data ID 118
+ goto WordResponse ;Data ID 119
+ goto WordResponse ;Data ID 120
+ goto WordResponse ;Data ID 121
+ goto WordResponse ;Data ID 122
+ goto WordResponse ;Data ID 123
+ goto FakeResponse ;Data ID 124
+ goto MessageID125 ;Data ID 125
+ goto MessageID126 ;Data ID 126
+ goto WordResponse ;Data ID 127
+
+messageinv movlw B_INV
+ goto setbyte1
+messageack movfw originaltype ;Get original request type
+ iorlw b'11000000' ;Turn it into a matching acknowledgement
+ btfss byte1,7
+ andlw b'01111111' ;Match parity bit of the received msg
+setbyte1 xorwf byte1,W ;Check if the byte is different
+ skpz
+ bsf AlternativeUsed ;Going to modify the message
+ xorwf byte1,F ;Set the byte
+ retlw 0
+
+setbyte3 xorwf byte3,W ;Check if the byte is different
+ skpz
+ bsf AlternativeUsed ;Going to modify the message
+ xorwf byte3,F ;Set the byte
+ retlw 0
+
+datarestore movfw databyte1
+ call setbyte3
+ movfw databyte2
+setbyte4 xorwf byte4,W ;Check if the byte is different
+ skpz
+ bsf AlternativeUsed ;Going to modify the message
+ xorwf byte4,F ;Set the byte
+ retlw 0
+
+;DataID 0: Status
+;The gateway may want to manipulate the status message generated by the
+;thermostat for two reasons: The domestic hot water enable option specified
+;via a serial command may differ from what the thermostat sent. And if a
+;control setpoint has been specified via a serial command, central heating
+;mode must be enabled for the setpoint to have any effect.
+;When a status response comes back from the boiler, several LED states are
+;updated. Also if the master status byte was changed before sending it to the
+;boiler, the original master status must be restored before the message is
+;returned to the thermostat, so it doesn't get confused
+MessageID0 btfsc MsgResponse ;Status request or response?
+ goto statusreturn
+readstatus call MandatoryID ;Remove any blacklisting for the DataID
+ movfw byte3
+ tstf controlsetpt1
+ skpnz
+ goto normalstatus
+ iorlw b'1' ;Enable central heating mode
+ btfsc CHModeOff
+ andlw ~b'1' ;Disable central heating mode
+normalstatus btfss HotWaterSwitch
+ goto setbyte3 ;Don't manipulate hot water enable
+ andlw ~b'10' ;Domestic hot water enable off
+ btfsc HotWaterEnable
+ iorlw b'10' ;Domestic hot water enable on
+ goto setbyte3
+
+statusreturn btfsc byte1,5
+ return ;Not a valid response from the boiler
+ movfw byte4
+ movwf databyte2 ;Copy slave status to response message
+ andlw b'01000001' ;Check fault bit and diagnostic bit
+ skpnz
+ bcf BoilerFault ;Clear any previous boiler faults
+ skpz
+ btfsc BoilerFault ;New boiler fault?
+ goto statusmaint
+ bsf BoilerFault ;Remember a fault was reported
+ movlw MSG_FAULTCODE ;ASF-flags/OEM-fault-code message
+ movwf prioritymsgid ;Schedule request for more information
+ bsf PriorityMsg
+statusmaint movlw 'M'
+ call SwitchLED ;Update the maintenance LED
+ movfw byte4
+ andlw b'00001000' ;Check the flame status bit
+ movlw 'F'
+ call SwitchLED ;Update the flame LED
+ movfw byte4
+ andlw b'00000100' ;Check the DHW mode bit
+ movlw 'W'
+ call SwitchLED ;Update the hot water LED
+ movfw byte4
+ andlw b'00000010' ;Check the CH mode bit
+ movlw 'H'
+ call SwitchLED ;Update the central heating LED
+ movfw byte3
+ andlw b'00000010' ;Check the DHW enable bit
+ movlw 'C'
+ goto SwitchLED ;Update the comfort mode LED
+
+;DataID 1: Control Setpoint
+;If a control setpoint has been specified via a serial command, the specified
+;value is put into the message before sending it to the boiler.
+;If the boiler indicates that the control setpoint is invalid, the value
+;specified by the user is cleared.
+MessageID1 btfsc MsgResponse ;Request or response?
+ goto ctrlsetptreturn
+ call MandatoryID ;Prevent the DataID getting blacklisted
+ movfw controlsetpt1 ;Check if a setpoint has been specified
+ xorwf controlsetpt2,W
+ skpz
+ btfss byte1,4 ;WriteData request?
+ return ;Continue with normal processing
+ movfw controlsetpt1 ;Fill in the specified setpoint
+ call setbyte3
+ movfw controlsetpt2
+ goto setbyte4
+
+ctrlsetptreturn btfss byte1,5
+ return ;Setpoint acknowledged by boiler
+ btfsc byte1,4
+ return
+ tstf controlsetpt1
+ skpnz
+ return
+ clrf controlsetpt1 ;Zap the invalid value
+ clrf controlsetpt2
+ return
+
+;DataID 2: Master Configuration
+;The gateway will provide Smart Power support, even if the boiler doesn't.
+MessageID2 btfss MsgResponse
+ goto BrandDetect ;Detect thermostat manufacturer
+ movlw b'1' ;Don't know what the other bits are
+ andwf databyte1,W ;... so don't acknowledge those.
+ call setbyte3 ;Acknowledge Smart Power support
+ movfw databyte2 ;Make sure the master MemberID in ...
+ call setbyte4 ;... the response matches the request
+ goto messageack ;Turn request into acknowledgement
+BrandDetect btfsc byte3,0 ;Smart power bit
+ bsf SmartPower ;Thermostat supports Smart Power
+ btfsc TStatManual
+ return ;Don't auto-detect the thermostat model
+ movfw byte4 ;MemberID code of the master
+ sublw 11 ;Remeha
+ skpnz
+ btfsc TStatRemeha
+ return
+ bsf TStatRemeha ;The master is some Remeha thermostat
+ btfsc SmartPower ;No Smart Power Support?
+ bsf TStatISense ;Probably an iSense (or qSense)
+ ;Without Smart Power support, the thermostat can be a Celcia20
+ ;or a c-Mix. The Celcia20 periodically sends a Master Product
+ ;Version message (MsgID 126). So assume the thermostat is not
+ ;a Celcia20 until it informs us otherwise.
+ return
+
+;DataID 4: Remote Request
+;The remote request message with a request code of 170 is used by the Celcia
+;20 to specify a remote setpoint override.
+MessageID4 btfss MsgResponse
+ return ;Don't modify requests
+ movlw RCMD_TELEFOON ;Check for the celcia override request
+ subwf byte3,W
+ skpz
+ return ;Todo: Turn UnkDataID into DataInvalid?
+ call messageack ;Turn request into acknowledgement
+ movfw celciasetpoint
+ goto setbyte4
+
+;DataID 5: Application-specific flags
+;The Remeha iSense RF doesn't always report a room setpoint, but it does ask for
+;the Application-specific flags quite regularly. If such a message is received
+;after the remote setpoint clear command has been sent, we're going to assume
+;the setpoint clear has been accepted.
+MessageID5 btfsc MsgResponse ;Only interested in requests
+ goto WordResponse ;Default handling for responses
+ btfsc OverrideClr ;No pending clear ...
+ btfsc OverrideWait ;... or clear command has not been sent?
+ return ;Do nothing
+ goto roomsetptclear ;Consider setpoint clear accepted
+
+;DataID 6: Remote Boiler Parameter flags
+;If the boiler doesn't support the DHW Setpoint or Max CH Setpoint parameters,
+;the gateway will simulate read access.
+MessageID6 btfss MsgResponse
+ return
+ bcf InitParameter
+ clrw ;Assume no support for remote-parameters
+ btfss byte1,5
+ btfsc byte1,4
+ goto parameterupdate
+ movfw byte4 ;Get the remote-parameter r/w flags
+parameterupdate xorwf onoffflags,W
+ andlw b'11'
+ xorwf onoffflags,F
+ call messageack ;Turn request into acknowledgement
+ btfss dhwupdate
+ bcf InitHotWater ;Boiler doesn't allow DHW setpt update
+ btfss maxchupdate
+ bcf InitHeating ;Boiler doesn't allow MaxCH setpt update
+ movfw byte3
+ iorlw b'11' ;The gateway will simulate read access
+ goto setbyte3
+
+;If a remote override setpoint has been specified, return it to the thermostat
+MessageID9 btfss MsgResponse
+ return
+ call messageack ;Turn request into acknowledgement
+ btfss OverrideClr
+ movfw setpoint1
+ call setbyte3 ;Data byte 1 has integer part
+ btfss OverrideClr
+ movfw setpoint2
+ call setbyte4 ;Data byte 2 has the fraction
+ bcf OverrideWait ;Override has been requested
+ btfsc TStatCelcia ;Remeha Celcia thermostat?
+ btfss OverrideReq ;And override requested?
+ return
+ btfss OverrideAct ;And override not yet active?
+ bsf Unsolicited ;Then resend the request on next msg
+ return
+
+;Maximum relative modulation level
+MessageID14 btfsc MsgResponse ;Status request or response?
+ return
+ call MandatoryID ;Prevent the DataID getting blacklisted
+ btfss UserMaxModLevel
+ return
+ movfw MaxModLevel ;Get the user specified max modulation
+ call setbyte3 ;Set the max modulation level
+ goto setbyte4 ;Clear the second data byte
+
+;When the thermostat reports a room setpoint, compare it against the requested
+;override setpoint. When it matches, the remote setpoint has been accepted. If
+;at some later point it no longer matches, the thermostat has overriden the
+;remote setpoint and it must be released.
+MessageID16 btfsc MsgResponse
+ ;We don't want an überclever thermostat to stop sending these
+ ;messages, so make sure we always return an acknowledgement
+ goto messageack ;Turn request into acknowledgement
+ btfsc OverrideReq ;Check for pending override request
+ btfsc OverrideWait ;Must actually send the request first
+ return
+ btfsc OverrideClr
+ goto roomsetptclear
+ ;Another "nice" feature of the Remeha iSense thermostat: It
+ ;doesn't set the setpoint to the exact value requested. So we
+ ;have to allow for some variation. The delta chosen is 0.125
+ ;as that should be enough and is easy to do with bit operations
+ movfw setpoint2 ;Calcualte the difference between ...
+ subwf byte4,W ;... the received value and the ...
+ movwf temp ;... desired setpoint
+ movfw setpoint1 ;Get the units of the requested setpoint
+ skpc ;Check for borrow from fractional part
+ addlw 1 ;Adjust the integer part
+ subwf byte3,W ;Calculate the difference
+ skpc
+ xorlw -1 ;Make the result positive
+ skpz
+ goto roomsetptdiff ;Values differ more than 1 degree
+ skpc ;Carry is set if setpoint <= value
+ comf temp,F ;Invert the fraction
+ movlw b'11100000' ;Only the lower 5 bits may be set
+ andwf temp,W ;Reset the lower 5 bits
+ skpz ;Result should be 0 for a match
+ goto roomsetptdiff ;Values differ more than 0.125 degrees
+ ;Setpoint matches the request
+ btfsc OverrideAct
+ return ;Override remains in effect
+ bsf OverrideAct ;Override was picked up by thermostat
+ bcf OverrideBlink
+ movlw 'O'
+ goto SwitchOnLED ;Switch on the override LED
+roomsetptclear bcf OverrideClr ;Override clear data has been sent
+ movfw setpoint1
+ iorwf setpoint2,W ;Check if there is a new setpoint
+ skpnz
+ goto roomsetptreset
+ bsf OverrideWait ;The new setpoint must be sent next
+ return
+roomsetptdiff btfss OverrideAct ;Override currently active?
+ incf override,F ;Make 3 attempts to set the override
+ btfss OverrideAct ;Time to abandon override?
+ return
+roomsetptcancel btfsc TStatISense ;Special treatment for iSense thermostat
+ goto roomsetptreset
+ clrf setpoint1 ;Reset the setpoint variables
+ clrf setpoint2
+roomsetptreset clrf override ;Stop requesting a setpoint override
+ movlw 'O'
+ goto SwitchOffLED ;Switch off the override LED
+
+;Return the current date and time, if specified by the user
+MessageID20 btfss MsgResponse
+ return ;No need to process a request
+ call messageack ;Turn request into acknowledgement
+ btfss ChangeTime ;Is a new clock value available?
+ goto datarestore ;Restore the old data bytes and return
+ movfw clock1
+ call setbyte3
+ movfw clock2
+ goto setbyte4
+
+;Return the outside temperature, if specified via the serial interface
+MessageID27 btfsc MsgResponse ;Do not modify a request
+ btfss OutsideTemp ;Do nothing if no outside temp available
+ return
+ btfsc OutsideInvalid
+ goto messageinv
+ call messageack ;Turn request into acknowledgement
+ movfw outside1
+ call setbyte3
+ movfw outside2
+ goto setbyte4
+
+;Keep track of the boundaries for domestic hot water reported by the boiler
+MessageID48 btfss MsgResponse
+ return
+ bcf InitHotWater ;Received Hot Water Boundaries response
+ btfsc byte1,5 ;DataInvalid Or UnknownDataID?
+ goto WordResponse ;Ignore bad responses
+ movfw byte3
+ movwf dhwsetpointmax ;Remember upper boundary
+ movfw byte4
+ movwf dhwsetpointmin ;Remember lower boundary
+ goto WordResponse
+
+;Keep track of the boundaries for central heating reported by the boiler
+MessageID49 btfss MsgResponse
+ return
+ bcf InitHeating ;Received Heating Boundaries response
+ btfsc byte1,5 ;DataInvalid Or UnknownDataID?
+ goto WordResponse ;Ignore bad responses
+ movfw byte3
+ movwf chsetpointmax ;Remember upper boundary
+ movfw byte4
+ movwf chsetpointmin ;Remember lower boundary
+ goto WordResponse
+
+;Pass on user defined boundaries for domestic hot water to the boiler
+MessageID56 btfsc MsgResponse
+ goto hotwaterreturn
+ tstf dhwsetpoint1 ;Check if a setpoint has been specified
+ btfss byte1,4 ;Change on WriteData
+ btfsc WaterSetpoint ;Or new setpoint
+ skpnz ;And a setpoint exists
+ return ;Continue with normal processing
+SetWaterSet movlw T_WRITE
+ call setbyte1 ;Turn into a write request, if necessary
+ movfw dhwsetpoint1
+ call setbyte3 ;Load the data value high byte
+ movfw dhwsetpoint2
+ call setbyte4 ;Load the data value low byte
+ retlw MSG_DHWSETPOINT ;Return MsgID when used as alternative
+hotwaterreturn bcf WaterSetpoint
+ btfss byte1,5
+ return
+ btfss byte1,4
+ goto InvalidHotWater ;Boiler didn't respond with DataInvalid
+ ;Boiler does not support the message
+ call messageack ;Turn request into acknowledgement
+ movfw dhwsetpoint1
+ skpnz
+ movfw dhwsetpointmax
+ call setbyte3
+ movfw dhwsetpoint2
+ goto setbyte4
+InvalidHotWater tstf dhwsetpoint1
+ skpnz
+ return
+ clrf dhwsetpoint1 ;Zap the invalid value
+ clrf dhwsetpoint2
+ return
+
+;Pass on user defined boundaries for central heating to the boiler
+MessageID57 btfsc MsgResponse
+ goto maxchsetptret
+ tstf maxchsetpoint1 ;Check if a setpoint has been specified
+ btfss byte1,4 ;Change on WriteData
+ btfsc MaxHeatSetpoint ;Or new setpoint
+ skpnz ;And a setpoint exists
+ return ;Continue with normal processing
+SetHeatingSet movlw T_WRITE
+ call setbyte1 ;Turn into a write request, if necessary
+ movfw maxchsetpoint1
+ call setbyte3 ;Load the data value high byte
+ movfw maxchsetpoint2
+ call setbyte4 ;Load the data value low byte
+ retlw MSG_MAXCHSETPOINT ;Return MsgID when used as alternative
+maxchsetptret bcf MaxHeatSetpoint
+ btfss byte1,5
+ return ;Boiler was happy with the message
+ btfss byte1,4
+ goto InvalidHeating
+ ;Boiler does not support the message
+ call messageack ;Turn request into acknowledgement
+ movfw maxchsetpoint1
+ skpnz
+ movfw chsetpointmax
+ call setbyte3
+ movfw maxchsetpoint2
+ goto setbyte4
+InvalidHeating tstf maxchsetpoint1
+ skpnz
+ return
+ clrf maxchsetpoint1 ;Zap the invalid value
+ clrf maxchsetpoint2
+ return
+
+; Control setpoint ventilation / heat-recovery
+MessageID71 clrc ;Prepare for shift operation
+ rrf ventsetpoint,W ;Get the user defined setpoint
+ skpnc ;There is no user defined setpoint
+ btfsc MsgResponse ;Nothing to do for a response
+ return ;Leave message unmodified
+ call setbyte4 ;Put the setpoint in the low byte
+ goto setbyte3 ;Clear the high byte
+
+;Indicate under which conditions the remote override setpoint can be overridden
+;by the thermostat. The opentherm specs are quite confusing regarding which byte
+;the flags must be in. So the gateway has an option to put them in both bytes.
+MessageID100 btfss MsgResponse
+ return
+ btfsc OverrideFunc ;There is a new set of function bits
+ swapf RemOverrideFunc,F ;Start using the new function bits
+ bcf AlternativeUsed ;Allow user to override the response
+ call WordResponse
+ btfsc AlternativeUsed
+ goto UserOverride ;---
+ call messageack ;Turn request into acknowledgement
+ swapf RemOverrideFunc,W
+ andlw b'11' ;Get the remote override flags
+ call setbyte4
+ btfsc OverrideHigh
+ movfw byte4 ;Fix for confused manufacturers
+ call setbyte3 ;Reserved, must be 0?
+UserOverride btfsc TStatISense ;Special treatment for iSense thermostat
+ btfss OverrideFunc ;First time after setpoint change?
+ goto OverrideFuncEnd
+ swapf RemOverrideFunc,W
+ xorwf RemOverrideFunc,W ;Compare the upper and lower nibbles
+ skpz
+ btfsc OverrideWait ;Func before setpoint
+ goto OverrideFuncEnd ;Previously sent fucntion bits are OK
+ bsf OverrideClr ;Clear the remote override setpoint
+ bsf OverrideReq ;Then set it again
+ bsf OverrideWait
+OverrideFuncEnd bcf OverrideFunc ;Override func has been sent
+ return
+
+;The gateway implements version 3.0 of the opentherm protocol specification
+MessageID125 btfss MsgResponse
+ return
+ call messageack ;Turn request into acknowledgement
+ movlw 3
+ call setbyte3 ;Major version number
+ goto setbyte4 ;Minor version number
+
+;Check if work-arounds are needed for bugs in certain thermostats
+MessageID126 btfss MsgResponse ;Only interested in requests
+ btfss TStatRemeha ;Only Remeha thermostats have known bugs
+ return
+ ;c-Mix: 17/17
+ ;Celcia20: 20/3
+ ;iSense: 30/19, 32/23
+ bcf TStatISense ;Clear guess based on ID2 information
+ movfw byte3 ;Master device product version number
+ sublw 20 ;Celcia 20
+ skpnz
+ bsf TStatCelcia
+ skpc
+ bsf TStatISense ;At least 30 and 32 are iSenses
+ return
+
+;Messages to read transparent slave parameters (TSPs) and fault history buffer
+;(FHB) entries.
+TSPBufferSize btfss MsgResponse ;Only interested in responses
+ return
+ btfsc AlternativeUsed ;Info was not requested by the gateway
+ btfss BoilerResponse ;Response from the boiler
+ goto WordResponse ;Response going back to the thermostat
+ movfw byte3 ;Get the number of entries
+ btfss byte1,5 ;Check for valid response
+ skpnz
+ return ;No (valid) data available
+ movwf TSPCount ;Store the number of entries
+ incf byte2,W ;Reading entries is the next MsgID
+ movwf prioritymsgid ;Start reading entries
+ bsf PriorityMsg
+ return
+
+TSPReadEntry btfsc MsgResponse ;Only interested in responses ...
+ btfss AlternativeUsed ;... not requested by the thermostat
+ goto ByteResponse
+ incf TSPIndex,F ;Index of next entry
+ movfw TSPCount
+ subwf TSPIndex,W ;Check if more entries exist
+ skpc
+ bsf PriorityMsg ;Prepare to read next entry
+ return
+
+;Remove blacklisting for special DataIDs. Do this when receiving a request, so
+;even (accidental) manual action cannot blacklist the DataID.
+MandatoryID btfsc MsgResponse ;Only interested in requests
+ return
+ call UnknownMask ;Get mask and pointer into unknownmap
+ xorlw -1 ;Invert the mask
+ andwf INDF,F ;Unmark the ID as unsupported
+ return
+
+Alternative movlw T_READ ;Alternative is probably a read command
+ movwf byte1
+ clrf byte3 ;And a data value of 0
+ clrf byte4
+ tstf initflags
+ skpnz
+ goto GotConfigData
+ ;Obtain some parameters from the boiler that are useful for the
+ ;internal operation of the gateway.
+ bsf NoResetUnknown ;Don't whitelist if command succeeds
+ btfsc InitParameter
+ retlw MSG_REMOTEPARAM
+ btfsc InitHotWater
+ retlw MSG_DHWBOUNDS
+ btfsc InitHeating
+ retlw MSG_MAXCHBOUNDS
+ btfsc WaterSetpoint
+ goto SetWaterSet
+ btfsc MaxHeatSetpoint
+ goto SetHeatingSet
+ btfss PriorityMsg
+ goto InitDone
+ movfw TSPIndex
+ movwf byte3
+ movfw prioritymsgid
+ return
+InitDone clrf initflags
+GotConfigData movfw resetflags ;Check for pending reset requests
+ skpz
+ goto ResetCommand ;Send a reset command
+ btfsc NoAlternative
+ goto RestoreRequest ;No alternatives available, W = 0
+ movlw 32 ;Size of the list of alternatives
+ movwf loopcounter ;Counter to prevent infinite loop
+AltLoop incf alternativecmd,W
+ andlw b'11111'
+ movwf alternativecmd ;Next entry in the list of alternatives
+ addlw AlternativeCmd ;Add the starting address of the list
+ pcall EepromWait ;Wait for any pending EEPROM activity
+ bcf STATUS,RP0
+ movwf EEADR ;Load the list pointer
+ bsf STATUS,RP0
+ bsf EECON1,RD ;Prepare for reading EEPROM data
+ bcf STATUS,RP0
+ movfw EEDATA ;Get the alternative message ID
+ bcf STATUS,RP1 ;Bank 0
+ skpz ;Check if the slot is empty
+ return ;Found an entry
+ decfsz loopcounter,F
+ goto AltLoop ;Continue searching
+ bsf NoAlternative ;List of alternatives is empty
+RestoreRequest bcf AlternativeUsed ;Not providing an alternative after all
+ ;At this point W = 0
+CreateMessage iorwf originaltype,W ;Combine W and the original message type
+ movwf byte1 ;Set the message type for the response
+ movfw databyte1
+ movwf byte3 ;Restore data byte #1
+ movfw databyte2
+ movwf byte4 ;Restore data byte #2
+ movfw originalreq
+ movwf byte2 ;Restore the message ID
+ return
+
+ResetCommand movlw T_WRITE
+ movwf byte1 ;Initialize a Write-Data message
+ movlw 116 ;MsgID of first counter
+ movwf loopcounter ;Initialize loopcounter
+ clrf temp ;Use for mask
+ setc ;Make sure to shift in a '1'
+ goto ResetCmdJump
+ResetCmdLoop incf loopcounter,F
+ResetCmdJump rlf temp,F ;Shift the mask one position
+ movfw temp ;Get the mask
+ andwf resetflags,W ;Check if the matching resetflag is set
+ skpnz
+ goto ResetCmdLoop ;Find the first set bit
+ xorwf resetflags,F ;Clear the resetflag
+ movfw loopcounter ;Get the Message ID
+ return
+
+UnknownMask movfw byte2
+UnknownMask2 movwf temp2
+ rrf temp2,W
+ movwf temp
+ rrf temp,W
+ andlw b'00011111' ;Get bit 2-7
+ addlw unknownmap
+ movwf FSR ;Pointer into the map of unknown ID's
+ btfsc temp2,7 ;Check for msg ID's in the range 0..127
+ retlw 0 ;Don't blacklist vendor specific ID's
+ movlw b'11'
+ btfsc temp2,0
+ movlw b'1100'
+ movwf temp
+ btfsc temp2,1
+ swapf temp,F ;Calculate mask for the desired bit
+ comf INDF,W
+ andwf temp,W ;Check if ID is unsupported by boiler
+ swapf temp,F
+ swapf temp,W ;Get the mask without changing the Z bit
+ return ;Z = 1 if message is not supported
+
+;************************************************************************
+;Values to store
+;Byte 0 : (00) 0 1 6 01000011
+;Byte 1 : (03) 14 15 11000000
+;Byte 2 : (05) 16 17 18 00000111
+;Byte 3 : (08) 24 25 26 27 28 00011111
+;Byte 4 : 00000000
+;Byte 5 : 00000000
+;Byte 6 : (0D) 48 49 00000011
+;Byte 7 : (0F) 56 57 00000011
+;Byte 8 : 00000000
+;Byte 9 : 00000000
+;Byte 10: 00000000
+;Byte 11: 00000000
+;Byte 12: 00000000
+;Byte 13: 00000000
+;Byte 14: (11) 116 117 118 119 11110000
+;Byte 15: (15) 120 121 122 123 00001111
+;Keep the three tables in sync!
+
+#define outsideval1 valuestorage + 22
+#define outsideval2 valuestorage + 23
+
+StoreValue rlf byte2,W
+ skpc ;Ignore manufacturer specific ID's
+ btfsc byte1,5 ;Only store valid data
+ return
+ movwf temp
+ swapf temp,W
+ andlw b'00001111' ;Calculate table entry
+ movwf temp
+ movfw byte2
+ andlw b'00000111'
+ movwf loopcounter
+ incf loopcounter,F
+ call LookupStoreFlag
+ xorwf temp,W ;Exchange W and temp
+ xorwf temp,F
+ xorwf temp,W ;--
+ clrf tempvar0
+ clrc ;Carry may be set in LookupStoreFlag
+StoreValueLoop skpnc
+ incf tempvar0,F
+ rrf temp,F
+ decfsz loopcounter,F
+ goto StoreValueLoop
+ skpc
+ return ;Not storing this ID
+ call LookupStoreOffs
+ addwf tempvar0,F
+ rlf tempvar0,W
+ addlw valuestorage
+ movwf FSR
+ bsf STATUS,IRP
+ movfw byte3
+ movwf INDF
+ incf FSR,F
+ movfw byte4
+ movwf INDF
+ bcf STATUS,IRP
+ return
+
+LookupStoreFlag movlw high StoreFlagTable
+ movwf PCLATH
+ movfw temp
+ addlw low StoreFlagTable
+ skpnc
+ incf PCLATH,F
+ movwf PCL
+StoreFlagTable dt b'01000011'
+ dt b'11000000'
+ dt b'00000111'
+ dt b'00011111'
+ dt b'00000000'
+ dt b'00000000'
+ dt b'00000011'
+ dt b'00000011'
+ dt b'00000000'
+ dt b'00000000'
+ dt b'00000000'
+ dt b'00000000'
+ dt b'00000000'
+ dt b'00000000'
+ dt b'11110000'
+ dt b'00001111'
+
+LookupStoreOffs movwf temp
+ movlw high StoreOffsTable
+ movwf PCLATH
+ movfw temp
+ addlw low StoreOffsTable
+ skpnc
+ incf PCLATH,F
+ movwf PCL
+StoreOffsTable dt 0
+ dt 3
+ dt 5
+ dt 8
+ dt 13
+ dt 13
+ dt 13
+ dt 15
+ dt 17
+ dt 17
+ dt 17
+ dt 17
+ dt 17
+ dt 17
+ dt 17
+ dt 21
+ constant SUMMARYFIELDS=25 ;Maximum: 48
+
+;The following code should only run for responses going back to the thermostat.
+;A response to an alternative message coming back from the boiler (which will
+;not be sent to the thermostat) can be recognized by both BoilerResponse and
+;AlternativeUsed being set.
+;The clever combination of bit tests in the following code will end up at a
+;return command for requests or alternative responses.
+FakeResponse btfsc BoilerResponse
+ btfss AlternativeUsed
+ btfss MsgResponse
+ return ;Not a message towards the thermostat
+ goto ClearNoneResp
+ByteResponse btfsc BoilerResponse
+ btfss AlternativeUsed
+ btfss MsgResponse
+ return ;Not a message towards the thermostat
+ goto ClearByteResp
+MandatoryWord call MandatoryID ;Prevent the DataID getting blacklisted
+WordResponse btfsc BoilerResponse
+ btfss AlternativeUsed
+ btfss MsgResponse
+ return ;Not a message towards the thermostat
+ClearWordResp clrf databyte1
+ClearByteResp clrf databyte2
+ClearNoneResp movfw byte2 ;Get the DataID
+ pcall FindResponse
+ skpz
+ return ;No entry found for the DataID
+ call messageack
+ bsf STATUS,IRP ;Access memory bank 2&3
+ movfw INDF ;Get databyte 2
+ iorwf databyte2,W
+ call setbyte4
+ bcf FSR,7
+ movfw INDF ;Get databyte 1
+ bcf STATUS,IRP ;Access memory bank 0&1
+ iorwf databyte1,W
+ goto setbyte3
+
+;************************************************************************
+; Switch LEDs on and off depending on their configured function
+;************************************************************************
+
+SwitchLED skpnz ;Zero bit indicates LED state
+ goto SwitchOffLED
+
+SwitchOnLED addlw functions - 'A' ;Point into the table of functions
+ movwf FSR ;Setup indirect addressing
+ bsf INDF,0 ;Remember that this function is on
+ movlw b'11111110' ;Mask off the state bit
+ andwf INDF,W ;Get the LED(s) for the function
+ skpnz
+ return ;No LED is assigned to this function
+ xorlw -1 ;Invert the bitmask
+ andwf PORTB,F ;Switch on the LED
+ iorlw ~b'110'
+ andwf gpioflags,F ;Switch on virtual LEDs E/F
+ retlw 0
+
+SwitchOffLED addlw functions - 'A' ;Point into the table of functions
+ movwf FSR ;Setup indirect addressing
+ bcf INDF,0 ;Remember that this function is off
+ movfw INDF ;Get the LED for the function
+ skpnz
+ return ;No LED is assigned to this function
+ iorwf PORTB,F ;Switch off the LED
+ andlw b'110'
+ iorwf gpioflags,F ;Switch off virtual LEDs E/F
+ retlw 0
+
+;************************************************************************
+; Parse commands received on the serial interface
+;************************************************************************
+
+ package Serial
+SerialCommand btfsc Overrun
+ goto SerialOverrun
+SerialCmdSub movlw 4
+ subwf rxpointer,W ;Check the length of the command >= 4
+ skpc
+ retlw SyntaxError ;Invalid command
+ movfw rxbuffer + 2 ;Third character of every command ...
+ sublw '=' ;... must be '='
+ skpz
+ retlw SyntaxError
+ movlw high SerialCmdTable
+ movwf PCLATH
+ movfw rxbuffer ;Get the first character of the command
+ xorwf rxbuffer + 1,W ;Combine with the second character
+ movwf temp ;Save for later use
+ andlw ~b'11111' ;First quick check for a valid command
+ skpz
+ retlw CommandNG ;Command is not valid
+ ;Report the command back to the caller
+ movfw rxbuffer
+ lcall PrintChar
+ movfw rxbuffer + 1
+ call PrintChar
+ movlw ':'
+ call PrintChar
+ movlw ' '
+ call PrintChar
+ ;Indexed jump on the exclusive or of the 2 command characters
+ movlw high SerialCmdTable
+ movwf PCLATH
+ movfw temp
+ addlw low SerialCmdTable
+ skpnc
+ incf PCLATH,F
+ movwf PCL ;Make a quick initial selection
+; Calculate table index with Tcl: tcl::mathop::^ {*}[scan SB %c%c]
+SerialCmdTable goto SerialCmd00 ; AA, MM, TT commands
+ goto SerialCmd01 ; RS, SR commands
+ goto SerialCmd02 ; PR, KI commands
+ goto SerialCmd03 ; PS command
+ goto SerialCmd04 ; VR, SW commands
+ goto SerialCmd05 ; DA, GB, VS commands
+ goto SerialCmdGPIO ; GA command
+ goto SerialCmd07 ; OH command
+ goto SerialCmdLED ; LD command
+ goto SerialCmdLED ; LE command
+ goto SerialCmdLED ; LF command
+ goto SerialCmd0B ; CH command
+ retlw CommandNG
+#ifndef LVP
+ goto SerialCmdLED ; LA command
+#else
+ retlw CommandNG
+#endif
+ goto SerialCmdLED ; LB command
+ goto SerialCmdLED ; LC command
+ goto SerialCmd10 ; GW, SC, CS commands
+ goto SerialCmd11 ; CR, SB commands
+ goto SerialCmd12 ; FT command
+ retlw CommandNG
+ goto SerialCmd14 ; DP command
+ retlw CommandNG
+ retlw CommandNG
+ goto SerialCmd17 ; TC command
+ retlw CommandNG
+ retlw CommandNG
+ retlw CommandNG
+ goto SerialCmd1B ; OT, SH commands
+ goto SerialCmd1C ; UI command
+ goto SerialCmd1D ; IT, PM commands
+ retlw CommandNG
+ goto SerialCmd1F ; HW command
+
+SerialOverrun bcf Overrun
+ retlw OverrunError
+
+SerialCmd00 movfw rxbuffer
+ xorlw 'T'
+ skpnz
+ goto SetTempSetPoint
+ xorlw 'T' ^ 'A'
+ skpnz
+ goto SetAlternative
+ xorlw 'A' ^ 'M'
+ skpnz
+ goto SetMaxModLevel
+ retlw CommandNG
+
+SerialCmd01 movfw rxbuffer
+ xorlw 'R'
+ skpnz
+ goto ResetCounter
+ xorlw 'R' ^ 'S'
+ skpnz
+ goto SetResponse
+ retlw CommandNG
+
+SerialCmd02 movfw rxbuffer
+ xorlw 'P'
+ skpnz
+ goto ReportSetting
+ xorlw 'P' ^ 'K'
+ skpnz
+ goto KnownDataID
+ retlw CommandNG
+
+SerialCmd03 movfw rxbuffer
+ xorlw 'P'
+ skpnz
+ goto ReportSummary
+ retlw CommandNG
+
+SerialCmd04 movfw rxbuffer
+ xorlw 'V'
+ skpnz
+ goto SetVoltageRef
+ xorlw 'V' ^ 'S'
+ skpnz
+ goto SetHotWaterTemp
+ retlw CommandNG
+
+SerialCmd05 movfw rxbuffer
+ xorlw 'D'
+ skpnz
+ goto DelAlternative
+ xorlw 'D' ^ 'V'
+ skpnz
+ goto SetVentSetpoint
+ goto SerialCmdGPIO
+
+SerialCmd07 movfw rxbuffer
+ xorlw 'O'
+ skpnz
+ goto SetOverrideHigh
+ retlw CommandNG
+
+SerialCmd0B movfw rxbuffer
+ xorlw 'C'
+ skpnz
+ goto SetCentralHeat
+ retlw CommandNG
+
+SerialCmd10 movfw rxbuffer
+ xorlw 'S'
+ skpnz
+ goto SetClock
+ xorlw 'S' ^ 'G'
+ skpnz
+ goto SetGatewayMode
+ xorlw 'G' ^ 'C'
+ skpnz
+ goto SetCtrlSetpoint
+ retlw CommandNG
+
+SerialCmd11 movfw rxbuffer
+ xorlw 'C'
+ skpnz
+ goto ClearResponse
+ xorlw 'C' ^ 'S'
+ skpnz
+ goto SetSetBack
+ retlw CommandNG
+
+SerialCmd12 movfw rxbuffer
+ xorlw 'F'
+ skpnz
+ goto SetTStatModel
+ retlw CommandNG
+
+SerialCmd14 movfw rxbuffer
+ xorlw 'D'
+ skpnz
+ goto SetDebugPtr
+ retlw CommandNG
+
+SerialCmd17 movfw rxbuffer
+ xorlw 'T'
+ skpnz
+ goto SetContSetPoint
+ retlw CommandNG
+
+SerialCmd1B movfw rxbuffer
+ xorlw 'O'
+ skpnz
+ goto SetOutsideT
+ xorlw 'O' ^ 'S'
+ skpnz
+ goto SetHeatingTemp
+ retlw CommandNG
+
+SerialCmd1C movfw rxbuffer
+ xorlw 'U'
+ skpnz
+ goto UnknownDataID
+ retlw CommandNG
+
+SerialCmd1D movfw rxbuffer
+ xorlw 'I'
+ skpnz
+ goto IgnoreError1
+ xorlw 'I' ^ 'P'
+ skpnz
+ goto SetPrioMessage
+ retlw CommandNG
+
+SerialCmd1F movfw rxbuffer
+ xorlw 'H'
+ skpnz
+ goto SetHotWater
+ retlw CommandNG
+
+SerialCmdLED movfw rxbuffer
+ xorlw 'L'
+ skpz
+ retlw CommandNG ;LED commands must start with 'L'
+ movfw rxpointer
+ sublw 4
+ skpz
+ retlw SyntaxError ;Command length must be 4
+ movfw rxbuffer + 3
+ movwf temp ;Keep func code for storing in EEPROM
+ sublw 'Z'
+ sublw 'Z' - 'A' ;Check for valid function code
+ skpc
+ retlw BadValue ;Valid functions are 'A' - 'Z'
+ movlw 'A'
+ subwf rxbuffer + 1,W ;Get LED number
+ addlw FunctionLED1 ;Calculate EEPROM address
+ call WriteEpromData ;Save the LED configuration in EEPROM
+ movfw rxbuffer + 3
+ pcall PrintChar
+ movlw 'A'
+ subwf rxbuffer + 1,W ;Get LED number again
+ movwf temp
+ movfw rxbuffer + 3 ;Get the function code
+SetLEDFunction movwf float1 ;Save temporarily
+ movlw b'00001000' ;Bit mask for LED 0
+ btfsc temp,1
+ movlw b'01000000' ;Bit mask for LED 2
+ btfsc temp,2
+ movlw b'00000010' ;Bit mask for LED 4
+ movwf tempvar0 ;Save the bit mask
+ clrc
+ btfsc temp,0
+ rlf tempvar0,F ;Shift the bit mask for odd LEDs
+ ;Remove the old function for the LED
+ movlw functions ;Start of the function table
+ movwf FSR ;Setup indirect adressing
+ movlw 26 ;Size of the table
+ movwf loopcounter
+ comf tempvar0,W ;Invert the bit mask
+SetLEDLoop andwf INDF,F ;Remove the LED from the function flags
+ incf FSR,F ;Point to the next function
+ decfsz loopcounter,F
+ goto SetLEDLoop ;Repeat the loop
+ ;Setup the new function for the LED
+ movfw float1 ;Name of the new function
+ addlw functions - 'A' ;Pointer into the function table
+ movwf FSR ;Setup indirect addressing
+ movfw tempvar0 ;Reload the bit mask for the LED
+ iorwf INDF,F ;Link the LED to the selected function
+ movfw float1 ;Name of the new function, clears Z-bit
+ btfss INDF,0 ;Check the function state
+ setz ;Function state is off
+ lgoto SwitchLED ;Set the initial state of the LED
+
+SerialCmdGPIO movfw rxbuffer
+ sublw 'G'
+ skpz
+ retlw CommandNG ;GPIO commands must start with 'G'
+ movfw rxpointer
+ sublw 4
+ skpz
+ retlw SyntaxError ;Command length must be 4
+ movlw '0'
+ subwf rxbuffer + 3,W
+ movwf temp ;Keep func code for storing in EEPROM
+ andlw b'11111000' ;Currently there are 7 GPIO functions
+ skpz
+ retlw SyntaxError ;Invalid GPIO function
+ movlw b'11110000' ;Mask to clear old GPIO A function
+ btfsc rxbuffer + 1,0 ;Configuring GPIO B?
+ goto SetGPIOFunction
+ andwf GPIOFunction,W ;GPIO B function code in high nibble
+ sublw 0x70 ;Old GPIO B function was DS1820?
+ skpnz
+ bcf OutsideTemp ;Forget outside temperature
+ swapf temp,F ;Move new function code to upper nibble
+ movlw b'1111' ;Mask to clear old GPIO B function
+SetGPIOFunction andwf GPIOFunction,W ;Clear the old function code
+ iorwf temp,W ;Insert the new function code
+ movwf GPIOFunction ;Store in RAM
+ movwf temp ;Value to write to EEPROM
+ movlw FunctionGPIO ;EEPROM address to write to
+ call WriteEpromData ;Save in EEPROM
+ bsf gpio_port1
+ bcf gpio_port2
+ movlw b'11000000'
+ btfss rxbuffer + 1,0 ;Configuring GPIO A?
+ xorwf gpio_mask,F
+ lcall gpio_initport
+ movfw GPIOFunction
+ btfss rxbuffer + 1,0 ;Configuring GPIO A?
+ swapf GPIOFunction,W
+ andlw b'111'
+GoPrintDigit lgoto PrintDigit
+
+CheckBoolean clrc ;Carry indicates success
+ movfw rxpointer
+ xorlw 4 ;Check the command is 4 characters long
+ skpz
+ retlw SyntaxError ;Wrong length
+ movfw rxbuffer + 3 ;Get the flag state
+ sublw '1' ;It must be either '0' or '1'
+ sublw '1' - '0' ;Only the two allowed values ...
+ skpnc ;... result in a carry
+ return ;Success
+ retlw BadValue ;Illegal boolean value
+
+SetContSetPoint call GetFloatArg ;Parse floating point value
+ skpnc
+ return
+ movlw b'01' ;Only allow manual override
+ goto SetSerSetPoint
+SetTempSetPoint call GetFloatArg ;Parse floating point value
+ skpnc
+ return
+ movlw b'11' ;Allow manual and program override
+SetSerSetPoint btfsc NegativeTemp ;Check for a negative value
+ retlw SyntaxError ;Cannot set the thermostat below zero
+ call SetSetPoint ;Change the setpoint
+ goto CommandFloat ;Confirm the new setpoint
+
+SetSetPoint xorwf RemOverrideFunc,W ;Store function bits in lower nibble
+ andlw b'1111'
+ xorwf RemOverrideFunc,F ;--
+ movlw 0 ;Clear all old state information
+ btfss TStatISense ;Special treatment for iSense thermostat
+ goto SetSetPointJ1
+ movfw setpoint1 ;Check old setpoint
+ iorwf setpoint2,W
+ skpz ;There is no old setpoint
+ bsf OverrideClr ;Clear old setpoint before setting new
+ movlw 1 << 4 ;Leave OverrideClr bit intact
+SetSetPointJ1 andwf override,F ;Clear old state information
+ movfw float2 ;Copy the value to the setpoint vars
+ movwf setpoint2
+ movfw float1
+ movwf setpoint1 ;--
+ bsf OverrideReq ;Start blinking the override LED
+ bsf OverrideWait
+ bsf OverrideFunc
+ clrf celciasetpoint
+ btfsc TStatCelcia
+ bsf Unsolicited ;Send an unsolicited 04-frame
+ iorwf setpoint2,W ;Check if both bytes are 0
+ skpnz
+ goto ClearSetPoint
+ btfss TStatCelcia ;Special treatment for Celcia thermostat
+ return
+;The Remeha Celcia 20 thermostat does not react propperly to the value provided
+;in a remote room setpoint override (MsgID=09). It needs to receive a remote
+;command response (MsgID=04) to manipulate the room setpoint.
+;Thanks to Rien Groot for figuring out this feature and providing the necessary
+;information to add this option to the opentherm gateway firmware.
+CelciaValue movfw setpoint1
+ call Multiply5 ;Multiply the units by 5
+ movfw float2
+ movwf celciasetpoint
+ movfw setpoint2
+ call Multiply5 ;Multiply the fraction by 5
+ movfw float1
+ addwf celciasetpoint,F;Add the two together
+ btfsc float2,7
+ incf celciasetpoint,F;Round up
+ movfw setpoint1
+ movwf float1
+ movfw setpoint2
+ movwf float2
+ return
+ClearSetPoint bsf OverrideClr ;Override request to be cancelled
+ bcf ManChangePrio
+ bcf ProgChangePrio
+ return
+
+SetSetBack call GetFloatArg ;Parse floating point value
+ skpnc
+ return
+ iorwf float2,W
+ skpz ;Zero has special meaning in the code
+ btfsc NegativeTemp ;Check for a negative value
+ retlw SyntaxError ;Setpoint cannot be zero or below
+ movlw AwaySetpoint1 ;EEPROM address for units
+ call WriteEpromData ;Save in EEPROM
+ movfw float2
+ movwf temp
+ movlw AwaySetpoint2 ;EEPROM address for fraction
+ call WriteEpromData ;Save in EEPROM
+ goto CommandFloat
+
+SetHotWaterTemp call GetFloatArg
+ skpnc
+ return
+ iorwf float2,W
+ skpnz
+ goto HotWaterReset
+ movfw dhwsetpointmin
+ call Compare
+ skpc
+ retlw BadValue
+ movfw dhwsetpointmax
+ call Compare
+ skpz
+ skpc
+ goto HotWaterTempOK
+ retlw BadValue
+HotWaterReset bcf WaterSetpoint
+ goto HotWaterFinish
+HotWaterTempOK btfsc dhwupdate ;Check if boiler supports write access
+ bsf WaterSetpoint ;Schedule to send command to the boiler
+HotWaterFinish movfw float2
+ movwf dhwsetpoint2
+ movfw float1
+ movwf dhwsetpoint1
+ goto CommandFloat
+
+SetHeatingTemp call GetFloatArg
+ skpnc
+ return
+ iorwf float2,W
+ skpnz
+ goto HeatingReset
+ movfw chsetpointmin
+ call Compare
+ skpc
+ retlw BadValue
+ movfw chsetpointmax
+ call Compare
+ skpz
+ skpc
+ goto HeatingTempOK
+ retlw BadValue
+HeatingReset bcf MaxHeatSetpoint
+ goto HeatingFinish
+HeatingTempOK btfsc maxchupdate ;Check if boiler supports write access
+ bsf MaxHeatSetpoint ;Schedule to send command to the boiler
+HeatingFinish movfw float2
+ movwf maxchsetpoint2
+ movfw float1
+ movwf maxchsetpoint1
+ goto CommandFloat
+
+SetCtrlSetpoint call GetFloatArg
+ skpnc
+ return
+ btfsc NegativeTemp ;Check for a negative value
+ retlw SyntaxError ;Negative temperatures are not allowed
+ movwf controlsetpt1
+ movfw float2
+ movwf controlsetpt2
+ goto CommandFloat
+
+SetVentSetpoint call GetDecimalArg ;Get the ventilation setpoint
+ skpnc
+ goto ClrVentSetpoint ;Non-numeric value
+ tstf INDF
+ skpz
+ retlw SyntaxError ;Characters following the value
+ sublw 100
+ skpc
+ retlw OutOfRange ;Value must be in the range 0-100
+ rlf temp,W ;Shift a 1 into bit 0, value in bit 1-7
+ movwf ventsetpoint
+ movfw temp
+ lgoto PrintByte ;Confirm the command
+ClrVentSetpoint clrf ventsetpoint
+ goto ValueCleared
+
+SetOutsideT call GetFloatArg
+ skpnc
+ return
+ btfss float1,7 ;Temperature value negative?
+ btfss float1,6 ;Temperature higher than 64 degrees?
+ goto StoreOutsideT
+ bcf OutsideTemp ;No (reasonable) outside temperature
+ValueCleared movlw '-'
+ lgoto PrintChar
+StoreOutsideT call StoreOutTemp
+CommandFloat lgoto PrintSigned
+
+StoreOutTemp movfw float1
+ movwf outside1
+ bsf STATUS,RP1
+ movwf outsideval1
+ bcf STATUS,RP1
+ movfw float2
+ movwf outside2
+ bsf STATUS,RP1
+ movwf outsideval2
+ bcf STATUS,RP1
+ bsf OutsideTemp
+ bcf OutsideInvalid ;Outside temperature is valid
+ return
+
+SetDebugPtr movfw rxpointer
+ sublw 5
+ skpz
+ retlw SyntaxError
+ call GetHexArg
+ skpnc
+ return
+ movwf debug
+ lgoto PrintHex
+
+SetClock call GetDecimalArg ;Get the hours
+ skpnc
+ return
+ movwf clock1
+ sublw 23
+ skpc
+ retlw OutOfRange
+
+ movfw INDF
+ sublw ':'
+ skpz
+ retlw SyntaxError
+ incf FSR,F
+
+ call GetDecimal ;Get the minutes
+ skpnc
+ retlw SyntaxError
+ movwf clock2
+ sublw 59
+ skpc
+ retlw OutOfRange
+
+ movfw INDF
+ sublw '/'
+ skpz
+ retlw SyntaxError
+ incf FSR,F
+
+ movlw '0' ;Get the day of the week
+ subwf INDF,W
+ skpz
+ skpc
+ retlw BadValue
+ movwf temp
+ andlw b'11111000'
+ skpz
+ retlw BadValue
+ incf FSR,F
+ tstf INDF
+ skpz
+ retlw SyntaxError
+ swapf temp,F
+ clrc
+ rlf temp,W
+ iorwf clock1,F
+ movlw 120 | 1 << 7
+ movwf minutetimer
+ movfw clock1
+ andlw b'11111'
+ movwf temp
+ lcall PrintDecimal
+ movfw clock2
+ movwf temp
+ movlw ':'
+ call PrintFraction
+ movlw '/'
+ call PrintChar
+ swapf clock1,W
+ movwf temp
+ rrf temp,W
+ andlw b'111'
+ goto PrintDigit
+
+SetHotWater movfw rxpointer
+ sublw 4
+ skpz
+ retlw SyntaxError
+ bcf HotWaterSwitch
+ bcf HotWaterEnable
+ movfw rxbuffer + 3
+ sublw '1'
+ skpnz
+ bsf HotWaterEnable
+ andlw ~1
+ skpnz
+ bsf HotWaterSwitch
+ lgoto PrintHotWater
+
+SetCentralHeat call CheckBoolean
+ skpc
+ return
+ skpz
+ bcf CHModeOff
+ skpnz
+ bsf CHModeOff
+ goto GoPrintDigit
+
+;Set the reference voltage:
+;0 = 0.625V 1 = 0.833V 2 = 1.042V 3 = 1.250V 4 = 1.458V
+;5 = 1.667V 6 = 1.875V 7 = 2.083V 8 = 2.292V 9 = 2.500V
+SetVoltageRef movfw rxpointer
+ sublw 4
+ skpz
+ retlw SyntaxError
+ movfw rxbuffer + 3 ;Get the reference voltage setting
+ sublw '9' ;Valid values are '0' through '9'
+ sublw '9' - '0' ;Convert the value, while at the ...
+ skpc ;... same time checking for valid input
+ retlw BadValue
+ addlw 3
+ movwf temp
+ iorlw b'11100000'
+ bsf STATUS,RP0
+ movwf CVRCON
+ bcf STATUS,RP0
+ movlw b'11100000'
+ andwf settings,W
+ iorwf temp,W
+ movwf settings
+ call SaveSettings
+ lgoto PrintRefVoltage
+
+SaveSettings movwf temp
+ movlw SavedSettings
+;Write to data EEPROM. Data must be in temp, address in W
+WriteEpromData pcall EepromWait ;Wait for any pending EEPROM activity
+ bcf STATUS,RP0
+ movwf EEADR ;Setup the EEPROM data address
+ movfw temp ;Get the value to store in EEPROM
+StoreEpromData bsf STATUS,RP1
+ bsf STATUS,RP0
+ bsf EECON1,RD ;Read the current value
+ bcf STATUS,RP0
+ xorwf EEDATA,W ;Check if the write can be skipped
+ skpnz
+ goto StoreEpromSkip ;Prevent unnecessary EEPROM writes
+ xorwf EEDATA,F ;Load the data value
+ bsf STATUS,RP0
+ bsf EECON1,WREN ;Enable EEPROM writes
+ bcf INTCON,GIE ;The sequence should not be interrupted
+ ;Required sequence to write EEPROM
+ movlw 0x55
+ movwf EECON2
+ movlw 0xAA
+ movwf EECON2
+ bsf EECON1,WR ;Start the actual write
+ ;--
+ bsf INTCON,GIE ;Interrupts are allowed again
+ bcf EECON1,WREN ;Prevent accidental writes
+ bcf STATUS,RP0
+StoreEpromSkip bcf STATUS,RP1 ;Switch back to bank 0
+ movfw temp ;Return the byte that was written
+ return
+
+SetAlternative call GetDecimalArg ;Get the message number
+ skpnc ;Valid decimal value?
+ return ;Return the error code from GetDecimal
+ skpnz ;Don't allow adding message ID=0 ...
+ retlw OutOfRange ;... because 0 indicates an empty slot
+ tstf INDF ;Is this the end of the command?
+ skpz
+ retlw SyntaxError
+ bcf NoAlternative ;There now is at least one alternative
+ pcall EepromWait ;Wait for any pending EEPROM activity
+ bcf STATUS,RP0
+ movlw AlternativeCmd
+ movwf EEADR ;Setup access to list of alternatives
+ movfw temp ;Get DataID specified by the user
+SetAltLoop bsf STATUS,RP0
+ bsf EECON1,RD
+ bcf STATUS,RP0
+ tstf EEDATA ;Check for an empty slot
+ skpnz
+ goto StoreAlt ;Slot is empty, put the DataID in it
+ incf EEADR,F ;Slot not empty, try next position
+ skpz ;End of list reached?
+ goto SetAltLoop
+ bcf STATUS,RP1
+ retlw NoSpaceLeft ;No empty slot available
+StoreAlt call StoreEpromData
+ lgoto PrintByte
+
+DelAlternative call GetDecimalArg ;Get the message number
+ skpnc ;Valid decimal value?
+ return ;Return the error code from GetDecimal
+ skpnz ;Don't allow deleting message ID=0 ...
+ retlw OutOfRange ;... because 0 indicates an empty slot
+ tstf INDF ;Is this the end of the command?
+ skpz
+ retlw SyntaxError
+ call DeleteAlt
+ skpnc ;MessageID was actually deleted?
+ return ;Return the error code
+ lgoto PrintByte ;Print the message ID
+
+DeleteAlt movwf temp
+ pcall EepromWait ;Wait for any pending EEPROM activity
+ bcf STATUS,RP0
+ movlw AlternativeCmd ;Start of list of alternatives
+ movwf EEADR
+DelAltLoop bsf STATUS,RP0
+ bsf EECON1,RD ;Read a byte from EEPROM
+ bcf STATUS,RP0
+ movfw temp
+ xorwf EEDATA,W ;Compare to the message ID to delete
+ skpnz
+ goto StoreEpromData ;Release the slot by writing 0 to it
+ incf EEADR,F ;Proceed to the next slot
+ skpz ;Check if we're done
+ goto DelAltLoop ;Repeat the loop
+ bcf STATUS,RP1 ;Switch back to bank 0
+ setc ;Nothing was found
+ retlw NotFound ;The requested message ID was not found
+
+SetTStatModel movfw rxpointer
+ sublw 4 ;Command takes a single char argument
+ skpz
+ retlw SyntaxError
+ clrf remehaflags ;Assume auto-detect
+ movfw rxbuffer + 3
+ xorlw 'C' ;C=Celcia 20
+ skpnz
+ bsf TStatCelcia
+ xorlw 'C' ^ 'I' ;I=ISense
+ skpnz
+ bsf TStatISense
+ tstf remehaflags ;No set bits means auto-detection
+ skpz
+ bsf TStatManual ;Thermostat model set manually
+ movfw remehaflags
+ movwf temp
+ movlw TStatModel ;EEPROM address for thermostat model
+ call WriteEpromData ;Save in EEPROM
+ lgoto PrintRemeha ;Report the selected model
+
+SetGatewayMode movfw rxpointer ;Check the command is 4 characters long
+ sublw 4
+ skpz
+ retlw SyntaxError
+ call CheckBoolean
+ skpc
+ goto ResetGateway
+ xorwf mode,W ;Compare against the current setting
+ andlw 1 << 0
+ skpz
+ goto SetModeDone ;Not really changing the mode
+ bsf ChangeMode ;Remember to change the gateway mode
+ call SetMonitorMode
+ btfss ChangeMode ;Mode change still pending?
+SetModeDone movfw rxbuffer + 3
+ lgoto PrintChar
+
+SetMonitorMode btfsc T2CON,TMR2ON ;Check if timer 2 is running
+ retlw 'P' ;Don't change while communicating
+ movlw 1 << 0 ;Mask for the MonitorMode flag
+ xorwf mode,F ;Toggle gateway/monitor mode
+ movlw b'00000011' ;Comparator setting for gateway mode
+ btfsc MonitorMode ;Check which mode to switch to
+ movlw b'00000110' ;Comparator setting for monitor mode
+ bsf STATUS,RP0
+ movwf CMCON ;Configure the comparators
+ bcf STATUS,RP0
+ bcf ChangeMode ;Change has been applied
+ return
+
+ResetGateway movfw rxbuffer + 3
+ sublw 'R' ;GW=R resets the gateway
+ skpz
+ retlw BadValue
+ bsf resetreason,0 ;Resetting due to a serial command
+ bcf RSET ;Prepare the output latch
+ bsf STATUS,RP0
+ bcf RSET ;Switch the pin to output
+ lgoto 0 ;Simulate reset
+
+SetMaxModLevel call GetDecimalArg ;Get the modulation level
+ skpnc
+ goto ClrMaxModLevel
+ tstf INDF
+ skpz
+ retlw SyntaxError
+ sublw 100
+ skpc
+ retlw OutOfRange
+ bsf UserMaxModLevel
+ sublw 100
+ movwf MaxModLevel
+ lgoto PrintByte
+ClrMaxModLevel bcf UserMaxModLevel
+ movlw 100
+ movwf MaxModLevel
+ goto ValueCleared
+
+ReportSetting lgoto PrintSetting
+
+ReportSummary lgoto PrintSummary
+
+SetResponse call GetDecimalArg ;Get the DataID
+ skpnc
+ return ;No decimal value specified
+ skpnz ;DataID should not be 0
+ retlw BadValue
+ movwf temp2
+ movlw ':'
+ subwf INDF,W
+ skpz
+ retlw SyntaxError
+ incf FSR,F
+ clrf float1
+ call GetDecimal ;Get byte value
+ skpnc
+ return ;Missing byte value
+ tstf INDF
+ skpnz
+ goto OneByteResponse
+ movwf float1 ;Save as upper byte
+ movlw ','
+ subwf INDF,W
+ skpz
+ retlw SyntaxError
+ incf FSR,F
+ call GetDecimal ;Get the lower byte value
+ skpnc
+ return ;Missing lower byte value
+ tstf INDF ;Command should be finished here
+ skpz
+ retlw SyntaxError
+OneByteResponse movwf float2 ;Save lower byte
+ movfw temp2 ;Get specified DataID
+ call FindResponse ;Check if it is already in the list
+ skpnz
+ goto UpdateResponse ;DataID found in the list
+ clrw
+ call FindResponse ;Find an empty slot
+ skpz
+ retlw NoSpaceLeft ;There is no empty slot
+UpdateResponse bsf STATUS,IRP
+ movfw float2 ;Get the lower byte
+ movwf INDF ;Save in the slot matching the DataID
+ bcf FSR,7
+ movfw float1 ;Get the upper byte
+ movwf INDF ;Save in the slot matching the DataID
+ bcf STATUS,IRP
+ bsf FSR,7
+ movfw temp2
+ movwf INDF ;Save the DataID
+ lcall PrintByte
+ movlw ':'
+ call PrintChar
+ goto PrintBytes
+
+ClearResponse call GetDecimalArg ;Get the DataID
+ skpnc
+ return ;No decimal value specified
+ skpnz ;DataID should not be 0
+ retlw BadValue
+ tstf INDF
+ skpz
+ retlw SyntaxError
+ call FindResponse ;Find the slot for the DataID
+ skpz
+ retlw NotFound ;The DataID was not in the list
+ clrf INDF ;Release the slot
+ movfw temp
+ lgoto PrintByte
+
+FindResponse movwf temp ;Save the DataID to search for
+ movlw ResponseValues1
+ movwf FSR
+FindResLoop movfw INDF ;Get the DataID for the response
+ xorwf temp,W
+ skpnz ;Check if the slot matches
+ return ;DataID found (carry is cleared)
+ movlw 1
+ addwf FSR,F
+ skpdc
+ goto FindResLoop
+ retlw 0 ;DataID not found (carry is set)
+
+UnknownDataID call GetDecimalArg ;Get the DataID
+ skpnc
+ return ;No decimal value specified
+ skpz ;DataID should not be 0
+ movwf float1
+ btfsc float1,7 ;Only allow MsgID's in range 1..127
+ retlw BadValue
+ tstf INDF ;Is this the end of the command?
+ skpz
+ retlw SyntaxError
+ pcall UnknownMask2 ;Get mask and pointer into unknownmap
+ iorwf INDF,F ;Mark message as unsupported
+ movfw float1
+ call BitMask
+ addlw UnknownFlags
+ pcall ReadEpromData
+ iorwf temp,W
+StoreDataIdMask call StoreEpromData ;Update non-volatile storage
+ movfw float1
+ lgoto PrintByte
+
+KnownDataID call GetDecimalArg ;Get the DataID
+ skpnc
+ return ;No decimal value specified
+ skpz ;DataID should not be 0
+ movwf float1
+ btfsc float1,7 ;Only allow MsgID's in range 1..127
+ retlw BadValue
+ tstf INDF ;Is this the end of the command?
+ skpz
+ retlw SyntaxError
+ pcall UnknownMask2 ;Get mask and pointer into unknownmap
+ xorlw -1 ;Invert the mask
+ andwf INDF,F ;Mark message as supported
+ movfw float1
+ call BitMask
+ addlw UnknownFlags
+ pcall ReadEpromData
+ comf temp,F
+ andwf temp,W
+ goto StoreDataIdMask
+
+BitMask clrc
+ movwf temp2
+ movlw b'1'
+ btfsc temp2,1
+ movlw b'100'
+ movwf temp
+ btfsc temp2,0
+ rlf temp,F
+ btfsc temp2,2
+ swapf temp,F
+ swapf temp2,F
+ rlf temp2,W
+ rlf temp2,W
+ andlw b'11111'
+ return
+
+;There have been reports that some equipment does not produce clean transitions
+;between the two logical Opentherm signal levels. As a result the gateway may
+;produce frequent Error 01 reports. By setting the IT flag, the requirement for
+;a maximum of one mid-bit transition is no longer checked.
+;When this flag is set, Error 01 will never occur.
+IgnoreError1 call CheckBoolean
+ skpc
+ return
+ movlw 1 << 5 ;Mask for IgnoreErr1
+SetSetting btfsc rxbuffer + 3,0
+ iorwf settings,F ;Set the option
+ xorlw -1 ;Invert the mas
+ btfss rxbuffer + 3,0
+ andwf settings,F ;Clear the option
+ movfw settings
+ call SaveSettings ;Store the setting in EEPROM
+ movfw rxbuffer + 3
+ lgoto PrintChar
+
+SetOverrideHigh call CheckBoolean
+ skpc
+ return
+ movlw 1 << 6 ;Mask for the OverrideHigh bit
+ goto SetSetting
+
+;Reset boiler counters
+;Algorithm:
+;First char: H = 0, W = 2
+;Second char: B = 0, P = 1
+;Third char: S = 0, H = 4
+;If bit 1 is set, xor bit 0
+
+ResetCounter movfw rxpointer
+ sublw 6
+ skpz
+ retlw SyntaxError
+ movlw 1
+ movwf temp
+ movfw rxbuffer + 3
+ xorlw 'H'
+ skpnz
+ goto ResetCntJump1
+ xorlw 'H' ^ 'W'
+ skpz
+ retlw SyntaxError
+ movlw 4
+ movwf temp
+ movlw 'B' ^ 'P'
+ResetCntJump1 xorwf rxbuffer + 4,W
+ xorlw 'B'
+ skpnz
+ goto ResetCntJump2
+ xorlw 'B' ^ 'P'
+ skpz
+ retlw SyntaxError
+ clrc
+ rlf temp,F
+ResetCntJump2 movfw rxbuffer + 5
+ xorlw 'S'
+ skpnz
+ goto ResetCntJump3
+ xorlw 'S' ^ 'H'
+ skpz
+ retlw SyntaxError
+ swapf temp,F
+ResetCntJump3 movfw temp
+ iorwf resetflags,F
+ movfw rxbuffer + 3
+ lcall PrintChar
+ movfw rxbuffer + 4
+ call PrintChar
+ movfw rxbuffer + 5
+ goto PrintChar
+
+SetPrioMessage call GetDecimalArg ;Get the DataID
+ skpnc
+ return ;No decimal value specified
+ tstf INDF ;Is this the end of the command?
+ skpz
+ retlw SyntaxError
+ movwf prioritymsgid
+ bsf PriorityMsg
+ clrf TSPCount
+ clrf TSPIndex
+ lgoto PrintByte
+
+CmdArgPointer movlw rxbuffer + 3
+ movwf FSR
+ return
+
+;Returns the decimal value found. Carry is set on error
+GetDecimalArg call CmdArgPointer
+GetDecimal movfw INDF
+ sublw '9'
+ sublw '9' - '0'
+ skpc
+ goto CarrySyntaxErr
+GetDecLoop movwf temp
+ incf FSR,F
+ movfw INDF
+ sublw '9'
+ sublw '9' - '0'
+ skpc
+ goto GetDecReturn
+ movwf tempvar0
+ movlw 26
+ subwf temp,W
+ skpnc
+ retlw OutOfRange
+ movfw temp
+ call Multiply10
+ addwf tempvar0,W
+ skpc
+ goto GetDecLoop
+ retlw OutOfRange
+GetDecReturn movfw temp
+ return
+CarrySyntaxErr setc
+ retlw SyntaxError
+
+GetHexArg call CmdArgPointer
+GetHexadecimal call GetHexDigit
+ skpnc
+ return
+ movwf temp
+ swapf temp,F
+ call GetHexDigit
+ skpc
+ iorwf temp,W
+ return
+
+GetHexDigit movlw '0'
+ subwf INDF,W
+ skpc
+ goto CarrySyntaxErr
+ addlw -10
+ skpc
+ goto GetHexDigitNum
+ andlw ~h'20'
+ addlw -7
+ skpc
+ goto CarrySyntaxErr
+ addlw -6
+ skpnc
+ retlw SyntaxError
+ addlw 6
+GetHexDigitNum addlw 10
+ incf FSR,F
+ clrc
+ return
+
+GetFloatArg call CmdArgPointer
+GetFloat bsf NegativeTemp ;Assume a negative value
+ movfw INDF
+ sublw '-' ;Check if the assumption is right
+ skpnz
+ goto GetSignedTemp
+ bcf NegativeTemp ;No minus sign. Must be positive then.
+ sublw '-' - '+' ;Check for a plus sign
+ skpnz ;No sign specified
+GetSignedTemp incf FSR,F ;Skip the sign
+ call GetDecimal ;Get the integer part
+ skpnc ;Carry is set on error
+ return ;Return the error code from GetDecimal
+ clrf float1 ;Assume no fractional part
+ movfw INDF ;Check for end of string
+ skpnz
+ goto GetFloatValue ;No fractional part specified
+ sublw '.'
+ skpz
+ goto CarrySyntaxErr ;The only allowed character is a '.'
+ movlw b'000000111' ;Action encoding
+ movwf loopcounter
+FloatFracLoop incf FSR,F
+ movfw INDF ;Check for end of string
+ skpnz
+ goto GetFloatValue ;End of fractional part
+ sublw '9'
+ sublw '9' - '0' ;Only digits are allowed at this point
+ skpc
+ goto CarrySyntaxErr ;Found something other than a digit
+ btfsc loopcounter,2
+ call Multiply10 ;Multiply the digit by 10
+ btfsc loopcounter,1
+ addwf float1,F
+ clrc
+ rrf loopcounter,F
+ tstf loopcounter
+ skpnz
+ skpc
+ goto FloatFracLoop
+ addlw -5
+ skpnc
+ incf float1,F
+ goto FloatFracLoop
+GetFloatValue btfss NegativeTemp ;Check for negative values
+ goto GetFloatWrapUp
+ comf temp,F ;Invert the integer part
+ tstf float1
+ skpnz
+ goto GetFloatNegInt
+ movfw float1
+ sublw 100
+ movwf float1
+GetFloatWrapUp clrf float2
+ call Divide100
+ tstf tempvar1 ;The honneywell thermostat always ...
+ skpz ;... rounds down, so if the result ...
+ addlw 1 ;... is not exact, round up
+ movwf float2
+ movfw temp
+ movwf float1
+ return ;Carry is guaranteed to be clear
+GetFloatNegInt incf temp,F ;Add 1 to get negative value
+ goto GetFloatWrapUp
+
+Multiply5 clrf float1
+ movwf float2
+ call Multiply2
+ call Multiply2
+ addwf float2,F
+ skpnc
+ incf float1,F
+ return
+
+Multiply2 clrc
+ rlf float2,F
+ rlf float1,F
+ return
+
+Multiply10 movwf tempvar1 ;Store a copy in a temporary variable
+ addwf tempvar1,F ;Add again to get: value * 2(clears C)
+ rlf tempvar1,F ;Shift left gives: value * 4
+ addwf tempvar1,F ;Add original value: value * 5
+ rlf tempvar1,W ;Shift left for: value * 10
+ return ;Return the result
+
+;Divide float1:float2 by 100 - result in W
+Divide100 movlw 16
+ movwf loopcounter
+ clrf tempvar0
+ clrf tempvar1
+DivideLoop rlf float2,F
+ rlf float1,F
+ rlf tempvar1,F
+ movlw 100
+ subwf tempvar1,W
+ skpnc
+ movwf tempvar1
+ rlf tempvar0,F
+ decfsz loopcounter,F
+ goto DivideLoop
+ movfw tempvar0
+ return
+
+;Compare the value in float1:float2 to W, sets C and Z bit. C=1 if float >= W
+Compare subwf float1,W
+ skpz
+ return
+ tstf float2
+ return
+
+;************************************************************************
+; Implement the GPIO port functions
+;************************************************************************
+
+ package gpio
+ constant GPIO_NONE=0
+ constant GPIO_GND=1
+ constant GPIO_VCC=2
+ constant GPIO_LEDE=3
+ constant GPIO_LEDF=4
+ constant GPIO_HOME=5
+ constant GPIO_AWAY=6
+ constant GPIO_DS1820=7
+
+gpio_init bsf gpio_port1 ;Set up the port mask for gpio port 1
+ bcf gpio_port2
+ call gpio_initport ;Initialize the port funtion
+ bcf gpio_port1 ;Set up the port mask for gpio port 2
+ bsf gpio_port2
+gpio_initport movlw high gpio_inittab ;Standard indexed jump code
+ movwf PCLATH
+ movfw GPIOFunction
+ btfsc gpio_port2
+ swapf GPIOFunction,W
+ andlw b'111' ;Limit to 0-7
+ addlw low gpio_inittab
+ skpnc
+ incf PCLATH,F
+ movwf PCL ;Jump into the jump table
+gpio_inittab goto gpio_input ;GPIO_NONE
+ goto gpio_initgnd ;GPIO_GND
+ goto gpio_initvcc ;GPIO_VCC
+ goto gpio_initvcc ;GPIO_LEDE
+ goto gpio_initvcc ;GPIO_LEDF
+ goto gpio_input ;GPIO_HOME
+ goto gpio_input ;GPIO_AWAY
+ goto gpio_onewire ;GPIO_DS1820
+gpio_onewire movlw b'11110000'
+ btfss gpio_port2 ;Function is only supported on port 2
+ andwf GPIOFunction,F ;Disable function on port 1
+gpio_input movfw gpio_mask ;Get the port mask
+ andlw b'11000000' ;Mask of the other bits
+ bsf STATUS,RP0
+ iorwf TRISA,F ;Set the port to input
+ bcf STATUS,RP0
+ return
+gpio_output comf gpio_mask,W ;Get the inverted port mask
+ iorlw b'00111111' ;Set all other bits
+ bsf STATUS,RP0
+ andwf TRISA,F ;Set the port to output
+ bcf STATUS,RP0
+ return
+gpio_initgnd call gpio_output ;Make the port an output
+ andwf PORTA,F ;Pull the output low
+ return
+gpio_initvcc call gpio_output ;Make the port an output
+ xorlw -1 ;Invert the mask
+ iorwf PORTA,F ;Pull the output high
+ return
+
+gpio_common bsf gpio_port1 ;Set up the port mask for gpio port 1
+ bcf gpio_port2
+ call gpio_apply ;Perform the port function
+ bcf gpio_port1 ;Set up the port mask for gpio port 2
+ bsf gpio_port2
+gpio_apply movlw high gpio_table ;Standard indexed jump code
+ movwf PCLATH
+ movfw GPIOFunction
+ btfsc gpio_port2
+ swapf GPIOFunction,W
+ andlw b'111' ;Limit to 0-7
+ addlw low gpio_table
+ skpnc
+ incf PCLATH,F
+ movwf PCL ;Jump into the jump table
+gpio_table return ;GPIO_NONE
+ return ;GPIO_GND
+ return ;GPIO_VCC
+ goto gpio_lede ;GPIO_LEDE
+ goto gpio_ledf ;GPIO_LEDF
+ goto gpio_home ;GPIO_HOME
+ goto gpio_away ;GPIO_AWAY
+ goto gpio_temp ;GPIO_DS1820
+gpio_lede movfw PORTA ;Get current state of the GPIO pin
+ btfsc VirtualLedE ;Check the shadow LED state
+ comf PORTA,W ;Get inverted state of the GPIO pin
+ andwf gpio_mask,W ;Mask off the other GPIO pin
+ andlw b'11000000' ;Mask off the remaining bits
+ xorwf PORTA,F ;Update the GPIO port
+ return
+gpio_ledf movfw PORTA ;Get current state of the GPIO pin
+ btfsc VirtualLedF ;Check the shadow LED state
+ comf PORTA,W ;Get inverted state of the GPIO pin
+ andwf gpio_mask,W ;Mask off the other GPIO pin
+ andlw b'11000000' ;Mask off the remaining bits
+ xorwf PORTA,F ;Update the GPIO port
+ return
+gpio_temp btfss Intermission ;Wait for the gap between messages
+ return
+ tstf bitcount ;Check that no line events have happened
+ pcall Temperature ;Do one temperature measurement step
+ iorlw 0 ;Check if measurement is complete
+ skpnz
+ bcf Intermission ;No more steps to be done
+ return
+gpio_home comf PORTA,W ;Get the inverted state of PORT A
+ goto gpio_security
+gpio_away movfw PORTA ;Get the normal state of PORT A
+gpio_security andwf gpio_mask,W ;Apply the GPIO port mask
+ andlw b'11000000' ;Clear the irrelevant bits
+ skpz
+ goto gpio_armed ;Security system is armed
+ btfss gpioaway ;Did the port change since last check?
+ return ;Nothing to do
+ bcf gpioaway ;Security system is disarmed
+ clrf float1 ;Cancel the remote setpoint
+ clrf float2
+ goto gpio_setpoint ;W is 0
+gpio_armed btfsc gpioaway ;Did the port change since last check?
+ return ;Nothing to do
+ bsf gpioaway ;Security system is armed
+ movlw AwaySetpoint1 ;EEPROM address of the away setpoint
+ lcall ReadEpromData ;Get the setpoint from EEPROM
+ movwf float1 ;Configure the remote setpoint
+ movlw AwaySetpoint2 ;EEPROM address of the away setpoint
+ call ReadEpromData ;Get the setpoint from EEPROM
+ movwf float2 ;Configure the remote setpoint
+ movlw b'01' ;Temperature override mode: Continue
+gpio_setpoint pcall SetSetPoint ;Set setpoint
+ return
+
+;########################################################################
+
+;Initialize EEPROM data memory
+
+ org 0x2100
+SavedSettings de 6 | 1 << 5 | 1 << 6 ;Set IgnoreErr1 and OverrideHigh
+FunctionGPIO de GPIO_NONE | GPIO_NONE << 4
+AwaySetpoint1 de 16
+AwaySetpoint2 de 0
+PrintSettingL de "L="
+FunctionLED1 de 'M'
+FunctionLED2 de 'X'
+FunctionLED3 de 'O'
+FunctionLED4 de 'F'
+FunctionLED5 de 'P'
+FunctionLED6 de 'C'
+NullString de 0
+TStatModel de 0
+
+PrintSettingA de "A="
+GreetingStr de "OpenTherm Gateway ", version
+#ifdef __DEBUG
+ de phase, patch, ".", build
+#else
+ #ifdef patch
+ de phase, patch
+ #ifdef bugfix
+ de '.', bugfix
+ #endif
+ #endif
+#endif
+ de 0
+TimeoutStr de "WDT reset!", 0
+TDisconnect de "Thermostat disconnected", 0
+TConnect de "Thermostat connected", 0
+CommandNG de "NG", 0
+SyntaxError de "SE", 0
+NoSpaceLeft de "NS", 0
+BadValue de "BV", 0
+NotFound de "NF", 0
+OutOfRange de "OR", 0
+OverrunError de "OE", 0
+MediumPowerStr de "Medium", 0
+HighPowerStr de "High", 0
+NormalPowerStr de "Low"
+PowerStr de " power", 0
+ErrorStr de "Error ", 0
+PrintSettingB de "B="
+TimeStamp de tstamp, 0
+PrintSettingC de "C="
+SpeedString de mhzstr, " MHz", 0
+ResetReasonStr de "ECSLPB"
+
+ org 0x21D0
+UnknownFlags de 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+ org 0x21E0
+AlternativeCmd de 116,117,118,119,120,121,122,123
+ de 0,0,0,0,0,0,0,0
+ de 0,0,0,0,0,0,0,0
+ de 0,0,0,0,0,0,0,0
+ end
diff --git a/boiler-monster/original-pic-4.2.5/license.txt b/boiler-monster/original-pic-4.2.5/license.txt
new file mode 100644
index 0000000..6d576d3
--- /dev/null
+++ b/boiler-monster/original-pic-4.2.5/license.txt
@@ -0,0 +1,58 @@
+Copyright (c) 2009, Schelte Bron
+
+PREAMBLE
+
+This license establishes the terms under which a given software Package may
+be copied, modified, distributed, and/or redistributed. The intent is that
+the Copyright Holder maintains control over the development and distribution
+of the Package, while allowing the users of the Package to use the Package
+in a variety of ways.
+
+You are always permitted to make arrangements wholly outside of this license
+directly with the Copyright Holder of a given Package. If the terms of this
+license do not permit the full use that you propose to make of the Package,
+you should contact the Copyright Holder and seek a different licensing
+arrangement.
+
+DEFINITIONS
+
+"Copyright Holder" means the individual(s) or organization(s) named in the
+copyright notice for the entire Package.
+
+"You" and "your" means any person who would like to copy, distribute, or
+modify the Package.
+
+"Package" means the collection of files distributed by the Copyright Holder,
+and derivatives of that collection and/or of those files. A given Package
+may consist of either the Standard Version, or a Modified Version.
+
+"Distribute" means providing a copy of the Package or making it accessible
+to anyone else, or in the case of a company or organization, to others
+outside of your company or organization.
+
+"Standard Version" refers to the Package if it has not been modified, or has
+been modified only in ways explicitly requested by the Copyright Holder.
+
+"Modified Version" means the Package, if it has been changed, and such
+changes were not explicitly requested by the Copyright Holder.
+
+TERMS
+
+1. You may use this Package for non-commercial purposes without charge.
+
+2. You may make verbatim copies of this Package for personal use, provided
+that you duplicate all of the original copyright notices and associated
+disclaimers. You may not distribute copies of this Package, or copies of
+packages derived from this Package, to others without specific prior written
+permission from the Copyright Holder (although you are encouraged to direct
+them to sources from which they may obtain it for themselves).
+
+3. You may apply bug fixes, portability fixes, and other modifications. A
+Package modified in such a way shall still be covered by the terms of this
+license.
+
+4. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL
+THE AUTHORS BE HELD LIABLE FOR ANY DAMAGES ARISING FROM THE USE OF THIS
+SOFTWARE.
diff --git a/boiler-monster/original-pic-4.2.5/ot-gateway.brd b/boiler-monster/original-pic-4.2.5/ot-gateway.brd
new file mode 100644
index 0000000..301dede
--- /dev/null
+++ b/boiler-monster/original-pic-4.2.5/ot-gateway.brd
Binary files differ
diff --git a/boiler-monster/original-pic-4.2.5/ot-gateway.sch b/boiler-monster/original-pic-4.2.5/ot-gateway.sch
new file mode 100644
index 0000000..580fd38
--- /dev/null
+++ b/boiler-monster/original-pic-4.2.5/ot-gateway.sch
Binary files differ
diff --git a/boiler-monster/original-pic-4.2.5/readme.txt b/boiler-monster/original-pic-4.2.5/readme.txt
new file mode 100644
index 0000000..fd5d117
--- /dev/null
+++ b/boiler-monster/original-pic-4.2.5/readme.txt
@@ -0,0 +1,43 @@
+Requirements
+============
+
+The provided files are intended to be used together with the MPLAB tools that
+can be downloaded for free form Microchip (www.microchip.com). The firmware
+has been developed on MPLAB v8.53.
+
+
+Compiling
+=========
+
+The build.cmd script can be used to build the Opentherm Gateway firmware. If
+you installed the MPLAB tools in a non-standard location, you will have to
+modify the path on the second line of the script.
+
+
+MPLAB Project
+=============
+
+If you want to create a MPLAB IDE project for the Opentherm Gateway firmare,
+you can follow these steps:
+- Go to Project -> Project Wizard.
+- Click Next
+- Select the "PIC16F88" device and click Next
+- Select the "Microchip MPASM Toolsuite" and click Next
+- Browse to the otgw-4.2.5 directory and enter gateway.mcp as the filename.
+- Click Save, then click Next
+- Add the files "gateway.asm", "selfprog.asm", "ds1820.asm" and "16f88.lkr"
+ to the project and click Next
+- Click Finish
+- Select Project -> Build Configuration -> Release
+
+The "build.asm" file is supposed to be auto-generated at the start of every
+build. The included "build.vbs" script can take care of that. To automatically
+invoke the script at the start of each build, do the following:
+- Go to Project -> Build options -> Project
+- Select the "Custom Build" tab
+- Enable the "Pre-Build Step" option
+- Specify the command line: WScript "path\to\project\dir\build.vbs"
+- Optionally add a project name as an argument on the command line
+- Click OK
+
+Now press F10 to build the project.
diff --git a/pic/selfprog.asm b/boiler-monster/original-pic-4.2.5/selfprog.asm
index 9e18769..9e18769 100644
--- a/pic/selfprog.asm
+++ b/boiler-monster/original-pic-4.2.5/selfprog.asm
diff --git a/pic/.gitignore b/boiler-monster/pic/.gitignore
index f8ab154..f8ab154 100644
--- a/pic/.gitignore
+++ b/boiler-monster/pic/.gitignore
diff --git a/boiler-monster/pic/16f88.lkr b/boiler-monster/pic/16f88.lkr
new file mode 100644
index 0000000..b2b3e95
--- /dev/null
+++ b/boiler-monster/pic/16f88.lkr
@@ -0,0 +1,76 @@
+// File: 16F88_g.lkr
+// Generic linker script for the PIC16F88 processor
+
+#IFDEF _DEBUG
+
+LIBPATH .
+
+CODEPAGE NAME=page0 START=0x0 END=0x7FF
+CODEPAGE NAME=page1 START=0x800 END=0xEFF
+CODEPAGE NAME=debug START=0xF00 END=0xFFF PROTECTED
+CODEPAGE NAME=.idlocs START=0x2000 END=0x2003 PROTECTED
+CODEPAGE NAME=.device_id START=0x2006 END=0x2006 PROTECTED
+CODEPAGE NAME=.config START=0x2007 END=0x2009 PROTECTED
+CODEPAGE NAME=eedata START=0x2100 END=0x21FF PROTECTED
+
+DATABANK NAME=sfr0 START=0x0 END=0x1F PROTECTED
+DATABANK NAME=sfr1 START=0x80 END=0x9F PROTECTED
+DATABANK NAME=sfr2 START=0x100 END=0x10F PROTECTED
+DATABANK NAME=sfr3 START=0x180 END=0x18F PROTECTED
+
+SHAREBANK NAME=dbgnobnk START=0x70 END=0x70 PROTECTED
+SHAREBANK NAME=dbgnobnk START=0xF0 END=0xF0 PROTECTED
+SHAREBANK NAME=dbgnobnk START=0x170 END=0x170 PROTECTED
+SHAREBANK NAME=dbgnobnk START=0x1F0 END=0x1F0 PROTECTED
+
+SHAREBANK NAME=sfrnobnk START=0x71 END=0x7F
+SHAREBANK NAME=sfrnobnk START=0xF1 END=0xFF PROTECTED
+SHAREBANK NAME=sfrnobnk START=0x171 END=0x17F PROTECTED
+SHAREBANK NAME=sfrnobnk START=0x1F1 END=0x1FF PROTECTED
+
+DATABANK NAME=gpr0 START=0x20 END=0x6F
+DATABANK NAME=gpr1 START=0xA0 END=0xEF
+DATABANK NAME=gpr2 START=0x110 END=0x16F
+DATABANK NAME=gpr3 START=0x190 END=0x1E4
+DATABANK NAME=dbg3 START=0x1E5 END=0x1EF PROTECTED
+
+SECTION NAME=PROG0 ROM=page0 // ROM code space
+SECTION NAME=PROG1 ROM=page1 // ROM code space
+SECTION NAME=DEBUG ROM=debug // ICD debug executive
+SECTION NAME=IDLOCS ROM=.idlocs // ID locations
+SECTION NAME=DEVICEID ROM=.device_id // Device ID
+SECTION NAME=DEEPROM ROM=eedata // Data EEPROM
+
+#ELSE
+
+LIBPATH .
+
+CODEPAGE NAME=page0 START=0x0 END=0x7FF
+CODEPAGE NAME=page1 START=0x800 END=0xFFF
+CODEPAGE NAME=.idlocs START=0x2000 END=0x2003 PROTECTED
+CODEPAGE NAME=.device_id START=0x2006 END=0x2006 PROTECTED
+CODEPAGE NAME=.config START=0x2007 END=0x2009 PROTECTED
+CODEPAGE NAME=eedata START=0x2100 END=0x21FF PROTECTED
+
+DATABANK NAME=sfr0 START=0x0 END=0x1F PROTECTED
+DATABANK NAME=sfr1 START=0x80 END=0x9F PROTECTED
+DATABANK NAME=sfr2 START=0x100 END=0x10F PROTECTED
+DATABANK NAME=sfr3 START=0x180 END=0x18F PROTECTED
+
+SHAREBANK NAME=sfrnobnk START=0x70 END=0x7F
+SHAREBANK NAME=sfrnobnk START=0xF0 END=0xFF PROTECTED
+SHAREBANK NAME=sfrnobnk START=0x170 END=0x17F PROTECTED
+SHAREBANK NAME=sfrnobnk START=0x1F0 END=0x1FF PROTECTED
+
+DATABANK NAME=gpr0 START=0x20 END=0x6F
+DATABANK NAME=gpr1 START=0xA0 END=0xEF
+DATABANK NAME=gpr2 START=0x110 END=0x16F
+DATABANK NAME=gpr3 START=0x190 END=0x1EF
+
+SECTION NAME=PROG0 ROM=page0 // ROM code space
+SECTION NAME=PROG1 ROM=page1 // ROM code space
+SECTION NAME=IDLOCS ROM=.idlocs // ID locations
+SECTION NAME=DEVICEID ROM=.device_id // Device ID
+SECTION NAME=DEEPROM ROM=eedata // Data EEPROM
+
+#FI
diff --git a/pic/Makefile b/boiler-monster/pic/Makefile
index 31ab11c..31ab11c 100644
--- a/pic/Makefile
+++ b/boiler-monster/pic/Makefile
diff --git a/boiler-monster/pic/build.asm b/boiler-monster/pic/build.asm
new file mode 100644
index 0000000..67d0747
--- /dev/null
+++ b/boiler-monster/pic/build.asm
@@ -0,0 +1,2 @@
+#define build "1"
+#define tstamp "17:59 20-10-2015"
diff --git a/pic/docs/otgw-big-sch.png b/boiler-monster/pic/docs/otgw-big-sch.png
index 114e65b..114e65b 100644
--- a/pic/docs/otgw-big-sch.png
+++ b/boiler-monster/pic/docs/otgw-big-sch.png
Binary files differ
diff --git a/pic/docs/pinout.txt b/boiler-monster/pic/docs/pinout.txt
index a3db674..a3db674 100644
--- a/pic/docs/pinout.txt
+++ b/boiler-monster/pic/docs/pinout.txt
diff --git a/pic/gateway.asm b/boiler-monster/pic/gateway.asm
index b1c6324..b1c6324 100644
--- a/pic/gateway.asm
+++ b/boiler-monster/pic/gateway.asm
diff --git a/boiler-monster/pic/selfprog.asm b/boiler-monster/pic/selfprog.asm
new file mode 100644
index 0000000..9e18769
--- /dev/null
+++ b/boiler-monster/pic/selfprog.asm
@@ -0,0 +1,391 @@
+ title "Self Programming"
+ list b=8, r=dec
+
+;########################################################################
+; Self programming, based on Microchip AN851
+;########################################################################
+; This code has been optimized for size so it would fit in 0x100 code words.
+; To achieve this, some special tricks had to be used. As a result the code
+; may be a little hard to read
+;
+; The functionality differs from that described in AN851 in the following
+; respects:
+; 1. No auto baud detection. Communication must take place at 9600 baud.
+; 2. Dropped the first <STX> that was used for auto baud detection.
+; 3. Dropped the third address byte as it would always be 0.
+; 4. The code does not check the last byte of EEPROM data memory to enter boot
+; mode. Instead it transmits a <ETX> character and waits up to 1 second for
+; a <STX> character.
+; 5. The code is designed to be placed in the highest part of memory. That way
+; it doesn't interfere with the interrupt code address. It does rely on the
+; main program to call the self-program code at an appropriate time.
+; 6. Due to the location of the boot loader code, it cannot be protected using
+; the code protection bits in the configuration word. This means that the
+; boot loader code can also be updated, if necessary.
+; 7. The Erase Flash command has been implemented for the PIC16F device.
+; 8. The device reset command is 0x08, not any command with a 0 length.
+; 9. The version command can be called with a data length of 1-3 words. It will
+; report the following information:
+; 1. Version
+; 2. Boot loader start address
+; 3. Boot loader end address
+; The software used to reflash the device can use the last two pieces of
+; information to determine which part of memory should not be touched.
+
+#include "p16f88.inc"
+
+ errorlevel -302, -306
+
+#define MAJOR_VERSION 1
+#define MINOR_VERSION 1
+
+#define STX 0x0F
+#define ETX 0x04
+#define DLE 0x05
+
+CHKSUM equ 0x70
+COUNTER equ 0x71
+RXDATA equ 0x72
+TXDATA equ 0x73
+TEMP equ 0x74
+
+DATA_BUFF equ 0x10 ;Start of receive buffer
+COMMAND equ 0x10 ;Data mapped in receive buffer
+DATA_COUNT equ 0x11
+ADDRESS_L equ 0x12
+ADDRESS_H equ 0x13
+PACKET_DATA equ 0x14
+
+#define SELFRESET PORTB,1
+#define RX PORTB,2
+#define TX PORTB,5
+
+#ifdef SELFPROGNEW
+ #define SELFPROG SelfProgNew
+ #define STARTADDRESS 0x0700
+#else
+ #define SELFPROG SelfProg
+ #define STARTADDRESS 0x0f00
+#endif
+
+ global SELFPROG
+SELFPROG code STARTADDRESS
+ ;Do not go into selfprog mode after a watchdog timeout reset
+SELFPROG btfss STATUS,NOT_TO
+ return ;Let the main code handle a timeout
+
+ bcf INTCON,GIE ;Ignore interrupts
+
+ banksel OSCCON
+ movlw b'01100000' ;Internal oscillator set to 4MHz
+ movwf OSCCON ;Configure the oscillator
+
+ ;Set the LEDS as outputs
+#ifndef LVP
+ movlw b'00100111'
+#else
+ movlw b'00101111'
+#endif
+ movwf TRISB
+
+ ;Configure the comparators and voltage reference
+ ;Allow communication between thermostat and boiler to continue
+ ;while reprogramming the device
+ movlw b'11100111' ;Pins 3 and 4 are digital ouputs
+ movwf TRISA
+ movlw b'00000111' ;A0 through A2 are used for analog I/O
+ movwf ANSEL
+ movlw b'11100110' ;Voltage reference at 1.25V
+ movwf CVRCON
+ movlw b'00000110' ;Two common reference comps with output
+ movwf CMCON
+
+ ;Configure Timer 0 & Watchdog Timer
+ ;Bit 7 RBPU = 1 - Port B pull up disabled
+ ;Bit 6 INTEDG = 1 - Interrupt on rising edge
+ ;Bit 5 T0CS = 0 - Internal clock
+ ;Bit 4 T0SE = 1 - High to low transition
+ ;Bit 3 PSA = 0 - Prescaler assigned to Timer 0
+ ;Bit 2-0 PS = 7 - 1:256 Prescaler
+ movlw b'11010111'
+ movwf OPTION_REG
+
+ movlw 25 ;9600 baud
+ movwf SPBRG
+ movlw b'00100110'
+ movwf TXSTA ;High speed, Asynchronous, Enabled
+ bcf STATUS,RP0
+ bsf RCSTA,SPEN ;Enable serial port
+ bsf RCSTA,CREN ;Enable receive
+
+ ;Clear the LEDS
+ movlw b'11111111'
+ movwf PORTB
+
+ clrf TMR0
+ movlw 1
+ call Pause
+;Notify the external programming software (Pause returns <ETX>)
+ call WrRS232
+;Programmer should react within 1 second
+ movlw 16
+ call Pause
+ btfss PIR1,RCIF
+;No data received, resume the normal program
+ return
+;Check that the received character is <STX>
+ call RdRS232
+ skpz
+;Other serial data received, resume normal program
+ return
+
+ bsf STATUS,IRP
+ReSync movlw DATA_BUFF
+ movwf FSR
+ clrf CHKSUM
+GetNextDat call RdRS232 ;Get the data
+ skpnz
+ goto ReSync ;Found unprotected STX
+ xorlw STX ^ ETX ;Check for ETX
+ skpnz
+ goto CheckSum ;Yes, examine checksum
+ xorlw ETX ^ DLE ;Check for DLE
+ skpnz
+ call RdRS232 ;Yes, get the next byte
+ movfw RXDATA
+ movwf INDF ;Store the data
+ addwf CHKSUM,F ;Get sum
+ incf FSR,F
+ goto GetNextDat
+
+CheckSum tstf CHKSUM
+ skpz
+ goto StartOfLine
+ bsf STATUS,RP1
+ movfw ADDRESS_L
+ movwf EEADR
+ movfw ADDRESS_H
+ movwf EEADRH
+ movlw PACKET_DATA
+ movwf FSR
+ movfw DATA_COUNT
+ movwf COUNTER
+CheckCommand
+#ifdef SELFPROGNEW
+ movlw high ($ + 0x800)
+#else
+ movlw high $
+#endif
+ movwf PCLATH
+ movfw COMMAND
+ sublw 8
+ sublw 8
+ skpc
+ goto StartOfLine
+ addwf PCL,F
+ goto ReadVersion ;0 Read Version Information
+ goto ReadProgMem ;1 Read Program Memory
+ goto WriteProgMem ;2 Write Program Memory
+ goto EraseProgMem ;3 Erase Program Memory
+ goto ReadEE ;4 Read EEDATA Memory
+ goto WriteEE ;5 Write EEDATA Memory
+ goto StartOfLine ;6 Read Config Memory
+ goto StartOfLine ;7 Write Config Memory
+ goto VReset ;8 Reset
+
+VersionData data (MAJOR_VERSION << 8) | MINOR_VERSION
+#ifdef SELFPROGNEW
+ data SELFPROG + 0x800, SelfProgEnd + 0x800
+#else
+ data SELFPROG, SelfProgEnd
+#endif
+
+ReadVersion movlw low VersionData
+ movwf EEADR
+#ifdef SELFPROGNEW
+ movlw high (VersionData + 0x800)
+#else
+ movlw high VersionData
+#endif
+ movwf EEADRH
+ movlw DATA_BUFF + 2
+ movwf FSR
+ReadProgMem bsf STATUS,RP0
+ bsf EECON1,EEPGD
+ bsf EECON1,RD
+ nop
+ nop
+ bcf STATUS,RP0
+ movfw EEDATA
+ movwf INDF
+ incf FSR,F
+ movfw EEDATH
+ movwf INDF
+ incf FSR,F
+ incf EEADR,F
+ skpnz
+ incf EEADRH,F
+ decfsz COUNTER,F
+ goto ReadProgMem
+WritePacket movlw DATA_BUFF
+ subwf FSR,W
+WritePacketJ1 movwf COUNTER
+ movlw STX
+ call WrRS232
+ clrf CHKSUM
+ movlw DATA_BUFF
+ movwf FSR
+SendNext movfw INDF
+ addwf CHKSUM,F
+ incf FSR,F
+ call WrData
+ decfsz COUNTER,F
+ goto SendNext
+ comf CHKSUM,W
+ addlw 1
+ call WrData ;WrData returns <ETX>
+ call WrRS232
+StartOfLine bcf STATUS,RP1
+ call RdRS232 ;Look for a start of line
+ skpnz
+ goto ReSync
+ goto StartOfLine
+
+WriteProgMem movlw b'10000100'
+ call LoadEECon1
+ movlw b'11111100'
+ andwf EEADR,F
+ movlw 4
+ movwf TEMP
+Lp1 movfw INDF
+ movwf EEDATA
+ incf FSR,F
+ movfw INDF
+ movwf EEDATH
+ incf FSR,F
+ call StartWrite
+ decfsz TEMP,F
+ goto Lp1
+ decfsz COUNTER,F
+ goto WriteProgMem
+ goto WritePacketJ1
+
+EraseProgMem movlw b'10010100'
+ call LoadEECon1
+ movlw 0x1F
+ iorwf EEADR,F
+ call StartWrite
+ decfsz COUNTER,F
+ goto EraseProgMem
+ goto WritePacketJ1
+
+ReadEE bsf STATUS,RP0
+ clrf EECON1
+ bsf EECON1,RD
+ bcf STATUS,RP0
+ movfw EEDATA
+ movwf INDF
+ incf FSR,F
+ incf EEADR,F
+ decfsz COUNTER,F
+ goto ReadEE
+ goto WritePacket
+
+WriteEE movlw b'00000100'
+ call LoadEECon1
+ movfw INDF
+ movwf EEDATA
+ incf FSR,F
+ call StartWrite
+ decfsz COUNTER,F
+ goto WriteEE
+ goto WritePacketJ1
+
+WrData movwf TXDATA
+ xorlw STX
+ skpnz
+ goto WrDLE
+ xorlw STX ^ ETX
+ skpnz
+ goto WrDLE
+ xorlw ETX ^ DLE
+ skpz
+ goto WrNext
+WrDLE movlw DLE
+ call WrRS232
+WrNext movfw TXDATA
+WrRS232 clrwdt
+ bcf STATUS,RP1
+WaitTxEmpty btfss PIR1,TXIF
+ goto WaitTxEmpty
+ movwf TXREG
+ retlw ETX
+
+RdRS232 btfsc RCSTA,OERR
+ goto VReset
+RdLp1 clrwdt
+ btfss PIR1,RCIF
+ goto RdLp1
+ movfw RCREG
+ movwf RXDATA
+ xorlw STX
+ return
+
+LoadEECon1 bsf STATUS,RP0
+ movwf EECON1
+ bcf STATUS,RP0
+ return
+
+StartWrite clrwdt
+ bsf STATUS,RP0
+ movlw 0x55
+ movwf EECON2
+ movlw 0xAA
+ movwf EECON2
+ bsf EECON1,WR
+WaitEEWrite btfsc EECON1,WR ;Skipped when writing program memory
+ goto WaitEEWrite ;Skipped when writing program memory
+ bcf STATUS,RP0
+ incf EEADR,F
+ skpnz
+ incf EEADRH,F
+ retlw 1 ;Return length of acknowledgement
+
+Pause clrwdt
+ btfsc PIR1,RCIF
+ return
+ btfss INTCON,TMR0IF
+ goto Pause
+ bcf INTCON,TMR0IF
+ addlw -1
+ skpz
+ goto Pause
+ retlw ETX
+
+;Reset the device via an external connection from an I/O pin to the reset pin.
+VReset bcf STATUS,RP1
+ bcf SELFRESET ;Prepare the output latch
+ bsf STATUS,RP0
+ bcf SELFRESET ;Switch the pin to output
+
+;If resetting via the output pin doesn't work, restore all used registers to
+;their power-on defaults and jump to address 0.
+;Do not simply return because the code that called this routine may have been
+;wiped and reprogrammed to something completely different.
+ movlw b'11111111'
+ movwf TRISA
+ movwf TRISB
+ movwf OPTION_REG
+ movwf ANSEL
+ movlw b'00000111'
+ movwf CMCON
+ clrf TXSTA
+ clrf SPBRG
+ clrf STATUS
+ clrf RCSTA
+ clrf INTCON
+ clrf PCLATH
+ clrwdt
+SelfProgEnd goto 0
+
+ end
diff --git a/stm32/.gitignore b/boiler-monster/stm32/.gitignore
index 08e4672..64532f4 100644
--- a/stm32/.gitignore
+++ b/boiler-monster/stm32/.gitignore
@@ -1,4 +1,3 @@
-.gitmodules
*.o
*.d
*.map
diff --git a/stm32/Makefile b/boiler-monster/stm32/Makefile
index e1cd497..e1cd497 100644
--- a/stm32/Makefile
+++ b/boiler-monster/stm32/Makefile
diff --git a/stm32/Makefile.include b/boiler-monster/stm32/Makefile.include
index 4f5cbd9..4f5cbd9 100644
--- a/stm32/Makefile.include
+++ b/boiler-monster/stm32/Makefile.include
diff --git a/stm32/Makefile.rules b/boiler-monster/stm32/Makefile.rules
index a723e6f..a723e6f 100644
--- a/stm32/Makefile.rules
+++ b/boiler-monster/stm32/Makefile.rules
diff --git a/stm32/app/1wire.c b/boiler-monster/stm32/app/1wire.c
index c374c5c..c374c5c 100644
--- a/stm32/app/1wire.c
+++ b/boiler-monster/stm32/app/1wire.c
diff --git a/stm32/app/1wire.h b/boiler-monster/stm32/app/1wire.h
index 4a31418..4a31418 100644
--- a/stm32/app/1wire.h
+++ b/boiler-monster/stm32/app/1wire.h
diff --git a/stm32/app/Makefile b/boiler-monster/stm32/app/Makefile
index d4a4f5b..23b4a74 100644
--- a/stm32/app/Makefile
+++ b/boiler-monster/stm32/app/Makefile
@@ -25,26 +25,7 @@ HSRCS=pins.h project.h ring.h
EXTRACLEANS=*.orig scmversion commit.h
-
-
-
-TARGET=10.32.96.18
-#TARGET2=10.32.96.107
-
-#soup: ${PROG}.hex
-# rsync -vaP ${PROG}.hex ${TARGET2}:/tmp/boiler.hex
-# RV=$$(arm-none-eabi-objdump --disassemble=reset_handler ${PROG}.elf | awk '/^0/ { print "0x"$$1 }') && \
-# ssh ${TARGET2} "$(OOCD) -f $(OOCD_INTERFACE) \
-# -f $(OOCD_BOARD) \
-# -c init -c \"reset init\" \
-# -c \"flash write_image erase /tmp/boiler.hex\" \
-# -c \"reset halt\" \
-# -c \"resume $${RV}\" \
-# -c shutdown" $(NULL) && \
-# echo reset_handler is at $${RV}
-#
-
-
+TARGET=boiler-monster
V=1
default: ${PROG}.fw.bin
diff --git a/stm32/app/adc.c b/boiler-monster/stm32/app/adc.c
index ee6a4f3..2cfc4e3 100644
--- a/stm32/app/adc.c
+++ b/boiler-monster/stm32/app/adc.c
@@ -63,6 +63,39 @@ int adc_calibrate (void)
+int adc_convert_start (unsigned channel)
+{
+ uint8_t ch = channel;
+
+ adc_set_regular_sequence (ADC1, 1, &ch);
+
+ /* Start conversion on regular channels. */
+ ADC_CR2 (ADC1) |= ADC_CR2_SWSTART;
+
+#if 0
+ if (adc_wait (&ADC_CR2 (ADC1), ADC_CR2_SWSTART, 0, 2))
+ return -1;
+#endif
+
+ return 0;
+}
+
+
+
+int adc_convert_done(void)
+{
+
+ if (ADC_SR(ADC1) & ADC_SR_EOC)
+ return 1;
+
+ return 0;
+}
+
+unsigned adc_convert_get(void)
+{
+ return adc_read_regular (ADC1);
+}
+
unsigned adc_convert (unsigned channel)
{
diff --git a/stm32/app/boiler.ld b/boiler-monster/stm32/app/boiler.ld
index e15beca..e15beca 100644
--- a/stm32/app/boiler.ld
+++ b/boiler-monster/stm32/app/boiler.ld
diff --git a/stm32/app/cmd.c b/boiler-monster/stm32/app/cmd.c
index 7e1975a..7e1975a 100644
--- a/stm32/app/cmd.c
+++ b/boiler-monster/stm32/app/cmd.c
diff --git a/stm32/app/commit.c b/boiler-monster/stm32/app/commit.c
index 207448f..207448f 100644
--- a/stm32/app/commit.c
+++ b/boiler-monster/stm32/app/commit.c
diff --git a/stm32/app/ds1820.c b/boiler-monster/stm32/app/ds1820.c
index 200f4b7..200f4b7 100644
--- a/stm32/app/ds1820.c
+++ b/boiler-monster/stm32/app/ds1820.c
diff --git a/stm32/app/gdb.script b/boiler-monster/stm32/app/gdb.script
index 7cf9d09..7cf9d09 100644
--- a/stm32/app/gdb.script
+++ b/boiler-monster/stm32/app/gdb.script
diff --git a/stm32/app/led.c b/boiler-monster/stm32/app/led.c
index bdfd084..bdfd084 100644
--- a/stm32/app/led.c
+++ b/boiler-monster/stm32/app/led.c
diff --git a/stm32/app/main.c b/boiler-monster/stm32/app/main.c
index 70c22b2..5ceb4af 100644
--- a/stm32/app/main.c
+++ b/boiler-monster/stm32/app/main.c
@@ -126,7 +126,6 @@ main (void)
cmd_usart_dispatch();
temp_dispatch();
- pressure_dispatch();
}
diff --git a/stm32/app/ot.c b/boiler-monster/stm32/app/ot.c
index ba2b7d9..2a8ad36 100644
--- a/stm32/app/ot.c
+++ b/boiler-monster/stm32/app/ot.c
@@ -358,9 +358,8 @@ static void ot_force_status (void)
static void ot_30s_ticker (void)
{
uint8_t data[2];
- uint8_t msg[4];
- printf ("Q ot ticker - push control flags and log data");
+ printf ("Q ot ticker - push control flags\r\n");
if (ot_override_ch) {
data[0] = ot_override_ch;
@@ -375,25 +374,31 @@ static void ot_30s_ticker (void)
ot_request_ovr (OT_WRITE_DATA, OT_IDX_DHW_SETPOINT, data);
}
+}
+
+static void ot_2s_ticker (void)
+{
+ uint8_t msg[4];
if (!ot_fake_read_ack (OT_IDX_CH_WATER_PRESSURE, msg))
- ot_debug ("B", msg, " (fake reply)");
+ ot_debug ("B", msg, " (fake request and reply)");
if (!ot_fake_read_ack (OT_IDX_RETURN_WATER_TEMP, msg))
- ot_debug ("B", msg, " (fake reply)");
+ ot_debug ("B", msg, " (fake request and reply)");
if (!ot_fake_read_ack (OT_IDX_SUPPLY_INLET_TEMP, msg))
- ot_debug ("B", msg, " (fake reply)");
-
+ ot_debug ("B", msg, " (fake request and reply)");
}
+
static void ot_4hz_ticker (void)
{
- static unsigned i;
- i++;
+ static unsigned thirty, two;
+ thirty++;
+ two++;
ot_boiler_worker();
@@ -406,9 +411,15 @@ static void ot_4hz_ticker (void)
}
- if (i >= 120) {
+ if (two >= 8) {
+ ot_2s_ticker();
+ two = 0;
+ }
+
+
+ if (thirty >= 120) {
ot_30s_ticker();
- i = 0;
+ thirty = 0;
}
diff --git a/stm32/app/ot_phy_rx.c b/boiler-monster/stm32/app/ot_phy_rx.c
index 9348d24..9348d24 100644
--- a/stm32/app/ot_phy_rx.c
+++ b/boiler-monster/stm32/app/ot_phy_rx.c
diff --git a/stm32/app/ot_phy_tx.c b/boiler-monster/stm32/app/ot_phy_tx.c
index 36fbcc5..36fbcc5 100644
--- a/stm32/app/ot_phy_tx.c
+++ b/boiler-monster/stm32/app/ot_phy_tx.c
diff --git a/stm32/app/pic.c b/boiler-monster/stm32/app/pic.c
index b478766..b478766 100644
--- a/stm32/app/pic.c
+++ b/boiler-monster/stm32/app/pic.c
diff --git a/stm32/app/pins.h b/boiler-monster/stm32/app/pins.h
index 9ea01cf..9ea01cf 100644
--- a/stm32/app/pins.h
+++ b/boiler-monster/stm32/app/pins.h
diff --git a/boiler-monster/stm32/app/pressure.c b/boiler-monster/stm32/app/pressure.c
new file mode 100644
index 0000000..f719e4d
--- /dev/null
+++ b/boiler-monster/stm32/app/pressure.c
@@ -0,0 +1,164 @@
+#include "project.h"
+
+#define PRESSURE GPIO0
+#define PRESSURE_PORT GPIOA
+#define PRESSURE_CHANNEL ADC_CHANNEL0
+#define VREF_CHANNEL ADC_CHANNEL17
+
+#define N_SAMPLES 1000
+
+#define T do { printf("%s:%d\r\n",__FILE__,__LINE__); } while (0)
+
+static int pressure;
+
+struct avg {
+ int acc;
+ unsigned n;
+};
+
+static void avg_reset(struct avg *a)
+{
+a->acc=0;
+a->n=0;
+return;
+}
+static void avg_add(struct avg *a, int v)
+{
+a->acc+=v;
+a->n++;
+}
+inline static unsigned avg_n(struct avg *a)
+{
+return a->n;
+}
+
+static int avg_get(struct avg *a)
+{
+if (!a->n) return 0;
+return (a->acc) / (a->n);
+}
+
+
+uint16_t pressure_ch (void)
+{
+ return pressure;
+}
+
+
+static void pressure_calculate (int v,int r)
+{
+ /* r is 1.25 volts, transducer is 0.5V -> 0 psi 4.5V -> 100psi */
+ /* 100psi is 6.8947573 bar, and we want 256ths of bar */
+
+
+ if (!r) {
+ pressure = 0;
+ // return;
+ } else {
+ pressure = ((v * 552) / r) - 221;
+
+ if (pressure < 0) pressure = 0;
+ }
+
+ printf ("QP: %d %d %d\r\n", v, r, (pressure * 100) / 256);
+}
+
+
+
+void pressure_tick(void)
+{
+static unsigned state;
+static unsigned backoff=MS_TO_TICKS(1000);
+static unsigned timeout;
+
+static struct avg vref_avg;
+static struct avg pressure_avg;
+
+
+timeout++;
+
+if (backoff) {
+ backoff--;
+ return;
+}
+
+
+switch (state) {
+case 0:
+ adc_off (ADC1);
+ adc_power_on (ADC1);
+ backoff=MS_TO_TICKS(5);
+ state++;
+ break;
+case 1:
+ ADC_CR2 (ADC1) |= ADC_CR2_RSTCAL;
+ backoff=MS_TO_TICKS(2);
+ state++;
+ break;
+case 2:
+ if (ADC_CR2(ADC1) & ADC_CR2_CAL)
+ state=0;
+ ADC_CR2 (ADC1) |= ADC_CR2_CAL;
+ backoff=MS_TO_TICKS(2);
+ state++;
+ break;
+case 3:
+ if (ADC_CR2(ADC1) & ADC_CR2_CAL)
+ state=0;
+ state++;
+
+ avg_reset(&vref_avg);
+ avg_reset(&pressure_avg);
+ break;
+case 4:
+ if (adc_convert_start(VREF_CHANNEL))
+ state=0;
+ else {
+ timeout=0;
+ state++;
+ }
+ break;
+case 5:
+ if (adc_convert_done()) {
+ avg_add(&vref_avg,adc_convert_get());
+ state++;
+ } else if (timeout > MS_TO_TICKS(5))
+ state=0;
+ break;
+case 6:
+ if (adc_convert_start(PRESSURE_CHANNEL))
+ state=0;
+ else {
+ timeout=0;
+ state++;
+ }
+ break;
+case 7:
+ if (adc_convert_done()) {
+ avg_add(&pressure_avg,adc_convert_get());
+
+ if (avg_n(&pressure_avg)<N_SAMPLES)
+ state=4;
+ else
+ state++;
+ } else if (timeout > MS_TO_TICKS(5))
+ state=0;
+ break;
+case 8:
+ pressure_calculate(avg_get(&pressure_avg),avg_get(&vref_avg));
+ state=0;
+ break;
+}
+
+}
+
+
+
+void pressure_init (void)
+{
+ MAP_ANALOG (PRESSURE);
+}
+
+
+
+
diff --git a/stm32/app/project.h b/boiler-monster/stm32/app/project.h
index d1bfa52..d1bfa52 100644
--- a/stm32/app/project.h
+++ b/boiler-monster/stm32/app/project.h
diff --git a/stm32/app/prototypes.h b/boiler-monster/stm32/app/prototypes.h
index 25a1627..5206734 100644
--- a/stm32/app/prototypes.h
+++ b/boiler-monster/stm32/app/prototypes.h
@@ -28,6 +28,7 @@ extern void led_tick(void);
extern void led_init(void);
/* ticker.c */
extern void delay_us(uint32_t d);
+extern int tick_dispatch;
extern void sys_tick_handler(void);
extern void delay_ms(uint32_t d);
extern void ticker_init(void);
@@ -92,12 +93,14 @@ extern int ds1820_read(const Onewire_addr *a, int *temp);
/* pressure.c */
extern uint16_t pressure_ch(void);
extern void pressure_tick(void);
-extern void pressure_dispatch(void);
extern void pressure_init(void);
/* adc.c */
extern void adc_dump(void);
extern volatile unsigned timeout;
extern void adc_tick(void);
extern int adc_calibrate(void);
+extern int adc_convert_start(unsigned channel);
+extern int adc_convert_done(void);
+extern unsigned adc_convert_get(void);
extern unsigned adc_convert(unsigned channel);
extern void adc_init(void);
diff --git a/stm32/app/ring.c b/boiler-monster/stm32/app/ring.c
index 973f345..973f345 100644
--- a/stm32/app/ring.c
+++ b/boiler-monster/stm32/app/ring.c
diff --git a/stm32/app/ring.h b/boiler-monster/stm32/app/ring.h
index a329354..a329354 100644
--- a/stm32/app/ring.h
+++ b/boiler-monster/stm32/app/ring.h
diff --git a/stm32/app/stdio.c b/boiler-monster/stm32/app/stdio.c
index bcdd5c7..bcdd5c7 100644
--- a/stm32/app/stdio.c
+++ b/boiler-monster/stm32/app/stdio.c
diff --git a/stm32/app/temp.c b/boiler-monster/stm32/app/temp.c
index 4548d01..4548d01 100644
--- a/stm32/app/temp.c
+++ b/boiler-monster/stm32/app/temp.c
diff --git a/stm32/app/ticker.c b/boiler-monster/stm32/app/ticker.c
index e47f4eb..9e268ca 100644
--- a/stm32/app/ticker.c
+++ b/boiler-monster/stm32/app/ticker.c
@@ -14,12 +14,14 @@ delay_us (uint32_t d)
__asm__ ("nop");
}
+
void
sys_tick_handler (void)
{
if (delay_hms_count)
delay_hms_count--;
+
led_tick();
ot_phy_tx_tick();
ot_tick();
diff --git a/stm32/app/usart.c b/boiler-monster/stm32/app/usart.c
index 28676a0..28676a0 100644
--- a/stm32/app/usart.c
+++ b/boiler-monster/stm32/app/usart.c
diff --git a/stm32/app/util.c b/boiler-monster/stm32/app/util.c
index bf65510..bf65510 100644
--- a/stm32/app/util.c
+++ b/boiler-monster/stm32/app/util.c
diff --git a/stm32/docs/pinout.txt b/boiler-monster/stm32/docs/pinout.txt
index 03abe35..03abe35 100644
--- a/stm32/docs/pinout.txt
+++ b/boiler-monster/stm32/docs/pinout.txt
diff --git a/stm32/docs/pm0056.pdf b/boiler-monster/stm32/docs/pm0056.pdf
index 659c9de..659c9de 100644
--- a/stm32/docs/pm0056.pdf
+++ b/boiler-monster/stm32/docs/pm0056.pdf
Binary files differ
diff --git a/stm32/docs/rm0008.pdf b/boiler-monster/stm32/docs/rm0008.pdf
index 8f3d0e1..8f3d0e1 100644
--- a/stm32/docs/rm0008.pdf
+++ b/boiler-monster/stm32/docs/rm0008.pdf
Binary files differ
diff --git a/stm32/docs/stm32f103c8.pdf b/boiler-monster/stm32/docs/stm32f103c8.pdf
index 2723cee..2723cee 100644
--- a/stm32/docs/stm32f103c8.pdf
+++ b/boiler-monster/stm32/docs/stm32f103c8.pdf
Binary files differ
diff --git a/stm32/docs/stm32f103c8t6_pinout_voltage01.png b/boiler-monster/stm32/docs/stm32f103c8t6_pinout_voltage01.png
index 2c22a74..2c22a74 100644
--- a/stm32/docs/stm32f103c8t6_pinout_voltage01.png
+++ b/boiler-monster/stm32/docs/stm32f103c8t6_pinout_voltage01.png
Binary files differ
diff --git a/stm32/libopencm3 b/boiler-monster/stm32/libopencm3
-Subproject 5c73d601763dd140bff020be8d6d06f03c2bea7
+Subproject 5c73d601763dd140bff020be8d6d06f03c2bea7
diff --git a/DOCS/41hpa-warranty-registration.pdf b/boiler/41hpa-warranty-registration.pdf
index f42b06d..f42b06d 100644
--- a/DOCS/41hpa-warranty-registration.pdf
+++ b/boiler/41hpa-warranty-registration.pdf
Binary files differ
diff --git a/boiler/773447801-baxi-platinum-40-user.pdf b/boiler/773447801-baxi-platinum-40-user.pdf
new file mode 100644
index 0000000..77c293a
--- /dev/null
+++ b/boiler/773447801-baxi-platinum-40-user.pdf
Binary files differ
diff --git a/boiler/baxi-platinum-installation-and-service-manual.pdf b/boiler/baxi-platinum-installation-and-service-manual.pdf
new file mode 100644
index 0000000..20daa3c
--- /dev/null
+++ b/boiler/baxi-platinum-installation-and-service-manual.pdf
Binary files differ
diff --git a/munin/.gitignore b/munin/.gitignore
new file mode 100644
index 0000000..751553b
--- /dev/null
+++ b/munin/.gitignore
@@ -0,0 +1 @@
+*.bak
diff --git a/munin/boiler b/munin/boiler
new file mode 100755
index 0000000..199893c
--- /dev/null
+++ b/munin/boiler
@@ -0,0 +1,552 @@
+#!/usr/bin/env perl
+
+use Net::Telnet;
+use Data::Dumper;
+
+#0 Status => 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+#17 Relative modulation level => 0
+#18 CH water pressure => 1.14453125
+#25 Boiler water temperature => 53
+#26 DHW temperature => 49
+#28 Return water temperature => 48.375
+#57 Max CH water setpoint => 80
+#80 Supply inlet temperature => 21.25
+#256 Status => 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+#257 Control setpoint => 7
+#270 Maximum relative modulation level => 100
+#272 Room setpoint => 18
+#280 Room temperature => 26.7265625
+#312 DHW setpoint => 60
+
+my $ot_registers = {
+ 0 => {
+ format => ["flag16"],
+ name => "Status",
+ names => [
+ '', '',
+ '', '',
+ '', '',
+ 'DHW enabled', 'CH enabled',
+ '', 'diagnostic indication',
+ '', '',
+ 'Flame', 'DHW active',
+ 'CH active', 'fault'
+ ],
+ warnings => [
+ undef, undef, undef, undef, undef, undef, undef, undef,
+ undef, undef, undef, undef, undef, undef, undef, '1'
+ ]
+ },
+ 1 => { format => ["f8.8"], name => "Control setpoint", units => 'C' },
+ 2 => { format => [ "flag8", "u8" ], name => "Master configuration" },
+ 3 => { format => [ "flag8", "u8" ], name => "Slave configuration" },
+ 4 => { format => [ "u8", "u8" ], name => "Remote command" },
+ 5 => { format => [ "flag8", "u8" ], name => "Application-specific flags" },
+ 6 => { format => [ "flag8", "flag8" ], name => "Remote parameter flags" },
+ 7 => { format => ["f8.8"], name => "Cooling control signal" },
+ 8 => { format => ["f8.8"], name => "Control setpoint 2" },
+ 9 => { format => ["f8.8"], name => "Remote override room setpoint" },
+ 10 => { format => [ "u8", "nu" ], name => "Number of TSPs" },
+ 11 => { format => [ "u8", "u8" ], name => "TSP setting" },
+ 12 => { format => [ "u8", "nu" ], name => "Size of fault buffer" },
+ 13 => { format => [ "u8", "u8" ], name => "Fault buffer entry" },
+ 14 => {
+ format => ["f8.8"],
+ name => "Maximum relative modulation level",
+ units => '%'
+ },
+ 15 => {
+ format => [ "u8", "u8" ],
+ name => "Boiler capacity and modulation limits"
+ },
+ 16 => { format => ["f8.8"], name => "Room setpoint", units => 'C' },
+ 17 =>
+ { format => ["f8.8"], name => "Relative modulation level", units => '%' },
+ 18 => {
+ format => ["f8.8"],
+ name => "CH water pressure",
+ units => 'bar',
+ warning => '0.9:2.6',
+ critical => '0.8:2.8'
+ },
+ 19 => { format => ["f8.8"], name => "DHW flow rate" },
+ 20 => { format => ["time"], name => "Day of week and time of day" },
+ 21 => { format => ["date"], name => "Date" },
+ 22 => { format => ["u16"], name => "Year" },
+ 23 => { format => ["f8.8"], name => "Room Setpoint CH2" },
+ 24 => { format => ["f8.8"], name => "Room temperature", units => 'C' },
+ 25 =>
+ { format => ["f8.8"], name => "Boiler water temperature", units => 'C' },
+ 26 => { format => ["f8.8"], name => "DHW temperature", units => 'C' },
+ 27 => { format => ["f8.8"], name => "Outside temperature" },
+ 28 =>
+ { format => ["f8.8"], name => "Return water temperature", units => 'C' },
+ 29 => { format => ["f8.8"], name => "Solar storage temperature" },
+ 30 => { format => ["f8.8"], name => "Solar collector temperature" },
+ 31 => { format => ["f8.8"], name => "Flow temperature CH2" },
+ 32 => { format => ["f8.8"], name => "DHW2 temperature" },
+ 33 => { format => ["s16"], name => "Exhaust temperature" },
+ 34 => { format => ["f8.8"], name => "Boiler heat exchanger temperature" },
+ 35 => { format => [ "u8", "u8" ], name => "Boiler fan speed and setpoint" },
+ 48 => { format => [ "s8", "s8" ], name => "DHW setpoint boundaries" },
+ 49 => { format => [ "s8", "s8" ], name => "Max CH setpoint boundaries" },
+ 50 =>
+ { format => [ "s8", "s8" ], name => "OTC heat curve ratio boundaries" },
+ 51 => { format => [ "s8", "s8" ], name => "Remote parameter 4 boundaries" },
+ 52 => { format => [ "s8", "s8" ], name => "Remote parameter 5 boundaries" },
+ 53 => { format => [ "s8", "s8" ], name => "Remote parameter 6 boundaries" },
+ 54 => { format => [ "s8", "s8" ], name => "Remote parameter 7 boundaries" },
+ 55 => { format => [ "s8", "s8" ], name => "Remote parameter 8 boundaries" },
+ 56 => { format => ["f8.8"], name => "DHW setpoint", units => 'C' },
+ 57 => { format => ["f8.8"], name => "Max CH water setpoint", units => 'C' },
+ 58 => { format => ["f8.8"], name => "OTC heat curve ratio" },
+ 59 => { format => ["f8.8"], name => "Remote parameter 4" },
+ 60 => { format => ["f8.8"], name => "Remote parameter 5" },
+ 61 => { format => ["f8.8"], name => "Remote parameter 6" },
+ 62 => { format => ["f8.8"], name => "Remote parameter 7" },
+ 63 => { format => ["f8.8"], name => "Remote parameter 8" },
+ 70 => { format => [ "flag8", "flag8" ], name => "Status V/H" },
+ 71 => { format => [ "nu", "u8" ], name => "Control setpoint V/H" },
+ 72 => { format => [ "flag8", "u8" ], name => "Fault flags/code V/H" },
+ 73 => { format => ["u16"], name => "OEM diagnostic code V/H" },
+ 74 => { format => [ "flag8", "u8" ], name => "Configuration/memberid V/H" },
+ 75 => { format => ["f8.8"], name => "OpenTherm version V/H" },
+ 76 => { format => [ "u8", "u8" ], name => "Product version V/H" },
+ 77 => { format => [ "nu", "u8" ], name => "Relative ventilation" },
+ 78 => { format => [ "u8", "u8" ], name => "Relative humidity exhaust air" },
+ 79 => { format => ["u16"], name => "CO2 level exhaust air" },
+ 80 =>
+ { format => ["f8.8"], name => "Supply inlet temperature", units => 'C' },
+ 81 => { format => ["f8.8"], name => "Supply outlet temperature" },
+ 82 => { format => ["f8.8"], name => "Exhaust inlet temperature" },
+ 83 => { format => ["f8.8"], name => "Exhaust outlet temperature" },
+ 84 => { format => ["u16"], name => "Exhaust fan speed" },
+ 85 => { format => ["u16"], name => "Inlet fan speed" },
+ 86 => {
+ format => [ "flag8", "flag8" ],
+ name => "Remote parameter settings V/H"
+ },
+ 87 => { format => [ "u8", "nu" ], name => "Nominal ventilation value" },
+ 88 => { format => [ "u8", "nu" ], name => "Number of TSPs V/H" },
+ 89 => { format => [ "u8", "u8" ], name => "TSP setting V/H" },
+ 90 => { format => [ "u8", "nu" ], name => "Size of fault buffer V/H" },
+ 91 => { format => [ "u8", "u8" ], name => "Fault buffer entry V/H" },
+ 100 => { format => [ "nu", "flag8" ], name => "Remote override function" },
+ 101 => {
+ format => [ "flag8", "flag8" ],
+ name => "Solar storage mode and status"
+ },
+ 102 => { format => [ "flag8", "u8" ], name => "Solar storage fault flags" },
+ 103 =>
+ { format => [ "flag8", "u8" ], name => "Solar storage config/memberid" },
+ 104 =>
+ { format => [ "u8", "u8" ], name => "Solar storage product version" },
+ 105 => { format => [ "u8", "nu" ], name => "Number of TSPs solar storage" },
+ 106 => { format => [ "u8", "u8" ], name => "TSP setting solar storage" },
+ 107 => {
+ format => [ "u8", "u8" ],
+ name => "Size of fault buffer solar storage"
+ },
+ 108 =>
+ { format => [ "u8", "u8" ], name => "Fault buffer entry solar storage" },
+ 113 => { format => ["u16"], name => "Unsuccessful burner starts" },
+ 114 => { format => ["u16"], name => "Flame signal too low count" },
+ 115 => { format => ["u16"], name => "OEM diagnostic code" },
+ 116 => { format => ["u16"], name => "Burner starts" },
+ 117 => { format => ["u16"], name => "CH pump starts" },
+ 118 => { format => ["u16"], name => "DHW pump/valve starts" },
+ 119 => { format => ["u16"], name => "DHW burner starts" },
+ 120 => { format => ["u16"], name => "Burner operation hours" },
+ 121 => { format => ["u16"], name => "CH pump operation hours" },
+ 122 => { format => ["u16"], name => "DHW pump/valve operation hours" },
+ 123 => { format => ["u16"], name => "DHW burner operation hours" },
+ 124 => { format => ["f8.8"], name => "OpenTherm version Master" },
+ 125 => { format => ["f8.8"], name => "OpenTherm version Slave" },
+ 126 => { format => [ "u8", "u8" ], name => "Master product version" },
+ 127 => { format => [ "u8", "u8" ], name => "Slave product version" },
+};
+
+my $ot_types = {
+ 0 => "read data",
+ 1 => "write data",
+ 2 => "invalidate data",
+ 3 => "unknown 0x3",
+ 4 => "read ack",
+ 5 => "write ack",
+ 6 => "data invalid",
+ 7 => "unknown 0x7"
+};
+
+sub decode($$$) {
+ my ( $dataref, $string, $offset ) = @_;
+ my $b0 = hex( substr( $string, 0, 2 ) );
+
+ my $type = ( $b0 >> 4 ) & 7;
+ my $id = hex( substr( $string, 2, 2 ) );
+
+ my $data = pack( 'H4', substr( $string, 4, 4 ) );
+ my $format = ["u16"];
+
+ my $ret = {
+ id => $id,
+ fake_id => $id + $offset,
+ type => $type,
+ type_str => $ot_types->{$type},
+ };
+
+ my $bits = undef;
+
+ if ( exists $ot_registers->{$id} ) {
+ $ret->{id_str} = $ot_registers->{$id}->{name};
+ $ret->{units} = $ot_registers->{$id}->{units};
+ $format = $ot_registers->{$id}->{format};
+ $bits = $ot_registers->{$id}->{names};
+ }
+
+ $ret->{format} = join( ',', @$format );
+ $ret->{raw_hex} = substr( $string, 4, 4 );
+ $ret->{raw} = $data;
+
+ $ret->{values} = [];
+ for my $f (@$format) {
+
+ if ( $f eq "u16" ) {
+ push @{ $ret->{values} }, unpack( 'S>', $data );
+ $data = substr( $data, 2 );
+ }
+ elsif ( $f eq 's16' ) {
+ push @{ $ret->{values} }, unpack( 's>', $data );
+ $data = substr( $data, 2 );
+ }
+ elsif ( $f eq 'u8' ) {
+ push @{ $ret->{values} }, unpack( 'C', $data );
+ $data = substr( $data, 1 );
+ }
+ elsif ( $f eq 's8' ) {
+ push @{ $ret->{values} }, unpack( 'c', $data );
+ $data = substr( $data, 1 );
+ }
+ elsif ( $f eq 'nu' ) {
+ $data = substr( $data, 1 );
+ }
+ elsif ( $f eq 'flag16' ) {
+ push @{ $ret->{values} },
+ ( unpack( "(A)*", unpack( "B16", $data ) ) );
+ $data = substr( $data, 2 );
+ }
+ elsif ( $f eq 'flag8' ) {
+ push @{ $ret->{values} },
+ ( unpack( "(A)*", unpack( "B8", $data ) ) );
+ $data = substr( $data, 1 );
+ }
+ elsif ( $f eq 'f8.8' ) {
+ push @{ $ret->{values} }, unpack( 's>', $data ) / 256.0;
+ }
+
+ }
+
+ $dataref->{ $id + $offset } = $ret;
+
+ if ( defined $bits ) {
+ my @vs = ( @{ $ret->{values} } );
+ my $bit = scalar( @{ $ret->{values} } );
+
+ for my $label (@$bits) {
+ $bit--;
+ my $v = shift(@vs);
+ next unless length($label) > 0;
+ my $key = sprintf( "%d.%d", $id + $offset, $bit );
+ $ret = {};
+
+ $ret->{id} = sprintf( "%d.%d", $id, $bit );
+ $ret->{fake_id} = $key;
+ $ret->{type} = $type;
+ $ret->{type_str} = $ot_types->{$type};
+ $ret->{id_str} = $label;
+ $ret->{values} = [$v];
+
+ $dataref->{$key} = $ret;
+ }
+ }
+
+ return $ret;
+}
+
+sub thermostat($$) {
+ my ( $string, $data ) = @_;
+
+ return decode( $data, $string, 0x100 );
+
+}
+
+sub boiler($$) {
+ my ( $string, $data ) = @_;
+
+ return decode( $data, $string, 0x0 );
+
+}
+
+sub get_units($) {
+ my $id = shift;
+
+ if ( $id =~ /^\d+$/ ) {
+ return '' unless exists $ot_registers->{$id};
+ return '' unless exists $ot_registers->{$id}->{units};
+ return $ot_registers->{$id}->{units};
+ }
+ elsif ( $id =~ /^(\d+)\.(\d+)$/ ) {
+ my $base_id = $1;
+ my $bit = $2;
+
+ return '' unless exists $ot_registers->{$base_id};
+ return '' unless exists $ot_registers->{$base_id}->{unitss};
+
+ $bit =
+ ( scalar( @{ $ot_registers->{$base_id}->{unitss} } ) - 1 ) - $bit;
+ return $ot_registers->{$base_id}->{unitss}->[$bit];
+ }
+ else {
+ return '';
+ }
+}
+
+sub get_name($) {
+ my $id = shift;
+
+ if ( $id =~ /^\d+$/ ) {
+ return "Unknown $id" unless exists $ot_registers->{$id};
+ return $ot_registers->{$id}->{name};
+ }
+ elsif ( $id =~ /^(\d+)\.(\d+)$/ ) {
+ my $base_id = $1;
+ my $bit = $2;
+
+ return "Unknown $id" unless exists $ot_registers->{$base_id};
+ return "Unknown $id" unless exists $ot_registers->{$base_id}->{names};
+
+ $bit = ( scalar( @{ $ot_registers->{$base_id}->{names} } ) - 1 ) - $bit;
+ return $ot_registers->{$base_id}->{names}->[$bit];
+ }
+ else {
+ return undef;
+ }
+}
+
+sub get_warning($) {
+ my $id = shift;
+
+ if ( $id =~ /^\d+$/ ) {
+ return "Unknown $id" unless exists $ot_registers->{$id};
+ return $ot_registers->{$id}->{warning};
+ }
+ elsif ( $id =~ /^(\d+)\.(\d+)$/ ) {
+ my $base_id = $1;
+ my $bit = $2;
+
+ return "Unknown $id" unless exists $ot_registers->{$base_id};
+ return "Unknown $id" unless exists $ot_registers->{$base_id}->{names};
+
+ $bit =
+ ( scalar( @{ $ot_registers->{$base_id}->{warnings} } ) - 1 ) - $bit;
+ return $ot_registers->{$base_id}->{warnings}->[$bit];
+ }
+ else {
+ return undef;
+ }
+}
+
+sub get_critical($) {
+ my $id = shift;
+
+ if ( $id =~ /^\d+$/ ) {
+ return "Unknown $id" unless exists $ot_registers->{$id};
+ return $ot_registers->{$id}->{crtitical};
+ }
+ elsif ( $id =~ /^(\d+)\.(\d+)$/ ) {
+ my $base_id = $1;
+ my $bit = $2;
+
+ return "Unknown $id" unless exists $ot_registers->{$base_id};
+ return "Unknown $id" unless exists $ot_registers->{$base_id}->{names};
+
+ $bit =
+ ( scalar( @{ $ot_registers->{$base_id}->{crtiticals} } ) - 1 ) - $bit;
+ return $ot_registers->{$base_id}->{crtiticals}->[$bit];
+ }
+ else {
+ return undef;
+ }
+}
+
+sub maul($) {
+ my $n = shift;
+
+ $n =~ s/\s/_/g;
+ return "boiler_" . lc($n);
+}
+
+my $report = [
+ '0.0', '0.1', '0.2', '0.3', '0.6', '0.8', '0.9', '17', '18', '25',
+ '26', '28', '57', '80', '257', '270', '272', '280', '312'
+];
+
+sub do_thing($) {
+ my $t = shift;
+
+ my $name = get_name($t);
+ my $maul = maul($name);
+ my $warning = get_warning($t);
+ my $critical = get_critical($t);
+
+ print "$maul.draw LINE2\n";
+ print "$maul.type GAUGE\n";
+ print "$maul.label $name\n";
+
+ print "$maul.warning ", $warning, "\n" if $warning;
+ print "$maul.critical ", $critical, "\n" if $critical;
+}
+
+sub do_units($) {
+ my $want_unit = shift;
+
+ for my $t (@$report) {
+ my $unit = get_units($t);
+
+ next unless $want_unit eq $unit;
+ do_thing($t);
+ }
+}
+
+sub do_values_units($$) {
+ my ( $data, $wanted_units ) = @_;
+
+ for my $t (@$report) {
+ next unless exists $data->{$t};
+
+ my $units = get_units($t);
+ next unless $units eq $wanted_units;
+
+ my $name = get_name($t);
+ my $maul = maul($name);
+
+ print $maul, ".value ", $data->{$t}->{values}->[0], "\n";
+ }
+}
+
+my $wanted = { map { my $q = $_; $q =~ s/\..*$//; $q => 1 } @$report };
+my $n_wanted = scalar( keys %$wanted );
+
+for my $t ( keys %$ot_registers ) {
+ $ot_registers->{ $t + 0x100 } = $ot_registers->{$t};
+}
+
+my @bits = split( /_+/, $0 );
+
+exit 0 unless scalar(@bits) > 1;
+
+my $host = $bits[1];
+
+$host =~ s/-//g;
+
+my $short_host = $host;
+
+$short_host =~ s/\..*$//;
+
+if ( $ARGV[0] eq 'config' ) {
+
+ print "host_name ", $host, "\n";
+
+ print "multigraph temperatures_${short_host}\n";
+ print "graph_title Temperatures\n";
+ print "graph_category boiler\n";
+ print "graph_vlabel centigrade\n";
+
+ do_units('C');
+
+ print "multigraph pressures_${short_host}\n";
+ print "graph_title Pressures\n";
+ print "graph_category boiler\n";
+ print "graph_vlabel bar\n";
+
+ do_units('bar');
+
+ print "multigraph modulation_${short_host}\n";
+ print "graph_title Modulation\n";
+ print "graph_category boiler\n";
+ print "graph_vlabel bar\n";
+
+ do_units('%');
+
+ for my $t (@$report) {
+ next if length( get_units($t) ) > 0;
+
+ my $name = get_name($t);
+ my $maul = maul($name);
+
+ print "multigraph ${maul}_${short_host}\n";
+ print "graph_title $name\n";
+ print "graph_category boiler\n";
+
+ do_thing($t);
+
+ }
+
+ exit(0);
+}
+
+my $telnet = new Net::Telnet(
+ Timeout => 10,
+ Telnetmode => 0,
+ Binmode => 1,
+ Host => $bits[1],
+ Port => 2001
+);
+
+#$telnet->open($bits[1]);
+
+my $data = {};
+my $then = time;
+my $timeout = 60;
+
+while ( ($n_wanted) and ( ( time - $then ) < $timeout ) ) {
+ my $line = $telnet->getline( Timeout => 1, Errmode => 'return' );
+ my $id = -1;
+
+ chomp $line;
+ chomp $line;
+
+ if ( $line =~ /^T/ ) {
+ $id = thermostat( substr( $line, 1, 8 ), $data );
+ }
+ elsif ( $line =~ /^B/ ) {
+ $id = boiler( substr( $line, 1, 8 ), $data );
+ }
+
+ if ( exists $wanted->{$id} ) {
+ $n_wanted--;
+ delete $wanted->{$id};
+ }
+
+}
+
+print "multigraph temperatures_${short_host}\n";
+do_values_units( $data, 'C' );
+print "multigraph pressures_${short_host}\n";
+do_values_units( $data, 'bar' );
+print "multigraph modulation_${short_host}\n";
+do_values_units( $data, '%' );
+
+for my $t (@$report) {
+ next unless exists $data->{$t};
+
+ my $units = get_units($t);
+ next unless $units eq '';
+
+ my $name = get_name($t);
+ my $maul = maul($name);
+
+ print "multigraph ${maul}_${short_host}\n";
+ print $maul, ".value ", $data->{$t}->{values}->[0], "\n";
+}
+
+exit(0);
diff --git a/munin/boiler_rx b/munin/boiler_rx
new file mode 100755
index 0000000..f794999
--- /dev/null
+++ b/munin/boiler_rx
@@ -0,0 +1,65 @@
+#!/usr/bin/env perl
+
+use Net::Telnet;
+use Data::Dumper;
+
+sub dump_file($$) {
+ my ( $name, $guts ) = @_;
+
+ $fh = new IO::File ">" . $name;
+ return unless defined $fh;
+ $fh->print($guts);
+ $fh->close;
+ undef $fh;
+}
+
+my $timeout = 60;
+
+my $host = $ARGV[0];
+
+my $path = "/var/run/boiler_" . $host;
+
+mkdir( $path, 0755 );
+
+while (1) {
+ my $telnet = new Net::Telnet(
+ Timeout => 10,
+ Telnetmode => 0,
+ Binmode => 1,
+ Host => $host,
+ Port => 2001
+ );
+
+ #$telnet->open($bits[1]);
+
+ my $nodata = 0;
+
+ while ( ( not $telnet->eof ) and ( $nodata < $timeout ) ) {
+ my $line = $telnet->getline( Timeout => 1, Errmode => 'return' );
+
+ if ( not defined $line or ( length($line) == 0 ) ) {
+ $nodata++;
+ }
+ else {
+ $nodata = 0;
+ }
+
+ chomp $line;
+ chomp $line;
+
+ $line = uc($line);
+
+ next unless $line =~ /^([TB])(.)(.)(..)(....)/;
+
+ my $fn = $1 . $2 . "x" . $4;
+ my $c = $1 . $2 . $3 . $4 . $5;
+
+ dump_file( $path . "/" . $fn, $c );
+
+ }
+
+ $telnet->close;
+
+}
+
+exit(0);
diff --git a/otmonitor b/otmonitor
-Subproject 6ef7f2b1455ea10c5f7147216d442326c1ccfdd
+Subproject ef7861864b0e2c811de3804f3742eafbbb99aa5
diff --git a/radiator-plc/DOCS/DS18B20.pdf b/radiator-plc/DOCS/DS18B20.pdf
new file mode 100644
index 0000000..52dc259
--- /dev/null
+++ b/radiator-plc/DOCS/DS18B20.pdf
Binary files differ
diff --git a/radiator-plc/DOCS/plc_notes.txt b/radiator-plc/DOCS/plc_notes.txt
new file mode 100644
index 0000000..89ce2a2
--- /dev/null
+++ b/radiator-plc/DOCS/plc_notes.txt
@@ -0,0 +1,93 @@
+
+STM32 pin assignments
+
+PA0 X4
+PA1 X3
+PA2 RS422 pin 4
+PA3 RS422 pin 1
+PA4
+PA5
+PA6 RS422 pin 3
+PA7 Via 472 to Run switch
+PA8 Y1
+PA9 RS232
+PA10 RS232
+PA11 Y2
+PA12 Y3
+PA13 SWDIO
+PA14 SWCLK
+PA15
+
+PB0 10k to ground
+PB1 AD1 via condiitoning (conditioning removed (apart from zener) and with 10k pull up)
+PB2 AD0 via conditioning
+PB3
+PB4
+PB5
+PB6
+PB7
+PB8
+PB9
+PB10
+PB11
+PB12
+PB13
+PB14
+PB15 X2
+
+PC0
+PC1
+PC2 Err LED
+PC3 Run LED
+PC4 X5
+PC5
+PC6 X1
+PC7 X0
+PC8 24V detect
+PC9 Y0
+PC10
+PC11
+PC12
+PC13 tamper RTC
+PC14 OSC32 (no sign of xtal)
+PC15 OSC32 (no sign of xtal)
+
+PD0 Y4
+PD1 Y5
+PD2 Y6
+PD3 Y7
+PD4
+PD5
+PD6
+PD7
+PD8
+PD9
+PD10 X13
+PD11 X12
+PD12 X11
+PD13 X10
+PD14 X7
+PD15 X6
+
+PE0
+PE1
+PE2
+PE3
+PE4
+PE5
+PE6
+PE7
+PE8
+PE9
+PE10
+PE11
+PE12
+PE13
+PE14
+PE15
+
+VBAT
+BOOT0 strapped by R16/R72
+
+
+
diff --git a/radiator-plc/DOCS/radiator-numbering.txt b/radiator-plc/DOCS/radiator-numbering.txt
new file mode 100644
index 0000000..dabb942
--- /dev/null
+++ b/radiator-plc/DOCS/radiator-numbering.txt
@@ -0,0 +1,8 @@
+0: small radiator next to fireplace
+1: dd nearest road
+2: dd nearest garden
+3: hall
+4: kitchen
+5: music room
+6: bathroom
+
diff --git a/radiator-plc/DOCS/valve_01213210.pdf b/radiator-plc/DOCS/valve_01213210.pdf
new file mode 100644
index 0000000..3ad1c10
--- /dev/null
+++ b/radiator-plc/DOCS/valve_01213210.pdf
Binary files differ
diff --git a/radiator-plc/DOCS/valve_wiring.txt b/radiator-plc/DOCS/valve_wiring.txt
new file mode 100644
index 0000000..882aa63
--- /dev/null
+++ b/radiator-plc/DOCS/valve_wiring.txt
@@ -0,0 +1,15 @@
+Pin numbers on RJ45 and DB-9 are the same
+
+1 or/w 1 wire bus
+2 or GND
+3 g/w uSwitch (short to gound to indicate valve open)
+4 bl 5V
+5 bl/w 24V
+6 g uSwitch
+7 br/w coil - plc pulls to ground to open valve
+8 br coil
+
+(doubling up is to make wirign in plc neater)
+
+
+
diff --git a/radiator-plc/mt300n-v2/.gitignore b/radiator-plc/mt300n-v2/.gitignore
new file mode 100644
index 0000000..859afb1
--- /dev/null
+++ b/radiator-plc/mt300n-v2/.gitignore
@@ -0,0 +1 @@
+stamp
diff --git a/radiator-plc/mt300n-v2/Makefile b/radiator-plc/mt300n-v2/Makefile
new file mode 100644
index 0000000..cdf9d24
--- /dev/null
+++ b/radiator-plc/mt300n-v2/Makefile
@@ -0,0 +1,6 @@
+STUFF=$(shell find usr \! -type d -print)
+
+stamp: ${STUFF}
+ tar cf - ${STUFF} | ssh radiator-plc0 "cd / && tar xvpf -"
+ #tar cf - ${STUFF} | ssh radiator-plc1 "cd / && tar xvpf -"
+ touch $@
diff --git a/radiator-plc/mt300n-v2/usr/bin/mqtt_if.pl b/radiator-plc/mt300n-v2/usr/bin/mqtt_if.pl
new file mode 100755
index 0000000..f81b6e0
--- /dev/null
+++ b/radiator-plc/mt300n-v2/usr/bin/mqtt_if.pl
@@ -0,0 +1,76 @@
+#!/usr/bin/env perl
+
+use strict;
+
+use Device::SerialPort;
+use Net::MQTT::Simple;
+
+sub chomp_harder($) {
+ my $ret = shift;
+ my $irs = $/;
+ $/ = "\n";
+ chomp $ret;
+ $/ = "\r";
+ chomp $ret;
+ $/ = $irs;
+ return $ret;
+}
+
+sub get_line($$) {
+ my ( $port, $mqtt ) = @_;
+
+ my $ret = "";
+
+ while (1) {
+ my ( $count, $byte ) = $port->read(1);
+ $mqtt->tick();
+
+ if ( $count > 0 ) {
+ $ret .= $byte;
+ return chomp_harder($ret) if $byte eq "\n";
+ }
+ else {
+ $ret = chomp_harder($ret);
+ return $ret;
+ }
+ }
+}
+
+my $plc_port = "/dev/ttyS1";
+my $plc = new Device::SerialPort($plc_port) || die "can't open $plc_port\n";
+
+$plc->baudrate(38400);
+$plc->parity("none");
+$plc->databits(8);
+$plc->stopbits(1);
+$plc->stty_icanon(0);
+
+$plc->read_char_time(0);
+$plc->read_const_time(1000);
+
+my $mqtt = Net::MQTT::Simple->new("10.32.139.1");
+
+sub mqtt_msg($$) {
+ my ( $topic, $message ) = @_;
+
+ $plc->write("MQTT $topic $message\r\n");
+}
+
+$mqtt->subscribe( 'cmnd/+/var1', \&mqtt_msg );
+$mqtt->subscribe( 'cmnd/+/var2', \&mqtt_msg );
+$mqtt->subscribe( 'cmnd/+/var3', \&mqtt_msg );
+
+while (1) {
+ my $line = get_line( $plc, $mqtt );
+ next unless length($line) > 0;
+
+ if ( $line =~ /MQTT\s+([^\s]+)\s+(.+)/ ) {
+ $mqtt->publish( $1, $2 );
+ }
+
+ if ( $line =~ /MQTTR\s+([^\s]+)\s+(.+)/ ) {
+ $mqtt->retain( $1, $2 );
+ }
+
+}
+
diff --git a/radiator-plc/mt300n-v2/usr/bin/run_mqtt_if b/radiator-plc/mt300n-v2/usr/bin/run_mqtt_if
new file mode 100755
index 0000000..2c06f6b
--- /dev/null
+++ b/radiator-plc/mt300n-v2/usr/bin/run_mqtt_if
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+while true; do
+ /usr/local/bin/mqtt_if.pl
+ sleep 1
+done
diff --git a/radiator-plc/stm32/.gitignore b/radiator-plc/stm32/.gitignore
new file mode 100644
index 0000000..64532f4
--- /dev/null
+++ b/radiator-plc/stm32/.gitignore
@@ -0,0 +1,15 @@
+*.o
+*.d
+*.map
+*.hex
+*.elf
+*.swp
+*~
+*.dfu
+plot/data
+plot/script
+*.bin
+scmversion
+commit.h
+opencm3.build.stamp
+*.orig
diff --git a/radiator-plc/stm32/Makefile b/radiator-plc/stm32/Makefile
new file mode 100644
index 0000000..e1cd497
--- /dev/null
+++ b/radiator-plc/stm32/Makefile
@@ -0,0 +1,8 @@
+
+default:
+ make -C libopencm3
+ make -C app
+
+clean:
+ make -C libopencm3 clean
+ make -C app clean
diff --git a/radiator-plc/stm32/Makefile.include b/radiator-plc/stm32/Makefile.include
new file mode 100644
index 0000000..4f5cbd9
--- /dev/null
+++ b/radiator-plc/stm32/Makefile.include
@@ -0,0 +1,44 @@
+##
+## This file is part of the libopencm3 project.
+##
+## Copyright (C) 2009 Uwe Hermann <uwe@hermann-uwe.de>
+## Copyright (C) 2010 Piotr Esden-Tempski <piotr@esden.net>
+##
+## This library is free software: you can redistribute it and/or modify
+## it under the terms of the GNU Lesser General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## This library is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU Lesser General Public License for more details.
+##
+## You should have received a copy of the GNU Lesser General Public License
+## along with this library. If not, see <http://www.gnu.org/licenses/>.
+##
+
+LIBNAME = opencm3_stm32f1
+DEFS = -DSTM32F1
+
+FP_FLAGS ?= -msoft-float
+ARCH_FLAGS = -mthumb -mcpu=cortex-m3 $(FP_FLAGS) -mfix-cortex-m3-ldrd
+
+################################################################################
+# OpenOCD specific variables
+
+OOCD ?= openocd
+OOCD_INTERFACE ?= interface/stlink-v2.cfg
+OOCD_BOARD ?= target/stm32f1x.cfg
+
+################################################################################
+# Black Magic Probe specific variables
+# Set the BMP_PORT to a serial port and then BMP is used for flashing
+BMP_PORT ?=
+
+################################################################################
+# texane/stlink specific variables
+#STLINK_PORT ?= :4242
+
+
+include ../Makefile.rules
diff --git a/radiator-plc/stm32/Makefile.rules b/radiator-plc/stm32/Makefile.rules
new file mode 100644
index 0000000..a723e6f
--- /dev/null
+++ b/radiator-plc/stm32/Makefile.rules
@@ -0,0 +1,261 @@
+#
+## This file is part of the libopencm3 project.
+##
+## Copyright (C) 2009 Uwe Hermann <uwe@hermann-uwe.de>
+## Copyright (C) 2010 Piotr Esden-Tempski <piotr@esden.net>
+## Copyright (C) 2013 Frantisek Burian <BuFran@seznam.cz>
+##
+## This library is free software: you can redistribute it and/or modify
+## it under the terms of the GNU Lesser General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## This library is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU Lesser General Public License for more details.
+##
+## You should have received a copy of the GNU Lesser General Public License
+## along with this library. If not, see <http://www.gnu.org/licenses/>.
+##
+
+# Be silent per default, but 'make V=1' will show all compiler calls.
+ifneq ($(V),1)
+Q := @
+NULL := 2>/dev/null
+endif
+
+###############################################################################
+# Executables
+
+PREFIX ?= arm-none-eabi
+
+CC := $(PREFIX)-gcc
+CXX := $(PREFIX)-g++
+LD := $(PREFIX)-gcc
+AR := $(PREFIX)-ar
+AS := $(PREFIX)-as
+OBJCOPY := $(PREFIX)-objcopy
+OBJDUMP := $(PREFIX)-objdump
+GDB := $(PREFIX)-gdb
+STFLASH = $(shell which st-flash)
+STYLECHECK := /checkpatch.pl
+STYLECHECKFLAGS := --no-tree -f --terse --mailback
+STYLECHECKFILES := $(shell find . -name '*.[ch]')
+
+
+###############################################################################
+# Source files
+
+LDSCRIPT ?= $(BINARY).ld
+
+#OBJS += $(BINARY).o
+
+
+ifeq ($(strip $(OPENCM3_DIR)),)
+# user has not specified the library path, so we try to detect it
+
+# where we search for the library
+LIBPATHS := ./libopencm3 ../libopencm3
+
+OPENCM3_DIR := $(wildcard $(LIBPATHS:=/locm3.sublime-project))
+OPENCM3_DIR := $(firstword $(dir $(OPENCM3_DIR)))
+
+ifeq ($(strip $(OPENCM3_DIR)),)
+$(warning Cannot find libopencm3 library in the standard search paths.)
+$(error Please specify it through OPENCM3_DIR variable!)
+endif
+endif
+
+ifeq ($(V),1)
+$(info Using $(OPENCM3_DIR) path to library)
+endif
+
+INCLUDE_DIR = $(OPENCM3_DIR)/include
+LIB_DIR = $(OPENCM3_DIR)/lib
+SCRIPT_DIR = $(OPENCM3_DIR)/scripts
+
+###############################################################################
+# C flags
+
+CFLAGS += -Os -g
+CFLAGS += -Wextra -Wimplicit-function-declaration
+CFLAGS += -Wmissing-prototypes -Wstrict-prototypes
+CFLAGS += -fno-common -ffunction-sections -fdata-sections
+
+###############################################################################
+# C++ flags
+
+CXXFLAGS += -Os -g
+CXXFLAGS += -Wextra -Wshadow -Wredundant-decls -Weffc++
+CXXFLAGS += -fno-common -ffunction-sections -fdata-sections
+
+###############################################################################
+# C & C++ preprocessor common flags
+
+CPPFLAGS += -MD
+CPPFLAGS += -Wall -Wundef
+
+INCLUDES = -I$(INCLUDE_DIR)
+DEFINES = $(DEFS)
+
+CPPFLAGS += $(INCLUDES) $(DEFINES)
+
+###############################################################################
+# Linker flags
+
+LDFLAGS += --static -nostartfiles
+LDFLAGS += -L$(LIB_DIR)
+LDFLAGS += -T$(LDSCRIPT)
+LDFLAGS += -Wl,-Map=$(*).map
+LDFLAGS += -Wl,--gc-sections
+ifeq ($(V),99)
+LDFLAGS += -Wl,--print-gc-sections
+endif
+
+###############################################################################
+# Used libraries
+
+LDLIBS += -l$(LIBNAME)
+LDLIBS += -Wl,--start-group -lc -lgcc -lnosys -Wl,--end-group
+
+###############################################################################
+###############################################################################
+###############################################################################
+
+.SUFFIXES: .elf .bin .hex .srec .list .map .images .dfu
+.SECONDEXPANSION:
+.SECONDARY:
+
+all: elf
+
+
+elf: $(BINARY).elf
+bin: $(BINARY).bin
+hex: $(BINARY).hex
+srec: $(BINARY).srec
+list: $(BINARY).list
+
+images: $(BINARY).images
+flash: $(BINARY).flash
+
+%.images: %.bin %.hex %.srec %.list %.map %.dfu
+ @#printf "*** $* images generated ***\n"
+
+%.bin: %.elf
+ @#printf " OBJCOPY $(*).bin\n"
+ $(Q)$(OBJCOPY) -Obinary $(*).elf $(*).bin
+
+%.hex: %.elf
+ @#printf " OBJCOPY $(*).hex\n"
+ $(Q)$(OBJCOPY) -Oihex $(*).elf $(*).hex
+
+%.dfu: %.elf
+ @#printf " OBJCOPY $(*).dfu\n"
+ $(Q)$(OBJCOPY) -Obinary $(*).elf $(*).dfu
+
+%.srec: %.elf
+ @#printf " OBJCOPY $(*).srec\n"
+ $(Q)$(OBJCOPY) -Osrec $(*).elf $(*).srec
+
+%.list: %.elf
+ @#printf " OBJDUMP $(*).list\n"
+ $(Q)$(OBJDUMP) -S $(*).elf > $(*).list
+
+fish:
+ echo %.elf %.map: $(OBJS) $(LDSCRIPT) $(LIB_DIR)/lib$(LIBNAME).a
+ echo $(BINARY).elf
+
+%.elf %.map: $(OBJS) $(LDSCRIPT) $(LIB_DIR)/lib$(LIBNAME).a
+ @#printf " LD $(*).elf\n"
+ $(Q)$(LD) $(LDFLAGS) $(ARCH_FLAGS) $(OBJS) $(LDLIBS) -o $(*).elf
+
+%.o: %.c $(OPENCM3_DIR)/../opencm3.build.stamp
+ @#printf " CC $(*).c\n"
+ $(Q)$(CC) $(CFLAGS) $(CPPFLAGS) $(ARCH_FLAGS) -o $(*).o -c $(*).c
+
+%.o: %.cxx $(OPENCM3_DIR)/../opencm3.build.stamp
+ @#printf " CXX $(*).cxx\n"
+ $(Q)$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(ARCH_FLAGS) -o $(*).o -c $(*).cxx
+
+%.o: %.cpp $(OPENCM3_DIR)/../opencm3.build.stamp
+ @#printf " CXX $(*).cpp\n"
+ $(Q)$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(ARCH_FLAGS) -o $(*).o -c $(*).cpp
+
+clean:
+ @#printf " CLEAN\n"
+ $(Q)$(RM) *.o *.d *.elf *.bin *.hex *.srec *.list *.map *~ *.dfu ${EXTRACLEANS}
+
+stylecheck: $(STYLECHECKFILES:=.stylecheck)
+styleclean: $(STYLECHECKFILES:=.styleclean)
+
+# the cat is due to multithreaded nature - we like to have consistent chunks of text on the output
+%.stylecheck: %
+ $(Q)$(SCRIPT_DIR)$(STYLECHECK) $(STYLECHECKFLAGS) $* > $*.stylecheck; \
+ if [ -s $*.stylecheck ]; then \
+ cat $*.stylecheck; \
+ else \
+ rm -f $*.stylecheck; \
+ fi;
+
+%.styleclean:
+ $(Q)rm -f $*.stylecheck;
+
+
+%.stlink-flash: %.bin
+ @printf " FLASH $<\n"
+ $(Q)$(STFLASH) write $(*).bin 0x8000000
+
+ifeq ($(STLINK_PORT),)
+ifeq ($(BMP_PORT),)
+ifeq ($(OOCD_SERIAL),)
+%.flash: %.hex
+ @printf " FLASH $<\n"
+ @# IMPORTANT: Don't use "resume", only "reset" will work correctly!
+ $(Q)$(OOCD) -f $(OOCD_INTERFACE) \
+ -f $(OOCD_BOARD) \
+ -c "init" -c "reset init" \
+ -c "flash write_image erase $(*).hex" \
+ -c "reset" \
+ -c "shutdown" $(NULL)
+else
+%.flash: %.hex
+ @printf " FLASH $<\n"
+ @# IMPORTANT: Don't use "resume", only "reset" will work correctly!
+ $(Q)$(OOCD) -f $(OOCD_INTERFACE) \
+ -f $(OOCD_BOARD) \
+ -c "ft2232_serial $(OOCD_SERIAL)" \
+ -c "init" -c "reset init" \
+ -c "flash write_image erase $(*).hex" \
+ -c "reset" \
+ -c "shutdown" $(NULL)
+endif
+else
+%.flash: %.elf
+ @printf " GDB $(*).elf (flash)\n"
+ $(Q)$(GDB) --batch \
+ -ex 'target extended-remote $(BMP_PORT)' \
+ -x $(SCRIPT_DIR)/black_magic_probe_flash.scr \
+ $(*).elf
+endif
+else
+%.flash: %.elf
+ @printf " GDB $(*).elf (flash)\n"
+ $(Q)$(GDB) --batch \
+ -ex 'target extended-remote $(STLINK_PORT)' \
+ -x $(SCRIPT_DIR)/stlink_flash.scr \
+ $(*).elf
+endif
+
+.PHONY: images clean stylecheck styleclean elf bin hex srec list
+
+-include $(OBJS:.o=.d)
+
+
+$(LIB_DIR)/lib$(LIBNAME).a: $(OPENCM3_DIR)/../opencm3.build.stamp
+
+
+$(OPENCM3_DIR)/../opencm3.build.stamp:
+ ${MAKE} -C ${OPENCM3_DIR}
+ touch $@
+
diff --git a/radiator-plc/stm32/app/1wire.c b/radiator-plc/stm32/app/1wire.c
new file mode 100644
index 0000000..dd66e92
--- /dev/null
+++ b/radiator-plc/stm32/app/1wire.c
@@ -0,0 +1,384 @@
+#include "project.h"
+
+#define GPIO_PORT GPIOB
+#define GPIO GPIO1
+
+#define BIT_ZERO '0'
+#define BIT_ONE '1'
+#define BIT_UNKNOWN 'U'
+#define BIT_CONFLICT 'C'
+
+
+static inline void
+low (void)
+{
+ CLEAR (GPIO);
+}
+
+static inline void
+hiz (void)
+{
+ SET (GPIO);
+}
+
+static inline int
+get (void)
+{
+ return ! !GET (GPIO);
+}
+
+/*Returns 1 if timeout - waits ~100ms */
+static inline int
+wait_hiz (void)
+{
+ unsigned int timeout = 10000;
+ hiz();
+
+ while (!get()) {
+ timeout--;
+
+ if (! (timeout--))
+ return 1;
+
+ delay_us (10);
+ }
+
+ return 0;
+}
+
+
+/* returns 1 if nothing on bus or error */
+int
+onewire_reset (void)
+{
+ int ret;
+ cm_disable_interrupts();
+ low();
+ delay_us (600);
+ hiz();
+ delay_us (65);
+ ret = get();
+
+ if (wait_hiz())
+ ret = 1;
+
+ delay_us (100);
+ cm_enable_interrupts();
+ return ret;
+}
+
+void
+onewire_one (void)
+{
+ cm_disable_interrupts();
+ low();
+ delay_us (10);
+ hiz();
+ delay_us (90);
+ cm_enable_interrupts();
+}
+
+void
+onewire_zero (void)
+{
+ cm_disable_interrupts();
+ low();
+ delay_us (90);
+ hiz();
+ delay_us (10);
+ cm_enable_interrupts();
+}
+
+int
+onewire_read (void)
+{
+ int ret;
+ cm_disable_interrupts();
+ low();
+ delay_us (10);
+ hiz();
+ delay_us (20);
+ ret = get();
+ delay_us (70);
+ cm_enable_interrupts();
+ return ret;
+}
+
+
+void
+onewire_write_byte (uint8_t v)
+{
+ int c;
+
+ for (c = 0; c < 8; ++c) {
+ if (v & 1)
+ onewire_one();
+ else
+ onewire_zero();
+
+ v >>= 1;
+ }
+}
+
+uint8_t
+onewire_read_byte (void)
+{
+ uint8_t v = 0;
+ int c;
+
+ for (c = 0; c < 8; ++c) {
+ v >>= 1;
+
+ if (onewire_read())
+ v |= 0x80;
+ }
+
+
+ return v;
+}
+
+void
+onewire_read_bytes (uint8_t *buf, int n)
+{
+ while (n--)
+ * (buf++) = onewire_read_byte();
+}
+
+
+void
+onewire_write_bytes (const uint8_t *buf, int n)
+{
+ while (n--)
+ onewire_write_byte (* (buf++));
+}
+
+
+int onewire_select (const Onewire_addr *a)
+{
+ if (!a)
+ onewire_write_byte (ONEWIRE_SKIP_ROM);
+ else {
+ onewire_write_byte (ONEWIRE_MATCH_ROM);
+ onewire_write_bytes (a->a, 8);
+ }
+
+ return 0;
+}
+
+
+int onewire_reset_and_select (const Onewire_addr *a)
+{
+ if (onewire_reset())
+ return 1;
+
+ if (onewire_select (a))
+ return 1;
+
+ return 0;
+}
+
+
+
+int onewire_wait_complete (unsigned timeout)
+{
+ while (! (onewire_read())) {
+ delay_ms (10);
+
+ if (! (timeout--))
+ return 1;
+ }
+
+ return 0;
+}
+
+
+
+int
+onewire_check_crc (uint8_t *buf, int n, uint8_t v)
+{
+ uint8_t crc = 0;
+ int i;
+
+ while (n--) {
+ uint8_t v = * (buf++);
+
+ for (i = 0; i < 8; ++i) {
+ uint8_t mix = (crc ^ v) & 0x01;
+ crc >>= 1;
+
+ if (mix)
+ crc ^= 0x8C;
+
+ v >>= 1;
+ }
+ }
+
+ return ! (crc == v);
+
+}
+
+static int onewire_conduct_search (uint8_t *bits)
+{
+ unsigned i, ir, r;
+
+ if (onewire_reset())
+ return -1;
+
+ onewire_write_byte (ONEWIRE_SEARCH_ROM);
+
+
+ for (i = 0; i < 64; ++i) {
+
+ r = onewire_read();
+ ir = onewire_read();
+
+#if 0
+
+ if ((i == 27) || (i == 24) || (i == 39))
+ ir = r = 0;
+
+#endif
+
+ switch (bits[i]) {
+ case BIT_UNKNOWN:
+
+ if (!r && ir) { /* Zero */
+ bits[i] = BIT_ZERO;
+ onewire_zero();
+ } else if (r && !ir) { /*One */
+ bits[i] = BIT_ONE;
+ onewire_one();
+ } else if (r && ir) /*Nothing here */
+ return -1;
+
+ else if (!r && !ir) { /*Both */
+ bits[i] = BIT_CONFLICT;
+ onewire_zero();
+ }
+
+ break;
+
+ case BIT_CONFLICT:
+ if (!r && !ir) /*Both */
+ onewire_zero();
+ else
+ return -1;
+
+ break;
+
+ case BIT_ZERO:
+ if (!r)
+ onewire_zero();
+ else
+ return -1;
+
+ break;
+
+ case BIT_ONE:
+ if (!ir)
+ onewire_one();
+ else
+ return -1;
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int onewire_next (uint8_t *bits)
+{
+ unsigned i;
+
+ for (i = 0; i < 64; ++i) {
+
+ if (bits[63 - i] == BIT_CONFLICT) {
+ bits[63 - i] = BIT_ONE;
+ return 1;
+ }
+
+ bits[63 - i] = BIT_UNKNOWN;
+ }
+
+ return 0;
+}
+
+static void onewire_bits_to_a (uint8_t *bits, Onewire_addr *a)
+{
+ unsigned i, j, c;
+
+ for (i = 0, j = 0; i < 8; ++i) {
+
+ a->a[i] = 0;
+
+ for (c = 1; c < 0x100; c <<= 1, ++j) {
+ if (bits[j] == BIT_ONE)
+ a->a[i] |= c;
+ }
+ }
+}
+
+int onewire_search (void)
+{
+ uint8_t bits[64];
+ Onewire_addr a;
+ int r, c;
+
+
+ printf ("One wire search\n");
+ //delay_ms (2000);
+
+ memset (bits, BIT_UNKNOWN, sizeof (bits));
+
+ do {
+ r = onewire_conduct_search (bits);
+
+ if (!r) {
+ onewire_bits_to_a (bits, &a);
+ c = onewire_check_crc (&a.a[0], 7, a.a[7]);
+
+ if (!c) {
+ printf ("O: {0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x}\r\n",
+ a.a[0],
+ a.a[1],
+ a.a[2],
+ a.a[3],
+ a.a[4],
+ a.a[5],
+ a.a[6],
+ a.a[7]);
+ }
+
+ }
+
+ } while (onewire_next (bits));
+
+
+
+
+
+
+ return 0;
+}
+
+
+void
+onewire_init (void)
+{
+ MAP_OUTPUT_OD_SLOW (GPIO);
+
+ hiz();
+ delay_us (20);
+ printf ("hiz -> %d\n", get());
+
+ low();
+
+ delay_us (20);
+ printf ("low -> %d\n", get());
+
+ hiz();
+ delay_us (20);
+
+
+}
diff --git a/radiator-plc/stm32/app/1wire.h b/radiator-plc/stm32/app/1wire.h
new file mode 100644
index 0000000..edfc1b9
--- /dev/null
+++ b/radiator-plc/stm32/app/1wire.h
@@ -0,0 +1,9 @@
+#define ONEWIRE_SKIP_ROM 0xcc
+#define ONEWIRE_SEARCH_ROM 0xf0
+#define ONEWIRE_MATCH_ROM 0x55
+
+#define ONEWIRE_ADDR_LEN 8
+typedef struct {
+ uint8_t a[ONEWIRE_ADDR_LEN];
+} Onewire_addr;
+
diff --git a/radiator-plc/stm32/app/Makefile b/radiator-plc/stm32/app/Makefile
new file mode 100644
index 0000000..8ef06f7
--- /dev/null
+++ b/radiator-plc/stm32/app/Makefile
@@ -0,0 +1,59 @@
+##
+## This file is part of the libopencm3 project.
+##
+## Copyright (C) 2009 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This library is free software: you can redistribute it and/or modify
+## it under the terms of the GNU Lesser General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## This library is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU Lesser General Public License for more details.
+##
+## You should have received a copy of the GNU Lesser General Public License
+## along with this library. If not, see <http://www.gnu.org/licenses/>.
+##
+
+CPROTO=cproto
+PROG=radiator-plc
+
+D=radiator-plc0
+
+V=1
+default: ${PROG}.elf
+
+CSRCS=hexdump.c main.c ring.c ticker.c usart.c watchdog.c led.c 1wire.c inputs.c outputs.c logic.c ds18b20.c
+
+HSRCS= 1wire.h project.h ring.h
+
+BINARY = ${PROG}
+OBJS = ${CSRCS:%.c=%.o}
+
+${OBJS}:
+
+include ../Makefile.include
+
+INCLUDES += -I..
+
+protos: ${CSRCS}
+ echo -n > prototypes.h
+ ${CPROTO} $(INCLUDES) $(DEFINES) -e -v ${CSRCS} > prototypes.h.tmp
+ mv -f prototypes.h.tmp prototypes.h
+
+flash: ${PROG}.hex
+ ssh ${D} flash_stm32 < ${PROG}.hex
+
+local_flash: ${PROG}.hex
+ $(Q)$(OOCD) -f $(OOCD_INTERFACE) \
+ -f $(OOCD_BOARD) \
+ -c "init" -c "reset init" \
+ -c "flash write_image erase $<" \
+ -c "reset" \
+ -c "shutdown" $(NULL)
+
+tidy:
+ astyle -A3 -s2 --attach-extern-c -L -c -w -Y -m0 -f -p -H -U -k3 -xj -xd ${CSRCS} ${HSRCS}
+
diff --git a/radiator-plc/stm32/app/control.c b/radiator-plc/stm32/app/control.c
new file mode 100644
index 0000000..f3a35c9
--- /dev/null
+++ b/radiator-plc/stm32/app/control.c
@@ -0,0 +1,114 @@
+#include "project.h"
+
+
+static uint32_t target = 25000;
+static uint32_t value = ~0U;
+
+#define SHORT 200
+#define LONG 1200
+#define REPEAT 400
+
+#define HYST 75UL
+
+
+static void
+show_temp (char t, uint32_t v, int y)
+{
+ char buf[16];
+ uint32_t i, f;
+
+ i = v / 1000;
+ f = v / 100 - (i * 10);
+
+ if (v == ~0U)
+ {
+ sprintf (buf, "%c:error ", t);
+ }
+ else
+ {
+ sprintf (buf, "%c:%2d.%1d%cC", t, (int) i, (int) f, 0xdf);
+ }
+
+ lcd_write (buf, 0, y);
+}
+
+void
+control_update (void)
+{
+
+ show_temp ('P', value, 0);
+ show_temp ('T', target, 1);
+
+ if (value == ~0U)
+ relay_off ();
+ else if (value < (target - HYST))
+ relay_on ();
+ else if (value > (target + HYST))
+ relay_off ();
+
+
+}
+
+void
+control_tick (void)
+{
+ static int down, up, update;
+
+ if (!gpio_get (GPIOB, GPIO4))
+ down++;
+ else
+ down = 0;
+
+ if (!gpio_get (GPIOB, GPIO6))
+ up++;
+ else
+ up = 0;
+
+ if ((down == SHORT) || (down == LONG))
+ {
+ if (target > 100)
+ target -= 100;
+ if (down == LONG)
+ down = LONG - REPEAT;
+ control_update ();
+ }
+
+ if ((up == SHORT) || (up == LONG))
+ {
+ if (target < 99900)
+ target += 100;
+ if (up == LONG)
+ up = LONG - REPEAT;
+ control_update ();
+ }
+
+ update++;
+
+ if (update == 1000)
+ {
+ control_update ();
+ update = 0;
+ }
+}
+
+void
+control_report (uint32_t v)
+{
+
+ value = v;
+ control_update ();
+}
+
+void
+control_init (void)
+{
+
+ gpio_set_mode (GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN,
+ GPIO4 | GPIO6);
+ gpio_set_mode (GPIOB, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL,
+ GPIO5);
+ gpio_set (GPIOB, GPIO4 | GPIO6);
+ gpio_clear (GPIOB, GPIO5);
+
+
+}
diff --git a/radiator-plc/stm32/app/ds18b20.c b/radiator-plc/stm32/app/ds18b20.c
new file mode 100644
index 0000000..3bd63ed
--- /dev/null
+++ b/radiator-plc/stm32/app/ds18b20.c
@@ -0,0 +1,128 @@
+#include "project.h"
+
+#define DS18B20_CONVERT 0x44
+#define DS18B20_WRITE_SCRATCHPAD 0x4e
+#define DS18B20_READ_SCRATCHPAD 0xbe
+
+
+#define TIMEOUT 150
+
+
+unsigned extract_leu16 (uint8_t *d)
+{
+ uint32_t u;
+
+ u = (uint32_t) d[0] | (((uint32_t) d[1]) << 8);
+ return u;
+}
+
+
+int extract_les16 (uint8_t *d)
+{
+ uint32_t u;
+ u = extract_leu16 (d);
+
+ if (u & 0x8000) u |= 0xffff0000;
+
+ return (int) u;
+}
+
+
+/* do not call from interrupt context (ie ticker)*/
+int
+ds18b20_read_sp (const Onewire_addr *a, uint8_t *buf, unsigned len)
+{
+ if (onewire_reset_and_select (a))
+ return ~0U;
+
+ onewire_write_byte (DS18B20_READ_SCRATCHPAD);
+ onewire_read_bytes (buf, len);
+
+
+ if ((len == 9) && onewire_check_crc (buf, 8, buf[8]))
+ return ~0U;
+
+ return 0;
+}
+
+int
+ds18b20_write_sp (const Onewire_addr *a, uint8_t *buf, unsigned len)
+{
+ if (onewire_reset_and_select (a))
+ return ~0U;
+
+ onewire_write_byte (DS18B20_WRITE_SCRATCHPAD);
+ onewire_write_bytes (buf, len);
+
+ return 0;
+}
+
+int ds18b20_convert (const Onewire_addr *a, unsigned timeout)
+{
+
+ if (onewire_reset_and_select (a))
+ return ~0U;
+
+ onewire_write_byte (DS18B20_CONVERT);
+
+ if (timeout && onewire_wait_complete (timeout))
+ return ~0U;
+
+ return 0;
+}
+
+int
+ds18b20_set_12 (const Onewire_addr *a)
+{
+ uint8_t buf[9];
+
+
+ if (ds18b20_read_sp (a, buf, 9))
+ return 1;
+
+ buf[0] = 0x0;
+ buf[1] = 0x0;
+ buf[2] = 0x7f; /* 12 bit conversions */
+
+ return ds18b20_write_sp (a, buf, 3);
+
+}
+
+int ds18b20_read (const Onewire_addr *a, int *temp)
+{
+ uint8_t buf[9];
+
+ if (ds18b20_read_sp (a, buf, 9))
+ return 1;
+
+ *temp = (extract_les16 (buf) * 500) >> 3;
+ return 0;
+}
+
+
+int
+ds18b20_set_12_convert (const Onewire_addr *a)
+{
+
+ if (ds18b20_set_12 (a))
+ return 1;
+
+ if (ds18b20_convert (a, 0))
+ return 1;
+
+ return 0;
+}
+
+/* do not call from interrupt context (ie ticker)*/
+int
+ds18b20_set_12_convert_and_read (const Onewire_addr *a, int *temp)
+{
+ if (ds18b20_set_12 (a))
+ return 1;
+
+ if (ds18b20_convert (a, TIMEOUT))
+ return 1;
+
+ return ds18b20_read (a, temp);
+}
+
diff --git a/radiator-plc/stm32/app/gdb.script b/radiator-plc/stm32/app/gdb.script
new file mode 100644
index 0000000..69f0239
--- /dev/null
+++ b/radiator-plc/stm32/app/gdb.script
@@ -0,0 +1,2 @@
+target remote boreas:3333
+cont
diff --git a/radiator-plc/stm32/app/hexdump.c b/radiator-plc/stm32/app/hexdump.c
new file mode 100644
index 0000000..83f02ea
--- /dev/null
+++ b/radiator-plc/stm32/app/hexdump.c
@@ -0,0 +1,57 @@
+#include "project.h"
+
+void
+hexdump (void *_d, int len)
+{
+ uint8_t *d = (uint8_t *) _d;
+ int i, j, k;
+ int e;
+
+ if (!d || len < 0)
+ return;
+
+ e = len + 15;
+ e &= ~15;
+
+ for (i = 0; i < e; i += 16) {
+ usart1_drain();
+ printf (" %05x:", i);
+
+ for (j = 0; j < 16; ++j) {
+ k = i + j;
+
+ if (k < len)
+ printf (" %02x", d[k]);
+ else
+ printf (" ");
+
+ if (j == 7)
+ printf (" ");
+ }
+
+ printf (" ");
+
+ for (j = 0; j < 16; ++j) {
+ k = i + j;
+
+ if (k < len) {
+ uint8_t c = d[k];
+
+ if (c < 33)
+ c = '.';
+
+ if (c > 126)
+ c = '.';
+
+ printf ("%c", c);
+ }
+
+ if (j == 7)
+ printf (" ");
+ }
+
+ printf ("\r\n");
+ }
+
+ usart1_drain();
+}
diff --git a/radiator-plc/stm32/app/inputs.c b/radiator-plc/stm32/app/inputs.c
new file mode 100644
index 0000000..825a3fe
--- /dev/null
+++ b/radiator-plc/stm32/app/inputs.c
@@ -0,0 +1,26 @@
+#include "project.h"
+
+#define N_INPUTS 14
+
+static const uint32_t input_bank[N_INPUTS] = { GPIOC, GPIOC, GPIOB, GPIOA, GPIOA, GPIOC, GPIOD, GPIOD, 0, 0, GPIOD, GPIOD, GPIOD, GPIOD};
+static const uint32_t input[N_INPUTS] = { GPIO7, GPIO6, GPIO15, GPIO1, GPIO0, GPIO4, GPIO15, GPIO14, 0, 0, GPIO13, GPIO12, GPIO11, GPIO10};
+
+int input_get (unsigned r)
+{
+ if (r >= N_INPUTS) return 0;
+
+ if (!input[r]) return 0;
+
+ return !gpio_get (input_bank[r], input[r]);
+}
+
+
+void inputs_init (void)
+{
+ unsigned r;
+
+ for (r = 0; r < N_INPUTS; ++r)
+ if (input[r])
+ gpio_set_mode (input_bank[r], GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, input[r]);
+
+}
diff --git a/radiator-plc/stm32/app/led.c b/radiator-plc/stm32/app/led.c
new file mode 100644
index 0000000..390ed2e
--- /dev/null
+++ b/radiator-plc/stm32/app/led.c
@@ -0,0 +1,53 @@
+#include "project.h"
+
+#define LED0 GPIO3
+#define LED0_PORT GPIOC
+
+#define LED1 GPIO2
+#define LED1_PORT GPIOC
+
+static int led = 0;
+
+void
+led_init (void)
+{
+ MAP_OUTPUT_PP (LED0);
+ MAP_OUTPUT_PP (LED1);
+
+ SET (LED0);
+ SET (LED1);
+}
+
+void led1_set (void)
+{
+ CLEAR (LED1);
+}
+
+void led1_clear (void)
+{
+ SET (LED1);
+}
+
+
+
+void
+led_tick (void)
+{
+ static unsigned i;
+
+ i++;
+
+ if (i < 1000) return;
+
+ i = 0;
+
+ led ^= 1;
+
+ if (!led)
+ CLEAR (LED0);
+
+ if (led)
+ SET (LED0);
+
+}
+
diff --git a/radiator-plc/stm32/app/logic.c b/radiator-plc/stm32/app/logic.c
new file mode 100644
index 0000000..8cab7ca
--- /dev/null
+++ b/radiator-plc/stm32/app/logic.c
@@ -0,0 +1,158 @@
+#include "project.h"
+
+
+#define N_VALVES 8
+
+static volatile int semaphore = 0;
+
+
+
+static const char *valve_name[N_VALVES] = {
+ "dd_radiator1",
+ "dd_radiator2",
+ "dd_radiator3",
+ "hall_radiator",
+ "kitchen_radiator",
+ "music_room_radiator",
+ "bathroom_radiator",
+ "plc0_radiator7",
+};
+
+static Onewire_addr valve_owa[N_VALVES] = {
+ {{0x28, 0xe3, 0x5d, 0x56, 0xb5, 0x01, 0x3c, 0x96}},
+ {{0x28, 0x0d, 0xaf, 0x56, 0xb5, 0x01, 0x3c, 0x89}},
+ {{0x28, 0x79, 0xb1, 0x56, 0xb5, 0x01, 0x3c, 0x13}},
+ {{0x28, 0x99, 0x8f, 0x56, 0xb5, 0x01, 0x3c, 0x1c}},
+ {{0x28, 0xb4, 0x98, 0x6e, 0x32, 0x20, 0x01, 0xc7}},
+ {{0x28, 0x0d, 0x4d, 0x56, 0xb5, 0x01, 0x3c, 0xd0}},
+ {{0x28, 0xb1, 0x71, 0x75, 0x32, 0x20, 0x01, 0xa9}},
+ {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
+};
+volatile int temp[N_VALVES];
+
+#define BAD_TEMP 0x7fffffff
+
+int low_limit[N_VALVES] = {
+ 15000,
+ 15000,
+ 15000,
+ 15000,
+ 15000,
+ 15000,
+ 15000,
+ 00000,
+};
+
+
+int high_limit[N_VALVES] = {
+ 15000,
+ 15000,
+ 15000,
+ 15000,
+ 15000,
+ 15000,
+ 15000,
+ 00000,
+};
+
+int valve_state[N_VALVES];
+
+
+
+void mqtt_dispatch (char *type, char *who, char *what, char *msg)
+{
+ unsigned i;
+
+ if (strcmp (type, "cmnd")) return;
+
+ for (i = 0; i < N_VALVES; ++i) {
+
+ if (!strcmp (who, valve_name[i])) {
+ if (!strcmp (what, "var1"))
+ low_limit[i] = atoi (msg) * 1000;
+
+ if (!strcmp (what, "var2"))
+ high_limit[i] = atoi (msg) * 1000;
+ }
+ }
+}
+
+
+static float scale(int i)
+{
+float ret;
+
+ret=i;
+return ret/1000.;
+}
+
+void logic_tick (void)
+{
+ static unsigned i;
+ static int read_failed = 1;
+ static char msg_buf[80];
+ int t;
+ int o;
+ int v;
+
+
+
+ if (!read_failed)
+ read_failed = ds18b20_read (&valve_owa[i], &t);
+
+ if (read_failed) {
+ temp[i] = BAD_TEMP;
+ valve_state[i] = 0;
+ } else {
+ temp[i] = t;
+
+ if (t < low_limit[i])
+ valve_state[i] = 1;
+
+ if (t >= high_limit[i])
+ valve_state[i] = 0;
+ }
+
+ o = valve_state[i];
+
+ output_write (i, o);
+ v = input_get (i);
+
+ usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/POWER %s\r\n", valve_name[i], o ? "ON" : "OFF"));
+ usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/OPEN %d\r\n", valve_name[i], v));
+
+ usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/var1 %.2f\r\n", valve_name[i], scale(low_limit[i])));
+
+ usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/var2 %.2f\r\n", valve_name[i], scale(high_limit[i])));
+
+
+
+ if (!read_failed) {
+ usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/TEMPERATURE %.2f\r\n", valve_name[i], scale(temp[i])));
+ usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/DELTA %.2f\r\n", valve_name[i], scale(low_limit[i]-temp[i])));
+ }
+
+
+ i++;
+
+ if (i >= N_VALVES)
+ i = 0;
+
+ if (request_search) {
+ request_search = 0;
+ onewire_search();
+ }
+
+ read_failed = ds18b20_set_12_convert (&valve_owa[i]);
+}
+
+
+void logic_slow_tick (void)
+{
+ semaphore = 1;
+}
+
+
+void logic_dispatch (void)
+{
+}
diff --git a/radiator-plc/stm32/app/main.c b/radiator-plc/stm32/app/main.c
new file mode 100644
index 0000000..57b6752
--- /dev/null
+++ b/radiator-plc/stm32/app/main.c
@@ -0,0 +1,150 @@
+#include "project.h"
+
+
+volatile int request_search;
+
+static void mqtt_cmd (char *type)
+{
+ char *m;
+ char *who, *what;
+
+ m = rindex (type, ' ');
+
+ if (!m) return;
+
+ *m = 0;
+ m++;
+
+ while (index (type, ' ') == type)
+ type++;
+
+ who = index (type, ' ');
+ if (who) *who = 0;
+
+
+ who = index (type, '/');
+
+ if (!who) {
+ who = "";
+ what = "";
+ } else {
+ *who = 0;
+ who++;
+
+ what = index (who, '/');
+
+ if (!what)
+ what = "";
+
+ else {
+ *what = 0;
+ what++;
+ }
+ }
+
+
+ mqtt_dispatch (type, who, what, m);
+}
+
+static void cmd_dispatch (char *cmd)
+{
+
+ if (!strcmp (cmd, "SEARCH")) {
+ request_search = 1;
+ return;
+ }
+
+ if (!strncmp (cmd, "MQTT ", 5)) {
+ mqtt_cmd (cmd + 5);
+ return;
+ }
+
+
+ printf ("Unknown command %s\n", cmd);
+}
+
+
+
+
+static void
+kbd_dispatch (void)
+{
+ static char cmd_buf[80];
+ static unsigned cmd_ptr;
+
+ uint8_t c;
+
+ while (!ring_read_byte (&rx1_ring, &c)) {
+
+ switch (c) {
+ case ' ':
+ case '\t':
+ if (!cmd_ptr)
+ break;
+
+ /*fall through */
+ default:
+ cmd_buf[cmd_ptr++] = c;
+ cmd_buf[cmd_ptr] = 0;
+
+
+ if (cmd_ptr < (sizeof (cmd_buf) - 1))
+ break;
+
+ /*fall through*/
+ case '\r':
+ case '\n':
+ if (!cmd_ptr)
+ break;
+
+ cmd_dispatch (cmd_buf);
+ cmd_ptr = 0;
+ break;
+ }
+ }
+}
+
+
+int
+main (void)
+{
+ /*set up pll */
+ rcc_clock_setup_in_hse_8mhz_out_72mhz();
+
+ /*turn on clocks which aren't done elsewhere */
+ rcc_periph_clock_enable (RCC_GPIOA);
+ rcc_periph_clock_enable (RCC_GPIOB);
+ rcc_periph_clock_enable (RCC_GPIOC);
+ rcc_periph_clock_enable (RCC_GPIOD);
+ rcc_periph_clock_enable (RCC_GPIOE);
+ rcc_periph_clock_enable (RCC_AFIO);
+
+ //watchdog_init ();
+
+ /*Adjust interrupt priorities so that uarts trump timer */
+ nvic_set_priority (NVIC_USART1_IRQ, 0x40);
+ nvic_set_priority (NVIC_USART2_IRQ, 0x40);
+ nvic_set_priority (NVIC_USART3_IRQ, 0x40);
+ nvic_set_priority (NVIC_SYSTICK_IRQ, 0xff);
+
+ ticker_init();
+ usart_init();
+ led_init();
+ onewire_init();
+ inputs_init();
+ outputs_init();
+
+ printf ("D: RESET\n");
+
+ //onewire_search();
+
+ for (;;) {
+ if (!ring_empty (&rx1_ring))
+ kbd_dispatch();
+
+ logic_dispatch();
+
+ }
+
+ return 0;
+}
diff --git a/radiator-plc/stm32/app/outputs.c b/radiator-plc/stm32/app/outputs.c
new file mode 100644
index 0000000..67d0519
--- /dev/null
+++ b/radiator-plc/stm32/app/outputs.c
@@ -0,0 +1,47 @@
+#include "project.h"
+
+#define N_OUTPUTS 8
+
+static const uint32_t output_bank[N_OUTPUTS] = { GPIOC, GPIOA, GPIOA, GPIOA, GPIOD, GPIOD, GPIOD, GPIOD};
+static const uint32_t output[N_OUTPUTS] = { GPIO9, GPIO8, GPIO11, GPIO12, GPIO0, GPIO1, GPIO2, GPIO3};
+
+void output_set (unsigned r)
+{
+ if (r >= N_OUTPUTS) return;
+
+ gpio_set (output_bank[r], output[r]);
+}
+
+void output_clear (unsigned r)
+{
+ if (r >= N_OUTPUTS) return;
+
+ gpio_clear (output_bank[r], output[r]);
+}
+
+void outputs_set (unsigned v)
+{
+ unsigned r;
+
+ for (r = 0; r < N_OUTPUTS; r++, v >>= 1) {
+ if (v & 1) output_set (r);
+ else output_clear (r);
+ }
+}
+
+void output_write (unsigned r, unsigned v)
+{
+ if (v) output_set (r);
+ else output_clear (r);
+}
+
+
+
+void outputs_init (void)
+{
+ unsigned r;
+
+ for (r = 0; r < N_OUTPUTS; ++r)
+ gpio_set_mode (output_bank[r], GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, output[r]);
+
+}
diff --git a/radiator-plc/stm32/app/pins.h b/radiator-plc/stm32/app/pins.h
new file mode 100644
index 0000000..657cbf9
--- /dev/null
+++ b/radiator-plc/stm32/app/pins.h
@@ -0,0 +1,53 @@
+#ifndef _PINS_H_
+#define _PINS_H_
+
+/* st seem to change these with every chip revision */
+
+#define MAP_AF(a) do { \
+ gpio_set_mode( a ## _PORT, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, a ); \
+ } while (0)
+
+/* STM32F1 doesn't have AF pull up, but also doesn't disconnect af inputs so just use regular pull up */
+#define MAP_AF_PU(a) do { \
+ gpio_set_mode( a ## _PORT, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, a); \
+ gpio_set( a ## _PORT, a); \
+ } while (0)
+
+#define MAP_AF_OD(a) do { \
+ gpio_set_mode( a ## _PORT, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, a ); \
+ } while (0)
+
+
+#define MAP_OUTPUT_PP(a) do { \
+ gpio_set_mode( a ## _PORT, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, a ); \
+ } while (0)
+
+
+#define MAP_OUTPUT_OD(a) do { \
+ gpio_set_mode( a ## _PORT, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, a ); \
+ } while (0)
+
+#define MAP_OUTPUT_OD_SLOW(a) do { \
+ gpio_set_mode( a ## _PORT, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, a ); \
+ } while (0)
+
+
+/* STM32F1 madly uses the output register to drive the other end of the resistor, so pull up */
+/* requires us to write a 1 there */
+
+#define MAP_INPUT_PU(a) do { \
+ gpio_set_mode( a ## _PORT, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, a); \
+ gpio_set( a ## _PORT, a); \
+ } while (0)
+
+
+#define MAP_INPUT(a) do { \
+ gpio_set_mode( a ## _PORT, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, a); \
+ } while (0)
+
+
+#define CLEAR(a) gpio_clear( a ## _PORT, a)
+#define SET(a) gpio_set( a ## _PORT, a)
+#define GET(a) gpio_get( a ## _PORT, a)
+
+#endif
diff --git a/radiator-plc/stm32/app/project.h b/radiator-plc/stm32/app/project.h
new file mode 100644
index 0000000..85f14b9
--- /dev/null
+++ b/radiator-plc/stm32/app/project.h
@@ -0,0 +1,29 @@
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <libopencm3/stm32/rcc.h>
+#include <libopencm3/stm32/gpio.h>
+#include <libopencm3/stm32/usart.h>
+#include <libopencm3/stm32/iwdg.h>
+#include <libopencm3/stm32/dma.h>
+#include <libopencm3/stm32/i2c.h>
+#include <libopencm3/cm3/systick.h>
+#include <libopencm3/cm3/nvic.h>
+#include <libopencm3/cm3/cortex.h>
+#include <libopencm3/cm3/scs.h>
+
+
+
+#include <stdio.h>
+#include <errno.h>
+
+#include "pins.h"
+#include "ring.h"
+#include "1wire.h"
+
+#include "prototypes.h"
+
+#define MEH do { printf("Z: %s:%d\r\n",__FUNCTION__,__LINE__); } while (0)
+
+#define CLOCK_HZ 72000000
+
diff --git a/radiator-plc/stm32/app/prototypes.h b/radiator-plc/stm32/app/prototypes.h
new file mode 100644
index 0000000..2347f01
--- /dev/null
+++ b/radiator-plc/stm32/app/prototypes.h
@@ -0,0 +1,80 @@
+/* hexdump.c */
+extern void hexdump(void *_d, int len);
+/* main.c */
+extern volatile int request_search;
+extern int main(void);
+/* ring.c */
+extern void ring_init(ring_t *r, uint8_t *buf, size_t len);
+extern int ring_write_byte(ring_t *r, uint8_t c);
+extern int ring_read_byte(ring_t *r, uint8_t *c);
+extern int ring_write(ring_t *r, uint8_t *buf, size_t len);
+extern int ring_empty(ring_t *r);
+/* ticker.c */
+extern volatile uint32_t ticks;
+extern uint32_t clock_scale;
+extern void delay_us(uint32_t d);
+extern void sys_tick_handler(void);
+extern void delay_ms(uint32_t d);
+extern void ticker_init(void);
+/* usart.c */
+extern ring_t rx1_ring;
+extern ring_t tx1_ring;
+extern unsigned locked;
+extern void usart_ticker(void);
+extern void usart1_isr(void);
+extern int _write(int file, char *ptr, int len);
+extern void usart1_queue(uint8_t d);
+extern void usart1_queue_buf(void *buf, size_t len);
+extern void usart1_drain(void);
+extern void usart_init(void);
+/* watchdog.c */
+extern void watchdog_init(void);
+extern void sausages(void);
+/* led.c */
+extern void led_init(void);
+extern void led1_set(void);
+extern void led1_clear(void);
+extern void led_tick(void);
+/* 1wire.c */
+extern int onewire_reset(void);
+extern void onewire_one(void);
+extern void onewire_zero(void);
+extern int onewire_read(void);
+extern void onewire_write_byte(uint8_t v);
+extern uint8_t onewire_read_byte(void);
+extern void onewire_read_bytes(uint8_t *buf, int n);
+extern void onewire_write_bytes(const uint8_t *buf, int n);
+extern int onewire_select(const Onewire_addr *a);
+extern int onewire_reset_and_select(const Onewire_addr *a);
+extern int onewire_wait_complete(unsigned timeout);
+extern int onewire_check_crc(uint8_t *buf, int n, uint8_t v);
+extern int onewire_search(void);
+extern void onewire_init(void);
+/* inputs.c */
+extern int input_get(unsigned r);
+extern void inputs_init(void);
+/* outputs.c */
+extern void output_set(unsigned r);
+extern void output_clear(unsigned r);
+extern void outputs_set(unsigned v);
+extern void output_write(unsigned r, unsigned v);
+extern void outputs_init(void);
+/* logic.c */
+extern volatile int temp[8];
+extern int low_limit[8];
+extern int high_limit[8];
+extern int valve_state[8];
+extern void mqtt_dispatch(char *type, char *who, char *what, char *msg);
+extern void logic_tick(void);
+extern void logic_slow_tick(void);
+extern void logic_dispatch(void);
+/* ds18b20.c */
+extern unsigned extract_leu16(uint8_t *d);
+extern int extract_les16(uint8_t *d);
+extern int ds18b20_read_sp(const Onewire_addr *a, uint8_t *buf, unsigned len);
+extern int ds18b20_write_sp(const Onewire_addr *a, uint8_t *buf, unsigned len);
+extern int ds18b20_convert(const Onewire_addr *a, unsigned timeout);
+extern int ds18b20_set_12(const Onewire_addr *a);
+extern int ds18b20_read(const Onewire_addr *a, int *temp);
+extern int ds18b20_set_12_convert(const Onewire_addr *a);
+extern int ds18b20_set_12_convert_and_read(const Onewire_addr *a, int *temp);
diff --git a/radiator-plc/stm32/app/radiator-plc.ld b/radiator-plc/stm32/app/radiator-plc.ld
new file mode 100644
index 0000000..0fb2378
--- /dev/null
+++ b/radiator-plc/stm32/app/radiator-plc.ld
@@ -0,0 +1,38 @@
+/*
+ * This file is part of the libopencm3 project.
+ *
+ * Copyright (C) 2009 Uwe Hermann <uwe@hermann-uwe.de>
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Linker script for Olimex STM32-H103 (STM32F103RBT6, 128K flash, 20K RAM). */
+
+/* Define memory regions. */
+MEMORY
+{
+ rom (rx) : ORIGIN = 0x08000000, LENGTH = 128K
+ ram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
+}
+
+/* Include the common ld script. */
+INCLUDE libopencm3_stm32f1.ld
+
+
+
+
+/* PROVIDE(_stack = dfu_shared_location ); */
+
+
+
diff --git a/radiator-plc/stm32/app/rain.c b/radiator-plc/stm32/app/rain.c
new file mode 100644
index 0000000..4188990
--- /dev/null
+++ b/radiator-plc/stm32/app/rain.c
@@ -0,0 +1,100 @@
+#include "project.h"
+
+#define DEBOUNCE 20
+
+static uint8_t r_state;
+static uint32_t r_ticks;
+static uint8_t r_current;
+static uint8_t r_filtered;
+static unsigned r_expiry;
+
+
+#define RAIN_BANK GPIOA
+#define RAIN_GPIO GPIO15
+
+static int
+raw_rain (void)
+{
+ return !gpio_get (RAIN_BANK, RAIN_GPIO);
+}
+
+
+void rain_notify (void)
+{
+ if (r_current)
+ vents_rain_notify();
+}
+
+
+void rain_ticker (void)
+{
+ unsigned s;
+ static int ticker = 0;
+
+
+ s = raw_rain();
+
+ if (s == r_state) {
+ if (r_ticks < DEBOUNCE)
+ r_ticks++;
+ else if (r_ticks == DEBOUNCE) {
+ r_current = s;
+ r_ticks++;
+ printf ("D: rain state now %d\r\n", r_current);
+ }
+ } else {
+ r_ticks = 0;
+ r_state = s;
+ }
+
+ if (r_current) {
+ r_filtered = 1;
+ r_expiry = 1800;
+ }
+
+ rain_notify();
+
+ ticker++;
+
+ if (ticker < 1000) return;
+
+ ticker = 0;
+
+ if (r_expiry)
+ r_expiry--;
+ else
+ r_filtered = r_current;
+
+}
+
+void rain_slow_ticker (void)
+{
+ printf ("R: %d %d\r\n", r_current, r_filtered);
+}
+
+
+int rain_filtered_state (void)
+{
+ return r_filtered;
+}
+
+int rain_state (void)
+{
+ return r_current;
+}
+
+char rain_state_char (void)
+{
+ return r_current ? 'Y' : 'N';
+}
+
+void
+rain_init (void)
+{
+ gpio_set_mode (RAIN_BANK, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN,
+ RAIN_GPIO);
+
+ gpio_set (RAIN_BANK, RAIN_GPIO);
+}
+
+
diff --git a/radiator-plc/stm32/app/ring.c b/radiator-plc/stm32/app/ring.c
new file mode 100644
index 0000000..c1d1803
--- /dev/null
+++ b/radiator-plc/stm32/app/ring.c
@@ -0,0 +1,69 @@
+#include "project.h"
+
+
+static inline size_t
+ring_next (ring_t *r, size_t p)
+{
+ p++;
+
+ if (p >= r->size)
+ p -= r->size;
+
+ return p;
+}
+
+void
+ring_init (ring_t *r, uint8_t *buf, size_t len)
+{
+ r->data = buf;
+ r->size = len;
+ r->write = 0;
+ r->read = 0;
+}
+
+int
+ring_write_byte (ring_t *r, uint8_t c)
+{
+ size_t n = ring_next (r, r->write);
+
+ if (n == r->read)
+ return -1;
+
+ r->data[r->write] = c;
+
+ r->write = n;
+
+ return 0;
+}
+
+
+int
+ring_read_byte (ring_t *r, uint8_t *c)
+{
+ size_t n = ring_next (r, r->read);
+
+ if (r->read == r->write)
+ return -1;
+
+ *c = r->data[r->read];
+ r->read = n;
+
+ return 0;
+}
+
+int
+ring_write (ring_t *r, uint8_t *buf, size_t len)
+{
+ while (len--) {
+ if (ring_write_byte (r, * (buf++)))
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+ring_empty (ring_t *r)
+{
+ return (r->read == r->write) ? 1 : 0;
+}
diff --git a/radiator-plc/stm32/app/ring.h b/radiator-plc/stm32/app/ring.h
new file mode 100644
index 0000000..1eae65b
--- /dev/null
+++ b/radiator-plc/stm32/app/ring.h
@@ -0,0 +1,6 @@
+typedef struct ring {
+ uint8_t *data;
+ size_t size;
+ size_t write;
+ size_t read;
+} ring_t;
diff --git a/radiator-plc/stm32/app/ticker.c b/radiator-plc/stm32/app/ticker.c
new file mode 100644
index 0000000..bb346fb
--- /dev/null
+++ b/radiator-plc/stm32/app/ticker.c
@@ -0,0 +1,97 @@
+#include "project.h"
+
+static volatile uint32_t delay_ms_count;
+volatile uint32_t ticks;
+static volatile int tick_ready;
+
+uint32_t clock_scale = 72;
+
+void
+delay_us (uint32_t d)
+{
+ d *= clock_scale;
+
+ while (d--)
+ __asm__ ("nop");
+}
+
+
+
+void
+sys_tick_handler (void)
+{
+ static unsigned slow = 0;
+ static unsigned medium = 0;
+
+ if (delay_ms_count)
+ delay_ms_count--;
+
+ ticks++;
+
+ if (!tick_ready)
+ return;
+
+ //buttons_ticker();
+ //
+ led_tick();
+ usart_ticker();
+
+
+ medium++;
+
+ if (medium < 1000) return;
+
+ medium = 0;
+
+ //target_tick();
+
+ slow++;
+
+ if (slow < 2) return;
+ logic_tick();
+
+
+}
+
+
+
+void
+delay_ms (uint32_t d)
+{
+ delay_ms_count = d;
+
+ while (delay_ms_count);
+}
+
+
+void
+ticker_init (void)
+{
+ uint32_t v, w;
+
+ /*Start periodic timer */
+ systick_set_clocksource (STK_CSR_CLKSOURCE_AHB_DIV8);
+ /* 72MHz / 8 = > 9Mhz */
+ systick_set_reload (9000);
+ /* 9MHz / 9000 => 1kHz */
+ systick_interrupt_enable();
+ systick_counter_enable();
+
+ do {
+ clock_scale--;
+ v = ticks;
+
+ while (v == ticks);
+
+ delay_us (1000);
+ w = ticks;
+ v++;
+ w -= v;
+ } while (w);
+
+
+ tick_ready = 1;
+
+ SCS_DEMCR |= SCS_DEMCR_TRCENA;
+ SCS_DWT_CTRL |= SCS_DWT_CTRL_CYCCNTENA;
+}
diff --git a/radiator-plc/stm32/app/usart.c b/radiator-plc/stm32/app/usart.c
new file mode 100644
index 0000000..52866b7
--- /dev/null
+++ b/radiator-plc/stm32/app/usart.c
@@ -0,0 +1,149 @@
+#include "project.h"
+
+#define BUFFER_SIZE 768
+
+ring_t rx1_ring;
+static uint8_t rx1_ring_buf[BUFFER_SIZE];
+
+ring_t tx1_ring;
+static uint8_t tx1_ring_buf[BUFFER_SIZE];
+
+//unsigned locked = 25000;
+unsigned locked = 0;
+
+
+
+
+
+void usart_ticker (void)
+{
+ if (locked) locked--;
+}
+
+
+
+static char unlock[] = "octopus banana";
+static int unlock_ptr = 0;
+
+
+void
+usart1_isr (void)
+{
+ uint8_t data;
+
+ /* Check if we were called because of RXNE. */
+ if (((USART_CR1 (USART1) & USART_CR1_RXNEIE) != 0) &&
+ ((USART_SR (USART1) & USART_SR_RXNE) != 0)) {
+
+ /* Retrieve the data from the peripheral. */
+ data = usart_recv (USART1);
+
+ if (locked) {
+ if (unlock[unlock_ptr] == data)
+ unlock_ptr++;
+ else
+ unlock_ptr = 0;
+
+ if (!unlock[unlock_ptr]) {
+ printf ("Unlocked!\r\n");
+ unlock_ptr = 0;
+ locked = 0;
+ }
+ }
+
+
+ if (!locked)
+ ring_write_byte (&rx1_ring, data);
+ }
+
+ /* Check if we were called because of TXE. */
+ if (((USART_CR1 (USART1) & USART_CR1_TXEIE) != 0) &&
+ ((USART_SR (USART1) & USART_SR_TXE) != 0)) {
+
+ if (ring_read_byte (&tx1_ring, &data)) {
+ /*No more data, Disable the TXE interrupt, it's no longer needed. */
+ USART_CR1 (USART1) &= ~USART_CR1_TXEIE;
+ } else
+ usart_send (USART1, data);
+ }
+
+}
+
+
+int
+_write (int file, char *ptr, int len)
+{
+ int ret = len;
+
+ if (file == 1) {
+ while (len--) {
+ if (*ptr == '\n')
+ ring_write_byte (&tx1_ring, '\r');
+
+ ring_write_byte (&tx1_ring, * (ptr++));
+ }
+
+ USART_CR1 (USART1) |= USART_CR1_TXEIE;
+ return ret;
+ }
+
+ errno = EIO;
+ return -1;
+}
+
+void
+usart1_queue (uint8_t d)
+{
+ ring_write_byte (&tx1_ring, d);
+ USART_CR1 (USART1) |= USART_CR1_TXEIE;
+}
+void
+usart1_queue_buf (void *buf, size_t len)
+{
+ ring_write (&tx1_ring, buf, len);
+ USART_CR1 (USART1) |= USART_CR1_TXEIE;
+}
+
+
+
+void
+usart1_drain (void)
+{
+ while (!ring_empty (&tx1_ring));
+}
+
+
+
+void
+usart_init (void)
+{
+ rcc_periph_clock_enable (RCC_USART1);
+
+ ring_init (&rx1_ring, rx1_ring_buf, sizeof (rx1_ring_buf));
+ ring_init (&tx1_ring, tx1_ring_buf, sizeof (tx1_ring_buf));
+
+
+ /* Enable the USART1,2 interrupt. */
+ nvic_enable_irq (NVIC_USART1_IRQ);
+
+ /* Map pins, and set usart2 to have pull ups */
+ gpio_set_mode (GPIOA, GPIO_MODE_OUTPUT_50_MHZ,
+ GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO_USART1_TX);
+ gpio_set_mode (GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT,
+ GPIO_USART1_RX);
+
+ /* Setup UART1 parameters. */
+ usart_set_baudrate (USART1, 38400);
+ usart_set_databits (USART1, 8);
+ usart_set_stopbits (USART1, USART_STOPBITS_1);
+ usart_set_parity (USART1, USART_PARITY_NONE);
+ usart_set_flow_control (USART1, USART_FLOWCONTROL_NONE);
+ usart_set_mode (USART1, USART_MODE_TX_RX);
+
+
+ /* Enable USART1 Receive interrupt. */
+ USART_CR1 (USART1) |= USART_CR1_RXNEIE;
+
+ /* Finally enable the USARTs. */
+ usart_enable (USART1);
+}
diff --git a/radiator-plc/stm32/app/watchdog.c b/radiator-plc/stm32/app/watchdog.c
new file mode 100644
index 0000000..8516119
--- /dev/null
+++ b/radiator-plc/stm32/app/watchdog.c
@@ -0,0 +1,16 @@
+#include "project.h"
+
+
+
+void
+watchdog_init (void)
+{
+ //iwdg_set_period_ms (10000);
+ //iwdg_start();
+}
+
+void
+sausages()
+{
+ iwdg_reset();
+}
diff --git a/radiator-plc/stm32/libopencm3 b/radiator-plc/stm32/libopencm3
new file mode 160000
+Subproject 5c73d601763dd140bff020be8d6d06f03c2bea7
diff --git a/run_ot_monitor b/run_ot_monitor
new file mode 100755
index 0000000..3916435
--- /dev/null
+++ b/run_ot_monitor
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+wish otmonitor/otmonitor.vfs/main.tcl -f ./.otmonitor.config
+
diff --git a/set_radiator_target b/set_radiator_target
new file mode 100755
index 0000000..85ef1f9
--- /dev/null
+++ b/set_radiator_target
@@ -0,0 +1,10 @@
+#!/bin/bash
+R=$1
+T=$2
+H=10.32.139.1
+
+mosquitto_pub -h $H -t cmnd/$R/var1 -m $T
+sleep 1
+mosquitto_pub -h $H -t cmnd/$R/var2 -m $[ T + 1 ]
+
+
diff --git a/stm32/app/pressure.c b/stm32/app/pressure.c
deleted file mode 100644
index 6b90d93..0000000
--- a/stm32/app/pressure.c
+++ /dev/null
@@ -1,79 +0,0 @@
-#include "project.h"
-
-#define PRESSURE GPIO0
-#define PRESSURE_PORT GPIOA
-#define PRESSURE_CHANNEL ADC_CHANNEL0
-#define VREF_CHANNEL ADC_CHANNEL17
-
-static unsigned poke;
-
-static int pressure;
-
-uint16_t pressure_ch (void)
-{
- return pressure;
-}
-
-
-
-
-void pressure_tick (void)
-{
- static unsigned ticker;
-
- ticker++;
-
- if (ticker < MS_TO_TICKS (2500))
- return;
-
- ticker = 0;
- poke = 1;
-
-}
-
-
-
-
-void pressure_dispatch (void)
-{
- int v, r;
-
- if (!poke) return;
-
- poke = 0;
-
- if (adc_calibrate()) {
- pressure = 0;
- return;
- }
-
- v = adc_convert (PRESSURE_CHANNEL);
- r = adc_convert (VREF_CHANNEL);
-
-
- /* r is 1.25 volts, transducer is 0.5V -> 0 psi 4.5V -> 100psi */
- /* 100psi is 6.8947573 bar, and we want 256ths of bar */
-
- if (!r) {
- pressure = 0;
- return;
- }
-
- pressure = ((v * 552) / r) - 221;
-
- if (pressure < 0) pressure = 0;
-
- printf ("QP: %d %d %d\r\n", v, r, (pressure * 100) / 256);
-
-}
-
-
-
-void pressure_init (void)
-{
- MAP_ANALOG (PRESSURE);
-}
-
-
-
-
diff --git a/tasmota-config/configure-radiators b/tasmota-config/configure-radiators
new file mode 100755
index 0000000..488e7e3
--- /dev/null
+++ b/tasmota-config/configure-radiators
@@ -0,0 +1,74 @@
+#!/bin/bash
+
+# Adapted from https://tasmota.github.io/docs/Rules/#simple-thermostat-example
+
+# Button give 30 mins of hot.
+
+# Low setpoint is var1
+# High setpoint is var2
+# Override is in var3
+# we store current relay state in var4
+# and use var5 for computing delta
+
+# Timer1 is used as a WDT incase sensor fails
+#
+
+M=10.32.139.1
+
+
+set -x
+
+for T in laundry_radiator ; do #kstudy_radiator bedroom_radiator spare_bedroom_radiator; do
+ mosquitto_pub -h "${M}" -t "cmnd/${T}/Backlog" -m "Switchmode1 3; Rule1 1; Rule1 4; Rule2 1; Rule2 4; Rule3 1; Rule3 4"
+ sleep 3
+ mosquitto_pub -h "${M}" -t "cmnd/${T}/Backlog" -m "TelePeriod 60; SetOption26 0; SetOption0 0; PowerOnState 0"
+ sleep 3
+ mosquitto_pub -h "${M}" -t "cmnd/${T}/Backlog" -m "Setoption36 0; Setoption65 1; Setoption1 1; SensorRetain 1; PowerRetain 1"
+ sleep 3
+
+##
+
+ read -r -d '' R << EOF
+ ON system#boot DO Backlog RuleTimer1 70; var1 5; var2 5; var3 0; var4 0 ENDON
+ ON Rules#Timer=1 DO Backlog RuleTimer1 70; Power %var3% ENDON
+ ON tele-SI7021#temperature DO Backlog RuleTimer1 70; Event temp=%value% ENDON
+ ON Switch1#State=0 DO Backlog var3 0; RuleTimer2 0; Event temp=100 ENDON
+ ON Switch1#State=1 DO Backlog var3 1; RuleTimer2 1800; Event temp=100 ENDON
+EOF
+ R="$(echo $R)"
+
+ mosquitto_pub -h "${M}" -t "cmnd/${T}/Rule1" -m "${R}"
+ sleep 5
+
+##
+
+ read -r -d '' R << EOF
+ ON Switch2#State=0 DO Backlog var3 0; RuleTimer2 0; Event temp=100 ENDON
+ ON Switch2#State=1 DO Backlog var3 1; RuleTimer2 1800; Event temp=100 ENDON
+ ON Rules#Timer=2 DO var3 0 ENDON
+ ON Power1#State DO BackLog Var4 %value%; Publish2 stat/${T}/OPEN %value% ENDON
+ ON Event#temp DO BackLog Publish2 stat/${T}/var1 %var1%; Publish2 stat/${T}/var2 %var2% ENDON
+EOF
+ R="$(echo $R)"
+
+ mosquitto_pub -h "${M}" -t "cmnd/${T}/Rule2" -m "${R}"
+ sleep 5
+
+##
+
+ read -r -d '' R << EOF
+ ON Event#temp<%var1% DO Power1 1 ENDON
+ ON Event#temp>%var2% DO Power1 %var3% ENDON
+ ON Event#temp<5 DO Power1 1 ENDON
+ ON Event#temp DO BackLog Var5 %var1%; Sub5 %value%; Publish2 stat/${T}/DELTA %var5%; Publish2 stat/${T}/OPEN %var4% ENDON
+EOF
+ R="$(echo $R)"
+
+ mosquitto_pub -h "${M}" -t "cmnd/${T}/Rule3" -m "${R}"
+ sleep 5
+
+##
+
+ mosquitto_pub -h "${M}" -t "cmnd/${T}/Restart" -m "1"
+
+done