unleashed-firmware/applications/plugins/dht_temp_sensor/quenon_dht_mon.c

460 lines
17 KiB
C
Raw Normal View History

2022-11-02 01:38:09 +03:00
#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", &gpio_ext_pa7},
{3, "3", &gpio_ext_pa6},
{4, "4", &gpio_ext_pa4},
{5, "5", &gpio_ext_pb3},
{6, "6", &gpio_ext_pb2},
{7, "7", &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", &gpio_ext_pc1},
{16, "16", &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;
}
}
}
}
view_port_update(app->view_port);
release_mutex(&app->state_mutex, app);
}
//Освобождение памяти и деинициализация
DHTMon_sensors_deinit();
DHTMon_free();
return 0;
}
//TODO: Обработка ошибок
//TODO: Пропуск использованных портов в меню добавления датчиков