; ***************************************************************************
; copyright   : (C) 2023 by Martin Preuss
; email       : martin@libchipcard.de
;
; ***************************************************************************
; * This file is part of the project "AqHome".                              *
; * Please see toplevel file COPYING of that project for license details.   *
; ***************************************************************************


; This module monitors up to two reed contacts to determine whether a windows
; door is open.
;
; It has two modes.
;
; Standard Mode
; -------------
;
; In this mode up to two windows can be monitored. Each reed contact is reported on separately.
; If a window/door is open a value of 255 is reported, 0 otherwise.

; Tilt Detect Mode
; ----------------
;
; In this mode both reed sensors are handled together to determine whether a window
; or door is closed, tilted or fully open.
; - if exactly one of two reed contacts is open the window/door is reported as tilted (value 128)
; - if both reed contacts are open the window/door is reported as fully open (value 255)
; - if both reed contacts are closed the window/door is reported as closed (value 0)
;
;
; The window state is reported via the VALUE message with a value id of VALUE_ID_REED1 for the
; first reed contact (or for the whole window/door if in tilt detection mode), for the second
; reed contact a value id of VALUE_ID_REED2 is used. Value type is always AQHOME_VALUETYPE_DOOR
; (i.e. for doors and windows).
;
; There are basically two types of reed contacts.
; - normally closed (NC): The contact is closed as long as the magnet is near the switch. 
;                         If the magnet is removed the contact opens.
;                         This is the default working mode of this module.
; - normally open (NO):   The contact is open as long as the magnet is near the switch.
;                         If the magnet is removed the contact closes.
; To account for those different types of working modes this mode is configurable.



; The state byte contains the state for two reed contacts (one in each nibble).
;
; bit  meaning
; ------------------------------
; 0    last measurement
; 1    measurement before last
; 2    change occurred
; 3    current stable state



; ***************************************************************************
; defs

.equ REED_STATE_MASK_CURRENT     = 0x8
.equ REED_STATE_MASK_CHANGED     = 0x4
.equ REED_STATE_MASK_BEFORELAST  = 0x2
.equ REED_STATE_MASK_LAST        = 0x1

.equ REED_STATE_MASK_LASTVALUES  = 0x3 ; bits 0 and 1
.equ REED_STATE_BIT_CURRENT      = 3
.equ REED_STATE_BIT_CHANGED      = 2


.equ REED_CONFIG_MASK_SET        = 0x80
.equ REED_CONFIG_MASK_TILTDETECT = 0x40 ; use both reed contacts to detect tilted windows
.equ REED_CONFIG_MASK_NO         = 0x20 ; if set: normally open (e.g. disconnected when magnet near)

.equ REED_CONFIG_BIT_TILTDETECT  = 6
.equ REED_CONFIG_BIT_NO          = 5


.equ REED_INITIAL_CONFIG         = REED_CONFIG_MASK_SET


.equ REED_CFGMOD_CMD_SETCONFIG  = 1 ; only command for now






; ***************************************************************************
; data

.dseg

reedDataBegin:
  reedConfigFromEeprom: .byte 1
  reedState:            .byte 1
reedDataEnd:






; ***************************************************************************
; code

.cseg



; ---------------------------------------------------------------------------
; @routine REED_Init
;
; Init module.
;
; @return CFLAG set if okay, cleared on error
; @clobbers r16, r17, X

REED_Init:
  ; preset SRAM data area
  ldi xh, HIGH(reedDataBegin)
  ldi xl, LOW(reedDataBegin)
  clr r16
  ldi r17, (reedDataEnd-reedDataBegin)
  rcall Utils_FillSram                ; (r17, x)

  cbi REED1_PORT, REED1_PINNUM        ; disable internal pullup for DATA
  cbi REED1_DDR, REED1_PINNUM         ; set reed1 port as input

  cbi REED2_PORT, REED2_PINNUM        ; disable internal pullup for DATA
  cbi REED2_DDR, REED2_PINNUM         ; set reed2 port as input

  cbi REEDOUT1_PORT, REEDOUT1_PINNUM  ; set common reed port to low
  sbi REEDOUT1_DDR, REEDOUT1_PINNUM   ; set common reed output port as output

.ifdef REEDOUT2_DDR
  cbi REEDOUT2_PORT, REEDOUT2_PINNUM  ; set common reed port to low
  sbi REEDOUT2_DDR, REEDOUT2_PINNUM   ; set common reed output port as output
.endif

  rcall reedSetupConfig               ; initial setup from EEPROM, if possible

  ret



REED_Fini:
  sec
  ret



REED_Run:
;  clc ; debug
;  ret ; debug
  push r15
    in r15, SREG
    cli
    rcall reedHandleChanges
    out SREG, r15
  pop r15
  clc
  ret



; ---------------------------------------------------------------------------
; @routine REED_OnPacketReceived @global
;
; @clobbers any, -X

REED_OnPacketReceived:
  adiw xh:xl, 2                                ; command
  ld r16, X
  sbiw xh:xl, 2
  cpi r16, CPRO_CMD_VALUE_SET
  breq REED_OnPacketReceived_set
  clc                                          ; unexpected msg
  ret
REED_OnPacketReceived_set:
  rcall CPRO_ReadValue                         ; (none)
  cpi r17, VALUE_ID_REED_CONF
  breq REED_OnPacketReceived_setReedConf
  clc
  ret
REED_OnPacketReceived_setReedConf:
  mov r16, r18
  rcall REED_SetConfig
REED_OnPacketReceived_sendAck:
  ldi r16, CPRO_CMD_VALUE_SET_ACK
  rcall CPRO_SendSetValueResponse
  sec
  ret
; @end



REED_Every100ms:
  push r15
    in r15, SREG
    cli
    rcall reedUpdateState ; (r16, r17, r18, r19)
    out SREG, r15
  pop r15
  ret



; ---------------------------------------------------------------------------
; @routine REED_SetConfig
;
; Set config and write to EEPROM
;
; @return   nothing
; @param    r16 new configuration
; @clobbers r16, X, (R17)

REED_SetConfig:
  sts reedConfigFromEeprom, r16
  ldi xh, HIGH(EEPROM_OFFS_REED_CONF) ; write config to EEPROM
  ldi xl, LOW(EEPROM_OFFS_REED_CONF)  ;
  rcall Utils_WriteEepromIncr         ; (R17)
  ret
; @end



; ---------------------------------------------------------------------------
; @routine reedSetupConfig
;
; Read initial config from EEPROM or setup from new and write to EEPROM
;
; @clobbers r16, X

reedSetupConfig:
  ldi xh, HIGH(EEPROM_OFFS_REED_CONF) ; read initial config from EEPROM
  ldi xl, LOW(EEPROM_OFFS_REED_CONF)  ;
  rcall Utils_ReadEepromIncr          ; (r16)
  sbiw xh:xl, 1                       ; called routine increments X, undo
  tst r16
  breq reedSetupConfig_noConfig
  cpi r16, 0xff
  brne reedSetupConfig_haveConfig
reedSetupConfig_noConfig:
  ldi r16, REED_INITIAL_CONFIG
; disabled for now
; debug  rcall Utils_WriteEepromIncr
reedSetupConfig_haveConfig:
  sts reedConfigFromEeprom, r16
reedSetupConfig_end:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine reedHandleChanges
;
; @clobbers all

reedHandleChanges:
  lds r16, reedConfigFromEeprom
  sbrc r16, REED_CONFIG_BIT_TILTDETECT
    rjmp reedHandleChangesTiltDetectMode
  rjmp reedHandleChangesSingleMode
; @end



; ---------------------------------------------------------------------------
; @routine reedHandleChangesSingleMode
;
; @clobbers all

reedHandleChangesSingleMode:
  lds r19, reedState

  ; reed 1
  mov r16, r19
  ldi r17, VALUE_ID_REED1
  push r19
    rcall reedSendValueIfChanged
  pop r19
  andi r16, 0x0f
  andi r19, 0xf0
  or r19, r16

  ; reed 2
  mov r16, r19
  swap r16
  ldi r17, VALUE_ID_REED2
  push r19
    rcall reedSendValueIfChanged
  pop r19
  andi r16, 0x0f
  swap r16
  andi r19, 0x0f
  or r19, r16
  
  sts reedState, r19
  ret
; @end



; ---------------------------------------------------------------------------
; @routine reedHandleChangesTiltDetectMode
;
; Handle changes in multi reed contact mode. 
; This allows for detection of tilted windows/doors.
;
; @clobbers all

reedHandleChangesTiltDetectMode:
  lds r19, reedState
  
  mov r16, r19
  andi r16, (REED_STATE_MASK_CHANGED | (REED_STATE_MASK_CHANGED<<4))
  breq reedHandleChangesTiltDetectMode_end ; no changes
  ; at least one contact changed, handle new state
  clr r16
  sbrc r19, REED_STATE_BIT_CURRENT
    inc r16
  sbrc r19, (REED_STATE_BIT_CURRENT+4)
    inc r16
  ; no check for full state (0=closed, 1=tilted, 2=fully open)
  tst r16
  breq reedHandleChangesTiltDetectMode_closed
  cpi r16, 1
  breq reedHandleChangesTiltDetectMode_tilted
  ; otherwise fully open, fallthrough
  ldi r16, 255
  rjmp reedHandleChangesTiltDetectMode_sendChanges
reedHandleChangesTiltDetectMode_closed:
  ldi r16, 0
  rjmp reedHandleChangesTiltDetectMode_sendChanges
reedHandleChangesTiltDetectMode_tilted:
  ldi r16, 128
reedHandleChangesTiltDetectMode_sendChanges:
  ldi r17, VALUE_ID_REED1
  push r19
    rcall reedSendChange
  pop r19
  brcc reedHandleChangesTiltDetectMode_end
  ; changes handled, clear change flags and write back
  andi r19, ~(REED_STATE_MASK_CHANGED | (REED_STATE_MASK_CHANGED<<4))
  sts reedState, r19
reedHandleChangesTiltDetectMode_end:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine reedSendValueIfChanged
;
; @return r16 updated state
; @param r16: current state nibble
; @param r17: value id
; @clobbers r16, r19 (all others)

reedSendValueIfChanged:
  push r16
    mov r18, r16
    andi r16, REED_STATE_MASK_CHANGED
    breq reedSendValueIfChanged_l1     ; jmp if not changed
    ; value changed
    clr r16
    sbrc r18, REED_STATE_BIT_CURRENT
      dec r16                          ; if stable state=0 (meaning: closed)
    rcall reedSendChange
    brcc reedSendValueIfChanged_l1
  pop r16
  andi r16, ~REED_STATE_MASK_CHANGED   ; clear change bit
  ret
reedSendValueIfChanged_l1:
  pop r16
  ret
; @end



; ---------------------------------------------------------------------------
; @routine reedSendChange
;
; @param r16 value
; @param r17 value id
; @clobbers r16, r18, r19, r20, r21, r22, r23, X (R3, R4, R6, R7, R8, R9, R10, R11, R12, R15, R16, R17, R18, R19, R20, R21, R22, X, Y)

reedSendChange:
  lds r18, com2Address
  tst r18
  brne reedSendChange_haveAddress
  clc
  ret
reedSendChange_haveAddress:
  mov r18, r16  ; value to send in r19:r18
  clr r19
  ldi r16, 0xff ; dest addr: send to all
  ldi r20, 1    ; denominator in r21:r20
  clr r21
  ldi r22, AQHOME_VALUETYPE_DOOR
  ldi xl, LOW(com2SendBuffer)
  ldi xh, HIGH(com2SendBuffer)
  rcall CPRO_WriteReportValue   ; (R3, R4, R6, R7, R8, R9, R10, R11, R12, R15, R16, R17, R18, R19, R20, R21, X, Y)
  rjmp COM2_SendPacket    ; (r18, r19, r22, X)
; @end



; ---------------------------------------------------------------------------
; @routine reedUpdateState
;
; Updates SDRAM variable reedState according to current pin states.
;
; @clobbers r16, r17, r18, r19, r20

reedUpdateState:
  lds r19, reedState          ; current reed state
  lds r20, reedConfigFromEeprom

  ; handle reed 1 (low nibble state)
  rcall reedReadValue1          ; (r16)
  sbrc r20, REED_CONFIG_BIT_NO
    com r16                     ; invert reed data if "normally open" contact
  andi r16, 1
  mov r17, r16
  mov r16, r19
  rcall reedUpdateStateNibble   ; (r18)
  ; replace lower state nibble with new value
  andi r16, 0x0f                ; only keep low nibble
  andi r19, 0xf0                ; clear out low nibble before or'ing new value in
  or r19, r16

  ; handle reed2 (high nibble state)
  rcall reedReadValue2          ; (r16)
  sbrc r20, REED_CONFIG_BIT_NO
    com r16                     ; invert reed data if "normally open" contact
  andi r16, 1
  mov r17, r16
  mov r16, r19
  swap r16
  rcall reedUpdateStateNibble   ; (r18)
  swap r16
  ; replace higher state nibble with new value
  andi r16, 0xf0
  andi r19, 0x0f
  or r19, r16
  ; write back reed state
  sts reedState, r19
  ret
; @end



; ---------------------------------------------------------------------------
; @routine reedUpdateStateNibble
;
; @return r16 updated current state nibble
; @param r16 current state nibble
; @param r17 current pin status (o if clear, 1 if set)
; @clobbers r18

reedUpdateStateNibble:
  mov r18, r16
  andi r18, REED_STATE_MASK_LAST        ; clear everything but last measurement
  lsl r18                               ; shift previous measurement to left
  andi r17, 1
  or r18, r17                           ; or current measurement into value
  ; store new value in state nibble
  andi r16, ~REED_STATE_MASK_LASTVALUES ; clear pin states
  or r16, r18                           ; put updated measurements back into state nibble
  ; check for new stable state
  tst r18                               ; last measurements all zero?
  breq reedUpdateStateNibble_stable0
  cpi r18, 0x03                         ; last measurements all one?
  breq reedUpdateStateNibble_stable1
  ; not a stable state right now
  ret
reedUpdateStateNibble_stable0:
  sbrs r16, REED_STATE_BIT_CURRENT      ; new stable is 0, changed?
    ret                                 ; ret if no change
  ori r16, REED_STATE_MASK_CHANGED      ; flag change
  andi r16, ~REED_STATE_MASK_CURRENT    ; set new stable value to 0
  ret
reedUpdateStateNibble_stable1:
  sbrc r16, REED_STATE_BIT_CURRENT      ; new stable is 1, changed?
    ret                                 ; ret if no change
  ori r16, (REED_STATE_MASK_CHANGED | REED_STATE_MASK_CURRENT) ; flag change, set new stable value to 1
  ret
; @end



; ---------------------------------------------------------------------------
; @routine reedReadValue1
;
; @return r16 current pin state

reedReadValue1:
  clr r16
  clc
  sbic REED1_PIN, REED1_PINNUM
    sec
  adc r16, r16
  ret
; @end



; ---------------------------------------------------------------------------
; @routine reedReadValue2
;
; @return r16 current pin state

reedReadValue2:
  clr r16
  clc
  sbic REED2_PIN, REED2_PINNUM
    sec
  adc r16, r16
  ret
; @end



