Skip to content
Snippets Groups Projects
_transmission.h 9.32 KiB
Newer Older
/********************************************
 *          Data Prep / Validation          *
 ********************************************/

/**
 * Return ESP Module ID, based on MAC address
 * Source: https://arduino.stackexchange.com/a/58678
 *
 * 2021-11-07 - This method proved unreliable, switched to casting ESP.getChipID() as a string instead
 *
 * @return Unique ID for sensor
 */
String getSensorUID() {
  String sensorID = "EMIEI-"; // [E]nviornmental [M]onitoring [I]ndepenet of [E]xisting [I]nfrastructure
  sensorID.concat(String(ESP.getChipId()));
  return sensorID;
}



/********************************************
 *                  Timings                 *
 ********************************************/

/**
 * Introduce random delay, with optional minimum delay time
 *
 * @param minSecods Min number of secods to wait, default 0 (i.e.) any random delay between MIN and MAX
 */
void waitRandomDelay(int minSeconds = 0) {
  int randDelay = (minSeconds * 1000) + random(MIN_RAND_DELAY, MAX_RAND_DELAY);
  if (DEBUG){ Serial.println("[i] Delay for " + String(randDelay) + "ms"); }
  delay(randDelay);
}



/********************************************
 *                Transmission              *
 ********************************************/

/**
 * Send JSON Payload over LoRa
 *
 * @param payload JSON Payload to be send
 */
void sendJsonPayloadWithLoRa(DynamicJsonDocument payload) {
  Serial.println("[+] Transmit - Payload");
  LoRa.beginPacket();
  serializeJson(payload, LoRa);
  LoRa.endPacket();
}



/********************************************
 *                 Recieving                *
 ********************************************/

/**
 * Listen on Lora for other messages, returns true if no messages detected within [listenDuration] seconds
 * Use prior to transmissions to avoid interruption of other messages
 *
 * @param int listenDuration How long to listen on Lora RX in Seconds
 * @returns boolean If messages detected within listenDuration  
 */
boolean clearToSend(int listenDuration) {  
  int listenUntil = now() + listenDuration;

  // Listen until timeout expires
  while (listenUntil >= now()) {
    int packetSize = LoRa.parsePacket();
    
    if (packetSize) {
      Serial.println("[-] TX Busy - Not Clear To Send");
      return false; // Other message heard on Rx, infer that we can not transmit just now. 
    }

    delay(5);
  }

  return true; // We didn't hear anything, so continue
}

/**
 * Listen for messages on TX, expecting JSON
 * 
 * @param listenDuration How long to listen on Lora RX in Seconds
 * @return StaticJsonDocument|null RX Payload in JSON, null on timeout reached or error
 */
StaticJsonDocument<1024> listenForAndConsumeMessage(int listenDuration) {
  int listenUntil = now() + listenDuration;
  StaticJsonDocument<1024> json;

  // Listen until timeout expires
  while (listenUntil >= now()) {
    int packetSize = LoRa.parsePacket();

    if (!packetSize) {
      delay(3);
      continue;
    }

    // Read in packet, ensure we only bring in anything after { and before } inclusive
    String incoming = "";
    char temp;
    
    while (LoRa.available()) {
      temp = (char)LoRa.read();

      if (incoming.length() == 0 && temp == '{') { // Opening {
        incoming = "{";

      } else if (temp == '}') { // Closing } 
        incoming.concat("}");
        break;

      } else if (incoming.length() > 0) { // Anything else that's valid
        incoming.concat(temp);
      }
    }

    Serial.println(incoming);
    // Deserialize - TODO Error Handling https://arduinojson.org/v6/api/json/deserializejson/
    deserializeJson(json, incoming);
    break;
  }

  return json;
}

// TODO DOC
boolean listenForTxPayloadAccept(int listenDuration, int messageID) {
  Serial.println("[.] Transmit - Payload - Listening for Ack");

  StaticJsonDocument<1024> json = listenForAndConsumeMessage(listenDuration);

  // Timeout, likely TX Payload was not recieved or ack got lost.
  if (json.isNull()) {
    Serial.println("[-] Transmit - Payload - Ack Timeout Reached - Assuming Message Was Not Delivered\n\n");
    return false;
  }

  const bool ackStatus = json["ackStatus"];
  const String authIsForUid = json["uid"];
  const int authIsForMessageID = json["replyMsgID"];
  const String gatewayUid = json["gatewayUid"];
  

  // Verify Sensor UID Match
  if (authIsForUid == getSensorUID()
      && authIsForMessageID == messageID) {

    Serial.println("[+] Transmit - Payload - Ack Recieved: " + String(ackStatus) + "\n");
    
    if (ackStatus) {  // It all worked :)
      // Check for any updated config values
      const int newTxAfterNReadings = json["txAfterNReadings"];
      if (newTxAfterNReadings != NULL && newTxAfterNReadings != TX_AFTER_N_READINGS) {
        TX_AFTER_N_READINGS = newTxAfterNReadings;
        Serial.println("[+] Tx After N Readings Updated, now: " + String(TX_AFTER_N_READINGS) + " samples");
      }
      
      const int newPollingFrequency = json["pollingFrequency"];
      if (newPollingFrequency != NULL && newPollingFrequency != POLLING_FREQUENCY) {
        POLLING_FREQUENCY = newPollingFrequency;
        Serial.println("[+] Polling Frequency Updated, now: " + String(POLLING_FREQUENCY) + "ms");
      }
      
      return true; 
    }

    // TODO Retransmit, recover, etc
    Serial.println("[-] Transmit - Payload - Ack Failed - TODO Setup Retransmission");
    return false;
  }

  // TODO Else UID Mis-Match so we wait for next message
  Serial.println("[-] Transmit - Payload - Ack Message ID or Sensor ID Mis-Match");
  return false;
}



/********************************************
 *                  TX Hello                *
 ********************************************/

 /**
 * Send short "clear to send?" packet via LoRa
 * Tx - "I've got data to send!" + UID + how long to reserve radio
 * Note: Gateway has final say over TX_RESERVATION_TIME and may override requested value
 *
 * @param messageID ID of this message
 */
void sendTxHello(int messageID) {
  if (DEBUG){ Serial.println("[+] Transmit - \"Hello\""); }
  
  DynamicJsonDocument txHello(2048);
  txHello["uid"]                = getSensorUID();
  txHello["reservationTime"]    = TX_RESERVATION_TIME;
  txHello["messageID"]          = messageID;

  sendJsonPayloadWithLoRa(txHello);
}

/**
 * Listen for response to TxHello for [listenDuration] seconds.
 * Upon recieving response, check Sensor UID & Message ID match and return true if we have clear to transmit payload.
 *
 * @param listenDuration    How long to listen on Lora RX (Seconds)
 * @param messageID         ID of message we are expecting to receive
 * @return                  Clear to Transmit (Y/N)
 */
boolean listenForTxHelloAccept(int listenDuration, int messageID) {
  if (DEBUG){ Serial.println("[+] Transmit - \"Hello\" - Listening for \"Hello\" Ack & Clear to Send"); }

  StaticJsonDocument<1024> json = listenForAndConsumeMessage(listenDuration);

  // Timeout, likely TX Hello was not recieved or ack got lost
  if (json.isNull()) {
    Serial.println("[-] Transmit - \"Hello\" - Timeout while waiting for Clear to Send\n\n");
    return false;
  }

  const bool okToTransmit = json["okTransmit"];
  const String authIsForUid = json["uid"];
  const int authIsForMessageID = json["messageID"];
  const String gatewayUid = json["gatewayUid"];

  // Verify txHello.okTransmit is True and Sensor UID & Message IDs Match
  if (DEBUG) {
    if (authIsForUid == getSensorUID()) { 
      Serial.println("[+] Transmit - \"Hello\" - Sensor UID Match!"); 
    } else { 
      Serial.println("[-] Transmit - \"Hello\" - Sensor UID Mis-Match! " + String(authIsForUid) + " vs " + String(getSensorUID())); 
    }

    if (authIsForMessageID == messageID) {
      Serial.println("[+] Transmit - \"Hello\" - Message ID Match!"); 
    } else { 
      Serial.println("[-] Transmit - \"Hello\" - MessageID Mis-Match!"); 
    }
  }

  // Ok To Trasmit, Sensor UID Match & Message ID Match
  bool clearToSend = (okToTransmit 
                      && authIsForUid == getSensorUID() 
                      && authIsForMessageID == messageID);

  if (DEBUG) {
    if (clearToSend) {
      Serial.println("[+] Transmit - \"Hello\" - Recieved \"Clear to Transmit\" Payload");
    } else {
      Serial.println("[-] Transmit - \"Hello\" - Can Not Transmit At This Time");
    }
  }

  return clearToSend;
}

















// TODO - Finish Acks & Naks
boolean transmitData(DynamicJsonDocument payload) {

  // TODO MAX_RETRIES

  // Listen for other communication
  //    Rx - Listen for other messages, if no messages heard then continue
  if (!clearToSend(0.5)) {
    waitRandomDelay(); // Wait for short time before retrying
    while (!clearToSend(0.5)) { waitRandomDelay(); } // TODO MAX_TIMEOUT
  }

  // Send TX Hello
  sendTxHello(msgCount);

  // Await TX Hello Auth - Expect: Timeout | Not Auth | Accept + Clear To Transmit
  if (!listenForTxHelloAccept(TX_RESERVATION_TIME * 4, msgCount)) { 
    return false; // Can't transmit just now, we will retry
  }

  // Send TX Payload
  sendJsonPayloadWithLoRa(payload);

  // Await TX Payload Ack - Expect: Timeout | Nack | Accept + Data Match
  if (!listenForTxPayloadAccept(2, msgCount)) {
    return false; // TODO Ack Failed Setup a retry here!
  }

  // TODO Update Sensor Config

  // TODO Clear values & Continue
  //resetCounters(); TODO
  Serial.println("Packet Sent Succesfully\n");
  Serial.println("---------------+++++-------------------");
  msgCount++;

  return true;
}