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
'''
=========================================================================================
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!\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
# TODO 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
# T-0 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()
#!/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 = "https://emiei.4oh4.co/api"
MAX_TX_RESERVATION_TIME = 2 # Seconds
BOARD.setup()
parser = LoRaArgumentParser("Continous LoRa receiver.")
# Recived from Sensors when they have data to send
class RxHello(object):
def __init__(self, uid, reservationTime, messageID):
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
# Python Object to JSON Object
def ToJson(self):
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True)
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)
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 > 1:
print(response) # Ensure 200 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.gatewayUid = getserial()
self.uid = remoteSensorID
self.replyMsgID = replyMsgID
self.txAfterNReadings = None # We may update config of the sensor
self.pollingFrequency = None # We may update config of the sensor
def setAckStatus(self, ackStatus):
self.ackStatus = ackStatus
def updateConfigTxAfterNReadings(self, _nReadings):
self.txAfterNReadings = _nReadings
# Provide value in ms
def updateConfigPollingFrequency(self, _pollingFrequency):
self.pollingFrequency = _pollingFrequency
def ToJson(self):
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True)
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)
self.rxHelloQueue = RxHelloQueue() # Track transmission requests
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(self.rxHelloQueue, 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)
class RxHelloQueue():
def __init__(self):
self.reserved = False
self.sensorID = None
self.reservedUntil = None
def hasReservationExpired(self):
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 acceptNew(self, sensorID, reservationDuration):
if not self.canAccept:
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
# Upon succesfully receiving a message, send back an ack
def ackMsg(sensorResponse):
data = Reply(sensorResponse.sensorMetadata.uid, sensorResponse.sensorMetadata.messageID)
data.setAckStatus(True)
# TODO Fetch config for this sensor from the API here
# TODO Include Reply config updates here
data.updateConfigPollingFrequency(10000)
data.updateConfigTxAfterNReadings(6)
if DEBUG > 1:
print(data.ToJson())
_length, _payload = packer.Pack_Str( data.ToJson() )
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)
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):
timeBegin = time()
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!")
except Exception as e:
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()))
else:
rxHelloQueue.acceptNew(rxHello.uid, rxHello.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())
_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)
return True
# handle SensorResponse data packet
def handleSensorResponsePacket(data):
timeBegin = time()
print("\n[?] Check for Sensor Response Data")
if DEBUG > 1:
print("\nRaw data: %s" % data)
try:
parsed = json.loads(data)
p = SensorResponse(**parsed)
except Exception as e:
print("[-] 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!")
if DEBUG > 1:
print(" Received Payload: %s" % p.ToJson())
# TODO Validate response is valid and non-corrupt
# TODO Ack / process here
# TODO Transmit ACK
print(" Sending Sensor Response Ack")
ackMsg(p)
print("[+] Sensor Response Ack Sent")
# TODO Process response?
# TODO Send To API
print(" Sending Data to API")
p.sendToApi()
print("[+] Data Sent to API")
sleep(0.2)
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