From 0e1b9336722e42c0b9606ebdee8dcbb5adb30f7c Mon Sep 17 00:00:00 2001
From: Joshua Saxby <joshua.a.saxby@gmail.com>
Date: Thu, 5 Mar 2020 18:17:41 +0000
Subject: [PATCH] Wrote master pairing method

---
 source/main.cpp | 177 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 174 insertions(+), 3 deletions(-)

diff --git a/source/main.cpp b/source/main.cpp
index 9059259..ded7c11 100755
--- a/source/main.cpp
+++ b/source/main.cpp
@@ -26,6 +26,13 @@
 #include "Transceiver.hpp"
 
 
+// special config macros
+#define MORSE_UNIT_MS 100 // One Morse "unit" is 100ms duration
+// micro:bit pin number and pin name --important: both of these must correspond!
+#define COMMS_PIN_NUMBER 1
+#define COMMS_PIN_NAME MICROBIT_PIN_P1
+
+
 // please import all names from my namespace into local scope!
 using namespace com_saxbophone;
 
@@ -33,7 +40,7 @@ using namespace com_saxbophone;
 MicroBit ubit;
 
 // a global MorseDecoder instance
-MorseDecoder<> decoder(100); // set one "unit" to be 100ms long
+MorseDecoder<> decoder(MORSE_UNIT_MS);
 
 
 /**
@@ -53,12 +60,176 @@ Role get_my_role() {
         a_pressed = ubit.buttonA.isPressed();
         b_pressed = ubit.buttonB.isPressed();
     // NOTE: this is bitwise-XOR but given the boolean nature, it's acceptable
-    } while(not (a_pressed xor b_pressed)); // until exactly one button pressed
+    } while (not (a_pressed xor b_pressed)); // until exactly one button pressed
     // because of the XOR exit condition of loop, if a_pressed = 0, b_pressed = 1
     return a_pressed ? Role::MASTER : Role::SLAVE; // A = MASTER, B = SLAVE
 }
 
 
+class Pairer {
+public:
+    /**
+     * @brief Default constructor
+     */
+    Pairer()
+        : synchronisation_timestamp(0) // zero-initialise to prevent any issues
+        {}
+
+    /**
+     * @brief Attempt to pair with another connected micro:bit
+     * @param my_role Whether we wish to be the Master or Slave for comms
+     * purposes
+     * @returns true if paired successfully
+     * @returns false if unable to pair successfully
+     * @note This is a blocking call!
+     */
+    bool attempt_pair(Role my_role) {
+        if (my_role == Role::MASTER) {
+            // attempt pair as Master
+            return this->pair_as_master();
+        } else if (my_role == Role::SLAVE) {
+            // TODO: Attempt pair as Slave
+            return false;
+        } else {
+            // invalid Role!
+            return false;
+        }
+    }
+
+private:
+    bool pair_as_master() {
+        int pin_state = ubit.io.pin[COMMS_PIN_NUMBER].getDigitalValue();
+        // clarify if someone else is driving the pin HI or if pin is invalid
+        switch (pin_state) {
+        case 0:
+            break; // this is good, it means no-one else is driving the pin
+        case 1:
+        /* DELIBERATE FALLTHROUGH */
+        case MICROBIT_NOT_SUPPORTED:
+            /*
+             * either someone else on the line thinks they're Master, or we've
+             * been configured with a pin that can't do Digital I/O. In either
+             * case, this is a failure condition.
+             */
+            return false;
+        }
+        // successful execution continues here
+        // wait for the pin to stay LO for 500ms
+        unsigned long start_time = ubit.systemTime();
+        unsigned long now_time = start_time;
+        do {
+            /*
+             * NOTE: this is a busy wait loop with no sleep()
+             * this is okay as we're deliberately allowing this function to
+             * block the entire system, it's documented as such and we also can
+             * only sleep to a 6ms precision, but we want to time as accurately
+             * as possible for the pairing process
+             */
+            now_time = ubit.systemTime();
+            pin_state = ubit.io.pin[COMMS_PIN_NUMBER].getDigitalValue();
+        } while (pin_state == 0 and (now_time - start_time) < 500);
+        // check to make sure we didn't break out of the loop because of HI pin
+        if (pin_state != 0) return false;
+        // there's definitely no-one else transmitting on the line, now we transmit
+        start_time = ubit.systemTime();
+        now_time = start_time;
+        ubit.io.pin[COMMS_PIN_NUMBER].setDigitalValue(1);
+        // wait 500ms
+        do {
+            /*
+             * NOTE: as with the previous loop, this is a busy wait loop
+             * it's particularly important that this one is accurately timed,
+             * as we expect ourself and the Slave to synchronise clocks on the
+             * falling edge of this pulse we send
+             */
+            now_time = ubit.systemTime();
+        } while ((now_time - start_time) < 500);
+        // bring line LO
+        ubit.io.pin[COMMS_PIN_NUMBER].setDigitalValue(0);
+        // now_time is the clock synchronisation timestamp
+        this->synchronisation_timestamp = now_time;
+        // now, wait up to 100ms for a reply from the Slave
+        this->busy_wait_on_pin(0, 100, pin_state, start_time, now_time);
+        // this time, check that the pin is HI --it must be HI, otherwise timeout
+        if (pin_state != 1) return false;
+        // now, Slave should keep the Line HI for at least 450ms
+        this->busy_wait_on_pin(1, 450, pin_state, start_time, now_time);
+        // if the pin is LO, then Slave didn't drive it long enough
+        if (pin_state == 0) return false;
+        // otherwise, pin is still HI --allow Slave up to 100ms to bring it LO
+        this->busy_wait_on_pin(1, 100, pin_state, start_time, end_time);
+        // if the pin is still HI, then Slave drove the pin for too long
+        if (pin_state == 1) return false;
+        // otherwise, all we need to do now is to wait for synchronisation
+        return this->await_synchronisation();
+    }
+
+    bool pair_as_slave();
+
+    /**
+     * @brief Performs a busy-wait loop, continuously checking the comms pin
+     * to make sure it is in a given state for a certain amount of time
+     * @details method returns once either the pin is no longer in the desired
+     * state, or if the given timeout has passed
+     * @param keep_state the desired state (HI = 1 or LO = 0) that the pin is to
+     * keep
+     * @param timeout the maximum amount of time that the method will detect the
+     * pin holding this state (and block) for
+     * @param pin_state[out] the last detected pin state will be stored here
+     * @param start_time[out] the system time that detection commenced (ms) will
+     * be stored here
+     * @param end_time[out] the system time that detection ended (ms) will be
+     * stored here
+     */
+    void busy_wait_on_pin(
+        int keep_state,
+        unsigned long timeout
+        int& pin_state,
+        unsigned long& start_time,
+        unsigned long& end_time
+    ) {
+        // initialise time counters
+        start_time = ubit.systemTime();
+        end_time = start_time;
+        // wait up to the specified timeout
+        do {
+            /*
+             * NOTE: this is a busy wait loop with no sleep()
+             * this is okay as we're deliberately allowing this function to
+             * block the entire system, it's documented as such and we also can
+             * only sleep to a 6ms precision, but we want to time as accurately
+             * as possible
+             */
+            end_time = ubit.systemTime();
+            pin_state = ubit.io.pin[COMMS_PIN_NUMBER].getDigitalValue();
+        } while (pin_state == keep_state and (end_time - start_time) < timeout);
+    }
+
+    /**
+     * @brief Blocks caller until 1250ms have elapsed since the stored
+     * synchronisation timestamp
+     * @returns true if the timestamp was in the future and the synchronisation
+     * point has been reached
+     * @returns false if the timestamp was not in the future (in this case,
+     return is immediate)
+     * @warn Don't call this if this->synchronisation_timestamp hasn't been set
+     * first!
+     */
+    bool await_synchronisation() {
+        // calculate synchronisation time and check it's in the future
+        unsigned long sync_time = this->synchronisation_timestamp + 1250;
+        unsigned long now_time = ubit.systemTime();
+        if (sync_time < now_time) return false; // can't do it, sync point in past
+        // busy wait loop to wait until synchronisation time is reached
+        do {
+            now_time = ubit.systemTime();
+        } while (now_time < sync_time);
+    }
+
+    unsigned long synchronisation_timestamp;
+};
+
+
 /*
  * a very simple class that tracks the button state as it changes and the
  * duration between changes
@@ -203,7 +374,7 @@ int main() {
         Role my_role = get_my_role(); // this call blocks and asks the user
 
         // try to pair and if unsuccessful, go back to top of loop to restart
-        if (not transceiver.pair(my_role)) continue;
+        if (not attempt_pair(my_role)) continue;
         // otherwise, if execution continues here then we're paired
 
         // for now, we only act on button presses if we're the Master
-- 
GitLab