mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2024-12-20 20:01:54 +03:00
460 lines
17 KiB
C
460 lines
17 KiB
C
|
#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: Пропуск использованных портов в меню добавления датчиков
|