class LoraNet: def __init__(self, frequency, dr, region, device_class=LoRa.CLASS_C, activation=LoRa.OTAA, auth=None): self.frequency = frequency self.dr = dr self.region = region self.device_class = device_class self.activation = activation self.auth = auth self.sock = None self._exit = False self.s_lock = _thread.allocate_lock() self.lora = LoRa(mode=LoRa.LORAWAN, region=self.region, device_class=self.device_class) self._msg_queue = [] self.q_lock = _thread.allocate_lock() self._process_ota_msg = None def stop(self): self._exit = True def init(self, process_msg_callback): self._process_ota_msg = process_msg_callback def receive_callback(self, lora): events = lora.events() if events & LoRa.RX_PACKET_EVENT: rx, port = self.sock.recvfrom(256) if rx: if '$OTA' in rx: print("OTA msg received: {}".format(rx)) self._process_ota_msg(rx.decode()) else: self.q_lock.acquire() self._msg_queue.append(rx) self.q_lock.release() def connect(self): if self.activation != LoRa.OTAA and self.activation != LoRa.ABP: raise ValueError("Invalid Lora activation method") if len(self.auth) < 3: raise ValueError("Invalid authentication parameters") self.lora.callback(trigger=LoRa.RX_PACKET_EVENT, handler=self.receive_callback) # set the 3 default channels to the same frequency self.lora.add_channel(0, frequency=self.frequency, dr_min=0, dr_max=5) self.lora.add_channel(1, frequency=self.frequency, dr_min=0, dr_max=5) self.lora.add_channel(2, frequency=self.frequency, dr_min=0, dr_max=5) # remove all the non-default channels for i in range(3, 16): self.lora.remove_channel(i) # authenticate with abp or ota if self.activation == LoRa.OTAA: self._authenticate_otaa(self.auth) else: self._authenticate_abp(self.auth) # create socket to server self._create_socket() def _authenticate_otaa(self, auth_params): # create an OTAA authentication params self.dev_eui = binascii.unhexlify(auth_params[0]) self.app_eui = binascii.unhexlify(auth_params[1]) self.app_key = binascii.unhexlify(auth_params[2]) self.lora.join(activation=LoRa.OTAA, auth=(self.dev_eui, self.app_eui, self.app_key), timeout=0, dr=self.dr) while not self.lora.has_joined(): time.sleep(2.5) print('Not joined yet...') def has_joined(self): return self.lora.has_joined() def _authenticate_abp(self, auth_params): # create an ABP authentication params self.dev_addr = struct.unpack(">l", binascii.unhexlify(auth_params[0]))[0] self.nwk_swkey = binascii.unhexlify(auth_params[1]) self.app_swkey = binascii.unhexlify(auth_params[2]) self.lora.join(activation=LoRa.ABP, auth=(self.dev_addr, self.nwk_swkey, self.app_swkey)) def _create_socket(self): # create a LoRa socket self.sock = socket.socket(socket.AF_LORA, socket.SOCK_RAW) # set the LoRaWAN data rate self.sock.setsockopt(socket.SOL_LORA, socket.SO_DR, self.dr) # make the socket non blocking self.sock.setblocking(False) time.sleep(2) def send(self, packet): with self.s_lock: self.sock.send(packet) def receive(self, bufsize): with self.q_lock: if len(self._msg_queue) > 0: return self._msg_queue.pop(0) return '' def get_dev_eui(self): return binascii.hexlify(self.lora.mac()).decode('ascii') def change_to_multicast_mode(self, mcAuth): print('Start listening for firmware updates ...........') if self.device_class != LoRa.CLASS_C: self.lora = LoRa(mode=LoRa.LORAWAN, region=self.region, device_class=LoRa.CLASS_C) self.connect() mcAddr = struct.unpack(">l", binascii.unhexlify(mcAuth[0]))[0] mcNwkKey = binascii.unhexlify(mcAuth[1]) mcAppKey = binascii.unhexlify(mcAuth[2]) self.lora.join_multicast_group(mcAddr, mcNwkKey, mcAppKey)
def loraTask(): global lora_wdt_lock, lora_stop_flag global QueueDelay logger = Logger(name='LORA ' + __version__, level=logging.DEBUG, filename=None) logger.debug('** LORA Task started **') lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.EU868, adr=True, public=True, device_class=LoRa.CLASS_C) lorawan_socket = socket.socket(socket.AF_LORA, socket.SOCK_RAW) lorawan_socket.setblocking(False) RadioOffset = getConfigurationVariable(NVS_RADIO_DELAY) # defautl is OTAA (old config file may not have this defined) JM = 0 try: from deviceid import lora_join_method JM = lora_join_method except: pass #add the multicasts try: from deviceid import multicasts for multicast_address in multicasts: multicast_devAddr = multicast_address[0] multicast_NwkSKey = multicast_address[1] multicast_AppSKey = multicast_address[2] try: lora.join_multicast_group(multicast_devAddr, multicast_NwkSKey, multicast_AppSKey) except: pass except: pass # define the reserved Downlink minutes if it exists DRM = [8] try: from deviceid import downlinks_reserved_minutes DRM = downlinks_reserved_minutes except: pass if pycom.nvs_get(POWER_FAIL_STATE) == MODE_POWER_FAIL: # SPECIAL CASE FOR POWER FAIL AFTER DEEP SLEEP WAKE UP lora.nvram_restore() processMsgImmediate( LoraQueueImmediate, logger, lora, lorawan_socket) # the PF message should already be in the queue if lora_queue_immediate_semaphore.locked(): lora_queue_immediate_semaphore.release( ) # tell the main thread we are done time.sleep(10) if JM == OTAA_JOIN: logger.debug("Attempting OTAA Join Kotahi.net...using device EUI " + str(binascii.hexlify(dev_eui))) lora.join(activation=LoRa.OTAA, auth=(dev_eui, app_eui, app_key), timeout=0) counter = 0 while not lora.has_joined(): time.sleep(1) counter = counter + 1 if lora_wdt_lock.locked(): lora_wdt_lock.release( ) # tell the WDT that LoRa task is busy waiting to join logger.debug("Join successful after " + str(counter) + "s") elif JM == ABP_JOIN: from deviceid import dev_addr, nwk_swkey, app_swkey logger.debug("Joined Kotahi.net using ABP with EUI " + str(binascii.hexlify(dev_eui))) lora.join(activation=LoRa.ABP, auth=(dev_addr, nwk_swkey, app_swkey)) #time.sleep(3) # save the state in NVS lora.nvram_save() #lora.nvram_save() # Lora loop starts here, processing the QOS queues while not lora_stop_flag.locked(): # wait 1s while checking for any message to be sent immediately (e.g PF Alarm) counter = 10 while counter > 0: if processMsgImmediate(LoraQueueImmediate, logger, lora, lorawan_socket) == True: if lora_queue_immediate_semaphore.locked(): lora_queue_immediate_semaphore.release( ) # signal that the immediate msg has been sent time.sleep(1) counter = counter - 1 time.sleep_ms(100) # Process any downlinks lora_rx_data(lora, lorawan_socket, logger) if lora_wdt_lock.locked(): lora_wdt_lock.release( ) # tell the WDT that LoRa task is doing just fine unixLocalTime = time.time() + time.timezone() timetuple = time.localtime() maskUplinks = False for minute in DRM: if (timetuple[4] % 10 == minute): maskUplinks = True break # process the uplinks using basic QOS Queues if maskUplinks == False: processQOSQueue(LoraQueueP5, 5, unixLocalTime, logger, lora, lorawan_socket, RadioOffset) processQOSQueue(LoraQueueP4, 4, unixLocalTime, logger, lora, lorawan_socket, RadioOffset) processQOSQueue(LoraQueueP3, 3, unixLocalTime, logger, lora, lorawan_socket, RadioOffset) processQOSQueue(LoraQueueP2, 2, unixLocalTime, logger, lora, lorawan_socket, RadioOffset) processQOSQueue(LoraQueueP1, 1, unixLocalTime, logger, lora, lorawan_socket, RadioOffset) logger.error('** LORA Task ended **')