Work on NMEA2000 device list

This commit is contained in:
2026-01-12 20:03:02 +01:00
parent c953340362
commit eb73c573b4
7 changed files with 485 additions and 22 deletions

View File

@@ -4,6 +4,7 @@
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Update.h>
#include <Wire.h>
#include <SHT31.h> // temp. sensor
#include <NMEA2000.h>
@@ -11,6 +12,7 @@
#include <N2kMessages.h>
#include "main.h"
#include "Nmea2kTwai.h"
#include "N2kDeviceList.h"
#include <map>
#include "mbedtls/md.h" // for SHA256
@@ -112,6 +114,7 @@ uint8_t rgb_brightness = 64;
uint buzzerpower = 50; // TBD make use of this
uint8_t keycode[6]; // configurable keycodes
uint8_t longcode[6]; // configurable keycodes for long pressed keys
SHT31 sht(SHT31_ADDRESS);
bool sht_available = false;
@@ -120,7 +123,7 @@ float hum = 0.0;
int nodeid; // NMEA2000 id on bus
Nmea2kTwai &NMEA2000=*(new Nmea2kTwai(CAN_TX, CAN_RX, CAN_RECOVERY_PERIOD));
tN2kDeviceList *pN2kDeviceList;
String processor(const String& var) {
// dummy for now
@@ -300,6 +303,12 @@ void setup() {
keycode[3] = preferences.getInt("key4", 4);
keycode[4] = preferences.getInt("key5", 5);
keycode[5] = preferences.getInt("key6", 6);
longcode[0] = preferences.getInt("key1long", 11);
longcode[1] = preferences.getInt("key2long", 12);
longcode[2] = preferences.getInt("key3long", 13);
longcode[3] = preferences.getInt("key4long", 14);
longcode[4] = preferences.getInt("key5long", 15);
longcode[5] = preferences.getInt("key6long", 16);
preferences.end();
// Configure I/O pins
@@ -404,6 +413,31 @@ void setup() {
serializeJson(doc, out);
request->send(200, "application/json", out);
});
server.on("/api/devicelist", HTTP_GET, [](AsyncWebServerRequest *request){
// NMEA2000 device list
AsyncResponseStream *response = request->beginResponseStream("application/json");
response->print("[");
bool first = true;
for (int i = 0; i <= 252; i++) {
const tNMEA2000::tDevice *d = pN2kDeviceList->FindDeviceBySource(i);
if (d == nullptr) {
continue;
}
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));
if (!first) {
response->print(",");
} else {
first = false;
}
// TODO last seen?
response->printf("{\"source\":%d,\"name\":\"%s\",\"manuf\":\"%d\",\"model\":\"%s\"}",
i, hex_name, d->GetManufacturerCode(), d->GetModelID());
}
response->print("]");
request->send(response);
});
server.on("/api/config", HTTP_GET, [](AsyncWebServerRequest *request){
StaticJsonDocument<512> doc;
doc["systemName"] = "Keypad1";
@@ -428,6 +462,13 @@ void setup() {
doc["key4"] = keycode[BUTTON_4];
doc["key5"] = keycode[BUTTON_5];
doc["key6"] = keycode[BUTTON_6];
doc["key1long"] = longcode[BUTTON_1];
doc["key2long"] = longcode[BUTTON_2];
doc["key3long"] = longcode[BUTTON_3];
doc["key4long"] = longcode[BUTTON_4];
doc["key5long"] = longcode[BUTTON_5];
doc["key6long"] = longcode[BUTTON_6];
doc["envInterval"] = 5;
String out;
serializeJson(doc, out);
request->send(200, "application/json", out);
@@ -466,11 +507,33 @@ void setup() {
request->send(200, "application/json", out);
});
server.on("/api/update", HTTP_POST, [](AsyncWebServerRequest *request){
// the request handler is triggered after the upload has finished...
// create the response, add header, and send response
StaticJsonDocument<100> doc;
doc["status"] = "FAILED";
String out;
serializeJson(doc, out);
request->send(200, "application/json", out);
}, [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
// this is the new image upload part
Serial.print("Retrieving firmware image named: ");
Serial.println(filename);
if (index == 0) {
if (! Update.begin(UPDATE_SIZE_UNKNOWN)) {
Update.printError(Serial);
}
}
if (Update.write(data, len) != len) {
Update.printError(Serial);
}
if (final) {
if (!Update.end(true)) {
Update.printError(Serial);
}
}
});
// TODO POST vom Client entgegennehmen
@@ -501,21 +564,31 @@ void setup() {
4 // Industrygoup=Marine
);
NMEA2000.SetForwardType(tNMEA2000::fwdt_Text);
// Debug
// Debug Start
// NMEA2000.SetForwardStream(&Serial);
// NMEA2000.SetForwardType(tNMEA2000::fwdt_Text);
// NMEA2000.SetForwardOwnMessages(true);
// NMEA2000.SetDebugMode(tNMEA2000::dm_2);
// Debug End
NMEA2000.SetMode(tNMEA2000::N2km_ListenAndNode, nodeid);
//TODO: N2km_NodeOnly N2km_ListenAndNode?
NMEA2000.SetMode(tNMEA2000::N2km_ListenAndNode, nodeid);
NMEA2000.SetForwardOwnMessages(false);
NMEA2000.SetHeartbeatIntervalAndOffset(NMEA2000_HEARTBEAT_INTERVAL);
const unsigned long TransmitPGNs[] = {
127502UL,
// 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();
// internal user led (red)
@@ -618,6 +691,41 @@ void atariKeyclick() {
ledcWriteTone(LEDC_CHANNEL, 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("------------------------------");
}
// rename to: void sendSwitchBank()
void SendToN2K(uint8_t keycode) {
tN2kMsg N2kMsg;
tN2kBinaryStatus bankstatus;
@@ -636,6 +744,20 @@ void SendToN2K(uint8_t keycode) {
Serial.println(" Action=On");
}
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
Serial.printf("Sending temp=%f K, hum=%f %%\n", 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 loop() {
ButtonEvent event;
@@ -684,17 +806,29 @@ void loop() {
if (mode == 'N') {
mode = 'C';
analogWrite(RGBLED_B, rgb_brightness); // blue status indicator
Serial.println("Entering config mode");
} else {
mode = 'N';
analogWrite(RGBLED_B, 0);
Serial.println("Leaving config mode");
}
}
} else {
// normal button
if (mode == 'N') {
// TODO send key code to destination
// Send key code to destination
atariKeyclick();
SendToN2K(keycode[event.buttonId]);
if ((event.pressType == ButtonPressType::SHORT)) {
SendToN2K(keycode[event.buttonId]);
} else if ((event.pressType == ButtonPressType::MEDIUM)) {
SendToN2K(longcode[event.buttonId]);
}
// Debug:
if ((event.buttonId == BUTTON_1)
and (event.pressType == ButtonPressType::MEDIUM))
{
print_n2k_devicelist();
}
} else {
// online config mode
switch (event.buttonId) {
@@ -777,8 +911,11 @@ void loop() {
if (millis() - lastSensor >= 5000) {
lastSensor = millis();
sht.read();
temp = sht.getTemperature();
hum = sht.getHumidity();
temp = sht.getTemperature(); // °C
hum = sht.getHumidity(); // Percent
// Send environment data to NMEA2000
send_sensor_temphum(temp + 273.15, hum);
}
// development heartbeat