diff --git a/examples/lorawan-nano-gateway/config.py b/examples/lorawan-nano-gateway/config.py index 8165662..612a88a 100644 --- a/examples/lorawan-nano-gateway/config.py +++ b/examples/lorawan-nano-gateway/config.py @@ -2,14 +2,8 @@ GATEWAY_ID = '11aa334455bb7788' -SERVER = 'router.eu.thethings.network' +SERVER = '198.199.97.15' PORT = 1700 -NTP = "pool.ntp.org" -NTP_PERIOD_S = 3600 - -WIFI_SSID = 'my-wifi' -WIFI_PASS = 'my-wifi-password' - -LORA_FREQUENCY = 868100000 +LORA_FREQUENCY = 912100000 LORA_DR = "SF7BW125" # DR_5 diff --git a/examples/lorawan-nano-gateway/main.py b/examples/lorawan-nano-gateway/main.py index 451dfa6..6652180 100644 --- a/examples/lorawan-nano-gateway/main.py +++ b/examples/lorawan-nano-gateway/main.py @@ -5,8 +5,7 @@ nanogw = NanoGateway(id=config.GATEWAY_ID, frequency=config.LORA_FREQUENCY, - datarate=config.LORA_DR, ssid=config.WIFI_SSID, - password=config.WIFI_PASS, server=config.SERVER, - port=config.PORT, ntp=config.NTP, ntp_period=config.NTP_PERIOD_S) + datarate=config.LORA_DR, server=config.SERVER, + port=config.PORT) nanogw.start() diff --git a/examples/lorawan-nano-gateway/nanogateway.py b/examples/lorawan-nano-gateway/nanogateway.py index 4bf2470..68ae714 100644 --- a/examples/lorawan-nano-gateway/nanogateway.py +++ b/examples/lorawan-nano-gateway/nanogateway.py @@ -1,25 +1,30 @@ """ LoPy Nano Gateway class """ -from network import WLAN -from network import LoRa -from machine import Timer import os import binascii -import machine +from binascii import unhexlify +import random +import sys import json import time import errno import _thread import socket +import time +import datetime +from threading import Timer + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -PROTOCOL_VERSION = const(2) +PROTOCOL_VERSION = '\x02' -PUSH_DATA = const(0) -PUSH_ACK = const(1) -PULL_DATA = const(2) -PULL_ACK = const(4) -PULL_RESP = const(3) +PUSH_DATA = '\x00' +PUSH_ACK = '\x01' +PULL_DATA = '\x02' +PULL_ACK = '\x04' +PULL_RESP = '\x03' TX_ERR_NONE = "NONE" TX_ERR_TOO_LATE = "TOO_LATE" @@ -38,7 +43,7 @@ RX_PK = {"rxpk": [{"time": "", "tmst": 0, "chan": 0, "rfch": 0, - "freq": 868.1, "stat": 1, + "freq": 9, "stat": 1, "modu": "LORA", "datr": "SF7BW125", "codr": "4/5", "rssi": 0, "lsnr": 0, "size": 0, @@ -46,6 +51,19 @@ TX_ACK_PK = {"txpk_ack":{"error":""}} +#(timestamp, rssi, snr, sf) +class stats: + timestamp = 0 + rssi = 0 + snr = 0 + sf = 8 + +UP_LINK = 0 +DOWN_LINK = 1 + +AppSKey = '271E403DF4225EEF7E90836494A5B345' +dev_addr = '000015E4' +payload_hex= '686f6c61' #hola in hex class NanoGateway: @@ -76,13 +94,8 @@ def __init__(self, id, frequency, datarate, ssid, password, server, port, ntp='p self.lora_sock = None def start(self): - # Change WiFi to STA mode and connect - self.wlan = WLAN(mode=WLAN.STA) - self._connect_to_wifi() - # Get a time Sync - self.rtc = machine.RTC() - self.rtc.ntp_sync(self.ntp, update_period=self.ntp_period) + self._make_node_data() # Get the server IP and create an UDP socket self.server_ip = socket.getaddrinfo(self.server, self.port)[0][-1] @@ -94,21 +107,24 @@ def start(self): self._push_data(self._make_stat_packet()) # Create the alarms - self.stat_alarm = Timer.Alarm(handler=lambda t: self._push_data(self._make_stat_packet()), s=60, periodic=True) - self.pull_alarm = Timer.Alarm(handler=lambda u: self._pull_data(), s=25, periodic=True) + #self.stat_alarm = Timer.Alarm(handler=lambda t: self._push_data(self._make_stat_packet()), s=60, periodic=True) + + #self.pull_alarm = Timer.Alarm(handler=lambda u: self._pull_data(), s=25, periodic=True) # Start the UDP receive thread - _thread.start_new_thread(self._udp_thread, ()) + #_thread.start_new_thread(self._udp_thread, ()) # Initialize LoRa in LORA mode - self.lora = LoRa(mode=LoRa.LORA, frequency=self.frequency, bandwidth=LoRa.BW_125KHZ, sf=self.sf, - preamble=8, coding_rate=LoRa.CODING_4_5, tx_iq=True) + #self.lora = LoRa(mode=LoRa.LORA, frequency=self.frequency, bandwidth=LoRa.BW_125KHZ, sf=self.sf, + # preamble=8, coding_rate=LoRa.CODING_4_5, tx_iq=True) # Create a raw LoRa socket - self.lora_sock = socket.socket(socket.AF_LORA, socket.SOCK_RAW) - self.lora_sock.setblocking(False) - self.lora_tx_done = False + #self.lora_sock = socket.socket(socket.AF_LORA, socket.SOCK_RAW) + #self.lora_sock.setblocking(False) + #self.lora_tx_done = False - self.lora.callback(trigger=(LoRa.RX_PACKET_EVENT | LoRa.TX_PACKET_EVENT), handler=self._lora_cb) + #self.lora.callback(trigger=(LoRa.RX_PACKET_EVENT | LoRa.TX_PACKET_EVENT), handler=self._lora_cb) + #Dato de Nodo recibido + self._lora_cb(10) def stop(self): # TODO: Check how to stop the NTP sync @@ -116,12 +132,6 @@ def stop(self): # TODO: kill the UDP thread self.sock.close() - def _connect_to_wifi(self): - self.wlan.connect(self.ssid, auth=(None, self.password)) - while not self.wlan.isconnected(): - time.sleep(0.5) - print("WiFi connected!") - def _dr_to_sf(self, dr): sf = dr[2:4] if sf[1] not in '0123456789': @@ -131,18 +141,19 @@ def _dr_to_sf(self, dr): def _sf_to_dr(self, sf): return "SF7BW125" - def _make_stat_packet(self): - now = self.rtc.now() - STAT_PK["stat"]["time"] = "%d-%02d-%02d %02d:%02d:%02d GMT" % (now[0], now[1], now[2], now[3], now[4], now[5]) + def _make_stat_packet(self): #Paquete de Status + now = datetime.datetime.now() + STAT_PK["stat"]["time"] = "%d-%02d-%02d %02d:%02d:%02d GMT" % (now.year, now.month, now.day, now.hour, now.minute, now.second) STAT_PK["stat"]["rxnb"] = self.rxnb STAT_PK["stat"]["rxok"] = self.rxok STAT_PK["stat"]["rxfw"] = self.rxfw STAT_PK["stat"]["dwnb"] = self.dwnb STAT_PK["stat"]["txnb"] = self.txnb + print("Make Status Packet") return json.dumps(STAT_PK) def _make_node_packet(self, rx_data, rx_time, tmst, sf, rssi, snr): - RX_PK["rxpk"][0]["time"] = "%d-%02d-%02dT%02d:%02d:%02d.%dZ" % (rx_time[0], rx_time[1], rx_time[2], rx_time[3], rx_time[4], rx_time[5], rx_time[6]) + RX_PK["rxpk"][0]["time"] = "%d-%02d-%02dT%02d:%02d:%02d.%dZ" % (rx_time.year, rx_time.month, rx_time.day, rx_time.hour, rx_time.minute, rx_time.second, rx_time.microsecond) RX_PK["rxpk"][0]["tmst"] = tmst RX_PK["rxpk"][0]["datr"] = self._sf_to_dr(sf) RX_PK["rxpk"][0]["rssi"] = rssi @@ -151,47 +162,142 @@ def _make_node_packet(self, rx_data, rx_time, tmst, sf, rssi, snr): RX_PK["rxpk"][0]["size"] = len(rx_data) return json.dumps(RX_PK) + def LoRaPayloadEncrypt(self, payload_hex, frameCount, AppSKey, dev_addr, direction=UP_LINK): + ''' + LoraMac decrypt + + Which is actually encrypting a predefined 16-byte block (ref LoraWAN + specification 4.3.3.1) and XORing that with each block of data. + + payload_hex: hex-encoded payload (FRMPayload) + sequence_counter: integer, sequence counter (FCntUp) + key: 16-byte hex-encoded AES key. (i.e. AABBCCDDEEFFAABBCCDDEEFFAABBCCDD) + dev_addr: 4-byte hex-encoded DevAddr (i.e. AABBCCDD) + direction: 0 for uplink packets, 1 for downlink packets + + returns an array of byte values. + + This method is based on `void LoRaMacPayloadEncrypt()` in + https://github.com/Lora-net/LoRaMac-node/blob/master/src/mac/LoRaMacCrypto.c#L108 + ''' + AppSKey = unhexlify(AppSKey) + dev_addr = unhexlify(dev_addr) + buffer = bytearray(unhexlify(payload_hex)) + size = len(buffer) + + bufferIndex = 0 + # block counter + ctr = 1 + + # output buffer, initialize to input buffer size. + encBuffer = [0x00] * size + + cipher = Cipher(algorithms.AES(AppSKey), modes.ECB(), backend=default_backend()) + + def aes_encrypt_block(aBlock): + ''' + AES encrypt a block. + aes.encrypt expects a string, so we convert the input to string and + the return value to bytes again. + ''' + encryptor = cipher.encryptor() + + return bytearray( + encryptor.update(self.to_bytes(aBlock)) + encryptor.finalize() + ) + + # For the exact definition of this block refer to + # 'chapter 4.3.3.1 Encryption in LoRaWAN' in the LoRaWAN specification + aBlock = bytearray([ + 0x01, # 0 always 0x01 + 0x00, # 1 always 0x00 + 0x00, # 2 always 0x00 + 0x00, # 3 always 0x00 + 0x00, # 4 always 0x00 + direction, # 5 dir, 0 for uplink, 1 for downlink + dev_addr[3], # 6 devaddr, lsb + dev_addr[2], # 7 devaddr + dev_addr[1], # 8 devaddr + dev_addr[0], # 9 devaddr, msb + frameCount & 0xff, # 10 sequence counter (FCntUp) lsb + (frameCount >> 8) & 0xff, # 11 sequence counter + (frameCount >> 16) & 0xff, # 12 sequence counter + (frameCount >> 24) & 0xff, # 13 sequence counter (FCntUp) msb + 0x00, # 14 always 0x01 + 0x00 # 15 block counter + ]) + + # complete blocks + while size >= 16: + aBlock[15] = ctr & 0xFF + ctr += 1 + sBlock = aes_encrypt_block(aBlock) + for i in range(16): + encBuffer[bufferIndex + i] = buffer[bufferIndex + i] ^ sBlock[i] + + size -= 16 + bufferIndex += 16 + + # partial blocks + if size > 0: + aBlock[15] = ctr & 0xFF + sBlock = aes_encrypt_block(aBlock) + for i in range(size): + encBuffer[bufferIndex + i] = buffer[bufferIndex + i] ^ sBlock[i] + + return encBuffer + def _push_data(self, data): token = os.urandom(2) - packet = bytes([PROTOCOL_VERSION]) + token + bytes([PUSH_DATA]) + binascii.unhexlify(self.id) + data + packet = PROTOCOL_VERSION + token + PUSH_DATA + binascii.unhexlify(self.id) + (data) + print(packet) with self.udp_lock: try: self.sock.sendto(packet, self.server_ip) + print("Push Data") except Exception: print("PUSH exception") + def _pull_data(self): token = os.urandom(2) - packet = bytes([PROTOCOL_VERSION]) + token + bytes([PULL_DATA]) + binascii.unhexlify(self.id) + packet = PROTOCOL_VERSION + token + PULL_DATA + binascii.unhexlify(self.id) with self.udp_lock: try: self.sock.sendto(packet, self.server_ip) + print("Send Pull data") except Exception: print("PULL exception") def _ack_pull_rsp(self, token, error): TX_ACK_PK["txpk_ack"]["error"] = error resp = json.dumps(TX_ACK_PK) - packet = bytes([PROTOCOL_VERSION]) + token + bytes([PULL_ACK]) + binascii.unhexlify(self.id) + resp + packet = PROTOCOL_VERSION + token + PULL_ACK + binascii.unhexlify(self.id) + resp with self.udp_lock: try: self.sock.sendto(packet, self.server_ip) + print("ack") except Exception: print("PULL RSP ACK exception") def _lora_cb(self, lora): - events = lora.events() - if events & LoRa.RX_PACKET_EVENT: + events = 1#lora.events() + if events & 1: #LoRa.RX_PACKET_EVENT self.rxnb += 1 self.rxok += 1 - rx_data = self.lora_sock.recv(256) - stats = lora.stats() - self._push_data(self._make_node_packet(rx_data, self.rtc.now(), stats.timestamp, stats.sf, stats.rssi, stats.snr)) + rx_data = "hola" #self.lora_sock.recv(256) + # stats=(timestamp, rssi, snr, sf) + #stats = lora.stats() + #marca de tiempo del paquete + stats.timestamp = int(time.time()) + self._push_data(self._make_node_packet(rx_data, datetime.datetime.now(), stats.timestamp, stats.sf, stats.rssi, stats.snr)) self.rxfw += 1 - if events & LoRa.TX_PACKET_EVENT: + print("LoRa.RX_PACKET_EVENT") + if events & 0: #LoRa.TX_PACKET_EVENT self.txnb += 1 lora.init(mode=LoRa.LORA, frequency=self.frequency, bandwidth=LoRa.BW_125KHZ, sf=self.sf, preamble=8, coding_rate=LoRa.CODING_4_5, tx_iq=True) + print("LoRa.TX_PACKET_EVENT") def _send_down_link(self, data, tmst, datarate, frequency): self.lora.init(mode=LoRa.LORA, frequency=frequency, bandwidth=LoRa.BW_125KHZ, @@ -223,6 +329,7 @@ def _udp_thread(self): self.uplink_alarm = Timer.Alarm(handler=lambda x: self._send_down_link(binascii.a2b_base64(tx_pk["txpk"]["data"]), tx_pk["txpk"]["tmst"] - 10, tx_pk["txpk"]["datr"], int(tx_pk["txpk"]["freq"] * 1000000)), us=t_us) + print("Downlink") else: ack_error = TX_ERR_TOO_LATE print("Downlink timestamp error!, t_us:", t_us)