application: commit dachtuer application

This commit is contained in:
King Kévin 2020-03-06 11:11:53 +01:00
parent aaeed65c18
commit 7d425f6cfb
1 changed files with 360 additions and 6 deletions

View File

@ -12,7 +12,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** STM32F1 application example
/** dachboden front panel access control
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @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
}