Skip to content
Snippets Groups Projects
Commit 8f761057 authored by Callum Inglis's avatar Callum Inglis
Browse files

Update sensor config after check-in

parent adc61259
No related branches found
No related tags found
2 merge requests!4Api authentication,!3Api authentication
This commit is part of merge request !4. Comments created here will be created in the context of that merge request.
#!/usr/bin/env python3 #!/usr/bin/env python3
''' '''
========================================================================================= =========================================================================================
CS408 Environmental Monitoring Independent of Existing Infrastructure CS408 Environmental Monitoring Independent of Existing Infrastructure
Copyright (C) 2021 Callum Inglis Copyright (C) 2021 Callum Inglis
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or the Free Software Foundation; either version 2 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License along 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., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Contact: Callum.Inglis.2018(at)uni.strath.ac.uk Contact: Callum.Inglis.2018(at)uni.strath.ac.uk
========================================================================================= =========================================================================================
Libraries Used Libraries Used
https://github.com/raspberrypi-tw/lora-sx1276 https://github.com/raspberrypi-tw/lora-sx1276
========================================================================================= =========================================================================================
''' '''
# Usage: python Pi_Receiver.py -f 433 -b BW125 -s 7 # Usage: python Pi_Receiver.py -f 433 -b BW125 -s 7
from argparse import ArgumentError from argparse import ArgumentError
import sys import sys
sys.path.insert(0, '../') sys.path.insert(0, '../')
from time import sleep, time from time import sleep, time
import json import json
import requests # pip3 install requests import requests # pip3 install requests
import random import random
from SX127x.LoRa import * from SX127x.LoRa import *
from SX127x.LoRaArgumentParser import LoRaArgumentParser from SX127x.LoRaArgumentParser import LoRaArgumentParser
from SX127x.board_config import BOARD from SX127x.board_config import BOARD
import SX127x.packer as packer import SX127x.packer as packer
DEBUG = 1 DEBUG = 1
API_URL = "http://environmental-monitoring.int.4oh4.co/api" API_URL = "https://emiei.4oh4.co/api"
MAX_TX_RESERVATION_TIME = 2 # Seconds
BOARD.setup()
BOARD.setup()
parser = LoRaArgumentParser("Continous LoRa receiver.")
parser = LoRaArgumentParser("Continous LoRa receiver.")
# Recived from Sensors when they have data to send
class RxHello(object): # Recived from Sensors when they have data to send
def __init__(self, uid, reservationTime): class RxHello(object):
self.uid = uid def __init__(self, uid, reservationTime, messageID):
self.reservationTime = reservationTime self.uid = uid
self.okToTransmit = False self.reservationTime = min(reservationTime, MAX_TX_RESERVATION_TIME)
self.txAuthID = None self.messageID = messageID
self.gatewayUid = getserial()
def setOK(self, okToTransmit): self.okTransmit = False
self.okToTransmit = okToTransmit
def setOK(self, okToTransmit):
def setTxAuthID(self, txAuthID): self.okTransmit = okToTransmit
self.txAuthID = txAuthID
# Python Object to JSON Object
# Python Object to JSON Object def ToJson(self):
def ToJson(self): return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True)
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
class SensorResponse(object):
class SensorResponse(object): def __init__(self, sensorMetadata, data):
def __init__(self, sensorMetadata, data): self.sensorMetadata = SensorMetadata(**sensorMetadata)
self.sensorMetadata = SensorMetadata(**sensorMetadata) self.sensorReading = SensorReading(**data)
self.sensorReading = SensorReading(**data)
# Python Object to JSON Object
# Python Object to JSON Object def ToJson(self):
def ToJson(self): return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True)
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
def sendToApi(self):
def sendToApi(self): headers = {'Content-Type': 'Application/json'}
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.ToJson(), headers=headers, verify=False) # Using self signed cert for now, sort later! if DEBUG > 1:
if DEBUG > 0: print(response) # Ensure 200 Response!
print(response) return
return
class SensorMetadata(object):
class SensorMetadata(object): def __init__(self, uid, messageID, samplePeriod):
def __init__(self, uid, messageID, samplePeriod): self.uid = uid
self.uid = uid self.messageID = messageID
self.messageID = messageID self.samplePeriod = samplePeriod
self.samplePeriod = samplePeriod self.sampleTime = round(time())
self.sampleTime = round(time())
class SensorReading(object):
class SensorReading(object): def __init__(self, ppm, sht, co2):
def __init__(self, ppm, sht, co2): self.ppm = PPM(**ppm)
self.ppm = PPM(**ppm) self.sht = SHT(**sht)
self.sht = SHT(**sht) self.co2 = Co2(**co2)
self.co2 = Co2(**co2)
class PPM(object):
class PPM(object): def __init__(self, p10, p25, p100):
def __init__(self, p10, p25, p100): self.p10 = p10
self.p10 = p10 self.p25 = p25
self.p25 = p25 self.p100 = p100
self.p100 = p100
class SHT(object):
class SHT(object): def __init__(self, temperature, humidity):
def __init__(self, temperature, humidity): self.temperature = temperature
self.temperature = temperature self.humidity = humidity
self.humidity = humidity
class Co2(object):
class Co2(object): def __init__(self, tmp):
def __init__(self, tmp): self.tmp = "Coming Soon!"
self.tmp = "Coming Soon!"
class Reply():
class Reply(): ackStatus = False
ackStatus = False
def __init__(self, remoteSensorID, replyMsgID):
def __init__(self, remoteSensorID, replyMsgID): self.gatewayUid = getserial()
self.messageID = random.randrange(10000, 99999) self.uid = remoteSensorID
self.gatewayUid = getserial() self.replyMsgID = replyMsgID
self.remoteSensorID = remoteSensorID self.txAfterNReadings = None # We may update config of the sensor
self.replyMsgID = replyMsgID self.pollingFrequency = None # We may update config of the sensor
def setAckStatus(self, ackStatus): def setAckStatus(self, ackStatus):
self.ackStatus = ackStatus self.ackStatus = ackStatus
def ToJson(self): def updateConfigTxAfterNReadings(self, _nReadings):
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4) self.txAfterNReadings = _nReadings
class LoRaReceiver(LoRa): # Provide value in ms
def __init__(self, verbose=False): def updateConfigPollingFrequency(self, _pollingFrequency):
super(LoRaReceiver, self).__init__(verbose) self.pollingFrequency = _pollingFrequency
self._id = "Base-01"
self.set_mode(MODE.SLEEP) def ToJson(self):
self.set_dio_mapping([0] * 6) return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True)
def on_rx_done(self): class LoRaReceiver(LoRa):
if DEBUG > 1: def __init__(self, verbose=False):
print("\n[+] Rx Done") super(LoRaReceiver, self).__init__(verbose)
self._id = "Base-01"
self.clear_irq_flags(RxDone=1) self.set_mode(MODE.SLEEP)
payload = self.read_payload(nocheck=True) self.set_dio_mapping([0] * 6)
self.rxHelloQueue = RxHelloQueue() # Track transmission requests
# Parse our data here!
data = ''.join([chr(c) for c in payload]) def on_rx_done(self):
handleData(data) ###if DEBUG > 1:
### print("\n[+] Rx Done")
self.reset_ptr_rx()
self.set_mode(MODE.RXCONT) self.clear_irq_flags(RxDone=1)
payload = self.read_payload(nocheck=True)
def on_tx_done(self):
print("\nTxDone") # Parse our data here!
# set RX data = ''.join([chr(c) for c in payload])
self.set_dio_mapping([0,0,0,0,0,0]) # RX handleData(self.rxHelloQueue, data)
sleep(1)
self.reset_ptr_rx() self.reset_ptr_rx()
self.set_mode(MODE.RXCONT) self.set_mode(MODE.RXCONT)
self.clear_irq_flags(RxDone=1)
def on_tx_done(self):
def on_cad_done(self): print("\nTxDone")
print("\non_CadDone") # set RX
print(self.get_irq_flags()) self.set_dio_mapping([0,0,0,0,0,0]) # RX
sleep(1)
def on_rx_timeout(self): self.reset_ptr_rx()
print("\non_RxTimeout") self.set_mode(MODE.RXCONT)
print(self.get_irq_flags()) self.clear_irq_flags(RxDone=1)
def on_valid_header(self): def on_cad_done(self):
print("\non_ValidHeader") print("\non_CadDone")
print(self.get_irq_flags()) print(self.get_irq_flags())
def on_payload_crc_error(self): def on_rx_timeout(self):
print("\non_PayloadCrcError") print("\non_RxTimeout")
print(self.get_irq_flags()) print(self.get_irq_flags())
def on_fhss_change_channel(self): def on_valid_header(self):
print("\non_FhssChangeChannel") print("\non_ValidHeader")
print(self.get_irq_flags()) print(self.get_irq_flags())
def receive(self): def on_payload_crc_error(self):
self.reset_ptr_rx() print("\non_PayloadCrcError")
self.set_mode(MODE.RXCONT) print(self.get_irq_flags())
while True:
if (self.get_mode() == MODE.TX): def on_fhss_change_channel(self):
rssi_value = self.get_rssi_value() print("\non_FhssChangeChannel")
status = self.get_modem_status() print(self.get_irq_flags())
if DEBUG > 1: def receive(self):
sys.stdout.flush() self.reset_ptr_rx()
sys.stdout.write("\r%d %d %d" % (rssi_value, status['rx_ongoing'], status['modem_clear'])) self.set_mode(MODE.RXCONT)
while True:
def transmit(self, _payload): if (self.get_mode() == MODE.TX):
global args rssi_value = self.get_rssi_value()
self.tx_counter = 0 status = self.get_modem_status()
self.write_payload(_payload)
self.set_mode(MODE.TX) # if DEBUG > 1:
sys.stdout.flush()
# Source: https://www.raspberrypi-spy.co.uk/2012/09/getting-your-raspberry-pi-serial-number-using-python/ sys.stdout.write("\r%d %d %d" % (rssi_value, status['rx_ongoing'], status['modem_clear']))
def getserial():
# Extract serial from cpuinfo file def transmit(self, _payload):
cpuserial = "0000000000000000" global args
try: self.tx_counter = 0
f = open('/proc/cpuinfo','r') self.write_payload(_payload)
for line in f: self.set_mode(MODE.TX)
if line[0:6]=='Serial':
cpuserial = line[10:26]
f.close() class RxHelloQueue():
except: def __init__(self):
cpuserial = "ERROR000000000" self.reserved = False
self.sensorID = None
return cpuserial self.reservedUntil = None
# Upon succesfully receiving a message, send back an ack
def ackMsg(sensorResponse): def hasReservationExpired(self):
data = Reply(sensorResponse.sensorMetadata.uid, sensorResponse.sensorMetadata.messageID) print("Time Now: ", time(), ", Reserved Until: ", self.reservedUntil)
data.setAckStatus(True) if self.reserved and self.reservedUntil < time():
self.reserved = False
print(data.ToJson()) return True
_length, _payload = packer.Pack_Str( data.ToJson() ) return False
payload = [int(hex(c), 0) for c in _payload]
def canAccept(self):
loraReceiver.transmit(payload) self.hasReservationExpired()
return
if not self.reserved:
# When a sensor has data to transmit, it will send a TxHello. return True
# Pick up that message here and decide if we can accept the message at this time.
def handleRxHello(data): # TODO Additional Logic Here!
timeBegin = time() return False
print("\n[?] Try RX Hello!\nRaw data: %s" % data)
try: def acceptNew(self, sensorID, reservationDuration):
rxHelloJson = json.loads(data) if not self.canAccept:
rxHello = RxHello(**rxHelloJson) return False
except Exception as e: self.reserved = True
print("[-] Not RX Hello\n") self.sensorID = sensorID
return False self.reservedUntil = time() + reservationDuration + 1
print("[+] RX Hello Confirmed!\n") # Source: https://www.raspberrypi-spy.co.uk/2012/09/getting-your-raspberry-pi-serial-number-using-python/
# TODO Check i'm not expecting any methods within rxHello.reservationTime seconds def getserial():
# Extract serial from cpuinfo file
# TODO Send "OK" + UID + TX_AUTH_ID cpuserial = "0000000000000000"
rxHello.setOK(True) # We are happy for this sensor to send it's data try:
rxHello.setTxAuthID(123) f = open('/proc/cpuinfo','r')
print(rxHello.ToJson()) for line in f:
if line[0:6]=='Serial':
_length, _payload = packer.Pack_Str( rxHello.ToJson() ) # Send OK Back Back cpuserial = line[10:26]
payload = [int(hex(c), 0) for c in _payload] f.close()
except:
loraReceiver.transmit(payload) cpuserial = "ERROR000000000"
print("\n[+] RX Hello \"OK\" Reply Sent\n")
return cpuserial
# Sleep until out transmit block expires (2 Seconds)
#sleep(2 - (time() - timeBegin)) # Upon succesfully receiving a message, send back an ack
sleep(0.2) def ackMsg(sensorResponse):
return True data = Reply(sensorResponse.sensorMetadata.uid, sensorResponse.sensorMetadata.messageID)
data.setAckStatus(True)
# handle SensorResponse data packet # TODO Fetch config for this sensor from the API here
def handleSensorResponsePacket(data): # TODO Include Reply config updates here
timeBegin = time() data.updateConfigPollingFrequency(10000)
print("\n[?] Try Sensor Response Data!\nRaw data: %s" % data) data.updateConfigTxAfterNReadings(6)
try: if DEBUG > 1:
parsed = json.loads(data) print(data.ToJson())
p = SensorResponse(**parsed)
_length, _payload = packer.Pack_Str( data.ToJson() )
except Exception as e:
print("\n[-] Unable to Parse response, ignoring") # TODO Error handling, log increased error rates etc payload = [int(hex(c), 0) for c in _payload]
if DEBUG > 1:
print("\tE: %e" % e) sleep(0.2) # Slight delay before transmitting, else too quick for sensor to start listening
return False loraReceiver.transmit(payload)
return
print("[+] Sensor Response Data Confirmed!\n")
print(p.ToJson()) # 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.
# TODO Validate response is valid and non-corrupt def handleRxHello(rxHelloQueue, data):
timeBegin = time()
# TODO Ack / process here print("\n[?] Check for RX Hello")
if DEBUG > 1:
# TODO Transmit ACK print("\nRaw data: %s" % data)
print("\n[+] Sending Sensor Response Ack")
ackMsg(p) try:
print("[+] Sensor Response Ack Sent\n") rxHelloJson = json.loads(data)
rxHello = RxHello(**rxHelloJson)
# Process response print(" RX Hello Confirmed!")
print("Sensor ID: %s \tPPM 10: %s\n" % (p.sensorMetadata.uid, p.sensorReading.ppm.p10))
except Exception as e:
# TODO Send To API print(" Not RX Hello")
print("\n[+] Sending Data to API") return False
p.sendToApi()
print("[+] Data Sent to API\n") # TODO Check i'm not expecting any methods within rxHello.reservationTime seconds
if not rxHelloQueue.canAccept():
# Sleep until out transmit block expires (2 Seconds) rxHello.setOK(False)
#sleep(2 - (time() - timeBegin)) print("[-] RX Hello \"No\" Reply Sent - Reserved by %s for %i seconds" % (rxHelloQueue.sensorID, rxHelloQueue.reservedUntil - time()))
sleep(0.2)
else:
rxHelloQueue.acceptNew(rxHello.uid, rxHello.reservationTime)
# Parse LoRa response, validate, save / transmit # Send "OK" + UID
def handleData(data): rxHello.setOK(True) # We are happy for this sensor to send it's data
# Handle TxHello Transmission print("[+] RX Hello \"OK\" Reply Sent - %s is permitted to send for %d seconds" % (rxHello.uid, rxHello.reservationTime))
# Rx - "I've got data to send!" + UID (From Sensor)
# If not expecting any messages, continue if DEBUG > 1:
# Tx - "OK" + UID (Back to Sensor) + TX_Auth_ID print(rxHello.ToJson())
#
# <Do not Tx for 2s> _length, _payload = packer.Pack_Str( rxHello.ToJson() ) # Send OK Back
if (handleRxHello(data)): # Handled all OK payload = [int(hex(c), 0) for c in _payload]
return
sleep(0.2) # Slight delay before transmitting, else too quick for sensor to start listening
# T-0 Rx - Packet + UID + Tx_Auth_ID (From Sensor) loraReceiver.transmit(payload)
# Handle message, ack / nak appropriately sleep(0.2)
# Tx - Ack + UID + Next_Send_Interval return True
# Tx - Nak + UID
#
# <Do not Tx for 3s> # handle SensorResponse data packet
if (handleSensorResponsePacket(data)): def handleSensorResponsePacket(data):
return timeBegin = time()
print("\n[?] Check for Sensor Response Data")
if DEBUG > 1:
# T+3 Tx reserved window expires print("\nRaw data: %s" % data)
try:
# Setup Receiver parsed = json.loads(data)
loraReceiver = LoRaReceiver(verbose=False) p = SensorResponse(**parsed)
args = parser.parse_args(loraReceiver)
except Exception as e:
loraReceiver.set_mode(MODE.STDBY) print("[-] Unable to Parse response, ignoring") # TODO Error handling, log increased error rates etc
loraReceiver.set_pa_config(pa_select=1) if DEBUG > 1:
#loraReceiver.set_rx_crc(True) print("\tE: %e" % e)
#loraReceiver.set_coding_rate(CODING_RATE.CR4_6) return False
#loraReceiver.set_pa_config(max_power=0, output_power=0)
#loraReceiver.set_lna_gain(GAIN.G1) print(" Sensor Response Data Confirmed!")
#loraReceiver.set_implicit_header_mode(False)
#loraReceiver.set_low_data_rate_optim(True) if DEBUG > 1:
#loraReceiver.set_pa_ramp(PA_RAMP.RAMP_50_us) print(" Received Payload: %s" % p.ToJson())
#loraReceiver.set_agc_auto_on(True)
# TODO Validate response is valid and non-corrupt
# Go Go Go!
print("[+] Receiver & API Gateway") # TODO Ack / process here
assert(loraReceiver.get_agc_auto_on() == 1)
# TODO Transmit ACK
try: print(" Sending Sensor Response Ack")
loraReceiver.receive() ackMsg(p)
print("[+] Sensor Response Ack Sent")
except KeyboardInterrupt:
sys.stdout.flush() # TODO Process response?
print("")
sys.stderr.write("KeyboardInterrupt\n") # TODO Send To API
print(" Sending Data to API")
finally: p.sendToApi()
sys.stdout.flush() print("[+] Data Sent to API")
print("")
loraReceiver.set_mode(MODE.SLEEP) sleep(0.2)
BOARD.teardown() return True
# Parse LoRa response, validate, save / transmit
def handleData(rxHelloQueue, 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
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=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)
print("Power:")
print(loraReceiver.get_pa_config(convert_dBm=True))
# Go Go Go!
print("[+] Receiver & API Gateway")
loraReceiver.set_agc_auto_on(1)
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()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment