unleashed-firmware/applications/plugins/dht_temp_sensor/quenon_dht_mon.c
2022-11-04 17:51:56 +03:00

462 lines
17 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "quenon_dht_mon.h"
//Порты ввода/вывода, которые не были обозначены в общем списке
const GpioPin SWC_10 = {.pin = LL_GPIO_PIN_14, .port = GPIOA};
const GpioPin SIO_12 = {.pin = LL_GPIO_PIN_13, .port = GPIOA};
const GpioPin TX_13 = {.pin = LL_GPIO_PIN_6, .port = GPIOB};
const GpioPin RX_14 = {.pin = LL_GPIO_PIN_7, .port = GPIOB};
//Количество доступных портов ввода/вывода
#define GPIO_ITEMS (sizeof(gpio_item) / sizeof(GpioItem))
//Перечень достуных портов ввода/вывода
static const GpioItem gpio_item[] = {
{2, "2 (A7)", &gpio_ext_pa7},
{3, "3 (A6)", &gpio_ext_pa6},
{4, "4 (A4)", &gpio_ext_pa4},
{5, "5 (B3)", &gpio_ext_pb3},
{6, "6 (B2)", &gpio_ext_pb2},
{7, "7 (C3)", &gpio_ext_pc3},
{10, " 10(SWC) ", &SWC_10},
{12, "12 (SIO)", &SIO_12},
{13, "13 (TX)", &TX_13},
{14, "14 (RX)", &RX_14},
{15, "15 (C1)", &gpio_ext_pc1},
{16, "16 (C0)", &gpio_ext_pc0},
{17, "17 (1W)", &ibutton_gpio}};
//Данные плагина
static PluginData* app;
uint8_t DHTMon_GPIO_to_int(const GpioPin* gpio) {
if(gpio == NULL) return 255;
for(uint8_t i = 0; i < GPIO_ITEMS; i++) {
if(gpio_item[i].pin->pin == gpio->pin && gpio_item[i].pin->port == gpio->port) {
return gpio_item[i].num;
}
}
return 255;
}
const GpioPin* DHTMon_GPIO_form_int(uint8_t name) {
for(uint8_t i = 0; i < GPIO_ITEMS; i++) {
if(gpio_item[i].num == name) {
return gpio_item[i].pin;
}
}
return NULL;
}
const GpioPin* DHTMon_GPIO_from_index(uint8_t index) {
if(index > GPIO_ITEMS) return NULL;
return gpio_item[index].pin;
}
uint8_t DHTMon_GPIO_to_index(const GpioPin* gpio) {
if(gpio == NULL) return 255;
for(uint8_t i = 0; i < GPIO_ITEMS; i++) {
if(gpio_item[i].pin->pin == gpio->pin && gpio_item[i].pin->port == gpio->port) {
return i;
}
}
return 255;
}
const char* DHTMon_GPIO_getName(const GpioPin* gpio) {
if(gpio == NULL) return NULL;
for(uint8_t i = 0; i < GPIO_ITEMS; i++) {
if(gpio_item[i].pin->pin == gpio->pin && gpio_item[i].pin->port == gpio->port) {
return gpio_item[i].name;
}
}
return NULL;
}
void DHTMon_sensors_init(void) {
//Включение 5V если на порту 1 FZ его нет
if(furi_hal_power_is_otg_enabled() != true) {
furi_hal_power_enable_otg();
}
//Настройка GPIO загруженных датчиков
for(uint8_t i = 0; i < app->sensors_count; i++) {
//Высокий уровень по умолчанию
furi_hal_gpio_write(app->sensors[i].GPIO, true);
//Режим работы - OpenDrain, подтяжка включается на всякий случай
furi_hal_gpio_init(
app->sensors[i].GPIO, //Порт FZ
GpioModeOutputOpenDrain, //Режим работы - открытый сток
GpioPullUp, //Принудительная подтяжка линии данных к питанию
GpioSpeedVeryHigh); //Скорость работы - максимальная
}
}
void DHTMon_sensors_deinit(void) {
//Возврат исходного состояния 5V
if(app->last_OTG_State != true) {
furi_hal_power_disable_otg();
}
//Перевод портов GPIO в состояние по умолчанию
for(uint8_t i = 0; i < app->sensors_count; i++) {
furi_hal_gpio_init(
app->sensors[i].GPIO, //Порт FZ
GpioModeAnalog, //Режим работы - аналог
GpioPullNo, //Отключение подтяжки
GpioSpeedLow); //Скорость работы - низкая
//Установка низкого уровня
furi_hal_gpio_write(app->sensors[i].GPIO, false);
}
}
bool DHTMon_sensor_check(DHT_sensor* sensor) {
/* Проверка имени */
//1) Строка должна быть длиной от 1 до 10 символов
//2) Первый символ строки должен быть только 0-9, A-Z, a-z и _
if(strlen(sensor->name) == 0 || strlen(sensor->name) > 10 ||
(!(sensor->name[0] >= '0' && sensor->name[0] <= '9') &&
!(sensor->name[0] >= 'A' && sensor->name[0] <= 'Z') &&
!(sensor->name[0] >= 'a' && sensor->name[0] <= 'z') && !(sensor->name[0] == '_'))) {
FURI_LOG_D(APP_NAME, "Sensor [%s] name check failed\r\n", sensor->name);
return false;
}
//Проверка GPIO
if(DHTMon_GPIO_to_int(sensor->GPIO) == 255) {
FURI_LOG_D(
APP_NAME,
"Sensor [%s] GPIO check failed: %d\r\n",
sensor->name,
DHTMon_GPIO_to_int(sensor->GPIO));
return false;
}
//Проверка типа датчика
if(sensor->type != DHT11 && sensor->type != DHT22) {
FURI_LOG_D(APP_NAME, "Sensor [%s] type check failed: %d\r\n", sensor->name, sensor->type);
return false;
}
//Возврат истины если всё ок
FURI_LOG_D(APP_NAME, "Sensor [%s] all checks passed\r\n", sensor->name);
return true;
}
void DHTMon_sensor_delete(DHT_sensor* sensor) {
if(sensor == NULL) return;
//Делаем параметры датчика неверными
sensor->name[0] = '\0';
sensor->type = 255;
//Теперь сохраняем текущие датчики. Сохранятор не сохранит неисправный датчик
DHTMon_sensors_save();
//Перезагружаемся с SD-карты
DHTMon_sensors_reload();
}
uint8_t DHTMon_sensors_save(void) {
//Выделение памяти для потока
app->file_stream = file_stream_alloc(app->storage);
uint8_t savedSensorsCount = 0;
//Переменная пути к файлу
char filepath[sizeof(APP_PATH_FOLDER) + sizeof(APP_FILENAME)] = {0};
//Составление пути к файлу
strcpy(filepath, APP_PATH_FOLDER);
strcat(filepath, "/");
strcat(filepath, APP_FILENAME);
//Открытие потока. Если поток открылся, то выполнение сохранения датчиков
if(file_stream_open(app->file_stream, filepath, FSAM_READ_WRITE, FSOM_CREATE_ALWAYS)) {
const char template[] =
"#DHT monitor sensors file\n#Name - name of sensor. Up to 10 sumbols\n#Type - type of sensor. DHT11 - 0, DHT22 - 1\n#GPIO - connection port. May being 2-7, 10, 12-17\n#Name Type GPIO\n";
stream_write(app->file_stream, (uint8_t*)template, strlen(template));
//Сохранение датчиков
for(uint8_t i = 0; i < app->sensors_count; i++) {
//Если параметры датчика верны, то сохраняемся
if(DHTMon_sensor_check(&app->sensors[i])) {
stream_write_format(
app->file_stream,
"%s %d %d\n",
app->sensors[i].name,
app->sensors[i].type,
DHTMon_GPIO_to_int(app->sensors[i].GPIO));
savedSensorsCount++;
}
}
} else {
//TODO: печать ошибки на экран
FURI_LOG_E(APP_NAME, "cannot create sensors file\r\n");
}
stream_free(app->file_stream);
return savedSensorsCount;
}
bool DHTMon_sensors_load(void) {
//Обнуление количества датчиков
app->sensors_count = -1;
//Очистка предыдущих датчиков
memset(app->sensors, 0, sizeof(app->sensors));
//Открытие файла на SD-карте
//Выделение памяти для потока
app->file_stream = file_stream_alloc(app->storage);
//Переменная пути к файлу
char filepath[sizeof(APP_PATH_FOLDER) + sizeof(APP_FILENAME)] = {0};
//Составление пути к файлу
strcpy(filepath, APP_PATH_FOLDER);
strcat(filepath, "/");
strcat(filepath, APP_FILENAME);
//Открытие потока к файлу
if(!file_stream_open(app->file_stream, filepath, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) {
//Если файл отсутствует, то создание болванки
FURI_LOG_W(APP_NAME, "Missing sensors file. Creating new file\r\n");
app->sensors_count = 0;
stream_free(app->file_stream);
DHTMon_sensors_save();
return false;
}
//Вычисление размера файла
size_t file_size = stream_size(app->file_stream);
if(file_size == (size_t)0) {
//Выход если файл пустой
FURI_LOG_W(APP_NAME, "Sensors file is empty\r\n");
app->sensors_count = 0;
stream_free(app->file_stream);
return false;
}
//Выделение памяти под загрузку файла
uint8_t* file_buf = malloc(file_size);
//Опустошение буфера файла
memset(file_buf, 0, file_size);
//Загрузка файла
if(stream_read(app->file_stream, file_buf, file_size) != file_size) {
//Выход при ошибке чтения
FURI_LOG_E(APP_NAME, "Error reading sensor file\r\n");
app->sensors_count = 0;
stream_free(app->file_stream);
return false;
}
//Построчное чтение файла
char* line = strtok((char*)file_buf, "\n");
while(line != NULL && app->sensors_count < MAX_SENSORS) {
if(line[0] != '#') {
DHT_sensor s = {0};
int type, port;
char name[11];
sscanf(line, "%s %d %d", name, &type, &port);
s.type = type;
s.GPIO = DHTMon_GPIO_form_int(port);
name[10] = '\0';
strcpy(s.name, name);
//Если данные корректны, то
if(DHTMon_sensor_check(&s) == true) {
//Установка нуля при первом датчике
if(app->sensors_count == -1) app->sensors_count = 0;
//Добавление датчика в общий список
app->sensors[app->sensors_count] = s;
//Увеличение количества загруженных датчиков
app->sensors_count++;
}
}
line = strtok((char*)NULL, "\n");
}
stream_free(app->file_stream);
free(file_buf);
//Обнуление количества датчиков если ни один из них не был загружен
if(app->sensors_count == -1) app->sensors_count = 0;
//Инициализация портов датчиков если таковые есть
if(app->sensors_count > 0) {
DHTMon_sensors_init();
return true;
} else {
return false;
}
return false;
}
bool DHTMon_sensors_reload(void) {
DHTMon_sensors_deinit();
return DHTMon_sensors_load();
}
/**
* @brief Обработчик отрисовки экрана
*
* @param canvas Указатель на холст
* @param ctx Данные плагина
*/
static void render_callback(Canvas* const canvas, void* ctx) {
PluginData* app = acquire_mutex((ValueMutex*)ctx, 25);
if(app == NULL) {
return;
}
//Вызов отрисовки главного экрана
scene_main(canvas, app);
release_mutex((ValueMutex*)ctx, app);
}
/**
* @brief Обработчик нажатия кнопок главного экрана
*
* @param input_event Указатель на событие
* @param event_queue Указатель на очередь событий
*/
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
furi_assert(event_queue);
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
/**
* @brief Выделение места под переменные плагина
*
* @return true Если всё прошло успешно
* @return false Если в процессе загрузки произошла ошибка
*/
static bool DHTMon_alloc(void) {
//Выделение места под данные плагина
app = malloc(sizeof(PluginData));
//Выделение места под очередь событий
app->event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
//Обнуление количества датчиков
app->sensors_count = -1;
//Инициализация мутекса
if(!init_mutex(&app->state_mutex, app, sizeof(PluginData))) {
FURI_LOG_E(APP_NAME, "cannot create mutex\r\n");
return false;
}
// Set system callbacks
app->view_port = view_port_alloc();
view_port_draw_callback_set(app->view_port, render_callback, &app->state_mutex);
view_port_input_callback_set(app->view_port, input_callback, app->event_queue);
// Open GUI and register view_port
app->gui = furi_record_open(RECORD_GUI);
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
app->view_dispatcher = view_dispatcher_alloc();
sensorActions_sceneCreate(app);
sensorEdit_sceneCreate(app);
app->widget = widget_alloc();
view_dispatcher_add_view(app->view_dispatcher, WIDGET_VIEW, widget_get_view(app->widget));
app->text_input = text_input_alloc();
view_dispatcher_add_view(
app->view_dispatcher, TEXTINPUT_VIEW, text_input_get_view(app->text_input));
view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
//Уведомления
app->notifications = furi_record_open(RECORD_NOTIFICATION);
//Подготовка хранилища
app->storage = furi_record_open(RECORD_STORAGE);
storage_common_mkdir(app->storage, APP_PATH_FOLDER);
app->file_stream = file_stream_alloc(app->storage);
return true;
}
/**
* @brief Освыбождение памяти после работы приложения
*/
static void DHTMon_free(void) {
//Автоматическое управление подсветкой
notification_message(app->notifications, &sequence_display_backlight_enforce_auto);
furi_record_close(RECORD_STORAGE);
furi_record_close(RECORD_NOTIFICATION);
text_input_free(app->text_input);
widget_free(app->widget);
sensorEdit_sceneRemove();
sensorActions_screneRemove();
view_dispatcher_free(app->view_dispatcher);
furi_record_close(RECORD_GUI);
view_port_enabled_set(app->view_port, false);
gui_remove_view_port(app->gui, app->view_port);
view_port_free(app->view_port);
furi_message_queue_free(app->event_queue);
delete_mutex(&app->state_mutex);
free(app);
}
/**
* @brief Точка входа в приложение
*
* @return Код ошибки
*/
int32_t quenon_dht_mon_app() {
if(!DHTMon_alloc()) {
DHTMon_free();
return 255;
}
//Постоянное свечение подсветки
notification_message(app->notifications, &sequence_display_backlight_enforce_on);
//Сохранение состояния наличия 5V на порту 1 FZ
app->last_OTG_State = furi_hal_power_is_otg_enabled();
//Загрузка датчиков с SD-карты
DHTMon_sensors_load();
app->currentSensorEdit = &app->sensors[0];
PluginEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(app->event_queue, &event, 100);
acquire_mutex_block(&app->state_mutex);
if(event_status == FuriStatusOk) {
// press events
if(event.type == EventTypeKey) {
if(event.input.type == InputTypePress) {
switch(event.input.key) {
case InputKeyUp:
break;
case InputKeyDown:
break;
case InputKeyRight:
break;
case InputKeyLeft:
break;
case InputKeyOk:
view_port_update(app->view_port);
release_mutex(&app->state_mutex, app);
mainMenu_scene(app);
break;
case InputKeyBack:
processing = false;
break;
default:
break;
}
}
}
}
view_port_update(app->view_port);
release_mutex(&app->state_mutex, app);
}
//Освобождение памяти и деинициализация
DHTMon_sensors_deinit();
DHTMon_free();
return 0;
}
//TODO: Обработка ошибок
//TODO: Пропуск использованных портов в меню добавления датчиков