diff --git a/test/Pi_Receiver.py b/test/Pi_Receiver.py new file mode 100644 index 0000000000000000000000000000000000000000..6495104dde00af5606fe5adf6f36d90b03a791bc --- /dev/null +++ b/test/Pi_Receiver.py @@ -0,0 +1,363 @@ +#!/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()