firmware-base/src/components/Joystick.cpp
2025-05-24 05:45:24 +02:00

263 lines
9.5 KiB
C++

#include "Joystick.h"
#include <Arduino.h>
#include <string.h>
// Define F() macro wrapper for non-AVR architectures if needed
#if defined(ARDUINO_ARCH_AVR)
#define JOY_L(FSTR) F(FSTR)
#else
#define JOY_L(FSTR) (FSTR)
#endif
// Constructor
Joystick::Joystick(Component *owner,
ushort _pinUp,
ushort _pinDown,
ushort _pinLeft,
ushort _pinRight,
ushort _modbusAddress)
: Component("Joystick", 100, Component::COMPONENT_DEFAULT, owner),
pinUp (_pinUp),
pinDown (_pinDown),
pinLeft (_pinLeft),
pinRight (_pinRight),
modbusAddr (_modbusAddress),
currentPosition (E_POSITION::CENTER),
lastPosition (E_POSITION::CENTER),
mode (E_MODE::LOCAL),
overridePosition (E_POSITION::CENTER),
positionStartMs (0),
lastReadMs (0),
proposedPosition (E_POSITION::CENTER),
confirmCount (0),
useDebouncing (true),
modbusBlockCount (0)
{
setNetCapability(OBJECT_NET_CAPS::E_NCAPS_MODBUS);
memset(modbusBlocks, 0, sizeof(modbusBlocks));
const char *joyGroup = "Joystick - 4P";
// Position register
modbusBlocks[0] = {
static_cast<ushort>(modbusAddr + static_cast<ushort>(E_REGISTER::POSITION)),
1, E_FN_CODE::FN_READ_HOLD_REGISTER, MB_ACCESS_READ_ONLY,
static_cast<ushort>(id), static_cast<ushort>(E_REGISTER::POSITION),
"Position", joyGroup };
// Mode register (read/write)
modbusBlocks[1] = {
static_cast<ushort>(modbusAddr + static_cast<ushort>(E_REGISTER::MODE)),
1, E_FN_CODE::FN_READ_HOLD_REGISTER, MB_ACCESS_READ_WRITE,
static_cast<ushort>(id), static_cast<ushort>(E_REGISTER::MODE),
"Mode", joyGroup };
// Override register (read/write)
modbusBlocks[2] = {
static_cast<ushort>(modbusAddr + static_cast<ushort>(E_REGISTER::OVERRIDE)),
1, E_FN_CODE::FN_READ_HOLD_REGISTER, MB_ACCESS_READ_WRITE,
static_cast<ushort>(id), static_cast<ushort>(E_REGISTER::OVERRIDE),
"Override", joyGroup };
modbusBlockCount = 3;
modbusView.data = modbusBlocks;
modbusView.count = modbusBlockCount;
}
// Initialize component
short Joystick::setup() {
Component::setup();
// Configure pins as inputs with pull-up resistors
pinMode(pinUp, INPUT_PULLUP);
pinMode(pinDown, INPUT_PULLUP);
pinMode(pinLeft, INPUT_PULLUP);
pinMode(pinRight, INPUT_PULLUP);
return reset();
}
// Reset component state
short Joystick::reset() {
// Reset state
currentPosition = readPinsPosition();
lastPosition = currentPosition;
proposedPosition = currentPosition;
confirmCount = DEBOUNCE_COUNT;
positionStartMs = now;
return E_OK;
}
// Read joystick position from GPIO pins
Joystick::E_POSITION Joystick::readPinsPosition() {
// Note: Pull-up means LOW when pressed/active
bool up = !digitalRead(pinUp);
bool down = !digitalRead(pinDown);
bool left = !digitalRead(pinLeft);
bool right = !digitalRead(pinRight);
// Detect invalid combinations (opposing directions pressed simultaneously)
if ((up && down) || (left && right)) {
Log.verboseln(JOY_L("Joystick ID:%d: Invalid input detected U:%d D:%d L:%d R:%d"),
id, up, down, left, right);
return E_POSITION::UNKNOWN;
}
// Determine joystick position based on pin states
// Simple 4-way logic
if (up) return E_POSITION::UP;
if (down) return E_POSITION::DOWN;
if (left) return E_POSITION::LEFT;
if (right) return E_POSITION::RIGHT;
// No buttons pressed - CENTER position
return E_POSITION::CENTER;
}
// Main loop
short Joystick::loop() {
Component::loop();
// Check if it's time to read pins
if (now - lastReadMs < READ_INTERVAL_MS) return E_OK;
lastReadMs = now;
// Only process input changes if in LOCAL mode
if (mode == E_MODE::LOCAL) {
E_POSITION candidatePosition = readPinsPosition();
// Apply debouncing if enabled
if (useDebouncing) {
if (candidatePosition == proposedPosition) {
if (confirmCount < DEBOUNCE_COUNT) ++confirmCount;
} else {
proposedPosition = candidatePosition;
confirmCount = 1;
}
// Update position only when debouncing confirms it
if (confirmCount >= DEBOUNCE_COUNT && proposedPosition != currentPosition) {
lastPosition = currentPosition;
currentPosition = proposedPosition;
positionStartMs = now;
Log.verboseln(JOY_L("Joystick ID:%d: Position %d → %d (debounced)"),
id, static_cast<int>(lastPosition), static_cast<int>(currentPosition));
notifyStateChange();
}
} else {
// Direct position update without debouncing
if (candidatePosition != currentPosition) {
lastPosition = currentPosition;
currentPosition = candidatePosition;
positionStartMs = now;
Log.verboseln(JOY_L("Joystick ID:%d: Position %d → %d (direct)"),
id, static_cast<int>(lastPosition), static_cast<int>(currentPosition));
notifyStateChange();
}
}
}
return E_OK;
}
// Info display
short Joystick::info(short, short) {
Log.infoln(JOY_L("Joystick::info - ID:%d"), id);
Log.infoln(JOY_L(" Pins - Up:%d Down:%d Left:%d Right:%d"),
pinUp, pinDown, pinLeft, pinRight);
Log.infoln(JOY_L(" Mode: %s, Debouncing: %s"),
mode == E_MODE::LOCAL ? "LOCAL" : "REMOTE",
useDebouncing ? "ENABLED" : "DISABLED");
Log.infoln(JOY_L(" Current Position: %d, Last Position: %d"),
static_cast<int>(currentPosition), static_cast<int>(lastPosition));
Log.infoln(JOY_L(" Position Holding Time: %lu ms"), getHoldingTime());
if (mode == E_MODE::REMOTE) {
Log.infoln(JOY_L(" Override Position: %d"), static_cast<int>(overridePosition));
}
return E_OK;
}
// Notify of state changes
void Joystick::notifyStateChange() {
Component::notifyStateChange();
}
// Modbus write implementation
short Joystick::mb_tcp_write(MB_Registers *reg, short networkValue) {
ushort addr = reg->startAddress;
// Handle MODE register
if (addr == modbusAddr + static_cast<ushort>(E_REGISTER::MODE)) {
if (networkValue < 0 || networkValue > 1) {
Log.warningln(JOY_L("Joystick ID:%d: Invalid mode value: %d"), id, networkValue);
return MODBUS_ERROR_ILLEGAL_DATA_VALUE;
}
setMode(static_cast<E_MODE>(networkValue));
Log.verboseln(JOY_L("Joystick ID:%d: Mode set to %s"),
id, networkValue == 0 ? "LOCAL" : "REMOTE");
return E_OK;
}
// Handle OVERRIDE register
if (addr == modbusAddr + static_cast<ushort>(E_REGISTER::OVERRIDE)) {
if (networkValue < 0 || networkValue > static_cast<int>(E_POSITION::UNKNOWN)) {
Log.warningln(JOY_L("Joystick ID:%d: Invalid override position: %d"), id, networkValue);
return MODBUS_ERROR_ILLEGAL_DATA_VALUE;
}
setOverridePosition(static_cast<E_POSITION>(networkValue));
Log.verboseln(JOY_L("Joystick ID:%d: Override position set to %d"), id, networkValue);
return E_OK;
}
// Handle POSITION register (read-only)
if (addr == modbusAddr + static_cast<ushort>(E_REGISTER::POSITION)) {
Log.warningln(JOY_L("Joystick ID:%d: Write to read-only address %d"), id, addr);
return MODBUS_ERROR_ILLEGAL_FUNCTION;
}
Log.warningln(JOY_L("Joystick ID:%d: Write to unknown address %d"), id, addr);
return MODBUS_ERROR_ILLEGAL_DATA_ADDRESS;
}
// Modbus read implementation
short Joystick::mb_tcp_read(MB_Registers *reg) {
ushort addr = reg->startAddress;
// Current position (accounting for mode)
if (addr == modbusAddr + static_cast<ushort>(E_REGISTER::POSITION)) {
return static_cast<short>(getValue());
}
// Mode (LOCAL/REMOTE)
if (addr == modbusAddr + static_cast<ushort>(E_REGISTER::MODE)) {
return static_cast<short>(mode);
}
// Override position value
if (addr == modbusAddr + static_cast<ushort>(E_REGISTER::OVERRIDE)) {
return static_cast<short>(overridePosition);
}
Log.warningln(JOY_L("Joystick ID:%d: Read from unknown address %d"), id, addr);
return 0;
}
// Register with ModbusTCP manager
void Joystick::mb_tcp_register(ModbusTCP *mgr) const {
auto *blocks = mb_tcp_blocks();
if (!blocks || !blocks->data) return;
Component *thiz = const_cast<Joystick*>(this);
for (ushort i = 0; i < blocks->count; ++i) mgr->registerModbus(thiz, blocks->data[i]);
}
// Return Modbus blocks
ModbusBlockView *Joystick::mb_tcp_blocks() const {
return const_cast<ModbusBlockView*>(&modbusView);
}
// Register with serial bridge
short Joystick::serial_register(Bridge *bridge) {
bridge->registerMemberFunction(id, this, C_STR("info"), (ComponentFnPtr)&Joystick::info);
return E_OK;
}