firmware: prevent RS485 echo starvation
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
- Added standalone XIAO ESP32-C6 BNO086/MAX-M10S sensor-node firmware and a versioned RS485 telemetry contract without changing Cargo `/api/v1` or JBD/NimBLE behavior.
|
- Added standalone XIAO ESP32-C6 BNO086/MAX-M10S sensor-node firmware and a versioned RS485 telemetry contract without changing Cargo `/api/v1` or JBD/NimBLE behavior.
|
||||||
- Added XIAO-side persistent level calibration with stability checking and RS485 start/clear commands; no dashboard behavior changed.
|
- Added XIAO-side persistent level calibration with stability checking and RS485 start/clear commands; no dashboard behavior changed.
|
||||||
|
- Prevented echoed or oversized RS485 frames from starving the XIAO sensor loop.
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
String commandBuffer;
|
String commandBuffer;
|
||||||
|
bool discardCommandUntilNewline = false;
|
||||||
void sendJson(HardwareSerial& serial, JsonDocument& doc) {
|
void sendJson(HardwareSerial& serial, JsonDocument& doc) {
|
||||||
digitalWrite(RS485_DE_PIN, HIGH); delayMicroseconds(20);
|
digitalWrite(RS485_DE_PIN, HIGH); delayMicroseconds(20);
|
||||||
serializeJson(doc, serial); serial.write('\n'); serial.flush();
|
serializeJson(doc, serial); serial.write('\n'); serial.flush();
|
||||||
@@ -63,12 +64,25 @@ void publishCalibrationResult(HardwareSerial& serial, const String& result) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void pollRs485Commands(HardwareSerial& serial, const ImuState& imu) {
|
void pollRs485Commands(HardwareSerial& serial, const ImuState& imu) {
|
||||||
while (serial.available()) {
|
// Bound RX work so an electrically echoed or noisy bus cannot starve sensors.
|
||||||
|
int bytesRemaining = RS485_COMMAND_MAX_LENGTH * 2;
|
||||||
|
while (serial.available() && bytesRemaining-- > 0) {
|
||||||
char c = serial.read();
|
char c = serial.read();
|
||||||
if (c == '\r') continue;
|
if (c == '\r') continue;
|
||||||
if (c != '\n') {
|
if (c != '\n') {
|
||||||
if (commandBuffer.length() < RS485_COMMAND_MAX_LENGTH) commandBuffer += c;
|
if (discardCommandUntilNewline) continue;
|
||||||
else commandBuffer = "";
|
if (commandBuffer.length() < RS485_COMMAND_MAX_LENGTH) {
|
||||||
|
commandBuffer += c;
|
||||||
|
} else {
|
||||||
|
commandBuffer = "";
|
||||||
|
discardCommandUntilNewline = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discardCommandUntilNewline) {
|
||||||
|
discardCommandUntilNewline = false;
|
||||||
|
commandBuffer = "";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +91,8 @@ void pollRs485Commands(HardwareSerial& serial, const ImuState& imu) {
|
|||||||
commandBuffer = "";
|
commandBuffer = "";
|
||||||
if (error) { publishCalibrationResult(serial, "invalid_json"); continue; }
|
if (error) { publishCalibrationResult(serial, "invalid_json"); continue; }
|
||||||
String type = request["type"] | "";
|
String type = request["type"] | "";
|
||||||
|
// Some RS485 boards echo transmitted frames into RX. Ignore our own output.
|
||||||
|
if (type == "sensor_status" || type == "calibration_response") continue;
|
||||||
if (type == "calibrate_level") {
|
if (type == "calibrate_level") {
|
||||||
publishCalibrationResult(serial, startLevelCalibration(imu) ? "started" : "imu_unavailable");
|
publishCalibrationResult(serial, startLevelCalibration(imu) ? "started" : "imu_unavailable");
|
||||||
} else if (type == "clear_level_calibration") {
|
} else if (type == "clear_level_calibration") {
|
||||||
|
|||||||
@@ -56,6 +56,12 @@ def test_level_calibration_is_persistent_and_non_blocking():
|
|||||||
assert "updateLevelCalibration" in loop
|
assert "updateLevelCalibration" in loop
|
||||||
assert "delay(5000)" not in calibration
|
assert "delay(5000)" not in calibration
|
||||||
|
|
||||||
|
def test_rs485_echo_cannot_starve_sensor_loop():
|
||||||
|
transport = source("rs485_transport.cpp")
|
||||||
|
assert "bytesRemaining" in transport
|
||||||
|
assert "discardCommandUntilNewline" in transport
|
||||||
|
assert 'type == "sensor_status"' in transport
|
||||||
|
|
||||||
def test_node_does_not_take_cargo_or_ble_authority():
|
def test_node_does_not_take_cargo_or_ble_authority():
|
||||||
combined = "\n".join(p.read_text() for p in NODE.glob("*.*"))
|
combined = "\n".join(p.read_text() for p in NODE.glob("*.*"))
|
||||||
assert "NimBLE" not in combined and "JBD" not in combined and "server.on(" not in combined
|
assert "NimBLE" not in combined and "JBD" not in combined and "server.on(" not in combined
|
||||||
|
|||||||
Reference in New Issue
Block a user