From 291d0d703a9cd80fd13ccccd3a7980d9b0c644f9 Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Tue, 8 Sep 2026 22:50:45 +0200 Subject: [PATCH 01/10] Report errors to Home Assistant, including desk initialization error, any Console/LIN UART errors and node errors. --- README.md | 28 ++--- include/avr/ConsoleHandler.h | 4 +- include/avr/LegHandler.h | 16 +-- include/esp/ConsoleHandler.h | 6 +- include/esp/DeskService.h | 33 +++++- src/avr/ConsoleHandler.cpp | 9 +- src/avr/ControllerService.cpp | 2 +- src/avr/LegHandler.cpp | 13 ++- src/esp/ConsoleHandler.cpp | 60 ++++++---- src/esp/DeskService.cpp | 192 +++++++++++++++++++++++++------ src/esp/HomeAssistantHandler.cpp | 10 ++ 11 files changed, 281 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 1d5c56a..e7fc0df 100644 --- a/README.md +++ b/README.md @@ -196,19 +196,19 @@ Home Assistant with MQTT is recommended for the best experience, but the desk al #### Diagnostics -| Name | Description | Requirement | -| -------------- | ---------------------------------------- | ----------- | -| Button down | Simulate a physical down-button press | `PIN_TPDN` | -| Button up | Simulate a physical up-button press | `PIN_TPUP` | -| Calibrate | Recalibrate the leg encoder sensors | | -| Firmware AVR | AVR firmware version | | -| Firmware ESP32 | ESP32 firmware version | | -| Offset | Current leg offset | | -| Position | Average encoder position | | -| Power supply | Input voltage of the desk’s power supply | `PIN_ADC` | -| Serial RX | Last UART message received | | -| Serial TX | Last UART message sent | | -| Temperature | Internal temperature of the ESP32 | | -| Wi-Fi signal | ESP32 Wi-Fi RSSI | | +| Name | Description | Requirement | +| ------------ | ---------------------------------------- | ----------- | +| Button down | Simulate a physical down-button press | `PIN_TPDN` | +| Button up | Simulate a physical up-button press | `PIN_TPUP` | +| Calibrate | Recalibrate the leg encoder sensors | | +| Errors | Currently detected communication errors | | +| Firmware | ESP32 firmware version | | +| Offset | Current leg offset | | +| Position | Average encoder position | | +| Power supply | Input voltage of the desk’s power supply | `PIN_ADC` | +| Serial RX | Last UART message received | | +| Serial TX | Last UART message sent | | +| Temperature | Internal temperature of the ESP32 | | +| Wi-Fi signal | ESP32 Wi-Fi RSSI | | To avoid cluttering the Home Assistant interface, only a handful of entities are enabled by default. diff --git a/include/avr/ConsoleHandler.h b/include/avr/ConsoleHandler.h index 939e014..1011e3c 100644 --- a/include/avr/ConsoleHandler.h +++ b/include/avr/ConsoleHandler.h @@ -29,9 +29,11 @@ class ConsoleHandler { BUTTON_DOWN = 1U, BUTTON_UP, + CONSOLE, ENCODER8, ENCODER9, - INITIALIZE, + INITIALIZATION, + LIN, NODE8, NODE9, POSITION, diff --git a/include/avr/LegHandler.h b/include/avr/LegHandler.h index 019e95c..8b53153 100644 --- a/include/avr/LegHandler.h +++ b/include/avr/LegHandler.h @@ -28,7 +28,7 @@ class LegHandler [[nodiscard]] unsigned char calcParity(unsigned char identifier); - [[nodiscard]] int readWithTimeout(unsigned int &remainingTime); + [[nodiscard]] int read(unsigned int &remainingTime); /** * Calculates the complemented checksum for a sequence of bytes. @@ -90,12 +90,12 @@ class LegHandler template void send(unsigned char identifier, const unsigned char (&data)[N]) { static_assert(N <= 8U); - const unsigned char addressByte{static_cast((identifier & 0x3FU) | calcParity(identifier))}; + const unsigned char idByte{static_cast((identifier & 0x3FU) | calcParity(identifier))}; serialBreak(); Serial.write(linSyncByte); - Serial.write(addressByte); + Serial.write(idByte); Serial.write(data, N); - Serial.write(calcChecksum(data, identifier == linDiagnosticRequestId ? 0U : addressByte)); + Serial.write(calcChecksum(data, identifier == linDiagnosticRequestId ? 0U : idByte)); Serial.flush(); } @@ -119,7 +119,7 @@ class LegHandler unsigned int remainingTime{static_cast(LinFrame::frameBits * 1'000'000UL / baudRate)}; do // NOLINT(cppcoreguidelines-avoid-do-while) { - receivedByte = readWithTimeout(remainingTime); + receivedByte = read(remainingTime); } while (receivedByte != -1 && receivedByte != static_cast(linSyncByte)); if (receivedByte == -1) { @@ -127,7 +127,7 @@ class LegHandler } do // NOLINT(cppcoreguidelines-avoid-do-while) { - receivedByte = readWithTimeout(remainingTime); + receivedByte = read(remainingTime); } while (receivedByte != -1 && receivedByte != idByte); if (receivedByte == -1) { @@ -135,14 +135,14 @@ class LegHandler } for (unsigned char &dataByte : data) { - receivedByte = readWithTimeout(remainingTime); + receivedByte = read(remainingTime); if (receivedByte == -1) { return false; } dataByte = static_cast(receivedByte); } - receivedByte = readWithTimeout(remainingTime); + receivedByte = read(remainingTime); return receivedByte != -1 && calcChecksum(data, identifier == linDiagnosticResponseId ? 0U : idByte) == receivedByte; } diff --git a/include/esp/ConsoleHandler.h b/include/esp/ConsoleHandler.h index 6fac6e7..9073d12 100644 --- a/include/esp/ConsoleHandler.h +++ b/include/esp/ConsoleHandler.h @@ -22,9 +22,11 @@ class ConsoleHandler { BUTTON_DOWN = 1U, BUTTON_UP, + CONSOLE, ENCODER8, ENCODER9, - INITIALIZE, + INITIALIZATION, + LIN, NODE8, NODE9, POSITION, @@ -72,8 +74,6 @@ class ConsoleHandler */ State stateRx{}; - static inline hardwareSerial_error_t lastError{hardwareSerial_error_t::UART_NO_ERROR}; - /** * Parses a received console payload. */ diff --git a/include/esp/DeskService.h b/include/esp/DeskService.h index 98166d4..7e18a07 100644 --- a/include/esp/DeskService.h +++ b/include/esp/DeskService.h @@ -15,14 +15,19 @@ class DeskService { private: + bool avr{true}; bool buttonDown{false}; bool buttonUp{false}; bool enable{true}; + bool node8{true}; + bool node9{true}; bool pending{true}; bool process{true}; bool reset{false}; bool saved{true}; + uint8_t errorLin{0U}; + uint8_t errorTx{0U}; uint8_t state8{0U}; uint8_t state9{0U}; @@ -36,6 +41,8 @@ class DeskService size_t lengthRx{0U}; size_t lengthTx{0U}; + hardwareSerial_error_t errorRx{hardwareSerial_error_t::UART_NO_ERROR}; + std::string versionLatest{}; std::array payloadRx{}; @@ -68,6 +75,8 @@ class DeskService void statusNode(); + void toErrorArray(JsonArray &list); + [[nodiscard]] float decode(float encoder); [[nodiscard]] uint16_t encode(float height); @@ -97,9 +106,25 @@ class DeskService void setButtonUp(bool state); - void setEncoder8(uint16_t position); + void setErrorAvr(); + + void setErrorLin(uint8_t flags); + + void setErrorRx(hardwareSerial_error_t flags); + + void setErrorTx(uint8_t flags); + + void setNode8(); - void setEncoder9(uint16_t position); + void setNode8(uint8_t state); + + void setNode8(uint16_t position); + + void setNode9(); + + void setNode9(uint8_t state); + + void setNode9(uint16_t position); void setPresetHigh(uint16_t encoder); @@ -107,10 +132,6 @@ class DeskService void setRx(std::span payload); - void setState8(uint8_t state); - - void setState9(uint8_t state); - void setTx(std::span payload); void statusRed(); diff --git a/src/avr/ConsoleHandler.cpp b/src/avr/ConsoleHandler.cpp index acf872a..8f0e12d 100644 --- a/src/avr/ConsoleHandler.cpp +++ b/src/avr/ConsoleHandler.cpp @@ -12,9 +12,14 @@ */ void ConsoleHandler::handle() { - const int byte{Serial1.read()}; - if (byte != -1) + if (Serial1.available() != 0) { + const unsigned char errors{UCSR1A}; + if ((errors & ((0b1U << DOR1) | (0b1U << FE1))) != 0U) + { + send(State::CONSOLE, errors); + } + const int byte{Serial1.read()}; if (lengthRx == 0U) { lengthRx = static_cast(static_cast(byte) >> 4U); diff --git a/src/avr/ControllerService.cpp b/src/avr/ControllerService.cpp index 6f39e8f..4edc789 100644 --- a/src/avr/ControllerService.cpp +++ b/src/avr/ControllerService.cpp @@ -28,7 +28,7 @@ void ControllerService::begin() console.send(ConsoleHandler::State::PRESET_LOW, presetLow); if (!lin.begin()) { - console.send(ConsoleHandler::State::INITIALIZE); + console.send(ConsoleHandler::State::INITIALIZATION); tone(0b1U << 8U); return; } diff --git a/src/avr/LegHandler.cpp b/src/avr/LegHandler.cpp index 9111d4f..6d38d44 100644 --- a/src/avr/LegHandler.cpp +++ b/src/avr/LegHandler.cpp @@ -2,6 +2,7 @@ #include "avr/LegHandler.h" +#include "avr/ConsoleHandler.h" #include "avr/constants.h" #include @@ -118,7 +119,7 @@ unsigned char LegHandler::calcParity(unsigned char identifier) * @param remainingTime Maximum wait time in microseconds; reduced by the time spent waiting. * @return int The received byte, or -1 if no byte is available before the timeout. */ -int LegHandler::readWithTimeout(unsigned int &remainingTime) +int LegHandler::read(unsigned int &remainingTime) { constexpr unsigned int interval{static_cast(1'000'000UL / baudRate)}; while (remainingTime != 0U && Serial.available() == 0) @@ -127,6 +128,16 @@ int LegHandler::readWithTimeout(unsigned int &remainingTime) delayMicroseconds(delayTime); remainingTime -= delayTime; } + if (Serial.available() == 0) + { + return -1; + } + const unsigned char errors{UCSR0A}; + if ((errors & ((0b1U << DOR0) | (0b1U << FE0))) != 0U) + { + Serial1.write((1U << 4U) | static_cast(ConsoleHandler::State::LIN)); + Serial1.write(errors); + } return Serial.read(); } diff --git a/src/esp/ConsoleHandler.cpp b/src/esp/ConsoleHandler.cpp index cdcbad8..92a2696 100644 --- a/src/esp/ConsoleHandler.cpp +++ b/src/esp/ConsoleHandler.cpp @@ -40,16 +40,6 @@ void ConsoleHandler::handle() lengthRx = 0U; } } - else if (lastError != hardwareSerial_error_t::UART_NO_ERROR) - { - const uint8_t _error{static_cast(lastError)}; - lastError = hardwareSerial_error_t::UART_NO_ERROR; - ESP_LOGW("hardwareSerial_error_t", "%d", _error); - desk.statusRed(); - JsonDocument doc{}; - doc["hardwareSerial_error_t"].set(_error); - desk.transmit(doc); - } else { forward(); @@ -98,27 +88,47 @@ void ConsoleHandler::parse() const { desk.setButtonUp(static_cast(bufferRx.at(1U))); } + else if (stateRx == State::CONSOLE && lengthRx == 1U) + { + desk.setErrorTx(bufferRx.at(1U)); + } else if (stateRx == State::ENCODER8 && lengthRx == 2U) { - desk.setEncoder8(static_cast(bufferRx.at(1U)) | - static_cast(static_cast(bufferRx.at(2U)) << 8U)); + desk.setNode8(static_cast(static_cast(bufferRx.at(1U)) | + static_cast(static_cast(bufferRx.at(2U)) << 8U))); } else if (stateRx == State::ENCODER9 && lengthRx == 2U) { - desk.setEncoder9(static_cast(bufferRx.at(1U)) | - static_cast(static_cast(bufferRx.at(2U)) << 8U)); + desk.setNode9(static_cast(static_cast(bufferRx.at(1U)) | + static_cast(static_cast(bufferRx.at(2U)) << 8U))); + } + else if (stateRx == State::INITIALIZATION) + { + desk.setErrorAvr(); + } + else if (stateRx == State::LIN && lengthRx == 1U) + { + desk.setErrorLin(bufferRx.at(1U)); + } + else if (stateRx == State::NODE8 && lengthRx == 0U) + { + desk.setNode8(); } else if (stateRx == State::NODE8 && lengthRx == 3U) { - desk.setEncoder8(static_cast(bufferRx.at(1U)) | - static_cast(static_cast(bufferRx.at(2U)) << 8U)); - desk.setState8(bufferRx.at(3U)); + desk.setNode8(static_cast(static_cast(bufferRx.at(1U)) | + static_cast(static_cast(bufferRx.at(2U)) << 8U))); + desk.setNode8(bufferRx.at(3U)); + } + else if (stateRx == State::NODE9 && lengthRx == 0U) + { + desk.setNode9(); } else if (stateRx == State::NODE9 && lengthRx == 3U) { - desk.setEncoder9(static_cast(bufferRx.at(1U)) | - static_cast(static_cast(bufferRx.at(2U)) << 8U)); - desk.setState9(bufferRx.at(3U)); + desk.setNode9(static_cast(static_cast(bufferRx.at(1U)) | + static_cast(static_cast(bufferRx.at(2U)) << 8U))); + desk.setNode9(bufferRx.at(3U)); } else if (stateRx == State::PRESET_HIGH && lengthRx == 2U) { @@ -132,11 +142,11 @@ void ConsoleHandler::parse() const } else if (stateRx == State::STATE8 && lengthRx == 1U) { - desk.setState8(bufferRx.at(1U)); + desk.setNode8(bufferRx.at(1U)); } else if (stateRx == State::STATE9 && lengthRx == 1U) { - desk.setState9(bufferRx.at(1U)); + desk.setNode9(bufferRx.at(1U)); } else { @@ -191,6 +201,10 @@ void ConsoleHandler::write(std::span payload) * * @param error Hardware serial error to store. */ -void ConsoleHandler::onReceiveError(hardwareSerial_error_t error) { lastError = error; } +void ConsoleHandler::onReceiveError(hardwareSerial_error_t error) +{ + ESP_LOGW("hardwareSerial_error_t", "%u", static_cast(error)); + desk.setErrorRx(error); +} #endif // ARDUINO_ARCH_ESP32 diff --git a/src/esp/DeskService.cpp b/src/esp/DeskService.cpp index b457743..2c3a406 100644 --- a/src/esp/DeskService.cpp +++ b/src/esp/DeskService.cpp @@ -269,6 +269,8 @@ void DeskService::transmit(JsonDocument &doc) doc["desk"].set(decode(static_cast(encoder8 + encoder9) / 2.0F)); doc["encoders"][0U].set(encoder8); doc["encoders"][1U].set(encoder9); + JsonArray errors{doc["errors"].to()}; + toErrorArray(errors); const float leg8{decode(static_cast(encoder8))}; const float leg9{decode(static_cast(encoder9))}; doc["legs"][0U].set(leg8); @@ -369,6 +371,69 @@ void DeskService::setDriveUp(bool state) #endif // PIN_TPUP } +void DeskService::setErrorAvr() +{ + avr = false; + pending = true; + statusRed(); +} + +void DeskService::setErrorLin(uint8_t flags) +{ + if (flags != errorLin) + { + errorLin = flags; + pending = true; + } + statusRed(); +} + +void DeskService::setErrorRx(hardwareSerial_error_t flags) +{ + if (flags != errorRx) + { + errorRx = flags; + pending = true; + } + statusRed(); +} + +void DeskService::setErrorTx(uint8_t flags) +{ + if (flags != errorTx) + { + errorTx = flags; + pending = true; + } + statusRed(); +} + +void DeskService::setNode8() +{ + if (node8) + { + node8 = false; + pending = true; + } + statusRed(); +} + +/** + * @brief Updates the state of drive 8. + * + * @param state New drive state. + */ +void DeskService::setNode8(uint8_t state) +{ + if (state != state8) + { + state8 = state; + pending = true; + statusNode(); + } + node8 = true; +} + /** * @brief Updates the encoder 8 position and marks the desk state for saving and publication. * @@ -376,7 +441,7 @@ void DeskService::setDriveUp(bool state) * * @param position New encoder 8 position. */ -void DeskService::setEncoder8(uint16_t position) +void DeskService::setNode8(uint16_t position) { if (position != encoder8) { @@ -385,6 +450,33 @@ void DeskService::setEncoder8(uint16_t position) pending = true; statusNode(); } + node8 = true; +} + +void DeskService::setNode9() +{ + if (node9) + { + node9 = false; + pending = true; + } + statusRed(); +} + +/** + * @brief Updates the motor state for encoder 9. + * + * @param state New motor state. + */ +void DeskService::setNode9(uint8_t state) +{ + if (state != state9) + { + state9 = state; + pending = true; + statusNode(); + } + node9 = true; } /** @@ -394,7 +486,7 @@ void DeskService::setEncoder8(uint16_t position) * * @param position New secondary encoder value. */ -void DeskService::setEncoder9(uint16_t position) +void DeskService::setNode9(uint16_t position) { if (position != encoder9) { @@ -403,6 +495,7 @@ void DeskService::setEncoder9(uint16_t position) pending = true; statusNode(); } + node9 = true; } /** @@ -479,36 +572,6 @@ void DeskService::setRx(std::span payload) } } -/** - * @brief Updates the state of drive 8. - * - * @param state New drive state. - */ -void DeskService::setState8(uint8_t state) -{ - if (state != state8) - { - state8 = state; - pending = true; - statusNode(); - } -} - -/** - * @brief Updates the motor state for encoder 9. - * - * @param state New motor state. - */ -void DeskService::setState9(uint8_t state) -{ - if (state != state9) - { - state9 = state; - pending = true; - statusNode(); - } -} - /** * @brief Updates the stored transmitted serial payload. * @@ -577,6 +640,54 @@ std::string DeskService::toHex(std::span payload) return hex; } +void DeskService::toErrorArray(JsonArray &list) +{ + if (!avr) + { + list.add("desk initialization error"); + } + if (!node8) + { + list.add("leg node 8 error"); + } + if (!node9) + { + list.add("leg node 9 error"); + } + if ((errorRx & (1U << static_cast(hardwareSerial_error_t::UART_BREAK_ERROR))) != 0U) + { + list.add("UART break"); + } + if ((errorRx & (1U << static_cast(hardwareSerial_error_t::UART_BUFFER_FULL_ERROR))) != 0U) + { + list.add("UART buffer full"); + } + if ((errorRx & (1U << static_cast(hardwareSerial_error_t::UART_FIFO_OVF_ERROR))) != 0U) + { + list.add("UART FIFO overflow"); + } + if ((errorRx & (1U << static_cast(hardwareSerial_error_t::UART_FRAME_ERROR))) != 0U) + { + list.add("UART frame error"); + } + if ((errorLin & (0b1U << 3U)) != 0U) + { + list.add("USART0 data overrun"); + } + if ((errorLin & (0b1U << 4U)) != 0U) + { + list.add("USART0 frame error"); + } + if ((errorTx & (0b1U << 3U)) != 0U) + { + list.add("USART1 data overrun"); + } + if ((errorTx & (0b1U << 4U)) != 0U) + { + list.add("USART1 frame error"); + } +} + /** * @brief Retrieves the latest firmware release version from GitHub. * @@ -673,7 +784,22 @@ void DeskService::onInterruptDown() void DeskService::onInterruptReset() { desk.reset = digitalRead(PIN_RST) == LOW; - desk.reset ? desk.status.setNone(true) : desk.status.setWhite(); + if (desk.reset) + { + desk.avr = true; + desk.node8 = true; + desk.node9 = true; + desk.errorLin = 0U; + desk.errorRx = hardwareSerial_error_t::UART_NO_ERROR; + desk.errorTx = 0U; + desk.lengthRx = 0U; + desk.lengthTx = 0U; + desk.status.setNone(true); + } + else + { + desk.status.setWhite(); + } desk.pending = true; } diff --git a/src/esp/HomeAssistantHandler.cpp b/src/esp/HomeAssistantHandler.cpp index f472098..dcbb7fc 100644 --- a/src/esp/HomeAssistantHandler.cpp +++ b/src/esp/HomeAssistantHandler.cpp @@ -307,6 +307,16 @@ void HomeAssistantHandler::diagnostic() calibrate[ComponentAbbreviations::platform].set("button"); calibrate[ComponentAbbreviations::unique_id].set("calibrate"); } + { + JsonObject errors{discovery[ComponentAbbreviations::components]["error"].to()}; + errors[ComponentAbbreviations::entity_category].set(entityCategory); + errors[ComponentAbbreviations::icon].set("mdi:alert-outline"); + errors[ComponentAbbreviations::name].set("Errors"); + errors[ComponentAbbreviations::platform].set("sensor"); + errors[ComponentAbbreviations::state_topic].set(stateTopic); + errors[ComponentAbbreviations::unique_id].set("error"); + errors[ComponentAbbreviations::value_template].set("{{value_json.errors|join(', ')}}"); + } { JsonObject firmware{discovery[ComponentAbbreviations::components]["firmware"].to()}; firmware[ComponentAbbreviations::enabled_by_default].set(false); From a8838431f8091150d063b44af021d3ea36ca87ba Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Wed, 9 Sep 2026 00:24:04 +0200 Subject: [PATCH 02/10] Drop encoder* and state* due to complexity --- include/avr/ConsoleHandler.h | 4 -- include/esp/ConsoleHandler.h | 4 -- include/esp/DeskService.h | 16 +++--- src/avr/ConsoleHandler.cpp | 2 +- src/avr/ControllerService.cpp | 36 ++++---------- src/avr/LegHandler.cpp | 2 +- src/esp/ConsoleHandler.cpp | 46 +++++------------ src/esp/DeskService.cpp | 94 ++++++++++++++--------------------- 8 files changed, 68 insertions(+), 136 deletions(-) diff --git a/include/avr/ConsoleHandler.h b/include/avr/ConsoleHandler.h index 1011e3c..96f3988 100644 --- a/include/avr/ConsoleHandler.h +++ b/include/avr/ConsoleHandler.h @@ -30,8 +30,6 @@ class ConsoleHandler BUTTON_DOWN = 1U, BUTTON_UP, CONSOLE, - ENCODER8, - ENCODER9, INITIALIZATION, LIN, NODE8, @@ -39,8 +37,6 @@ class ConsoleHandler POSITION, PRESET_HIGH, PRESET_LOW, - STATE8, - STATE9, }; /** diff --git a/include/esp/ConsoleHandler.h b/include/esp/ConsoleHandler.h index 9073d12..53c9618 100644 --- a/include/esp/ConsoleHandler.h +++ b/include/esp/ConsoleHandler.h @@ -23,8 +23,6 @@ class ConsoleHandler BUTTON_DOWN = 1U, BUTTON_UP, CONSOLE, - ENCODER8, - ENCODER9, INITIALIZATION, LIN, NODE8, @@ -32,8 +30,6 @@ class ConsoleHandler POSITION, PRESET_HIGH, PRESET_LOW, - STATE8, - STATE9, }; /** diff --git a/include/esp/DeskService.h b/include/esp/DeskService.h index 7e18a07..615f975 100644 --- a/include/esp/DeskService.h +++ b/include/esp/DeskService.h @@ -110,21 +110,17 @@ class DeskService void setErrorLin(uint8_t flags); - void setErrorRx(hardwareSerial_error_t flags); - - void setErrorTx(uint8_t flags); + void setErrorNode8(); - void setNode8(); + void setErrorNode9(); - void setNode8(uint8_t state); - - void setNode8(uint16_t position); + void setErrorRx(hardwareSerial_error_t flags); - void setNode9(); + void setErrorTx(uint8_t flags); - void setNode9(uint8_t state); + void setNode8(uint16_t position, uint8_t state); - void setNode9(uint16_t position); + void setNode9(uint16_t position, uint8_t state); void setPresetHigh(uint16_t encoder); diff --git a/src/avr/ConsoleHandler.cpp b/src/avr/ConsoleHandler.cpp index 8f0e12d..effb892 100644 --- a/src/avr/ConsoleHandler.cpp +++ b/src/avr/ConsoleHandler.cpp @@ -14,7 +14,7 @@ void ConsoleHandler::handle() { if (Serial1.available() != 0) { - const unsigned char errors{UCSR1A}; + const unsigned char errors{UCSR1A}; // NOLINT(clang-analyzer-core.FixedAddressDereference) if ((errors & ((0b1U << DOR1) | (0b1U << FE1))) != 0U) { send(State::CONSOLE, errors); diff --git a/src/avr/ControllerService.cpp b/src/avr/ControllerService.cpp index 4edc789..c774d21 100644 --- a/src/avr/ControllerService.cpp +++ b/src/avr/ControllerService.cpp @@ -72,24 +72,16 @@ bool ControllerService::read() if (valid8) { const unsigned int _encoder8{static_cast(node8[0U]) | static_cast(node8[1U]) << 8U}; - if (_encoder8 != encoder8 && state8 != node8[2U]) + if (_encoder8 != encoder8 || state8 != node8[2U]) { + if (_encoder8 != encoder8) + { + lastMillis = millis(); + } encoder8 = _encoder8; state8 = node8[2U]; - lastMillis = millis(); console.send(ConsoleHandler::State::NODE8, node8); } - else if (_encoder8 != encoder8) - { - encoder8 = _encoder8; - lastMillis = millis(); - console.send(ConsoleHandler::State::ENCODER8, encoder8); - } - else if (state8 != node8[2U]) - { - state8 = node8[2U]; - console.send(ConsoleHandler::State::STATE8, state8); - } } else { @@ -102,24 +94,16 @@ bool ControllerService::read() if (valid9) { const unsigned int _encoder9{static_cast(node9[0U]) | static_cast(node9[1U]) << 8U}; - if (_encoder9 != encoder9 && state9 != node9[2U]) + if (_encoder9 != encoder9 || state9 != node9[2U]) { + if (_encoder9 != encoder9) + { + lastMillis = millis(); + } encoder9 = _encoder9; state9 = node9[2U]; - lastMillis = millis(); console.send(ConsoleHandler::State::NODE9, node9); } - else if (_encoder9 != encoder9) - { - encoder9 = _encoder9; - lastMillis = millis(); - console.send(ConsoleHandler::State::ENCODER9, encoder9); - } - else if (state9 != node9[2U]) - { - state9 = node9[2U]; - console.send(ConsoleHandler::State::STATE9, state9); - } } else { diff --git a/src/avr/LegHandler.cpp b/src/avr/LegHandler.cpp index 6d38d44..3d46437 100644 --- a/src/avr/LegHandler.cpp +++ b/src/avr/LegHandler.cpp @@ -132,7 +132,7 @@ int LegHandler::read(unsigned int &remainingTime) { return -1; } - const unsigned char errors{UCSR0A}; + const unsigned char errors{UCSR0A}; // NOLINT(clang-analyzer-core.FixedAddressDereference) if ((errors & ((0b1U << DOR0) | (0b1U << FE0))) != 0U) { Serial1.write((1U << 4U) | static_cast(ConsoleHandler::State::LIN)); diff --git a/src/esp/ConsoleHandler.cpp b/src/esp/ConsoleHandler.cpp index 92a2696..1f4a9db 100644 --- a/src/esp/ConsoleHandler.cpp +++ b/src/esp/ConsoleHandler.cpp @@ -92,16 +92,6 @@ void ConsoleHandler::parse() const { desk.setErrorTx(bufferRx.at(1U)); } - else if (stateRx == State::ENCODER8 && lengthRx == 2U) - { - desk.setNode8(static_cast(static_cast(bufferRx.at(1U)) | - static_cast(static_cast(bufferRx.at(2U)) << 8U))); - } - else if (stateRx == State::ENCODER9 && lengthRx == 2U) - { - desk.setNode9(static_cast(static_cast(bufferRx.at(1U)) | - static_cast(static_cast(bufferRx.at(2U)) << 8U))); - } else if (stateRx == State::INITIALIZATION) { desk.setErrorAvr(); @@ -110,25 +100,21 @@ void ConsoleHandler::parse() const { desk.setErrorLin(bufferRx.at(1U)); } - else if (stateRx == State::NODE8 && lengthRx == 0U) - { - desk.setNode8(); - } - else if (stateRx == State::NODE8 && lengthRx == 3U) + else if (stateRx == State::NODE8) { - desk.setNode8(static_cast(static_cast(bufferRx.at(1U)) | - static_cast(static_cast(bufferRx.at(2U)) << 8U))); - desk.setNode8(bufferRx.at(3U)); + lengthRx == 3U + ? desk.setNode8(static_cast(static_cast(bufferRx.at(1U)) | + static_cast(static_cast(bufferRx.at(2U)) << 8U)), + bufferRx.at(3U)) + : desk.setErrorNode8(); } - else if (stateRx == State::NODE9 && lengthRx == 0U) + else if (stateRx == State::NODE9) { - desk.setNode9(); - } - else if (stateRx == State::NODE9 && lengthRx == 3U) - { - desk.setNode9(static_cast(static_cast(bufferRx.at(1U)) | - static_cast(static_cast(bufferRx.at(2U)) << 8U))); - desk.setNode9(bufferRx.at(3U)); + lengthRx == 3U + ? desk.setNode9(static_cast(static_cast(bufferRx.at(1U)) | + static_cast(static_cast(bufferRx.at(2U)) << 8U)), + bufferRx.at(3U)) + : desk.setErrorNode9(); } else if (stateRx == State::PRESET_HIGH && lengthRx == 2U) { @@ -140,14 +126,6 @@ void ConsoleHandler::parse() const desk.setPresetLow(static_cast(bufferRx.at(1U)) | static_cast(static_cast(bufferRx.at(2U)) << 8U)); } - else if (stateRx == State::STATE8 && lengthRx == 1U) - { - desk.setNode8(bufferRx.at(1U)); - } - else if (stateRx == State::STATE9 && lengthRx == 1U) - { - desk.setNode9(bufferRx.at(1U)); - } else { desk.statusRed(); diff --git a/src/esp/DeskService.cpp b/src/esp/DeskService.cpp index 2c3a406..e8b9551 100644 --- a/src/esp/DeskService.cpp +++ b/src/esp/DeskService.cpp @@ -388,113 +388,95 @@ void DeskService::setErrorLin(uint8_t flags) statusRed(); } -void DeskService::setErrorRx(hardwareSerial_error_t flags) +void DeskService::setErrorNode8() { - if (flags != errorRx) + if (node8) { - errorRx = flags; + node8 = false; pending = true; } statusRed(); } -void DeskService::setErrorTx(uint8_t flags) +void DeskService::setErrorNode9() { - if (flags != errorTx) + if (node9) { - errorTx = flags; + node9 = false; pending = true; } statusRed(); } -void DeskService::setNode8() +void DeskService::setErrorRx(hardwareSerial_error_t flags) { - if (node8) + if (flags != errorRx) { - node8 = false; + errorRx = flags; pending = true; } statusRed(); } -/** - * @brief Updates the state of drive 8. - * - * @param state New drive state. - */ -void DeskService::setNode8(uint8_t state) +void DeskService::setErrorTx(uint8_t flags) { - if (state != state8) + if (flags != errorTx) { - state8 = state; + errorTx = flags; pending = true; - statusNode(); } - node8 = true; + statusRed(); } -/** - * @brief Updates the encoder 8 position and marks the desk state for saving and publication. - * - * Updates the status indicator when the position changes. - * - * @param position New encoder 8 position. - */ -void DeskService::setNode8(uint16_t position) +void DeskService::setNode8(uint16_t position, uint8_t state) { - if (position != encoder8) + if (position != encoder8 && state != state8) { encoder8 = position; + state8 = state; saved = false; pending = true; statusNode(); } - node8 = true; -} - -void DeskService::setNode9() -{ - if (node9) + else if (position != encoder8) { - node9 = false; + encoder8 = position; + saved = false; pending = true; + statusNode(); } - statusRed(); + else if (state != state8) + { + state8 = state; + pending = true; + statusNode(); + } + node8 = true; } -/** - * @brief Updates the motor state for encoder 9. - * - * @param state New motor state. - */ -void DeskService::setNode9(uint8_t state) +void DeskService::setNode9(uint16_t position, uint8_t state) { - if (state != state9) + if (position != encoder9 && state != state9) { + encoder9 = position; state9 = state; + saved = false; pending = true; statusNode(); } - node9 = true; -} - -/** - * @brief Updates the secondary encoder value. - * - * Marks the device state for persistence and publication when the value changes. - * - * @param position New secondary encoder value. - */ -void DeskService::setNode9(uint16_t position) -{ - if (position != encoder9) + else if (position != encoder9) { encoder9 = position; saved = false; pending = true; statusNode(); } + else if (state != state9) + { + state9 = state; + pending = true; + statusNode(); + } node9 = true; } From d4cdbe2323decf0f4144b706974cf2409fba52af Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Wed, 9 Sep 2026 00:28:41 +0200 Subject: [PATCH 03/10] Reduce Clang-tidy failure threshold to 1 --- .github/workflows/clang.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index 44b9d6b..faac1b5 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -131,7 +131,7 @@ jobs: - name: Summary env: - clang-threshold: 2 + clang-threshold: 1 run: | echo "Status: ${{ steps.tidy.outputs.checks-failed }} checks failed, threshold for success is ${{ env.clang-threshold }}." echo "Clang report:" From 1fd1ddcf8188d20bc235879e026cbd5806d49bba Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Wed, 9 Sep 2026 01:01:48 +0200 Subject: [PATCH 04/10] Fix findings --- src/esp/DeskService.cpp | 49 ++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/src/esp/DeskService.cpp b/src/esp/DeskService.cpp index e8b9551..a15a9f4 100644 --- a/src/esp/DeskService.cpp +++ b/src/esp/DeskService.cpp @@ -434,6 +434,7 @@ void DeskService::setNode8(uint16_t position, uint8_t state) { encoder8 = position; state8 = state; + node8 = true; saved = false; pending = true; statusNode(); @@ -441,6 +442,7 @@ void DeskService::setNode8(uint16_t position, uint8_t state) else if (position != encoder8) { encoder8 = position; + node8 = true; saved = false; pending = true; statusNode(); @@ -448,10 +450,15 @@ void DeskService::setNode8(uint16_t position, uint8_t state) else if (state != state8) { state8 = state; + node8 = true; pending = true; statusNode(); } - node8 = true; + else if (!node8) + { + node8 = true; + pending = true; + } } void DeskService::setNode9(uint16_t position, uint8_t state) @@ -460,6 +467,7 @@ void DeskService::setNode9(uint16_t position, uint8_t state) { encoder9 = position; state9 = state; + node9 = true; saved = false; pending = true; statusNode(); @@ -467,6 +475,7 @@ void DeskService::setNode9(uint16_t position, uint8_t state) else if (position != encoder9) { encoder9 = position; + node9 = true; saved = false; pending = true; statusNode(); @@ -474,10 +483,15 @@ void DeskService::setNode9(uint16_t position, uint8_t state) else if (state != state9) { state9 = state; + node9 = true; pending = true; statusNode(); } - node9 = true; + else if (!node9) + { + node9 = true; + pending = true; + } } /** @@ -636,22 +650,6 @@ void DeskService::toErrorArray(JsonArray &list) { list.add("leg node 9 error"); } - if ((errorRx & (1U << static_cast(hardwareSerial_error_t::UART_BREAK_ERROR))) != 0U) - { - list.add("UART break"); - } - if ((errorRx & (1U << static_cast(hardwareSerial_error_t::UART_BUFFER_FULL_ERROR))) != 0U) - { - list.add("UART buffer full"); - } - if ((errorRx & (1U << static_cast(hardwareSerial_error_t::UART_FIFO_OVF_ERROR))) != 0U) - { - list.add("UART FIFO overflow"); - } - if ((errorRx & (1U << static_cast(hardwareSerial_error_t::UART_FRAME_ERROR))) != 0U) - { - list.add("UART frame error"); - } if ((errorLin & (0b1U << 3U)) != 0U) { list.add("USART0 data overrun"); @@ -668,6 +666,21 @@ void DeskService::toErrorArray(JsonArray &list) { list.add("USART1 frame error"); } + switch (errorRx) + { + case hardwareSerial_error_t::UART_BREAK_ERROR: + list.add("UART break"); + break; + case hardwareSerial_error_t::UART_BUFFER_FULL_ERROR: + list.add("UART buffer full"); + break; + case hardwareSerial_error_t::UART_FIFO_OVF_ERROR: + list.add("UART FIFO overflow"); + break; + case hardwareSerial_error_t::UART_FRAME_ERROR: + list.add("UART frame error"); + break; + } } /** From d46ca46bad25c6c61e6556bb1d0f4258ccea795e Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Thu, 10 Sep 2026 00:25:17 +0200 Subject: [PATCH 05/10] Refactor LIN bus initialization sequence to also report errors --- include/avr/LegHandler.h | 110 ++++++++++++++++++---------------- include/esp/DeskService.h | 14 ++--- src/avr/ConsoleHandler.cpp | 2 +- src/avr/ControllerService.cpp | 48 +++++++-------- src/avr/LegHandler.cpp | 108 ++++++++++++++++++--------------- src/esp/ConsoleHandler.cpp | 33 +++++----- src/esp/DeskService.cpp | 103 ++++++++++++++++++------------- 7 files changed, 232 insertions(+), 186 deletions(-) diff --git a/include/avr/LegHandler.h b/include/avr/LegHandler.h index 8b53153..70349f3 100644 --- a/include/avr/LegHandler.h +++ b/include/avr/LegHandler.h @@ -24,39 +24,26 @@ class LegHandler static constexpr unsigned char linDiagnosticResponseId{0x3DU}; static constexpr unsigned char linSyncByte{0x55U}; + void requestDiscardResponse(); + void sendResponse(unsigned char pid); void serialBreak(); - [[nodiscard]] unsigned char calcParity(unsigned char identifier); - [[nodiscard]] int read(unsigned int &remainingTime); - /** - * Calculates the complemented checksum for a sequence of bytes. - * @param data Bytes to include in the checksum. - * @param sum Initial checksum sum. - * @return The complemented checksum. - */ - template [[nodiscard]] unsigned char calcChecksum(const unsigned char (&data)[N], unsigned int sum) - { - for (const unsigned char byte : data) - { - sum += byte; - } - while ((sum >> 8U) != 0U) - { - sum = (sum & 0xFFU) + (sum >> 8U); - } - return static_cast(~sum); - } - - template bool sendRequest(const unsigned char (&packet)[N]) +public: + [[nodiscard]] static constexpr unsigned char getPid(unsigned char identifier) { - send(linDiagnosticRequestId, packet); - unsigned char response[N]{}; - return request(linDiagnosticResponseId, response); + identifier &= 0x3FU; + const unsigned int parity0{ + static_cast(identifier & 1U) ^ (static_cast(identifier >> 1U) & 1U) ^ + (static_cast(identifier >> 2U) & 1U) ^ (static_cast(identifier >> 4U) & 1U)}; + const unsigned int parity1{ + ~((static_cast(identifier >> 1U) & 1U) ^ (static_cast(identifier >> 3U) & 1U) ^ + (static_cast(identifier >> 4U) & 1U) ^ (static_cast(identifier >> 5U) & 1U)) & + 1U}; + return static_cast(identifier | ((parity0 | (parity1 << 1U)) << 6U)); } -public: enum class Command : unsigned char { FINISH = 0x84U, @@ -69,9 +56,9 @@ class LegHandler IDLE = 0xFCU, }; - [[nodiscard]] bool begin(); + [[nodiscard]] unsigned char begin(); - void send(unsigned char identifier); + [[nodiscard]] unsigned char getLeg(unsigned char pid, unsigned char (&node)[3U]); /** * Sends a desk command with a target encoder position. @@ -82,21 +69,23 @@ class LegHandler void sendCommand(Command command, unsigned int position); /** - * Sends a LIN frame containing the specified payload. - * - * @param identifier LIN frame identifier. - * @param data Payload bytes to transmit. + * Calculates the complemented checksum for a sequence of bytes. + * @param data Bytes to include in the checksum. + * @param sum Initial checksum sum. + * @return The complemented checksum. */ - template void send(unsigned char identifier, const unsigned char (&data)[N]) + template + [[nodiscard]] unsigned char getChecksum(const unsigned char (&data)[N], unsigned int sum = 0U) { - static_assert(N <= 8U); - const unsigned char idByte{static_cast((identifier & 0x3FU) | calcParity(identifier))}; - serialBreak(); - Serial.write(linSyncByte); - Serial.write(idByte); - Serial.write(data, N); - Serial.write(calcChecksum(data, identifier == linDiagnosticRequestId ? 0U : idByte)); - Serial.flush(); + for (const unsigned char byte : data) + { + sum += byte; + } + while ((sum >> 8U) != 0U) + { + sum = (sum & 0xFFU) + (sum >> 8U); + } + return static_cast(~sum); } /** @@ -107,13 +96,12 @@ class LegHandler * @return `true` if a complete response with a valid checksum is received, `false` on timeout or checksum * failure. */ - template [[nodiscard]] bool request(unsigned char identifier, unsigned char (&data)[N]) + template int receiveResponse(unsigned char pid, unsigned char (&data)[N]) { static_assert(N <= 8U); - const unsigned char idByte{static_cast((identifier & 0x3FU) | calcParity(identifier))}; serialBreak(); Serial.write(linSyncByte); - Serial.write(idByte); + Serial.write(pid); Serial.flush(); int receivedByte{}; unsigned int remainingTime{static_cast(LinFrame::frameBits * 1'000'000UL / baudRate)}; @@ -123,28 +111,48 @@ class LegHandler } while (receivedByte != -1 && receivedByte != static_cast(linSyncByte)); if (receivedByte == -1) { - return false; + return -1; } do // NOLINT(cppcoreguidelines-avoid-do-while) { receivedByte = read(remainingTime); - } while (receivedByte != -1 && receivedByte != idByte); + } while (receivedByte != -1 && receivedByte != pid); if (receivedByte == -1) { - return false; + return -1; } for (unsigned char &dataByte : data) { receivedByte = read(remainingTime); if (receivedByte == -1) { - return false; + return -1; } dataByte = static_cast(receivedByte); } - receivedByte = read(remainingTime); - return receivedByte != -1 && - calcChecksum(data, identifier == linDiagnosticResponseId ? 0U : idByte) == receivedByte; + return read(remainingTime); + } + + template void sendDiagnosticRequest(const unsigned char (&data)[N]) + { + static_assert(N <= 8U); + serialBreak(); + Serial.write(linSyncByte); + Serial.write(getPid(linDiagnosticRequestId)); + Serial.write(data, N); + Serial.write(getChecksum(data)); + Serial.flush(); + } + + template void sendResponse(unsigned char pid, const unsigned char (&data)[N]) + { + static_assert(N <= 8U); + serialBreak(); + Serial.write(linSyncByte); + Serial.write(pid); + Serial.write(data, N); + Serial.write(getChecksum(data, pid)); + Serial.flush(); } }; diff --git a/include/esp/DeskService.h b/include/esp/DeskService.h index 615f975..9b77993 100644 --- a/include/esp/DeskService.h +++ b/include/esp/DeskService.h @@ -15,17 +15,17 @@ class DeskService { private: - bool avr{true}; bool buttonDown{false}; bool buttonUp{false}; bool enable{true}; - bool node8{true}; - bool node9{true}; bool pending{true}; bool process{true}; bool reset{false}; bool saved{true}; + uint8_t error8{0U}; + uint8_t error9{0U}; + uint8_t errorInit{0U}; uint8_t errorLin{0U}; uint8_t errorTx{0U}; uint8_t state8{0U}; @@ -106,13 +106,13 @@ class DeskService void setButtonUp(bool state); - void setErrorAvr(); + void setError8(uint8_t flags); - void setErrorLin(uint8_t flags); + void setError9(uint8_t flags); - void setErrorNode8(); + void setErrorInit(uint8_t flags); - void setErrorNode9(); + void setErrorLin(uint8_t flags); void setErrorRx(hardwareSerial_error_t flags); diff --git a/src/avr/ConsoleHandler.cpp b/src/avr/ConsoleHandler.cpp index effb892..f8fde65 100644 --- a/src/avr/ConsoleHandler.cpp +++ b/src/avr/ConsoleHandler.cpp @@ -95,7 +95,7 @@ void ConsoleHandler::send(State state) { Serial1.write(static_cast(state)); + Serial1.write(static_cast((1U << 4U) | static_cast(state))); Serial1.write(byte); } diff --git a/src/avr/ControllerService.cpp b/src/avr/ControllerService.cpp index c774d21..1abcf08 100644 --- a/src/avr/ControllerService.cpp +++ b/src/avr/ControllerService.cpp @@ -26,9 +26,10 @@ void ControllerService::begin() EEPROM.get(static_cast('l'), presetLow); console.send(ConsoleHandler::State::PRESET_HIGH, presetHigh); console.send(ConsoleHandler::State::PRESET_LOW, presetLow); - if (!lin.begin()) + const unsigned char init{lin.begin()}; + if (init != 0U) { - console.send(ConsoleHandler::State::INITIALIZATION); + console.send(ConsoleHandler::State::INITIALIZATION, init); tone(0b1U << 8U); return; } @@ -64,57 +65,52 @@ void ControllerService::handle() bool ControllerService::read() { constexpr unsigned char empty[3U]{0U, 0U, 0U}; - lin.send(0x11U, empty); - unsigned char node8[3U]{}; - unsigned char node9[3U]{}; - const bool valid8{lin.request(0x8U, node8)}; - const bool valid9{lin.request(0x9U, node9)}; - if (valid8) + lin.sendResponse(lin.getPid(0x11U), empty); + unsigned char node[3U]{}; + const unsigned char error8{lin.getLeg(LegHandler::getPid(0x8U), node)}; + if (error8 == 0U) { - const unsigned int _encoder8{static_cast(node8[0U]) | static_cast(node8[1U]) << 8U}; - if (_encoder8 != encoder8 || state8 != node8[2U]) + const unsigned int _encoder8{static_cast(node[0U]) | static_cast(node[1U]) << 8U}; + if (_encoder8 != encoder8 || state8 != node[2U]) { if (_encoder8 != encoder8) { lastMillis = millis(); } encoder8 = _encoder8; - state8 = node8[2U]; - console.send(ConsoleHandler::State::NODE8, node8); + state8 = node[2U]; + console.send(ConsoleHandler::State::NODE8, node); } } else { - console.send(ConsoleHandler::State::NODE8); - if (pending) - { - tone(0b1U << 8U); - } + console.send(ConsoleHandler::State::NODE8, error8); } - if (valid9) + const unsigned char error9{lin.getLeg(LegHandler::getPid(0x9U), node)}; + if (error9 == 0U) { - const unsigned int _encoder9{static_cast(node9[0U]) | static_cast(node9[1U]) << 8U}; - if (_encoder9 != encoder9 || state9 != node9[2U]) + const unsigned int _encoder9{static_cast(node[0U]) | static_cast(node[1U]) << 8U}; + if (_encoder9 != encoder9 || state9 != node[2U]) { if (_encoder9 != encoder9) { lastMillis = millis(); } encoder9 = _encoder9; - state9 = node9[2U]; - console.send(ConsoleHandler::State::NODE9, node9); + state9 = node[2U]; + console.send(ConsoleHandler::State::NODE9, node); } } else { - console.send(ConsoleHandler::State::NODE9); + console.send(ConsoleHandler::State::NODE9, error9); + } + if (error8 != 0U || error9 != 0U) + { if (pending) { tone(0b1U << 8U); } - } - if (!valid8 || !valid9) - { return false; } wdt_reset(); diff --git a/src/avr/LegHandler.cpp b/src/avr/LegHandler.cpp index 3d46437..979e180 100644 --- a/src/avr/LegHandler.cpp +++ b/src/avr/LegHandler.cpp @@ -12,7 +12,7 @@ * * @return true if initialization and device detection succeed, false otherwise. */ -bool LegHandler::begin() +unsigned char LegHandler::begin() { pinMode(Pin::lin, OUTPUT); Serial.begin(baudRate); @@ -24,22 +24,26 @@ bool LegHandler::begin() for (const unsigned char (&data)[2U] : initial) { const unsigned char packet[8U]{0xFFU, data[0U], data[1U], 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU}; - send(linDiagnosticRequestId, packet); + sendDiagnosticRequest(packet); } const unsigned char packet[8U]{0xD0U, 0x2U, 0x7U, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU}; - sendRequest(packet); + sendDiagnosticRequest(packet); + requestDiscardResponse(); unsigned char pid{0U}; for (; pid < 8U; ++pid) { const unsigned char probeA[8U]{pid, 0x2U, 0x7U, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU}; - if (sendRequest(probeA)) + sendDiagnosticRequest(probeA); + unsigned char response[sizeof(probeA)]{}; + const int checksum{receiveResponse(getPid(linDiagnosticResponseId), response)}; + if (checksum != -1 && getChecksum(response) == checksum) { break; } - } - if (pid == 8U) - { - return false; + if (pid == 7U) + { + return static_cast(checksum == -1 ? 0b1U : 0b1U << 1U); + } } constexpr unsigned char preProbe[6U][2U]{ {0x6U, 0x9U}, @@ -52,19 +56,24 @@ bool LegHandler::begin() for (const unsigned char (&data)[2U] : preProbe) { const unsigned char packet[8U]{pid, data[0U], data[1U], 0x0U, 0xFFU, 0xFFU, 0xFFU, 0xFFU}; - sendRequest(packet); + sendDiagnosticRequest(packet); + requestDiscardResponse(); } for (; pid < 8U; ++pid) { const unsigned char probeB[8U]{pid, 0x2U, 0x0U, 0x0U, 0xFFU, 0xFFU, 0xFFU, 0xFFU}; - if (sendRequest(probeB)) + // unsigned char response[sizeof(probeB)]{}; + sendDiagnosticRequest(probeB); + unsigned char response[sizeof(probeB)]{}; + const int checksum{receiveResponse(getPid(linDiagnosticResponseId), response)}; + if (checksum != -1 && getChecksum(response) == checksum) { break; } - } - if (pid == 8U) - { - return false; + if (pid == 7U) + { + return static_cast(checksum == -1 ? 0b1U << 2U : 0b1U << 3U); + } } constexpr unsigned char preBroadcast[6U][2U]{ {0x6U, 0x9U}, @@ -77,40 +86,38 @@ bool LegHandler::begin() for (const unsigned char (&data)[2U] : preBroadcast) { const unsigned char packet[8U]{pid, data[0U], data[1U], 0x0U, 0xFFU, 0xFFU, 0xFFU, 0xFFU}; - sendRequest(packet); + sendDiagnosticRequest(packet); + requestDiscardResponse(); } for (; pid < 8U; ++pid) { const unsigned char broadcast[8U]{pid, 0x2U, 0x1U, 0x0U, 0xFFU, 0xFFU, 0xFFU, 0xFFU}; - sendRequest(broadcast); + sendDiagnosticRequest(broadcast); + requestDiscardResponse(); } constexpr unsigned char postBroadcast[2U]{0x1U, 0x2U}; for (const unsigned char &data : postBroadcast) { const unsigned char packet[8U]{0xD0U, data, 0x7U, 0x0U, 0xFFU, 0xFFU, 0xFFU, 0xFFU}; - send(linDiagnosticRequestId, packet); + sendDiagnosticRequest(packet); } constexpr unsigned char final[3U]{0xF6U, 0xFFU, 0xBFU}; - send(0x12U, final); - return true; + sendResponse(getPid(0x12U), final); + return 0U; } -/** - * @brief Calculates the LIN protected identifier parity bits. - * - * @param identifier Six-bit LIN identifier. - * @return Parity bits positioned in bits 6 and 7. - */ -unsigned char LegHandler::calcParity(unsigned char identifier) +unsigned char LegHandler::getLeg(unsigned char pid, unsigned char (&node)[3U]) { - const unsigned int parity0{ - static_cast(identifier & 1U) ^ (static_cast(identifier >> 1U) & 1U) ^ - (static_cast(identifier >> 2U) & 1U) ^ (static_cast(identifier >> 4U) & 1U)}; - const unsigned int parity1{ - ~((static_cast(identifier >> 1U) & 1U) ^ (static_cast(identifier >> 3U) & 1U) ^ - (static_cast(identifier >> 4U) & 1U) ^ (static_cast(identifier >> 5U) & 1U)) & - 1U}; - return static_cast((parity0 | (parity1 << 1U)) << 6U); + const int checksum{receiveResponse(pid, node)}; + if (checksum == -1) + { + return 0b1U; + } + if (getChecksum(node, pid) == checksum) + { + return 0U; + } + return 0b1U << 1U; } /** @@ -141,19 +148,17 @@ int LegHandler::read(unsigned int &remainingTime) return Serial.read(); } -/** - * @brief Transmits a LIN frame for the specified identifier. - * - * @param identifier LIN identifier; its lower six bits are used to form the protected identifier. - */ -void LegHandler::send(unsigned char identifier) +void LegHandler::requestDiscardResponse() { - const unsigned char address{static_cast((identifier & 0x3FU) | calcParity(identifier))}; serialBreak(); Serial.write(linSyncByte); - Serial.write(address); - Serial.write(identifier == linDiagnosticRequestId ? 0xFFU : static_cast(~address)); + Serial.write(getPid(linDiagnosticResponseId)); Serial.flush(); + unsigned int remainingTime{static_cast(LinFrame::frameBits * 1'000'000UL / baudRate)}; + while (remainingTime != 0U) + { + static_cast(read(remainingTime)); + } } /** @@ -162,19 +167,28 @@ void LegHandler::send(unsigned char identifier) * @param command Command code to transmit. * @param position Position value included in the command payload. */ -void LegHandler::sendCommand(LegHandler::Command command, unsigned int position) +void LegHandler::sendCommand(Command command, unsigned int position) { for (unsigned char idx{0U}; idx < 6U; ++idx) { - send(0x10U); + sendResponse(getPid(0x10U)); } - send(0x1U); + sendResponse(getPid(0x1U)); const unsigned char packet[3U]{ static_cast(position & 0xFFU), static_cast(position >> 8U), static_cast(command), }; - send(0x12U, packet); + sendResponse(getPid(0x12U), packet); +} + +void LegHandler::sendResponse(unsigned char pid) +{ + serialBreak(); + Serial.write(linSyncByte); + Serial.write(pid); + Serial.write(static_cast(~pid)); + Serial.flush(); } /** diff --git a/src/esp/ConsoleHandler.cpp b/src/esp/ConsoleHandler.cpp index 1f4a9db..208166d 100644 --- a/src/esp/ConsoleHandler.cpp +++ b/src/esp/ConsoleHandler.cpp @@ -15,6 +15,7 @@ void ConsoleHandler::begin() pinMode(PIN_MISO, INPUT); pinMode(PIN_SCK, OUTPUT); Serial1.onReceiveError(&onReceiveError); + Serial1.setRxBufferSize(0b1U << 9U); Serial1.begin(115'200UL, SerialConfig::SERIAL_8N1, PIN_MISO, PIN_SCK); } @@ -92,29 +93,33 @@ void ConsoleHandler::parse() const { desk.setErrorTx(bufferRx.at(1U)); } - else if (stateRx == State::INITIALIZATION) + else if (stateRx == State::INITIALIZATION && lengthRx == 1U) { - desk.setErrorAvr(); + desk.setErrorInit(bufferRx.at(1U)); } else if (stateRx == State::LIN && lengthRx == 1U) { desk.setErrorLin(bufferRx.at(1U)); } - else if (stateRx == State::NODE8) + else if (stateRx == State::NODE8 && lengthRx == 1U) { - lengthRx == 3U - ? desk.setNode8(static_cast(static_cast(bufferRx.at(1U)) | - static_cast(static_cast(bufferRx.at(2U)) << 8U)), - bufferRx.at(3U)) - : desk.setErrorNode8(); + desk.setError8(bufferRx.at(1U)); } - else if (stateRx == State::NODE9) + else if (stateRx == State::NODE8 && lengthRx == 3U) { - lengthRx == 3U - ? desk.setNode9(static_cast(static_cast(bufferRx.at(1U)) | - static_cast(static_cast(bufferRx.at(2U)) << 8U)), - bufferRx.at(3U)) - : desk.setErrorNode9(); + desk.setNode8(static_cast(static_cast(bufferRx.at(1U)) | + static_cast(static_cast(bufferRx.at(2U)) << 8U)), + bufferRx.at(3U)); + } + else if (stateRx == State::NODE9 && lengthRx == 1U) + { + desk.setError9(bufferRx.at(1U)); + } + else if (stateRx == State::NODE9 && lengthRx == 3U) + { + desk.setNode9(static_cast(static_cast(bufferRx.at(1U)) | + static_cast(static_cast(bufferRx.at(2U)) << 8U)), + bufferRx.at(3U)); } else if (stateRx == State::PRESET_HIGH && lengthRx == 2U) { diff --git a/src/esp/DeskService.cpp b/src/esp/DeskService.cpp index a15a9f4..7cd9c11 100644 --- a/src/esp/DeskService.cpp +++ b/src/esp/DeskService.cpp @@ -69,10 +69,10 @@ void DeskService::begin() attachInterrupt(PIN_TPUP, onInterruptUp, CHANGE); #endif // PIN_TPUP digitalWrite(PIN_RST, HIGH); + console.begin(); wifi.begin(); ota.begin(); isp.begin(); - console.begin(); mqtt.begin(); fetchRelease(); } @@ -371,38 +371,41 @@ void DeskService::setDriveUp(bool state) #endif // PIN_TPUP } -void DeskService::setErrorAvr() +void DeskService::setError8(uint8_t flags) { - avr = false; - pending = true; + if (flags != error8) + { + error8 = flags; + pending = true; + } statusRed(); } -void DeskService::setErrorLin(uint8_t flags) +void DeskService::setError9(uint8_t flags) { - if (flags != errorLin) + if (flags != error9) { - errorLin = flags; + error9 = flags; pending = true; } statusRed(); } -void DeskService::setErrorNode8() +void DeskService::setErrorInit(uint8_t flags) { - if (node8) + if (flags != errorInit) { - node8 = false; + errorInit = flags; pending = true; } statusRed(); } -void DeskService::setErrorNode9() +void DeskService::setErrorLin(uint8_t flags) { - if (node9) + if (flags != errorLin) { - node9 = false; + errorLin = flags; pending = true; } statusRed(); @@ -434,7 +437,7 @@ void DeskService::setNode8(uint16_t position, uint8_t state) { encoder8 = position; state8 = state; - node8 = true; + error8 = 0U; saved = false; pending = true; statusNode(); @@ -442,7 +445,7 @@ void DeskService::setNode8(uint16_t position, uint8_t state) else if (position != encoder8) { encoder8 = position; - node8 = true; + error8 = 0U; saved = false; pending = true; statusNode(); @@ -450,13 +453,13 @@ void DeskService::setNode8(uint16_t position, uint8_t state) else if (state != state8) { state8 = state; - node8 = true; + error8 = 0U; pending = true; statusNode(); } - else if (!node8) + else if (error8 != 0U) { - node8 = true; + error8 = 0U; pending = true; } } @@ -467,7 +470,7 @@ void DeskService::setNode9(uint16_t position, uint8_t state) { encoder9 = position; state9 = state; - node9 = true; + error9 = 0U; saved = false; pending = true; statusNode(); @@ -475,7 +478,7 @@ void DeskService::setNode9(uint16_t position, uint8_t state) else if (position != encoder9) { encoder9 = position; - node9 = true; + error9 = 0U; saved = false; pending = true; statusNode(); @@ -483,13 +486,13 @@ void DeskService::setNode9(uint16_t position, uint8_t state) else if (state != state9) { state9 = state; - node9 = true; + error9 = 0U; pending = true; statusNode(); } - else if (!node9) + else if (error9 != 0U) { - node9 = true; + error9 = 0U; pending = true; } } @@ -638,47 +641,67 @@ std::string DeskService::toHex(std::span payload) void DeskService::toErrorArray(JsonArray &list) { - if (!avr) + if ((errorInit & 0b1U) != 0U) + { + list.add("probe A: no response"); + } + if ((errorInit & (0b1U << 1U)) != 0U) + { + list.add("probe A: checksum mismatch"); + } + if ((errorInit & (0b1U << 2U)) != 0U) + { + list.add("probe B: no response"); + } + if ((errorInit & (0b1U << 3U)) != 0U) + { + list.add("probe B: checksum mismatch"); + } + if ((error8 & 0b1U) != 0U) + { + list.add("node 8: no response"); + } + if ((error8 & (0b1U << 1U)) != 0U) { - list.add("desk initialization error"); + list.add("node 8: checksum mismatch"); } - if (!node8) + if ((error9 & 0b1U) != 0U) { - list.add("leg node 8 error"); + list.add("node 9: no response"); } - if (!node9) + if ((error9 & (0b1U << 1U)) != 0U) { - list.add("leg node 9 error"); + list.add("node 9: checksum mismatch"); } if ((errorLin & (0b1U << 3U)) != 0U) { - list.add("USART0 data overrun"); + list.add("USART0: data overrun"); } if ((errorLin & (0b1U << 4U)) != 0U) { - list.add("USART0 frame error"); + list.add("USART0: frame error"); } if ((errorTx & (0b1U << 3U)) != 0U) { - list.add("USART1 data overrun"); + list.add("USART1: data overrun"); } if ((errorTx & (0b1U << 4U)) != 0U) { - list.add("USART1 frame error"); + list.add("USART1: frame error"); } switch (errorRx) { case hardwareSerial_error_t::UART_BREAK_ERROR: - list.add("UART break"); + list.add("UART: break"); break; case hardwareSerial_error_t::UART_BUFFER_FULL_ERROR: - list.add("UART buffer full"); + list.add("UART: buffer full"); break; case hardwareSerial_error_t::UART_FIFO_OVF_ERROR: - list.add("UART FIFO overflow"); + list.add("UART: FIFO overflow"); break; case hardwareSerial_error_t::UART_FRAME_ERROR: - list.add("UART frame error"); + list.add("UART: frame error"); break; } } @@ -781,9 +804,9 @@ void DeskService::onInterruptReset() desk.reset = digitalRead(PIN_RST) == LOW; if (desk.reset) { - desk.avr = true; - desk.node8 = true; - desk.node9 = true; + desk.error8 = 0U; + desk.error9 = 0U; + desk.errorInit = 0U; desk.errorLin = 0U; desk.errorRx = hardwareSerial_error_t::UART_NO_ERROR; desk.errorTx = 0U; From fc54819813741ebe020382f092522626e2156626 Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Thu, 10 Sep 2026 01:25:02 +0200 Subject: [PATCH 06/10] Refactoring --- include/avr/ControllerService.h | 2 +- include/avr/LegHandler.h | 2 + include/esp/ConsoleHandler.h | 16 ++- include/esp/DeskService.h | 12 +- src/avr/ControllerService.cpp | 14 +- src/esp/ConsoleHandler.cpp | 86 ++++++++++- src/esp/DeskService.cpp | 244 ++++++++++++-------------------- 7 files changed, 202 insertions(+), 174 deletions(-) diff --git a/include/avr/ControllerService.h b/include/avr/ControllerService.h index 755258c..c691631 100644 --- a/include/avr/ControllerService.h +++ b/include/avr/ControllerService.h @@ -66,7 +66,7 @@ class ControllerService /** * Handles leg movement commands and encoder communication. */ - LegHandler lin{}; + LegHandler leg{}; State state{State::IDLE}; diff --git a/include/avr/LegHandler.h b/include/avr/LegHandler.h index 70349f3..5e81b4c 100644 --- a/include/avr/LegHandler.h +++ b/include/avr/LegHandler.h @@ -25,7 +25,9 @@ class LegHandler static constexpr unsigned char linSyncByte{0x55U}; void requestDiscardResponse(); + void sendResponse(unsigned char pid); + void serialBreak(); [[nodiscard]] int read(unsigned int &remainingTime); diff --git a/include/esp/ConsoleHandler.h b/include/esp/ConsoleHandler.h index 53c9618..814a197 100644 --- a/include/esp/ConsoleHandler.h +++ b/include/esp/ConsoleHandler.h @@ -2,6 +2,7 @@ #ifdef ARDUINO_ARCH_ESP32 +#include // NOLINT(misc-include-cleaner) #include #include #include @@ -37,6 +38,8 @@ class ConsoleHandler */ void begin(); + void getErrors(JsonArray &errors); + /** * Processes available console input. */ @@ -47,11 +50,16 @@ class ConsoleHandler */ void forward(); + void reset(); + void send(Command command); void send(Command command, uint16_t value); private: + uint8_t errorLin{0U}; + uint8_t errorTx{0U}; + size_t bytesRx{0U}; size_t bytesTx{0U}; size_t lengthRx{0U}; @@ -70,10 +78,16 @@ class ConsoleHandler */ State stateRx{}; + static inline hardwareSerial_error_t errorRx{hardwareSerial_error_t::UART_NO_ERROR}; + /** * Parses a received console payload. */ - void parse() const; + void parse(); + + void setErrorLin(uint8_t flags); + + void setErrorTx(uint8_t flags); void write(std::span payload); diff --git a/include/esp/DeskService.h b/include/esp/DeskService.h index 9b77993..d0ce539 100644 --- a/include/esp/DeskService.h +++ b/include/esp/DeskService.h @@ -26,8 +26,6 @@ class DeskService uint8_t error8{0U}; uint8_t error9{0U}; uint8_t errorInit{0U}; - uint8_t errorLin{0U}; - uint8_t errorTx{0U}; uint8_t state8{0U}; uint8_t state9{0U}; @@ -75,7 +73,7 @@ class DeskService void statusNode(); - void toErrorArray(JsonArray &list); + void getErrors(JsonArray &list); [[nodiscard]] float decode(float encoder); @@ -112,16 +110,12 @@ class DeskService void setErrorInit(uint8_t flags); - void setErrorLin(uint8_t flags); - - void setErrorRx(hardwareSerial_error_t flags); - - void setErrorTx(uint8_t flags); - void setNode8(uint16_t position, uint8_t state); void setNode9(uint16_t position, uint8_t state); + void setPending(); + void setPresetHigh(uint16_t encoder); void setPresetLow(uint16_t encoder); diff --git a/src/avr/ControllerService.cpp b/src/avr/ControllerService.cpp index 1abcf08..32c521d 100644 --- a/src/avr/ControllerService.cpp +++ b/src/avr/ControllerService.cpp @@ -26,7 +26,7 @@ void ControllerService::begin() EEPROM.get(static_cast('l'), presetLow); console.send(ConsoleHandler::State::PRESET_HIGH, presetHigh); console.send(ConsoleHandler::State::PRESET_LOW, presetLow); - const unsigned char init{lin.begin()}; + const unsigned char init{leg.begin()}; if (init != 0U) { console.send(ConsoleHandler::State::INITIALIZATION, init); @@ -65,9 +65,9 @@ void ControllerService::handle() bool ControllerService::read() { constexpr unsigned char empty[3U]{0U, 0U, 0U}; - lin.sendResponse(lin.getPid(0x11U), empty); + leg.sendResponse(LegHandler::getPid(0x11U), empty); unsigned char node[3U]{}; - const unsigned char error8{lin.getLeg(LegHandler::getPid(0x8U), node)}; + const unsigned char error8{leg.getLeg(LegHandler::getPid(0x8U), node)}; if (error8 == 0U) { const unsigned int _encoder8{static_cast(node[0U]) | static_cast(node[1U]) << 8U}; @@ -86,7 +86,7 @@ bool ControllerService::read() { console.send(ConsoleHandler::State::NODE8, error8); } - const unsigned char error9{lin.getLeg(LegHandler::getPid(0x9U), node)}; + const unsigned char error9{leg.getLeg(LegHandler::getPid(0x9U), node)}; if (error9 == 0U) { const unsigned int _encoder9{static_cast(node[0U]) | static_cast(node[1U]) << 8U}; @@ -155,7 +155,7 @@ void ControllerService::process() break; case State::RECAL_DONE: state = State::IDLE; - lin.sendCommand(LegHandler::Command::CALIBRATE_END, 99U); + leg.sendCommand(LegHandler::Command::CALIBRATE_END, 99U); break; } } @@ -273,7 +273,7 @@ void ControllerService::handleStateRecalOngoing() state = State::RECAL_DONE; return; } - lin.sendCommand(LegHandler::Command::CALIBRATE_BEGIN, 0U); + leg.sendCommand(LegHandler::Command::CALIBRATE_BEGIN, 0U); } /** @@ -289,7 +289,7 @@ void ControllerService::sendCommand(LegHandler::Command command) : Encoder::maxLimit}; const unsigned int minTarget{maxCurrent > Encoder::minLimit + Encoder::maxDelta ? maxCurrent - Encoder::maxDelta : Encoder::minLimit}; - lin.sendCommand(command, constrain(encoderTarget, minTarget, maxTarget)); + leg.sendCommand(command, constrain(encoderTarget, minTarget, maxTarget)); } /** diff --git a/src/esp/ConsoleHandler.cpp b/src/esp/ConsoleHandler.cpp index 208166d..4c2eb3d 100644 --- a/src/esp/ConsoleHandler.cpp +++ b/src/esp/ConsoleHandler.cpp @@ -73,12 +73,58 @@ void ConsoleHandler::forward() } } +void ConsoleHandler::getErrors(JsonArray &errors) +{ + if ((errorLin & (0b1U << 2U)) != 0U) + { + errors.add("USART0: parity error"); + } + if ((errorLin & (0b1U << 3U)) != 0U) + { + errors.add("USART0: data overrun"); + } + if ((errorLin & (0b1U << 4U)) != 0U) + { + errors.add("USART0: frame error"); + } + if ((errorTx & (0b1U << 2U)) != 0U) + { + errors.add("USART1: parity error"); + } + if ((errorTx & (0b1U << 3U)) != 0U) + { + errors.add("USART1: data overrun"); + } + if ((errorTx & (0b1U << 4U)) != 0U) + { + errors.add("USART1: frame error"); + } + switch (errorRx) + { + case hardwareSerial_error_t::UART_BREAK_ERROR: + errors.add("UART: break"); + break; + case hardwareSerial_error_t::UART_BUFFER_FULL_ERROR: + errors.add("UART: buffer full"); + break; + case hardwareSerial_error_t::UART_FIFO_OVF_ERROR: + errors.add("UART: FIFO overflow"); + break; + case hardwareSerial_error_t::UART_FRAME_ERROR: + errors.add("UART: frame error"); + break; + case hardwareSerial_error_t::UART_PARITY_ERROR: + errors.add("UART: pairity error"); + break; + } +} + /** * @brief Applies the buffered console frame to the corresponding device state. * * Invalid command and payload-length combinations set the device status to red. */ -void ConsoleHandler::parse() const +void ConsoleHandler::parse() { desk.setRx(std::span{bufferRx}.subspan(0U, lengthRx + 1U)); if (stateRx == State::BUTTON_DOWN && lengthRx == 1U) @@ -91,7 +137,7 @@ void ConsoleHandler::parse() const } else if (stateRx == State::CONSOLE && lengthRx == 1U) { - desk.setErrorTx(bufferRx.at(1U)); + setErrorTx(bufferRx.at(1U)); } else if (stateRx == State::INITIALIZATION && lengthRx == 1U) { @@ -99,7 +145,7 @@ void ConsoleHandler::parse() const } else if (stateRx == State::LIN && lengthRx == 1U) { - desk.setErrorLin(bufferRx.at(1U)); + setErrorLin(bufferRx.at(1U)); } else if (stateRx == State::NODE8 && lengthRx == 1U) { @@ -137,6 +183,13 @@ void ConsoleHandler::parse() const } } +void ConsoleHandler::reset() +{ + errorLin = 0U; + errorRx = hardwareSerial_error_t::UART_NO_ERROR; + errorTx = 0U; +} + /** * @brief Sends a command without an associated value. * @@ -164,6 +217,26 @@ void ConsoleHandler::send(Command command, uint16_t value) write(payload); } +void ConsoleHandler::setErrorLin(uint8_t flags) +{ + if (flags != errorLin) + { + errorLin = flags; + desk.setPending(); + } + desk.statusRed(); +} + +void ConsoleHandler::setErrorTx(uint8_t flags) +{ + if (flags != errorTx) + { + errorTx = flags; + desk.setPending(); + } + desk.statusRed(); +} + /** * @brief Transmits a framed payload through the secondary serial interface. * @@ -187,7 +260,12 @@ void ConsoleHandler::write(std::span payload) void ConsoleHandler::onReceiveError(hardwareSerial_error_t error) { ESP_LOGW("hardwareSerial_error_t", "%u", static_cast(error)); - desk.setErrorRx(error); + if (error != errorRx) + { + errorRx = error; + desk.setPending(); + } + desk.statusRed(); } #endif // ARDUINO_ARCH_ESP32 diff --git a/src/esp/DeskService.cpp b/src/esp/DeskService.cpp index 7cd9c11..2f87956 100644 --- a/src/esp/DeskService.cpp +++ b/src/esp/DeskService.cpp @@ -125,32 +125,35 @@ void DeskService::handle() } /** - * @brief Converts an encoder value to the corresponding physical desk height. - * - * @param encoder Encoder value to convert. - * @return Physical desk height corresponding to the encoder value. + * @brief Disables device processing and disconnects serial and MQTT services. */ -float DeskService::decode(float encoder) +void DeskService::safeMode() { - return ((encoder - static_cast(ReferenceHeight::encoderLow)) * - (ReferenceHeight::heightHigh - ReferenceHeight::heightLow) / - static_cast(ReferenceHeight::encoderHigh - ReferenceHeight::encoderLow)) + - ReferenceHeight::heightLow; + process = false; + Serial1.end(); + mqtt.disconnect(); } /** - * @brief Converts a physical desk height to its corresponding encoder value. - * - * @param height Physical desk height. - * @return uint16_t Encoder value mapped from the configured height range. + * @brief Persists encoder, preset, and output-enable state to non-volatile storage. */ -uint16_t DeskService::encode(float height) +void DeskService::save() { - return static_cast( - lroundf(((height - ReferenceHeight::heightLow) * - static_cast(ReferenceHeight::encoderHigh - ReferenceHeight::encoderLow) / - (ReferenceHeight::heightHigh - ReferenceHeight::heightLow)) + - static_cast(ReferenceHeight::encoderLow))); + nvs_handle_t handle{}; + if (nvs_open("bekant", nvs_open_mode_t::NVS_READWRITE, &handle) == ESP_OK) + { + saved = true; + nvs_set_u16(handle, "8", encoder8); + nvs_set_u16(handle, "9", encoder9); + nvs_set_u16(handle, "h", presetHigh); + nvs_set_u16(handle, "l", presetLow); + nvs_set_u8(handle, "oe", static_cast(enable)); + if (nvs_commit(handle) != ESP_OK) + { + saved = false; + } + nvs_close(handle); + } } /** @@ -225,38 +228,6 @@ void DeskService::request(JsonObjectConst doc) } } -/** - * @brief Disables device processing and disconnects serial and MQTT services. - */ -void DeskService::safeMode() -{ - process = false; - Serial1.end(); - mqtt.disconnect(); -} - -/** - * @brief Persists encoder, preset, and output-enable state to non-volatile storage. - */ -void DeskService::save() -{ - nvs_handle_t handle{}; - if (nvs_open("bekant", nvs_open_mode_t::NVS_READWRITE, &handle) == ESP_OK) - { - saved = true; - nvs_set_u16(handle, "8", encoder8); - nvs_set_u16(handle, "9", encoder9); - nvs_set_u16(handle, "h", presetHigh); - nvs_set_u16(handle, "l", presetLow); - nvs_set_u8(handle, "oe", static_cast(enable)); - if (nvs_commit(handle) != ESP_OK) - { - saved = false; - } - nvs_close(handle); - } -} - /** * @brief Publishes the current device state and telemetry. * @@ -270,7 +241,8 @@ void DeskService::transmit(JsonDocument &doc) doc["encoders"][0U].set(encoder8); doc["encoders"][1U].set(encoder9); JsonArray errors{doc["errors"].to()}; - toErrorArray(errors); + getErrors(errors); + console.getErrors(errors); const float leg8{decode(static_cast(encoder8))}; const float leg9{decode(static_cast(encoder9))}; doc["legs"][0U].set(leg8); @@ -313,6 +285,71 @@ void DeskService::transmit(JsonDocument &doc) mqtt.transmit(doc); } +/** + * @brief Converts an encoder value to the corresponding physical desk height. + * + * @param encoder Encoder value to convert. + * @return Physical desk height corresponding to the encoder value. + */ +float DeskService::decode(float encoder) +{ + return ((encoder - static_cast(ReferenceHeight::encoderLow)) * + (ReferenceHeight::heightHigh - ReferenceHeight::heightLow) / + static_cast(ReferenceHeight::encoderHigh - ReferenceHeight::encoderLow)) + + ReferenceHeight::heightLow; +} + +/** + * @brief Converts a physical desk height to its corresponding encoder value. + * + * @param height Physical desk height. + * @return uint16_t Encoder value mapped from the configured height range. + */ +uint16_t DeskService::encode(float height) +{ + return static_cast( + lroundf(((height - ReferenceHeight::heightLow) * + static_cast(ReferenceHeight::encoderHigh - ReferenceHeight::encoderLow) / + (ReferenceHeight::heightHigh - ReferenceHeight::heightLow)) + + static_cast(ReferenceHeight::encoderLow))); +} + +void DeskService::getErrors(JsonArray &list) +{ + if ((errorInit & 0b1U) != 0U) + { + list.add("probe A: no response"); + } + if ((errorInit & (0b1U << 1U)) != 0U) + { + list.add("probe A: checksum mismatch"); + } + if ((errorInit & (0b1U << 2U)) != 0U) + { + list.add("probe B: no response"); + } + if ((errorInit & (0b1U << 3U)) != 0U) + { + list.add("probe B: checksum mismatch"); + } + if ((error8 & 0b1U) != 0U) + { + list.add("node 8: no response"); + } + if ((error8 & (0b1U << 1U)) != 0U) + { + list.add("node 8: checksum mismatch"); + } + if ((error9 & 0b1U) != 0U) + { + list.add("node 9: no response"); + } + if ((error9 & (0b1U << 1U)) != 0U) + { + list.add("node 9: checksum mismatch"); + } +} + /** * @brief Updates the down-button state and requests a state publication. * @@ -401,36 +438,6 @@ void DeskService::setErrorInit(uint8_t flags) statusRed(); } -void DeskService::setErrorLin(uint8_t flags) -{ - if (flags != errorLin) - { - errorLin = flags; - pending = true; - } - statusRed(); -} - -void DeskService::setErrorRx(hardwareSerial_error_t flags) -{ - if (flags != errorRx) - { - errorRx = flags; - pending = true; - } - statusRed(); -} - -void DeskService::setErrorTx(uint8_t flags) -{ - if (flags != errorTx) - { - errorTx = flags; - pending = true; - } - statusRed(); -} - void DeskService::setNode8(uint16_t position, uint8_t state) { if (position != encoder8 && state != state8) @@ -519,6 +526,8 @@ void DeskService::setOutputEnable(bool state) #endif // PIN_OE } +void DeskService::setPending() { pending = true; } + /** * @brief Sets the high preset value and marks the device state for persistence and publication. * @@ -639,73 +648,6 @@ std::string DeskService::toHex(std::span payload) return hex; } -void DeskService::toErrorArray(JsonArray &list) -{ - if ((errorInit & 0b1U) != 0U) - { - list.add("probe A: no response"); - } - if ((errorInit & (0b1U << 1U)) != 0U) - { - list.add("probe A: checksum mismatch"); - } - if ((errorInit & (0b1U << 2U)) != 0U) - { - list.add("probe B: no response"); - } - if ((errorInit & (0b1U << 3U)) != 0U) - { - list.add("probe B: checksum mismatch"); - } - if ((error8 & 0b1U) != 0U) - { - list.add("node 8: no response"); - } - if ((error8 & (0b1U << 1U)) != 0U) - { - list.add("node 8: checksum mismatch"); - } - if ((error9 & 0b1U) != 0U) - { - list.add("node 9: no response"); - } - if ((error9 & (0b1U << 1U)) != 0U) - { - list.add("node 9: checksum mismatch"); - } - if ((errorLin & (0b1U << 3U)) != 0U) - { - list.add("USART0: data overrun"); - } - if ((errorLin & (0b1U << 4U)) != 0U) - { - list.add("USART0: frame error"); - } - if ((errorTx & (0b1U << 3U)) != 0U) - { - list.add("USART1: data overrun"); - } - if ((errorTx & (0b1U << 4U)) != 0U) - { - list.add("USART1: frame error"); - } - switch (errorRx) - { - case hardwareSerial_error_t::UART_BREAK_ERROR: - list.add("UART: break"); - break; - case hardwareSerial_error_t::UART_BUFFER_FULL_ERROR: - list.add("UART: buffer full"); - break; - case hardwareSerial_error_t::UART_FIFO_OVF_ERROR: - list.add("UART: FIFO overflow"); - break; - case hardwareSerial_error_t::UART_FRAME_ERROR: - list.add("UART: frame error"); - break; - } -} - /** * @brief Retrieves the latest firmware release version from GitHub. * @@ -807,11 +749,9 @@ void DeskService::onInterruptReset() desk.error8 = 0U; desk.error9 = 0U; desk.errorInit = 0U; - desk.errorLin = 0U; - desk.errorRx = hardwareSerial_error_t::UART_NO_ERROR; - desk.errorTx = 0U; desk.lengthRx = 0U; desk.lengthTx = 0U; + desk.console.reset(); desk.status.setNone(true); } else From a11cebf429ca2be2289bb9605b04d0214ea85b9a Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Thu, 10 Sep 2026 01:35:26 +0200 Subject: [PATCH 07/10] Add pairity check, in case of wiring errors --- src/avr/ConsoleHandler.cpp | 2 +- src/avr/LegHandler.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/avr/ConsoleHandler.cpp b/src/avr/ConsoleHandler.cpp index f8fde65..6d93569 100644 --- a/src/avr/ConsoleHandler.cpp +++ b/src/avr/ConsoleHandler.cpp @@ -15,7 +15,7 @@ void ConsoleHandler::handle() if (Serial1.available() != 0) { const unsigned char errors{UCSR1A}; // NOLINT(clang-analyzer-core.FixedAddressDereference) - if ((errors & ((0b1U << DOR1) | (0b1U << FE1))) != 0U) + if ((errors & ((0b1U << UPE1) | (0b1U << DOR1) | (0b1U << FE1))) != 0U) { send(State::CONSOLE, errors); } diff --git a/src/avr/LegHandler.cpp b/src/avr/LegHandler.cpp index 979e180..46c8927 100644 --- a/src/avr/LegHandler.cpp +++ b/src/avr/LegHandler.cpp @@ -140,7 +140,7 @@ int LegHandler::read(unsigned int &remainingTime) return -1; } const unsigned char errors{UCSR0A}; // NOLINT(clang-analyzer-core.FixedAddressDereference) - if ((errors & ((0b1U << DOR0) | (0b1U << FE0))) != 0U) + if ((errors & ((0b1U << UPE0) | (0b1U << DOR0) | (0b1U << FE0))) != 0U) { Serial1.write((1U << 4U) | static_cast(ConsoleHandler::State::LIN)); Serial1.write(errors); From dc5f01e94c0e1e8558a62352af1c9f07a5cf7900 Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Thu, 10 Sep 2026 02:24:50 +0200 Subject: [PATCH 08/10] Fix typo and add UART_NO_ERROR for completeness --- src/esp/ConsoleHandler.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/esp/ConsoleHandler.cpp b/src/esp/ConsoleHandler.cpp index 4c2eb3d..bdec643 100644 --- a/src/esp/ConsoleHandler.cpp +++ b/src/esp/ConsoleHandler.cpp @@ -101,6 +101,8 @@ void ConsoleHandler::getErrors(JsonArray &errors) } switch (errorRx) { + case hardwareSerial_error_t::UART_NO_ERROR: + break; case hardwareSerial_error_t::UART_BREAK_ERROR: errors.add("UART: break"); break; @@ -114,7 +116,7 @@ void ConsoleHandler::getErrors(JsonArray &errors) errors.add("UART: frame error"); break; case hardwareSerial_error_t::UART_PARITY_ERROR: - errors.add("UART: pairity error"); + errors.add("UART: parity error"); break; } } From f6202c62ba5c2b80729074e9916846a6cdff96d6 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 00:44:01 +0000 Subject: [PATCH 09/10] Document LIN and desk error handling APIs --- include/avr/LegHandler.h | 24 +++++++++++++++--- src/avr/ConsoleHandler.cpp | 3 ++- src/avr/LegHandler.cpp | 20 ++++++++++++++- src/esp/ConsoleHandler.cpp | 22 ++++++++++++++-- src/esp/DeskService.cpp | 43 ++++++++++++++++++++++++++++++++ src/esp/HomeAssistantHandler.cpp | 2 +- 6 files changed, 105 insertions(+), 9 deletions(-) diff --git a/include/avr/LegHandler.h b/include/avr/LegHandler.h index 5e81b4c..c8368d5 100644 --- a/include/avr/LegHandler.h +++ b/include/avr/LegHandler.h @@ -33,6 +33,12 @@ class LegHandler [[nodiscard]] int read(unsigned int &remainingTime); public: + /** + * Builds a LIN protected identifier from a frame identifier. + * + * @param identifier Frame identifier; only the lower six bits are used. + * @return The identifier with its parity bits in the upper two bits. + */ [[nodiscard]] static constexpr unsigned char getPid(unsigned char identifier) { identifier &= 0x3FU; @@ -91,12 +97,11 @@ class LegHandler } /** - * Receives a LIN response for the specified identifier. + * Requests a LIN response and reads its payload and checksum. * - * @param identifier LIN frame identifier to request. + * @param pid Protected identifier to transmit in the response header. * @param data Buffer to populate with the received payload. - * @return `true` if a complete response with a valid checksum is received, `false` on timeout or checksum - * failure. + * @return The received checksum byte, or `-1` if the response is incomplete. */ template int receiveResponse(unsigned char pid, unsigned char (&data)[N]) { @@ -135,6 +140,11 @@ class LegHandler return read(remainingTime); } + /** + * Sends a LIN diagnostic request with a classic checksum. + * + * @param data Diagnostic payload to transmit. + */ template void sendDiagnosticRequest(const unsigned char (&data)[N]) { static_assert(N <= 8U); @@ -146,6 +156,12 @@ class LegHandler Serial.flush(); } + /** + * Sends a LIN response with an enhanced checksum. + * + * @param pid Protected identifier to transmit. + * @param data Response payload to transmit. + */ template void sendResponse(unsigned char pid, const unsigned char (&data)[N]) { static_assert(N <= 8U); diff --git a/src/avr/ConsoleHandler.cpp b/src/avr/ConsoleHandler.cpp index 6d93569..f962e07 100644 --- a/src/avr/ConsoleHandler.cpp +++ b/src/avr/ConsoleHandler.cpp @@ -8,7 +8,8 @@ /** * @brief Buffers a serial command and parses it when its complete payload is received. * - * The first byte specifies the payload length and command identifier. + * The first byte specifies the payload length and command identifier. USART receive errors are reported before the + * byte is consumed. */ void ConsoleHandler::handle() { diff --git a/src/avr/LegHandler.cpp b/src/avr/LegHandler.cpp index 46c8927..f6de71e 100644 --- a/src/avr/LegHandler.cpp +++ b/src/avr/LegHandler.cpp @@ -10,7 +10,8 @@ /** * @brief Initializes the LIN interface and configures the connected device. * - * @return true if initialization and device detection succeed, false otherwise. + * @return `0` on success; bits 0 and 1 report no response and checksum mismatch for probe A, and bits 2 and 3 + * report the same conditions for probe B. */ unsigned char LegHandler::begin() { @@ -106,6 +107,13 @@ unsigned char LegHandler::begin() return 0U; } +/** + * @brief Requests a desk-leg status frame and validates its checksum. + * + * @param pid Protected identifier of the leg status frame. + * @param node Buffer for the two-byte encoder position and one-byte state. + * @return `0` for a valid response, bit 0 for an incomplete response, or bit 1 for a checksum mismatch. + */ unsigned char LegHandler::getLeg(unsigned char pid, unsigned char (&node)[3U]) { const int checksum{receiveResponse(pid, node)}; @@ -123,6 +131,8 @@ unsigned char LegHandler::getLeg(unsigned char pid, unsigned char (&node)[3U]) /** * @brief Reads a serial byte within the available time budget. * + * Reports parity, data-overrun, and frame errors to the console before returning the byte. + * * @param remainingTime Maximum wait time in microseconds; reduced by the time spent waiting. * @return int The received byte, or -1 if no byte is available before the timeout. */ @@ -148,6 +158,9 @@ int LegHandler::read(unsigned int &remainingTime) return Serial.read(); } +/** + * @brief Requests and discards a diagnostic response within one frame time budget. + */ void LegHandler::requestDiscardResponse() { serialBreak(); @@ -182,6 +195,11 @@ void LegHandler::sendCommand(Command command, unsigned int position) sendResponse(getPid(0x12U), packet); } +/** + * @brief Sends a LIN response header followed by the complemented protected identifier. + * + * @param pid Protected identifier to transmit. + */ void LegHandler::sendResponse(unsigned char pid) { serialBreak(); diff --git a/src/esp/ConsoleHandler.cpp b/src/esp/ConsoleHandler.cpp index bdec643..cd89d9d 100644 --- a/src/esp/ConsoleHandler.cpp +++ b/src/esp/ConsoleHandler.cpp @@ -20,7 +20,7 @@ void ConsoleHandler::begin() } /** - * @brief Processes available secondary-serial data, pending UART errors, or primary-serial input. + * @brief Processes a secondary-serial byte or forwards primary-serial input when none is available. */ void ConsoleHandler::handle() { @@ -73,6 +73,11 @@ void ConsoleHandler::forward() } } +/** + * @brief Appends descriptions of recorded serial communication errors. + * + * @param errors JSON array to append to. + */ void ConsoleHandler::getErrors(JsonArray &errors) { if ((errorLin & (0b1U << 2U)) != 0U) @@ -185,6 +190,9 @@ void ConsoleHandler::parse() } } +/** + * @brief Clears all recorded serial communication errors. + */ void ConsoleHandler::reset() { errorLin = 0U; @@ -219,6 +227,11 @@ void ConsoleHandler::send(Command command, uint16_t value) write(payload); } +/** + * @brief Records LIN USART error flags and signals an error state. + * + * @param flags AVR USART status flags. + */ void ConsoleHandler::setErrorLin(uint8_t flags) { if (flags != errorLin) @@ -229,6 +242,11 @@ void ConsoleHandler::setErrorLin(uint8_t flags) desk.statusRed(); } +/** + * @brief Records console USART error flags and signals an error state. + * + * @param flags AVR USART status flags. + */ void ConsoleHandler::setErrorTx(uint8_t flags) { if (flags != errorTx) @@ -255,7 +273,7 @@ void ConsoleHandler::write(std::span payload) } /** - * @brief Stores the latest hardware serial error for processing. + * @brief Records the latest hardware serial receive error and signals an error state. * * @param error Hardware serial error to store. */ diff --git a/src/esp/DeskService.cpp b/src/esp/DeskService.cpp index 2f87956..3b1adf1 100644 --- a/src/esp/DeskService.cpp +++ b/src/esp/DeskService.cpp @@ -314,6 +314,11 @@ uint16_t DeskService::encode(float height) static_cast(ReferenceHeight::encoderLow))); } +/** + * @brief Appends descriptions of recorded leg initialization and communication errors. + * + * @param list JSON array to append to. + */ void DeskService::getErrors(JsonArray &list) { if ((errorInit & 0b1U) != 0U) @@ -408,6 +413,11 @@ void DeskService::setDriveUp(bool state) #endif // PIN_TPUP } +/** + * @brief Records node 8 communication errors and signals an error state. + * + * @param flags Error bitmask with bit 0 for no response and bit 1 for a checksum mismatch. + */ void DeskService::setError8(uint8_t flags) { if (flags != error8) @@ -418,6 +428,11 @@ void DeskService::setError8(uint8_t flags) statusRed(); } +/** + * @brief Records node 9 communication errors and signals an error state. + * + * @param flags Error bitmask with bit 0 for no response and bit 1 for a checksum mismatch. + */ void DeskService::setError9(uint8_t flags) { if (flags != error9) @@ -428,6 +443,12 @@ void DeskService::setError9(uint8_t flags) statusRed(); } +/** + * @brief Records leg initialization errors and signals an error state. + * + * @param flags Error bitmask with response and checksum failures in bits 0 and 1 for probe A and bits 2 and 3 for + * probe B. + */ void DeskService::setErrorInit(uint8_t flags) { if (flags != errorInit) @@ -438,6 +459,14 @@ void DeskService::setErrorInit(uint8_t flags) statusRed(); } +/** + * @brief Updates node 8 data and clears its communication error. + * + * Changes are marked for publication, and position changes are also marked for persistence. + * + * @param position Encoder position reported by the node. + * @param state State reported by the node. + */ void DeskService::setNode8(uint16_t position, uint8_t state) { if (position != encoder8 && state != state8) @@ -471,6 +500,14 @@ void DeskService::setNode8(uint16_t position, uint8_t state) } } +/** + * @brief Updates node 9 data and clears its communication error. + * + * Changes are marked for publication, and position changes are also marked for persistence. + * + * @param position Encoder position reported by the node. + * @param state State reported by the node. + */ void DeskService::setNode9(uint16_t position, uint8_t state) { if (position != encoder9 && state != state9) @@ -526,6 +563,9 @@ void DeskService::setOutputEnable(bool state) #endif // PIN_OE } +/** + * @brief Requests device-state publication on the next service cycle. + */ void DeskService::setPending() { pending = true; } /** @@ -740,6 +780,9 @@ void DeskService::onInterruptDown() /** * @brief Updates the reset state and status indicator from the reset input. + * + * Clears recorded communication errors and removes captured serial payloads from subsequent publications while reset + * is asserted. */ void DeskService::onInterruptReset() { diff --git a/src/esp/HomeAssistantHandler.cpp b/src/esp/HomeAssistantHandler.cpp index dcbb7fc..7c22eb3 100644 --- a/src/esp/HomeAssistantHandler.cpp +++ b/src/esp/HomeAssistantHandler.cpp @@ -250,7 +250,7 @@ void HomeAssistantHandler::configuration() * @brief Configures diagnostic entities for Home Assistant discovery. * * Adds diagnostic controls and sensors for calibration, encoder data, firmware - * versions, positional offset, serial activity, temperature, Wi-Fi signal + * versions, communication errors, positional offset, serial activity, temperature, Wi-Fi signal * strength, and optionally button inputs and power-supply voltage. Diagnostic * entities are categorized and selected hardware-specific entities are disabled * by default. From 2794aaa4d02d6ad14fc8dd9ee5e0afdd76b51a82 Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Thu, 10 Sep 2026 03:25:23 +0200 Subject: [PATCH 10/10] Address error state clearing --- include/avr/ControllerService.h | 2 ++ src/avr/ControllerService.cpp | 22 +++++++++++++--------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/include/avr/ControllerService.h b/include/avr/ControllerService.h index c691631..dfec33b 100644 --- a/include/avr/ControllerService.h +++ b/include/avr/ControllerService.h @@ -46,6 +46,8 @@ class ControllerService static ControllerService &getInstance(); private: + bool error8{false}; + bool error9{false}; bool pending{false}; unsigned char state8{0U}; diff --git a/src/avr/ControllerService.cpp b/src/avr/ControllerService.cpp index 32c521d..0119892 100644 --- a/src/avr/ControllerService.cpp +++ b/src/avr/ControllerService.cpp @@ -67,11 +67,11 @@ bool ControllerService::read() constexpr unsigned char empty[3U]{0U, 0U, 0U}; leg.sendResponse(LegHandler::getPid(0x11U), empty); unsigned char node[3U]{}; - const unsigned char error8{leg.getLeg(LegHandler::getPid(0x8U), node)}; - if (error8 == 0U) + const unsigned char status8{leg.getLeg(LegHandler::getPid(0x8U), node)}; + if (status8 == 0U) { const unsigned int _encoder8{static_cast(node[0U]) | static_cast(node[1U]) << 8U}; - if (_encoder8 != encoder8 || state8 != node[2U]) + if (_encoder8 != encoder8 || state8 != node[2U] || error8) { if (_encoder8 != encoder8) { @@ -79,18 +79,20 @@ bool ControllerService::read() } encoder8 = _encoder8; state8 = node[2U]; + error8 = false; console.send(ConsoleHandler::State::NODE8, node); } } else { - console.send(ConsoleHandler::State::NODE8, error8); + error8 = true; + console.send(ConsoleHandler::State::NODE8, status8); } - const unsigned char error9{leg.getLeg(LegHandler::getPid(0x9U), node)}; - if (error9 == 0U) + const unsigned char status9{leg.getLeg(LegHandler::getPid(0x9U), node)}; + if (status9 == 0U) { const unsigned int _encoder9{static_cast(node[0U]) | static_cast(node[1U]) << 8U}; - if (_encoder9 != encoder9 || state9 != node[2U]) + if (_encoder9 != encoder9 || state9 != node[2U] || error9) { if (_encoder9 != encoder9) { @@ -98,14 +100,16 @@ bool ControllerService::read() } encoder9 = _encoder9; state9 = node[2U]; + error9 = false; console.send(ConsoleHandler::State::NODE9, node); } } else { - console.send(ConsoleHandler::State::NODE9, error9); + error9 = true; + console.send(ConsoleHandler::State::NODE9, status9); } - if (error8 != 0U || error9 != 0U) + if (status8 != 0U || status9 != 0U) { if (pending) {