Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
134 commits
Select commit Hold shift + click to select a range
1676bb8
Update JoinAcceptPayload.py
DGaffney Aug 16, 2022
3f99224
iterate on helium
Aug 20, 2022
bc6e05a
remove globals, wire msg properly
Aug 20, 2022
3ba1380
Update rssi_helium.py
DGaffney Aug 20, 2022
7f2352c
Merge branch 'master' into ted/dgaf_iter1
DGaffney Aug 21, 2022
97e3d42
Merge pull request #1 from tedder/ted/dgaf_iter1
DGaffney Aug 21, 2022
960febf
updates
DGaffney Aug 21, 2022
0f5ed59
updates
DGaffney Aug 21, 2022
2588c91
updates
DGaffney Aug 21, 2022
9bab489
add shortuuid to reqs
Aug 21, 2022
581ad72
more rssi hacking
DGaffney Aug 21, 2022
8e14e56
update to store generic last message
DGaffney Aug 21, 2022
97273b3
update to store generic last message
DGaffney Aug 21, 2022
0782eee
more hacking on iteration
DGaffney Aug 21, 2022
a5e4e1a
more hacking on iteration
DGaffney Aug 21, 2022
ec33b59
more hacking on iteration
DGaffney Aug 21, 2022
daaec65
watch for missing keys
Aug 21, 2022
9e5b32c
more hacking on iteration
DGaffney Aug 21, 2022
bf21bbd
Merge branch 'master' of github.com:DGaffney/LoRaWAN
DGaffney Aug 21, 2022
a299f40
more hacking on iteration
DGaffney Aug 21, 2022
1d1d527
fix output fail, ensure it's a digit while we're here
Aug 21, 2022
e8a33ee
no idea
DGaffney Aug 21, 2022
b6c5e70
no idea
DGaffney Aug 21, 2022
f95c3e1
no idea
DGaffney Aug 21, 2022
8709cce
no idea
DGaffney Aug 21, 2022
49ac744
ping on startup, move 'send' to actual location, simpler conditional,…
Aug 21, 2022
d57f7dd
updates
DGaffney Aug 21, 2022
9dcad9c
updates
DGaffney Aug 21, 2022
74725ee
reformat; move code.interact to an argument; enable confirm (wasn't g…
Aug 21, 2022
b02c8b7
refactor to make helium more independent module
DGaffney Aug 21, 2022
a9b9076
updates
DGaffney Aug 21, 2022
e6794ff
updates
DGaffney Aug 21, 2022
3c8ed0b
updates
DGaffney Aug 21, 2022
9124faf
updates
DGaffney Aug 21, 2022
6af5066
updates
DGaffney Aug 21, 2022
846b511
Updates
DGaffney Aug 21, 2022
fffd392
updates
DGaffney Aug 21, 2022
dc8944f
updates
DGaffney Aug 21, 2022
ffb2431
updates
DGaffney Aug 21, 2022
2f1324a
updates
DGaffney Aug 21, 2022
fe448ec
updates
DGaffney Aug 21, 2022
782ac6f
updates
DGaffney Aug 21, 2022
ac49e3b
updates
DGaffney Aug 21, 2022
e19a31f
updates
DGaffney Aug 21, 2022
5c50f0e
updates
DGaffney Aug 21, 2022
5a72983
updates
DGaffney Aug 21, 2022
fbcdce4
updates
DGaffney Aug 21, 2022
a2e016f
updates
DGaffney Aug 21, 2022
9a7aa12
updates
DGaffney Aug 21, 2022
cf932b1
updates
DGaffney Aug 21, 2022
5f03337
updates
DGaffney Aug 21, 2022
e0e7b01
updates
DGaffney Aug 21, 2022
a5d58b4
updates
DGaffney Aug 21, 2022
f7e52cd
updates
DGaffney Aug 21, 2022
fbbd46c
updates
DGaffney Aug 21, 2022
3aa2841
updates
DGaffney Aug 21, 2022
4cb8e3e
updates
DGaffney Aug 21, 2022
648f461
updates
DGaffney Aug 21, 2022
5d5d0d5
updates
DGaffney Aug 21, 2022
ff84ea8
updates
DGaffney Aug 21, 2022
fce53e0
updates
DGaffney Aug 21, 2022
9b5ab5f
updates
DGaffney Aug 21, 2022
1f247f2
updates
DGaffney Aug 21, 2022
f483fdd
updates
DGaffney Aug 21, 2022
e01f325
updates
DGaffney Aug 21, 2022
25f414d
updates
DGaffney Aug 21, 2022
4457e33
updates
DGaffney Aug 21, 2022
0f7095a
updates
DGaffney Aug 21, 2022
a308be6
updates
DGaffney Aug 21, 2022
fa347a3
updates
DGaffney Aug 21, 2022
ee1b6d3
updates
DGaffney Aug 21, 2022
9fd0704
updates
DGaffney Aug 21, 2022
fde4444
updates
DGaffney Aug 21, 2022
a32a8e2
updates
DGaffney Aug 21, 2022
280722b
updates
DGaffney Aug 21, 2022
fc47b51
updates
DGaffney Aug 21, 2022
6436470
updates
DGaffney Aug 21, 2022
3216c84
updates
DGaffney Aug 21, 2022
aa64bfc
updates
DGaffney Aug 21, 2022
0398e81
updates
DGaffney Aug 21, 2022
6db7679
updates
DGaffney Aug 21, 2022
6b1bee3
updates
DGaffney Aug 21, 2022
4b08aa2
updates
DGaffney Aug 21, 2022
a89fbbf
updates
DGaffney Aug 21, 2022
ab18687
updates
DGaffney Aug 21, 2022
877d84d
updates
DGaffney Aug 21, 2022
4637abf
updates
DGaffney Aug 21, 2022
e716774
updates
DGaffney Aug 21, 2022
b50123a
updates
DGaffney Aug 21, 2022
603a862
updates
DGaffney Aug 21, 2022
027a042
updates
DGaffney Aug 21, 2022
a2b6566
updates
DGaffney Aug 21, 2022
85a10a6
updates
DGaffney Aug 21, 2022
d910028
updates
DGaffney Aug 21, 2022
a97bedc
updates
DGaffney Aug 21, 2022
045af0d
updates
DGaffney Aug 21, 2022
44b7a40
updates
DGaffney Aug 21, 2022
bf46466
updates
DGaffney Aug 21, 2022
b6ab8ac
updates
DGaffney Aug 21, 2022
97972bb
updates
DGaffney Aug 21, 2022
d124002
updates
DGaffney Aug 21, 2022
d328203
updates
DGaffney Aug 21, 2022
04ad727
updates
DGaffney Aug 21, 2022
9a146f9
updates
DGaffney Aug 21, 2022
83f7625
updates
DGaffney Aug 22, 2022
2c23d6a
updates
DGaffney Aug 22, 2022
fe38680
updates
DGaffney Aug 22, 2022
db67fe3
updates
DGaffney Aug 22, 2022
897a37e
updates
DGaffney Aug 22, 2022
70dfd61
updates
DGaffney Aug 22, 2022
053aaf5
updates
DGaffney Aug 22, 2022
d9fca81
updates
DGaffney Aug 22, 2022
6813b26
updates
DGaffney Aug 22, 2022
99e158d
updates
DGaffney Aug 22, 2022
1d8ae3d
updates
DGaffney Aug 22, 2022
5e8476b
updates
DGaffney Aug 22, 2022
d4c7d54
more safety checks
DGaffney Aug 23, 2022
b1d75e5
back to defaults
DGaffney Aug 23, 2022
9216333
updates
DGaffney Aug 23, 2022
3267728
updates
DGaffney Aug 23, 2022
2ffb8a5
updates
DGaffney Aug 23, 2022
5cfd08e
updates
DGaffney Aug 23, 2022
ae76422
tighten package
DGaffney Aug 24, 2022
1effea0
updates
DGaffney Aug 25, 2022
7246346
updates
DGaffney Aug 25, 2022
1b1b010
updates
DGaffney Aug 26, 2022
f0e7e94
updates
DGaffney Aug 26, 2022
203e933
updates
DGaffney Aug 26, 2022
9151e95
updates
DGaffney Aug 26, 2022
4338e0f
updates
DGaffney Aug 26, 2022
a8ffe0d
updates
DGaffney Aug 26, 2022
e359db6
updates
DGaffney Aug 26, 2022
a86eb24
updates
DGaffney Aug 26, 2022
e27741f
updates
DGaffney Aug 31, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.pyc
keys.py
.env
frame.txt
4 changes: 2 additions & 2 deletions LoRaWAN/DataPayload.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def decrypt_payload(self, key, direction, mic):
a += [0x00]
a += [i+1]

cipher = AES.new(bytes(key))
cipher = AES.new(bytes(key), AES.MODE_ECB)
s = cipher.encrypt(bytes(a))

padded_payload = []
Expand Down Expand Up @@ -84,7 +84,7 @@ def encrypt_payload(self, key, direction, data):
a += [0x00]
a += [i+1]

cipher = AES.new(bytes(key))
cipher = AES.new(bytes(key), AES.MODE_ECB)
s = cipher.encrypt(bytes(a))

padded_payload = []
Expand Down
8 changes: 4 additions & 4 deletions LoRaWAN/JoinAcceptPayload.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def decrypt_payload(self, key, direction, mic):
a += self.encrypted_payload
a += mic

cipher = AES.new(bytes(key))
cipher = AES.new(bytes(key), AES.MODE_ECB)
self.payload = cipher.encrypt(bytes(a))[:-4]

self.appnonce = self.payload[:3]
Expand All @@ -75,7 +75,7 @@ def encrypt_payload(self, key, direction, mhdr):
a += self.to_clear_raw()
a += self.compute_mic(key, direction, mhdr)

cipher = AES.new(bytes(key))
cipher = AES.new(bytes(key), AES.MODE_ECB)
return list(map(int, cipher.decrypt(bytes(a))))

def derive_nwskey(self, key, devnonce):
Expand All @@ -85,7 +85,7 @@ def derive_nwskey(self, key, devnonce):
a += devnonce
a += [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]

cipher = AES.new(bytes(key))
cipher = AES.new(bytes(key), AES.MODE_ECB)
return list(map(int, cipher.encrypt(bytes(a))))

def derive_appskey(self, key, devnonce):
Expand All @@ -95,5 +95,5 @@ def derive_appskey(self, key, devnonce):
a += devnonce
a += [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]

cipher = AES.new(bytes(key))
cipher = AES.new(bytes(key), AES.MODE_ECB)
return list(map(int, cipher.encrypt(bytes(a))))
48 changes: 44 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
#RPI install routine
```
sudo apt-get install -y tmux htop
sudo pip3 install adafruit-circuitpython-ssd1306
sudo pip3 install adafruit-circuitpython-framebuf
sudo pip3 install adafruit-circuitpython-rfm9x
sudo pip3 install adafruit-circuitpython-gps
sudo pip3 install pycryptodome
sudo pip3 install shortuuid
sudo pip3 install python-dotenv
sudo apt-get install gpsd gpsd-clients
git clone git@github.com:DGaffney/LoRaWAN.git
```

# LoRaWAN
This is a LoRaWAN v1.0 implementation in python.

Expand All @@ -9,13 +23,39 @@ This fork adds support for the Adafruit LoRA Radio Bonnet with OLED - RFM95W @ 9

It also allows you to connect as a client to the Helium Network.

You must create a device on the Helium Console at https://console.helium.com/

You need to rename "keys_example.py" to "keys.py" and enter you device information from the Helium Console.


## Installation
To register a device and get a device ID you need to run otaa_helium.py and stores the results in keys.py.

After forking the repo, install the python libs:

pip3 install -r requirements.txt

## Credentials

The keys can be added to a `./.env` file:

```
deveui = 1234ASD432
appeui = 234ASD4321
appkey = 34ASD43210

devaddr = [00,11,22]
nwskey = [11,22,33]
appskey = [22,33,44]
```

They can also be passed as environment variables:

```
deveui=1234ASD432 python3 rssi_helium.py
```

## Registration

You must create a device on the Helium Console at https://console.helium.com/

After copying the device credentials (the first three in the `.env` file, above), run otaa_helium.py, which will get the final three credentials for the `.env` file.

Then you can run tx_helium.py to send messages by specifying the msg and the frame like this:

Expand Down
1 change: 0 additions & 1 deletion frame.py

This file was deleted.

50 changes: 50 additions & 0 deletions gps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from math import sin, cos, sqrt, atan2, radians
import datetime
from gpsdclient import GPSDClient

client = GPSDClient(host="127.0.0.1")
def get_gps_data(transact_timeout=10):
start = datetime.datetime.now()
for result in client.dict_stream(convert_datetime=True):
(datetime.datetime.now() - start).seconds < transact_timeout
if result["class"] == "TPV":
return {"lat": result.get("lat"), "lon": result.get("lon"), "speed": result.get("speed"), "alt": result.get("alt")}


def get_dist(lat1,lon1,lat2,lon2):
# approximate radius of earth in km
R = 6373.0
lat1 = radians(lat1)
lon1 = radians(lon1)
lat2 = radians(lat2)
lon2 = radians(lon2)
dlon = lon2 - lon1
dlat = lat2 - lat1
a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
print(a)
if not a:
return 0
c = 2 * atan2(sqrt(a), sqrt(1 - a))
distance = R * c
return distance

def should_send_gps(new, old, last_sent_at):
if last_sent_at is None:
return True
if not new:
return False
if new and not old:
return True
if new.get("lat") is not None and new.get("lon") is not None and None in [old.get("lat"), old.get("lon")]:
return True
if None not in [new["lat"], new["lon"], old["lat"], old["lon"]]:
distance = get_dist(new["lat"], new["lon"], old["lat"], old["lon"])
if distance < 0.05:
if (datetime.datetime.now() - last_sent_at).seconds > 60*5:
return True
else:
return False
else:
return True
else:
return False
109 changes: 109 additions & 0 deletions gps_simpletest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

# Simple GPS module demonstration.
# Will wait for a fix and print a message every second with the current location
# and other details.
import time
import board
import busio

import adafruit_gps

# Create a serial connection for the GPS connection using default speed and
# a slightly higher timeout (GPS modules typically update once a second).
# These are the defaults you should use for the GPS FeatherWing.
# For other boards set RX = GPS module TX, and TX = GPS module RX pins.
# uart = busio.UART(board.TX, board.RX, baudrate=9600, timeout=10)

# for a computer, use the pyserial library for uart access
import serial
uart = serial.Serial("/dev/ttyUSB1", baudrate=9600, timeout=10)

# If using I2C, we'll create an I2C interface to talk to using default pins
# i2c = board.I2C()

# Create a GPS module instance.
gps = adafruit_gps.GPS(uart, debug=False) # Use UART/pyserial
# gps = adafruit_gps.GPS_GtopI2C(i2c, debug=False) # Use I2C interface

# Initialize the GPS module by changing what data it sends and at what rate.
# These are NMEA extensions for PMTK_314_SET_NMEA_OUTPUT and
# PMTK_220_SET_NMEA_UPDATERATE but you can send anything from here to adjust
# the GPS module behavior:
# https://cdn-shop.adafruit.com/datasheets/PMTK_A11.pdf

# Turn on the basic GGA and RMC info (what you typically want)
gps.send_command(b"PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0")
# Turn on just minimum info (RMC only, location):
# gps.send_command(b'PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0')
# Turn off everything:
# gps.send_command(b'PMTK314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0')
# Turn on everything (not all of it is parsed!)
# gps.send_command(b'PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0')

# Set update rate to once a second (1hz) which is what you typically want.
gps.send_command(b"PMTK220,1000")
# Or decrease to once every two seconds by doubling the millisecond value.
# Be sure to also increase your UART timeout above!
# gps.send_command(b'PMTK220,2000')
# You can also speed up the rate, but don't go too fast or else you can lose
# data during parsing. This would be twice a second (2hz, 500ms delay):
# gps.send_command(b'PMTK220,500')

# Main loop runs forever printing the location, etc. every second.
last_print = time.monotonic()
while True:
# Make sure to call gps.update() every loop iteration and at least twice
# as fast as data comes from the GPS unit (usually every second).
# This returns a bool that's true if it parsed new data (you can ignore it
# though if you don't care and instead look at the has_fix property).
gps.update()
# Every second print out current location details if there's a fix.
current = time.monotonic()
if current - last_print >= 1.0:
last_print = current
if not gps.has_fix:
# Try again if we don't have a fix yet.
print("Waiting for fix...")
continue
# We have a fix! (gps.has_fix is true)
# Print out details about the fix like location, date, etc.
print("=" * 40) # Print a separator line.
print(
"Fix timestamp: {}/{}/{} {:02}:{:02}:{:02}".format(
gps.timestamp_utc.tm_mon, # Grab parts of the time from the
gps.timestamp_utc.tm_mday, # struct_time object that holds
gps.timestamp_utc.tm_year, # the fix time. Note you might
gps.timestamp_utc.tm_hour, # not get all data like year, day,
gps.timestamp_utc.tm_min, # month!
gps.timestamp_utc.tm_sec,
)
)
print("Latitude: {0:.6f} degrees".format(gps.latitude))
print("Longitude: {0:.6f} degrees".format(gps.longitude))
print(
"Precise Latitude: {:2.}{:2.4f} degrees".format(
gps.latitude_degrees, gps.latitude_minutes
)
)
print(
"Precise Longitude: {:2.}{:2.4f} degrees".format(
gps.longitude_degrees, gps.longitude_minutes
)
)
print("Fix quality: {}".format(gps.fix_quality))
# Some attributes beyond latitude, longitude and timestamp are optional
# and might not be present. Check if they're None before trying to use!
if gps.satellites is not None:
print("# satellites: {}".format(gps.satellites))
if gps.altitude_m is not None:
print("Altitude: {} meters".format(gps.altitude_m))
if gps.speed_knots is not None:
print("Speed: {} knots".format(gps.speed_knots))
if gps.track_angle_deg is not None:
print("Track angle: {} degrees".format(gps.track_angle_deg))
if gps.horizontal_dilution is not None:
print("Horizontal dilution: {}".format(gps.horizontal_dilution))
if gps.height_geoid is not None:
print("Height geoid: {} meters".format(gps.height_geoid))
44 changes: 42 additions & 2 deletions helium.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,42 @@
UPFREQ = 903.9
DOWNFREQ = 923.3
import time
import requests
from helium_authenticator import HeliumAuthenticator
from helium_transactor import keys, HeliumTransactor

class Helium:
def authenticate(self):
try_count = 0
while try_count < 5:
try:
authentication = HeliumAuthenticator.authenticate()
cur_keys = self.keys
for k,v in authentication.items():
cur_keys[k] = v
keys.write(cur_keys)
except:
print("Failed Auth")
try_count += 1
time.sleep(3)
return authentication

def register_device(self):
# if we have never seen the device, build some ... machinery and APIs etc to register this device.
# response = requests.get("http://somewebsite.com/register_device.json"+some_authentication_information)
return None

def get_last_message(self):
return self.ht.last_message

def transact(self, msg):
return self.ht.transact(msg)

def __init__(self, passed_keys=None, verbose=False):
self.keys = keys.get_keys()
if not self.keys.get("deveui"):
self.register_device()
if not self.keys.get("nwskey"):
self.authenticate()
helium_keys = passed_keys or keys.get_keys()
self.ht = HeliumTransactor.init(verbose, helium_keys)


Loading