summaryrefslogtreecommitdiffstats
path: root/boiler-monster/original-pic-4.2.5
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 /boiler-monster/original-pic-4.2.5
parentdafd8cf2fdcdd637cc06f760d318cf8391b1a294 (diff)
downloadheating-9d87c925a9eaa4fc256be3173c14a20d1469472d.tar.gz
heating-9d87c925a9eaa4fc256be3173c14a20d1469472d.tar.bz2
heating-9d87c925a9eaa4fc256be3173c14a20d1469472d.zip
everything, mostly, working
Diffstat (limited to 'boiler-monster/original-pic-4.2.5')
-rw-r--r--boiler-monster/original-pic-4.2.5/.gitignore6
-rw-r--r--boiler-monster/original-pic-4.2.5/16f88.lkr76
-rw-r--r--boiler-monster/original-pic-4.2.5/Makefile22
-rw-r--r--boiler-monster/original-pic-4.2.5/build.asm2
-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.asm391
12 files changed, 5397 insertions, 0 deletions
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/boiler-monster/original-pic-4.2.5/16f88.lkr b/boiler-monster/original-pic-4.2.5/16f88.lkr
new file mode 100644
index 0000000..b2b3e95
--- /dev/null
+++ b/boiler-monster/original-pic-4.2.5/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/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/boiler-monster/original-pic-4.2.5/build.asm b/boiler-monster/original-pic-4.2.5/build.asm
new file mode 100644
index 0000000..67d0747
--- /dev/null
+++ b/boiler-monster/original-pic-4.2.5/build.asm
@@ -0,0 +1,2 @@
+#define build "1"
+#define tstamp "17:59 20-10-2015"
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/boiler-monster/original-pic-4.2.5/selfprog.asm b/boiler-monster/original-pic-4.2.5/selfprog.asm
new file mode 100644
index 0000000..9e18769
--- /dev/null
+++ b/boiler-monster/original-pic-4.2.5/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