mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-11-23 13:03:13 +00:00
Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
2ab9769b4b
17 changed files with 1749 additions and 2 deletions
|
@ -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/)
|
||||
|
|
169
applications/plugins/dht_temp_sensor/DHT.c
Normal file
169
applications/plugins/dht_temp_sensor/DHT.c
Normal 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;
|
||||
}
|
39
applications/plugins/dht_temp_sensor/DHT.h
Normal file
39
applications/plugins/dht_temp_sensor/DHT.h
Normal 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
|
13
applications/plugins/dht_temp_sensor/application.fam
Normal file
13
applications/plugins/dht_temp_sensor/application.fam
Normal 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,
|
||||
)
|
BIN
applications/plugins/dht_temp_sensor/icon.png
Normal file
BIN
applications/plugins/dht_temp_sensor/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
460
applications/plugins/dht_temp_sensor/quenon_dht_mon.c
Normal file
460
applications/plugins/dht_temp_sensor/quenon_dht_mon.c
Normal 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: Пропуск использованных портов в меню добавления датчиков
|
176
applications/plugins/dht_temp_sensor/quenon_dht_mon.h
Normal file
176
applications/plugins/dht_temp_sensor/quenon_dht_mon.h
Normal 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
|
|
@ -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);
|
||||
// }
|
|
@ -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...");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
15
applications/plugins/morse_code/application.fam
Normal file
15
applications/plugins/morse_code/application.fam
Normal 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"
|
||||
|
||||
)
|
161
applications/plugins/morse_code/morse_code.c
Normal file
161
applications/plugins/morse_code/morse_code.c
Normal 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;
|
||||
}
|
BIN
applications/plugins/morse_code/morse_code_10px.png
Normal file
BIN
applications/plugins/morse_code/morse_code_10px.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 168 B |
164
applications/plugins/morse_code/morse_code_worker.c
Normal file
164
applications/plugins/morse_code/morse_code_worker.c
Normal 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");
|
||||
}
|
42
applications/plugins/morse_code/morse_code_worker.h
Normal file
42
applications/plugins/morse_code/morse_code_worker.h
Normal 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);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue