diff --git a/ESP8266_Transmitter/_transmission.h b/ESP8266_Transmitter/_transmission.h
index fdfd00828685edb6f694986a6e160033e7354552..6495c3dd5b8f9ba69fa730c22ff12ecfd1d450e5 100644
--- a/ESP8266_Transmitter/_transmission.h
+++ b/ESP8266_Transmitter/_transmission.h
@@ -155,7 +155,7 @@ boolean listenForTxPayloadAccept(int listenDuration, int messageID) {
 
   // Verify Sensor UID Match & Message ID
   if (authIsForUid != getSensorUID() || authIsForMessageID != messageID) {
-    // TODO Else UID Mis-Match so we wait for next message
+    // UID Mis-Match so we wait for next message
     if (DEBUG) { Serial.println("[-] Transmit - Payload - Ack Message ID or Sensor ID Mis-Match"); }
     return false;
   }
diff --git a/RaspberryPi_Receiver/Pi_Receiver.py b/RaspberryPi_Receiver/Pi_Receiver.py
index f1c37e10e4de7ed03510815eeda526d6b78e5502..bcbd745fbd14a17daf995a84da4f04e583d331d9 100644
--- a/RaspberryPi_Receiver/Pi_Receiver.py
+++ b/RaspberryPi_Receiver/Pi_Receiver.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 
-'''
+"""
     =========================================================================================
  
         CS408 Environmental Monitoring Independent of Existing Infrastructure
@@ -28,141 +28,199 @@
         https://github.com/raspberrypi-tw/lora-sx1276
 
     =========================================================================================
-'''
 
-# Usage: python Pi_Receiver.py -f 433 -b BW125 -s 7
-
-from argparse import ArgumentError
-import sys 
-sys.path.insert(0, '../')     
+    Usage: python Pi_Receiver.py -f 433 -b BW125 -s 7
 
+    =========================================================================================
+"""
+import sys
 from time import sleep, time
 import json
-import requests # pip3 install requests
-import random
-
+import requests  # pip3 install requests
 from SX127x.LoRa import *
 from SX127x.LoRaArgumentParser import LoRaArgumentParser
 from SX127x.board_config import BOARD
 import SX127x.packer as packer
-
 import secrets
 
+sys.path.insert(0, '../')
+
+# SYSTEM CONFIG
 DEBUG = 2
 API_URL = "https://emiei.4oh4.co/api"
-MAX_TX_RESERVATION_TIME = 2 # Seconds
+MAX_TX_RESERVATION_TIME = 2  # Seconds
 
+# Configure Pi for LoRa
 BOARD.setup()
+parser = LoRaArgumentParser("Continuous LoRa receiver.")
 
-parser = LoRaArgumentParser("Continous LoRa receiver.")
 
-# Recived from Sensors when they have data to send
 class RxHello(object):
-    def __init__(self, uid, reservationTime, messageID):
+    """JSON Object received from sensor when they transmit a "TX Hello" request to send
+    NOTE: Variable names must match JSON object from sensor!
+    """
+
+    def __init__(self, gatewayUID, uid, reservationTime, messageID):
+        """Initialise object, we will pass JSON object from which these values are retrieved
+        :param gatewayUID: UID Of this gateway
+        :param uid: Sensor UID, of the form EMIEI-... where ... is a number
+        :param reservationTime: Requested period of time to transmit message, may be overridden by ourselves
+        :param messageID: ID of message coming from sensor
+        """
+        self.gatewayUid = gatewayUID
         self.uid = uid
         self.reservationTime = min(reservationTime, MAX_TX_RESERVATION_TIME)
         self.messageID = messageID
-        self.gatewayUid = getserial()
         self.okTransmit = False
 
-    def setOK(self, okToTransmit):
-        self.okTransmit = okToTransmit
+    def set_ok(self, okTransmit):
+        """Is sensor node OK to transmit?
+        :param okTransmit: Boolean Value, True/False
+        """
+        self.okTransmit = okTransmit
 
     # Python Object to JSON Object
-    def ToJson(self):
+    def to_json(self):
         return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True)
 
+
 class SensorResponse(object):
+    """JSON Object received from sensor when they transmit a "TX Payload"
+    NOTE: Variable names must match JSON object from sensor!
+    """
+
     def __init__(self, sensorMetadata, data):
         self.gatewayMetadata = GatewayMetadata()
         self.sensorMetadata = SensorMetadata(**sensorMetadata)
         self.sensorReading = SensorReading(**data)
 
     # Python Object to JSON Object
-    def ToJson(self):
+    def to_json(self):
         return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True)
 
-    def sendToApi(self):
+    def send_to_api(self):
         headers = {'Content-Type': 'Application/json'}
-        response = requests.post(API_URL + '/sensor/reading', data=self.ToJson(), headers=headers)#, verify=False) # Using self signed cert for now, sort later!
+        response = requests.post(API_URL + '/sensor/reading', data=self.to_json(), headers=headers)
+
+        # If using self-signed cert turn off signature validation
+        # response = requests.post(API_URL + '/sensor/reading', data=self.ToJson(), headers=headers, verify=False) #
+
         if DEBUG > 1:
-            print(self.ToJson())
-            print(response) # Ensure 200 Response!
+            print(self.to_json())
+            print(response)  # TODO Ensure 200 Response!
         return
 
+
 class GatewayMetadata(object):
+    """JSON Object received from sensor when they transmit a "TX Payload". Component within SensorResponse(object)
+    NOTE: Variable names must match JSON object from sensor!
+    """
     def __init__(self):
-        self.gatewayUID = getserial()
+        self.gatewayUID = get_gateway_serial()
         self.apiKey = secrets.GATEWAY_API_KEY
 
+
 class SensorMetadata(object):
+    """JSON Object received from sensor when they transmit a "TX Payload". Component within SensorResponse(object)
+    NOTE: Variable names must match JSON object from sensor!
+    """
     def __init__(self, uid, messageID, samplePeriod):
         self.uid = uid
         self.messageID = messageID
         self.samplePeriod = samplePeriod
         self.sampleTime = round(time())
 
+
 class SensorReading(object):
+    """JSON Object received from sensor when they transmit a "TX Payload". Component within SensorResponse(object)
+    NOTE: Variable names must match JSON object from sensor!
+    """
     def __init__(self, ppm, sht, co2):
         self.ppm = PPM(**ppm)
         self.sht = SHT(**sht)
         self.co2 = Co2(**co2)
 
+
 class PPM(object):
+    """JSON Object received from sensor when they transmit a "TX Payload".
+    Component within SensorResponse(object).SensorReading
+    NOTE: Variable names must match JSON object from sensor!
+    """
     def __init__(self, p10, p25, p100):
         self.p10 = p10
         self.p25 = p25
         self.p100 = p100
 
+
 class SHT(object):
+    """JSON Object received from sensor when they transmit a "TX Payload".
+    Component within SensorResponse(object).SensorReading
+    NOTE: Variable names must match JSON object from sensor!
+    """
     def __init__(self, temperature, humidity):
         self.temperature = temperature
         self.humidity = humidity
 
+
 class Co2(object):
+    """JSON Object received from sensor when they transmit a "TX Payload".
+    Component within SensorResponse(object).SensorReading
+    NOTE: Variable names must match JSON object from sensor!
+    """
     def __init__(self, co2):
         self.co2 = co2
 
-class API():
-    def getSensorConfig(self, sensorUID):
-        headers = {'Content-Type': 'Application/json'}
-        response = requests.post(API_URL + '/' + secrets.API_KEY + '/sensor/getConfig/' + sensorUID, headers=headers)
 
-        if (response.status_code != 200):
-            return None
+def get_sensor_config_from_api(sensor_uid):
+    headers = {'Content-Type': 'Application/json'}
+    response = requests.post(API_URL + '/' + secrets.API_KEY + '/sensor/getConfig/' + sensor_uid, headers=headers)
 
-        return json.loads(response.content)
+    if response.status_code != 200:
+        return None
 
-class Reply():
+    return json.loads(response.content)
+
+
+class Reply:
+    """JSON Object transmitted back to sensor when they transmit a "TX Payload".
+    Include ack status, and any config updates, along with sensor ID, gateway ID & message ID
+    NOTE: Variable names must match JSON object from sensor!
+    """
     ackStatus = False
 
     def __init__(self, remoteSensorID, replyMsgID):
-        self.gatewayUid = getserial()
+        self.gatewayUid = get_gateway_serial()
         self.uid = remoteSensorID
         self.replyMsgID = replyMsgID
         self.txAfterNReadings = None
         self.pollingFrequency = None
 
-    def setAckStatus(self, ackStatus):
-        self.ackStatus = ackStatus
+    def set_ack_status(self, ack_status):
+        """Set Acknowledgement Status
+        :param ack_status: True if gateway correctly processed value, False otherwise
+        """
+        self.ackStatus = ack_status
 
-    def updateConfigTxAfterNReadings(self, _nReadings):
-        self.txAfterNReadings = _nReadings
+    def update_config_tx_after_n_readings(self, _n_readings):
+        self.txAfterNReadings = _n_readings
 
     # Provide value in ms
-    def updateConfigPollingFrequency(self, _pollingFrequency):
-        self.pollingFrequency = _pollingFrequency
+    def update_config_polling_frequency(self, _polling_frequency):
+        self.pollingFrequency = _polling_frequency
 
-    def ToJson(self):
+    def to_json(self):
         return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True)
 
+
 class LoRaReceiver(LoRa):
+    """ See SX127x Library
+    """
     def __init__(self, verbose=False):
         super(LoRaReceiver, self).__init__(verbose)
-        self._id = "Base-01"
+        self._id = get_gateway_serial()
         self.set_mode(MODE.SLEEP)
         self.set_dio_mapping([0] * 6)
-        self.rxHelloQueue = RxHelloQueue() # Track transmission requests
+        self.rx_hello_queue = RxHelloQueue()  # Track transmission requests
 
     def on_rx_done(self):
         self.clear_irq_flags(RxDone=1)
@@ -170,7 +228,7 @@ class LoRaReceiver(LoRa):
 
         # Parse our data here!
         data = ''.join([chr(c) for c in payload])
-        handleData(self.rxHelloQueue, data)
+        handle_incoming_lora_packet(self.rx_hello_queue, data)
 
         self.reset_ptr_rx()
         self.set_mode(MODE.RXCONT)
@@ -178,7 +236,7 @@ class LoRaReceiver(LoRa):
     def on_tx_done(self):
         print("\nTxDone")
         # set RX
-        self.set_dio_mapping([0,0,0,0,0,0])    # RX
+        self.set_dio_mapping([0, 0, 0, 0, 0, 0])  # RX
         sleep(1)
         self.reset_ptr_rx()
         self.set_mode(MODE.RXCONT)
@@ -208,11 +266,10 @@ class LoRaReceiver(LoRa):
         self.reset_ptr_rx()
         self.set_mode(MODE.RXCONT)
         while True:
-            if (self.get_mode() == MODE.TX):
+            if self.get_mode() == MODE.TX:
                 rssi_value = self.get_rssi_value()
                 status = self.get_modem_status()
 
-                # if DEBUG > 1:
                 sys.stdout.flush()
                 sys.stdout.write("\r%d %d %d" % (rssi_value, status['rx_ongoing'], status['modem_clear']))
 
@@ -223,119 +280,123 @@ class LoRaReceiver(LoRa):
         self.set_mode(MODE.TX)
 
 
-class RxHelloQueue():
+def transmit_object_with_lora(obj):
+    if DEBUG > 1:
+        print(obj.to_json())
+
+    _length, _payload = packer.Pack_Str(obj.to_json())
+
+    payload = [int(hex(c), 0) for c in _payload]
+
+    sleep(0.2)  # Slight delay before transmitting, else too quick for sensor to start listening
+    loraReceiver.transmit(payload)
+    sleep(0.2)
+
+
+class RxHelloQueue:
+    """Keep track of what messages we have incoming and when their transmission window expires
+    Used to accept / reject "TX Hello" requests
+    """
     def __init__(self):
         self.reserved = False
         self.sensorID = None
         self.reservedUntil = None
 
-
-    def hasReservationExpired(self):
+    def has_reservation_expired(self):
+        """Returns true when there is no longer any scheduled transmission due
+        Returns false otherwise
+        """
         print("Time Now: ", time(), ", Reserved Until: ", self.reservedUntil)
+
         if self.reserved and self.reservedUntil < time():
             self.reserved = False
             return True
-        
-        return False
-
-
-    def canAccept(self):
-        self.hasReservationExpired()
-
-        if not self.reserved:
-            return True
 
-        # TODO Additional Logic Here!
         return False
 
+    def can_accept(self):
+        self.has_reservation_expired()
+        return not self.reserved
 
-    def acceptNew(self, sensorID, reservationDuration):
-        if not self.canAccept:
+    def accept_new(self, sensor_id, reservation_duration):
+        if not self.can_accept:
             return False
-        
+
         self.reserved = True
-        self.sensorID = sensorID
-        self.reservedUntil = time() + reservationDuration + 1
-
-# Source: https://www.raspberrypi-spy.co.uk/2012/09/getting-your-raspberry-pi-serial-number-using-python/
-def getserial():
-  # Extract serial from cpuinfo file
-  cpuserial = "0000000000000000"
-  try:
-    f = open('/proc/cpuinfo','r')
-    for line in f:
-      if line[0:6]=='Serial':
-        cpuserial = line[10:26]
-    f.close()
-  except:
-    cpuserial = "ERROR000000000"
- 
-  return cpuserial
+        self.sensorID = sensor_id
+        self.reservedUntil = time() + reservation_duration + 1
 
-# Upon succesfully receiving a message, send back an ack
-def ackMsg(sensorResponse):
-    data = Reply(sensorResponse.sensorMetadata.uid, sensorResponse.sensorMetadata.messageID)
-    data.setAckStatus(True)
 
-    # Retrieve up-to-date sensor config from API & Transmit back to sensor
-    updated_sensor_config = API().getSensorConfig(sensorResponse.sensorMetadata.uid)
-    if updated_sensor_config is not None:
-        data.updateConfigPollingFrequency(updated_sensor_config['pollingFrequency'])
-        data.updateConfigTxAfterNReadings(updated_sensor_config['txAfterNReadings'])
+def get_gateway_serial():
+    """Extract serial number from cpu info file
+    Source: https://www.raspberrypi-spy.co.uk/2012/09/getting-your-raspberry-pi-serial-number-using-python/
+    """
+    cpu_serial = "0000000000000000"
+    try:
+        f = open('/proc/cpuinfo', 'r')
+        for line in f:
+            if line[0:6] == 'Serial':
+                cpu_serial = line[10:26]
+        f.close()
 
-    if DEBUG > 1:
-        print(data.ToJson())
+    except Exception:
+        cpu_serial = "ERROR000000000"
 
-    _length, _payload = packer.Pack_Str( data.ToJson() )
+    return cpu_serial
 
-    payload = [int(hex(c), 0) for c in _payload]
 
-    sleep(0.2) # Slight delay before transmitting, else too quick for sensor to start listening
-    loraReceiver.transmit(payload)
+# Upon successfully receiving a message, send back an ack
+def ack_message(sensor_response):
+    data = Reply(sensor_response.sensorMetadata.uid, sensor_response.sensorMetadata.messageID)
+    data.set_ack_status(True)
+
+    # Retrieve up-to-date sensor config from API & Transmit back to sensor
+    updated_sensor_config = get_sensor_config_from_api(sensor_response.sensorMetadata.uid)
+    if updated_sensor_config is not None:
+        data.update_config_polling_frequency(updated_sensor_config['pollingFrequency'])
+        data.update_config_tx_after_n_readings(updated_sensor_config['txAfterNReadings'])
+
+    transmit_object_with_lora(data)
     return
 
+
 # When a sensor has data to transmit, it will send a TxHello. 
 # Pick up that message here and decide if we can accept the message at this time.
-def handleRxHello(rxHelloQueue, data):
+def handle_rx_hello(rx_hello_queue, data):
     print("\n[?] Check for RX Hello")
     if DEBUG > 1:
         print("\nRaw data: %s" % data)
 
     try:
-        rxHelloJson = json.loads(data)
-        rxHello = RxHello(**rxHelloJson)
-        print("    RX Hello Confirmed!")
+        rx_hello_json = json.loads(data)
+        rx_hello = RxHello(get_gateway_serial(), **rx_hello_json)
 
-    except Exception as e:
+    except Exception:
         print("    Not RX Hello")
         return False
 
-    # TODO Check i'm not expecting any methods within rxHello.reservationTime seconds
-    if not rxHelloQueue.canAccept():
-        rxHello.setOK(False)
-        print("[-] RX Hello \"No\" Reply Sent - Reserved by %s for %i seconds" % (rxHelloQueue.sensorID,  rxHelloQueue.reservedUntil - time()))
-    
+    print("    RX Hello Confirmed!")
+
+    # Check I'm not expecting any methods within rx_hello.reservationTime seconds
+    if not rx_hello_queue.can_accept():
+        rx_hello.set_ok(False)
+        print("[-] RX Hello \"No\" Reply Sent - Reserved by %s for %i seconds"
+              % (rx_hello_queue.sensorID, rx_hello_queue.reservedUntil - time()))
+
     else:
-        rxHelloQueue.acceptNew(rxHello.uid, rxHello.reservationTime)
+        rx_hello_queue.accept_new(rx_hello.uid, rx_hello.reservationTime)
 
         # Send "OK" + UID
-        rxHello.setOK(True) # We are happy for this sensor to send it's data
-        print("[+] RX Hello \"OK\" Reply Sent - %s is permitted to send for %d seconds" % (rxHello.uid, rxHello.reservationTime))
-    
-    if DEBUG > 1:
-       print(rxHello.ToJson())
+        rx_hello.set_ok(True)  # We are happy for this sensor to send its data
+        print("[+] RX Hello \"OK\" Reply Sent - %s is permitted to send for %d seconds"
+              % (rx_hello.uid, rx_hello.reservationTime))
 
-    _length, _payload = packer.Pack_Str( rxHello.ToJson() ) # Send OK Back
-    payload = [int(hex(c), 0) for c in _payload]
-
-    sleep(0.2) # Slight delay before transmitting, else too quick for sensor to start listening
-    loraReceiver.transmit(payload)
-    sleep(0.2)
+    transmit_object_with_lora(rx_hello)
     return True
 
 
 # handle SensorResponse data packet
-def handleSensorResponsePacket(data):
+def handle_sensor_response_packet(data):
     print("\n[?] Check for Sensor Response Data")
     if DEBUG > 1:
         print("\nRaw data: %s" % data)
@@ -345,62 +406,55 @@ def handleSensorResponsePacket(data):
         p = SensorResponse(**parsed)
 
     except Exception as e:
-        print("[-] Unable to Parse response, ignoring") # TODO Error handling, log increased error rates etc
+        print("[-] Unable to Parse response, ignoring")
         if DEBUG > 1:
             print("\tE: %e" % e)
         return False
 
     print("    Sensor Response Data Confirmed!")
-    
-    if DEBUG > 1:
-        print("    Received Payload: %s" % p.ToJson())
-
-    # TODO Validate response is valid and non-corrupt
 
-    # TODO Ack / process here
+    if DEBUG > 1:
+        print("    Received Payload: %s" % p.to_json())
 
     # TODO Transmit ACK
     print("    Sending Sensor Response Ack")
-    ackMsg(p)
+    ack_message(p)
     print("[+] Sensor Response Ack Sent")
 
-    # TODO Process response?
+    # TODO Process response? Rabbit MQ?
 
-    # TODO Send To API
+    # Send Data To API
     print("    Sending Data to API")
-    p.sendToApi()
+    p.send_to_api()
     print("[+] Data Sent to API")
 
     sleep(0.2)
     return True
 
 
-
 # Parse LoRa response, validate, save / transmit
-def handleData(rxHelloQueue, data):
+def handle_incoming_lora_packet(rx_hello_queue, data):
     print("\n===================\n[i] Packet Incoming")
 
-    # Handle TxHello Transmission
-    #       Rx - "I've got data to send!" + UID (From Sensor)
-    #       If not expecting any messages, continue
-    #       Tx - "OK" + UID (Back to Sensor) + TX_Auth_ID
-    #
-    #       <Do not Tx for 2s>
-    if (handleRxHello(rxHelloQueue, data)): # Handled all OK
+    """  Handle TX Hello Transmission
+          Rx - "I've got data to send!" + UID (From Sensor)
+          
+          If not expecting any messages, permit payload send, else send tx auth denied 
+          Tx - "OK" + UID (Back to Sensor) + TX_Auth_ID
+     [or] TX - "NO" + UID (Back to Sensor)
+    """
+    if handle_rx_hello(rx_hello_queue, data):  # Handled all OK
         return
 
-    # Handle Payload Transmission
-    #       Rx - Packet + UID + Tx_Auth_ID (From Sensor)
-    #       Handle message, ack / nak appropriately
-    #       Tx - Ack + UID + Next_Send_Interval
-    #       Tx - Nak + UID
-    #
-    #       <Do not Tx for 3s>
-    if (handleSensorResponsePacket(data)):
+    """ Handle Payload Transmission
+          Rx - Packet + UID + Tx_Auth_ID (From Sensor)
+          
+          Handle message, ack / nak appropriately
+          Tx - Ack + UID + Next_Send_Interval
+     [or] Tx - Nak + UID
+    """
+    if handle_sensor_response_packet(data):
         return
-        
-
-    # T+3   Tx reserved window expires
 
 
 # Setup Receiver
@@ -409,23 +463,13 @@ args = parser.parse_args(loraReceiver)
 
 loraReceiver.set_mode(MODE.STDBY)
 loraReceiver.set_pa_config(pa_select=PA_SELECT.PA_BOOST)
-#loraReceiver.set_rx_crc(True)
-#loraReceiver.set_coding_rate(CODING_RATE.CR4_6)
-loraReceiver.set_pa_config(max_power=300, output_power=500) # Default max_power = 10.8dB, output_power  = 4.2dB
-#loraReceiver.set_lna_gain(GAIN.G1)
-#loraReceiver.set_implicit_header_mode(False)
-#loraReceiver.set_low_data_rate_optim(True)
-#loraReceiver.set_pa_ramp(PA_RAMP.RAMP_50_us)
-#loraReceiver.set_agc_auto_on(True)
-
+loraReceiver.set_pa_config(max_power=300, output_power=500)  # Default max_power = 10.8dB, output_power  = 4.2dB
 
-print("Power:")
-print(loraReceiver.get_pa_config(convert_dBm=True))
-
-# Go Go Go!
 print("[+] Receiver & API Gateway")
+print("[i] Power Levels:", loraReceiver.get_pa_config(convert_dBm=True))
+
 loraReceiver.set_agc_auto_on(1)
-assert(loraReceiver.get_agc_auto_on() == 1)
+assert (loraReceiver.get_agc_auto_on() == 1)
 
 try:
     loraReceiver.receive()
diff --git a/test/Pi_Receiver.py b/test/Pi_Receiver.py
deleted file mode 100644
index 6495104dde00af5606fe5adf6f36d90b03a791bc..0000000000000000000000000000000000000000
--- a/test/Pi_Receiver.py
+++ /dev/null
@@ -1,363 +0,0 @@
-#!/usr/bin/env python3
-
-'''
-    =========================================================================================
- 
-        CS408 Environmental Monitoring Independent of Existing Infrastructure
-        Copyright (C) 2021 Callum Inglis
-
-        This program is free software; you can redistribute it and/or modify
-        it under the terms of the GNU General Public License as published by
-        the Free Software Foundation; either version 2 of the License, or
-        (at your option) any later version.
-
-        This program is distributed in the hope that it will be useful,
-        but WITHOUT ANY WARRANTY; without even the implied warranty of
-        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-        GNU General Public License for more details.
-
-        You should have received a copy of the GNU General Public License along
-        with this program; if not, write to the Free Software Foundation, Inc.,
-        51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-        Contact: Callum.Inglis.2018(at)uni.strath.ac.uk
-
-    =========================================================================================
-
-    Libraries Used
-        https://github.com/raspberrypi-tw/lora-sx1276
-
-    =========================================================================================
-'''
-
-# Usage: python Pi_Receiver.py -f 433 -b BW125 -s 7
-
-from argparse import ArgumentError
-import sys 
-sys.path.insert(0, '../')     
-
-from time import sleep, time
-import json
-import requests # pip3 install requests
-import random
-
-from SX127x.LoRa import *
-from SX127x.LoRaArgumentParser import LoRaArgumentParser
-from SX127x.board_config import BOARD
-import SX127x.packer as packer
-
-DEBUG = 1
-API_URL = "http://environmental-monitoring.int.4oh4.co/api"
-
-BOARD.setup()
-
-parser = LoRaArgumentParser("Continous LoRa receiver.")
-
-# Recived from Sensors when they have data to send
-class RxHello(object):
-    def __init__(self, uid, reservationTime):
-        self.uid = uid
-        self.reservationTime = reservationTime
-        self.okToTransmit = False
-        self.txAuthID = None
-
-    def setOK(self, okToTransmit):
-        self.okToTransmit = okToTransmit
-
-    def setTxAuthID(self, txAuthID):
-        self.txAuthID = txAuthID
-
-    # Python Object to JSON Object
-    def ToJson(self):
-        return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
-
-class SensorResponse(object):
-    def __init__(self, sensorMetadata, data):
-        self.sensorMetadata = SensorMetadata(**sensorMetadata)
-        self.sensorReading = SensorReading(**data)
-
-    # Python Object to JSON Object
-    def ToJson(self):
-        return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
-
-    def sendToApi(self):
-        headers = {'Content-Type': 'Application/json'}
-        response = requests.post(API_URL + '/sensor/reading', data=self.ToJson(), headers=headers, verify=False) # Using self signed cert for now, sort later!
-        if DEBUG > 0:
-            print(response)
-        return
-
-class SensorMetadata(object):
-    def __init__(self, uid, messageID, samplePeriod):
-        self.uid = uid
-        self.messageID = messageID
-        self.samplePeriod = samplePeriod
-        self.sampleTime = round(time())
-
-class SensorReading(object):
-    def __init__(self, ppm, sht, co2):
-        self.ppm = PPM(**ppm)
-        self.sht = SHT(**sht)
-        self.co2 = Co2(**co2)
-
-class PPM(object):
-    def __init__(self, p10, p25, p100):
-        self.p10 = p10
-        self.p25 = p25
-        self.p100 = p100
-
-class SHT(object):
-    def __init__(self, temperature, humidity):
-        self.temperature = temperature
-        self.humidity = humidity
-
-class Co2(object):
-    def __init__(self, tmp):
-        self.tmp = "Coming Soon!"
-
-class Reply():
-    ackStatus = False
-
-    def __init__(self, remoteSensorID, replyMsgID):
-        self.messageID = random.randrange(10000, 99999)  
-        self.gatewayUid = getserial()
-        self.remoteSensorID = remoteSensorID
-        self.replyMsgID = replyMsgID
-
-    def setAckStatus(self, ackStatus):
-        self.ackStatus = ackStatus
-
-    def ToJson(self):
-        return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
-
-class LoRaReceiver(LoRa):
-    def __init__(self, verbose=False):
-        super(LoRaReceiver, self).__init__(verbose)
-        self._id = "Base-01"
-        self.set_mode(MODE.SLEEP)
-        self.set_dio_mapping([0] * 6)
-
-    def on_rx_done(self):
-        if DEBUG > 1:
-            print("\n[+] Rx Done")
-
-        self.clear_irq_flags(RxDone=1)
-        payload = self.read_payload(nocheck=True)
-
-        # Parse our data here!
-        data = ''.join([chr(c) for c in payload])
-        handleData(data)
-
-        self.reset_ptr_rx()
-        self.set_mode(MODE.RXCONT)
-
-    def on_tx_done(self):
-        print("\nTxDone")
-        # set RX
-        self.set_dio_mapping([0,0,0,0,0,0])    # RX
-        sleep(1)
-        self.reset_ptr_rx()
-        self.set_mode(MODE.RXCONT)
-        self.clear_irq_flags(RxDone=1)
-
-    def on_cad_done(self):
-        print("\non_CadDone")
-        print(self.get_irq_flags())
-
-    def on_rx_timeout(self):
-        print("\non_RxTimeout")
-        print(self.get_irq_flags())
-
-    def on_valid_header(self):
-        print("\non_ValidHeader")
-        print(self.get_irq_flags())
-
-    def on_payload_crc_error(self):
-        print("\non_PayloadCrcError")
-        print(self.get_irq_flags())
-
-    def on_fhss_change_channel(self):
-        print("\non_FhssChangeChannel")
-        print(self.get_irq_flags())
-
-    def receive(self):
-        self.reset_ptr_rx()
-        self.set_mode(MODE.RXCONT)
-        while True:
-            if (self.get_mode() == MODE.TX):
-                rssi_value = self.get_rssi_value()
-                status = self.get_modem_status()
-
-                if DEBUG > 1:
-                    sys.stdout.flush()
-                    sys.stdout.write("\r%d %d %d" % (rssi_value, status['rx_ongoing'], status['modem_clear']))
-
-    def transmit(self, _payload):
-        global args
-        self.tx_counter = 0
-        self.write_payload(_payload)
-        self.set_mode(MODE.TX)
-
-# Source: https://www.raspberrypi-spy.co.uk/2012/09/getting-your-raspberry-pi-serial-number-using-python/
-def getserial():
-  # Extract serial from cpuinfo file
-  cpuserial = "0000000000000000"
-  try:
-    f = open('/proc/cpuinfo','r')
-    for line in f:
-      if line[0:6]=='Serial':
-        cpuserial = line[10:26]
-    f.close()
-  except:
-    cpuserial = "ERROR000000000"
- 
-  return cpuserial
-
-# Upon succesfully receiving a message, send back an ack
-def ackMsg(sensorResponse):
-    data = Reply(sensorResponse.sensorMetadata.uid, sensorResponse.sensorMetadata.messageID)
-    data.setAckStatus(True)
-
-    print(data.ToJson())
-
-    _length, _payload = packer.Pack_Str( data.ToJson() )
-
-    payload = [int(hex(c), 0) for c in _payload]
-
-    loraReceiver.transmit(payload)
-    return
-
-# When a sensor has data to transmit, it will send a TxHello. 
-# Pick up that message here and decide if we can accept the message at this time.
-def handleRxHello(data):
-    timeBegin = time()
-    print("\n[?] Try RX Hello!")
-    if DEBUG > 1:
-        print("\nRaw data: %s" % data)
-
-    try:
-        rxHelloJson = json.loads(data)
-        rxHello = RxHello(**rxHelloJson)
-
-    except Exception as e:
-        print("[-] Not RX Hello\n")
-        return False
-
-    print("[+] RX Hello Confirmed!\n")
-    # TODO Check i'm not expecting any methods within rxHello.reservationTime seconds
-
-    # Send "OK" + UID + TX_AUTH_ID
-    rxHello.setOK(True) # We are happy for this sensor to send it's data
-    rxHello.setTxAuthID(123)
-    print(rxHello.ToJson())
-
-    _length, _payload = packer.Pack_Str( rxHello.ToJson() ) # Send OK Back Back
-    payload = [int(hex(c), 0) for c in _payload]
-
-    loraReceiver.transmit(payload)
-    print("\n[+] RX Hello \"OK\" Reply Sent\n")
-
-    # Sleep until out transmit block expires (2 Seconds)
-    #sleep(2 - (time() - timeBegin))
-    sleep(0.2)
-    return True
-
-
-# handle SensorResponse data packet
-def handleSensorResponsePacket(data):
-    timeBegin = time()
-    print("\n[?] Try Sensor Response Data!\nRaw data: %s" % data)
-
-    try:
-        parsed = json.loads(data)
-        p = SensorResponse(**parsed)
-
-    except Exception as e:
-        print("\n[-] Unable to Parse response, ignoring") # TODO Error handling, log increased error rates etc
-        if DEBUG > 1:
-            print("\tE: %e" % e)
-        return False
-
-    print("[+] Sensor Response Data Confirmed!\n")
-    print(p.ToJson())
-
-    # TODO Validate response is valid and non-corrupt
-
-    # TODO Ack / process here
-
-    # TODO Transmit ACK
-    print("\n[+] Sending Sensor Response Ack")
-    ackMsg(p)
-    print("[+] Sensor Response Ack Sent\n")
-
-    # Process response
-    print("Sensor ID: %s \tPPM 10: %s\n" % (p.sensorMetadata.uid, p.sensorReading.ppm.p10))
-
-    # TODO Send To API
-    print("\n[+] Sending Data to API")
-    p.sendToApi()
-    print("[+] Data Sent to API\n")
-
-    # Sleep until out transmit block expires (2 Seconds)
-    #sleep(2 - (time() - timeBegin))
-    sleep(0.2)
-
-
-
-# Parse LoRa response, validate, save / transmit
-def handleData(data):
-    # Handle TxHello Transmission
-    #       Rx - "I've got data to send!" + UID (From Sensor)
-    #       If not expecting any messages, continue
-    #       Tx - "OK" + UID (Back to Sensor) + TX_Auth_ID
-    #
-    #       <Do not Tx for 2s>
-    if (handleRxHello(data)): # Handled all OK
-        return
-
-    # Handle Payload Transmission
-    #       Rx - Packet + UID + Tx_Auth_ID (From Sensor)
-    #       Handle message, ack / nak appropriately
-    #       Tx - Ack + UID + Next_Send_Interval
-    #       Tx - Nak + UID
-    #
-    #       <Do not Tx for 3s>
-    if (handleSensorResponsePacket(data)):
-        return
-        
-
-    # T+3   Tx reserved window expires
-
-
-# Setup Receiver
-loraReceiver = LoRaReceiver(verbose=False)
-args = parser.parse_args(loraReceiver)
-
-loraReceiver.set_mode(MODE.STDBY)
-loraReceiver.set_pa_config(pa_select=1)
-#loraReceiver.set_rx_crc(True)
-#loraReceiver.set_coding_rate(CODING_RATE.CR4_6)
-#loraReceiver.set_pa_config(max_power=0, output_power=0)
-#loraReceiver.set_lna_gain(GAIN.G1)
-#loraReceiver.set_implicit_header_mode(False)
-#loraReceiver.set_low_data_rate_optim(True)
-#loraReceiver.set_pa_ramp(PA_RAMP.RAMP_50_us)
-#loraReceiver.set_agc_auto_on(True)
-
-# Go Go Go!
-print("[+] Receiver & API Gateway")
-assert(loraReceiver.get_agc_auto_on() == 1)
-
-try:
-    loraReceiver.receive()
-
-except KeyboardInterrupt:
-    sys.stdout.flush()
-    print("")
-    sys.stderr.write("KeyboardInterrupt\n")
-
-finally:
-    sys.stdout.flush()
-    print("")
-    loraReceiver.set_mode(MODE.SLEEP)
-    BOARD.teardown()