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
+}