Compare commits

..

1 Commits

Author SHA1 Message Date
Jonathan Bennett
495cd240e3 Add support for Hackaday Communicator function keys 2026-01-26 16:55:52 -06:00
13 changed files with 69 additions and 130 deletions

View File

@@ -1731,6 +1731,26 @@ int Screen::handleInputEvent(const InputEvent *event)
showFrame(FrameDirection::PREVIOUS);
} else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) {
showFrame(FrameDirection::NEXT);
} else if (event->inputEvent == INPUT_BROKER_FN_F1) {
this->ui->switchToFrame(0);
lastScreenTransition = millis();
setFastFramerate();
} else if (event->inputEvent == INPUT_BROKER_FN_F2) {
this->ui->switchToFrame(1);
lastScreenTransition = millis();
setFastFramerate();
} else if (event->inputEvent == INPUT_BROKER_FN_F3) {
this->ui->switchToFrame(2);
lastScreenTransition = millis();
setFastFramerate();
} else if (event->inputEvent == INPUT_BROKER_FN_F4) {
this->ui->switchToFrame(3);
lastScreenTransition = millis();
setFastFramerate();
} else if (event->inputEvent == INPUT_BROKER_FN_F5) {
this->ui->switchToFrame(4);
lastScreenTransition = millis();
setFastFramerate();
} else if (event->inputEvent == INPUT_BROKER_UP_LONG) {
// Long press up button for fast frame switching
showPrevFrame();

View File

@@ -20,20 +20,20 @@ constexpr uint8_t modifierLeftShift = 0b0001;
// Num chars per key, Modulus for rotating through characters
static uint8_t HackadayCommunicatorTapMod[_TCA8418_NUM_KEYS] = {
0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 0, 0, 0, 1, 2, 2, 2, 1, 2, 2, 0, 0, 0, 2, 1, 2, 2, 0, 1, 1, 0,
};
static unsigned char HackadayCommunicatorTapMap[_TCA8418_NUM_KEYS][2] = {{},
{},
{Key::FUNCTION_F1},
{'+'},
{'9'},
{'8'},
{'7'},
{'2'},
{'3'},
{'4'},
{'5'},
{Key::FUNCTION_F2},
{Key::FUNCTION_F3},
{Key::FUNCTION_F4},
{Key::FUNCTION_F5},
{Key::ESC},
{'q', 'Q'},
{'w', 'W'},
@@ -141,6 +141,7 @@ void HackadayCommunicatorKeyboard::pressed(uint8_t key)
if (state == Init || state == Busy) {
return;
}
LOG_DEBUG("Key pressed: %u", key);
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
modifierFlag = 0;

View File

@@ -27,6 +27,11 @@ enum input_broker_event {
INPUT_BROKER_SHUTDOWN = 0x9b,
INPUT_BROKER_GPS_TOGGLE = 0x9e,
INPUT_BROKER_SEND_PING = 0xaf,
INPUT_BROKER_FN_F1 = 0xf1,
INPUT_BROKER_FN_F2 = 0xf2,
INPUT_BROKER_FN_F3 = 0xf3,
INPUT_BROKER_FN_F4 = 0xf4,
INPUT_BROKER_FN_F5 = 0xf5,
INPUT_BROKER_MATRIXKEY = 0xFE,
INPUT_BROKER_ANYKEY = 0xff

View File

@@ -26,7 +26,12 @@ class TCA8418KeyboardBase
GPS_TOGGLE = 0x9E,
MUTE_TOGGLE = 0xAC,
SEND_PING = 0xAF,
BL_TOGGLE = 0xAB
BL_TOGGLE = 0xAB,
FUNCTION_F1 = 0xF1,
FUNCTION_F2 = 0xF2,
FUNCTION_F3 = 0xF3,
FUNCTION_F4 = 0xF4,
FUNCTION_F5 = 0xF5
};
typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len);

View File

@@ -321,6 +321,26 @@ int32_t KbI2cBase::runOnce()
e.inputEvent = INPUT_BROKER_ANYKEY;
e.kbchar = INPUT_BROKER_MSG_TAB;
break;
case TCA8418KeyboardBase::FUNCTION_F1:
e.inputEvent = INPUT_BROKER_FN_F1;
e.kbchar = 0x00;
break;
case TCA8418KeyboardBase::FUNCTION_F2:
e.inputEvent = INPUT_BROKER_FN_F2;
e.kbchar = 0x00;
break;
case TCA8418KeyboardBase::FUNCTION_F3:
e.inputEvent = INPUT_BROKER_FN_F3;
e.kbchar = 0x00;
break;
case TCA8418KeyboardBase::FUNCTION_F4:
e.inputEvent = INPUT_BROKER_FN_F4;
e.kbchar = 0x00;
break;
case TCA8418KeyboardBase::FUNCTION_F5:
e.inputEvent = INPUT_BROKER_FN_F5;
e.kbchar = 0x00;
break;
default:
if (nextEvent > 127) {
e.inputEvent = INPUT_BROKER_NONE;

View File

@@ -1401,39 +1401,7 @@ void loop()
if (inputBroker)
inputBroker->processInputEventQueue();
#endif
#if ARCH_PORTDUINO
if (portduino_config.lora_spi_dev == "ch341" && ch341Hal != nullptr) {
ch341Hal->checkError();
}
if (portduino_status.LoRa_in_error && rebootAtMsec == 0) {
LOG_ERROR("LoRa in error detected, attempting to recover");
if (portduino_config.lora_spi_dev == "ch341") {
if (ch341Hal != nullptr) {
delete ch341Hal;
ch341Hal = nullptr;
sleep(3);
}
try {
ch341Hal = new Ch341Hal(0, portduino_config.lora_usb_serial_num, portduino_config.lora_usb_vid,
portduino_config.lora_usb_pid);
} catch (std::exception &e) {
std::cerr << e.what() << std::endl;
std::cerr << "Could not initialize CH341 device!" << std::endl;
exit(EXIT_FAILURE);
}
}
if (initLoRa()) {
router->addInterface(rIf);
portduino_status.LoRa_in_error = false;
} else {
LOG_WARN("Reconfigure failed, rebooting");
if (screen) {
screen->showSimpleBanner("Rebooting...");
}
rebootAtMsec = millis() + 25;
}
}
#if HAS_TFT
#if ARCH_PORTDUINO && HAS_TFT
if (screen && portduino_config.displayPanel == x11 &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
auto dispdev = screen->getDisplayDevice();
@@ -1441,7 +1409,6 @@ void loop()
static_cast<TFTDisplay *>(dispdev)->sdlLoop();
}
#endif
#endif
#if HAS_SCREEN && ENABLE_MESSAGE_PERSISTENCE
messageStoreAutosaveTick();
#endif

View File

@@ -2247,10 +2247,7 @@ void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, co
// Currently portuino is mostly used for simulation. Make sure the user notices something really bad happened
#ifdef ARCH_PORTDUINO
LOG_ERROR("A critical failure occurred");
// TODO: Determine if other critical errors should also cause an immediate exit
if (code == meshtastic_CriticalErrorCode_FLASH_CORRUPTION_RECOVERABLE ||
code == meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE)
exit(2);
LOG_ERROR("A critical failure occurred, portduino is exiting");
exit(2);
#endif
}

View File

@@ -269,12 +269,8 @@ template <typename T> void SX126xInterface<T>::setStandby()
if (err != RADIOLIB_ERR_NONE)
LOG_DEBUG("SX126x standby %s%d", radioLibErr, err);
#ifdef ARCH_PORTDUINO
if (err != RADIOLIB_ERR_NONE)
portduino_status.LoRa_in_error = true;
#else
assert(err == RADIOLIB_ERR_NONE);
#endif
isReceiving = false; // If we were receiving, not any more
activeReceiveStart = 0;
disableInterrupt();
@@ -317,12 +313,7 @@ template <typename T> void SX126xInterface<T>::startReceive()
int err = lora.startReceiveDutyCycleAuto(preambleLength, 8, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS);
if (err != RADIOLIB_ERR_NONE)
LOG_ERROR("SX126X startReceiveDutyCycleAuto %s%d", radioLibErr, err);
#ifdef ARCH_PORTDUINO
if (err != RADIOLIB_ERR_NONE)
portduino_status.LoRa_in_error = true;
#else
assert(err == RADIOLIB_ERR_NONE);
#endif
RadioLibInterface::startReceive();
@@ -350,12 +341,7 @@ template <typename T> bool SX126xInterface<T>::isChannelActive()
return true;
if (result != RADIOLIB_CHANNEL_FREE)
LOG_ERROR("SX126X scanChannel %s%d", radioLibErr, result);
#ifdef ARCH_PORTDUINO
if (result == RADIOLIB_ERR_WRONG_MODEM)
portduino_status.LoRa_in_error = true;
#else
assert(result != RADIOLIB_ERR_WRONG_MODEM);
#endif
return false;
}

View File

@@ -29,7 +29,6 @@
#include "platform/portduino/USBHal.h"
portduino_config_struct portduino_config;
portduino_status_struct portduino_status;
std::ofstream traceFile;
std::ofstream JSONFile;
Ch341Hal *ch341Hal = nullptr;
@@ -401,10 +400,6 @@ void portduinoSetup()
if (found_hat) {
product_config =
cleanupNameForAutoconf("lora-hat-" + std::string(hat_vendor) + "-" + autoconf_product + ".yaml");
if (strncmp(hat_vendor, "RAK", 14) == 0 && strncmp(autoconf_product, "6421 Pi Hat", 14) == 0) {
std::cout << "autoconf: Setting hardwareModel to RAK6421" << std::endl;
portduino_status.hardwareModel = meshtastic_HardwareModel_RAK6421;
}
} else if (found_ch341) {
product_config = cleanupNameForAutoconf("lora-usb-" + std::string(autoconf_product) + ".yaml");
// look for more data after the null terminator
@@ -413,10 +408,6 @@ void portduinoSetup()
memcpy(portduino_config.device_id, autoconf_product + len + 1, 16);
if (!memfll(portduino_config.device_id, '\0', 16) && !memfll(portduino_config.device_id, 0xff, 16)) {
portduino_config.has_device_id = true;
if (strncmp(autoconf_product, "MESHSTICK 1262", 14) == 0) {
std::cout << "autoconf: Setting hardwareModel to Meshstick 1262" << std::endl;
portduino_status.hardwareModel = meshtastic_HardwareModel_MESHSTICK_1262;
}
}
}
}

View File

@@ -1,21 +1,14 @@
#pragma once
#include <fstream>
#include <map>
#include <unistd.h>
#include <unordered_map>
#include <vector>
#include "LR11x0Interface.h"
#include "Module.h"
#include "mesh/generated/meshtastic/mesh.pb.h"
#include "yaml-cpp/yaml.h"
extern struct portduino_status_struct {
bool LoRa_in_error = false;
_meshtastic_HardwareModel hardwareModel = meshtastic_HardwareModel_PORTDUINO;
} portduino_status;
#include "platform/portduino/USBHal.h"
#include "yaml-cpp/yaml.h"
// Product strings for auto-configuration
// {"PRODUCT_STRING", "CONFIG.YAML"}

View File

@@ -9,8 +9,6 @@
#include <libpinedio-usb.h>
#include <unistd.h>
extern uint32_t rebootAtMsec;
// include the library for Raspberry GPIO pins
#define PI_RISING (PINEDIO_INT_MODE_RISING)
@@ -47,7 +45,7 @@ class Ch341Hal : public RadioLibHal
int32_t ret = pinedio_init(&pinedio, NULL);
if (ret != 0) {
std::string s = "Could not open SPI: ";
throw std::runtime_error(s + std::to_string(ret));
throw(s + std::to_string(ret));
}
pinedio_set_option(&pinedio, PINEDIO_OPTION_AUTO_CS, 0);
@@ -76,55 +74,30 @@ class Ch341Hal : public RadioLibHal
// RADIOLIB_NC as an alias for non-connected pins
void pinMode(uint32_t pin, uint32_t mode) override
{
if (checkError()) {
return;
}
if (pin == RADIOLIB_NC) {
return;
}
auto res = pinedio_set_pin_mode(&pinedio, pin, mode);
if (res < 0 && rebootAtMsec == 0) {
LOG_ERROR("USBHal pinMode: Could not set pin %d mode to %d: %d", pin, mode, res);
}
pinedio_set_pin_mode(&pinedio, pin, mode);
}
void digitalWrite(uint32_t pin, uint32_t value) override
{
if (checkError()) {
return;
}
if (pin == RADIOLIB_NC) {
return;
}
auto res = pinedio_digital_write(&pinedio, pin, value);
if (res < 0 && rebootAtMsec == 0) {
LOG_ERROR("USBHal digitalWrite: Could not write pin %d: %d", pin, res);
portduino_status.LoRa_in_error = true;
}
pinedio_digital_write(&pinedio, pin, value);
}
uint32_t digitalRead(uint32_t pin) override
{
if (checkError()) {
return 0;
}
if (pin == RADIOLIB_NC) {
return 0;
}
auto res = pinedio_digital_read(&pinedio, pin);
if (res < 0 && rebootAtMsec == 0) {
LOG_ERROR("USBHal digitalRead: Could not read pin %d: %d", pin, res);
portduino_status.LoRa_in_error = true;
return 0;
}
return res;
return pinedio_digital_read(&pinedio, pin);
}
void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override
{
if (checkError()) {
return;
}
if (interruptNum == RADIOLIB_NC) {
return;
}
@@ -134,9 +107,6 @@ class Ch341Hal : public RadioLibHal
void detachInterrupt(uint32_t interruptNum) override
{
if (checkError()) {
return;
}
if (interruptNum == RADIOLIB_NC) {
return;
}
@@ -182,9 +152,6 @@ class Ch341Hal : public RadioLibHal
void spiTransfer(uint8_t *out, size_t len, uint8_t *in)
{
if (checkError()) {
return;
}
int32_t ret = pinedio_transceive(&this->pinedio, out, in, len);
if (ret < 0) {
std::cerr << "Could not perform SPI transfer: " << ret << std::endl;
@@ -193,22 +160,9 @@ class Ch341Hal : public RadioLibHal
void spiEndTransaction() {}
void spiEnd() {}
bool checkError()
{
if (pinedio.in_error) {
if (!has_warned)
LOG_ERROR("USBHal: libch341 in_error detected");
portduino_status.LoRa_in_error = true;
has_warned = true;
return true;
}
has_warned = false;
return false;
}
private:
pinedio_inst pinedio = {0};
bool has_warned = false;
};
#endif

View File

@@ -6,7 +6,7 @@
// set HW_VENDOR
//
#define HW_VENDOR portduino_status.hardwareModel
#define HW_VENDOR meshtastic_HardwareModel_PORTDUINO
#ifndef HAS_BUTTON
#define HAS_BUTTON 1

View File

@@ -29,7 +29,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.7
# renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main
https://github.com/pine64/libch341-spi-userspace/archive/23c42319a69cffcb65868e3c72e6bed83974a393.zip
https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip
# renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library
adafruit/Adafruit seesaw Library@1.7.9
# renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main