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