1
0
mirror of https://github.com/thooge/esp32-nmea2000-obp60.git synced 2025-12-28 05:03:06 +01:00

PGN 127502 remote keyboard support

This commit is contained in:
2025-12-27 17:07:01 +01:00
parent 470c0e5f4d
commit 898922769a
8 changed files with 96 additions and 24 deletions

View File

@@ -137,6 +137,7 @@ class GwApi{
* thread safe methods - can directly be called from a user task * thread safe methods - can directly be called from a user task
*/ */
virtual GwRequestQueue *getQueue()=0; virtual GwRequestQueue *getQueue()=0;
virtual QueueHandle_t getKbQueue()=0;
virtual void sendN2kMessage(const tN2kMsg &msg, bool convert=true)=0; virtual void sendN2kMessage(const tN2kMsg &msg, bool convert=true)=0;
/** /**
* deprecated - sourceId will be ignored * deprecated - sourceId will be ignored

View File

@@ -71,6 +71,7 @@ class GwConverterConfig{
int rmcInterval=1000; int rmcInterval=1000;
int rmcCheckTime=4000; int rmcCheckTime=4000;
int winst312=256; int winst312=256;
int swBankInstance=0;
bool unmappedXdr=false; bool unmappedXdr=false;
unsigned long xdrTimeout=60000; unsigned long xdrTimeout=60000;
std::vector<WindMapping> windMappings; std::vector<WindMapping> windMappings;
@@ -97,6 +98,7 @@ class GwConverterConfig{
windMappings.push_back(mapping); windMappings.push_back(mapping);
} }
} }
swBankInstance=config->getInt(GwConfigDefinitions::swBankInstance,0);
} }
const WindMapping findWindMapping(const tN2kWindReference &n2k) const{ const WindMapping findWindMapping(const tN2kWindReference &n2k) const{
for (const auto & it:windMappings){ for (const auto & it:windMappings){
@@ -113,4 +115,4 @@ class GwConverterConfig{
}; };
#endif #endif

View File

@@ -1572,6 +1572,36 @@ private:
finalizeXdr(); finalizeXdr();
} }
void Handle127502(const tN2kMsg &msg){
// switch bank control / receive remote key strokes
unsigned char instance=-1;
tN2kBinaryStatus bankstatus;
LOG_DEBUG(GwLog::LOG,"received switch bank control");
// check if we are addressed and our configured instance is used
if (! ParseN2kPGN127502(msg,instance,bankstatus)) {
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
return;
}
if (! (instance == config.swBankInstance)) {
LOG_DEBUG(GwLog::DEBUG,"switch bank instance #%d ignored",instance);
return;
}
// TODO (?) multiple keys together
// only process configured key count (default 6)
for (uint8_t i=1; i<=6; i++) {
tN2kOnOff keystatus = N2kGetStatusOnBinaryStatus(bankstatus, i);
if (keystatus == 1) {
// key pressed: send key to queue
xQueueSend(keyboardQueue, &i, 0);
} else if (keystatus == 2) {
// long key pressed: send long key to queue
xQueueSend(keyboardQueue, &i, 0);
}
}
}
void registerConverters() void registerConverters()
{ {
//register all converter functions //register all converter functions
@@ -1607,6 +1637,7 @@ private:
converters.registerConverter(127488UL, &N2kToNMEA0183Functions::Handle127488); converters.registerConverter(127488UL, &N2kToNMEA0183Functions::Handle127488);
converters.registerConverter(130316UL, &N2kToNMEA0183Functions::Handle130316); converters.registerConverter(130316UL, &N2kToNMEA0183Functions::Handle130316);
converters.registerConverter(127257UL, &N2kToNMEA0183Functions::Handle127257); converters.registerConverter(127257UL, &N2kToNMEA0183Functions::Handle127257);
converters.registerConverter(127502UL, &N2kToNMEA0183Functions::Handle127502);
#define HANDLE_AIS #define HANDLE_AIS
#ifdef HANDLE_AIS #ifdef HANDLE_AIS
converters.registerConverter(129038UL, &N2kToNMEA0183Functions::HandleAISClassAPosReport); // AIS Class A Position Report, Message Type 1 converters.registerConverter(129038UL, &N2kToNMEA0183Functions::HandleAISClassAPosReport); // AIS Class A Position Report, Message Type 1
@@ -1620,13 +1651,15 @@ private:
public: public:
N2kToNMEA0183Functions(GwLog *logger, GwBoatData *boatData, N2kToNMEA0183Functions(GwLog *logger, GwBoatData *boatData,
SendNMEA0183MessageCallback callback, SendNMEA0183MessageCallback callback,
String talkerId, GwXDRMappings *xdrMappings, const GwConverterConfig &cfg) String talkerId, GwXDRMappings *xdrMappings, const GwConverterConfig &cfg,
QueueHandle_t kbQueue)
: N2kDataToNMEA0183(logger, boatData, callback,talkerId) : N2kDataToNMEA0183(logger, boatData, callback,talkerId)
{ {
this->logger = logger; this->logger = logger;
this->boatData = boatData; this->boatData = boatData;
this->xdrMappings=xdrMappings; this->xdrMappings=xdrMappings;
this->config=cfg; this->config=cfg;
this->keyboardQueue=kbQueue;
registerConverters(); registerConverters();
} }
virtual void loop(unsigned long lastExtRmc) override virtual void loop(unsigned long lastExtRmc) override
@@ -1642,8 +1675,8 @@ private:
N2kDataToNMEA0183* N2kDataToNMEA0183::create(GwLog *logger, GwBoatData *boatData, N2kDataToNMEA0183* N2kDataToNMEA0183::create(GwLog *logger, GwBoatData *boatData,
SendNMEA0183MessageCallback callback, String talkerId, GwXDRMappings *xdrMappings, SendNMEA0183MessageCallback callback, String talkerId, GwXDRMappings *xdrMappings,
const GwConverterConfig &cfg){ const GwConverterConfig &cfg, QueueHandle_t kbQueue){
LOG_DEBUG(GwLog::LOG,"creating N2kToNMEA0183"); LOG_DEBUG(GwLog::LOG,"creating N2kToNMEA0183");
return new N2kToNMEA0183Functions(logger,boatData,callback, talkerId,xdrMappings,cfg); return new N2kToNMEA0183Functions(logger,boatData,callback, talkerId,xdrMappings,cfg,kbQueue);
} }
//***************************************************************************** //*****************************************************************************

View File

@@ -43,6 +43,7 @@ protected:
GwBoatData *boatData; GwBoatData *boatData;
int sourceId=0; int sourceId=0;
char talkerId[3]; char talkerId[3];
QueueHandle_t keyboardQueue;
SendNMEA0183MessageCallback sendNMEA0183MessageCallback; SendNMEA0183MessageCallback sendNMEA0183MessageCallback;
void SendMessage(const tNMEA0183Msg &NMEA0183Msg); void SendMessage(const tNMEA0183Msg &NMEA0183Msg);
N2kDataToNMEA0183(GwLog *logger, GwBoatData *boatData, N2kDataToNMEA0183(GwLog *logger, GwBoatData *boatData,
@@ -50,7 +51,8 @@ protected:
public: public:
static N2kDataToNMEA0183* create(GwLog *logger, GwBoatData *boatData, SendNMEA0183MessageCallback callback, static N2kDataToNMEA0183* create(GwLog *logger, GwBoatData *boatData, SendNMEA0183MessageCallback callback,
String talkerId, GwXDRMappings *xdrMappings,const GwConverterConfig &cfg); String talkerId, GwXDRMappings *xdrMappings,const GwConverterConfig &cfg,
QueueHandle_t kbQueue);
virtual void HandleMsg(const tN2kMsg &N2kMsg, int sourceId) = 0; virtual void HandleMsg(const tN2kMsg &N2kMsg, int sourceId) = 0;
virtual void loop(unsigned long lastRmc); virtual void loop(unsigned long lastRmc);
virtual ~N2kDataToNMEA0183(){} virtual ~N2kDataToNMEA0183(){}
@@ -59,4 +61,4 @@ public:
virtual void toJson(GwJsonDocument *json)=0; virtual void toJson(GwJsonDocument *json)=0;
virtual String handledKeys()=0; virtual String handledKeys()=0;
}; };
#endif #endif

View File

@@ -85,7 +85,7 @@ void OBP60Init(GwApi *api){
// Get CPU speed // Get CPU speed
int freq = getCpuFrequencyMhz(); int freq = getCpuFrequencyMhz();
logger->logDebug(GwLog::LOG,"CPU speed at boot: %i MHz", freq); logger->logDebug(GwLog::LOG,"CPU speed at boot: %i MHz", freq);
// Settings for backlight // Settings for backlight
String backlightMode = api->getConfig()->getConfigItem(api->getConfig()->backlight,true)->asString(); String backlightMode = api->getConfig()->getConfigItem(api->getConfig()->backlight,true)->asString();
logger->logDebug(GwLog::DEBUG,"Backlight Mode is: %s", backlightMode.c_str()); logger->logDebug(GwLog::DEBUG,"Backlight Mode is: %s", backlightMode.c_str());
@@ -223,7 +223,7 @@ void registerAllPages(PageList &list){
extern PageDescription registerPageWind; extern PageDescription registerPageWind;
list.add(&registerPageWind); list.add(&registerPageWind);
extern PageDescription registerPageWindPlot; extern PageDescription registerPageWindPlot;
list.add(&registerPageWindPlot); list.add(&registerPageWindPlot);
extern PageDescription registerPageWindRose; extern PageDescription registerPageWindRose;
list.add(&registerPageWindRose); list.add(&registerPageWindRose);
extern PageDescription registerPageWindRoseFlex; extern PageDescription registerPageWindRoseFlex;
@@ -344,6 +344,8 @@ void OBP60Task(GwApi *api){
tN2kMsg N2kMsg; tN2kMsg N2kMsg;
QueueHandle_t keyboardQueue = api->getKbQueue();
LOG_DEBUG(GwLog::LOG,"obp60task started"); LOG_DEBUG(GwLog::LOG,"obp60task started");
for (auto it=allPages.pages.begin();it != allPages.pages.end();it++){ for (auto it=allPages.pages.begin();it != allPages.pages.end();it++){
LOG_DEBUG(GwLog::LOG,"found registered page %s",(*it)->pageName.c_str()); LOG_DEBUG(GwLog::LOG,"found registered page %s",(*it)->pageName.c_str());
@@ -402,7 +404,7 @@ void OBP60Task(GwApi *api){
getdisplay().nextPage(); // Fast Refresh getdisplay().nextPage(); // Fast Refresh
getdisplay().nextPage(); // Fast Refresh getdisplay().nextPage(); // Fast Refresh
} }
// Init pages // Init pages
int numPages=1; int numPages=1;
PageStruct pages[MAX_PAGE_NUMBER]; PageStruct pages[MAX_PAGE_NUMBER];
@@ -473,7 +475,7 @@ void OBP60Task(GwApi *api){
for (auto it=description->fixedParam.begin();it != description->fixedParam.end();it++){ for (auto it=description->fixedParam.begin();it != description->fixedParam.end();it++){
GwApi::BoatValue *value=boatValues.findValueOrCreate(*it); GwApi::BoatValue *value=boatValues.findValueOrCreate(*it);
LOG_DEBUG(GwLog::DEBUG,"added fixed value %s to page %d",value->getName().c_str(),i); LOG_DEBUG(GwLog::DEBUG,"added fixed value %s to page %d",value->getName().c_str(),i);
pages[i].parameters.values.push_back(value); pages[i].parameters.values.push_back(value);
} }
// Add boat history data to page parameters // Add boat history data to page parameters
pages[i].parameters.boatHstry = &hstryBufList; pages[i].parameters.boatHstry = &hstryBufList;
@@ -549,11 +551,11 @@ void OBP60Task(GwApi *api){
GwApi::BoatValue *hdop = boatValues.findValueOrCreate("HDOP"); // Load GpsHDOP GwApi::BoatValue *hdop = boatValues.findValueOrCreate("HDOP"); // Load GpsHDOP
LOG_DEBUG(GwLog::LOG,"obp60task: start mainloop"); LOG_DEBUG(GwLog::LOG,"obp60task: start mainloop");
commonData.time = boatValues.findValueOrCreate("GPST"); // Load GpsTime commonData.time = boatValues.findValueOrCreate("GPST"); // Load GpsTime
commonData.date = boatValues.findValueOrCreate("GPSD"); // Load GpsTime commonData.date = boatValues.findValueOrCreate("GPSD"); // Load GpsTime
bool delayedDisplayUpdate = false; // If select a new pages then make a delayed full display update bool delayedDisplayUpdate = false; // If select a new pages then make a delayed full display update
bool cpuspeedsetted = false; // Marker for change CPU speed bool cpuspeedsetted = false; // Marker for change CPU speed
long firststart = millis(); // First start long firststart = millis(); // First start
long starttime0 = millis(); // Mainloop long starttime0 = millis(); // Mainloop
long starttime1 = millis(); // Full display refresh for the first 5 min (more often as normal) long starttime1 = millis(); // Full display refresh for the first 5 min (more often as normal)
@@ -582,7 +584,7 @@ void OBP60Task(GwApi *api){
} }
} }
// Set CPU speed after boot after 1min // Set CPU speed after boot after 1min
if(millis() > firststart + (1 * 60 * 1000) && cpuspeedsetted == false){ if(millis() > firststart + (1 * 60 * 1000) && cpuspeedsetted == false){
if(String(cpuspeed) == "80"){ if(String(cpuspeed) == "80"){
setCpuFrequencyMhz(80); setCpuFrequencyMhz(80);
@@ -603,7 +605,7 @@ void OBP60Task(GwApi *api){
commonData.data=shared->getSensorData(); commonData.data=shared->getSensorData();
commonData.data.actpage = pageNumber + 1; commonData.data.actpage = pageNumber + 1;
commonData.data.maxpage = numPages; commonData.data.maxpage = numPages;
// If GPS fix then LED off (HDOP) // If GPS fix then LED off (HDOP)
if(String(gpsFix) == "GPS Fix Lost" && hdop->value <= hdopAccuracy && hdop->valid == true){ if(String(gpsFix) == "GPS Fix Lost" && hdop->value <= hdopAccuracy && hdop->valid == true){
setFlashLED(false); setFlashLED(false);
@@ -612,9 +614,18 @@ void OBP60Task(GwApi *api){
if((String(gpsFix) == "GPS Fix Lost" && hdop->value > hdopAccuracy && hdop->valid == true) || (String(gpsFix) == "GPS Fix Lost" && hdop->valid == false)){ if((String(gpsFix) == "GPS Fix Lost" && hdop->value > hdopAccuracy && hdop->valid == true) || (String(gpsFix) == "GPS Fix Lost" && hdop->valid == false)){
setFlashLED(true); setFlashLED(true);
} }
// Keyboard messages from remote
uint8_t remotekey = 0;
if (xQueueReceive(keyboardQueue, &remotekey, 0) == pdPASS) {
LOG_DEBUG(GwLog::LOG, "OBP received remote key: %d", remotekey);
// inject into internal keyboard queue
xQueueSend(allParameters.queue, &remotekey, 0);
}
// Check the keyboard message // Check the keyboard message
int keyboardMessage=0; int keyboardMessage=0;
while (xQueueReceive(allParameters.queue,&keyboardMessage,0)){ while (xQueueReceive(allParameters.queue,&keyboardMessage,0)){
LOG_DEBUG(GwLog::LOG,"new key from keyboard %d",keyboardMessage); LOG_DEBUG(GwLog::LOG,"new key from keyboard %d",keyboardMessage);
keypressed = true; keypressed = true;
@@ -677,13 +688,13 @@ void OBP60Task(GwApi *api){
commonData.data.actpage = pageNumber + 1; commonData.data.actpage = pageNumber + 1;
commonData.data.maxpage = numPages; commonData.data.maxpage = numPages;
} }
// #9 or #10 Refresh display after a new page after 4s waiting time and if refresh is disabled // #9 or #10 Refresh display after a new page after 4s waiting time and if refresh is disabled
if(refreshmode == true && (keyboardMessage == 9 || keyboardMessage == 10 || keyboardMessage == 4 || keyboardMessage == 3)){ if(refreshmode == true && (keyboardMessage == 9 || keyboardMessage == 10 || keyboardMessage == 4 || keyboardMessage == 3)){
starttime4 = millis(); starttime4 = millis();
starttime2 = millis(); // Reset the timer for full display update starttime2 = millis(); // Reset the timer for full display update
delayedDisplayUpdate = true; delayedDisplayUpdate = true;
} }
} }
LOG_DEBUG(GwLog::LOG,"set pagenumber to %d",pageNumber); LOG_DEBUG(GwLog::LOG,"set pagenumber to %d",pageNumber);
} }
@@ -709,7 +720,7 @@ void OBP60Task(GwApi *api){
commonData.sundata = calcSunsetSunriseRTC(&commonData.data.rtcTime, homelat, homelon, tz); commonData.sundata = calcSunsetSunriseRTC(&commonData.data.rtcTime, homelat, homelon, tz);
} }
} }
// Full display update afer a new selected page and 8s wait time // Full display update afer a new selected page and 8s wait time
if(millis() > starttime4 + 8000 && delayedDisplayUpdate == true){ if(millis() > starttime4 + 8000 && delayedDisplayUpdate == true){
starttime1 = millis(); starttime1 = millis();
@@ -786,8 +797,8 @@ void OBP60Task(GwApi *api){
// getdisplay().nextPage(); // Partial update // getdisplay().nextPage(); // Partial update
// getdisplay().nextPage(); // Partial update // getdisplay().nextPage(); // Partial update
} }
} }
// Refresh display data, default all 1s // Refresh display data, default all 1s
currentPage = pages[pageNumber].page; currentPage = pages[pageNumber].page;
int pagetime = 1000; int pagetime = 1000;

View File

@@ -189,6 +189,10 @@ public:
{ {
return api->getQueue(); return api->getQueue();
} }
virtual QueueHandle_t getKbQueue()
{
return api->getKbQueue();
}
virtual void sendN2kMessage(const tN2kMsg &msg,bool convert) virtual void sendN2kMessage(const tN2kMsg &msg,bool convert)
{ {
GWSYNCHRONIZED(mainLock); GWSYNCHRONIZED(mainLock);
@@ -428,4 +432,4 @@ void GwUserCode::handleWebRequest(const String &url,AsyncWebServerRequest *req){
} }
LOG_DEBUG(GwLog::DEBUG,"no task found for web request %s[%s]",url.c_str(),tname.c_str()); LOG_DEBUG(GwLog::DEBUG,"no task found for web request %s[%s]",url.c_str(),tname.c_str());
req->send(404, "text/plain", "not found"); req->send(404, "text/plain", "not found");
} }

View File

@@ -158,6 +158,8 @@ GwCounter<unsigned long> countNMEA2KIn("countNMEA2000in");
GwCounter<unsigned long> countNMEA2KOut("countNMEA2000out"); GwCounter<unsigned long> countNMEA2KOut("countNMEA2000out");
GwIntervalRunner timers; GwIntervalRunner timers;
QueueHandle_t keyboardQueue = NULL;
bool checkPass(String hash){ bool checkPass(String hash){
return config.checkPass(hash); return config.checkPass(hash);
} }
@@ -269,6 +271,10 @@ public:
{ {
return &mainQueue; return &mainQueue;
} }
virtual QueueHandle_t getKbQueue()
{
return keyboardQueue;
}
virtual void sendN2kMessage(const tN2kMsg &msg,bool convert) virtual void sendN2kMessage(const tN2kMsg &msg,bool convert)
{ {
handleN2kMessage(msg,sourceId,!convert); handleN2kMessage(msg,sourceId,!convert);
@@ -860,6 +866,8 @@ void setup() {
webserver.begin(); webserver.begin();
xdrMappings.begin(); xdrMappings.begin();
logger.flush(); logger.flush();
// remote keyboard support
keyboardQueue = xQueueCreate(10, sizeof(uint8_t));
GwConverterConfig converterConfig; GwConverterConfig converterConfig;
converterConfig.init(&config,&logger); converterConfig.init(&config,&logger);
nmea0183Converter= N2kDataToNMEA0183::create(&logger, &boatData, nmea0183Converter= N2kDataToNMEA0183::create(&logger, &boatData,
@@ -869,7 +877,8 @@ void setup() {
, ,
config.getString(config.talkerId,String("GP")), config.getString(config.talkerId,String("GP")),
&xdrMappings, &xdrMappings,
converterConfig converterConfig,
keyboardQueue
); );
toN2KConverter= NMEA0183DataToN2K::create(&logger,&boatData,[](const tN2kMsg &msg, int sourceId)->bool{ toN2KConverter= NMEA0183DataToN2K::create(&logger,&boatData,[](const tN2kMsg &msg, int sourceId)->bool{

View File

@@ -88,6 +88,16 @@
"description":"the brightness of the led (0..255)", "description":"the brightness of the led (0..255)",
"category":"system" "category":"system"
}, },
{
"name": "swBankInstance",
"type": "number",
"default": 0,
"min": 0,
"max": 252,
"check": "checkMinMax",
"description": "the instance of switch bank to receive data for (0..252)",
"category": "system"
},
{ {
"name": "talkerId", "name": "talkerId",
"label": "NMEA0183 ID", "label": "NMEA0183 ID",
@@ -1287,4 +1297,4 @@
"check": "checkXDR", "check": "checkXDR",
"category": "xdr30" "category": "xdr30"
} }
] ]