diff --git a/receiver/receiver.ino b/receiver/receiver.ino index 4ded327..a9ca7d4 100644 --- a/receiver/receiver.ino +++ b/receiver/receiver.ino @@ -17,11 +17,19 @@ * TX GPIO17 -> RX GPIO16 on transmitter * GND -> GND (mandatory - common ground) * - * LED circuits (see LED_1_PIN / LED_2_PIN below): + * LED circuits - both on THIS board, returning to THIS board's GND: * GPIO25 -> resistor -> LED pair -> GND * GPIO26 -> resistor -> LED pair -> GND * Pins output 3.3V, not 3V. ~12mA per pin is comfortable, 40mA is the hard * limit. Anything drawing more than ~20mA per circuit needs a transistor. + * Do not tap the LED return off the UART ground wire to the other board - + * that reference needs to stay clean. + * + * STARTUP ORDER: + * Wake BOTH hubs with their green buttons and check both are blinking, + * THEN power this board. hub1 is only serviced once hub0 is connected, so a + * sleeping hub0 blocks the whole sequence. Hubs stop advertising after a + * couple of minutes idle. * * ARMS ARE POSITION CONTROLLED: * The triggers set an ANGLE, not a power level. Trigger released holds the @@ -125,6 +133,10 @@ static const unsigned long HUB_MIN_GAP_MS = 25; // per hub, ~40 cmd/sec static const unsigned long LINK_TIMEOUT_MS = 400; // failsafe static const unsigned long RECONNECT_GAP_MS = 2000; +// A Legoino scan that never finds its hub expires silently, leaving the hub +// stuck waiting forever. This is how long we give it before starting over. +static const unsigned long SCAN_TIMEOUT_MS = 12000; + static const int LINK_RX_PIN = 16; static const int LINK_TX_PIN = 17; static const long LINK_BAUD = 115200; @@ -139,7 +151,8 @@ struct HubLink { const char *label; bool initialised; unsigned long retryAt; - unsigned long lastCmdAt; // per-hub rate limit, shared across its ports + unsigned long lastCmdAt; // per-hub rate limit, shared across ports + unsigned long scanExpiresAt; // when to give up on the current scan }; struct PortState { @@ -161,8 +174,8 @@ struct Frame { // ================================================================= globals static HubLink gHubs[2] = { - {Lpf2Hub(), HUB0_ADDR, "hub0", false, 0, 0}, - {Lpf2Hub(), HUB1_ADDR, "hub1", false, 0, 0}, + {Lpf2Hub(), HUB0_ADDR, "hub0", false, 0, 0, 0}, + {Lpf2Hub(), HUB1_ADDR, "hub1", false, 0, 0, 0}, }; // Indexes follow physical ports, not functions: @@ -280,6 +293,10 @@ static void stopEverything() { // immediately afterwards isConnected() and isConnecting() are both still // false. Guarding on those alone re-enters NimBLEDevice::init() thousands of // times a second and the Bluetooth controller aborts. +// +// The scanExpiresAt deadline exists because a scan that finds nothing just +// ends quietly - isConnecting() never goes true, so without a timeout the hub +// sits on a dead scan until the board is power cycled. static void serviceHub(HubLink &hl) { if (hl.hub.isConnected()) return; @@ -296,10 +313,18 @@ static void serviceHub(HubLink &hl) { return; } + if (hl.initialised && millis() >= hl.scanExpiresAt) { + Serial.printf("[%s] scan timed out, restarting\n", hl.label); + hl.initialised = false; + hl.retryAt = millis() + RECONNECT_GAP_MS; + return; + } + if (!hl.initialised && millis() >= hl.retryAt) { Serial.printf("[%s] scanning for %s\n", hl.label, hl.addr); hl.hub.init(std::string(hl.addr)); hl.initialised = true; + hl.scanExpiresAt = millis() + SCAN_TIMEOUT_MS; } } @@ -421,7 +446,7 @@ void setup() { digitalWrite(LED_2_PIN, LOW); Serial.println("LEGO hub receiver starting (Johnny 5 Evolved)"); - Serial.println("Both arms must be DOWN before hub1 connects"); + Serial.println("Wake both hubs first. Both arms must be DOWN."); } void loop() {