├── icon.png ├── application.fam ├── DHT.h ├── README.md ├── scenes ├── DHTMon_main_scene.c ├── DHTMon_sensorEdit_scene.c ├── DHTMon_mainMenu_scene.c └── DHTMon_sensorActions_scene.c ├── README_RU.md ├── quenon_dht_mon.h ├── DHT.c └── quenon_dht_mon.c /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quen0n/FipperZero-DHT-Monitor/HEAD/icon.png -------------------------------------------------------------------------------- /application.fam: -------------------------------------------------------------------------------- 1 | App( 2 | appid="quenon_dht_mon", 3 | name="[DHT] monitor", 4 | apptype=FlipperAppType.EXTERNAL, 5 | entry_point="quenon_dht_mon_app", 6 | cdefines=["QUENON_DHT_MON"], 7 | requires=[ 8 | "gui", 9 | ], 10 | fap_category="GPIO", 11 | fap_icon="icon.png", 12 | stack_size=2 * 1024, 13 | ) -------------------------------------------------------------------------------- /DHT.h: -------------------------------------------------------------------------------- 1 | #ifndef DHT_H_ 2 | #define DHT_H_ 3 | 4 | #include 5 | 6 | /* Настройки */ 7 | #define DHT_TIMEOUT 65534 //Количество итераций, после которых функция вернёт пустые значения 8 | #define DHT_POLLING_CONTROL 1 //Включение проверки частоты опроса датчика 9 | #define DHT_POLLING_INTERVAL_DHT11 \ 10 | 2000 //Интервал опроса DHT11 (0.5 Гц по даташиту). Можно поставить 1500, будет работать 11 | //Костыль, временно 2 секунды для датчика AM2302 12 | #define DHT_POLLING_INTERVAL_DHT22 2000 //Интервал опроса DHT22 (1 Гц по даташиту) 13 | #define DHT_IRQ_CONTROL //Выключать прерывания во время обмена данных с датчиком 14 | /* Структура возвращаемых датчиком данных */ 15 | typedef struct { 16 | float hum; 17 | float temp; 18 | } DHT_data; 19 | 20 | /* Тип используемого датчика */ 21 | typedef enum { DHT11, DHT22 } DHT_type; 22 | 23 | /* Структура объекта датчика */ 24 | typedef struct { 25 | char name[11]; 26 | const GpioPin* GPIO; //Пин датчика 27 | DHT_type type; //Тип датчика (DHT11 или DHT22) 28 | 29 | //Контроль частоты опроса датчика. Значения не заполнять! 30 | #if DHT_POLLING_CONTROL == 1 31 | uint32_t lastPollingTime; //Время последнего опроса датчика 32 | float lastTemp; //Последнее значение температуры 33 | float lastHum; //Последнее значение влажности 34 | #endif 35 | } DHT_sensor; 36 | 37 | /* Прототипы функций */ 38 | DHT_data DHT_getData(DHT_sensor* sensor); //Получить данные с датчика 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FipperZero-DHT-Monitor 2 | Flipper Zero plugin for monitoring air temperature and humidity using DHT11 and DHT22 (AM2302/AM2301) sensors 3 | 4 | # No longer supported. Use [Unitemp](https://github.com/quen0n/unitemp-flipperzero) 5 | 6 | ![UsageExample](https://sun9-27.userapi.com/impg/hkRTht9w6kq2lwdNi3fvKKxhQhif8Mc-lAVhGQ/HkgLO14EY0g.jpg?size=1914x480&quality=96&sign=b4363ea7edc74cb60e401f0c53876e98&type=album) 7 | ## Capabilities 8 | - Support for DHT11/DHT22(AM2302)/AM2301 sensors 9 | - Ability to work with sensors without strapping 10 | - Free choice of I/O ports 11 | - Automatic power on 5V output 1 12 | - Saving settings to an SD card 13 | ## Installation 14 | Copy the contents of the repository to the `applications/plugins/dht_monitor` folder and build the project. Flash FZ along with resources. 15 | 16 | Or use the plugin included with [Unleashed Firmware](https://github.com/DarkFlippers/unleashed-firmware) 17 | ## Connecting 18 | |Sensor pin |Flipper Zero pin| 19 | |--------------|----------------| 20 | |VCC (none, +, VCC, red wire)| 1 (5V) or 9 (3V3)| 21 | |GND (-, GND, black wire)| 8, 18 (GND)| 22 | |DATA (OUT, S, yellow wire)| 2-7, 10, 12-17 (to choose from)| 23 | 24 | The selected pin of the DATA line must be specified when adding a sensor in the application. 25 | ## Usage 26 | 1) Connect the sensor to FZ. 27 | 2) Launch the application and press the center button. Select "Add new sensor". 28 | 3) Specify sensor parameters - name, type, pin. 29 | 4) Click "Save". 30 | The next time you launch the application, the saved sensors will be loaded automatically. 31 | You can delete or change sensor parameters in the main menu. 32 | 33 | -------------------------------------------------------------------------------- /scenes/DHTMon_main_scene.c: -------------------------------------------------------------------------------- 1 | #include "../quenon_dht_mon.h" 2 | 3 | /* ============== Главный экран ============== */ 4 | void scene_main(Canvas* const canvas, PluginData* app) { 5 | //Рисование бара 6 | canvas_draw_box(canvas, 0, 0, 128, 14); 7 | canvas_set_color(canvas, ColorWhite); 8 | canvas_set_font(canvas, FontPrimary); 9 | canvas_draw_str(canvas, 32, 11, "DHT Monitor"); 10 | 11 | canvas_set_color(canvas, ColorBlack); 12 | if(app->sensors_count > 0) { 13 | if(!furi_hal_power_is_otg_enabled()) { 14 | furi_hal_power_enable_otg(); 15 | } 16 | for(uint8_t i = 0; i < app->sensors_count; i++) { 17 | app->data = DHT_getData(&app->sensors[i]); 18 | 19 | canvas_set_font(canvas, FontPrimary); 20 | canvas_draw_str(canvas, 0, 24 + 10 * i, app->sensors[i].name); 21 | 22 | canvas_set_font(canvas, FontSecondary); 23 | if(app->data.hum == -128.0f && app->data.temp == -128.0f) { 24 | canvas_draw_str(canvas, 96, 24 + 10 * i, "timeout"); 25 | } else { 26 | snprintf( 27 | app->txtbuff, 28 | sizeof(app->txtbuff), 29 | "%2.1f*C/%d%%", 30 | (double)app->data.temp, 31 | (int8_t)app->data.hum); 32 | canvas_draw_str(canvas, 64, 24 + 10 * i, app->txtbuff); 33 | } 34 | } 35 | } else { 36 | canvas_set_font(canvas, FontSecondary); 37 | if(app->sensors_count == 0) canvas_draw_str(canvas, 0, 24, "Sensors not found"); 38 | if(app->sensors_count == -1) canvas_draw_str(canvas, 0, 24, "Loading..."); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /README_RU.md: -------------------------------------------------------------------------------- 1 | # DHT Monitor 2 | Плагин Fipper Zero для получения данных температуры и влажности воздуха с датчиков DHT11/DHT22(AM2302)/AM2301 3 | 4 | ![UsageExample](https://sun9-27.userapi.com/impg/hkRTht9w6kq2lwdNi3fvKKxhQhif8Mc-lAVhGQ/HkgLO14EY0g.jpg?size=1914x480&quality=96&sign=b4363ea7edc74cb60e401f0c53876e98&type=album) 5 | ## Возможности 6 | - Поддержка датчиков DHT11/DHT22(AM2302)/AM2301 7 | - Возможность работы с датчиками без обвязки 8 | - Свободный выбор портов ввода/вывода 9 | - Автматическое включение питания 5В на выходе 1 10 | - Сохранение настроек на SD-карту 11 | ## Установка 12 | Скопируйте содержимое репозитория в папку `applications/plugins/dht_monitor` и соберите проект. Прошейте FZ вместе с ресурсами. 13 | Или воспользуйтесь плагином в составе [Unleashed Firmware](https://github.com/DarkFlippers/unleashed-firmware) 14 | ## Подключение 15 | |Пин датчика|Порт Flipper Zero| 16 | |--------------|----------------| 17 | |VCC (нет, +, VCC, красный провод)| 1 (5V) или 9 (3V3)| 18 | |GND (-, GND, чёрный провод)| 8, 18 (GND)| 19 | |DATA (OUT, S, жёлтый провод)| 2-7, 10, 12-17 (на выбор)| 20 | 21 | Выбранный пин линии DATA необходимо указать при добавлении датчика в приложении. 22 | ## Использование 23 | 1) Подключите датчик к FZ. 24 | 2) Запустите приложение и нажмите на центральную кнопку. Выберите пункт "Add new sensor". 25 | 3) Укажите параметры датчика - имя, тип, порт. 26 | 4) Нажмите "Save". 27 | При следующих запусках приложения сохранённые датчики будут загружаться автоматически. 28 | Удалить или изменить параметры датчика можно в главном меню. 29 | ## FAQ 30 | **В: Не могу получить данные с датчика, всегда получается timeout, что делать?** 31 | 32 | О: Проверьте подключение датчика - убедитесь в правильности и надёжности соединений, в наличии питания, а так же в верно указанном порте/типе датчика. Так же проверьте обратную часть датчика - если пластик оплавлен, то датчик неисправен. 33 | 34 | **В: Почему у моего DHT11 нет дробной части температуры?** 35 | 36 | О: Дробная часть температуры имеется только у датчиков фирмы ASAIR. 37 | 38 | ![DHT11](https://sun9-12.userapi.com/impf/p3JOS1g5SKhOsqi5kWDULmhF77CvJ46TUoxpjg/b5sz8ltiPQ0.jpg?size=450x552&quality=96&sign=c4674be871628dcb6d581067ed806138&type=album) 39 | 40 | **В: Что насчёт DHT12?** 41 | 42 | О: Он скорее всего будет работать в однопроводном режиме как и DHT11. 43 | 44 | ## Обратная связь, нужна помощь? 45 | Предложения или найденные баги можно сообщить в Telegram http://t.me/quen0n или ВКонтакте https://vk.com/quenon 46 | -------------------------------------------------------------------------------- /scenes/DHTMon_sensorEdit_scene.c: -------------------------------------------------------------------------------- 1 | #include "../quenon_dht_mon.h" 2 | 3 | static VariableItem* nameItem; 4 | static VariableItemList* variable_item_list; 5 | 6 | static const char* const sensorsTypes[2] = { 7 | "DHT11", 8 | "DHT22", 9 | }; 10 | 11 | // /* ============== Добавление датчика ============== */ 12 | static uint32_t addSensor_exitCallback(void* context) { 13 | UNUSED(context); 14 | DHTMon_sensors_reload(); 15 | return VIEW_NONE; 16 | } 17 | 18 | static void addSensor_sensorTypeChanged(VariableItem* item) { 19 | uint8_t index = variable_item_get_current_value_index(item); 20 | PluginData* app = variable_item_get_context(item); 21 | variable_item_set_current_value_text(item, sensorsTypes[index]); 22 | app->currentSensorEdit->type = index; 23 | } 24 | 25 | static void addSensor_GPIOChanged(VariableItem* item) { 26 | uint8_t index = variable_item_get_current_value_index(item); 27 | variable_item_set_current_value_text(item, DHTMon_GPIO_getName(DHTMon_GPIO_from_index(index))); 28 | PluginData* app = variable_item_get_context(item); 29 | app->currentSensorEdit->GPIO = DHTMon_GPIO_from_index(index); 30 | } 31 | 32 | static void addSensor_sensorNameChanged(void* context) { 33 | PluginData* app = context; 34 | variable_item_set_current_value_text(nameItem, app->currentSensorEdit->name); 35 | view_dispatcher_switch_to_view(app->view_dispatcher, ADDSENSOR_MENU_VIEW); 36 | } 37 | static void addSensor_sensorNameChange(PluginData* app) { 38 | text_input_set_header_text(app->text_input, "Sensor name"); 39 | //По неясной мне причине в длину строки входит терминатор. Поэтому при длине 10 приходится указывать 11 40 | text_input_set_result_callback( 41 | app->text_input, addSensor_sensorNameChanged, app, app->currentSensorEdit->name, 11, true); 42 | view_dispatcher_switch_to_view(app->view_dispatcher, TEXTINPUT_VIEW); 43 | } 44 | 45 | static void addSensor_enterCallback(void* context, uint32_t index) { 46 | PluginData* app = context; 47 | if(index == 0) { 48 | addSensor_sensorNameChange(app); 49 | } 50 | if(index == 3) { 51 | //Сохранение датчика 52 | DHTMon_sensors_save(); 53 | DHTMon_sensors_reload(); 54 | view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_NONE); 55 | } 56 | } 57 | 58 | void sensorEdit_sceneCreate(PluginData* app) { 59 | variable_item_list = variable_item_list_alloc(); 60 | 61 | variable_item_list_reset(variable_item_list); 62 | 63 | variable_item_list_set_enter_callback(variable_item_list, addSensor_enterCallback, app); 64 | 65 | app->view = variable_item_list_get_view(variable_item_list); 66 | 67 | view_set_previous_callback(app->view, addSensor_exitCallback); 68 | 69 | view_dispatcher_add_view(app->view_dispatcher, ADDSENSOR_MENU_VIEW, app->view); 70 | } 71 | void sensorEdit_scene(PluginData* app) { 72 | //Очистка списка 73 | variable_item_list_reset(variable_item_list); 74 | 75 | //Имя редактируемого датчика 76 | nameItem = variable_item_list_add(variable_item_list, "Name: ", 1, NULL, NULL); 77 | variable_item_set_current_value_index(nameItem, 0); 78 | variable_item_set_current_value_text(nameItem, app->currentSensorEdit->name); 79 | 80 | //Тип датчика 81 | app->item = 82 | variable_item_list_add(variable_item_list, "Type:", 2, addSensor_sensorTypeChanged, app); 83 | 84 | variable_item_set_current_value_index(app->item, app->currentSensorEdit->type); 85 | variable_item_set_current_value_text(app->item, sensorsTypes[app->currentSensorEdit->type]); 86 | 87 | //GPIO 88 | app->item = 89 | variable_item_list_add(variable_item_list, "GPIO:", 13, addSensor_GPIOChanged, app); 90 | variable_item_set_current_value_index( 91 | app->item, DHTMon_GPIO_to_index(app->currentSensorEdit->GPIO)); 92 | variable_item_set_current_value_text( 93 | app->item, DHTMon_GPIO_getName(app->currentSensorEdit->GPIO)); 94 | variable_item_list_add(variable_item_list, "Save", 1, NULL, app); 95 | 96 | //Сброс выбранного пункта в ноль 97 | variable_item_list_set_selected_item(variable_item_list, 0); 98 | 99 | view_dispatcher_switch_to_view(app->view_dispatcher, ADDSENSOR_MENU_VIEW); 100 | } 101 | void sensorEdit_sceneRemove(void) { 102 | variable_item_list_free(variable_item_list); 103 | } -------------------------------------------------------------------------------- /scenes/DHTMon_mainMenu_scene.c: -------------------------------------------------------------------------------- 1 | #include "../quenon_dht_mon.h" 2 | //Текущий вид 3 | static View* view; 4 | //Список 5 | static VariableItemList* variable_item_list; 6 | 7 | /** 8 | * @brief Функция обработки нажатия кнопки "Назад" 9 | * 10 | * @param context Указатель на данные приложения 11 | * @return ID вида в который нужно переключиться 12 | */ 13 | static uint32_t actions_exitCallback(void* context) { 14 | PluginData* app = context; 15 | UNUSED(app); 16 | //Возвращаем ID вида, в который нужно вернуться 17 | return VIEW_NONE; 18 | } 19 | /** 20 | * @brief Функция обработки нажатия средней кнопки 21 | * 22 | * @param context Указатель на данные приложения 23 | * @param index На каком элементе списка была нажата кнопка 24 | */ 25 | static void enterCallback(void* context, uint32_t index) { 26 | PluginData* app = context; 27 | if((uint8_t)index < (uint8_t)app->sensors_count) { 28 | app->currentSensorEdit = &app->sensors[index]; 29 | sensorActions_scene(app); 30 | } 31 | if((uint8_t)index == (uint8_t)app->sensors_count) { 32 | app->currentSensorEdit = &app->sensors[app->sensors_count++]; 33 | strcpy(app->currentSensorEdit->name, "NewSensor"); 34 | app->currentSensorEdit->GPIO = DHTMon_GPIO_from_index(0); 35 | app->currentSensorEdit->type = DHT11; 36 | sensorEdit_scene(app); 37 | } 38 | } 39 | 40 | /** 41 | * @brief Создание списка действий с указанным датчиком 42 | * 43 | * @param app Указатель на данные плагина 44 | */ 45 | void mainMenu_scene(PluginData* app) { 46 | variable_item_list = variable_item_list_alloc(); 47 | //Сброс всех элементов меню 48 | variable_item_list_reset(variable_item_list); 49 | //Добавление названий датчиков в качестве элементов списка 50 | for(uint8_t i = 0; i < app->sensors_count; i++) { 51 | variable_item_list_add(variable_item_list, app->sensors[i].name, 1, NULL, NULL); 52 | } 53 | if(app->sensors_count < (uint8_t)MAX_SENSORS) { 54 | variable_item_list_add(variable_item_list, " + Add new sensor +", 1, NULL, NULL); 55 | } 56 | 57 | //Добавление колбека на нажатие средней кнопки 58 | variable_item_list_set_enter_callback(variable_item_list, enterCallback, app); 59 | 60 | //Создание вида из списка 61 | view = variable_item_list_get_view(variable_item_list); 62 | //Добавление колбека на нажатие кнопки "Назад" 63 | view_set_previous_callback(view, actions_exitCallback); 64 | //Добавление вида в диспетчер 65 | view_dispatcher_add_view(app->view_dispatcher, MAIN_MENU_VIEW, view); 66 | 67 | view_dispatcher_enable_queue(app->view_dispatcher); 68 | 69 | //Переключение на наш вид 70 | view_dispatcher_switch_to_view(app->view_dispatcher, MAIN_MENU_VIEW); 71 | 72 | //Запуск диспетчера 73 | view_dispatcher_run(app->view_dispatcher); 74 | 75 | //Очистка списка элементов 76 | variable_item_list_free(variable_item_list); 77 | //Удаление вида после обработки 78 | view_dispatcher_remove_view(app->view_dispatcher, MAIN_MENU_VIEW); 79 | } 80 | 81 | /* 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | */ 123 | 124 | // static VariableItemList* variable_item_list; 125 | // /* ============== Главное меню ============== */ 126 | // static uint32_t mainMenu_exitCallback(void* context) { 127 | // UNUSED(context); 128 | // variable_item_list_free(variable_item_list); 129 | // DHT_sensors_reload(); 130 | // return VIEW_NONE; 131 | // } 132 | // static void mainMenu_enterCallback(void* context, uint32_t index) { 133 | // PluginData* app = context; 134 | // if((uint8_t)index == (uint8_t)app->sensors_count) { 135 | // addSensor_scene(app); 136 | // view_dispatcher_run(app->view_dispatcher); 137 | // } 138 | // } 139 | // void mainMenu_scene(PluginData* app) { 140 | // variable_item_list = variable_item_list_alloc(); 141 | // variable_item_list_reset(variable_item_list); 142 | // for(uint8_t i = 0; i < app->sensors_count; i++) { 143 | // variable_item_list_add(variable_item_list, app->sensors[i].name, 1, NULL, NULL); 144 | // } 145 | // variable_item_list_add(variable_item_list, "+ Add new sensor +", 1, NULL, NULL); 146 | 147 | // app->view = variable_item_list_get_view(variable_item_list); 148 | // app->view_dispatcher = view_dispatcher_alloc(); 149 | 150 | // view_dispatcher_enable_queue(app->view_dispatcher); 151 | // view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); 152 | // view_dispatcher_add_view(app->view_dispatcher, MAIN_MENU_VIEW, app->view); 153 | // view_dispatcher_switch_to_view(app->view_dispatcher, MAIN_MENU_VIEW); 154 | 155 | // variable_item_list_set_enter_callback(variable_item_list, mainMenu_enterCallback, app); 156 | // view_set_previous_callback(app->view, mainMenu_exitCallback); 157 | // } -------------------------------------------------------------------------------- /quenon_dht_mon.h: -------------------------------------------------------------------------------- 1 | #ifndef QUENON_DHT_MON 2 | #define QUENON_DHT_MON 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | #include "DHT.h" 25 | 26 | #define APP_NAME "DHT monitor" 27 | #define APP_PATH_FOLDER "/ext/DHT monitor" 28 | #define APP_FILENAME "sensors.txt" 29 | #define MAX_SENSORS 5 30 | 31 | // //Виды менюшек 32 | typedef enum { 33 | MAIN_MENU_VIEW, 34 | ADDSENSOR_MENU_VIEW, 35 | TEXTINPUT_VIEW, 36 | SENSOR_ACTIONS_VIEW, 37 | WIDGET_VIEW, 38 | } MENU_VIEWS; 39 | 40 | typedef enum { 41 | EventTypeTick, 42 | EventTypeKey, 43 | } EventType; 44 | 45 | typedef struct { 46 | EventType type; 47 | InputEvent input; 48 | } PluginEvent; 49 | 50 | typedef struct { 51 | const uint8_t num; 52 | const char* name; 53 | const GpioPin* pin; 54 | } GpioItem; 55 | 56 | //Структура с данными плагина 57 | typedef struct { 58 | //Очередь сообщений 59 | FuriMessageQueue* event_queue; 60 | //Мутекс 61 | ValueMutex state_mutex; 62 | //Вьюпорт 63 | ViewPort* view_port; 64 | //GUI 65 | Gui* gui; 66 | NotificationApp* notifications; 67 | ViewDispatcher* view_dispatcher; 68 | View* view; 69 | TextInput* text_input; 70 | VariableItem* item; 71 | Widget* widget; 72 | 73 | char txtbuff[30]; //Буффер для печати строк на экране 74 | bool last_OTG_State; //Состояние OTG до запуска приложения 75 | Storage* storage; //Хранилище датчиков 76 | Stream* file_stream; //Поток файла с датчиками 77 | int8_t sensors_count; // Количество загруженных датчиков 78 | DHT_sensor sensors[MAX_SENSORS]; //Сохранённые датчики 79 | DHT_data data; //Инфа из датчика 80 | DHT_sensor* currentSensorEdit; //Указатель на редактируемый датчик 81 | 82 | } PluginData; 83 | 84 | /* ================== Работа с GPIO ================== */ 85 | /** 86 | * @brief Конвертация GPIO в его номер на корпусе FZ 87 | * 88 | * @param gpio Указатель на преобразовываемый GPIO 89 | * @return Номер порта на корпусе FZ 90 | */ 91 | uint8_t DHTMon_GPIO_to_int(const GpioPin* gpio); 92 | /** 93 | * @brief Конвертация номера порта на корпусе FZ в GPIO 94 | * 95 | * @param name Номер порта на корпусе FZ 96 | * @return Указатель на GPIO при успехе, NULL при ошибке 97 | */ 98 | const GpioPin* DHTMon_GPIO_form_int(uint8_t name); 99 | /** 100 | * @brief Преобразование порядкового номера порта в GPIO 101 | * 102 | * @param index Индекс порта от 0 до GPIO_ITEMS-1 103 | * @return Указатель на GPIO при успехе, NULL при ошибке 104 | */ 105 | const GpioPin* DHTMon_GPIO_from_index(uint8_t index); 106 | /** 107 | * @brief Преобразование GPIO в порядковый номер порта 108 | * 109 | * @param gpio Указатель на GPIO 110 | * @return index при успехе, 255 при ошибке 111 | */ 112 | uint8_t DHTMon_GPIO_to_index(const GpioPin* gpio); 113 | 114 | /** 115 | * @brief Получить имя GPIO в виде строки 116 | * 117 | * @param gpio Искомый порт 118 | * @return char* Указатель на строку с именем порта 119 | */ 120 | const char* DHTMon_GPIO_getName(const GpioPin* gpio); 121 | 122 | /* ================== Работа с датчиками ================== */ 123 | /** 124 | * @brief Инициализация портов ввода/вывода датчиков 125 | */ 126 | void DHTMon_sensors_init(void); 127 | /** 128 | * @brief Функция деинициализации портов ввода/вывода датчиков 129 | */ 130 | void DHTMon_sensors_deinit(void); 131 | /** 132 | * @brief Проверка корректности параметров датчика 133 | * 134 | * @param sensor Указатель на проверяемый датчик 135 | * @return true Параметры датчика корректные 136 | * @return false Параметры датчика некорректные 137 | */ 138 | bool DHTMon_sensor_check(DHT_sensor* sensor); 139 | /** 140 | * @brief Удаление датчика из списка и перезагрузка 141 | * 142 | * @param sensor Указатель на удаляемый датчик 143 | */ 144 | void DHTMon_sensor_delete(DHT_sensor* sensor); 145 | /** 146 | * @brief Сохранение датчиков на SD-карту 147 | * 148 | * @return Количество сохранённых датчиков 149 | */ 150 | uint8_t DHTMon_sensors_save(void); 151 | /** 152 | * @brief Загрузка датчиков с SD-карты 153 | * 154 | * @return true Был загружен хотя бы 1 датчик 155 | * @return false Датчики отсутствуют 156 | */ 157 | bool DHTMon_sensors_load(void); 158 | /** 159 | * @brief Перезагрузка датчиков с SD-карты 160 | * 161 | * @return true Когда был загружен хотя бы 1 датчик 162 | * @return false Ни один из датчиков не был загружен 163 | */ 164 | bool DHTMon_sensors_reload(void); 165 | 166 | void scene_main(Canvas* const canvas, PluginData* app); 167 | void mainMenu_scene(PluginData* app); 168 | 169 | void sensorEdit_sceneCreate(PluginData* app); 170 | void sensorEdit_scene(PluginData* app); 171 | void sensorEdit_sceneRemove(void); 172 | 173 | void sensorActions_sceneCreate(PluginData* app); 174 | void sensorActions_scene(PluginData* app); 175 | void sensorActions_screneRemove(void); 176 | #endif -------------------------------------------------------------------------------- /DHT.c: -------------------------------------------------------------------------------- 1 | #include "DHT.h" 2 | 3 | #define lineDown() furi_hal_gpio_write(sensor->GPIO, false) 4 | #define lineUp() furi_hal_gpio_write(sensor->GPIO, true) 5 | #define getLine() furi_hal_gpio_read(sensor->GPIO) 6 | #define Delay(d) furi_delay_ms(d) 7 | 8 | DHT_data DHT_getData(DHT_sensor* sensor) { 9 | DHT_data data = {-128.0f, -128.0f}; 10 | 11 | #if DHT_POLLING_CONTROL == 1 12 | /* Ограничение по частоте опроса датчика */ 13 | //Определение интервала опроса в зависимости от датчика 14 | uint16_t pollingInterval; 15 | if(sensor->type == DHT11) { 16 | pollingInterval = DHT_POLLING_INTERVAL_DHT11; 17 | } else { 18 | pollingInterval = DHT_POLLING_INTERVAL_DHT22; 19 | } 20 | 21 | //Если интервал маленький, то возврат последнего удачного значения 22 | if((furi_get_tick() - sensor->lastPollingTime < pollingInterval) && 23 | sensor->lastPollingTime != 0) { 24 | data.hum = sensor->lastHum; 25 | data.temp = sensor->lastTemp; 26 | return data; 27 | } 28 | sensor->lastPollingTime = furi_get_tick() + 1; 29 | #endif 30 | 31 | //Опускание линии данных на 18 мс 32 | lineDown(); 33 | #ifdef DHT_IRQ_CONTROL 34 | //Выключение прерываний, чтобы ничто не мешало обработке данных 35 | __disable_irq(); 36 | #endif 37 | Delay(18); 38 | 39 | //Подъём линии 40 | lineUp(); 41 | 42 | /* Ожидание ответа от датчика */ 43 | uint16_t timeout = 0; 44 | while(!getLine()) { 45 | timeout++; 46 | if(timeout > DHT_TIMEOUT) { 47 | #ifdef DHT_IRQ_CONTROL 48 | __enable_irq(); 49 | #endif 50 | //Если датчик не отозвался, значит его точно нет 51 | //Обнуление последнего удачного значения, чтобы 52 | //не получать фантомные значения 53 | sensor->lastHum = -128.0f; 54 | sensor->lastTemp = -128.0f; 55 | 56 | return data; 57 | } 58 | } 59 | //Ожидание спада 60 | while(getLine()) { 61 | timeout++; 62 | if(timeout > DHT_TIMEOUT) { 63 | #ifdef DHT_IRQ_CONTROL 64 | __enable_irq(); 65 | #endif 66 | //Если датчик не отозвался, значит его точно нет 67 | //Обнуление последнего удачного значения, чтобы 68 | //не получать фантомные значения 69 | sensor->lastHum = -128.0f; 70 | sensor->lastTemp = -128.0f; 71 | 72 | return data; 73 | } 74 | } 75 | timeout = 0; 76 | //Ожидание подъёма 77 | while(!getLine()) { 78 | timeout++; 79 | if(timeout > DHT_TIMEOUT) { 80 | if(timeout > DHT_TIMEOUT) { 81 | #ifdef DHT_IRQ_CONTROL 82 | __enable_irq(); 83 | #endif 84 | //Если датчик не отозвался, значит его точно нет 85 | //Обнуление последнего удачного значения, чтобы 86 | //не получать фантомные значения 87 | sensor->lastHum = -128.0f; 88 | sensor->lastTemp = -128.0f; 89 | 90 | return data; 91 | } 92 | } 93 | } 94 | timeout = 0; 95 | //Ожидание спада 96 | while(getLine()) { 97 | timeout++; 98 | if(timeout > DHT_TIMEOUT) { 99 | #ifdef DHT_IRQ_CONTROL 100 | __enable_irq(); 101 | #endif 102 | //Если датчик не отозвался, значит его точно нет 103 | //Обнуление последнего удачного значения, чтобы 104 | //не получать фантомные значения 105 | sensor->lastHum = -128.0f; 106 | sensor->lastTemp = -128.0f; 107 | return data; 108 | } 109 | } 110 | 111 | /* Чтение ответа от датчика */ 112 | uint8_t rawData[5] = {0, 0, 0, 0, 0}; 113 | for(uint8_t a = 0; a < 5; a++) { 114 | for(uint8_t b = 7; b != 255; b--) { 115 | uint16_t hT = 0, lT = 0; 116 | //Пока линия в низком уровне, инкремент переменной lT 117 | while(!getLine() && lT != 65535) lT++; 118 | //Пока линия в высоком уровне, инкремент переменной hT 119 | timeout = 0; 120 | while(getLine() && hT != 65535) hT++; 121 | //Если hT больше lT, то пришла единица 122 | if(hT > lT) rawData[a] |= (1 << b); 123 | } 124 | } 125 | #ifdef DHT_IRQ_CONTROL 126 | //Включение прерываний после приёма данных 127 | __enable_irq(); 128 | #endif 129 | /* Проверка целостности данных */ 130 | if((uint8_t)(rawData[0] + rawData[1] + rawData[2] + rawData[3]) == rawData[4]) { 131 | //Если контрольная сумма совпадает, то конвертация и возврат полученных значений 132 | if(sensor->type == DHT22) { 133 | data.hum = (float)(((uint16_t)rawData[0] << 8) | rawData[1]) * 0.1f; 134 | //Проверка на отрицательность температуры 135 | if(!(rawData[2] & (1 << 7))) { 136 | data.temp = (float)(((uint16_t)rawData[2] << 8) | rawData[3]) * 0.1f; 137 | } else { 138 | rawData[2] &= ~(1 << 7); 139 | data.temp = (float)(((uint16_t)rawData[2] << 8) | rawData[3]) * -0.1f; 140 | } 141 | } 142 | if(sensor->type == DHT11) { 143 | data.hum = (float)rawData[0]; 144 | data.temp = (float)rawData[2]; 145 | //DHT11 производства ASAIR имеют дробную часть в температуре 146 | //А ещё температуру измеряет от -20 до +60 *С 147 | //Вот прикол, да? 148 | if(rawData[3] != 0) { 149 | //Проверка знака 150 | if(!(rawData[3] & (1 << 7))) { 151 | //Добавление положительной дробной части 152 | data.temp += rawData[3] * 0.1f; 153 | } else { 154 | //А тут делаем отрицательное значение 155 | rawData[3] &= ~(1 << 7); 156 | data.temp += rawData[3] * 0.1f; 157 | data.temp *= -1; 158 | } 159 | } 160 | } 161 | } 162 | 163 | #if DHT_POLLING_CONTROL == 1 164 | sensor->lastHum = data.hum; 165 | sensor->lastTemp = data.temp; 166 | #endif 167 | 168 | return data; 169 | } -------------------------------------------------------------------------------- /scenes/DHTMon_sensorActions_scene.c: -------------------------------------------------------------------------------- 1 | #include "../quenon_dht_mon.h" 2 | 3 | //Текущий вид 4 | static View* view; 5 | //Список 6 | static VariableItemList* variable_item_list; 7 | 8 | /* ================== Информация о датчике ================== */ 9 | /** 10 | * @brief Функция обработки нажатия кнопки "Назад" 11 | * 12 | * @param context Указатель на данные приложения 13 | * @return ID вида в который нужно переключиться 14 | */ 15 | static uint32_t infoWidget_exitCallback(void* context) { 16 | PluginData* app = context; 17 | UNUSED(app); 18 | //Возвращаем ID вида, в который нужно вернуться 19 | return SENSOR_ACTIONS_VIEW; 20 | } 21 | /** 22 | * @brief Обработчик нажатий на кнопку в виджете 23 | * 24 | * @param result Какая из кнопок была нажата 25 | * @param type Тип нажатия 26 | * @param context Указатель на данные плагина 27 | */ 28 | static void infoWidget_callback(GuiButtonType result, InputType type, void* context) { 29 | PluginData* app = context; 30 | //Коротко нажата левая кнопка (Back) 31 | if(result == GuiButtonTypeLeft && type == InputTypeShort) { 32 | view_dispatcher_switch_to_view(app->view_dispatcher, SENSOR_ACTIONS_VIEW); 33 | } 34 | } 35 | /** 36 | * @brief Создание виджета информации о датчике 37 | * 38 | * @param app Указатель на данные плагина 39 | */ 40 | static void sensorInfo_widget(PluginData* app) { 41 | //Очистка виджета 42 | widget_reset(app->widget); 43 | //Добавление кнопок 44 | widget_add_button_element(app->widget, GuiButtonTypeLeft, "Back", infoWidget_callback, app); 45 | 46 | char str[32]; 47 | snprintf(str, sizeof(str), "\e#%s\e#", app->currentSensorEdit->name); 48 | widget_add_text_box_element(app->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, str, false); 49 | snprintf(str, sizeof(str), "\e#Type:\e# %s", app->currentSensorEdit->type ? "DHT22" : "DHT11"); 50 | widget_add_text_box_element(app->widget, 0, 0, 128, 47, AlignLeft, AlignCenter, str, false); 51 | snprintf( 52 | str, sizeof(str), "\e#GPIO:\e# %s", DHTMon_GPIO_getName(app->currentSensorEdit->GPIO)); 53 | widget_add_text_box_element(app->widget, 0, 0, 128, 72, AlignLeft, AlignCenter, str, false); 54 | view_set_previous_callback(widget_get_view(app->widget), infoWidget_exitCallback); 55 | view_dispatcher_switch_to_view(app->view_dispatcher, WIDGET_VIEW); 56 | } 57 | 58 | /* ================== Подтверждение удаления ================== */ 59 | /** 60 | * @brief Функция обработки нажатия кнопки "Назад" 61 | * 62 | * @param context Указатель на данные приложения 63 | * @return ID вида в который нужно переключиться 64 | */ 65 | static uint32_t deleteWidget_exitCallback(void* context) { 66 | PluginData* app = context; 67 | UNUSED(app); 68 | //Возвращаем ID вида, в который нужно вернуться 69 | return SENSOR_ACTIONS_VIEW; 70 | } 71 | /** 72 | * @brief Обработчик нажатий на кнопку в виджете 73 | * 74 | * @param result Какая из кнопок была нажата 75 | * @param type Тип нажатия 76 | * @param context Указатель на данные плагина 77 | */ 78 | static void deleteWidget_callback(GuiButtonType result, InputType type, void* context) { 79 | PluginData* app = context; 80 | //Коротко нажата левая кнопка (Cancel) 81 | if(result == GuiButtonTypeLeft && type == InputTypeShort) { 82 | view_dispatcher_switch_to_view(app->view_dispatcher, SENSOR_ACTIONS_VIEW); 83 | } 84 | //Коротко нажата правая кнопка (Delete) 85 | if(result == GuiButtonTypeRight && type == InputTypeShort) { 86 | //Удаление датчика 87 | DHTMon_sensor_delete(app->currentSensorEdit); 88 | //Выход из меню 89 | view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_NONE); 90 | } 91 | } 92 | /** 93 | * @brief Создание виджета удаления датчика 94 | * 95 | * @param app Указатель на данные плагина 96 | */ 97 | static void sensorDelete_widget(PluginData* app) { 98 | //Очистка виджета 99 | widget_reset(app->widget); 100 | //Добавление кнопок 101 | widget_add_button_element( 102 | app->widget, GuiButtonTypeLeft, "Cancel", deleteWidget_callback, app); 103 | widget_add_button_element( 104 | app->widget, GuiButtonTypeRight, "Delete", deleteWidget_callback, app); 105 | 106 | char delete_str[32]; 107 | snprintf(delete_str, sizeof(delete_str), "\e#Delete %s?\e#", app->currentSensorEdit->name); 108 | widget_add_text_box_element( 109 | app->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, delete_str, false); 110 | snprintf( 111 | delete_str, 112 | sizeof(delete_str), 113 | "\e#Type:\e# %s", 114 | app->currentSensorEdit->type ? "DHT22" : "DHT11"); 115 | widget_add_text_box_element( 116 | app->widget, 0, 0, 128, 47, AlignLeft, AlignCenter, delete_str, false); 117 | snprintf( 118 | delete_str, 119 | sizeof(delete_str), 120 | "\e#GPIO:\e# %s", 121 | DHTMon_GPIO_getName(app->currentSensorEdit->GPIO)); 122 | widget_add_text_box_element( 123 | app->widget, 0, 0, 128, 72, AlignLeft, AlignCenter, delete_str, false); 124 | view_set_previous_callback(widget_get_view(app->widget), deleteWidget_exitCallback); 125 | view_dispatcher_switch_to_view(app->view_dispatcher, WIDGET_VIEW); 126 | } 127 | 128 | /* ================== Меню действий ================== */ 129 | /** 130 | * @brief Функция обработки нажатия средней кнопки 131 | * 132 | * @param context Указатель на данные приложения 133 | * @param index На каком элементе списка была нажата кнопка 134 | */ 135 | static void enterCallback(void* context, uint32_t index) { 136 | PluginData* app = context; 137 | if(index == 0) { 138 | sensorInfo_widget(app); 139 | } 140 | if(index == 1) { 141 | sensorEdit_scene(app); 142 | } 143 | if(index == 2) { 144 | sensorDelete_widget(app); 145 | } 146 | } 147 | 148 | /** 149 | * @brief Функция обработки нажатия кнопки "Назад" 150 | * 151 | * @param context Указатель на данные приложения 152 | * @return ID вида в который нужно переключиться 153 | */ 154 | static uint32_t actions_exitCallback(void* context) { 155 | PluginData* app = context; 156 | UNUSED(app); 157 | //Возвращаем ID вида, в который нужно вернуться 158 | return MAIN_MENU_VIEW; 159 | } 160 | 161 | /** 162 | * @brief Создание списка действий с указанным датчиком 163 | * 164 | * @param app Указатель на данные плагина 165 | */ 166 | void sensorActions_sceneCreate(PluginData* app) { 167 | variable_item_list = variable_item_list_alloc(); 168 | //Сброс всех элементов меню 169 | variable_item_list_reset(variable_item_list); 170 | //Добавление элементов в список 171 | variable_item_list_add(variable_item_list, "Info", 0, NULL, NULL); 172 | variable_item_list_add(variable_item_list, "Edit", 0, NULL, NULL); 173 | variable_item_list_add(variable_item_list, "Delete", 0, NULL, NULL); 174 | 175 | //Добавление колбека на нажатие средней кнопки 176 | variable_item_list_set_enter_callback(variable_item_list, enterCallback, app); 177 | 178 | //Создание вида из списка 179 | view = variable_item_list_get_view(variable_item_list); 180 | //Добавление колбека на нажатие кнопки "Назад" 181 | view_set_previous_callback(view, actions_exitCallback); 182 | //Добавление вида в диспетчер 183 | view_dispatcher_add_view(app->view_dispatcher, SENSOR_ACTIONS_VIEW, view); 184 | } 185 | void sensorActions_scene(PluginData* app) { 186 | //Сброс выбранного пункта в ноль 187 | variable_item_list_set_selected_item(variable_item_list, 0); 188 | //Переключение на наш вид 189 | view_dispatcher_switch_to_view(app->view_dispatcher, SENSOR_ACTIONS_VIEW); 190 | } 191 | 192 | void sensorActions_screneRemove(void) { 193 | variable_item_list_free(variable_item_list); 194 | } 195 | -------------------------------------------------------------------------------- /quenon_dht_mon.c: -------------------------------------------------------------------------------- 1 | #include "quenon_dht_mon.h" 2 | #include 3 | 4 | //Порты ввода/вывода, которые не были обозначены в общем списке 5 | const GpioPin SWC_10 = {.pin = LL_GPIO_PIN_14, .port = GPIOA}; 6 | const GpioPin SIO_12 = {.pin = LL_GPIO_PIN_13, .port = GPIOA}; 7 | const GpioPin TX_13 = {.pin = LL_GPIO_PIN_6, .port = GPIOB}; 8 | const GpioPin RX_14 = {.pin = LL_GPIO_PIN_7, .port = GPIOB}; 9 | 10 | //Количество доступных портов ввода/вывода 11 | #define GPIO_ITEMS (sizeof(gpio_item) / sizeof(GpioItem)) 12 | 13 | //Перечень достуных портов ввода/вывода 14 | static const GpioItem gpio_item[] = { 15 | {2, "2 (A7)", &gpio_ext_pa7}, 16 | {3, "3 (A6)", &gpio_ext_pa6}, 17 | {4, "4 (A4)", &gpio_ext_pa4}, 18 | {5, "5 (B3)", &gpio_ext_pb3}, 19 | {6, "6 (B2)", &gpio_ext_pb2}, 20 | {7, "7 (C3)", &gpio_ext_pc3}, 21 | {10, " 10(SWC) ", &SWC_10}, 22 | {12, "12 (SIO)", &SIO_12}, 23 | {13, "13 (TX)", &TX_13}, 24 | {14, "14 (RX)", &RX_14}, 25 | {15, "15 (C1)", &gpio_ext_pc1}, 26 | {16, "16 (C0)", &gpio_ext_pc0}, 27 | {17, "17 (1W)", &ibutton_gpio}}; 28 | 29 | //Данные плагина 30 | static PluginData* app; 31 | 32 | uint8_t DHTMon_GPIO_to_int(const GpioPin* gpio) { 33 | if(gpio == NULL) return 255; 34 | for(uint8_t i = 0; i < GPIO_ITEMS; i++) { 35 | if(gpio_item[i].pin->pin == gpio->pin && gpio_item[i].pin->port == gpio->port) { 36 | return gpio_item[i].num; 37 | } 38 | } 39 | return 255; 40 | } 41 | 42 | const GpioPin* DHTMon_GPIO_form_int(uint8_t name) { 43 | for(uint8_t i = 0; i < GPIO_ITEMS; i++) { 44 | if(gpio_item[i].num == name) { 45 | return gpio_item[i].pin; 46 | } 47 | } 48 | return NULL; 49 | } 50 | 51 | const GpioPin* DHTMon_GPIO_from_index(uint8_t index) { 52 | if(index > GPIO_ITEMS) return NULL; 53 | return gpio_item[index].pin; 54 | } 55 | 56 | uint8_t DHTMon_GPIO_to_index(const GpioPin* gpio) { 57 | if(gpio == NULL) return 255; 58 | for(uint8_t i = 0; i < GPIO_ITEMS; i++) { 59 | if(gpio_item[i].pin->pin == gpio->pin && gpio_item[i].pin->port == gpio->port) { 60 | return i; 61 | } 62 | } 63 | return 255; 64 | } 65 | 66 | const char* DHTMon_GPIO_getName(const GpioPin* gpio) { 67 | if(gpio == NULL) return NULL; 68 | for(uint8_t i = 0; i < GPIO_ITEMS; i++) { 69 | if(gpio_item[i].pin->pin == gpio->pin && gpio_item[i].pin->port == gpio->port) { 70 | return gpio_item[i].name; 71 | } 72 | } 73 | return NULL; 74 | } 75 | 76 | void DHTMon_sensors_init(void) { 77 | //Включение 5V если на порту 1 FZ его нет 78 | if(furi_hal_power_is_otg_enabled() != true) { 79 | furi_hal_power_enable_otg(); 80 | } 81 | 82 | //Настройка GPIO загруженных датчиков 83 | for(uint8_t i = 0; i < app->sensors_count; i++) { 84 | //Высокий уровень по умолчанию 85 | furi_hal_gpio_write(app->sensors[i].GPIO, true); 86 | //Режим работы - OpenDrain, подтяжка включается на всякий случай 87 | furi_hal_gpio_init( 88 | app->sensors[i].GPIO, //Порт FZ 89 | GpioModeOutputOpenDrain, //Режим работы - открытый сток 90 | GpioPullUp, //Принудительная подтяжка линии данных к питанию 91 | GpioSpeedVeryHigh); //Скорость работы - максимальная 92 | } 93 | } 94 | 95 | void DHTMon_sensors_deinit(void) { 96 | //Возврат исходного состояния 5V 97 | if(app->last_OTG_State != true) { 98 | furi_hal_power_disable_otg(); 99 | } 100 | 101 | //Перевод портов GPIO в состояние по умолчанию 102 | for(uint8_t i = 0; i < app->sensors_count; i++) { 103 | furi_hal_gpio_init( 104 | app->sensors[i].GPIO, //Порт FZ 105 | GpioModeAnalog, //Режим работы - аналог 106 | GpioPullNo, //Отключение подтяжки 107 | GpioSpeedLow); //Скорость работы - низкая 108 | //Установка низкого уровня 109 | furi_hal_gpio_write(app->sensors[i].GPIO, false); 110 | } 111 | } 112 | 113 | bool DHTMon_sensor_check(DHT_sensor* sensor) { 114 | /* Проверка имени */ 115 | //1) Строка должна быть длиной от 1 до 10 символов 116 | //2) Первый символ строки должен быть только 0-9, A-Z, a-z и _ 117 | if(strlen(sensor->name) == 0 || strlen(sensor->name) > 10 || 118 | (!(sensor->name[0] >= '0' && sensor->name[0] <= '9') && 119 | !(sensor->name[0] >= 'A' && sensor->name[0] <= 'Z') && 120 | !(sensor->name[0] >= 'a' && sensor->name[0] <= 'z') && !(sensor->name[0] == '_'))) { 121 | FURI_LOG_D(APP_NAME, "Sensor [%s] name check failed\r\n", sensor->name); 122 | return false; 123 | } 124 | //Проверка GPIO 125 | if(DHTMon_GPIO_to_int(sensor->GPIO) == 255) { 126 | FURI_LOG_D( 127 | APP_NAME, 128 | "Sensor [%s] GPIO check failed: %d\r\n", 129 | sensor->name, 130 | DHTMon_GPIO_to_int(sensor->GPIO)); 131 | return false; 132 | } 133 | //Проверка типа датчика 134 | if(sensor->type != DHT11 && sensor->type != DHT22) { 135 | FURI_LOG_D(APP_NAME, "Sensor [%s] type check failed: %d\r\n", sensor->name, sensor->type); 136 | return false; 137 | } 138 | 139 | //Возврат истины если всё ок 140 | FURI_LOG_D(APP_NAME, "Sensor [%s] all checks passed\r\n", sensor->name); 141 | return true; 142 | } 143 | 144 | void DHTMon_sensor_delete(DHT_sensor* sensor) { 145 | if(sensor == NULL) return; 146 | //Делаем параметры датчика неверными 147 | sensor->name[0] = '\0'; 148 | sensor->type = 255; 149 | //Теперь сохраняем текущие датчики. Сохранятор не сохранит неисправный датчик 150 | DHTMon_sensors_save(); 151 | //Перезагружаемся с SD-карты 152 | DHTMon_sensors_reload(); 153 | } 154 | 155 | uint8_t DHTMon_sensors_save(void) { 156 | //Выделение памяти для потока 157 | app->file_stream = file_stream_alloc(app->storage); 158 | uint8_t savedSensorsCount = 0; 159 | //Переменная пути к файлу 160 | FuriString* filepath = furi_string_alloc(); 161 | //Составление пути к файлу 162 | furi_string_printf(filepath, "%s/%s", APP_PATH_FOLDER, APP_FILENAME); 163 | 164 | //Открытие потока. Если поток открылся, то выполнение сохранения датчиков 165 | if(file_stream_open( 166 | app->file_stream, furi_string_get_cstr(filepath), FSAM_READ_WRITE, FSOM_CREATE_ALWAYS)) { 167 | const char template[] = 168 | "#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"; 169 | stream_write(app->file_stream, (uint8_t*)template, strlen(template)); 170 | //Сохранение датчиков 171 | for(uint8_t i = 0; i < app->sensors_count; i++) { 172 | //Если параметры датчика верны, то сохраняемся 173 | if(DHTMon_sensor_check(&app->sensors[i])) { 174 | stream_write_format( 175 | app->file_stream, 176 | "%s %d %d\n", 177 | app->sensors[i].name, 178 | app->sensors[i].type, 179 | DHTMon_GPIO_to_int(app->sensors[i].GPIO)); 180 | savedSensorsCount++; 181 | } 182 | } 183 | } else { 184 | //TODO: печать ошибки на экран 185 | FURI_LOG_E(APP_NAME, "cannot create sensors file\r\n"); 186 | } 187 | stream_free(app->file_stream); 188 | 189 | return savedSensorsCount; 190 | } 191 | 192 | bool DHTMon_sensors_load(void) { 193 | //Обнуление количества датчиков 194 | app->sensors_count = -1; 195 | //Очистка предыдущих датчиков 196 | memset(app->sensors, 0, sizeof(app->sensors)); 197 | 198 | //Открытие файла на SD-карте 199 | //Выделение памяти для потока 200 | app->file_stream = file_stream_alloc(app->storage); 201 | //Переменная пути к файлу 202 | FuriString* filepath = furi_string_alloc(); 203 | //Составление пути к файлу 204 | furi_string_printf(filepath, "%s/%s", APP_PATH_FOLDER, APP_FILENAME); 205 | //Открытие потока к файлу 206 | if(!file_stream_open( 207 | app->file_stream, furi_string_get_cstr(filepath), FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) { 208 | //Если файл отсутствует, то создание болванки 209 | FURI_LOG_W(APP_NAME, "Missing sensors file. Creating new file\r\n"); 210 | app->sensors_count = 0; 211 | stream_free(app->file_stream); 212 | DHTMon_sensors_save(); 213 | return false; 214 | } 215 | //Вычисление размера файла 216 | size_t file_size = stream_size(app->file_stream); 217 | if(file_size == (size_t)0) { 218 | //Выход если файл пустой 219 | FURI_LOG_W(APP_NAME, "Sensors file is empty\r\n"); 220 | app->sensors_count = 0; 221 | stream_free(app->file_stream); 222 | return false; 223 | } 224 | 225 | //Выделение памяти под загрузку файла 226 | uint8_t* file_buf = malloc(file_size); 227 | //Опустошение буфера файла 228 | memset(file_buf, 0, file_size); 229 | //Загрузка файла 230 | if(stream_read(app->file_stream, file_buf, file_size) != file_size) { 231 | //Выход при ошибке чтения 232 | FURI_LOG_E(APP_NAME, "Error reading sensor file\r\n"); 233 | app->sensors_count = 0; 234 | stream_free(app->file_stream); 235 | return false; 236 | } 237 | //Построчное чтение файла 238 | //Указатель на начало строки 239 | FuriString* file = furi_string_alloc_set_str((char*)file_buf); 240 | //Сколько байт до конца строки 241 | size_t line_end = 0; 242 | while(line_end != STRING_FAILURE && app->sensors_count < MAX_SENSORS) { 243 | if(((char*)(file_buf + line_end))[1] != '#') { 244 | DHT_sensor s = {0}; 245 | int type, port; 246 | char name[11] = {0}; 247 | sscanf(((char*)(file_buf + line_end)), "%s %d %d", name, &type, &port); 248 | s.type = type; 249 | s.GPIO = DHTMon_GPIO_form_int(port); 250 | 251 | name[10] = '\0'; 252 | strcpy(s.name, name); 253 | //Если данные корректны, то 254 | if(DHTMon_sensor_check(&s) == true) { 255 | //Установка нуля при первом датчике 256 | if(app->sensors_count == -1) app->sensors_count = 0; 257 | //Добавление датчика в общий список 258 | app->sensors[app->sensors_count] = s; 259 | //Увеличение количества загруженных датчиков 260 | app->sensors_count++; 261 | } 262 | } 263 | line_end = furi_string_search_char(file, '\n', line_end + 1); 264 | } 265 | stream_free(app->file_stream); 266 | free(file_buf); 267 | 268 | //Обнуление количества датчиков если ни один из них не был загружен 269 | if(app->sensors_count == -1) app->sensors_count = 0; 270 | 271 | //Инициализация портов датчиков если таковые есть 272 | if(app->sensors_count > 0) { 273 | DHTMon_sensors_init(); 274 | return true; 275 | } else { 276 | return false; 277 | } 278 | return false; 279 | } 280 | 281 | bool DHTMon_sensors_reload(void) { 282 | DHTMon_sensors_deinit(); 283 | return DHTMon_sensors_load(); 284 | } 285 | 286 | /** 287 | * @brief Обработчик отрисовки экрана 288 | * 289 | * @param canvas Указатель на холст 290 | * @param ctx Данные плагина 291 | */ 292 | static void render_callback(Canvas* const canvas, void* ctx) { 293 | PluginData* app = acquire_mutex((ValueMutex*)ctx, 25); 294 | if(app == NULL) { 295 | return; 296 | } 297 | //Вызов отрисовки главного экрана 298 | scene_main(canvas, app); 299 | 300 | release_mutex((ValueMutex*)ctx, app); 301 | } 302 | 303 | /** 304 | * @brief Обработчик нажатия кнопок главного экрана 305 | * 306 | * @param input_event Указатель на событие 307 | * @param event_queue Указатель на очередь событий 308 | */ 309 | static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { 310 | furi_assert(event_queue); 311 | 312 | PluginEvent event = {.type = EventTypeKey, .input = *input_event}; 313 | furi_message_queue_put(event_queue, &event, FuriWaitForever); 314 | } 315 | 316 | /** 317 | * @brief Выделение места под переменные плагина 318 | * 319 | * @return true Если всё прошло успешно 320 | * @return false Если в процессе загрузки произошла ошибка 321 | */ 322 | static bool DHTMon_alloc(void) { 323 | //Выделение места под данные плагина 324 | app = malloc(sizeof(PluginData)); 325 | //Выделение места под очередь событий 326 | app->event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); 327 | 328 | //Обнуление количества датчиков 329 | app->sensors_count = -1; 330 | 331 | //Инициализация мутекса 332 | if(!init_mutex(&app->state_mutex, app, sizeof(PluginData))) { 333 | FURI_LOG_E(APP_NAME, "cannot create mutex\r\n"); 334 | return false; 335 | } 336 | 337 | // Set system callbacks 338 | app->view_port = view_port_alloc(); 339 | view_port_draw_callback_set(app->view_port, render_callback, &app->state_mutex); 340 | view_port_input_callback_set(app->view_port, input_callback, app->event_queue); 341 | 342 | // Open GUI and register view_port 343 | app->gui = furi_record_open(RECORD_GUI); 344 | gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); 345 | 346 | app->view_dispatcher = view_dispatcher_alloc(); 347 | 348 | sensorActions_sceneCreate(app); 349 | sensorEdit_sceneCreate(app); 350 | 351 | app->widget = widget_alloc(); 352 | view_dispatcher_add_view(app->view_dispatcher, WIDGET_VIEW, widget_get_view(app->widget)); 353 | 354 | app->text_input = text_input_alloc(); 355 | view_dispatcher_add_view( 356 | app->view_dispatcher, TEXTINPUT_VIEW, text_input_get_view(app->text_input)); 357 | 358 | view_dispatcher_enable_queue(app->view_dispatcher); 359 | view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); 360 | 361 | //Уведомления 362 | app->notifications = furi_record_open(RECORD_NOTIFICATION); 363 | 364 | //Подготовка хранилища 365 | app->storage = furi_record_open(RECORD_STORAGE); 366 | storage_common_mkdir(app->storage, APP_PATH_FOLDER); 367 | app->file_stream = file_stream_alloc(app->storage); 368 | 369 | return true; 370 | } 371 | 372 | /** 373 | * @brief Освыбождение памяти после работы приложения 374 | */ 375 | static void DHTMon_free(void) { 376 | //Автоматическое управление подсветкой 377 | notification_message(app->notifications, &sequence_display_backlight_enforce_auto); 378 | 379 | furi_record_close(RECORD_STORAGE); 380 | furi_record_close(RECORD_NOTIFICATION); 381 | 382 | text_input_free(app->text_input); 383 | widget_free(app->widget); 384 | sensorEdit_sceneRemove(); 385 | sensorActions_screneRemove(); 386 | view_dispatcher_free(app->view_dispatcher); 387 | 388 | furi_record_close(RECORD_GUI); 389 | 390 | view_port_enabled_set(app->view_port, false); 391 | gui_remove_view_port(app->gui, app->view_port); 392 | 393 | view_port_free(app->view_port); 394 | furi_message_queue_free(app->event_queue); 395 | delete_mutex(&app->state_mutex); 396 | 397 | free(app); 398 | } 399 | 400 | /** 401 | * @brief Точка входа в приложение 402 | * 403 | * @return Код ошибки 404 | */ 405 | int32_t quenon_dht_mon_app() { 406 | if(!DHTMon_alloc()) { 407 | DHTMon_free(); 408 | return 255; 409 | } 410 | //Постоянное свечение подсветки 411 | notification_message(app->notifications, &sequence_display_backlight_enforce_on); 412 | //Сохранение состояния наличия 5V на порту 1 FZ 413 | app->last_OTG_State = furi_hal_power_is_otg_enabled(); 414 | 415 | //Загрузка датчиков с SD-карты 416 | DHTMon_sensors_load(); 417 | 418 | app->currentSensorEdit = &app->sensors[0]; 419 | 420 | PluginEvent event; 421 | for(bool processing = true; processing;) { 422 | FuriStatus event_status = furi_message_queue_get(app->event_queue, &event, 100); 423 | 424 | acquire_mutex_block(&app->state_mutex); 425 | 426 | if(event_status == FuriStatusOk) { 427 | // press events 428 | if(event.type == EventTypeKey) { 429 | if(event.input.type == InputTypePress) { 430 | switch(event.input.key) { 431 | case InputKeyUp: 432 | break; 433 | case InputKeyDown: 434 | break; 435 | case InputKeyRight: 436 | break; 437 | case InputKeyLeft: 438 | break; 439 | case InputKeyMAX: 440 | break; 441 | case InputKeyOk: 442 | view_port_update(app->view_port); 443 | release_mutex(&app->state_mutex, app); 444 | mainMenu_scene(app); 445 | break; 446 | case InputKeyBack: 447 | processing = false; 448 | break; 449 | } 450 | } 451 | } 452 | } else { 453 | FURI_LOG_D(APP_NAME, "FuriMessageQueue: event timeout"); 454 | // event timeout 455 | } 456 | 457 | view_port_update(app->view_port); 458 | release_mutex(&app->state_mutex, app); 459 | } 460 | //Освобождение памяти и деинициализация 461 | DHTMon_sensors_deinit(); 462 | DHTMon_free(); 463 | 464 | return 0; 465 | } 466 | //TODO: Обработка ошибок 467 | //TODO: Пропуск использованных портов в меню добавления датчиков --------------------------------------------------------------------------------