From 7d425f6cfb51a573a71a5c7295e25c38131274fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?King=20K=C3=A9vin?= Date: Fri, 6 Mar 2020 11:11:53 +0100 Subject: [PATCH] application: commit dachtuer application --- application.c | 366 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 360 insertions(+), 6 deletions(-) diff --git a/application.c b/application.c index 9662262..dc1dddf 100644 --- a/application.c +++ b/application.c @@ -12,7 +12,7 @@ * along with this program. If not, see . * */ -/** STM32F1 application example +/** dachboden front panel access control * @file * @author King Kévin * @date 2016-2020 @@ -76,6 +76,48 @@ static time_t time_start = 0; volatile bool rtc_internal_tick_flag = false; /**< flag set when internal RTC ticked */ /** @} */ +/** GPIO pin connected to relay, used to control button connection to panel */ +#define RELAY_PANEL_PIN PB6 +/** GPIO pin connected to relay, used to simulate button press */ +#define RELAY_BUTTON_PIN PB7 +/** GPIO for button 1 */ +#define BUTTON1_PIN PB8 +/** GPIO for button 2 */ +#define BUTTON2_PIN PB9 + +/** which button has been pressed */ +volatile uint8_t button_pressed = 0; + +/** if we apply the opening policy */ +bool opening_apply = false; + +static struct opening_settings_t { + uint8_t days; /**< which days of the week it door access applies (bit 7 = Monday) */ + uint16_t start_time; /**< at which minutes of the day to start */ + uint16_t stop_time; /**< at which minutes of the day to stop */ + uint8_t button_pattern[10]; /**< sequence of buttons to press to open the door */ +} opening_settings; + +/** save current opening_settings into SRAM */ +static void save_opening_settings(void) +{ + BKP_DR1 = 0; // invalid saved settings + BKP_DR2 = opening_settings.days & 0x7f; + BKP_DR3 = opening_settings.start_time; + BKP_DR4 = opening_settings.stop_time; + BKP_DR5 = opening_settings.button_pattern[0]; + BKP_DR6 = opening_settings.button_pattern[1]; + BKP_DR7 = opening_settings.button_pattern[2]; + BKP_DR8 = opening_settings.button_pattern[3]; + BKP_DR9 = opening_settings.button_pattern[4]; + BKP_DR10 = opening_settings.button_pattern[5]; + BKP_DR11 = opening_settings.button_pattern[6]; + BKP_DR12 = opening_settings.button_pattern[7]; + BKP_DR13 = opening_settings.button_pattern[8]; + BKP_DR14 = opening_settings.button_pattern[9]; + BKP_DR1 = 0x4223; //validate saved setting +} + size_t putc(char c) { size_t length = 0; // number of characters printed @@ -135,6 +177,148 @@ static void command_bootloader_dfu(void* argument); */ static void command_bootloader_embedded(void* argument); +/** show/set on which days the access policy applies + * @param[in] argument 7x0/1 to enable day of the week, starting with Monday (optional) + */ +static void command_days(void* argument) +{ + const char* days = (char*)argument; // argument is optional days + if (NULL != argument) { // days are provided, parse and save them + bool valid = (7 == strlen(days)); // verify input string + for (uint8_t day = 0; day < 7 && valid; day++) { + if (days[day] != '0' && days[day] != '1') { + valid = false; + } + } + if (valid) { // save provided settings + // parse new days + opening_settings.days = 0; + for (uint8_t day = 0; day < 7; day++) { + if ('1' == days[day]) { + opening_settings.days |= (1 << (6 - day)); + } + } + save_opening_settings(); // save days + puts("days saved\n"); + } else { + puts("provide exactly 7 times 0 (off) or 1 (on). 1st digit for Monday, 7th digit for Sunday\n"); + } + } + // display current days + printf("opening days: %07b\n", opening_settings.days); + const char* day_names[] = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}; + for (uint8_t day = 0; day < LENGTH(day_names); day++) { + printf("- %s: %s\n", day_names[day], (opening_settings.days & (1 << (6 - day))) ? "on" : "off"); + } +} + +/** show/set on which time the access policy starts applying + * @param[in] argument string with time of day, optional + */ +static void command_start(void* argument) +{ + const char* time = (char*)argument; // argument is optional time + if (NULL != argument) { // days are provided, parse and save them + bool valid = (5 == strlen(time)); // verify input string + if (!(valid && isdigit((int8_t)time[0]) && isdigit((int8_t)time[1]) && ':' == time[2] && isdigit((int8_t)time[3]) && isdigit((int8_t)time[4]))) { + valid = false; + } + if (valid) { // save provided settings + opening_settings.start_time = 0; + opening_settings.start_time += (time[4] - '0') * 1; + opening_settings.start_time += (time[3] - '0') * 10; + opening_settings.start_time += (time[1] - '0') * 60; + opening_settings.start_time += (time[0] - '0') * 600; + save_opening_settings(); // save days + puts("start time saved\n"); + } else { + puts("provide time in HH:MM format\n"); + } + } + printf("start time: %02u:%02u\n", opening_settings.start_time / 60, opening_settings.start_time % 60); +} + +/** show/set on which time the access policy stops applying + * @param[in] argument string with time of day, optional + */ +static void command_stop(void* argument) +{ + const char* time = (char*)argument; // argument is optional time + if (NULL != argument) { // days are provided, parse and save them + bool valid = (5 == strlen(time)); // verify input string + if (!(valid && isdigit((int8_t)time[0]) && isdigit((int8_t)time[1]) && ':' == time[2] && isdigit((int8_t)time[3]) && isdigit((int8_t)time[4]))) { + valid = false; + } + if (valid) { // save provided settings + opening_settings.stop_time = 0; + opening_settings.stop_time += (time[4] - '0') * 1; + opening_settings.stop_time += (time[3] - '0') * 10; + opening_settings.stop_time += (time[1] - '0') * 60; + opening_settings.stop_time += (time[0] - '0') * 600; + save_opening_settings(); // save days + puts("stop time saved\n"); + } else { + puts("provide time in HH:MM format\n"); + } + } + printf("stop time: %02u:%02u\n", opening_settings.stop_time / 60, opening_settings.stop_time % 60); +} + +/** open door by simulating button press + * @param[in] argument not used + */ +static void command_open(void* argument) +{ + (void)argument; // we won't use the argument + gpio_set(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set high to activate relay and take control over the button + gpio_set(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set high to activate relay an simulate button press + sleep_ms(1000); // hold button a bit + gpio_clear(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set low to deactivate relay and release button + if (!opening_apply) { + gpio_clear(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set low to deactivate relay and git control back to button + } + +} + +/** show/set button pattern + * @param[in] argument sequence of 1/2 + */ +static void command_pattern(void* argument) +{ + const char* pattern = (char*)argument; // argument is optional pattern + if (NULL != argument) { // pattern provided + bool valid = (LENGTH(opening_settings.button_pattern) >= strlen(pattern)); // verify input string + for (uint8_t i = 0; i < strlen(pattern) && valid; i++) { + if ('1' != pattern[i] && '2' != pattern[i]) { + valid = false; + } + } + if (valid) { // save provided settings + // reset pattern + for (uint8_t i = 0; i < LENGTH(opening_settings.button_pattern); i++) { + opening_settings.button_pattern[i] = 0; + } + // save new pattern + for (uint8_t i = 0; i < strlen(pattern); i++) { + opening_settings.button_pattern[i] = pattern[i] - '0'; + } + save_opening_settings(); // save days + puts("button sequence saved\n"); + } else { + printf("provide buttons sequence of up to %u 1 or 2\n", LENGTH(opening_settings.button_pattern)); + } + } + if (0 == opening_settings.button_pattern[0]) { + puts("no button sequence set\n"); + } else { + puts("button sequence: "); + for (uint8_t i = 0; i < LENGTH(opening_settings.button_pattern) && opening_settings.button_pattern[i]; i++) { + putc(opening_settings.button_pattern[i] + '0'); + } + putc('\n'); + } +} + /** list of all supported commands */ static const struct menu_command_t menu_commands[] = { { @@ -163,7 +347,7 @@ static const struct menu_command_t menu_commands[] = { }, #if RTC_DATE_TIME { - .shortcut = 'd', + .shortcut = 'D', .name = "date", .command_description = "show/set date and time", .argument = MENU_ARGUMENT_STRING, @@ -195,6 +379,46 @@ static const struct menu_command_t menu_commands[] = { .argument_description = NULL, .command_handler = &command_bootloader_embedded, }, + { + .shortcut = 'd', + .name = "days", + .command_description = "on which days to apply the access policy", + .argument = MENU_ARGUMENT_STRING, + .argument_description = "[0001000, 0/1 for Monday to Sunday]", + .command_handler = &command_days, + }, + { + .shortcut = 's', + .name = "start", + .command_description = "on which time to start the access policy", + .argument = MENU_ARGUMENT_STRING, + .argument_description = "[HH:MM]", + .command_handler = &command_start, + }, + { + .shortcut = 'S', + .name = "stop", + .command_description = "on which time to stop the access policy", + .argument = MENU_ARGUMENT_STRING, + .argument_description = "[HH:MM]", + .command_handler = &command_stop, + }, + { + .shortcut = 'o', + .name = "open", + .command_description = "open door", + .argument = MENU_ARGUMENT_NONE, + .argument_description = NULL, + .command_handler = &command_open, + }, + { + .shortcut = 'p', + .name = "password", + .command_description = "set/show password button sequence", + .argument = MENU_ARGUMENT_STRING, + .argument_description = "[sequence of 1/2]", + .command_handler = &command_pattern, + }, }; static void command_help(void* argument) @@ -490,6 +714,9 @@ static void command_reset(void* argument) static void command_bootloader_dfu(void* argument) { (void)argument; // we won't use the argument + // disable relays + gpio_clear(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); + gpio_clear(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set DFU magic to specific RAM location __dfu_magic[0] = 'D'; __dfu_magic[1] = 'F'; @@ -502,6 +729,9 @@ static void command_bootloader_dfu(void* argument) static void command_bootloader_embedded(void* argument) { (void)argument; // we won't use the argument + // disable relays + gpio_clear(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); + gpio_clear(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set watchdog to exit system memory after some time iwdg_set_period_ms(25000); // set independent watchdog period (26214.4 ms if the max timeout) iwdg_start(); // start independent watchdog @@ -562,7 +792,7 @@ void main(void) uart_setup(); // setup USART (for printing) #endif usb_cdcacm_setup(); // setup USB CDC ACM (for printing) - puts("\nwelcome to the CuVoodoo STM32F1 example application\n"); // print welcome message + puts("\nwelcome to the dachboden door panel\n"); // print welcome message #if DEBUG // show reset cause @@ -611,6 +841,62 @@ void main(void) time_start = rtc_get_counter_val(); // get start time from internal RTC puts("OK\n"); + // setup relays + puts("setup relays: "); + rcc_periph_clock_enable(GPIO_RCC(RELAY_PANEL_PIN)); // enable clock for GPIO domain + gpio_clear(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set low to leave per default + gpio_set_mode(GPIO_PORT(RELAY_PANEL_PIN), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(RELAY_PANEL_PIN)); // set as output to control the transistor controlling the relay + rcc_periph_clock_enable(GPIO_RCC(RELAY_BUTTON_PIN)); // enable clock for GPIO domain + gpio_clear(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set low to leave per default + gpio_set_mode(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(RELAY_BUTTON_PIN)); // set as output to control the transistor controlling the relay + puts("OK\n"); + + // setup buttons + puts("setup buttons: "); + rcc_periph_clock_enable(RCC_AFIO); // enable alternate function clock for external interrupt + rcc_periph_clock_enable(GPIO_RCC(BUTTON1_PIN)); // enable clock for button + gpio_set(GPIO_PORT(BUTTON1_PIN), GPIO_PIN(BUTTON1_PIN)); // pull up to be able to detect button push (go low) + gpio_set_mode(GPIO_PORT(BUTTON1_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN(BUTTON1_PIN)); // set button pin to input + exti_select_source(GPIO_EXTI(BUTTON1_PIN), GPIO_PORT(BUTTON1_PIN)); // mask external interrupt of this pin only for this port + exti_set_trigger(GPIO_EXTI(BUTTON1_PIN), EXTI_TRIGGER_RISING); // trigger when button is released + exti_enable_request(GPIO_EXTI(BUTTON1_PIN)); // enable external interrupt + nvic_enable_irq(GPIO_NVIC_EXTI_IRQ(BUTTON1_PIN)); // enable interrupt + rcc_periph_clock_enable(GPIO_RCC(BUTTON2_PIN)); // enable clock for button + gpio_set(GPIO_PORT(BUTTON2_PIN), GPIO_PIN(BUTTON2_PIN)); // pull up to be able to detect button push (go low) + gpio_set_mode(GPIO_PORT(BUTTON2_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN(BUTTON2_PIN)); // set button pin to input + exti_select_source(GPIO_EXTI(BUTTON2_PIN), GPIO_PORT(BUTTON2_PIN)); // mask external interrupt of this pin only for this port + exti_set_trigger(GPIO_EXTI(BUTTON2_PIN), EXTI_TRIGGER_RISING); // trigger when button is released + exti_enable_request(GPIO_EXTI(BUTTON2_PIN)); // enable external interrupt + nvic_enable_irq(GPIO_NVIC_EXTI_IRQ(BUTTON2_PIN)); // enable interrupt + puts("OK\n"); + + // read opening settings from SRAM + puts("reading access settings: "); + RCC_APB1ENR |= (RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN); // enable power + PWR_CR |= PWR_CR_DBP; // enable access + if (0x4223 == BKP_DR1) { // the magic header is present + opening_settings.days = BKP_DR2 & 0x7f; + opening_settings.start_time = BKP_DR3; + opening_settings.stop_time = BKP_DR4; + opening_settings.button_pattern[0] = BKP_DR5; + opening_settings.button_pattern[1] = BKP_DR6; + opening_settings.button_pattern[2] = BKP_DR7; + opening_settings.button_pattern[3] = BKP_DR8; + opening_settings.button_pattern[4] = BKP_DR9; + opening_settings.button_pattern[5] = BKP_DR10; + opening_settings.button_pattern[6] = BKP_DR11; + opening_settings.button_pattern[7] = BKP_DR12; + opening_settings.button_pattern[8] = BKP_DR13; + opening_settings.button_pattern[9] = BKP_DR14; + puts("loaded\n"); + } else { // there are no settings saved + memset(&opening_settings, 0, sizeof(struct opening_settings_t)); // clear all values + puts("default\n"); + } + // figure out how many button need to be pressed + uint8_t pattern_length; + for (pattern_length = 0; pattern_length < LENGTH(opening_settings.button_pattern) && opening_settings.button_pattern[pattern_length]; pattern_length++); + // setup terminal terminal_prefix = ""; // set default prefix terminal_process = &process_command; // set central function to process commands @@ -619,6 +905,9 @@ void main(void) // start main loop bool action = false; // if an action has been performed don't go to sleep button_flag = false; // reset button flag + uint32_t last_button_action = 0; // the last time a button has been pressed + uint8_t button_pattern[LENGTH(opening_settings.button_pattern)]; // to store the input button pattern + uint8_t button_input = 0; // how many buttons have been pressed while (true) { // infinite loop iwdg_reset(); // kick the dog if (user_input_available) { // user input is available @@ -627,19 +916,71 @@ void main(void) char c = user_input_get(); // store receive character terminal_send(c); // send received character to terminal } - if (button_flag) { // user pressed button + if (button_flag || button_pressed) { // user pressed button action = true; // action has been performed - puts("button pressed\n"); + printf("button released: %u\n", button_pressed); led_toggle(); // toggle LED + if (pattern_length > 0 && opening_apply) { // only check pattern if there is one to compare to + // store button + if (button_input < LENGTH(button_pattern)) { + button_pattern[button_input++] = button_pressed; + last_button_action = rtc_get_counter_val(); // remember last button action + } + // compare pattern + if (button_input >= pattern_length) { + bool pattern_valid = true; + for (uint8_t i = 0; i < pattern_length; i++) { + if (button_pattern[i] != opening_settings.button_pattern[i]) { + pattern_valid = false; + break; + } + } + // if the correct pattern has been input, press button + if (pattern_valid) { + puts("button sequence valid\n"); + gpio_set(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set high to activate relay an simulate button press + sleep_ms(1000); // hold button a bit + gpio_clear(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set low to deactivate relay and release button + } + button_input = 0; // restart from scratch + last_button_action = 0; // restart sequence + } + } else { // ignore all button entry when not within the opening hours + button_input = 0; + last_button_action = 0; + } sleep_ms(100); // wait a bit to remove noise and double trigger + button_pressed = 0; // reset button pressed button_flag = false; // reset flag } if (rtc_internal_tick_flag) { // the internal RTC ticked rtc_internal_tick_flag = false; // reset flag action = true; // action has been performed - if (0 == (rtc_get_counter_val() % RTC_TICKS_SECOND)) { // one seond has passed + if (0 == (rtc_get_counter_val() % RTC_TICKS_SECOND)) { // one second has passed led_toggle(); // toggle LED (good to indicate if main function is stuck) } + if (last_button_action && last_button_action + 5 * RTC_TICKS_SECOND <= rtc_get_counter_val()) { // pattern entry timeout + puts("button sequence entry timeout\n"); + last_button_action = 0; // reset last button time + button_input = 0; // reset pattern input + } + // always enforce the right state + gpio_clear(GPIO_PORT(RELAY_PANEL_PIN), GPIO_PIN(RELAY_PANEL_PIN)); // set low to not simulate button press + // verify if day matches + const time_t time_rtc = rtc_get_counter_val() / RTC_TICKS_SECOND + rtc_offset; // get time from internal RTC + const struct tm* time_tm = localtime(&time_rtc); // convert time + const uint16_t current_time = time_tm->tm_hour * 60 + time_tm->tm_min; // get time of day in minutes + const uint8_t day = 6 - ((time_tm->tm_wday + 6) % 7); // get bit for the current day of week + if (opening_settings.stop_time > opening_settings.start_time) { // stop time is on same day + opening_apply = ((opening_settings.days & (1 << day)) && current_time > opening_settings.start_time && current_time < opening_settings.stop_time); + } else { // stop time is on next day + opening_apply = ((opening_settings.days & (1 << day)) && current_time > opening_settings.start_time) || (opening_settings.days & (1 << (day + 1 % 7)) && current_time < opening_settings.stop_time); + } + if (opening_apply) { // we are in the opening hours + gpio_set(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set high to activate relay and disconnect button + } else { + gpio_clear(GPIO_PORT(RELAY_BUTTON_PIN), GPIO_PIN(RELAY_BUTTON_PIN)); // set high to release relay and connect button + } } if (action) { // go to sleep if nothing had to be done, else recheck for activity action = false; @@ -655,3 +996,16 @@ void rtc_isr(void) rtc_clear_flag(RTC_SEC); // clear flag rtc_internal_tick_flag = true; // notify to show new time } + +void GPIO_EXTI_ISR(BUTTON1_PIN)(void) // it's the same at BUTTON2_PIN: EXT9_5 +{ + if (exti_get_flag_status(GPIO_EXTI(BUTTON1_PIN))) { + exti_reset_request(GPIO_EXTI(BUTTON1_PIN)); // reset interrupt + button_pressed = 1; // remember which button has been released + } + if (exti_get_flag_status(GPIO_EXTI(BUTTON2_PIN))) { + exti_reset_request(GPIO_EXTI(BUTTON2_PIN)); // reset interrupt + button_pressed = 2; // remember which button has been released + } + button_flag = true; // perform button action +}