Merge pull request #147 from quen0n/dev

New plugin - DHT Monitor
This commit is contained in:
MX 2022-11-02 01:44:22 +03:00 committed by GitHub
commit f3d45ab350
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1348 additions and 0 deletions

View File

@ -0,0 +1,169 @@
#include "DHT.h"
#define lineDown() furi_hal_gpio_write(sensor->GPIO, false)
#define lineUp() furi_hal_gpio_write(sensor->GPIO, true)
#define getLine() furi_hal_gpio_read(sensor->GPIO)
#define Delay(d) furi_delay_ms(d)
DHT_data DHT_getData(DHT_sensor* sensor) {
DHT_data data = {-128.0f, -128.0f};
#if DHT_POLLING_CONTROL == 1
/* Ограничение по частоте опроса датчика */
//Определение интервала опроса в зависимости от датчика
uint16_t pollingInterval;
if(sensor->type == DHT11) {
pollingInterval = DHT_POLLING_INTERVAL_DHT11;
} else {
pollingInterval = DHT_POLLING_INTERVAL_DHT22;
}
//Если интервал маленький, то возврат последнего удачного значения
if((furi_get_tick() - sensor->lastPollingTime < pollingInterval) &&
sensor->lastPollingTime != 0) {
data.hum = sensor->lastHum;
data.temp = sensor->lastTemp;
return data;
}
sensor->lastPollingTime = furi_get_tick() + 1;
#endif
//Опускание линии данных на 18 мс
lineDown();
#ifdef DHT_IRQ_CONTROL
//Выключение прерываний, чтобы ничто не мешало обработке данных
__disable_irq();
#endif
Delay(18);
//Подъём линии
lineUp();
/* Ожидание ответа от датчика */
uint16_t timeout = 0;
while(!getLine()) {
timeout++;
if(timeout > DHT_TIMEOUT) {
#ifdef DHT_IRQ_CONTROL
__enable_irq();
#endif
//Если датчик не отозвался, значит его точно нет
//Обнуление последнего удачного значения, чтобы
//не получать фантомные значения
sensor->lastHum = -128.0f;
sensor->lastTemp = -128.0f;
return data;
}
}
//Ожидание спада
while(getLine()) {
timeout++;
if(timeout > DHT_TIMEOUT) {
#ifdef DHT_IRQ_CONTROL
__enable_irq();
#endif
//Если датчик не отозвался, значит его точно нет
//Обнуление последнего удачного значения, чтобы
//не получать фантомные значения
sensor->lastHum = -128.0f;
sensor->lastTemp = -128.0f;
return data;
}
}
timeout = 0;
//Ожидание подъёма
while(!getLine()) {
timeout++;
if(timeout > DHT_TIMEOUT) {
if(timeout > DHT_TIMEOUT) {
#ifdef DHT_IRQ_CONTROL
__enable_irq();
#endif
//Если датчик не отозвался, значит его точно нет
//Обнуление последнего удачного значения, чтобы
//не получать фантомные значения
sensor->lastHum = -128.0f;
sensor->lastTemp = -128.0f;
return data;
}
}
}
timeout = 0;
//Ожидание спада
while(getLine()) {
timeout++;
if(timeout > DHT_TIMEOUT) {
#ifdef DHT_IRQ_CONTROL
__enable_irq();
#endif
//Если датчик не отозвался, значит его точно нет
//Обнуление последнего удачного значения, чтобы
//не получать фантомные значения
sensor->lastHum = -128.0f;
sensor->lastTemp = -128.0f;
return data;
}
}
/* Чтение ответа от датчика */
uint8_t rawData[5] = {0, 0, 0, 0, 0};
for(uint8_t a = 0; a < 5; a++) {
for(uint8_t b = 7; b != 255; b--) {
uint16_t hT = 0, lT = 0;
//Пока линия в низком уровне, инкремент переменной lT
while(!getLine() && lT != 65535) lT++;
//Пока линия в высоком уровне, инкремент переменной hT
timeout = 0;
while(getLine() && hT != 65535) hT++;
//Если hT больше lT, то пришла единица
if(hT > lT) rawData[a] |= (1 << b);
}
}
#ifdef DHT_IRQ_CONTROL
//Включение прерываний после приёма данных
__enable_irq();
#endif
/* Проверка целостности данных */
if((uint8_t)(rawData[0] + rawData[1] + rawData[2] + rawData[3]) == rawData[4]) {
//Если контрольная сумма совпадает, то конвертация и возврат полученных значений
if(sensor->type == DHT22) {
data.hum = (float)(((uint16_t)rawData[0] << 8) | rawData[1]) * 0.1f;
//Проверка на отрицательность температуры
if(!(rawData[2] & (1 << 7))) {
data.temp = (float)(((uint16_t)rawData[2] << 8) | rawData[3]) * 0.1f;
} else {
rawData[2] &= ~(1 << 7);
data.temp = (float)(((uint16_t)rawData[2] << 8) | rawData[3]) * -0.1f;
}
}
if(sensor->type == DHT11) {
data.hum = (float)rawData[0];
data.temp = (float)rawData[2];
//DHT11 производства ASAIR имеют дробную часть в температуре
//А ещё температуру измеряет от -20 до +60 *С
//Вот прикол, да?
if(rawData[3] != 0) {
//Проверка знака
if(!(rawData[3] & (1 << 7))) {
//Добавление положительной дробной части
data.temp += rawData[3] * 0.1f;
} else {
//А тут делаем отрицательное значение
rawData[3] &= ~(1 << 7);
data.temp += rawData[3] * 0.1f;
data.temp *= -1;
}
}
}
}
#if DHT_POLLING_CONTROL == 1
sensor->lastHum = data.hum;
sensor->lastTemp = data.temp;
#endif
return data;
}

View File

@ -0,0 +1,39 @@
#ifndef DHT_H_
#define DHT_H_
#include <furi_hal_resources.h>
/* Настройки */
#define DHT_TIMEOUT 65534 //Количество итераций, после которых функция вернёт пустые значения
#define DHT_POLLING_CONTROL 1 //Включение проверки частоты опроса датчика
#define DHT_POLLING_INTERVAL_DHT11 \
2000 //Интервал опроса DHT11 (0.5 Гц по даташиту). Можно поставить 1500, будет работать
#define DHT_POLLING_INTERVAL_DHT22 1000 //Интервал опроса DHT22 (1 Гц по даташиту)
#define DHT_IRQ_CONTROL //Выключать прерывания во время обмена данных с датчиком
/* Структура возвращаемых датчиком данных */
typedef struct {
float hum;
float temp;
} DHT_data;
/* Тип используемого датчика */
typedef enum { DHT11, DHT22 } DHT_type;
/* Структура объекта датчика */
typedef struct {
char name[11];
const GpioPin* GPIO; //Пин датчика
DHT_type type; //Тип датчика (DHT11 или DHT22)
//Контроль частоты опроса датчика. Значения не заполнять!
#if DHT_POLLING_CONTROL == 1
uint32_t lastPollingTime; //Время последнего опроса датчика
float lastTemp; //Последнее значение температуры
float lastHum; //Последнее значение влажности
#endif
} DHT_sensor;
/* Прототипы функций */
DHT_data DHT_getData(DHT_sensor* sensor); //Получить данные с датчика
#endif

View File

@ -0,0 +1,13 @@
App(
appid="quenon_dht_mon",
name="[DHT] Temp. Monitor",
apptype=FlipperAppType.EXTERNAL,
entry_point="quenon_dht_mon_app",
cdefines=["QUENON_DHT_MON"],
requires=[
"gui",
],
fap_category="GPIO",
fap_icon="icon.png",
stack_size=2 * 1024,
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,460 @@
#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: Пропуск использованных портов в меню добавления датчиков

View File

@ -0,0 +1,176 @@
#ifndef QUENON_DHT_MON
#define QUENON_DHT_MON
#include <stdlib.h>
#include <stdio.h>
#include <furi.h>
#include <furi_hal_power.h>
#include <gui/gui.h>
#include <gui/modules/submenu.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/text_input.h>
#include <gui/modules/widget.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <toolbox/stream/file_stream.h>
#include <input/input.h>
#include "DHT.h"
#define APP_NAME "DHT_monitor"
#define APP_PATH_FOLDER "/ext/dht_monitor"
#define APP_FILENAME "sensors.txt"
#define MAX_SENSORS 5
// //Виды менюшек
typedef enum {
MAIN_MENU_VIEW,
ADDSENSOR_MENU_VIEW,
TEXTINPUT_VIEW,
SENSOR_ACTIONS_VIEW,
WIDGET_VIEW,
} MENU_VIEWS;
typedef enum {
EventTypeTick,
EventTypeKey,
} EventType;
typedef struct {
EventType type;
InputEvent input;
} PluginEvent;
typedef struct {
const uint8_t num;
const char* name;
const GpioPin* pin;
} GpioItem;
//Структура с данными плагина
typedef struct {
//Очередь сообщений
FuriMessageQueue* event_queue;
//Мутекс
ValueMutex state_mutex;
//Вьюпорт
ViewPort* view_port;
//GUI
Gui* gui;
NotificationApp* notifications;
ViewDispatcher* view_dispatcher;
View* view;
TextInput* text_input;
VariableItem* item;
Widget* widget;
char txtbuff[30]; //Буффер для печати строк на экране
bool last_OTG_State; //Состояние OTG до запуска приложения
Storage* storage; //Хранилище датчиков
Stream* file_stream; //Поток файла с датчиками
int8_t sensors_count; // Количество загруженных датчиков
DHT_sensor sensors[MAX_SENSORS]; //Сохранённые датчики
DHT_data data; //Инфа из датчика
DHT_sensor* currentSensorEdit; //Указатель на редактируемый датчик
} PluginData;
/* ================== Работа с GPIO ================== */
/**
* @brief Конвертация GPIO в его номер на корпусе FZ
*
* @param gpio Указатель на преобразовываемый GPIO
* @return Номер порта на корпусе FZ
*/
uint8_t DHTMon_GPIO_to_int(const GpioPin* gpio);
/**
* @brief Конвертация номера порта на корпусе FZ в GPIO
*
* @param name Номер порта на корпусе FZ
* @return Указатель на GPIO при успехе, NULL при ошибке
*/
const GpioPin* DHTMon_GPIO_form_int(uint8_t name);
/**
* @brief Преобразование порядкового номера порта в GPIO
*
* @param index Индекс порта от 0 до GPIO_ITEMS-1
* @return Указатель на GPIO при успехе, NULL при ошибке
*/
const GpioPin* DHTMon_GPIO_from_index(uint8_t index);
/**
* @brief Преобразование GPIO в порядковый номер порта
*
* @param gpio Указатель на GPIO
* @return index при успехе, 255 при ошибке
*/
uint8_t DHTMon_GPIO_to_index(const GpioPin* gpio);
/**
* @brief Получить имя GPIO в виде строки
*
* @param gpio Искомый порт
* @return char* Указатель на строку с именем порта
*/
const char* DHTMon_GPIO_getName(const GpioPin* gpio);
/* ================== Работа с датчиками ================== */
/**
* @brief Инициализация портов ввода/вывода датчиков
*/
void DHTMon_sensors_init(void);
/**
* @brief Функция деинициализации портов ввода/вывода датчиков
*/
void DHTMon_sensors_deinit(void);
/**
* @brief Проверка корректности параметров датчика
*
* @param sensor Указатель на проверяемый датчик
* @return true Параметры датчика корректные
* @return false Параметры датчика некорректные
*/
bool DHTMon_sensor_check(DHT_sensor* sensor);
/**
* @brief Удаление датчика из списка и перезагрузка
*
* @param sensor Указатель на удаляемый датчик
*/
void DHTMon_sensor_delete(DHT_sensor* sensor);
/**
* @brief Сохранение датчиков на SD-карту
*
* @return Количество сохранённых датчиков
*/
uint8_t DHTMon_sensors_save(void);
/**
* @brief Загрузка датчиков с SD-карты
*
* @return true Был загружен хотя бы 1 датчик
* @return false Датчики отсутствуют
*/
bool DHTMon_sensors_load(void);
/**
* @brief Перезагрузка датчиков с SD-карты
*
* @return true Когда был загружен хотя бы 1 датчик
* @return false Ни один из датчиков не был загружен
*/
bool DHTMon_sensors_reload(void);
void scene_main(Canvas* const canvas, PluginData* app);
void mainMenu_scene(PluginData* app);
void sensorEdit_sceneCreate(PluginData* app);
void sensorEdit_scene(PluginData* app);
void sensorEdit_sceneRemove(void);
void sensorActions_sceneCreate(PluginData* app);
void sensorActions_scene(PluginData* app);
void sensorActions_screneRemove(void);
#endif

View File

@ -0,0 +1,157 @@
#include "../quenon_dht_mon.h"
//Текущий вид
static View* view;
//Список
static VariableItemList* variable_item_list;
/**
* @brief Функция обработки нажатия кнопки "Назад"
*
* @param context Указатель на данные приложения
* @return ID вида в который нужно переключиться
*/
static uint32_t actions_exitCallback(void* context) {
PluginData* app = context;
UNUSED(app);
//Возвращаем ID вида, в который нужно вернуться
return VIEW_NONE;
}
/**
* @brief Функция обработки нажатия средней кнопки
*
* @param context Указатель на данные приложения
* @param index На каком элементе списка была нажата кнопка
*/
static void enterCallback(void* context, uint32_t index) {
PluginData* app = context;
if((uint8_t)index < (uint8_t)app->sensors_count) {
app->currentSensorEdit = &app->sensors[index];
sensorActions_scene(app);
}
if((uint8_t)index == (uint8_t)app->sensors_count) {
app->currentSensorEdit = &app->sensors[app->sensors_count++];
strcpy(app->currentSensorEdit->name, "NewSensor");
app->currentSensorEdit->GPIO = DHTMon_GPIO_from_index(0);
app->currentSensorEdit->type = DHT11;
sensorEdit_scene(app);
}
}
/**
* @brief Создание списка действий с указанным датчиком
*
* @param app Указатель на данные плагина
*/
void mainMenu_scene(PluginData* app) {
variable_item_list = variable_item_list_alloc();
//Сброс всех элементов меню
variable_item_list_reset(variable_item_list);
//Добавление названий датчиков в качестве элементов списка
for(uint8_t i = 0; i < app->sensors_count; i++) {
variable_item_list_add(variable_item_list, app->sensors[i].name, 1, NULL, NULL);
}
if(app->sensors_count < (uint8_t)MAX_SENSORS) {
variable_item_list_add(variable_item_list, " + Add new sensor +", 1, NULL, NULL);
}
//Добавление колбека на нажатие средней кнопки
variable_item_list_set_enter_callback(variable_item_list, enterCallback, app);
//Создание вида из списка
view = variable_item_list_get_view(variable_item_list);
//Добавление колбека на нажатие кнопки "Назад"
view_set_previous_callback(view, actions_exitCallback);
//Добавление вида в диспетчер
view_dispatcher_add_view(app->view_dispatcher, MAIN_MENU_VIEW, view);
view_dispatcher_enable_queue(app->view_dispatcher);
//Переключение на наш вид
view_dispatcher_switch_to_view(app->view_dispatcher, MAIN_MENU_VIEW);
//Запуск диспетчера
view_dispatcher_run(app->view_dispatcher);
//Очистка списка элементов
variable_item_list_free(variable_item_list);
//Удаление вида после обработки
view_dispatcher_remove_view(app->view_dispatcher, MAIN_MENU_VIEW);
}
/*
*/
// static VariableItemList* variable_item_list;
// /* ============== Главное меню ============== */
// static uint32_t mainMenu_exitCallback(void* context) {
// UNUSED(context);
// variable_item_list_free(variable_item_list);
// DHT_sensors_reload();
// return VIEW_NONE;
// }
// static void mainMenu_enterCallback(void* context, uint32_t index) {
// PluginData* app = context;
// if((uint8_t)index == (uint8_t)app->sensors_count) {
// addSensor_scene(app);
// view_dispatcher_run(app->view_dispatcher);
// }
// }
// void mainMenu_scene(PluginData* app) {
// variable_item_list = variable_item_list_alloc();
// variable_item_list_reset(variable_item_list);
// for(uint8_t i = 0; i < app->sensors_count; i++) {
// variable_item_list_add(variable_item_list, app->sensors[i].name, 1, NULL, NULL);
// }
// variable_item_list_add(variable_item_list, "+ Add new sensor +", 1, NULL, NULL);
// app->view = variable_item_list_get_view(variable_item_list);
// app->view_dispatcher = view_dispatcher_alloc();
// view_dispatcher_enable_queue(app->view_dispatcher);
// view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
// view_dispatcher_add_view(app->view_dispatcher, MAIN_MENU_VIEW, app->view);
// view_dispatcher_switch_to_view(app->view_dispatcher, MAIN_MENU_VIEW);
// variable_item_list_set_enter_callback(variable_item_list, mainMenu_enterCallback, app);
// view_set_previous_callback(app->view, mainMenu_exitCallback);
// }

View File

@ -0,0 +1,40 @@
#include "../quenon_dht_mon.h"
/* ============== Главный экран ============== */
void scene_main(Canvas* const canvas, PluginData* app) {
//Рисование бара
canvas_draw_box(canvas, 0, 0, 128, 14);
canvas_set_color(canvas, ColorWhite);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 32, 11, "DHT Monitor");
canvas_set_color(canvas, ColorBlack);
if(app->sensors_count > 0) {
if(!furi_hal_power_is_otg_enabled()) {
furi_hal_power_enable_otg();
}
for(uint8_t i = 0; i < app->sensors_count; i++) {
app->data = DHT_getData(&app->sensors[i]);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 0, 24 + 10 * i, app->sensors[i].name);
canvas_set_font(canvas, FontSecondary);
if(app->data.hum == -128.0f && app->data.temp == -128.0f) {
canvas_draw_str(canvas, 96, 24 + 10 * i, "timeout");
} else {
snprintf(
app->txtbuff,
sizeof(app->txtbuff),
"%2.1f*C/%d%%",
(double)app->data.temp,
(int8_t)app->data.hum);
canvas_draw_str(canvas, 64, 24 + 10 * i, app->txtbuff);
}
}
} else {
canvas_set_font(canvas, FontSecondary);
if(app->sensors_count == 0) canvas_draw_str(canvas, 0, 24, "Sensors not found");
if(app->sensors_count == -1) canvas_draw_str(canvas, 0, 24, "Loading...");
}
}

View File

@ -0,0 +1,194 @@
#include "../quenon_dht_mon.h"
//Текущий вид
static View* view;
//Список
static VariableItemList* variable_item_list;
/* ================== Информация о датчике ================== */
/**
* @brief Функция обработки нажатия кнопки "Назад"
*
* @param context Указатель на данные приложения
* @return ID вида в который нужно переключиться
*/
static uint32_t infoWidget_exitCallback(void* context) {
PluginData* app = context;
UNUSED(app);
//Возвращаем ID вида, в который нужно вернуться
return SENSOR_ACTIONS_VIEW;
}
/**
* @brief Обработчик нажатий на кнопку в виджете
*
* @param result Какая из кнопок была нажата
* @param type Тип нажатия
* @param context Указатель на данные плагина
*/
static void infoWidget_callback(GuiButtonType result, InputType type, void* context) {
PluginData* app = context;
//Коротко нажата левая кнопка (Back)
if(result == GuiButtonTypeLeft && type == InputTypeShort) {
view_dispatcher_switch_to_view(app->view_dispatcher, SENSOR_ACTIONS_VIEW);
}
}
/**
* @brief Создание виджета информации о датчике
*
* @param app Указатель на данные плагина
*/
static void sensorInfo_widget(PluginData* app) {
//Очистка виджета
widget_reset(app->widget);
//Добавление кнопок
widget_add_button_element(app->widget, GuiButtonTypeLeft, "Back", infoWidget_callback, app);
char str[32];
snprintf(str, sizeof(str), "\e#%s\e#", app->currentSensorEdit->name);
widget_add_text_box_element(app->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, str, false);
snprintf(str, sizeof(str), "\e#Type:\e# %s", app->currentSensorEdit->type ? "DHT22" : "DHT11");
widget_add_text_box_element(app->widget, 0, 0, 128, 47, AlignLeft, AlignCenter, str, false);
snprintf(
str, sizeof(str), "\e#GPIO:\e# %s", DHTMon_GPIO_getName(app->currentSensorEdit->GPIO));
widget_add_text_box_element(app->widget, 0, 0, 128, 72, AlignLeft, AlignCenter, str, false);
view_set_previous_callback(widget_get_view(app->widget), infoWidget_exitCallback);
view_dispatcher_switch_to_view(app->view_dispatcher, WIDGET_VIEW);
}
/* ================== Подтверждение удаления ================== */
/**
* @brief Функция обработки нажатия кнопки "Назад"
*
* @param context Указатель на данные приложения
* @return ID вида в который нужно переключиться
*/
static uint32_t deleteWidget_exitCallback(void* context) {
PluginData* app = context;
UNUSED(app);
//Возвращаем ID вида, в который нужно вернуться
return SENSOR_ACTIONS_VIEW;
}
/**
* @brief Обработчик нажатий на кнопку в виджете
*
* @param result Какая из кнопок была нажата
* @param type Тип нажатия
* @param context Указатель на данные плагина
*/
static void deleteWidget_callback(GuiButtonType result, InputType type, void* context) {
PluginData* app = context;
//Коротко нажата левая кнопка (Cancel)
if(result == GuiButtonTypeLeft && type == InputTypeShort) {
view_dispatcher_switch_to_view(app->view_dispatcher, SENSOR_ACTIONS_VIEW);
}
//Коротко нажата правая кнопка (Delete)
if(result == GuiButtonTypeRight && type == InputTypeShort) {
//Удаление датчика
DHTMon_sensor_delete(app->currentSensorEdit);
//Выход из меню
view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_NONE);
}
}
/**
* @brief Создание виджета удаления датчика
*
* @param app Указатель на данные плагина
*/
static void sensorDelete_widget(PluginData* app) {
//Очистка виджета
widget_reset(app->widget);
//Добавление кнопок
widget_add_button_element(
app->widget, GuiButtonTypeLeft, "Cancel", deleteWidget_callback, app);
widget_add_button_element(
app->widget, GuiButtonTypeRight, "Delete", deleteWidget_callback, app);
char delete_str[32];
snprintf(delete_str, sizeof(delete_str), "\e#Delete %s?\e#", app->currentSensorEdit->name);
widget_add_text_box_element(
app->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, delete_str, false);
snprintf(
delete_str,
sizeof(delete_str),
"\e#Type:\e# %s",
app->currentSensorEdit->type ? "DHT22" : "DHT11");
widget_add_text_box_element(
app->widget, 0, 0, 128, 47, AlignLeft, AlignCenter, delete_str, false);
snprintf(
delete_str,
sizeof(delete_str),
"\e#GPIO:\e# %s",
DHTMon_GPIO_getName(app->currentSensorEdit->GPIO));
widget_add_text_box_element(
app->widget, 0, 0, 128, 72, AlignLeft, AlignCenter, delete_str, false);
view_set_previous_callback(widget_get_view(app->widget), deleteWidget_exitCallback);
view_dispatcher_switch_to_view(app->view_dispatcher, WIDGET_VIEW);
}
/* ================== Меню действий ================== */
/**
* @brief Функция обработки нажатия средней кнопки
*
* @param context Указатель на данные приложения
* @param index На каком элементе списка была нажата кнопка
*/
static void enterCallback(void* context, uint32_t index) {
PluginData* app = context;
if(index == 0) {
sensorInfo_widget(app);
}
if(index == 1) {
sensorEdit_scene(app);
}
if(index == 2) {
sensorDelete_widget(app);
}
}
/**
* @brief Функция обработки нажатия кнопки "Назад"
*
* @param context Указатель на данные приложения
* @return ID вида в который нужно переключиться
*/
static uint32_t actions_exitCallback(void* context) {
PluginData* app = context;
UNUSED(app);
//Возвращаем ID вида, в который нужно вернуться
return MAIN_MENU_VIEW;
}
/**
* @brief Создание списка действий с указанным датчиком
*
* @param app Указатель на данные плагина
*/
void sensorActions_sceneCreate(PluginData* app) {
variable_item_list = variable_item_list_alloc();
//Сброс всех элементов меню
variable_item_list_reset(variable_item_list);
//Добавление элементов в список
variable_item_list_add(variable_item_list, "Info", 0, NULL, NULL);
variable_item_list_add(variable_item_list, "Edit", 0, NULL, NULL);
variable_item_list_add(variable_item_list, "Delete", 0, NULL, NULL);
//Добавление колбека на нажатие средней кнопки
variable_item_list_set_enter_callback(variable_item_list, enterCallback, app);
//Создание вида из списка
view = variable_item_list_get_view(variable_item_list);
//Добавление колбека на нажатие кнопки "Назад"
view_set_previous_callback(view, actions_exitCallback);
//Добавление вида в диспетчер
view_dispatcher_add_view(app->view_dispatcher, SENSOR_ACTIONS_VIEW, view);
}
void sensorActions_scene(PluginData* app) {
//Сброс выбранного пункта в ноль
variable_item_list_set_selected_item(variable_item_list, 0);
//Переключение на наш вид
view_dispatcher_switch_to_view(app->view_dispatcher, SENSOR_ACTIONS_VIEW);
}
void sensorActions_screneRemove(void) {
variable_item_list_free(variable_item_list);
}

View File

@ -0,0 +1,100 @@
#include "../quenon_dht_mon.h"
static VariableItem* nameItem;
static VariableItemList* variable_item_list;
static const char* const sensorsTypes[2] = {
"DHT11",
"DHT22",
};
// /* ============== Добавление датчика ============== */
static uint32_t addSensor_exitCallback(void* context) {
UNUSED(context);
DHTMon_sensors_reload();
return VIEW_NONE;
}
static void addSensor_sensorTypeChanged(VariableItem* item) {
uint8_t index = variable_item_get_current_value_index(item);
PluginData* app = variable_item_get_context(item);
variable_item_set_current_value_text(item, sensorsTypes[index]);
app->currentSensorEdit->type = index;
}
static void addSensor_GPIOChanged(VariableItem* item) {
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, DHTMon_GPIO_getName(DHTMon_GPIO_from_index(index)));
PluginData* app = variable_item_get_context(item);
app->currentSensorEdit->GPIO = DHTMon_GPIO_from_index(index);
}
static void addSensor_sensorNameChanged(void* context) {
PluginData* app = context;
variable_item_set_current_value_text(nameItem, app->currentSensorEdit->name);
view_dispatcher_switch_to_view(app->view_dispatcher, ADDSENSOR_MENU_VIEW);
}
static void addSensor_sensorNameChange(PluginData* app) {
text_input_set_header_text(app->text_input, "Sensor name");
//По неясной мне причине в длину строки входит терминатор. Поэтому при длине 10 приходится указывать 11
text_input_set_result_callback(
app->text_input, addSensor_sensorNameChanged, app, app->currentSensorEdit->name, 11, true);
view_dispatcher_switch_to_view(app->view_dispatcher, TEXTINPUT_VIEW);
}
static void addSensor_enterCallback(void* context, uint32_t index) {
PluginData* app = context;
if(index == 0) {
addSensor_sensorNameChange(app);
}
if(index == 3) {
//Сохранение датчика
DHTMon_sensors_save();
DHTMon_sensors_reload();
view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_NONE);
}
}
void sensorEdit_sceneCreate(PluginData* app) {
variable_item_list = variable_item_list_alloc();
variable_item_list_reset(variable_item_list);
variable_item_list_set_enter_callback(variable_item_list, addSensor_enterCallback, app);
app->view = variable_item_list_get_view(variable_item_list);
view_set_previous_callback(app->view, addSensor_exitCallback);
view_dispatcher_add_view(app->view_dispatcher, ADDSENSOR_MENU_VIEW, app->view);
}
void sensorEdit_scene(PluginData* app) {
//Очистка списка
variable_item_list_reset(variable_item_list);
//Имя редактируемого датчика
nameItem = variable_item_list_add(variable_item_list, "Name: ", 1, NULL, NULL);
variable_item_set_current_value_index(nameItem, 0);
variable_item_set_current_value_text(nameItem, app->currentSensorEdit->name);
//Тип датчика
app->item =
variable_item_list_add(variable_item_list, "Type:", 2, addSensor_sensorTypeChanged, app);
variable_item_set_current_value_index(nameItem, app->currentSensorEdit->type);
variable_item_set_current_value_text(app->item, sensorsTypes[app->currentSensorEdit->type]);
//GPIO
app->item =
variable_item_list_add(variable_item_list, "GPIO:", 13, addSensor_GPIOChanged, app);
variable_item_set_current_value_index(
app->item, DHTMon_GPIO_to_index(app->currentSensorEdit->GPIO));
variable_item_set_current_value_text(
app->item, DHTMon_GPIO_getName(app->currentSensorEdit->GPIO));
variable_item_list_add(variable_item_list, "Save", 1, NULL, app);
view_dispatcher_switch_to_view(app->view_dispatcher, ADDSENSOR_MENU_VIEW);
}
void sensorEdit_sceneRemove(void) {
variable_item_list_free(variable_item_list);
}