mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-11-27 15:00:46 +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!
|
- 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)
|
- 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)
|
- 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:
|
Games:
|
||||||
- DOOM (fixed) [(By p4nic4ttack)](https://github.com/p4nic4ttack/doom-flipper-zero/)
|
- 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_WIDTH 9
|
||||||
#define KEY_HEIGHT 12
|
#define KEY_HEIGHT 12
|
||||||
#define KEY_PADDING 1
|
#define KEY_PADDING 1
|
||||||
#define ROW_COUNT 6
|
#define ROW_COUNT 7
|
||||||
#define COLUMN_COUNT 12
|
#define COLUMN_COUNT 12
|
||||||
|
|
||||||
// 0 width items are not drawn, but there value is used
|
// 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 = 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) {
|
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
|
// Select if back is clicked and its the backspace key
|
||||||
// Deselect when the button clicked or not hovered
|
// Deselect when the button clicked or not hovered
|
||||||
bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y;
|
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;
|
bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE;
|
||||||
usb_hid_keyboard_draw_key(
|
usb_hid_keyboard_draw_key(
|
||||||
canvas,
|
canvas,
|
||||||
|
|
Loading…
Reference in a new issue