Skip to content

Commit

Permalink
feat: manual latch state detection
Browse files Browse the repository at this point in the history
  • Loading branch information
portasynthinca3 committed Jun 15, 2024
1 parent 14bdba5 commit 267775b
Show file tree
Hide file tree
Showing 11 changed files with 215 additions and 20 deletions.
1 change: 0 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
cmake_minimum_required(VERSION 3.16)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(EXTRA_COMPONENT_DIRS esp-idf-lib/components)
project(undef_rfid)
idf_build_set_property(COMPILE_OPTIONS "-Wno-error=format"
"-fsanitize=undefined" "-fno-sanitize=shift-base" APPEND)
2 changes: 1 addition & 1 deletion main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
idf_component_register(
SRCS "led_task.c" "http.c" "pn532.c" "main.c" "auth.c"
SRCS "led_strip_encoder.c" "led_task.c" "latch.c" "http.c" "pn532.c" "main.c" "auth.c"
INCLUDE_DIRS "include/"
EMBED_TXTFILES "tg_api_root.pem")
2 changes: 1 addition & 1 deletion main/auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ void auth_task(void* _arg) {
http_message_t msg = {
.type = http_message_type_fail,
};
strcpy(msg.username, credential);
strcpy(msg.rejected_credential, credential);
xQueueSend(http_queue, &msg, portMAX_DELAY);

led_set_status(led_status_refused);
Expand Down
23 changes: 15 additions & 8 deletions main/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ static const char* refusal_emoji[] = {
"🤬", "😤", "🧐", "🤨", "🤛", "🖕",
};
#define REFUSAL_EMOJIS (sizeof(refusal_emoji) / sizeof(char*))
static const char* latch_emoji[] = {
"🔴", "🟢",
};

QueueHandle_t http_queue;

Expand All @@ -47,13 +50,15 @@ static esp_err_t _http_event_handler(esp_http_client_event_t *evt) {
return ESP_OK;
}

static esp_err_t _http_hass_log_entry(http_message_t* msg) {
static esp_err_t _http_hass_log(http_message_t* msg) {
// format JSON
char post_data[300];
if(msg->type == http_message_type_entry)
sprintf(post_data, "{\"name\": \"Open Ring 1\", \"message\": \"triggered by %s's card\", \"domain\": \"lock\", \"entity_id\": \"button.open_ring_1\"}", msg->username);
else
sprintf(post_data, "{\"name\": \"Open Ring 1\", \"message\": \"denied: %s\", \"domain\": \"lock\", \"entity_id\": \"button.open_ring_1\"}", msg->username); // contains the unauthorized credential
else if(msg->type == http_message_type_fail)
sprintf(post_data, "{\"name\": \"Open Ring 1\", \"message\": \"denied: %s\", \"domain\": \"lock\", \"entity_id\": \"button.open_ring_1\"}", msg->rejected_credential);
else if(msg->type == http_message_type_latch)
return ESP_OK;

// configure HTTP client
esp_http_client_config_t config = {
Expand All @@ -73,7 +78,7 @@ static esp_err_t _http_hass_log_entry(http_message_t* msg) {
return ESP_OK;
}

static esp_err_t _http_tg_log_entry(http_message_t* msg) {
static esp_err_t _http_tg_log(http_message_t* msg) {
// format path
char path[128];
sprintf(path, "/bot%s/sendMessage", TG_KEY);
Expand All @@ -83,9 +88,11 @@ static esp_err_t _http_tg_log_entry(http_message_t* msg) {
if(msg->type == http_message_type_entry) {
const char* emoji = entry_emoji[esp_random() % ENTRY_EMOJIS];
sprintf(text, "<a href=\"t.me/%s\">@%s</a> %s в спейс %s", msg->username, msg->username, gendered_verb_table[msg->gender], emoji);
} else {
} else if(msg->type == http_message_type_fail) {
const char* emoji = refusal_emoji[esp_random() % ENTRY_EMOJIS];
sprintf(text, "Попытка входа: %s %s", msg->username, emoji); // "username" contains the refused credential in this case
sprintf(text, "Попытка входа: %s %s", msg->rejected_credential, emoji);
} else if(msg->type == http_message_type_latch) {
sprintf(text, "%s Задвижка теперь <b>%s</b>", latch_emoji[msg->latch_open], msg->latch_open ? "открыта" : "закрыта");
}

// format query
Expand Down Expand Up @@ -122,12 +129,12 @@ void http_task(void* _arg) {
// send HTTP requests
for(int i = 0; i < HTTP_TRIES; i++) {
ESP_LOGD(TAG, "hass: attempt %d", i + 1);
if(_http_hass_log_entry(&msg) == ESP_OK)
if(_http_hass_log(&msg) == ESP_OK)
break;
}
for(int i = 0; i < HTTP_TRIES; i++) {
ESP_LOGD(TAG, "tg: attempt %d", i + 1);
if(_http_tg_log_entry(&msg) == ESP_OK)
if(_http_tg_log(&msg) == ESP_OK)
break;
}
}
Expand Down
8 changes: 8 additions & 0 deletions main/include/common.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
#pragma once

// https://hackaday.com/2019/09/11/lambdas-for-c-sort-of/
#define lambda(__lambda_ret, __lambda_args, __lambda_body)\
({\
__lambda_ret __lambda_anon __lambda_args \
__lambda_body; \
&__lambda_anon; \
})

#define EARLY_ERR_RETURN(x) do { \
esp_err_t err_rc_ = (x); \
if (unlikely(err_rc_ != ESP_OK)) { \
Expand Down
3 changes: 3 additions & 0 deletions main/include/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#define TZ "GMT-3"
#define HASS_SERVER "undef:8123"

#define IR_LED 3
#define IR_PHOTOTR 2
#define PN532_SCK 4
#define PN532_MISO 5
#define PN532_MOSI 6
Expand All @@ -18,5 +20,6 @@
#define REPL_LOGON_TIMEOUT (5LL * 60 * 1000 * 1000) // uS
#define NFC_REINIT_PERIOD (3600LL * 1000 * 1000) // uS
#define OPEN_DOOR_FOR (5000 / portTICK_PERIOD_MS)
#define LATCH_REJECT (500LL * 1000) // uS

#define HTTP_TRIES 3
13 changes: 10 additions & 3 deletions main/include/http.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@
#include <freertos/queue.h>
#include "auth.h"

#define HTTP_Q_SIZE 4
#define HTTP_Q_SIZE 8
extern QueueHandle_t http_queue;

typedef struct {
char username[32];
gender_t gender;
union {
struct {
char username[32];
gender_t gender;
};
char rejected_credential[32];
bool latch_open;
};
enum {
http_message_type_entry,
http_message_type_fail,
http_message_type_latch,
} type;
} http_message_t;

Expand Down
6 changes: 6 additions & 0 deletions main/include/latch.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>

#define LATCH_SQWAVE_FQ 10000 // Hz

void latch_init(void);
160 changes: 160 additions & 0 deletions main/latch.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// This is an absolutely over-engineered solution to a simple problem.
// Problem: we need to know if a mechanical latch is closed or not.
// Solution: make an infrared LED emit a square wave and make an infrared
// phototransistor receive that wave. The latch goes between the emitter and the
// receiver, blocking the signal if it's locked.
//
// Anyways, here's the required external circuitry:
//
// 3V3 >────────────────────────┬─────╮
// │ │
// 5V >──────────────╮ │ │
// │ │ ╭─┴─╮
// ╭─┴─╮ │ │ │ 330
// 100 │ │ │ │ │ ohm
// ohm │ │ │ ╰─┬─╯
// ╰─┬─╯ │ │
// │ │ │
// ╭─┴─╮ → ╷╱ ├────> GPIO
// ╲ ╱ → │ │ (IR_PHOTOTR)
// ╶─┼─╴ → ╵↘ │
// │ │ ╷/
// 1k │ ╰───┤
// ╭─────╮ ╷/ ╵↘
// GPIO >─┤ ├────┤ │
// (IR_LED) ╰─────╯ ╵↘ │
// │ │
// │ │
// GND ───────────────┴───────────────╯
//
// I spent a lot of time drawing this. I hope you appreciate it.

#include <driver/rmt_tx.h>
#include <driver/gpio.h>
#include <esp_timer.h>
#include <esp_log.h>

#include "common.h"
#include "config.h"
#include "latch.h"
#include "http.h"

#define TAG "latch"

static bool _latch_is_open = false;
static bool _latch_first_notif = true;
static void _latch_state_stable(void* arg) {
ESP_DRAM_LOGD(TAG, "open: %d%s", _latch_is_open, _latch_first_notif ? " (ignored)" : "");
if(!_latch_first_notif) {
http_message_t message = {
.type = http_message_type_latch,
.latch_open = _latch_is_open,
};
xQueueSend(http_queue, &message, 0);
}
_latch_first_notif = false;
}

static esp_timer_handle_t _latch_filter_timer;
static void _latch_state_prefilter(void) {
esp_timer_stop(_latch_filter_timer);
esp_timer_start_once(_latch_filter_timer, 500000LL); // 500ms
}

static esp_timer_handle_t _latch_sqwave_timer;
static void _latch_feed_close_timer(void) {
esp_timer_stop(_latch_sqwave_timer);
esp_timer_start_once(_latch_sqwave_timer, 10000000LL / LATCH_SQWAVE_FQ); // 10x tolerance
// such a high tolerance is needed due to high-prio wi-fi shenanigans :/
}

static void _latch_rx_isr(void* arg) {
// if we don't get another pulse after this one, the path is blocked
_latch_feed_close_timer();

// we have gotten a pulse, so the path is not blocked
if(!_latch_is_open) {
_latch_is_open = true;
_latch_state_prefilter();
}
}

void latch_init(void) {
// configure transmitter
rmt_channel_handle_t tx_channel = NULL;
rmt_tx_channel_config_t tx_config = {
.gpio_num = IR_LED,
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = 1000000,
.mem_block_symbols = 48,
.trans_queue_depth = 1,
.flags.invert_out = false,
.flags.with_dma = false,
};
rmt_carrier_config_t carrier_cfg = {
.duty_cycle = 0.5,
.frequency_hz = LATCH_SQWAVE_FQ,
.flags.polarity_active_low = false,
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_config, &tx_channel));
ESP_ERROR_CHECK(rmt_apply_carrier(tx_channel, &carrier_cfg));
ESP_ERROR_CHECK(rmt_enable(tx_channel));

// transmit ones in a hardware loop, basically making the hardware emit a
// steady square wave
rmt_encoder_handle_t encoder;
rmt_copy_encoder_config_t encoder_config = { };
rmt_transmit_config_t tx_trans_config = {
.loop_count = -1,
.flags.eot_level = 0,
.flags.queue_nonblocking = true,
};
rmt_symbol_word_t one = {
.level0 = 1,
.duration0 = 10000, // the value here really doesn't matter
.level1 = 1,
.duration1 = 10000,
};
ESP_ERROR_CHECK(rmt_new_copy_encoder(&encoder_config, &encoder));
ESP_ERROR_CHECK(rmt_transmit(tx_channel, encoder, &one, sizeof(one), &tx_trans_config));

// Configure rx pin. I couldn't figure out a way to use the same RMT
// peripheral to detect the square wave that we're sending, so I'm manually
// processing GPIO interrupts.
// The way the detection works is that there's a timer whose timeout is set
// to 10x of the total pulse period. That timer gets reset every time a
// pulse is received, signaling that the light pathway is clear and thus the
// latch is open. If the timer does, however, fire, that means that the
// pathway is obstructed, meaning the latch is closed.
gpio_config_t rx_config = {
.pin_bit_mask = 1 << IR_PHOTOTR,
.intr_type = GPIO_INTR_POSEDGE,
.pull_down_en = 0,
.pull_up_en = 0,
.mode = GPIO_MODE_INPUT,
};
ESP_ERROR_CHECK(gpio_config(&rx_config));
ESP_ERROR_CHECK(gpio_install_isr_service(0));
ESP_ERROR_CHECK(gpio_isr_handler_add(IR_PHOTOTR, _latch_rx_isr, NULL));
ESP_ERROR_CHECK(gpio_intr_enable(IR_PHOTOTR));

// initialize timers
esp_timer_create_args_t sqwave_timer_cfg = {
.callback = lambda(void, (void* arg), {
_latch_is_open = false;
_latch_state_prefilter();
}),
.arg = NULL,
.dispatch_method = ESP_TIMER_TASK,
.name = "latch closed",
};
esp_timer_create_args_t filter_timer_cfg = {
.callback = _latch_state_stable,
.arg = NULL,
.dispatch_method = ESP_TIMER_TASK,
.name = "latch state filtering",
};
ESP_ERROR_CHECK(esp_timer_create(&sqwave_timer_cfg, &_latch_sqwave_timer));
ESP_ERROR_CHECK(esp_timer_create(&filter_timer_cfg, &_latch_filter_timer));
_latch_feed_close_timer();
}
15 changes: 10 additions & 5 deletions main/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "config.h"
#include "http.h"
#include "led_task.h"
#include "latch.h"

// declarations
void app_main(void);
Expand Down Expand Up @@ -154,20 +155,24 @@ void app_main(void) {
esp_sntp_config_t sntp_config = ESP_NETIF_SNTP_DEFAULT_CONFIG(NTP_SERVER);
esp_netif_sntp_init(&sntp_config);

// start tasks
// launch tasks
auth_init();
http_init();
led_init();
xTaskCreate(&nfc_task, "nfc", 2048, NULL, 4, NULL);
xTaskCreate(&auth_task, "auth", 2048, NULL, 4, NULL);
latch_init();
xTaskCreate(&nfc_task, "nfc", 2048, NULL, 10, NULL);
xTaskCreate(&auth_task, "auth", 2048, NULL, 9, NULL);
xTaskCreate(&http_task, "http", 4096, NULL, 4, NULL);
xTaskCreate(&led_task, "led", 2048, NULL, 4, NULL);
xTaskCreate(&led_task, "led", 4096, NULL, 8, NULL);

// print RSSI every minute
// print RSSI and memory stats every minute
while(1) {
wifi_ap_record_t ap;
esp_wifi_sta_get_ap_info(&ap);
ESP_LOGD(TAG, "RSSI: %d dBm", ap.rssi);

heap_caps_print_heap_info(MALLOC_CAP_DEFAULT);

vTaskDelay(60000 / portTICK_PERIOD_MS);
}
}
2 changes: 1 addition & 1 deletion sdkconfig
Original file line number Diff line number Diff line change
Expand Up @@ -932,7 +932,7 @@ CONFIG_ESP_IPC_TASK_STACK_SIZE=1024
#
# High resolution timer (esp_timer)
#
# CONFIG_ESP_TIMER_PROFILING is not set
CONFIG_ESP_TIMER_PROFILING=y
CONFIG_ESP_TIME_FUNCS_USE_RTC_TIMER=y
CONFIG_ESP_TIME_FUNCS_USE_ESP_TIMER=y
CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584
Expand Down

0 comments on commit 267775b

Please sign in to comment.