#include "quenon_dht_mon.h" #include <m-string.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; //Переменная пути к файлу FuriString* filepath = furi_string_alloc(); //Составление пути к файлу furi_string_printf(filepath, "%s/%s", APP_PATH_FOLDER, APP_FILENAME); //Открытие потока. Если поток открылся, то выполнение сохранения датчиков if(file_stream_open( app->file_stream, furi_string_get_cstr(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); //Переменная пути к файлу FuriString* filepath = furi_string_alloc(); //Составление пути к файлу furi_string_printf(filepath, "%s/%s", APP_PATH_FOLDER, APP_FILENAME); //Открытие потока к файлу if(!file_stream_open( app->file_stream, furi_string_get_cstr(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; } //Построчное чтение файла //Указатель на начало строки FuriString* file = furi_string_alloc_set_str((char*)file_buf); //Сколько байт до конца строки size_t line_end = 0; while(line_end != STRING_FAILURE && app->sensors_count < MAX_SENSORS) { if(((char*)(file_buf + line_end))[1] != '#') { DHT_sensor s = {0}; int type, port; char name[11] = {0}; sscanf(((char*)(file_buf + line_end)), "%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_end = furi_string_search_char(file, '\n', line_end + 1); } 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: Пропуск использованных портов в меню добавления датчиков