Skip to content
Snippets Groups Projects
door.c 7.15 KiB
#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(" "));
}