Compare commits

..

2 Commits

Author SHA1 Message Date
Thomas Hooge 494acbf0d0 Work on system page 2025-08-25 20:56:14 +02:00
Thomas Hooge d7251eeb8a Code cleanup for keyboard task 2025-08-25 20:47:42 +02:00
8 changed files with 207 additions and 120 deletions

View File

@ -81,7 +81,7 @@ void hardwareInit(GwApi *api)
Wire.begin(); Wire.begin();
// Init PCF8574 digital outputs // Init PCF8574 digital outputs
Wire.setClock(I2C_SPEED); // Set I2C clock on 10 kHz Wire.setClock(I2C_SPEED); // Set I2C clock as defined
if(pcf8574_Out.begin()){ // Initialize PCF8574 if(pcf8574_Out.begin()){ // Initialize PCF8574
pcf8574_Out.write8(255); // Clear all outputs pcf8574_Out.write8(255); // Clear all outputs
} }

View File

@ -1,8 +1,8 @@
#ifndef _OBP60FUNCTIONS_H // SPDX-License-Identifier: GPL-2.0-or-later
#define _OBP60FUNCTIONS_H #if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
#include <Arduino.h> #include <Arduino.h>
#include "OBP60Hardware.h" #include "OBP60Hardware.h"
#include "OBPKeyboardTask.h"
// Global vars // Global vars
@ -58,9 +58,9 @@ void initKeys(CommonData &commonData) {
commonData.keydata[5].h = height; commonData.keydata[5].h = height;
} }
#ifdef HARDWARE_V21 #ifdef HARDWARE_V21
// Keypad functions for original OBP60 hardware // Keypad functions for original OBP60 hardware
int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) { int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) {
// Touch sensor values // Touch sensor values
// 35000 - Not touched // 35000 - Not touched
@ -233,35 +233,35 @@ void initKeys(CommonData &commonData) {
keycodeold2 = keycode2; keycodeold2 = keycode2;
return keystatus; return keystatus;
} }
#endif #endif
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
int readSensorpads(){ int readSensorpads(){
// Read key code // Read key code
if(digitalRead(UP) == LOW){ if (digitalRead(UP) == LOW) {
keycode = 10; // Left swipe keycode = 10; // Left swipe
} }
else if(digitalRead(DOWN) == LOW){ else if (digitalRead(DOWN) == LOW) {
keycode = 9; // Right swipe keycode = 9; // Right swipe
} }
else if(digitalRead(CONF) == LOW){ else if (digitalRead(CONF) == LOW) {
keycode = 3; // Key 3 keycode = 3; // Key 3
} }
else if(digitalRead(MENUE) == LOW){ else if (digitalRead(MENUE) == LOW) {
keycode = 1; // Key 1 keycode = 1; // Key 1
} }
else if(digitalRead(EXIT) == LOW){ else if (digitalRead(EXIT) == LOW) {
keycode = 2; // Key 2 keycode = 2; // Key 2
} }
else{ else {
keycode = 0; // No key activ keycode = 0; // No key activ
} }
return keycode; return keycode;
} }
// Keypad functions for OBP60 clone (thSensitivity is inactiv) // Keypad functions for OBP60 clone (thSensitivity is inactiv)
int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) { int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) {
pinMode(UP, INPUT); pinMode(UP, INPUT);
pinMode(DOWN, INPUT); pinMode(DOWN, INPUT);
pinMode(CONF, INPUT); pinMode(CONF, INPUT);
@ -286,18 +286,51 @@ void initKeys(CommonData &commonData) {
} }
// Copy keycode // Copy keycode
keycodeold = keycode; keycodeold = keycode;
// 100% Task-CPU RLY?
while(readSensorpads() > 0){} // Wait for pad release while(readSensorpads() > 0){} // Wait for pad release
delay(keydelay); delay(keydelay);
} }
} }
else{ else {
keycode = 0; keycode = 0;
keycodeold = 0; keycodeold = 0;
keystatus = 0; keystatus = 0;
} }
return keystatus; return keystatus;
}
#endif
void keyboardTask(void *param) {
// params needed:
// queue
// logger
// sensitivity
// use_syspage for deep sleep activation
KbTaskData *data = (KbTaskData *)param;
int keycode = 0;
data->logger->logDebug(GwLog::LOG, "Start keyboard task");
while (true) {
keycode = readKeypad(data->logger, data->sensitivity, data->use_syspage);
//send a key event
if (keycode != 0) {
xQueueSend(data->queue, &keycode, 0);
data->logger->logDebug(GwLog::LOG,"kbtask: send keycode: %d", keycode);
} }
#endif delay(20); // 50Hz update rate (20ms)
}
vTaskDelete(NULL);
}
void createKeyboardTask(KbTaskData *param) {
TaskHandle_t xHandle = NULL;
if (xTaskCreate(keyboardTask, "keyboard", configMINIMAL_STACK_SIZE + 1024, param, configMAX_PRIORITIES-1, &xHandle) != pdPASS) {
param->logger->logDebug(GwLog::ERROR, "Failed to create keyboard task!");
};
}
#endif #endif

View File

@ -0,0 +1,16 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "GwLog.h"
#include "Pagedata.h"
typedef struct {
QueueHandle_t queue;
GwLog* logger = nullptr;
uint sensitivity = 100;
#ifdef BOARD_OBP40S3
bool use_syspage = true;
#endif
} KbTaskData;
void initKeys(CommonData &commonData);
void createKeyboardTask(KbTaskData *param);

View File

@ -1,3 +1,4 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 #if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
#include <Adafruit_Sensor.h> // Adafruit Lib for sensors #include <Adafruit_Sensor.h> // Adafruit Lib for sensors
#include <Adafruit_BME280.h> // Adafruit Lib for BME280 #include <Adafruit_BME280.h> // Adafruit Lib for BME280
@ -568,6 +569,11 @@ void sensorTask(void *param){
// Send data from environment sensor all 2s // Send data from environment sensor all 2s
if(millis() > starttime6 + 2000){ if(millis() > starttime6 + 2000){
starttime6 = millis(); starttime6 = millis();
// DEBUG
UBaseType_t stackfree = uxTaskGetStackHighWaterMark(NULL);
api->getLogger()->logDebug(GwLog::LOG, "obpSensortask Stack=%d", stackfree);
unsigned char TempSource = 2; // Inside temperature unsigned char TempSource = 2; // Inside temperature
unsigned char PressureSource = 0; // Atmospheric pressure unsigned char PressureSource = 0; // Atmospheric pressure
unsigned char HumiditySource = 0; // Inside humidity unsigned char HumiditySource = 0; // Inside humidity
@ -785,8 +791,12 @@ void sensorTask(void *param){
vTaskDelete(NULL); vTaskDelete(NULL);
} }
void createSensorTask(SharedData *shared) {
void createSensorTask(SharedData *shared){ TaskHandle_t xHandle = NULL;
xTaskCreate(sensorTask,"readSensors",10000,shared,3,NULL); GwLog *logger = shared->api->getLogger();
esp_err_t err = xTaskCreate(sensorTask, "readSensors", configMINIMAL_STACK_SIZE + 2048, shared, 3, &xHandle);
if ( err != pdPASS) {
logger->logDebug(GwLog::ERROR, "Failed to create sensor task! (err=%d)", err);
};
} }
#endif #endif

View File

@ -1,3 +1,4 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include "GwSynchronized.h" #include "GwSynchronized.h"
#include "GwApi.h" #include "GwApi.h"

View File

@ -8,7 +8,7 @@
* 1. Hard and software information * 1. Hard and software information
* 2. System settings * 2. System settings
* 3. System configuration: running and NVRAM * 3. System configuration: running and NVRAM
* 4. NMEA2000 device list * 4. NMEA2000 device list if NMEA2000 enabled
* 5. SD Card information if available * 5. SD Card information if available
* *
* TODO * TODO
@ -43,6 +43,8 @@
#define DISPLAYINFO STRINGIZE(EPDTYPE) #define DISPLAYINFO STRINGIZE(EPDTYPE)
#define GXEPD2INFO STRINGIZE(GXEPD2VERS) #define GXEPD2INFO STRINGIZE(GXEPD2VERS)
#define N2K_INACTIVE_AGE 30000
class PageSystem : public Page class PageSystem : public Page
{ {
private: private:
@ -68,6 +70,10 @@ private:
double homelon; double homelon;
Nmea2kTwai *NMEA2000; Nmea2kTwai *NMEA2000;
unsigned long n2kRxOld = 0; // to detect bus activity
unsigned long n2kTxOld = 0;
long n2k_ts = 0; // timestamp of last activity
bool n2k_active = false;
char mode = 'N'; // (N)ormal, (S)ettings, (C)onfiguration, (D)evice list, c(A)rd char mode = 'N'; // (N)ormal, (S)ettings, (C)onfiguration, (D)evice list, c(A)rd
int8_t editmode = -1; // marker for menu/edit/set function int8_t editmode = -1; // marker for menu/edit/set function
@ -233,7 +239,7 @@ private:
epd->print(String(Heap_free)); epd->print(String(Heap_free));
// RAM free for task // RAM free for task
int RAM_free = uxTaskGetStackHighWaterMark(NULL); UBaseType_t RAM_free = uxTaskGetStackHighWaterMark(NULL);
epd->setCursor(202, y0 + 32); epd->setCursor(202, y0 + 32);
epd->print("Task free:"); epd->print("Task free:");
epd->setCursor(300, y0 + 32); epd->setCursor(300, y0 + 32);
@ -390,7 +396,6 @@ private:
epd->setFont(&Ubuntu_Bold8pt8b); epd->setFont(&Ubuntu_Bold8pt8b);
epd->setCursor(x0, y0); epd->setCursor(x0, y0);
epd->print("Work in progress...");
#ifdef BOARD_OBP60S3 #ifdef BOARD_OBP60S3
// This mode should not be callable by devices without card hardware // This mode should not be callable by devices without card hardware
// In case of accidential reaching this, display a friendly message // In case of accidential reaching this, display a friendly message
@ -402,32 +407,39 @@ private:
magic.dat magic.dat
version.dat version.dat
readme.txt readme.txt
IMAGES/ BAK/
CHARTS/ CHARTS/
LOGS/
DATA/ DATA/
IMAGES/
LOGS/
*/ */
// Simple test for one file in root // Simple test for magic file in root
epd->setCursor(x0, y0 + 32); epd->setCursor(x0, y0 + 16);
String file_test = MOUNT_POINT "/IMG/icon-settings2.pbm"; const char* file_magic = MOUNT_POINT "/magic.dat";
logger->logDebug(GwLog::LOG, "Testfile: %s", file_test.c_str()); logger->logDebug(GwLog::LOG, "Test magicfile: %s", file_magic);
struct stat st; struct stat st;
if (stat(file_test.c_str(), &st) == 0) { if (stat(file_magic, &st) == 0) {
epd->printf("File %s exists", file_test.c_str()); epd->printf("Magicfile %s exists", file_magic);
} else { } else {
epd->printf("File %s not found", file_test.c_str()); epd->printf("Magicfile %s not found", file_magic);
} }
// Root directory check // Root directory check
DIR* dir = opendir(MOUNT_POINT); DIR* dir = opendir(MOUNT_POINT);
int dy = 0;
if (dir != NULL) { if (dir != NULL) {
logger->logDebug(GwLog::LOG, "Root directory: %s", MOUNT_POINT); logger->logDebug(GwLog::LOG, "Root directory: %s", MOUNT_POINT);
struct dirent* entry; struct dirent* entry;
while ((entry = readdir(dir)) != NULL) { while (((entry = readdir(dir)) != NULL) and (dy < 140)) {
epd->setCursor(x0, y0 + 64 + dy);
epd->print(entry->d_name);
// type 1 is file, type 2 is dir
if (entry->d_type == 2) {
epd->print("/");
}
dy += 20;
logger->logDebug(GwLog::LOG, " %s type %d", entry->d_name, entry->d_type); logger->logDebug(GwLog::LOG, " %s type %d", entry->d_name, entry->d_type);
// type 1 is file
// type 2 is dir
} }
closedir(dir); closedir(dir);
} else { } else {
@ -436,7 +448,7 @@ private:
// try to load example pbm file // try to load example pbm file
// TODO create PBM load function in imglib // TODO create PBM load function in imglib
String filepath = MOUNT_POINT "/IMG/icon-settings2.pbm"; String filepath = MOUNT_POINT "/IMAGES/icon-settings2.pbm";
const char* file_img = filepath.c_str(); const char* file_img = filepath.c_str();
FILE* fh = fopen(file_img, "r"); FILE* fh = fopen(file_img, "r");
if (fh != NULL) { if (fh != NULL) {
@ -494,8 +506,12 @@ private:
#endif #endif
} }
void displayModeDevicelist() { void displayModeDevicelist(bool bus_active) {
// NMEA2000 device list // NMEA2000 device list
// TODO Check if NMEA2000 enabled globally
// if disabled this page should not be shown
epd->setFont(&Ubuntu_Bold12pt8b); epd->setFont(&Ubuntu_Bold12pt8b);
epd->setCursor(8, 48); epd->setCursor(8, 48);
epd->print("NMEA2000 device list"); epd->print("NMEA2000 device list");
@ -508,6 +524,11 @@ private:
epd->print("TxD: "); epd->print("TxD: ");
epd->print(String(commonData->status.n2kTx)); epd->print(String(commonData->status.n2kTx));
// show bus activity
epd->setCursor(20, 120);
epd->print("Bus: ");
epd->print(bus_active ? "active" : "inactive");
epd->setCursor(20, 140); epd->setCursor(20, 140);
epd->printf("N2k source address: %d", NMEA2000->GetN2kSource()); epd->printf("N2k source address: %d", NMEA2000->GetN2kSource());
@ -685,7 +706,15 @@ public:
displayModeSDCard(); displayModeSDCard();
break; break;
case 'D': case 'D':
displayModeDevicelist(); // check bus status
if (commonData->status.n2kRx != n2kRxOld || commonData->status.n2kTx != n2kTxOld) {
n2kRxOld = commonData->status.n2kRx;
n2kTxOld = commonData->status.n2kTx;
n2k_ts = millis();
} else {
n2k_active = (millis() - n2k_ts <= N2K_INACTIVE_AGE);
}
displayModeDevicelist(n2k_active);
break; break;
} }

View File

@ -332,6 +332,7 @@ public:
// Show value6 (=fourth user-configured parameter) and ssource, so that they do not collide with the wind pointer // Show value6 (=fourth user-configured parameter) and ssource, so that they do not collide with the wind pointer
if (cos(value1) > 0) { if (cos(value1) > 0) {
// pointer points upwards
epd->setFont(&DSEG7Classic_BoldItalic16pt7b); epd->setFont(&DSEG7Classic_BoldItalic16pt7b);
epd->setCursor(160, 200); epd->setCursor(160, 200);
epd->print(svalue6); // Value epd->print(svalue6); // Value
@ -347,6 +348,7 @@ public:
epd->print(ssource); // true or app. epd->print(ssource); // true or app.
} }
else { else {
// pointer points downwards
epd->setFont(&DSEG7Classic_BoldItalic16pt7b); epd->setFont(&DSEG7Classic_BoldItalic16pt7b);
epd->setCursor(160, 130); epd->setCursor(160, 130);
epd->print(svalue6); epd->print(svalue6);
@ -355,9 +357,9 @@ public:
epd->print(" "); epd->print(" ");
epd->print(holdvalues ? unit6old : unit6); epd->print(holdvalues ? unit6old : unit6);
if (sin(value1) > 0) { if (sin(value1) > 0) {
epd->setCursor(160, 130); epd->setCursor(160, 200);
} else { } else {
epd->setCursor(220, 130); epd->setCursor(220, 200);
} }
epd->print(ssource); //true or app. epd->print(ssource); //true or app.
} }

View File

@ -13,10 +13,14 @@
#include <NMEA0183Messages.h> #include <NMEA0183Messages.h>
#include <GxEPD2_BW.h> // GxEPD2 lib for b/w E-Ink displays #include <GxEPD2_BW.h> // GxEPD2 lib for b/w E-Ink displays
#include "OBP60Extensions.h" // Functions lib for extension board #include "OBP60Extensions.h" // Functions lib for extension board
#include "OBP60Keypad.h" // Functions for keypad #include "OBPKeyboardTask.h" // Functions lib for keyboard handling
#include "BoatDataCalibration.h" // Functions lib for data instance calibration #include "BoatDataCalibration.h" // Functions lib for data instance calibration
#include "OBPRingBuffer.h" // Functions lib with ring buffer for history storage of some boat data #include "OBPRingBuffer.h" // Functions lib with ring buffer for history storage of some boat data
#include "OBPDataOperations.h" // Functions lib for data operations such as true wind calculation #include "OBPDataOperations.h" // Functions lib for data operations such as true wind calculation
#include "OBP60QRWiFi.h" // Functions lib for WiFi QR code
#include "OBPSensorTask.h" // Functions lib for sensor data
#include "freertos/task.h" // WIP possible unused
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
#include "driver/rtc_io.h" // Needs for weakup from deep sleep #include "driver/rtc_io.h" // Needs for weakup from deep sleep
@ -26,8 +30,6 @@
// Pictures // Pictures
#include "images/OBP_400x300.xbm" // OBP Logo #include "images/OBP_400x300.xbm" // OBP Logo
#include "images/unknown.xbm" // unknown page indicator #include "images/unknown.xbm" // unknown page indicator
#include "OBP60QRWiFi.h" // Functions lib for WiFi QR code
#include "OBPSensorTask.h" // Functions lib for sensor data
// Global vars // Global vars
bool initComplete = false; // Initialization complete bool initComplete = false; // Initialization complete
@ -119,34 +121,26 @@ void OBP60Init(GwApi *api){
} }
typedef struct { /* ux-functions not working WTF?
int page0 = 0; bool listTasks(GwLog *logger) {
QueueHandle_t queue; UBaseType_t taskCount = uxTaskGetNumberOfTasks();
GwLog* logger = nullptr; TaskStatus_t *taskStatusArray;
// GwApi* api = NULL;
uint sensitivity = 100;
bool use_syspage = true;
} MyData;
// Keyboard Task taskStatusArray = (TaskStatus_t *)pvPortMalloc(taskCount * sizeof(TaskStatus_t));
void keyboardTask(void *param){ if (taskStatusArray != NULL) {
MyData *data=(MyData *)param; taskCount = uxTaskGetSystemState(taskStatusArray, taskCount, NULL);
for (UBaseType_t i = 0; i < taskCount; i++) {
int keycode = 0; logger->logDebug(GwLog::LOG, "Task Name: %s (Stack=%d)", taskStatusArray[i].pcTaskName,
data->logger->logDebug(GwLog::LOG,"Start keyboard task"); taskStatusArray[i].usStackHighWaterMark);
// more fields in task status
// Loop for keyboard task // xHandle, uxCurrentPriority, uxBasePriority, usStackHighWaterMark
while (true){
keycode = readKeypad(data->logger, data->sensitivity, data->use_syspage);
//send a key event
if(keycode != 0){
xQueueSend(data->queue, &keycode, 0);
data->logger->logDebug(GwLog::LOG,"Send keycode: %d", keycode);
} }
delay(20); // 50Hz update rate (20ms) vPortFree(taskStatusArray);
return true;
} }
vTaskDelete(NULL); logger->logDebug(GwLog::ERROR, "Failed to allocate memory for task list");
} return false;
} */
class BoatValueList{ class BoatValueList{
public: public:
@ -718,18 +712,18 @@ void OBP60Task(GwApi *api){
doImageRequest(api, &pageNumber, pages, request); doImageRequest(api, &pageNumber, pages, request);
}); });
//now we have prepared the page data // now we have prepared the page data
//we start a separate task that will fetch our keys... // we start a separate task that will fetch our keys...
MyData allParameters; KbTaskData kbparams;
allParameters.logger=api->getLogger(); kbparams.logger = api->getLogger();
allParameters.page0=3; kbparams.queue = xQueueCreate(10, sizeof(int));
allParameters.queue=xQueueCreate(10,sizeof(int)); kbparams.sensitivity = api->getConfig()->getInt(GwConfigDefinitions::tSensitivity);
allParameters.sensitivity= api->getConfig()->getInt(GwConfigDefinitions::tSensitivity);
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
allParameters.use_syspage = syspage_enabled; kbparams.use_syspage = syspage_enabled;
#endif #endif
xTaskCreate(keyboardTask,"keyboard",2000,&allParameters,configMAX_PRIORITIES-1,NULL); createKeyboardTask(&kbparams);
SharedData *shared=new SharedData(api); // we start a separate task to collect sensor data
SharedData *shared = new SharedData(api);
createSensorTask(shared); createSensorTask(shared);
// Task Loop // Task Loop
@ -785,6 +779,8 @@ void OBP60Task(GwApi *api){
pages[pageNumber].page->setupKeys(); // Initialize keys for first page pages[pageNumber].page->setupKeys(); // Initialize keys for first page
// listTasks(logger);
// Main loop runs with 100ms // Main loop runs with 100ms
//#################################################################################### //####################################################################################
@ -836,7 +832,7 @@ void OBP60Task(GwApi *api){
// Check the keyboard message // Check the keyboard message
int keyboardMessage=0; int keyboardMessage=0;
while (xQueueReceive(allParameters.queue,&keyboardMessage,0)){ while (xQueueReceive(kbparams.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;