786 lines
26 KiB
C++
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
|
|
}
|