Keyboard functional and first sending of PGN 127502

This commit is contained in:
2026-01-04 19:28:21 +01:00
parent 12687b17ab
commit c953340362
5 changed files with 501 additions and 165 deletions

74
README
View File

@@ -2,11 +2,10 @@ OBP Keypad
========== ==========
- Stromversorgung über M12-Anschluß über NMEA2000 - Stromversorgung über M12-Anschluß über NMEA2000
Eingangsbereich 6~21V - Eingangsbereich 6~21V
- Ein- und Ausschalten durch langen Tastendruck auf DST/ ONOFF - Konfigurationsmodus durch langen Tastendruck auf DST
- Tiefschlaf und Reset aus Konfigmodus heraus auswählbar
- Konfiguration über Web-Interface - Konfiguration über Web-Interface
- PWR leuchtet grün wen NMEA2000-Verbindung etabliert
- PWR leuchtet rot wenn nur Stromversorgung aktiv ist
- Buzzer für Tastendruck-Feedback - Buzzer für Tastendruck-Feedback
passiv, damit können die Töne mit PWM programmiert werden passiv, damit können die Töne mit PWM programmiert werden
- I²C Temp/Hum-Sensor SHT31 - I²C Temp/Hum-Sensor SHT31
@@ -25,14 +24,15 @@ Optionen für später
bedeutet auch: viel komplizierteres Gehäuse bedeutet auch: viel komplizierteres Gehäuse
Damit die LEDs nicht stören, kann umgeschaltet werden zwischen Damit die LEDs nicht stören, kann umgeschaltet werden zwischen
permanentem Leuchten und nur kurzem Aufblinken bei Betätigung. permanentem Leuchten und nur kurzem Aufblinken bei Betätigung:
Tag- und Nachtmodus
Einschaltvorgang Einschaltvorgang
---------------- ----------------
Es erfolgt ein "Lampentest": Alle LEDs werden einmal durchgeschaltet. - Status leuchtet kurz rot auf
- Es erfolgt ein "Lampentest": Alle LEDs werden einmal durchgeschaltet.
Sind alle Tests erfolgreich ertönt ein Buzzer-Signal. - Sind alle Tests erfolgreich ertönt ein Buzzer-Signal.
Beschreibung Beschreibung
------------ ------------
@@ -44,31 +44,29 @@ Die Destination-LEDs leuchten nur, wenn ein entsprechendes
N2K-Zielgerät konfiguriert und erkannt wurde. N2K-Zielgerät konfiguriert und erkannt wurde.
Gehäuse
-------
Bohrung Taster: 12mm Bohrung Taster: 12mm
Taster Außenmaß: 17.5mm Taster Außenmaß: 17.5mm
Verbindungskabel CPU-Platine Verbindungskabel CPU-Platine
JST 2.54 XH 6 Pin Steckverbinder -> LED JST 2.54 XH 7 Pin Steckverbinder -> LEDs + GND
Anschlußmöglichkeiten Anschlußmöglichkeiten
---------------------
für Stromversorgung +12V und NMEA2000 für Stromversorgung +12V und NMEA2000
4pin Terminalblock steck-/schraubbar +12V, DNG, CAN-L, CAN-H 2pin Terminalblock
für I²C-Module für I²C-Module
2x 4pin Buchsenleiste weibl. 2x 4pin Buchsenleiste weibl.
1x QWIIC-Buchse (JST_SH_BM04B-SRSS-TB_04x1.00mm) 1x qwiic-Buchse (JST_SH_BM04B-SRSS-TB_04x1.00mm)
für CAN-Transceiver-Modul: 4fach buchse liegend
für mechanische Taster für mechanische Taster
1x JST 2.54 XH 7 Pin Steckverbinder -> Tasten 1x Terminalblock 8fach, 7 Tasten und GND
Masseverbindung über einzelnes getrenntes Kabel
in eine 8-fach WAGO-Klemme
für LEDs
TBD
Bemerkungen Bemerkungen
@@ -77,6 +75,10 @@ Bemerkungen
Bei den aktuell verwendeten Tasten sind die Anschlußdrähte extrem Bei den aktuell verwendeten Tasten sind die Anschlußdrähte extrem
filigran. Leichtes Brechen und schlechte Verarbeitung. filigran. Leichtes Brechen und schlechte Verarbeitung.
Es gibt verschiedene Varianten mit unterschiedlicher Federkraft.
Auswahl muß noch erfolgen
Beschaltung MCU Nano Beschaltung MCU Nano
-------------------- --------------------
@@ -94,7 +96,7 @@ werden!
Die Pins für SPI (D11, D12, D13) sind absichtlich Die Pins für SPI (D11, D12, D13) sind absichtlich
nicht belegt um frei für Erweiterungen zu sein. An SPI kann nicht belegt um frei für Erweiterungen zu sein. An SPI kann
ggf. ein Epaper angeschlossen werden. ggf. ein E-Paper angeschlossen werden.
Key Color Pin Remarks Key Color Pin Remarks
@@ -124,27 +126,35 @@ ggf. ein Epaper angeschlossen werden.
BUZZER BUZZER
TBD TBD
Bauteilliste Bauteilliste (WIP)
------------ ------------
1x ESP32-S3 Nano (Waveshare) 1x ESP32-S3 Nano (Waveshare)
berrybase.de berrybase.de
eckstein-shop.de eckstein-shop.de
5x Taster schwarz 6x Taster schwarz (1-6)
2x Taster gelb 1x Taster farbig (DST)
1x M12 Einbaubuchse 1x M12 Micro-C Einbaubuchse
1x RGB LED (gemeinsame Anode), diffus 1x RGB LED (gemeinsame Kathode), diffus
3x LED grün, diffus 3x LED grün, diffus
6x Widerstand 330 Ohm 6x Widerstand 330 Ohm
1x SN65HVD230 CAN Transceiver 1x SN65HVD230 CAN Transceiver
1x Buzzer, passiv 1x Buzzer 12V, passiv
1x Kabelsatz 1x MOSFET 2N7000
1x Terminalblock 4pol. 2,54mm schraubbar 1x Widerstand 150 Ohm
1x Gehäuse 150x60x40 bestehend auf Front- und Rückseite 1x Kabelsatz für Tasten, 0,25 bis 0,5 mm²
4x Befestigungsschraube M4 1x Terminalblock 2pol. 2,54mm schraubbar
4x Gehäuseschraube M2,5 1x 3D-Gehäuse bestehend auf Front- und Rückseite
1x Mutternwerkzeug 3D-Druck
4x Befestigungsschraube M4 Senkkopf
4x Gehäuseschraube M2,5, lang
8x Platinenschraube M2,5, kurz
1x Silikondichtschnur 2mm 1x Silikondichtschnur 2mm
1x SHT31 I²C-Modul
Buchsenleiste 2,54 mm
Stiftleiste 2,54mm
2x Jumper
1x Polyfuse
Konfiguration Konfiguration
------------- -------------

View File

@@ -6,6 +6,7 @@
// NMEA2000 defaults // NMEA2000 defaults
#define N2K_DEFAULT_NODEID 124 #define N2K_DEFAULT_NODEID 124
#define NMEA2000_HEARTBEAT_INTERVAL 5000 // milliseconds
// Keys // Keys
#define KEY_1 GPIO_NUM_5 // D2 #define KEY_1 GPIO_NUM_5 // D2
@@ -54,3 +55,24 @@
#define SPI_MISO GPIO_NUM_47 // D12 #define SPI_MISO GPIO_NUM_47 // D12
#define SPI_SCK GPIO_NUM_48 // D13 #define SPI_SCK GPIO_NUM_48 // D13
#define SPI_CS GPIO_NUM_46 // B0 #define SPI_CS GPIO_NUM_46 // B0
// Button indices
#define BUTTON_1 0
#define BUTTON_2 1
#define BUTTON_3 2
#define BUTTON_4 3
#define BUTTON_5 4
#define BUTTON_6 5
#define BUTTON_DST 6
enum class ButtonPressType : uint8_t
{
SHORT, // < 1 second
MEDIUM, // >= 1 second and < 3 seconds
LONG // >= 3 seconds
};
struct ButtonEvent
{
uint8_t buttonId;
ButtonPressType pressType;
};

View File

@@ -41,10 +41,9 @@ build_unflags =
[env:esp32-s3-nano] [env:esp32-s3-nano]
build_type = release # debug | release build_type = release # debug | release
# board = esp32-s3-devkitc-1
board = esp32_s3_nano board = esp32_s3_nano
#board = arduino_nano_esp32 # ATTENTION! Pin numbering scheme changes #board = arduino_nano_esp32 # ATTENTION! Pin numbering scheme changes
board_upload.flash_size = 16MB board_upload.flash_size = 16MB
board_build.partitions = default.csv board_build.partitions = default_16MB.csv #ESP32-S3 N16, 16MB flash
upload_port = /dev/ttyACM0 upload_port = /dev/ttyACM0
upload_protocol = esptool upload_protocol = esptool

View File

@@ -13,9 +13,9 @@
#include "Nmea2kTwai.h" #include "Nmea2kTwai.h"
#include <map> #include <map>
#include "mbedtls/md.h" // for SHA256 #include "mbedtls/md.h" // for SHA256
#include <esp32/clk.h> // for cpu frequency #include <esp32/clk.h> // for cpu frequency
#include "driver/rtc_io.h" // for wakeup from deep sleep
String get_sha256(String payload) { String get_sha256(String payload) {
byte shaResult[32]; byte shaResult[32];
@@ -90,8 +90,10 @@ uint64_t chipid = ESP.getEfuseMac();
const char* wifi_ssid = "OBPKP61"; const char* wifi_ssid = "OBPKP61";
const char* wifi_pass = "keypad61"; const char* wifi_pass = "keypad61";
bool apEnabled = false;
AsyncWebServer server(80); AsyncWebServer server(80);
unsigned long firstStart = 0;
unsigned long lastSensor = 0; unsigned long lastSensor = 0;
unsigned long lastPrint = 0; unsigned long lastPrint = 0;
unsigned long counter = 0; unsigned long counter = 0;
@@ -100,12 +102,17 @@ bool rgb_r = false;
bool rgb_g = false; bool rgb_g = false;
bool rgb_b = false; bool rgb_b = false;
char destination = 'A'; // A | B | C char mode = 'N'; // (N)ormal | (C)onfig
char ledmode = 'D'; // (D)ay | (N)ight
char audiomode = 'E'; // (E)nabled | (D)isabled
RTC_DATA_ATTR char destination = 'A'; // A | B | C im RTC-Speicher überlebt deep sleep
uint8_t led_brightness = 16; // analog pin: 0 .. 255 uint8_t led_brightness = 16; // analog pin: 0 .. 255
uint8_t rgb_brightness = 64; uint8_t rgb_brightness = 64;
uint buzzerpower = 50; // TBD make use of this uint buzzerpower = 50; // TBD make use of this
uint8_t keycode[6]; // configurable keycodes
SHT31 sht(SHT31_ADDRESS); SHT31 sht(SHT31_ADDRESS);
bool sht_available = false; bool sht_available = false;
float temp = 0.0; float temp = 0.0;
@@ -143,7 +150,7 @@ String uptime_with_unit() {
void ledtest() { void ledtest() {
// all led one after another to test functionality // all led one after another to test functionality
Serial.print("LED test started"); Serial.println("LED test started");
// Onbard RGB LED, inverted mode // Onbard RGB LED, inverted mode
digitalWrite(LED_IR, LOW); digitalWrite(LED_IR, LOW);
@@ -173,9 +180,6 @@ void ledtest() {
analogWrite(LED_C, led_brightness); analogWrite(LED_C, led_brightness);
delay(500); delay(500);
analogWrite(LED_C, 0); analogWrite(LED_C, 0);
// select dst A finally
analogWrite(LED_A, led_brightness);
delay(500);
// enclosure rgb led (common cathode, so low is off) // enclosure rgb led (common cathode, so low is off)
analogWrite(RGBLED_R, rgb_brightness); analogWrite(RGBLED_R, rgb_brightness);
@@ -190,25 +194,126 @@ void ledtest() {
delay(700); delay(700);
analogWrite(RGBLED_B, 0); analogWrite(RGBLED_B, 0);
Serial.print("LED test finished"); Serial.println("LED test finished");
} }
TaskHandle_t ledTaskHandle = NULL;
TaskHandle_t sensorTaskHandle = NULL;
TaskHandle_t keyTaskHandle = NULL;
QueueHandle_t ledQueue = NULL;
QueueHandle_t keyQueue = NULL;
void ledTask(void *parameter) {
Serial.println("Starting LED task");
for (;;) {
vTaskDelay(5000);
analogWrite(RGBLED_G, 10); // a short activity flash
vTaskDelay(20);
analogWrite(RGBLED_G, 0);
}
}
void sensorTask(void *parameter) {
Serial.println("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
Serial.println("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 cpuFreqTimerCallback(TimerHandle_t xTimer) {
Serial.println("after 3 minutes: set CPU frequency to 160MHz");
setCpuFrequencyMhz(160);
}
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
// while (!Serial) delay(10); // while (!Serial) delay(10); verhindert Booten ohne USB-Verbindung
delay(500); delay(500);
preferences.begin("nvs", false);
keycode[0] = preferences.getInt("key1", 1);
keycode[1] = preferences.getInt("key2", 2);
keycode[2] = preferences.getInt("key3", 3);
keycode[3] = preferences.getInt("key4", 4);
keycode[4] = preferences.getInt("key5", 5);
keycode[5] = preferences.getInt("key6", 6);
preferences.end();
// Configure I/O pins // Configure I/O pins
// internal user led (red) // internal user led (red)
pinMode(LED_USER, OUTPUT); pinMode(LED_USER, OUTPUT);
// Init onbard RGB led // Init onbard RGB led and switch off
pinMode(LED_IR, OUTPUT); pinMode(LED_IR, OUTPUT);
pinMode(LED_IG, OUTPUT); pinMode(LED_IG, OUTPUT);
pinMode(LED_IB, OUTPUT); pinMode(LED_IB, OUTPUT);
digitalWrite(LED_IR, HIGH);
digitalWrite(LED_IG, HIGH);
digitalWrite(LED_IB, HIGH);
// destination leds // destination leds
pinMode(LED_A, OUTPUT); pinMode(LED_A, OUTPUT);
@@ -244,6 +349,13 @@ void setup() {
ESP_LOGI(TAG, "Starting ..."); ESP_LOGI(TAG, "Starting ...");
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
if (cause == ESP_SLEEP_WAKEUP_EXT0) {
ESP_LOGI(TAG, " Wake up by key");
} else {
destination = 'A';
}
// N2K basics // N2K basics
nodeid = N2K_DEFAULT_NODEID; nodeid = N2K_DEFAULT_NODEID;
ESP_LOGI(TAG, "N2K default node is %d", nodeid); ESP_LOGI(TAG, "N2K default node is %d", nodeid);
@@ -264,6 +376,7 @@ void setup() {
IPAddress ap_subnet(255, 255, 255, 0); IPAddress ap_subnet(255, 255, 255, 0);
IPAddress ap_gateway(ap_addr); IPAddress ap_gateway(ap_addr);
WiFi.softAPConfig(ap_addr, ap_gateway, ap_subnet); WiFi.softAPConfig(ap_addr, ap_gateway, ap_subnet);
apEnabled = true;
// Route for root / web page // Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
@@ -292,11 +405,29 @@ void setup() {
request->send(200, "application/json", out); request->send(200, "application/json", out);
}); });
server.on("/api/config", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/api/config", HTTP_GET, [](AsyncWebServerRequest *request){
StaticJsonDocument<100> doc; StaticJsonDocument<512> doc;
doc["systemName"] = "Keypad1"; doc["systemName"] = "Keypad1";
doc["logLevel"] = 0;
doc["version"] = "0.0"; doc["version"] = "0.0";
doc["fwtype"] = "unknown"; doc["fwtype"] = "unknown";
doc["salt"] = "secret"; doc["salt"] = "secret";
doc["AdminPassword"] = "********";
doc["useAdminPass"] = false;
doc["apIp"] = "192.168.15.1";
doc["apMask"] = "255.255.255.0";
doc["apPassword"] = "********";
doc["stopApTime"] = 0;
doc["cpuSpeed"] = 160;
doc["tempFormat"] = "C";
doc["ledBrightness"] = led_brightness;
doc["ledRgbBrightness"] = rgb_brightness;
doc["tempFormat"] = "C";
doc["key1"] = keycode[BUTTON_1];
doc["key2"] = keycode[BUTTON_2];
doc["key3"] = keycode[BUTTON_3];
doc["key4"] = keycode[BUTTON_4];
doc["key5"] = keycode[BUTTON_5];
doc["key6"] = keycode[BUTTON_6];
String out; String out;
serializeJson(doc, out); serializeJson(doc, out);
request->send(200, "application/json", out); request->send(200, "application/json", out);
@@ -334,6 +465,13 @@ void setup() {
serializeJson(doc, out); serializeJson(doc, out);
request->send(200, "application/json", out); request->send(200, "application/json", out);
}); });
server.on("/api/update", HTTP_POST, [](AsyncWebServerRequest *request){
StaticJsonDocument<100> doc;
doc["status"] = "FAILED";
String out;
serializeJson(doc, out);
request->send(200, "application/json", out);
});
// TODO POST vom Client entgegennehmen // TODO POST vom Client entgegennehmen
@@ -348,17 +486,37 @@ void setup() {
NMEA2000.SetProductInformation("00000001", // Manufacturer's Model serial code NMEA2000.SetProductInformation("00000001", // Manufacturer's Model serial code
74, // Manufacturer's product code 74, // Manufacturer's product code
"OBPkeypad6/1", // Manufacturer's Model ID "OBPkeypad6/1", // Manufacturer's Model ID
"1.0.0 (2025-11-28)", // Manufacturer's Software version code "0.1.0 (2026-01-04)", // Manufacturer's Software version code
"0.1" // Manufacturer's Model version "0.1", // Manufacturer's Model version
1 // LoadEquivalency (LEN)
); );
uint32_t uid; // ISO 21bit identity number devived from chipid (MAC)
uid = ((chipid >> 32) ^ (chipid & 0xFFFFFFFF)) & 0x1FFFFF;
// TODO Device unique id stored in preferences // TODO Device unique id stored in preferences
NMEA2000.SetDeviceInformation(1, // Unique number. Use e.g. Serial number. NMEA2000.SetDeviceInformation(uid, // Unique number. Use e.g. Serial number.
130, // Device function=Atmospheric 130, // Device function=Button Interface (or 190?)
85, // Device class=External Environment 110, // Device class=Human Interface? (or 140?)
2046 2046, // Manufacturer code (custom OBP)
4 // Industrygoup=Marine
); );
NMEA2000.SetForwardType(tNMEA2000::fwdt_Text);
// Debug
// NMEA2000.SetForwardStream(&Serial);
// NMEA2000.SetDebugMode(tNMEA2000::dm_2);
NMEA2000.SetMode(tNMEA2000::N2km_ListenAndNode, nodeid);
NMEA2000.SetForwardOwnMessages(false);
NMEA2000.SetHeartbeatIntervalAndOffset(NMEA2000_HEARTBEAT_INTERVAL);
const unsigned long TransmitPGNs[] = {
127502UL,
0
};
NMEA2000.ExtendTransmitMessages(TransmitPGNs);
NMEA2000.Open();
// internal user led (red) // internal user led (red)
digitalWrite(LED_USER, HIGH); digitalWrite(LED_USER, HIGH);
@@ -369,10 +527,11 @@ void setup() {
ledcSetup(LEDC_CHANNEL, LEDC_BASE_FREQ, LEDC_TIMER_8_BIT); ledcSetup(LEDC_CHANNEL, LEDC_BASE_FREQ, LEDC_TIMER_8_BIT);
ledcAttachPin(BUZZER, LEDC_CHANNEL); ledcAttachPin(BUZZER, LEDC_CHANNEL);
// Test tone while booting // Test tone while booting
// Buzzer 12V, 2500Hz +/- 200Hz, 30mA, ca. 90dB
if (ledcWriteTone(LEDC_CHANNEL, 2300) == 0) { if (ledcWriteTone(LEDC_CHANNEL, 2300) == 0) {
Serial.println("Error setting buzzer tone"); Serial.println("Error setting buzzer tone");
} else { } else {
delay(750); delay(50);
ledcWriteTone(LEDC_CHANNEL, 0); // Buzzer off ledcWriteTone(LEDC_CHANNEL, 0); // Buzzer off
} }
@@ -380,6 +539,19 @@ void setup() {
analogWrite(RGBLED_R, 0); // boot status off analogWrite(RGBLED_R, 0); // boot status off
delay(500); delay(500);
ledtest(); ledtest();
// select current destination
switch (destination) {
case 'A':
analogWrite(LED_A, led_brightness);
break;
case 'B':
analogWrite(LED_B, led_brightness);
break;
case 'C':
analogWrite(LED_C, led_brightness);
break;
}
// I²C // I²C
Serial.print("SHT31_LIB_VERSION: "); Serial.print("SHT31_LIB_VERSION: ");
@@ -396,18 +568,47 @@ void setup() {
// Additional tests // Additional tests
String passhash = get_sha256("secretTEST"); String passhash = get_sha256("secretTEST");
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)) {
Serial.println("DST-Taste ist als Wakeup-Pin konfiguriert");
esp_sleep_enable_ext0_wakeup(KEY_DST, 0);
} else {
Serial.println("Keine Wakeup-Funktion vorhanden! Deep-sleep deaktiviert.");
}
TimerHandle_t cpuFreqTimer = xTimerCreate(
"cpuFreqTimer",
pdMS_TO_TICKS(2 * 60 * 1000),
pdFALSE, // one shot only
nullptr,
cpuFreqTimerCallback
);
xTimerStart(cpuFreqTimer, 0);
}
void shortBeep() {
ledcWriteTone(LEDC_CHANNEL, 2500);
delay(15);
ledcWriteTone(LEDC_CHANNEL, 0);
} }
void atariKeyclick() { void atariKeyclick() {
/*
* Anderer 12V-fähiger Buzzer!
*
Buzzer + → 12V
Buzzer → IRLZ44N Drain
IRLZ44N Source → GND
ESP32 GPIO43 → 1kΩ resistor → Gate
ESP32 GND must be connected to the same GND as the 12V supply.
* */
ledcWriteTone(LEDC_CHANNEL, 3200); ledcWriteTone(LEDC_CHANNEL, 3200);
delayMicroseconds(3000); delayMicroseconds(3000);
ledcWriteTone(LEDC_CHANNEL, 0); ledcWriteTone(LEDC_CHANNEL, 0);
@@ -417,118 +618,161 @@ void atariKeyclick() {
ledcWriteTone(LEDC_CHANNEL, 0); ledcWriteTone(LEDC_CHANNEL, 0);
} }
void SendToN2K(uint8_t keycode) {
tN2kMsg N2kMsg;
tN2kBinaryStatus bankstatus;
N2kResetBinaryStatus(bankstatus);
N2kSetStatusBinaryOnStatus(bankstatus, N2kOnOff_On, keycode);
// TODO
// Needs filled N2K device list to map NAME to current address
// N2kMsg.Destination = deviceAddress;
//
SetN2kPGN127502(N2kMsg, 0, bankstatus);
NMEA2000.SendMsg(N2kMsg);
Serial.print("PGN127502 sent: Switch=");
Serial.print(keycode);
Serial.println(" Action=On");
}
void loop() { void loop() {
// Button pressed? (active low) ButtonEvent event;
uint8_t button = 0; if (xQueueReceive(keyQueue, &event, 0) == pdPASS) {
if (digitalRead(KEY_1) == LOW) { Serial.print("Button ");
Serial.println("Button detected: 1"); Serial.print(event.buttonId);
button = 1; Serial.print(" -> ");
if (rgb_r) { switch (event.pressType) {
rgb_r = false; case ButtonPressType::SHORT:
analogWrite(RGBLED_R, 255); Serial.println("SHORT");
} else { break;
rgb_r = true; case ButtonPressType::MEDIUM:
analogWrite(RGBLED_R, 255 - rgb_brightness); Serial.println("MEDIUM");
break;
case ButtonPressType::LONG:
Serial.println("LONG");
break;
default:
Serial.print("UNBEKANNT: ");
Serial.println(static_cast<uint8_t>(event.pressType));
break;
} }
} if (event.buttonId == BUTTON_DST) {
if (digitalRead(KEY_2) == LOW) { if (event.pressType == ButtonPressType::SHORT) {
Serial.println("Button detected: 2"); if (mode == 'N') {
button += 2; // switch destination only in normal mode
if (rgb_g) { if (destination == 'A') {
rgb_g = false; destination = 'B';
analogWrite(RGBLED_G, 255); analogWrite(LED_A, 0);
analogWrite(LED_B, led_brightness);
} else if (destination == 'B') {
destination = 'C';
analogWrite(LED_B, 0);
analogWrite(LED_C, led_brightness);
} else {
destination = 'A';
analogWrite(LED_C, 0);
analogWrite(LED_A, led_brightness);
}
Serial.print("New destination=");
Serial.println(destination);
}
} else if (event.pressType == ButtonPressType::LONG) {
shortBeep();
// switch config mode
if (mode == 'N') {
mode = 'C';
analogWrite(RGBLED_B, rgb_brightness); // blue status indicator
} else {
mode = 'N';
analogWrite(RGBLED_B, 0);
}
}
} else { } else {
rgb_g = true; // normal button
analogWrite(RGBLED_G, 255 - rgb_brightness); if (mode == 'N') {
} // TODO send key code to destination
} atariKeyclick();
if (digitalRead(KEY_3) == LOW) { SendToN2K(keycode[event.buttonId]);
Serial.println("Button detected: 3"); } else {
button += 4; // online config mode
if (rgb_b) { switch (event.buttonId) {
rgb_b = false; case BUTTON_1: // switch day/night mode
analogWrite(RGBLED_B, 255); if (ledmode == 'D') {
} else { Serial.println("Night mode enabled");
rgb_b = true; ledmode = 'N';
analogWrite(RGBLED_B, 255 - rgb_brightness); } else {
Serial.println("Day mode enabled");
ledmode = 'D';
}
break;
case BUTTON_2: // switch audio on/off
if (audiomode == 'E') {
Serial.println("Disabled audio");
audiomode = 'D';
} else {
Serial.println("Enabled audio");
audiomode = 'E';
}
break;
case BUTTON_3: // switch accesspoint on/off
if (apEnabled) {
Serial.println("Disable Accesspoint");
WiFi.softAPdisconnect(true);
apEnabled = false;
} else {
Serial.println("Enable Accesspoint");
WiFi.softAP(wifi_ssid, wifi_pass);
apEnabled = true;
}
break;
case BUTTON_4: // reserved
break;
case BUTTON_5: // reset
Serial.println("Device reset");
analogWrite(RGBLED_B, 0);
delay(500);
analogWrite(RGBLED_G, 255);
delay(500);
analogWrite(RGBLED_G, 0);
delay(500);
analogWrite(RGBLED_G, 255);
delay(500);
analogWrite(RGBLED_G, 0);
delay(500);
analogWrite(RGBLED_G, 255);
delay(500);
ESP.restart();
break;
case BUTTON_6: // deep sleep
Serial.println("Going into deep sleep");
Serial.flush();
analogWrite(RGBLED_B, 0);
delay(500);
analogWrite(RGBLED_B, 255);
delay(500);
analogWrite(RGBLED_B, 0);
delay(500);
analogWrite(RGBLED_B, 255);
delay(500);
analogWrite(RGBLED_B, 0);
delay(500);
analogWrite(RGBLED_B, 255);
delay(500);
rtc_gpio_pullup_en(KEY_DST);
rtc_gpio_pulldown_dis(KEY_DST);
esp_deep_sleep_start();
break;
}
}
} }
} }
if (digitalRead(KEY_4) == LOW) { NMEA2000.loop();
Serial.println("Button detected: 4"); NMEA2000.ParseMessages();
button += 8;
atariKeyclick();
digitalWrite(LED_USER, HIGH); // Turn LED on
delay(500); // Keep it on 0.5s
digitalWrite(LED_USER, LOW); // Turn LED off
}
if (digitalRead(KEY_5) == LOW) {
Serial.println("Button detected: 5");
button += 16;
ledcWriteTone(LEDC_CHANNEL, 3200);
delay(8);
ledcWriteTone(LEDC_CHANNEL, 0);
digitalWrite(LED_USER, HIGH); // Turn LED on
delay(500); // Keep it on 0.5s
digitalWrite(LED_USER, LOW); // Turn LED off
}
if (digitalRead(KEY_6) == LOW) {
Serial.println("Button detected: 6");
button += 32;
ledcWriteTone(LEDC_CHANNEL, 3200);
delay(8);
ledcWriteTone(LEDC_CHANNEL, 0);
digitalWrite(LED_USER, HIGH); // Turn LED on
delay(500); // Keep it on 0.5s
digitalWrite(LED_USER, LOW); // Turn LED off
}
if (digitalRead(KEY_DST) == LOW) {
Serial.println("Button detected: DST");
button += 64;
if (destination == 'A') {
destination = 'B';
analogWrite(LED_A, 0);
analogWrite(LED_B, led_brightness);
} else if (destination == 'B') {
destination = 'C';
analogWrite(LED_B, 0);
analogWrite(LED_C, led_brightness);
} else {
destination = 'A';
analogWrite(LED_C, 0);
analogWrite(LED_A, led_brightness);
}
Serial.print("Destination=");
Serial.println(destination);
/*
digitalWrite(LED_USER, HIGH); // Turn LED on
delay(500); // Keep it on 0.5s
digitalWrite(LED_USER, LOW); // Turn LED off
*/
}
if (button > 0) {
Serial.print(temp, 1);
Serial.print("\t");
Serial.println(hum, 1);
// Debounce delay to avoid multiple triggers
delay(200);
}
// NMEA2000.loop();
// NMEA2000.ParseMessages();
if (millis() - lastSensor >= 5000) { if (millis() - lastSensor >= 5000) {
lastSensor = millis(); lastSensor = millis();
@@ -544,4 +788,5 @@ void loop() {
Serial.printf("Loop counter: %lu\n", counter); Serial.printf("Loop counter: %lu\n", counter);
} }
delay(1); // 1ms für freertos
} }

View File

@@ -125,5 +125,65 @@
], ],
"description": "Temperature format: Kelvin, Celsius or Fahrenheit [K|C|F].", "description": "Temperature format: Kelvin, Celsius or Fahrenheit [K|C|F].",
"category": "Units" "category": "Units"
},
{
"name": "key1",
"label": "Key 1 code",
"type": "number",
"default": 1,
"min": 1,
"max": 28,
"description": "The keycode to send for key 1 (leftmost) [1 .. 28]",
"category": "Keys"
},
{
"name": "key2",
"label": "Key 2 code",
"type": "number",
"default": 2,
"min": 1,
"max": 28,
"description": "The keycode to send for key 2 (second from left) [1 .. 28]",
"category": "Keys"
},
{
"name": "key3",
"label": "Key 3 code",
"type": "number",
"default": 3,
"min": 1,
"max": 28,
"description": "The keycode to send for key 3 (third from left) [1 .. 28]",
"category": "Keys"
},
{
"name": "key4",
"label": "Key 4 code",
"type": "number",
"default": 4,
"min": 1,
"max": 28,
"description": "The keycode to send for key 4 (fourth from left) [1 .. 28]",
"category": "Keys"
},
{
"name": "key5",
"label": "Key 5 code",
"type": "number",
"default": 5,
"min": 1,
"max": 28,
"description": "The keycode to send for key 5 (fifth from left) [1 .. 28]",
"category": "Keys"
},
{
"name": "key6",
"label": "Key 6 code",
"type": "number",
"default": 6,
"min": 1,
"max": 28,
"description": "The keycode to send for key 6 (rightmost) [1 .. 28]",
"category": "Keys"
} }
] ]