#include "door.h" #include "log.h" uint8_t door_read_pin_status(void); uint8_t door_update_status(uint8_t sensor_status); uint8_t is_alarm_status(uint8_t door_status); char status_unlocked[] PROGMEM = "Unlocked"; char status_locked[] PROGMEM = "Locked"; char status_open[] PROGMEM = "Open"; char status_alarm_cut[] PROGMEM = "Alarm wire open"; char status_alarm[] PROGMEM = "Alarm"; char status_unlocking[] PROGMEM = "Unlocking..."; char status_locking[] PROGMEM = "Locking..."; char* status_names[] PROGMEM = {status_unlocked, status_locked, status_open, status_alarm_cut, status_alarm, status_unlocking, status_locking}; volatile uint8_t door_status; volatile uint8_t sensor_candidate_status; volatile uint8_t sensor_stability; #define DOOR_PIN_NEEDED_STABILITY 4 #define DOOR_COMMAND_PULSE_TIME 35 //350ms volatile uint8_t door_command_pulse_time = 0; #define DOOR_COMMAND_WAIT_TIMEOUT 1000 //10s volatile uint16_t door_command_wait_timeout = 0; void door_init(void){ DOOR_PORT &= ~(COMMAND_UNLOCK_PIN | COMMAND_LOCK_PIN); //commnd pins off DOOR_DDR |= COMMAND_UNLOCK_PIN | COMMAND_LOCK_PIN; //and output DOOR_DDR &= ~DOOR_PIN_MASK; //sensor pins to input DOOR_PORT |= DOOR_PIN_MASK; //and pullup on PCMSK2 = DOOR_PIN_MASK; //enable pin change interrupts on DOOR STATUS pins PCICR = (1 << PCIE2); //enable pin change interrupt on PORTC (PCINT block 2) KEYMATIC_POWER_DDR |= KEYMATIC_POWER_LINE; door_status = door_read_pin_status(); sensor_candidate_status = door_status; sensor_stability = DOOR_PIN_NEEDED_STABILITY; } void door_tick(void){ if (door_command_pulse_time != 0) { door_command_pulse_time--; if (door_command_pulse_time == 0) DOOR_PORT &= (uint8_t)~(COMMAND_LOCK_PIN|COMMAND_UNLOCK_PIN); } if ( ((door_status & DOOR_STATUS_UNLOCKING) && !(door_status & DOOR_STATUS_UNLOCKED)) || ((door_status & DOOR_STATUS_LOCKING) && !(door_status & DOOR_STATUS_LOCKED)) ){ door_command_wait_timeout--; if (door_command_wait_timeout == 0){ printf_P(PSTR("Error: Door command timeout\n")); log_append(LOG_EVENT_DOOR_COMMAND_TIMEOUT, 0); //TODO: Retry (and turn keymatic power on if currently off) door_status &= (uint8_t)~(DOOR_STATUS_UNLOCKING | DOOR_STATUS_LOCKING); //clear activity flags } } uint8_t door_current_pin_status = door_read_pin_status(); if (door_current_pin_status == sensor_candidate_status){ if (sensor_stability == DOOR_PIN_NEEDED_STABILITY){ //Sensor pins are stable update the HW status bits in the status var door_status = door_update_status(sensor_candidate_status); } if (sensor_stability <= DOOR_PIN_NEEDED_STABILITY) sensor_stability++; } else { sensor_candidate_status = door_current_pin_status; sensor_stability = 0; } } uint8_t door_read_pin_status(void){ uint8_t status_pins; status_pins = DOOR_PIN & DOOR_PIN_MASK; status_pins ^= DOOR_PIN_FLIP; //flip bits for active low pins so that "bit set" always means active. return (status_pins >> DOOR_PIN_OFFSET); } uint8_t door_update_status(uint8_t sensor_status){ uint8_t new_door_status = (sensor_status & DOOR_STATUS_HW_MASK) | (door_status & DOOR_STATUS_SW_MASK); if (new_door_status & DOOR_STATUS_LOCKED) new_door_status &= (uint8_t)~DOOR_STATUS_LOCKING; if (new_door_status & DOOR_STATUS_UNLOCKED) new_door_status &= (uint8_t)~DOOR_STATUS_UNLOCKING; if (is_alarm_status(new_door_status)) new_door_status |= DOOR_STATUS_ALARM; if (new_door_status & DOOR_STATUS_ALARM){ log_append( (door_status & DOOR_STATUS_ALARM) ? LOG_EVENT_ALARM_CHANGED : LOG_EVENT_ALARM_RAISED, 0); } return new_door_status; } uint8_t is_alarm_status(uint8_t door_status){ if (door_status & DOOR_STATUS_ALARM_CUT) return 1; //NOTE: This condition is supposed to trigger, if the door is slammed open. // We can't use the LOCKED sensor, because the connection between lock bolt and frame will be interrupted by this. // This connection is what the sensor actually detects. TODO: Can we improove this? // The UNLOCKED sensor, however will be uneffected, leaving the lock sensors in an // undefined (neither locked nor unlocked) state. We trigger an alarm if this occurs when the door is open. // Unfortunately the UNLOCKED has shown failures in the past, possibly causing a false alarm. if ((door_status & DOOR_STATUS_OPEN) && !(door_status & DOOR_STATUS_UNLOCKED)) return 1; //An undefined state (neither locked nor unlocked) may only occur if we have issued a LOCKING or UNLOCKING command if ((door_status & (DOOR_STATUS_LOCKED | DOOR_STATUS_UNLOCKED | DOOR_STATUS_UNLOCKING | DOOR_STATUS_LOCKING)) == 0) return 1; return 0; } uint8_t door_get_status(void){ return door_status; } void unlock(void){ door_command_pulse_time = DOOR_COMMAND_PULSE_TIME; door_command_wait_timeout = DOOR_COMMAND_WAIT_TIMEOUT; door_status = (door_status & (uint8_t)~DOOR_STATUS_LOCKING) | DOOR_STATUS_UNLOCKING; DOOR_PORT = (DOOR_PORT & (uint8_t)~COMMAND_LOCK_PIN) | COMMAND_UNLOCK_PIN; } void lock(void){ door_command_pulse_time = DOOR_COMMAND_PULSE_TIME; door_command_wait_timeout = DOOR_COMMAND_WAIT_TIMEOUT; door_status = (door_status & (uint8_t)~DOOR_STATUS_UNLOCKING) | DOOR_STATUS_LOCKING; DOOR_PORT = (DOOR_PORT & (uint8_t)~COMMAND_UNLOCK_PIN) | COMMAND_LOCK_PIN; } uint8_t toggle_lock_unlock(void){ if ((door_status & DOOR_STATUS_UNLOCKED) == 0){ // if (door_status & DOOR_STATUS_LOCKED){ return unlock_checked(); } else { return lock_checked(); } } uint8_t unlock_checked(void){ if (door_status & DOOR_STATUS_UNLOCKED){ printf_P(PSTR("Already unlocked\n")); } else if (door_status & DOOR_STATUS_LOCKING){ printf_P(PSTR("Can't unlock while locking in progress\n")); } else { unlock(); return 1; } return 0; } uint8_t lock_checked(void){ if (door_status & DOOR_STATUS_ALARM){ printf_P(PSTR("Can't lock in alarm state\n")); } else if (door_status & DOOR_STATUS_UNLOCKING) { printf_P(PSTR("Can't lock while unlocking in progress\n")); } else if (door_status & DOOR_STATUS_OPEN){ printf_P(PSTR("Can't lock while door open\n")); } else if (door_status & DOOR_STATUS_LOCKED){ printf_P(PSTR("Already locked\n")); } else { lock(); return 1; } return 0; } uint8_t door_clear_alarm(void){ if (is_alarm_status(door_status)) return 0; log_append(LOG_EVENT_ALARM_DISABLED, 0); door_status &= (uint8_t)~DOOR_STATUS_ALARM; return 1; } void door_set_keymatic_charge_power(uint8_t on){ printf_P(PSTR("Keymatic charge voltage turned "), on); if (on) { printf_P(PSTR("on\n")); KEYMATIC_POWER_PORT |= KEYMATIC_POWER_LINE; } else { printf_P(PSTR("off\n")); KEYMATIC_POWER_PORT &= ~KEYMATIC_POWER_LINE; } } void door_write_status(void){ uint8_t status = door_status; //copy to avoid race with interrupt printf_P(PSTR("Status (%02X): "), status); uint8_t c = 0; for (uint8_t i = 0; i <= DOOR_MAX_STATUS_FLAG; i++){ if (status & (1 << i)){ if (c) printf_P(PSTR(",")); c++; printf_P((PGM_P)pgm_read_word(&(status_names[i]))); } } if (status == 0) printf_P(PSTR("Unknown")); // printf_P(PSTR(" ")); }