forked from btclock/btclock_v3
Fix WebUI currency converter, reorganize some code
This commit is contained in:
parent
5dd47c2275
commit
ff50acf913
14 changed files with 228 additions and 195 deletions
2
data
2
data
|
@ -1 +1 @@
|
|||
Subproject commit 761c7f2991d347e97e77470ea3bf5511d7a7e507
|
||||
Subproject commit 7d82b1e1a9014e80f725478333774f4d53e22134
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "lib/led_handler.hpp"
|
||||
#include "lib/screen_handler.hpp"
|
||||
#include "lib/timers.hpp"
|
||||
#include "lib/shared.hpp"
|
||||
|
||||
// using namespace websockets;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "lib/screen_handler.hpp"
|
||||
#include "lib/shared.hpp"
|
||||
#include "lib/timers.hpp"
|
||||
|
||||
extern TaskHandle_t buttonTaskHandle;
|
||||
|
||||
|
|
|
@ -404,7 +404,7 @@ void setupWebsocketClients(void *pvParameters)
|
|||
{
|
||||
if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE))
|
||||
{
|
||||
setupV2Notify();
|
||||
V2Notify::setupV2Notify();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -553,7 +553,7 @@ void setupHardware()
|
|||
{
|
||||
Serial.println(F("Found BH1750"));
|
||||
hasLuxSensor = true;
|
||||
bh1750.begin(BH1750::CONTINUOUS_LOW_RES_MODE, 0x5C);
|
||||
bh1750.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x5C);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include "price_notify.hpp"
|
||||
#include "block_notify.hpp"
|
||||
#include "lib/timers.hpp"
|
||||
|
||||
void setupNostrNotify(bool asDatasource, bool zapNotify);
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "lib/config.hpp"
|
||||
#include "lib/shared.hpp"
|
||||
#include "lib/timers.hpp"
|
||||
|
||||
#ifndef UPDATE_MESSAGE_HPP
|
||||
#define UPDATE_MESSAGE_HPP
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
// TaskHandle_t timeUpdateTaskHandle;
|
||||
TaskHandle_t taskScreenRotateTaskHandle;
|
||||
TaskHandle_t workerTaskHandle;
|
||||
esp_timer_handle_t screenRotateTimer;
|
||||
esp_timer_handle_t minuteTimer;
|
||||
|
||||
|
||||
std::array<std::string, NUM_SCREENS> taskEpdContent = {};
|
||||
std::string priceString;
|
||||
|
@ -23,8 +22,6 @@ void workerTask(void *pvParameters) {
|
|||
while (1) {
|
||||
// Wait for a work item to be available in the queue
|
||||
if (xQueueReceive(workQueue, &receivedItem, portMAX_DELAY)) {
|
||||
uint firstIndex = 0;
|
||||
|
||||
// Process the work item based on its type
|
||||
switch (receivedItem.type) {
|
||||
case TASK_BITAXE_UPDATE: {
|
||||
|
@ -42,10 +39,7 @@ void workerTask(void *pvParameters) {
|
|||
case TASK_PRICE_UPDATE: {
|
||||
uint currency = getCurrentCurrency();
|
||||
uint price = getPrice(currency);
|
||||
// u_char priceSymbol = '$';
|
||||
// if (preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE)) {
|
||||
// priceSymbol = '[';
|
||||
// }
|
||||
|
||||
if (getCurrentScreen() == SCREEN_BTC_TICKER) {
|
||||
taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE));
|
||||
} else if (getCurrentScreen() == SCREEN_SATS_PER_CURRENCY) {
|
||||
|
@ -121,29 +115,6 @@ void taskScreenRotate(void *pvParameters) {
|
|||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR minuteTimerISR(void *arg) {
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
// vTaskNotifyGiveFromISR(timeUpdateTaskHandle, &xHigherPriorityTaskWoken);
|
||||
WorkItem timeUpdate = {TASK_TIME_UPDATE, 0};
|
||||
xQueueSendFromISR(workQueue, &timeUpdate, &xHigherPriorityTaskWoken);
|
||||
|
||||
if (bitaxeFetchTaskHandle != NULL) {
|
||||
vTaskNotifyGiveFromISR(bitaxeFetchTaskHandle, &xHigherPriorityTaskWoken);
|
||||
}
|
||||
|
||||
if (xHigherPriorityTaskWoken == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR screenRotateTimerISR(void *arg) {
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
vTaskNotifyGiveFromISR(taskScreenRotateTaskHandle, &xHigherPriorityTaskWoken);
|
||||
if (xHigherPriorityTaskWoken == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
void setupTasks() {
|
||||
workQueue = xQueueCreate(WORK_QUEUE_SIZE, sizeof(WorkItem));
|
||||
|
||||
|
@ -159,65 +130,6 @@ void setupTasks() {
|
|||
setCurrentScreen(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN));
|
||||
}
|
||||
|
||||
void setupTimeUpdateTimer(void *pvParameters) {
|
||||
const esp_timer_create_args_t minuteTimerConfig = {
|
||||
.callback = &minuteTimerISR, .name = "minute_timer"};
|
||||
|
||||
esp_timer_create(&minuteTimerConfig, &minuteTimer);
|
||||
|
||||
time_t currentTime;
|
||||
struct tm timeinfo;
|
||||
time(¤tTime);
|
||||
localtime_r(¤tTime, &timeinfo);
|
||||
uint32_t secondsUntilNextMinute = 60 - timeinfo.tm_sec;
|
||||
|
||||
if (secondsUntilNextMinute > 0)
|
||||
vTaskDelay(pdMS_TO_TICKS((secondsUntilNextMinute * 1000)));
|
||||
|
||||
esp_timer_start_periodic(minuteTimer, usPerMinute);
|
||||
|
||||
WorkItem timeUpdate = {TASK_TIME_UPDATE, 0};
|
||||
xQueueSend(workQueue, &timeUpdate, portMAX_DELAY);
|
||||
// xTaskNotifyGive(timeUpdateTaskHandle);
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void setupScreenRotateTimer(void *pvParameters) {
|
||||
const esp_timer_create_args_t screenRotateTimerConfig = {
|
||||
.callback = &screenRotateTimerISR, .name = "screen_rotate_timer"};
|
||||
|
||||
esp_timer_create(&screenRotateTimerConfig, &screenRotateTimer);
|
||||
|
||||
if (preferences.getBool("timerActive", DEFAULT_TIMER_ACTIVE)) {
|
||||
esp_timer_start_periodic(screenRotateTimer,
|
||||
getTimerSeconds() * usPerSecond);
|
||||
}
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
uint getTimerSeconds() { return preferences.getUInt("timerSeconds", DEFAULT_TIMER_SECONDS); }
|
||||
|
||||
bool isTimerActive() { return esp_timer_is_active(screenRotateTimer); }
|
||||
|
||||
void setTimerActive(bool status) {
|
||||
if (status) {
|
||||
esp_timer_start_periodic(screenRotateTimer,
|
||||
getTimerSeconds() * usPerSecond);
|
||||
queueLedEffect(LED_EFFECT_START_TIMER);
|
||||
preferences.putBool("timerActive", true);
|
||||
} else {
|
||||
esp_timer_stop(screenRotateTimer);
|
||||
queueLedEffect(LED_EFFECT_PAUSE_TIMER);
|
||||
preferences.putBool("timerActive", false);
|
||||
}
|
||||
|
||||
if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle);
|
||||
}
|
||||
|
||||
void toggleTimerActive() { setTimerActive(!isTimerActive()); }
|
||||
|
||||
uint getCurrentScreen() { return currentScreen; }
|
||||
|
||||
void setCurrentScreen(uint newScreen) {
|
||||
|
|
|
@ -16,9 +16,6 @@
|
|||
extern TaskHandle_t workerTaskHandle;
|
||||
extern TaskHandle_t taskScreenRotateTaskHandle;
|
||||
|
||||
extern esp_timer_handle_t screenRotateTimer;
|
||||
extern esp_timer_handle_t minuteTimer;
|
||||
|
||||
extern QueueHandle_t workQueue;
|
||||
|
||||
typedef enum {
|
||||
|
@ -42,21 +39,14 @@ void previousScreen();
|
|||
|
||||
void showSystemStatusScreen();
|
||||
|
||||
void setupTimeUpdateTimer(void *pvParameters);
|
||||
void setupScreenRotateTimer(void *pvParameters);
|
||||
|
||||
void IRAM_ATTR minuteTimerISR(void *arg);
|
||||
void IRAM_ATTR screenRotateTimerISR(void *arg);
|
||||
|
||||
// void taskPriceUpdate(void *pvParameters);
|
||||
// void taskBlockUpdate(void *pvParameters);
|
||||
// void taskTimeUpdate(void *pvParameters);
|
||||
void taskScreenRotate(void *pvParameters);
|
||||
|
||||
uint getTimerSeconds();
|
||||
bool isTimerActive();
|
||||
void setTimerActive(bool status);
|
||||
void toggleTimerActive();
|
||||
|
||||
|
||||
void setupTasks();
|
||||
void setCurrentCurrency(char currency);
|
||||
|
|
86
src/lib/timers.cpp
Normal file
86
src/lib/timers.cpp
Normal file
|
@ -0,0 +1,86 @@
|
|||
#include "timers.hpp"
|
||||
|
||||
esp_timer_handle_t screenRotateTimer;
|
||||
esp_timer_handle_t minuteTimer;
|
||||
|
||||
void setupTimeUpdateTimer(void *pvParameters) {
|
||||
const esp_timer_create_args_t minuteTimerConfig = {
|
||||
.callback = &minuteTimerISR, .name = "minute_timer"};
|
||||
|
||||
esp_timer_create(&minuteTimerConfig, &minuteTimer);
|
||||
|
||||
time_t currentTime;
|
||||
struct tm timeinfo;
|
||||
time(¤tTime);
|
||||
localtime_r(¤tTime, &timeinfo);
|
||||
uint32_t secondsUntilNextMinute = 60 - timeinfo.tm_sec;
|
||||
|
||||
if (secondsUntilNextMinute > 0)
|
||||
vTaskDelay(pdMS_TO_TICKS((secondsUntilNextMinute * 1000)));
|
||||
|
||||
esp_timer_start_periodic(minuteTimer, usPerMinute);
|
||||
|
||||
WorkItem timeUpdate = {TASK_TIME_UPDATE, 0};
|
||||
xQueueSend(workQueue, &timeUpdate, portMAX_DELAY);
|
||||
// xTaskNotifyGive(timeUpdateTaskHandle);
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void setupScreenRotateTimer(void *pvParameters) {
|
||||
const esp_timer_create_args_t screenRotateTimerConfig = {
|
||||
.callback = &screenRotateTimerISR, .name = "screen_rotate_timer"};
|
||||
|
||||
esp_timer_create(&screenRotateTimerConfig, &screenRotateTimer);
|
||||
|
||||
if (preferences.getBool("timerActive", DEFAULT_TIMER_ACTIVE)) {
|
||||
esp_timer_start_periodic(screenRotateTimer,
|
||||
getTimerSeconds() * usPerSecond);
|
||||
}
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
uint getTimerSeconds() { return preferences.getUInt("timerSeconds", DEFAULT_TIMER_SECONDS); }
|
||||
|
||||
bool isTimerActive() { return esp_timer_is_active(screenRotateTimer); }
|
||||
|
||||
void setTimerActive(bool status) {
|
||||
if (status) {
|
||||
esp_timer_start_periodic(screenRotateTimer,
|
||||
getTimerSeconds() * usPerSecond);
|
||||
queueLedEffect(LED_EFFECT_START_TIMER);
|
||||
preferences.putBool("timerActive", true);
|
||||
} else {
|
||||
esp_timer_stop(screenRotateTimer);
|
||||
queueLedEffect(LED_EFFECT_PAUSE_TIMER);
|
||||
preferences.putBool("timerActive", false);
|
||||
}
|
||||
|
||||
if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle);
|
||||
}
|
||||
|
||||
void toggleTimerActive() { setTimerActive(!isTimerActive()); }
|
||||
|
||||
void IRAM_ATTR minuteTimerISR(void *arg) {
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
// vTaskNotifyGiveFromISR(timeUpdateTaskHandle, &xHigherPriorityTaskWoken);
|
||||
WorkItem timeUpdate = {TASK_TIME_UPDATE, 0};
|
||||
xQueueSendFromISR(workQueue, &timeUpdate, &xHigherPriorityTaskWoken);
|
||||
|
||||
if (bitaxeFetchTaskHandle != NULL) {
|
||||
vTaskNotifyGiveFromISR(bitaxeFetchTaskHandle, &xHigherPriorityTaskWoken);
|
||||
}
|
||||
|
||||
if (xHigherPriorityTaskWoken == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR screenRotateTimerISR(void *arg) {
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
vTaskNotifyGiveFromISR(taskScreenRotateTaskHandle, &xHigherPriorityTaskWoken);
|
||||
if (xHigherPriorityTaskWoken == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
22
src/lib/timers.hpp
Normal file
22
src/lib/timers.hpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include <esp_timer.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include "lib/shared.hpp"
|
||||
#include "lib/screen_handler.hpp"
|
||||
|
||||
extern esp_timer_handle_t screenRotateTimer;
|
||||
extern esp_timer_handle_t minuteTimer;
|
||||
|
||||
void setupTimeUpdateTimer(void *pvParameters);
|
||||
void setupScreenRotateTimer(void *pvParameters);
|
||||
|
||||
void IRAM_ATTR minuteTimerISR(void *arg);
|
||||
void IRAM_ATTR screenRotateTimerISR(void *arg);
|
||||
|
||||
uint getTimerSeconds();
|
||||
bool isTimerActive();
|
||||
void setTimerActive(bool status);
|
||||
void toggleTimerActive();
|
|
@ -1,28 +1,38 @@
|
|||
#include "v2_notify.hpp"
|
||||
|
||||
WebSocketsClient webSocket;
|
||||
TaskHandle_t v2NotifyTaskHandle;
|
||||
using namespace V2Notify;
|
||||
|
||||
void setupV2Notify()
|
||||
namespace V2Notify
|
||||
{
|
||||
WebSocketsClient webSocket;
|
||||
|
||||
TaskHandle_t v2NotifyTaskHandle;
|
||||
|
||||
void setupV2Notify()
|
||||
{
|
||||
String hostname = "ws.btclock.dev";
|
||||
if ( preferences.getBool("stagingSource", DEFAULT_STAGING_SOURCE)) {
|
||||
if (preferences.getBool("stagingSource", DEFAULT_STAGING_SOURCE))
|
||||
{
|
||||
Serial.println(F("Connecting to V2 staging source"));
|
||||
hostname = "ws-staging.btclock.dev";
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println(F("Connecting to V2 source"));
|
||||
}
|
||||
|
||||
webSocket.beginSSL(hostname, 443, "/api/v2/ws");
|
||||
webSocket.onEvent(onWebsocketV2Event);
|
||||
webSocket.onEvent(V2Notify::onWebsocketV2Event);
|
||||
webSocket.setReconnectInterval(5000);
|
||||
webSocket.enableHeartbeat(15000, 3000, 2);
|
||||
|
||||
setupV2NotifyTask();
|
||||
}
|
||||
V2Notify::setupV2NotifyTask();
|
||||
}
|
||||
|
||||
void onWebsocketV2Event(WStype_t type, uint8_t * payload, size_t length) {
|
||||
switch(type) {
|
||||
void onWebsocketV2Event(WStype_t type, uint8_t *payload, size_t length)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case WStype_DISCONNECTED:
|
||||
Serial.printf("[WSc] Disconnected!\n");
|
||||
break;
|
||||
|
@ -35,7 +45,7 @@ void onWebsocketV2Event(WStype_t type, uint8_t * payload, size_t length) {
|
|||
response["type"] = "subscribe";
|
||||
response["eventType"] = "blockfee";
|
||||
size_t responseLength = measureMsgPack(response);
|
||||
uint8_t* buffer = new uint8_t[responseLength];
|
||||
uint8_t *buffer = new uint8_t[responseLength];
|
||||
serializeMsgPack(response, buffer, responseLength);
|
||||
webSocket.sendBIN(buffer, responseLength);
|
||||
delete[] buffer;
|
||||
|
@ -63,7 +73,7 @@ void onWebsocketV2Event(WStype_t type, uint8_t * payload, size_t length) {
|
|||
currenciesArray.add(str);
|
||||
}
|
||||
|
||||
// response["currencies"] = currenciesArray;
|
||||
// response["currencies"] = currenciesArray;
|
||||
responseLength = measureMsgPack(response);
|
||||
buffer = new uint8_t[responseLength];
|
||||
serializeMsgPack(response, buffer, responseLength);
|
||||
|
@ -81,7 +91,7 @@ void onWebsocketV2Event(WStype_t type, uint8_t * payload, size_t length) {
|
|||
JsonDocument doc;
|
||||
DeserializationError error = deserializeMsgPack(doc, payload, length);
|
||||
|
||||
handleV2Message(doc);
|
||||
V2Notify::handleV2Message(doc);
|
||||
break;
|
||||
}
|
||||
case WStype_ERROR:
|
||||
|
@ -93,14 +103,16 @@ void onWebsocketV2Event(WStype_t type, uint8_t * payload, size_t length) {
|
|||
case WStype_FRAGMENT_FIN:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleV2Message(JsonDocument doc) {
|
||||
void handleV2Message(JsonDocument doc)
|
||||
{
|
||||
if (doc.containsKey("blockheight"))
|
||||
{
|
||||
uint newBlockHeight = doc["blockheight"].as<uint>();
|
||||
|
||||
if (newBlockHeight == getBlockHeight()) {
|
||||
if (newBlockHeight == getBlockHeight())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -111,34 +123,38 @@ void handleV2Message(JsonDocument doc) {
|
|||
uint medianFee = doc["blockfee"].as<uint>();
|
||||
|
||||
processNewBlockFee(medianFee);
|
||||
} else if (doc.containsKey("price"))
|
||||
}
|
||||
else if (doc.containsKey("price"))
|
||||
{
|
||||
|
||||
// Iterate through the key-value pairs of the "price" object
|
||||
for (JsonPair kv : doc["price"].as<JsonObject>()) {
|
||||
const char* currency = kv.key().c_str();
|
||||
for (JsonPair kv : doc["price"].as<JsonObject>())
|
||||
{
|
||||
const char *currency = kv.key().c_str();
|
||||
uint newPrice = kv.value().as<uint>();
|
||||
|
||||
processNewPrice(newPrice, getCurrencyChar(currency));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void taskV2Notify(void *pvParameters) {
|
||||
for(;;) {
|
||||
void taskV2Notify(void *pvParameters)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
webSocket.loop();
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setupV2NotifyTask() {
|
||||
xTaskCreate(taskV2Notify, "v2Notify", (6 * 1024), NULL, tskIDLE_PRIORITY,
|
||||
&v2NotifyTaskHandle);
|
||||
void setupV2NotifyTask()
|
||||
{
|
||||
xTaskCreate(V2Notify::taskV2Notify, "v2Notify", (6 * 1024), NULL, tskIDLE_PRIORITY,
|
||||
&V2Notify::v2NotifyTaskHandle);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool isV2NotifyConnected()
|
||||
{
|
||||
bool isV2NotifyConnected()
|
||||
{
|
||||
return webSocket.isConnected();
|
||||
}
|
||||
}
|
|
@ -8,16 +8,18 @@
|
|||
|
||||
#include "lib/screen_handler.hpp"
|
||||
|
||||
extern TaskHandle_t v2NotifyTaskHandle;
|
||||
namespace V2Notify {
|
||||
extern TaskHandle_t v2NotifyTaskHandle;
|
||||
|
||||
void setupV2NotifyTask();
|
||||
void taskV2Notify(void *pvParameters);
|
||||
void setupV2NotifyTask();
|
||||
void taskV2Notify(void *pvParameters);
|
||||
|
||||
void setupV2Notify();
|
||||
void onWebsocketV2Event(WStype_t type, uint8_t * payload, size_t length);
|
||||
void handleV2Message(JsonDocument doc);
|
||||
void setupV2Notify();
|
||||
void onWebsocketV2Event(WStype_t type, uint8_t * payload, size_t length);
|
||||
void handleV2Message(JsonDocument doc);
|
||||
|
||||
bool isV2NotifyConnected();
|
||||
bool isV2NotifyConnected();
|
||||
}
|
||||
// void stopV2Notify();
|
||||
// void restartV2Notify();
|
||||
// bool getPriceNotifyInit();
|
||||
|
|
|
@ -256,9 +256,10 @@ JsonDocument getStatusObject()
|
|||
// root["espPsramSize"] = ESP.getPsramSize();
|
||||
|
||||
JsonObject conStatus = root["connectionStatus"].to<JsonObject>();
|
||||
|
||||
conStatus["price"] = isPriceNotifyConnected();
|
||||
conStatus["blocks"] = isBlockNotifyConnected();
|
||||
conStatus["V2"] = isV2NotifyConnected();
|
||||
conStatus["V2"] = V2Notify::isV2NotifyConnected();
|
||||
|
||||
conStatus["nostr"] = nostrConnected();
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ extern "C" void app_main()
|
|||
if (hasLightLevel()) {
|
||||
if (preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) != 0)
|
||||
{
|
||||
if (hasLightLevel() && getLightLevel() == 0)
|
||||
if (hasLightLevel() && getLightLevel() <= 2)
|
||||
{
|
||||
if (frontlightIsOn()) {
|
||||
frontlightFadeOutAll();
|
||||
|
|
Loading…
Reference in a new issue