Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
MX 2022-11-02 01:54:42 +03:00
commit 2ab9769b4b
No known key found for this signature in database
GPG key ID: 6C4C311DFD4B4AB5
17 changed files with 1749 additions and 2 deletions

View file

@ -95,6 +95,7 @@ Also check changelog in releases for latest updates!
- i2c Tools [(By NaejEL)](https://github.com/NaejEL/flipperzero-i2ctools) - C0 -> SCL / C1 -> SDA / GND -> GND | 3v3 logic levels only!
- Temperature Sensor Plugin - HTU21D / SI7021 [(By Mywk)](https://github.com/Mywk/FlipperTemperatureSensor) - [How to Connect](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/applications/plugins/temperature_sensor/Readme.md)
- HC-SR04 Distance sensor - Ported and modified by @xMasterX [(original by Sanqui)](https://github.com/Sanqui/flipperzero-firmware/tree/hc_sr04) - How to connect -> (5V -> VCC) / (GND -> GND) / (13|TX -> Trig) / (14|RX -> Echo)
- Morse Code [(by wh00hw)](https://github.com/wh00hw/MorseCodeFAP)
Games:
- DOOM (fixed) [(By p4nic4ttack)](https://github.com/p4nic4ttack/doom-flipper-zero/)

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);
}

View file

@ -0,0 +1,15 @@
App(
appid="morse_code",
name="Morse Code",
apptype=FlipperAppType.EXTERNAL,
entry_point="morse_code_app",
cdefines=["APP_MORSE_CODE"],
requires=[
"gui",
],
stack_size=1 * 1024,
order=20,
fap_icon="morse_code_10px.png",
fap_category="Music"
)

View file

@ -0,0 +1,161 @@
#include "morse_code_worker.h"
#include <furi.h>
#include <gui/gui.h>
#include <gui/elements.h>
#include <input/input.h>
#include <stdlib.h>
#include <furi_hal.h>
#include <string.h>
static const float MORSE_CODE_VOLUMES[] = {0, .25, .5, .75, 1};
typedef struct {
FuriString* words;
uint8_t volume;
uint32_t dit_delta;
} MorseCodeModel;
typedef struct {
MorseCodeModel* model;
FuriMutex** model_mutex;
FuriMessageQueue* input_queue;
ViewPort* view_port;
Gui* gui;
MorseCodeWorker* worker;
} MorseCode;
static void render_callback(Canvas* const canvas, void* ctx) {
MorseCode* morse_code = ctx;
furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk);
// border around the edge of the screen
canvas_set_font(canvas, FontPrimary);
//write words
elements_multiline_text_aligned(canvas, 64, 30, AlignCenter, AlignCenter, furi_string_get_cstr(morse_code->model->words));
// volume view_port
uint8_t vol_bar_x_pos = 124;
uint8_t vol_bar_y_pos = 0;
const uint8_t volume_h =
(64 / (COUNT_OF(MORSE_CODE_VOLUMES) - 1)) * morse_code->model->volume;
canvas_draw_frame(canvas, vol_bar_x_pos, vol_bar_y_pos, 4, 64);
canvas_draw_box(canvas, vol_bar_x_pos, vol_bar_y_pos + (64 - volume_h), 4, volume_h);
//dit bpm
canvas_draw_str_aligned(
canvas, 0, 10, AlignLeft, AlignCenter, furi_string_get_cstr(furi_string_alloc_printf("Dit: %ld ms", morse_code->model->dit_delta)));
//button info
elements_button_center(canvas, "Press/Hold");
furi_mutex_release(morse_code->model_mutex);
}
static void input_callback(InputEvent* input_event, void* ctx) {
MorseCode* morse_code = ctx;
furi_message_queue_put(morse_code->input_queue, input_event, FuriWaitForever);
}
static void morse_code_worker_callback(
FuriString* words,
void* context) {
MorseCode* morse_code = context;
furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk);
morse_code->model->words = words;
furi_mutex_release(morse_code->model_mutex);
view_port_update(morse_code->view_port);
}
MorseCode* morse_code_alloc() {
MorseCode* instance = malloc(sizeof(MorseCode));
instance->model = malloc(sizeof(MorseCodeModel));
instance->model->words = furi_string_alloc_set_str("");
instance->model->volume = 3;
instance->model->dit_delta = 150;
instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
instance->worker = morse_code_worker_alloc();
morse_code_worker_set_callback(instance->worker, morse_code_worker_callback, instance);
instance->view_port = view_port_alloc();
view_port_draw_callback_set(instance->view_port, render_callback, instance);
view_port_input_callback_set(instance->view_port, input_callback, instance);
// Open GUI and register view_port
instance->gui = furi_record_open(RECORD_GUI);
gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen);
return instance;
}
void morse_code_free(MorseCode* instance) {
gui_remove_view_port(instance->gui, instance->view_port);
furi_record_close(RECORD_GUI);
view_port_free(instance->view_port);
morse_code_worker_free(instance->worker);
furi_message_queue_free(instance->input_queue);
furi_mutex_free(instance->model_mutex);
free(instance->model);
free(instance);
}
int32_t morse_code_app() {
MorseCode* morse_code = morse_code_alloc();
InputEvent input;
morse_code_worker_start(morse_code->worker);
morse_code_worker_set_volume(
morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]);
morse_code_worker_set_dit_delta(morse_code->worker, morse_code->model->dit_delta);
while(furi_message_queue_get(morse_code->input_queue, &input, FuriWaitForever) == FuriStatusOk){
furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk);
if(input.key == InputKeyBack) {
furi_mutex_release(morse_code->model_mutex);
break;
}else if(input.key == InputKeyOk){
if(input.type == InputTypePress)
morse_code_worker_play(morse_code->worker, true);
else if(input.type == InputTypeRelease)
morse_code_worker_play(morse_code->worker, false);
}else if(input.key == InputKeyUp && input.type == InputTypePress){
if(morse_code->model->volume < COUNT_OF(MORSE_CODE_VOLUMES) - 1)
morse_code->model->volume++;
morse_code_worker_set_volume(
morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]);
}else if(input.key == InputKeyDown && input.type == InputTypePress){
if(morse_code->model->volume > 0)
morse_code->model->volume--;
morse_code_worker_set_volume(
morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]);
}else if(input.key == InputKeyLeft && input.type == InputTypePress){
if(morse_code->model->dit_delta > 10)
morse_code->model->dit_delta-=10;
morse_code_worker_set_dit_delta(
morse_code->worker, morse_code->model->dit_delta);
}
else if(input.key == InputKeyRight && input.type == InputTypePress){
if(morse_code->model->dit_delta >= 10)
morse_code->model->dit_delta+=10;
morse_code_worker_set_dit_delta(
morse_code->worker, morse_code->model->dit_delta);
}
FURI_LOG_D("Input", "%s %s %ld", input_get_key_name(input.key), input_get_type_name(input.type), input.sequence);
furi_mutex_release(morse_code->model_mutex);
view_port_update(morse_code->view_port);
}
morse_code_worker_stop(morse_code->worker);
morse_code_free(morse_code);
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

View file

@ -0,0 +1,164 @@
#include "morse_code_worker.h"
#include <furi_hal.h>
#include <lib/flipper_format/flipper_format.h>
#define TAG "MorseCodeWorker"
#define MORSE_CODE_VERSION 0
//A-Z0-1
const char morse_array[36][6] ={
".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.",
"--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..", ".----", "..---", "...--", "....-", ".....",
"-....", "--...", "---..", "----.", "-----"
};
const char symbol_array[36] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};
struct MorseCodeWorker {
FuriThread* thread;
MorseCodeWorkerCallback callback;
void* callback_context;
bool is_running;
bool play;
float volume;
uint32_t dit_delta;
FuriString* buffer;
FuriString* words;
};
void morse_code_worker_fill_buffer(MorseCodeWorker* instance, uint32_t duration){
FURI_LOG_D("MorseCode: Duration", "%ld", duration);
if( duration <= instance->dit_delta)
furi_string_push_back(instance->buffer, *DOT);
else if(duration <= (instance->dit_delta * 3))
furi_string_push_back(instance->buffer, *LINE);
if(furi_string_size(instance->buffer) > 5)
furi_string_reset(instance->buffer);
FURI_LOG_D("MorseCode: Buffer", "%s", furi_string_get_cstr(instance->buffer));
}
void morse_code_worker_fill_letter(MorseCodeWorker* instance){
if(furi_string_size(instance->words) > 63)
furi_string_reset(instance->words);
for (size_t i = 0; i < sizeof(morse_array); i++){
if(furi_string_cmp_str(instance->buffer, morse_array[i]) == 0){
furi_string_push_back(instance->words, symbol_array[i]);
furi_string_reset(instance->buffer);
break;
}
}
FURI_LOG_D("MorseCode: Words", "%s", furi_string_get_cstr(instance->words));
}
static int32_t morse_code_worker_thread_callback(void* context) {
furi_assert(context);
MorseCodeWorker* instance = context;
bool was_playing = false;
uint32_t start_tick = 0;
uint32_t end_tick = 0;
bool pushed = true;
bool spaced = true;
while(instance->is_running){
furi_delay_ms(SLEEP);
if(instance->play){
if(!was_playing){
start_tick = furi_get_tick();
furi_hal_speaker_start(FREQUENCY, instance->volume);
was_playing = true;
}
}else{
if(was_playing){
pushed = false;
spaced = false;
furi_hal_speaker_stop();
end_tick = furi_get_tick();
was_playing = false;
morse_code_worker_fill_buffer(instance, end_tick - start_tick);
start_tick = 0;
}
}
if(!pushed){
if(end_tick + (instance->dit_delta * 3) < furi_get_tick()){
//NEW LETTER
morse_code_worker_fill_letter(instance);
if(instance->callback)
instance->callback(instance->words, instance->callback_context);
pushed = true;
}
}
if(!spaced){
if(end_tick + (instance->dit_delta * 7) < furi_get_tick()){
//NEW WORD
furi_string_push_back(instance->words, *SPACE);
if(instance->callback)
instance->callback(instance->words, instance->callback_context);
spaced = true;
}
}
}
return 0;
}
MorseCodeWorker* morse_code_worker_alloc() {
MorseCodeWorker* instance = malloc(sizeof(MorseCodeWorker));
instance->thread = furi_thread_alloc();
furi_thread_set_name(instance->thread, "MorseCodeWorker");
furi_thread_set_stack_size(instance->thread, 1024);
furi_thread_set_context(instance->thread, instance);
furi_thread_set_callback(instance->thread, morse_code_worker_thread_callback);
instance->play = false;
instance->volume = 1.0f;
instance->dit_delta = 150;
instance->buffer = furi_string_alloc_set_str("");
instance->words = furi_string_alloc_set_str("");
return instance;
}
void morse_code_worker_free(MorseCodeWorker* instance) {
furi_assert(instance);
furi_thread_free(instance->thread);
free(instance);
}
void morse_code_worker_set_callback(
MorseCodeWorker* instance,
MorseCodeWorkerCallback callback,
void* context) {
furi_assert(instance);
instance->callback = callback;
instance->callback_context = context;
}
void morse_code_worker_play(MorseCodeWorker* instance, bool play){
furi_assert(instance);
instance->play = play;
}
void morse_code_worker_set_volume(MorseCodeWorker* instance, float level){
furi_assert(instance);
instance->volume = level;
}
void morse_code_worker_set_dit_delta(MorseCodeWorker* instance, uint32_t delta){
furi_assert(instance);
instance->dit_delta = delta;
}
void morse_code_worker_start(MorseCodeWorker* instance) {
furi_assert(instance);
furi_assert(instance->is_running == false);
instance->is_running = true;
furi_thread_start(instance->thread);
FURI_LOG_D("MorseCode: Start", "is Running");
}
void morse_code_worker_stop(MorseCodeWorker* instance) {
furi_assert(instance);
furi_assert(instance->is_running == true);
instance->is_running = false;
furi_thread_join(instance->thread);
FURI_LOG_D("MorseCode: Stop", "Stop");
}

View file

@ -0,0 +1,42 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <furi.h>
#define FREQUENCY 261.63f
#define SLEEP 10
#define DOT "."
#define LINE "-"
#define SPACE " "
typedef void (*MorseCodeWorkerCallback)(
FuriString* buffer,
void* context);
typedef struct MorseCodeWorker MorseCodeWorker;
MorseCodeWorker* morse_code_worker_alloc();
void morse_code_worker_free(MorseCodeWorker* instance);
void morse_code_worker_set_callback(
MorseCodeWorker* instance,
MorseCodeWorkerCallback callback,
void* context);
void morse_code_worker_start(MorseCodeWorker* instance);
void morse_code_worker_stop(MorseCodeWorker* instance);
void morse_code_worker_play(MorseCodeWorker* instance, bool play);
void morse_code_worker_set_volume(MorseCodeWorker* instance, float level);
void morse_code_worker_set_dit_delta(MorseCodeWorker* instance, uint32_t delta);

View file

@ -43,7 +43,7 @@ typedef struct {
#define KEY_WIDTH 9
#define KEY_HEIGHT 12
#define KEY_PADDING 1
#define ROW_COUNT 6
#define ROW_COUNT 7
#define COLUMN_COUNT 12
// 0 width items are not drawn, but there value is used
@ -136,6 +136,21 @@ const UsbHidKeyboardKey usb_hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = {
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB},
},
{
{.width = 1, .icon = NULL, .key = "1", .shift_key = "1", .value = HID_KEYBOARD_F1},
{.width = 1, .icon = NULL, .key = "2", .shift_key = "2", .value = HID_KEYBOARD_F2},
{.width = 1, .icon = NULL, .key = "3", .shift_key = "3", .value = HID_KEYBOARD_F3},
{.width = 1, .icon = NULL, .key = "4", .shift_key = "4", .value = HID_KEYBOARD_F4},
{.width = 1, .icon = NULL, .key = "5", .shift_key = "5", .value = HID_KEYBOARD_F5},
{.width = 1, .icon = NULL, .key = "6", .shift_key = "6", .value = HID_KEYBOARD_F6},
{.width = 1, .icon = NULL, .key = "7", .shift_key = "7", .value = HID_KEYBOARD_F7},
{.width = 1, .icon = NULL, .key = "8", .shift_key = "8", .value = HID_KEYBOARD_F8},
{.width = 1, .icon = NULL, .key = "9", .shift_key = "9", .value = HID_KEYBOARD_F9},
{.width = 1, .icon = NULL, .key = "0", .shift_key = "0", .value = HID_KEYBOARD_F10},
{.width = 1, .icon = NULL, .key = "1", .shift_key = "1", .value = HID_KEYBOARD_F11},
{.width = 1, .icon = NULL, .key = "2", .shift_key = "2", .value = HID_KEYBOARD_F12},
}
};
static void usb_hid_keyboard_to_upper(char* str) {
@ -217,6 +232,7 @@ static void usb_hid_keyboard_draw_callback(Canvas* canvas, void* context) {
// Select if back is clicked and its the backspace key
// Deselect when the button clicked or not hovered
bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y;
keySelected = y == ROW_COUNT - 1 ? !keySelected : keySelected;
bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE;
usb_hid_keyboard_draw_key(
canvas,
@ -369,4 +385,4 @@ void usb_hid_keyboard_free(UsbHidKeyboard* usb_hid_keyboard) {
View* usb_hid_keyboard_get_view(UsbHidKeyboard* usb_hid_keyboard) {
furi_assert(usb_hid_keyboard);
return usb_hid_keyboard->view;
}
}