From 898922769aafe9d6b29cf926ae12b1c73ffe8871 Mon Sep 17 00:00:00 2001 From: Thomas Hooge Date: Sat, 27 Dec 2025 17:07:01 +0100 Subject: [PATCH] PGN 127502 remote keyboard support --- lib/api/GwApi.h | 1 + lib/config/GwConverterConfig.h | 4 ++- lib/nmea2kto0183/N2kDataToNMEA0183.cpp | 41 +++++++++++++++++++++++--- lib/nmea2kto0183/N2kDataToNMEA0183.h | 6 ++-- lib/obp60task/obp60task.cpp | 39 +++++++++++++++--------- lib/usercode/GwUserCode.cpp | 6 +++- src/main.cpp | 11 ++++++- web/config.json | 12 +++++++- 8 files changed, 96 insertions(+), 24 deletions(-) diff --git a/lib/api/GwApi.h b/lib/api/GwApi.h index 88f9690..bde7670 100644 --- a/lib/api/GwApi.h +++ b/lib/api/GwApi.h @@ -137,6 +137,7 @@ class GwApi{ * thread safe methods - can directly be called from a user task */ virtual GwRequestQueue *getQueue()=0; + virtual QueueHandle_t getKbQueue()=0; virtual void sendN2kMessage(const tN2kMsg &msg, bool convert=true)=0; /** * deprecated - sourceId will be ignored diff --git a/lib/config/GwConverterConfig.h b/lib/config/GwConverterConfig.h index 4a93a71..0517074 100644 --- a/lib/config/GwConverterConfig.h +++ b/lib/config/GwConverterConfig.h @@ -71,6 +71,7 @@ class GwConverterConfig{ int rmcInterval=1000; int rmcCheckTime=4000; int winst312=256; + int swBankInstance=0; bool unmappedXdr=false; unsigned long xdrTimeout=60000; std::vector windMappings; @@ -97,6 +98,7 @@ class GwConverterConfig{ windMappings.push_back(mapping); } } + swBankInstance=config->getInt(GwConfigDefinitions::swBankInstance,0); } const WindMapping findWindMapping(const tN2kWindReference &n2k) const{ for (const auto & it:windMappings){ @@ -113,4 +115,4 @@ class GwConverterConfig{ }; -#endif \ No newline at end of file +#endif diff --git a/lib/nmea2kto0183/N2kDataToNMEA0183.cpp b/lib/nmea2kto0183/N2kDataToNMEA0183.cpp index dbf6af5..48d7029 100644 --- a/lib/nmea2kto0183/N2kDataToNMEA0183.cpp +++ b/lib/nmea2kto0183/N2kDataToNMEA0183.cpp @@ -1572,6 +1572,36 @@ private: 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() { //register all converter functions @@ -1607,6 +1637,7 @@ private: converters.registerConverter(127488UL, &N2kToNMEA0183Functions::Handle127488); converters.registerConverter(130316UL, &N2kToNMEA0183Functions::Handle130316); converters.registerConverter(127257UL, &N2kToNMEA0183Functions::Handle127257); + converters.registerConverter(127502UL, &N2kToNMEA0183Functions::Handle127502); #define HANDLE_AIS #ifdef HANDLE_AIS converters.registerConverter(129038UL, &N2kToNMEA0183Functions::HandleAISClassAPosReport); // AIS Class A Position Report, Message Type 1 @@ -1620,13 +1651,15 @@ private: public: N2kToNMEA0183Functions(GwLog *logger, GwBoatData *boatData, SendNMEA0183MessageCallback callback, - String talkerId, GwXDRMappings *xdrMappings, const GwConverterConfig &cfg) + String talkerId, GwXDRMappings *xdrMappings, const GwConverterConfig &cfg, + QueueHandle_t kbQueue) : N2kDataToNMEA0183(logger, boatData, callback,talkerId) { this->logger = logger; this->boatData = boatData; this->xdrMappings=xdrMappings; this->config=cfg; + this->keyboardQueue=kbQueue; registerConverters(); } virtual void loop(unsigned long lastExtRmc) override @@ -1642,8 +1675,8 @@ private: N2kDataToNMEA0183* N2kDataToNMEA0183::create(GwLog *logger, GwBoatData *boatData, SendNMEA0183MessageCallback callback, String talkerId, GwXDRMappings *xdrMappings, - const GwConverterConfig &cfg){ - LOG_DEBUG(GwLog::LOG,"creating N2kToNMEA0183"); - return new N2kToNMEA0183Functions(logger,boatData,callback, talkerId,xdrMappings,cfg); + const GwConverterConfig &cfg, QueueHandle_t kbQueue){ + LOG_DEBUG(GwLog::LOG,"creating N2kToNMEA0183"); + return new N2kToNMEA0183Functions(logger,boatData,callback, talkerId,xdrMappings,cfg,kbQueue); } //***************************************************************************** diff --git a/lib/nmea2kto0183/N2kDataToNMEA0183.h b/lib/nmea2kto0183/N2kDataToNMEA0183.h index 61e8ee2..f7d8c75 100644 --- a/lib/nmea2kto0183/N2kDataToNMEA0183.h +++ b/lib/nmea2kto0183/N2kDataToNMEA0183.h @@ -43,6 +43,7 @@ protected: GwBoatData *boatData; int sourceId=0; char talkerId[3]; + QueueHandle_t keyboardQueue; SendNMEA0183MessageCallback sendNMEA0183MessageCallback; void SendMessage(const tNMEA0183Msg &NMEA0183Msg); N2kDataToNMEA0183(GwLog *logger, GwBoatData *boatData, @@ -50,7 +51,8 @@ protected: public: 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 loop(unsigned long lastRmc); virtual ~N2kDataToNMEA0183(){} @@ -59,4 +61,4 @@ public: virtual void toJson(GwJsonDocument *json)=0; virtual String handledKeys()=0; }; -#endif \ No newline at end of file +#endif diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index b1c0e01..b570aeb 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -85,7 +85,7 @@ void OBP60Init(GwApi *api){ // Get CPU speed int freq = getCpuFrequencyMhz(); logger->logDebug(GwLog::LOG,"CPU speed at boot: %i MHz", freq); - + // Settings for backlight String backlightMode = api->getConfig()->getConfigItem(api->getConfig()->backlight,true)->asString(); logger->logDebug(GwLog::DEBUG,"Backlight Mode is: %s", backlightMode.c_str()); @@ -223,7 +223,7 @@ void registerAllPages(PageList &list){ extern PageDescription registerPageWind; list.add(®isterPageWind); extern PageDescription registerPageWindPlot; - list.add(®isterPageWindPlot); + list.add(®isterPageWindPlot); extern PageDescription registerPageWindRose; list.add(®isterPageWindRose); extern PageDescription registerPageWindRoseFlex; @@ -344,6 +344,8 @@ void OBP60Task(GwApi *api){ tN2kMsg N2kMsg; + QueueHandle_t keyboardQueue = api->getKbQueue(); + LOG_DEBUG(GwLog::LOG,"obp60task started"); for (auto it=allPages.pages.begin();it != allPages.pages.end();it++){ 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 } - + // Init pages int numPages=1; PageStruct pages[MAX_PAGE_NUMBER]; @@ -473,7 +475,7 @@ void OBP60Task(GwApi *api){ for (auto it=description->fixedParam.begin();it != description->fixedParam.end();it++){ GwApi::BoatValue *value=boatValues.findValueOrCreate(*it); 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 pages[i].parameters.boatHstry = &hstryBufList; @@ -549,11 +551,11 @@ void OBP60Task(GwApi *api){ GwApi::BoatValue *hdop = boatValues.findValueOrCreate("HDOP"); // Load GpsHDOP LOG_DEBUG(GwLog::LOG,"obp60task: start mainloop"); - + commonData.time = boatValues.findValueOrCreate("GPST"); // 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 cpuspeedsetted = false; // Marker for change CPU speed + bool cpuspeedsetted = false; // Marker for change CPU speed long firststart = millis(); // First start long starttime0 = millis(); // Mainloop 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(String(cpuspeed) == "80"){ setCpuFrequencyMhz(80); @@ -603,7 +605,7 @@ void OBP60Task(GwApi *api){ commonData.data=shared->getSensorData(); commonData.data.actpage = pageNumber + 1; commonData.data.maxpage = numPages; - + // If GPS fix then LED off (HDOP) if(String(gpsFix) == "GPS Fix Lost" && hdop->value <= hdopAccuracy && hdop->valid == true){ 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)){ 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 int keyboardMessage=0; + while (xQueueReceive(allParameters.queue,&keyboardMessage,0)){ LOG_DEBUG(GwLog::LOG,"new key from keyboard %d",keyboardMessage); keypressed = true; @@ -677,13 +688,13 @@ void OBP60Task(GwApi *api){ commonData.data.actpage = pageNumber + 1; commonData.data.maxpage = numPages; } - + // #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)){ starttime4 = millis(); starttime2 = millis(); // Reset the timer for full display update delayedDisplayUpdate = true; - } + } } 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); } } - + // Full display update afer a new selected page and 8s wait time if(millis() > starttime4 + 8000 && delayedDisplayUpdate == true){ starttime1 = millis(); @@ -786,8 +797,8 @@ void OBP60Task(GwApi *api){ // getdisplay().nextPage(); // Partial update // getdisplay().nextPage(); // Partial update } - } - + } + // Refresh display data, default all 1s currentPage = pages[pageNumber].page; int pagetime = 1000; diff --git a/lib/usercode/GwUserCode.cpp b/lib/usercode/GwUserCode.cpp index 1b007f8..b891422 100644 --- a/lib/usercode/GwUserCode.cpp +++ b/lib/usercode/GwUserCode.cpp @@ -189,6 +189,10 @@ public: { return api->getQueue(); } + virtual QueueHandle_t getKbQueue() + { + return api->getKbQueue(); + } virtual void sendN2kMessage(const tN2kMsg &msg,bool convert) { 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()); req->send(404, "text/plain", "not found"); -} \ No newline at end of file +} diff --git a/src/main.cpp b/src/main.cpp index 6daaa51..50625d8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -158,6 +158,8 @@ GwCounter countNMEA2KIn("countNMEA2000in"); GwCounter countNMEA2KOut("countNMEA2000out"); GwIntervalRunner timers; +QueueHandle_t keyboardQueue = NULL; + bool checkPass(String hash){ return config.checkPass(hash); } @@ -269,6 +271,10 @@ public: { return &mainQueue; } + virtual QueueHandle_t getKbQueue() + { + return keyboardQueue; + } virtual void sendN2kMessage(const tN2kMsg &msg,bool convert) { handleN2kMessage(msg,sourceId,!convert); @@ -860,6 +866,8 @@ void setup() { webserver.begin(); xdrMappings.begin(); logger.flush(); + // remote keyboard support + keyboardQueue = xQueueCreate(10, sizeof(uint8_t)); GwConverterConfig converterConfig; converterConfig.init(&config,&logger); nmea0183Converter= N2kDataToNMEA0183::create(&logger, &boatData, @@ -869,7 +877,8 @@ void setup() { , config.getString(config.talkerId,String("GP")), &xdrMappings, - converterConfig + converterConfig, + keyboardQueue ); toN2KConverter= NMEA0183DataToN2K::create(&logger,&boatData,[](const tN2kMsg &msg, int sourceId)->bool{ diff --git a/web/config.json b/web/config.json index 26d3166..bef7790 100644 --- a/web/config.json +++ b/web/config.json @@ -88,6 +88,16 @@ "description":"the brightness of the led (0..255)", "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", "label": "NMEA0183 ID", @@ -1287,4 +1297,4 @@ "check": "checkXDR", "category": "xdr30" } -] \ No newline at end of file +]