Files
OBPkp61/src/main.cpp
2026-03-07 19:32:24 +01:00

786 lines
26 KiB
C++

#include "main.h"
#include "config.h"
#include "webserver.h"
#include "led.h"
#include <ArduinoJson.h>
#include <WiFi.h>
#include <Wire.h>
#include <SHT31.h> // temp. sensor
#include <NMEA2000.h>
#include <N2kMsg.h>
#include <N2kMessages.h>
#include <map>
#include <esp32/clk.h> // for cpu frequency
#include "driver/rtc_io.h" // for wakeup from deep sleep
#include "esp_app_format.h" // for custom fw descriptor
#include "esp_rom_uart.h" // for uart wait idle
// ESP-IDF firmware descriptor
__attribute__((section(".rodata_custom_desc"))) esp_app_desc_t custom_app_desc = {
ESP_APP_DESC_MAGIC_WORD,
1, // secure version
{0, 0}, // reserved
VERSION,
FIRMWARE_TYPE,
BUILD_DATE,
BUILD_TIME,
IDF_VERSION,
{},
{}
};
// Logging
static const char* TAG = "MAIN";
uint64_t chipid = ESP.getEfuseMac();
uint8_t loglevel = 5;
const char* wifi_ssid = WIFI_SSID;
const char* wifi_pass = WIFI_PASS;
bool ap_hidden = false;
bool ap_enabled = true;
unsigned long firstStart = 0;
unsigned long lastSensor = 0;
unsigned long lastPrint = 0;
unsigned long lastRefresh = 0;
unsigned long env_interval = 2000;
bool rgb_r = false;
bool rgb_g = false;
bool rgb_b = false;
int cpuspeed = 240; // MHz
RTC_DATA_ATTR char globalmode = 'K'; // (K)eyboard | (A)utopilot | (L)ogbook
char mode = 'N'; // (N)ormal | (C)onfig -> do not store for reset!
RTC_DATA_ATTR char ledmode = 'D'; // (D)ay | (N)ight
RTC_DATA_ATTR char audiomode = 'E'; // (E)nabled | (D)isabled
RTC_DATA_ATTR char destination = 'A'; // A | B | C im RTC-Speicher überlebt deep sleep
bool buz_enabled = true;
uint buzzerpower = 50; // TBD make use of this 0 .. 100%
uint8_t keycode[6]; // configurable keycodes
uint8_t longcode[6]; // configurable keycodes for long pressed keys
SHT31 sht(SHT31_ADDRESS);
bool sht_available = false;
float temp = 0.0;
float hum = 0.0;
uint8_t nodeid; // NMEA2000 id on bus
Nmea2kTwai &NMEA2000=*(new Nmea2kTwai(CAN_TX, CAN_RX, CAN_RECOVERY_PERIOD));
tN2kDeviceList *pN2kDeviceList;
uint8_t n2k_id[3] = { 254, 254, 254 }; // destination ids; 254 = undef.
uint64_t n2k_name[3] = { 0, 0, 0 }; // destination names
uint8_t switchbank[3] = { 0, 0, 0 }; // switch bank of destionation
String processor(const String& var) {
// dummy for now
return "";
}
uint64_t config_to_n2kname(const String &cfgval) {
if (cfgval == "all") {
LOGD(TAG, "n2kname: Broadcast");
return NAME_BROADCAST;
}
if (cfgval == "none") {
LOGD(TAG, "n2kname: None");
return NAME_NONE;
}
// parse as hex string
LOGD(TAG, "n2kname: %s", cfgval.c_str());
return strtoull(cfgval.c_str(), nullptr, 16);
}
void refresh_n2k_ids() {
// Debug
for (uint8_t i = 0; i < 3; i++) {
char hex_name[17];
snprintf(hex_name, sizeof(hex_name), "%08X%08X",
(uint32_t)(n2k_name[i] >> 32), (uint32_t)(n2k_name[i] & 0xFFFFFFFF));
LOGD(TAG, "Dest %d: name=%s", i, hex_name);
}
if (!pN2kDeviceList->ReadResetIsListUpdated()) {
// no changes, nothing to do
return;
}
LOGD(TAG, "refreshing NMEA2000 device ids");
for (int i = 0; i < 3; i++) {
if (n2k_name[i] == NAME_BROADCAST) {
n2k_id[i] = 255;
} else {
n2k_id[i] = 254;
}
}
uint8_t hits = 0;
for (int i = 0; i <= 252; i++) {
const tNMEA2000::tDevice *d = pN2kDeviceList->FindDeviceBySource(i);
if (d == nullptr) {
continue;
}
for (int j = 0; j < 3; j++) {
if (d->GetName() == n2k_name[j] && n2k_id[j] == 254) {
n2k_id[j] = i;
hits++;
}
}
if (hits == 3) {
// all found no need to look further
break;
}
}
// Debug
for (uint8_t i = 0; i < 3; i++) {
char hex_name[17];
snprintf(hex_name, sizeof(hex_name), "%08X%08X",
(uint32_t)(n2k_name[i] >> 32), (uint32_t)(n2k_name[i] & 0xFFFFFFFF));
LOGD(TAG, "Dest %d: name=%s, id=%d", i, hex_name, n2k_id[i]);
}
}
TaskHandle_t ledTaskHandle = NULL;
TaskHandle_t sensorTaskHandle = NULL;
TaskHandle_t keyTaskHandle = NULL;
QueueHandle_t ledQueue = NULL;
QueueHandle_t keyQueue = NULL;
void ledTask(void *parameter) {
LOGI(TAG, "Starting LED task");
for (;;) {
vTaskDelay(5000);
ledcWrite(LEDC_RGBLED_G, 160); // a short activity flash
vTaskDelay(20);
ledcWrite(LEDC_RGBLED_G, 0);
}
}
void sensorTask(void *parameter) {
LOGI(TAG, "Starting sensor task");
for (;;) {
vTaskDelay(10000); // nothing yet
}
}
void keyTask(void *parameter) {
// short key press <= 1s
// medium key press >1s and < 3s
// long key press >= 3s
LOGI(TAG, "Starting keyboard task");
constexpr uint8_t NUM_BUTTONS = 7;
constexpr gpio_num_t buttonPins[NUM_BUTTONS] = {
KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_DST
};
constexpr TickType_t DEBOUNCE_TIME = pdMS_TO_TICKS(30);
constexpr TickType_t POLL_INTERVAL = pdMS_TO_TICKS(10); // 100Hz update rate (10ms)
bool lastRead[NUM_BUTTONS];
bool lastStable[NUM_BUTTONS];
TickType_t lastDebounce[NUM_BUTTONS];
TickType_t pressStart[NUM_BUTTONS];
// init
for (int i = 0; i < NUM_BUTTONS; i++) {
lastRead[i]= HIGH;
lastStable[i] = HIGH;
lastDebounce[i]= 0;
pressStart[i] = 0;
}
for (;;) {
TickType_t now = xTaskGetTickCount();
for (int i = 0; i < NUM_BUTTONS; i++) {
bool reading = digitalRead(buttonPins[i]);
if (reading != lastRead[i]) {
lastDebounce[i] = now;
lastRead[i] = reading;
}
if ((now - lastDebounce[i]) > DEBOUNCE_TIME) {
if (reading != lastStable[i]) {
lastStable[i] = reading;
if (reading == LOW) {
// button pressed
pressStart[i] = now;
} else {
// button released: send to queue
TickType_t duration = now - pressStart[i];
ButtonEvent event;
event.buttonId = i;
if (duration < 1000) {
event.pressType = ButtonPressType::SHORT;
} else if (duration < 3000) {
event.pressType = ButtonPressType::MEDIUM;
} else {
event.pressType = ButtonPressType::LONG;
}
xQueueSend(keyQueue, &event, 0);
}
}
}
vTaskDelay(POLL_INTERVAL);
}
}
vTaskDelete(NULL);
}
void stopApTimerCallback(TimerHandle_t xTimer) {
LOGI(TAG, "reached AP switchoff time: accesspoint switched off ");
WiFi.softAPdisconnect(true);
}
void cpuFreqTimerCallback(TimerHandle_t xTimer) {
LOGI(TAG, "after 3 minutes: set CPU frequency to 160MHz");
setCpuFrequencyMhz(cpuspeed);
}
void setup() {
Serial.begin(115200);
unsigned long start = millis();
while (!Serial && millis() - start < 3000) {
delay(10);
}
// Configure I/O pins
// internal user led (red)
pinMode(LED_USER, OUTPUT);
// Init onbard RGB led and switch off
pinMode(LED_IR, OUTPUT);
pinMode(LED_IG, OUTPUT);
pinMode(LED_IB, OUTPUT);
digitalWrite(LED_IR, HIGH);
digitalWrite(LED_IG, HIGH);
digitalWrite(LED_IB, HIGH);
// destination leds
pinMode(LED_A, OUTPUT);
pinMode(LED_B, OUTPUT);
pinMode(LED_C, OUTPUT);
// enclosure RGB led
pinMode(RGBLED_R, OUTPUT);
pinMode(RGBLED_G, OUTPUT);
pinMode(RGBLED_B, OUTPUT);
// Buttons active-low, internal resistor
pinMode(KEY_1, INPUT_PULLUP);
pinMode(KEY_2, INPUT_PULLUP);
pinMode(KEY_3, INPUT_PULLUP);
pinMode(KEY_4, INPUT_PULLUP);
pinMode(KEY_5, INPUT_PULLUP);
pinMode(KEY_6, INPUT_PULLUP);
pinMode(KEY_DST, INPUT_PULLUP);
#ifdef HARDWARE_V2
// Light sensor input
pinMode(LDR, INPUT);
#endif
// Early signal system activity, red while booting
digitalWrite(RGBLED_R, HIGH);
digitalWrite(RGBLED_G, LOW);
digitalWrite(RGBLED_B, LOW);
// early logging initialization
LOGI(TAG, "Starting ...");
config.loadValue("logLevel");
loglevel = config.getByte("logLevel");
if (loglevel > ESP_LOG_VERBOSE) {
loglevel = ESP_LOG_VERBOSE;
} else if (loglevel < 0) {
loglevel = ESP_LOG_NONE;
}
LOGI(TAG, "Setting loglevel to %d", loglevel);
esp_log_level_set("*", static_cast<esp_log_level_t>(loglevel));
// configuration from nvs
config.load();
config.dump();
keycode[0] = config.getByte("key1");
keycode[1] = config.getByte("key2");
keycode[2] = config.getByte("key3");
keycode[3] = config.getByte("key4");
keycode[4] = config.getByte("key5");
keycode[5] = config.getByte("key6");
longcode[0] = config.getByte("key1long");
longcode[1] = config.getByte("key2long");
longcode[2] = config.getByte("key3long");
longcode[3] = config.getByte("key4long");
longcode[4] = config.getByte("key5long");
longcode[5] = config.getByte("key6long");
switchbank[0] = config.getByte("switchBankA");
switchbank[1] = config.getByte("switchBankB");
switchbank[2] = config.getByte("switchBankC");
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
if (cause == ESP_SLEEP_WAKEUP_EXT0) {
LOGI(TAG, "Wake up by key");
} else {
destination = 'A';
}
// N2K basics
LOGI(TAG, "N2K default node is %d", N2K_DEFAULT_NODEID);
nodeid = config.getByte("LastNodeId");
LOGI(TAG, "N2K node id set to %d from preferences", nodeid);
// some other settings of interest
cpuspeed = config.getShort("cpuSpeed");
int16_t apstoptime = config.getShort("stopApTime");
String apip = config.getString("apIp");
String apmask = config.getString("apMask");
env_interval = config.getShort("envInterval") * 1000;
// Setup webserver
WiFi.persistent(false);
WiFi.mode(WIFI_MODE_AP);
// IPAddress ap_addr(192, 168, 15, 1);
IPAddress ap_addr;
ap_addr.fromString(apip);
// IPAddress ap_subnet(255, 255, 255, 0);
IPAddress ap_subnet;
ap_subnet.fromString(apmask);
IPAddress ap_gateway(ap_addr);
WiFi.softAPConfig(ap_addr, ap_gateway, ap_subnet);
// Only enable if preferences apEnable is true
// But later switch on by config key is possible
ap_hidden = config.getBool("apHidden");
ap_enabled = config.getBool("apEnable");
if (ap_enabled) {
WiFi.softAP(wifi_ssid, wifi_pass, WIFI_CHANNEL, ap_hidden, WIFI_MAX_STA);
}
// Initialize WebGUI
webserver_init();
// Start HTTP Webserver
server.begin();
// NMEA2000 configuration
// Destinations setup, refresh in loop later
n2k_name[0] = config_to_n2kname(config.getString("n2kDestA"));
n2k_name[1] = config_to_n2kname(config.getString("n2kDestB"));
n2k_name[2] = config_to_n2kname(config.getString("n2kDestC"));
NMEA2000.SetN2kCANMsgBufSize(8);
NMEA2000.SetN2kCANReceiveFrameBufSize(250);
NMEA2000.SetN2kCANSendFrameBufSize(250);
NMEA2000.SetProductInformation(
"00000001", // Manufacturer's Model serial code
74, // Manufacturer's product code
"OBPkeypad6/1", // Manufacturer's Model ID
"0.1.0 (2026-01-04)", // Manufacturer's Software version code
"0.1", // Manufacturer's Model version
1 // LoadEquivalency (LEN)
);
// Manufacturer information is not changeable
NMEA2000.SetConfigurationInformation(
"Open Boat Projects / NMEA2000 library", // Manufacturer
config.getCString("instDesc1"), // Info 1
config.getCString("instDesc2") // Info 2
);
uint32_t uid; // ISO 21bit identity number devived from chipid (MAC)
uid = ((chipid >> 32) ^ (chipid & 0xFFFFFFFF)) & 0x1FFFFF;
NMEA2000.SetDeviceInformation(uid, // Unique number. Use e.g. Serial number.
N2K_DEVFUNCT,
N2K_DEVCLASS,
N2K_MANUFACTURERCODE,
N2K_INDUSTRYGROUP
);
uint8_t devinst = config.getByte("n2kDevInst");
// devinst lower, devinst upper, sysinst
NMEA2000.SetDeviceInformationInstances(devinst & 0x07, devinst & 0xf8 >> 3, config.getByte("n2kSysInst"));
// Debug Start
// NMEA2000.SetForwardStream(&Serial);
// NMEA2000.SetForwardType(tNMEA2000::fwdt_Text);
// NMEA2000.SetForwardOwnMessages(true);
// NMEA2000.SetDebugMode(tNMEA2000::dm_2);
// Debug End
//TODO: N2km_NodeOnly N2km_ListenAndNode?
NMEA2000.SetMode(tNMEA2000::N2km_ListenAndNode, nodeid);
NMEA2000.SetForwardOwnMessages(false);
NMEA2000.SetHeartbeatIntervalAndOffset(N2K_HEARTBEAT_INTERVAL);
// Features?
// 130311 environment
// 130316 temperature extended range
const unsigned long TransmitPGNs[] PROGMEM= {
127502UL, // switch bank control
130312UL, // temperature
130313UL, // humidity
0
};
NMEA2000.ExtendTransmitMessages(TransmitPGNs);
pN2kDeviceList = new tN2kDeviceList(&NMEA2000);
// Debug: NMEA2000.EnableForward(true);
NMEA2000.Open();
led_init();
// Buzzer
ledcAttachPin(BUZZER, LEDC_BUZZER);
// Test tone while booting
// Buzzer 12V, 2500Hz +/- 200Hz, 30mA, ca. 90dB
if (ledcWriteTone(LEDC_BUZZER, 2300) == 0) {
Serial.println("Error setting buzzer tone");
} else {
delay(50);
ledcWriteTone(LEDC_BUZZER, 0); // Buzzer off
}
// Startup sequence: test all led and short beep buzzer
digitalWrite(RGBLED_R, LOW); // boot status off
delay(500);
led_test();
// select current destination
switch (destination) {
case 'A':
ledcWrite(LEDC_LED_A, led_brightness);
break;
case 'B':
ledcWrite(LEDC_LED_B, led_brightness);
break;
case 'C':
ledcWrite(LEDC_LED_C, led_brightness);
break;
}
// I²C
LOGI(TAG, "SHT31_LIB_VERSION: %s", SHT31_LIB_VERSION);
Wire.begin(I2C_SDA, I2C_SCL);
Wire.setClock(I2C_SPEED);
uint16_t stat = sht.readStatus();
// stat = ffff anscheinend Fehler
// = 8010 läuft anscheinend
sht_available = (stat == 0x8010);
LOGI(TAG, "SHT31 state=0x%X", stat);
xTaskCreatePinnedToCore(
ledTask, // Task function
"LEDTask", // Task name
10000, // Stack size (bytes)
NULL, // Parameters
1, // Priority
&ledTaskHandle, // Task handle
1 // Core 1
);
xTaskCreatePinnedToCore(sensorTask,"SensorTask", 10000, NULL, 1, &sensorTaskHandle, 1);
xTaskCreatePinnedToCore(keyTask,"KeyboardTask", 10000, NULL, 1, &keyTaskHandle, 1);
// Create queues (5 items, each uint16_t)
keyQueue = xQueueCreate(5, sizeof(ButtonEvent));
ledQueue = xQueueCreate(5, sizeof(uint8_t));
if (esp_sleep_is_valid_wakeup_gpio(KEY_DST)) {
LOGI(TAG, "DST-key configured as wakeup-pin");
esp_sleep_enable_ext0_wakeup(KEY_DST, 0);
} else {
LOGI(TAG, "No wakeup feature available! Deep-sleep disabled.");
}
if (cpuspeed < 240) {
TimerHandle_t cpuFreqTimer = xTimerCreate(
"cpuFreqTimer",
pdMS_TO_TICKS(3 * 60 * 1000),
pdFALSE, // one shot only
nullptr,
cpuFreqTimerCallback
);
xTimerStart(cpuFreqTimer, 0);
}
if (apstoptime > 0) {
TimerHandle_t stopApTimer = xTimerCreate(
"stopApTimer",
pdMS_TO_TICKS(apstoptime * 1000),
pdFALSE, // one shot only
nullptr,
stopApTimerCallback
);
xTimerStart(stopApTimer, 0);
}
refresh_n2k_ids(); // hopefully some devices already detected
}
void shortBeep() {
if (audiomode == 'D') {
return;
}
ledcWriteTone(LEDC_BUZZER, 2500);
delay(30);
ledcWriteTone(LEDC_BUZZER, 0);
}
void atariKeyclick() {
if (audiomode == 'D') {
return;
}
ledcWriteTone(LEDC_BUZZER, 3200);
delayMicroseconds(3000);
ledcWriteTone(LEDC_BUZZER, 0);
delayMicroseconds(800);
ledcWriteTone(LEDC_BUZZER, 3200);
delayMicroseconds(2000);
ledcWriteTone(LEDC_BUZZER, 0);
}
void print_n2k_devicelist() {
if (!pN2kDeviceList) {
Serial.println("Devicelist not initialized");
}
if (!pN2kDeviceList->ReadResetIsListUpdated()) {
// no changes, nothing to do
Serial.println("Devicelist empty or unchanged");
return;
}
Serial.println("---- NMEA2000 Device List ----");
for (int i = 0; i <= 252; i++) {
const tNMEA2000::tDevice *d = pN2kDeviceList->FindDeviceBySource(i);
if (d == nullptr) {
continue;
}
// age in milliseconds
unsigned long lastseen = pN2kDeviceList->GetDeviceLastMessageTime(i);
Serial.printf("Device %d age %d ms\n", i, lastseen);
uint64_t NAME = d->GetName();
char hex_name[17];
snprintf(hex_name, sizeof(hex_name), "%08X%08X",
(uint32_t)(NAME >> 32), (uint32_t)(NAME & 0xFFFFFFFF));
Serial.printf("Src:%d Man:%d Model:%s (0x%s)\n",
d->GetSource(),
d->GetManufacturerCode(),
d->GetModelID(),
hex_name
);
}
Serial.println("------------------------------");
}
void send_switchbank(uint8_t keycode, uint8_t bank_id, uint8_t dest_id) {
// Seems there is no destination possible for 127502?
//
if (dest_id == 254) {
LOGW(TAG, "nothing sent: destination undefined (254)");
return;
}
tN2kMsg N2kMsg;
tN2kBinaryStatus bankstatus;
N2kResetBinaryStatus(bankstatus);
N2kSetStatusBinaryOnStatus(bankstatus, N2kOnOff_On, keycode);
SetN2kPGN127502(N2kMsg, bank_id, bankstatus);
if (dest_id != 255) {
N2kMsg.Destination = dest_id;
}
NMEA2000.SendMsg(N2kMsg);
LOGI(TAG, "PGN127502 sent: switch #%d to device #%d", keycode, dest_id);
}
void send_sensor_temphum(float temp_k, float hum_perc) {
tN2kMsg N2kMsg;
static unsigned char SID = 0;
unsigned char instance = 0;
tN2kTempSource temp_src = N2kts_OutsideTemperature; // 1=outside, 2=inside
tN2kHumiditySource hum_src = N2khs_OutsideHumidity; // 0=inside, 1=outside
LOGI(TAG, "Sending temp=%f K, hum=%f %%", temp_k, hum_perc);
SetN2kPGN130312(N2kMsg, SID, instance, temp_src, temp_k);
NMEA2000.SendMsg(N2kMsg);
SetN2kPGN130313(N2kMsg, SID, instance, hum_src, hum_perc);
NMEA2000.SendMsg(N2kMsg);
SID = (SID + 1) % 256;
}
void send_sensor_brightness(uint16_t value) {
// value range 0..4095 from LDR
// proprietary PGN 65280
// device instance 8bits
// brightness 0-100%, resolution 0.1% 16bits
// 3 bytes reserved
uint16_t n2kvalue = value * 1000UL / 4095; // 0..100%, resolution 0.1
tN2kMsg N2kMsg;
N2kMsg.SetPGN(65280); // proprietary PGN
N2kMsg.Priority = 6;
// 11bits manuf.-code, 2bits reserved (1), 3bits industry group
N2kMsg.Add2ByteUInt((N2K_MANUFACTURERCODE & 0x7FF) | (0x03 << 11) | ((N2K_INDUSTRYGROUP & 0x7) << 13));
N2kMsg.AddByte(0); // instance not yet used now
N2kMsg.Add2ByteUInt(n2kvalue);
N2kMsg.AddByte(0xFF); //reserved bytes
N2kMsg.AddByte(0xFF);
N2kMsg.AddByte(0xFF);
LOGI(TAG, "Sending LDR value=%d (%d)", n2kvalue, value);
NMEA2000.SendMsg(N2kMsg);
}
void loop() {
ButtonEvent event;
if (xQueueReceive(keyQueue, &event, 0) == pdPASS) {
if (event.buttonId == BUTTON_DST) {
// destination / mode button
if (event.pressType == ButtonPressType::SHORT) {
atariKeyclick();
if (mode == 'N') {
// switch destination only in normal mode
if (destination == 'A') {
destination = 'B';
ledcWrite(LEDC_LED_A, 0);
ledcWrite(LEDC_LED_B, led_brightness);
} else if (destination == 'B') {
destination = 'C';
ledcWrite(LEDC_LED_B, 0);
ledcWrite(LEDC_LED_C, led_brightness);
} else {
destination = 'A';
ledcWrite(LEDC_LED_C, 0);
ledcWrite(LEDC_LED_A, led_brightness);
}
LOGI(TAG, "New destination=%c", destination);
}
} else if (event.pressType == ButtonPressType::LONG) {
shortBeep();
// switch config mode
if (mode == 'N') {
mode = 'C';
ledcWrite(LEDC_RGBLED_B, rgb_brightness); // blue status indicator
LOGI(TAG, "Entering config mode");
} else {
mode = 'N';
ledcWrite(LEDC_RGBLED_B, 0);
LOGI(TAG, "Leaving config mode");
}
led_set_mode();
}
} else {
// normal button
if (mode == 'N') {
// Send key code to destination
atariKeyclick();
uint8_t index = destination - 'A';
if ((event.pressType == ButtonPressType::SHORT)) {
LOGI(TAG, "Send key %d: dst index = %d", keycode[event.buttonId], index);
send_switchbank(keycode[event.buttonId], switchbank[index], n2k_id[index]);
} else if ((event.pressType == ButtonPressType::MEDIUM)) {
send_switchbank(longcode[event.buttonId], switchbank[index], n2k_id[index]);
}
// Debug:
if ((event.buttonId == BUTTON_1)
and (event.pressType == ButtonPressType::MEDIUM))
{
print_n2k_devicelist();
}
} else {
// online config mode
switch (event.buttonId) {
case BUTTON_1: // switch day/night mode
if (ledmode == 'D') {
LOGI(TAG, "Night mode enabled");
ledmode = 'N';
ledcWrite(LEDC_LED_A, led_brightness);
} else {
LOGI(TAG, "Day mode enabled");
ledmode = 'D';
ledcWrite(LEDC_LED_A, 0);
}
break;
case BUTTON_2: // switch audio on/off
if (audiomode == 'E') {
LOGI(TAG, "Disabled audio");
audiomode = 'D';
ledcWrite(LEDC_LED_B, led_brightness);
} else {
LOGI(TAG, "Enabled audio");
audiomode = 'E';
ledcWrite(LEDC_LED_B, 0);
}
break;
case BUTTON_3: // switch accesspoint on/off
if (ap_enabled) {
LOGI(TAG, "Disable Accesspoint");
WiFi.softAPdisconnect(true);
ap_enabled = false;
ledcWrite(LEDC_LED_C, 0);
} else {
LOGI(TAG, "Enable Accesspoint");
WiFi.softAP(wifi_ssid, wifi_pass, WIFI_CHANNEL, ap_hidden, WIFI_MAX_STA);
ap_enabled = true;
ledcWrite(LEDC_LED_C, led_brightness);
}
break;
case BUTTON_4: // reserved
break;
case BUTTON_5: // reset
LOGI(TAG, "Device reset");
esp_rom_uart_tx_wait_idle(0);
ledcWrite(LEDC_RGBLED_B, 0); // blue config light off
led_blink(LEDC_RGBLED_G, 3, 4095, 500);
ESP.restart();
break;
case BUTTON_6: // deep sleep
LOGI(TAG, "Going into deep sleep");
esp_rom_uart_tx_wait_idle(0);
led_blink(LEDC_RGBLED_B, 3, 4095, 500);
rtc_gpio_pullup_en(KEY_DST);
rtc_gpio_pulldown_dis(KEY_DST);
esp_deep_sleep_start();
break;
}
}
}
}
// NMEA2000.loop(); // not implemented yet
NMEA2000.ParseMessages();
if (millis() - lastRefresh >= 30000) {
// look every 30 seconds for changed devices
lastRefresh = millis();
refresh_n2k_ids();
}
if ((millis() - lastSensor >= env_interval) and sht_available) {
lastSensor = millis();
sht.read();
temp = sht.getTemperature(); // °C
hum = sht.getHumidity(); // Percent
// Send environment data to NMEA2000
send_sensor_temphum(temp + 273.15, hum);
#ifdef HARDWARE_V2
// Send brightness to NMEA2000 (proprietary)
int ldrval = analogRead(LDR);
send_sensor_brightness(ldrval);
#endif
}
delay(1); // 1ms for FreeRTOS
}