forked from mayeranalytics/pySX127x
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLoRaMAC.py
666 lines (524 loc) · 28.1 KB
/
LoRaMAC.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
#!/usr/bin/env python2.7
""" LoRaMAC """
""" Thomas Verbeke - [email protected]
HAL (pySX127x) is based on a fork from Mayer Analytics
This class implements a Lightweight MAC protocol "LoRa Lightweight MAC"
It has the following features:
*ETSI compliance is imposed with duty cycle limit/ band
*Payload header for packet filtering (iterative algortihm)
*Optional ACK in receive window
*MAC commands to change SF/Channel/Power
*Can be used in star of stars network configuration but direct node to node communication is also possible
# ADD FIXED PACKET LENGTH, IQ inversion, hopperiod, timeout, freq, power, over current protection
"""
import time
from math import ceil, floor
from time import sleep
from SX127x.LoRa import *
from SX127x.board_config import BOARD
BOARD.setup()
""" 868 ISM BANDS
BAND / DutyCycle / MaxPower / Channel Configuration Options / LoRaWAN use
=============================================================================================================================================================================
h1.2 / 1% / 14 dBm / 15 x 125 or 10x 250 or 4x500 / 5X125 kHz
h1.4 / 1% / 14 dBm / 3x 125 or 1x250 or 1x 500** / 3X125 or 2x125 and 1x250 kHz
h1.5 / 0.1% / 14 dBm / 2x 125 or 1x250 / 1XFSK
h1.6 / 10% / 27 dBm / 1x 125 / Reception Window channel
h1.7 / 100% / 7 dBm / 1x 125 / not used
Band h1.6 is used as the BEACON channel or reception channel
Band h1.7 is used as last resort channel when device has used up all of its available duty cycle resources
** Modify Tx filter settings needed: done by writing "0xE1" in register 0x3D
"""
BAND0_868 = {'name': "RF_Band_868_h1.2" , 'dutyCycle' : 100, 'maxPower' : 14, 'maxTxTime' : 36000 } # 1% use, maxTxTime in ms
BAND1_868 = {'name': "RF_Band_868_h1.4" , 'dutyCycle' : 100, 'maxPower' : 14, 'maxTxTime' : 36000} # 1% use
BAND2_868 = {'name': "RF_Band_868_h1.5" , 'dutyCycle' : 10000, 'maxPower' : 14, 'maxTxTime' : 3600} # 0.1% use
BAND3_868 = {'name': "RF_Band_868_h1.6" , 'dutyCycle' : 10, 'maxPower' : 14, 'maxTxTime' : 360000} #mbed platform only connect high efficiency amp so TX power is limited at 14 dBm # 10% use
BAND4_868 = {'name': "RF_Band_868_h1.7" , 'dutyCycle' : 1, 'maxPower' : 7, 'maxTxTime' : 3600000} #100% use
bands_868 = {'BAND0' : BAND0_868, 'BAND1' : BAND1_868, 'BAND2' : BAND2_868, 'BAND3' : BAND3_868, 'BAND4' : BAND4_868}
#default channels in bands h1.6 and h1.7
channel_D1_868= {'name' : 'CH_D1_868_Def_BEACON' , 'freq' : 869.525, 'bw' : BW.BW_125, 'band' : BAND3_868} #Freq is center freq
channel_D2_868= {'name' : 'CH_D2_868_Def_ALWAYS_ON' , 'freq' : 869.850 , 'bw' : BW.BW_125, 'band' : BAND4_868} #Freq is center freq
"""
#Channel configurations
OPTION 1: High capacity : conf h1.2 to use 15x125 kHz and use h1.4 and h1.5 as the high speed channel
OPTION 2: High speed: 4x500 kHz channels in the h1.2 band and 3x125 kHz channels in h1.4 and 1x250 kHz channel in h1.5
OPTION 3: LoRaWAN avoidance mode: avoid h1.4 band and use h1.2 band from 865.1-566.9 (10x 125 kHz channels)
OPTION 4: Testing: 1x 125 kHz channel, 1x250 kHz channel and 1x500 kHz channel in both h1.2 and
"""
channel1_868 = {'name' : 'CH1_868_125' , 'freq' : 865.100 , 'bw' : BW.BW_125, 'band' : BAND0_868}
channel2_868 = {'name' : 'CH2_868_125' , 'freq' : 865.200 , 'bw' : BW.BW_250, 'band' : BAND0_868}
channel3_868 = {'name' : 'CH3_868_125' , 'freq' : 865.600 , 'bw' : BW.BW_500, 'band' : BAND0_868}
channel4_868 = {'name' : 'CH4_868_125' , 'freq' : 868.050 , 'bw' : BW.BW_125, 'band' : BAND1_868}
channel5_868 = {'name' : 'CH5_868_125' , 'freq' : 868.300 , 'bw' : BW.BW_250, 'band' : BAND1_868}
channel6_868 = {'name' : 'CH6_868_125' , 'freq' : 868.300 , 'bw' : BW.BW_500, 'band' : BAND1_868} #warning: to use this channel register "0xE1" must be written in register 0x3D
ackList = {"admin_sf":False, "admin_channel":False, "frame": False, "admin_link": False}
class LoRaMAC(LoRa):
def __init__(self, verbose=False):
super(LoRaMAC, self).__init__(False) # show debug info (device mode)
self.transmission = {'headerOff' : False , 'channel': channel_D2_868, 'sf': 7, 'cr': CODING_RATE.CR4_5 , 'rx_crc_on': True, 'preambleLen': 8, 'low_data_rate_optim': False}
self.state = {'wf_ack' : False, 'wf_follow' : False, 'wf_p2p' : False, 'wf_for_join' : False} #wf: waiting for
self.retries = 0
self.lastFrame = None
self.options = { "numRetries" :3}
self.incoming = {'type' : None, 'payload' : None}
self.device = {'addr_high_h' : 0x00, 'addr_high_l' : 0x00, 'addr_low_h' : 0x00, 'addr_low_l' : 0x00 }
self.fifo = {'rx_current_addr': 0, 'tx_current_addr' : 0x80}
self.queue = {'delayed_write': False, 'frame' : {'data' : [], 'size' :0 }, 'timeOnAir' : 0, 'band' : BAND0_868 }
self.timer = 0
self.time_elapsed = 0
self.time_elapsed_tmp = 0
self.remainingDutyCycle_time = [] #format: band / timeStamp / timeOnAir
self.channelList = []
self.addChannels()
""" Add default channels """
def addChannels(self):
self.channelList.append(channel_D1_868)
self.channelList.append(channel_D2_868)
self.channelList.append(channel1_868)
self.channelList.append(channel2_868)
self.channelList.append(channel3_868)
self.channelList.append(channel4_868)
self.channelList.append(channel5_868)
self.channelList.append(channel6_868)
""" Updates the transceiver configuration registers """
def updateConfiguration(self):
print ("\n(UpdateConfig)")
self.set_mode(MODE.SLEEP)
self.mode = MODE.SLEEP
self.set_modem_config_1(bw=self.transmission['channel'] ['bw'], coding_rate=self.transmission['cr'], implicit_header_mode=self.transmission['headerOff'])
self.set_modem_config_2(spreading_factor=self.transmission['sf'], rx_crc=self.transmission['rx_crc_on'])
self.set_modem_config_3(low_data_rate_optim=(self.transmission['low_data_rate_optim']))
self.set_preamble(self.transmission['preambleLen'])
self.set_freq(self.transmission['channel'] ['freq'])
self.set_pa_config(pa_select=PA_SELECT.RFO) #NOTE: LoRa MBED board does not connect PA_BOOST
""" Duty Cycle Mgmt functions """
def addDutyCycle(self,band,timeOnAir):
print "*** Substracted timeOnAir (%s) from timeLeft" % timeOnAir
timestamp = time.time()
self.remainingDutyCycle_time.append((band['name'], timestamp, timeOnAir)) #append to list
""" Check if allocation of txTimeOnAir on band is available
Return Time Left on Band
"""
def checkDutyCycle(self, band, timeOnAir):
print ("\n(DutyCycle)")
if self.remainingDutyCycle_time:
totalTime = self.calcTotalTime(band) #calcTotalTime on given band
else:
totalTime=0
if (totalTime + timeOnAir) > band['maxTxTime'] :#if total time + txTimeOnAir > band['maxTxTime'] in seconds
print "*** Band full"
return 0
else:
timeLeft = band['maxTxTime']-totalTime
print "*** Time left on band: %s" % timeLeft
return timeLeft
""" calculate total time on air for subband, renove entries which are no longer applicable
"""
def calcTotalTime(self, band):
#print ("\n(DutyCycle): calcTotalTime")
#loop over list and return sum of timeOnAir; remove old entries
sum=0
for res in self.remainingDutyCycle_time:
if res[0] == band['name']:
currentTime = time.time()
#print currentTime, currentTime + 1
if (res[1] >= currentTime + 1): #compare timestamps; remove if older then 1 hour
list.remove(res)
print "removed old timestamp"
else:
sum = sum + res[2] # add timeOnAir
return sum
def openReceiveWindow(self, duration):
# DIO MAPPING
# 0x00 for RxDone (on DIO0) | 0x40 for TxDone (on DIO0)
# CadDone (on DIO3) | CadDone (on DIO3)
# 0x01 for RxDone (on DIO0) | 0x42 for TxDone (on DIO0)
# ValidHeader (on DIO3) | ValidHeader (on DIO3)
mapping = 0x01
self.spi.xfer([REG.LORA.DIO_MAPPING_1 | 0x80, mapping])[1]
self.get_dio_mapping() # update DIO mapping
sf = self.transmission['sf']
bw = 125e3
if self.transmission['channel'] ['bw'] == BW.BW_125:
bw = 125e3
elif self.transmission['channel'] ['bw'] == BW.BW_250:
bw = 250e3
elif self.transmission['channel'] ['bw'] == BW.BW_500:
bw = 500e3
rs = bw / (1 << sf)
ts = 1 / rs
TimeOut = ts * duration
print ("\n(OpenReceiveWindow) For %s symbols" % duration)
print ("*** Or %s s" % TimeOut)
#length of reception window writen in RegSymbTimeout register (should be in range 4-1023 symbols
#TimeOut = SymbTimout * Ts 0x1F
self.set_symb_timeout(duration)
#use PayloadCRCError interrupt for dropping packet if crc is not valid or do post prcessing (DIO3 config)
self.set_mode(MODE.RXSINGLE) #put transceiver into single rx mode for config duration
self.mode = MODE.RXSINGLE
""" Prepares the frame based on type (as defined in MAC spec)
"""
def prepareFrame(self, type, payload):
if type == "DATA_UP":
type = 0x00
elif type== "BEACON":
type = 0x01
elif type== "ACK":
type = 0x02
elif type== "P2P":
type = 0x03
elif type== "FOLLOW":
type = 0x04
elif type== "JOIN ACCEPT":
type = 0x05
elif type== "ADMIN":
type = 0x06
opts = 0x00 # ADDR_MODE_LARGE
major = 0x03 #LoRa Light; 0x00 for LoRaWAN
header = (type & 0x07) << 5 | (opts & 0x07) << 2 | (major & 0x03)
frame_HDR = [header]
frame_ADDR = [ self.device['addr_high_h'] , self.device['addr_high_l'] , self.device['addr_low_h'], self.device['addr_low_l'] ]
frame_PAYLOAD = payload# PING
frame = frame_HDR + frame_ADDR + frame_PAYLOAD
return {'data' : frame, 'size' : len(frame) }
""" Utility function to send a frame.
Calculates timeOnAir and checks if band is saturated on or not.
If band is saterated try other band; if all bands have been saturated do delayedWriteFrame
use option CAD to do a CAD before using the band
"""
def writeFrame(self, frame, CAD=True):
#print ("\n(WriteFrame) CAD: %s" % CAD)
timeOnAir = self.calc_time_on_air(frame['size']) #calculate timeOnAir (in ms)
band = self.transmission['channel']['band']
#print "*** TimeOnAir %s" % timeOnAir
# DIO MAPPING
# 0x00 for RxDone (on DIO0) | 0x40 for TxDone (on DIO0)
# CadDone (on DIO3) | CadDone (on DIO3)
# 0x01 for RxDone (on DIO0) | 0x42 for TxDone (on DIO0)
# ValidHeader (on DIO3) | ValidHeader (on DIO3)
mapping = 0x40
self.spi.xfer([REG.LORA.DIO_MAPPING_1 | 0x80, mapping])[1]
self.get_dio_mapping() # update DIO mapping
timeLeft = self.checkDutyCycle(band,timeOnAir)
if (timeLeft> 0):
#hourly duty cycle limit has not yet been reached
if CAD == True:
#do a channel activity detection
self.set_mode(MODE.SLEEP)
self.set_mode(MODE.CAD)
self.mode = MODE.CAD
self.queue['delayed_write']=True
self.queue['timeOnAir']=timeOnAir
self.queue['frame'] = frame
self.queue['band'] = band
sleep(0.1)
else:
self.write(band, timeOnAir, frame)
else:
print "band full, switching band"
sleep(5)
#delayed write
def write (self, band, timeOnAir, frame):
self.addDutyCycle(band,timeOnAir) # add duty cycle
self.set_payload_length(frame['size']) # set message length
base_addr = self.get_fifo_tx_base_addr() # SHOULD BE REMOVED; USELESS SPI CALL
self.set_fifo_addr_ptr(base_addr)
self.spi.xfer([REG.LORA.FIFO | 0x80] + frame['data'])[1:]
#print "*** Sending Message: %s" % frame
self.set_mode(MODE.TX) # send Message
self.lastFrame = frame # needed for retransmission
""" Utility function that calculates time on air """
def calc_time_on_air(self, pktLen):
cr = self.transmission['cr']
sf = self.transmission['sf']
rx_crc_on= self.transmission['rx_crc_on']
preambleLen= self.transmission['preambleLen']
low_data_rate_optim= self.transmission['low_data_rate_optim']
headerOff=self.transmission['headerOff']
if self.transmission['channel'] ['bw']== BW.BW_125:
bw = 125e3
elif self.transmission['channel'] ['bw'] == BW.BW_250:
bw = 250e3
elif self.transmission['channel'] ['bw']== BW.BW_500:
bw = 500e3
rs = bw / (1 << sf)
ts = 1 / rs
tPreamble = (preambleLen + 4.25)*ts
tmp = ceil( float(8*pktLen-4*sf+28+16*rx_crc_on-20*headerOff)/float(4*(sf-2*low_data_rate_optim)) ) * (cr+4)
nPayload = 8+max(tmp,0) # number of symbols that make up packet payload and header
tPayload = nPayload * ts
#time on air
tOnAir = tPreamble + tPayload
return floor(tOnAir * 1e6 + 0.999)/1000
""" TODO add power implemetation """
def send_MAC_AdminFrame(self, channel=False, sf=False, power=False, linkCheck=False):
payload = []
if channel != False: # change channel
type = 0x00 # 3 bits
ID = self.channelList.index(channel) # change this!!
channelID = ID & 0x1F # 5 bits
print "ID %s"%self.channelList[ID]["name"]
payload += [type + channelID]
ackList["admin_channel"]=True
self.transmission['channel'] = self.channelList[ID] #update channel (channel is only updated after receiving ack)
if sf != False: # change sf
type = (0x01 << 5) # 3 bits
spreadingF = (sf & 0x07) << 2 # 3 bits
payload += [type + spreadingF]
ackList["admin_sf"]=True
self.transmission['sf'] = sf+6 #update sf (sf is only updated after receiving ack)
if linkCheck == True: # request link check
type = (0x03 << 5) # 3 bits
payload += [type]
ackList["admin_link"] = True
elif (channel==False and sf==False and power==False and linkCheck==False):
print "no command given"
print channel, sf, power, linkCheck
return False
self.state['wf_ack']=ackList #set ACK
frame = self.prepareFrame("ADMIN",payload)
self.writeFrame(frame)
return True
""" process different types of ACK depending on ack type
TODO: check for empty payload
"""
def processACK(self, payload):
print("\n\n(processACK) Processing ACK Depending on state")
#make sure ACK doesn't have any payload
#print "*** payload %s" % payload
if payload == [0x50, 0x6F, 0x6E, 0x67]:
#loop over ALL flags
if self.state['wf_ack']['admin_channel'] == True:
#print "*** admin CHANNEL ACK"
#now change channel
#self.transmission['channel'] = channel_D2_868 #update channel
print "*** set channel to %s" % self.transmission['channel']['name']
if self.state['wf_ack']['admin_sf'] == True:
#print "*** admin SF ACK"
#now change sf
#self.transmission['sf'] = 7
print "*** set sf to %s" % self.transmission['sf']
if (self.state['wf_ack']['admin_link'] and (self.state['wf_ack']['admin_sf'] or self.state['wf_ack']['admin_channel']))==True:
print "*** admin LINK ACK"
elif ( self.state['wf_ack']['admin_link'] == True and self.state['wf_ack']['admin_sf']==False and self.state['wf_ack']['admin_channel'] == False ):
#skip update config
print "*** admin LINK ACK (or other unrecognised ACK"
self.state['wf_ack']=False #reset ACK flag
self.retries = 0 # reset retries
return True
self.state['wf_ack']=False #reset ACK flag
self.retries = 0 # reset retries
self.updateConfiguration()
return True
""" iterative algo that reads admin packet untill all MAC administration data has been processed
for now 1 byte per MAC frame but could be further compressed for SF/power change as 2 bits are unused
"""
def processADMIN_frame(self, payload):
print("\n\n(processADMIN) Processing incoming MAC Administration message")
numBytes_processed = 0
while numBytes_processed < len(payload) : #loop over payload until all MAC commands have been processed
type = (payload[numBytes_processed] >> 5) & 0x07 # 3 MSB
#next step iteration
if (type == 0x00): #change channel - 5 bit channel id
channelID = payload[numBytes_processed] & 0x1F # 5 LSB
channel = self.channelList[channelID]
print "*** type: change channel to: %s " % channel['name']
self.transmission['channel'] = channel #update channel
elif (type == 0x01): #change SF - 3 bit SF id
spreadingFactorID = (payload[numBytes_processed] >> 2) & 0x07
spreadingFactor = SF.lookup[spreadingFactorID]
print "*** type: change SF to %s" % spreadingFactor
self.transmission['sf'] = spreadingFactorID+6 #update sf
elif (type == 0x02): #change Power - 3 bit power id
powerSetting = (payload[numBytes_processed] >> 2) & 0x07
#add power setting in self.transmission
#check if power setting is allowed in band
#update power setting device
print "*** type: change power"
elif (type == 0x03): #linkCheck - empty
print "*** type: linkCheck"
#write all relevant information about frame to a file
#all config parameters plus rssi value
else:
print "*** MAC command not recognised"
numBytes_processed +=1
self.updateConfiguration() #when whole frame (multiple bytes) has been processed: update settings in device
""" only read payload and discard header and addr information
"""''' '''
def read_payload_noheader(self):
rx_nb_bytes = self.get_rx_nb_bytes()
fifo_rx_current_addr = self.get_fifo_rx_current_addr()
self.set_fifo_addr_ptr(fifo_rx_current_addr+5)
payload = self.spi.xfer([REG.LORA.FIFO] + [0] * (rx_nb_bytes - 5 ))[1:]
return payload
""" only read payload and discard header and addr information
"""''' '''
def read_payload(self):
rx_nb_bytes = self.get_rx_nb_bytes()
fifo_rx_current_addr = self.get_fifo_rx_current_addr()
self.set_fifo_addr_ptr(fifo_rx_current_addr)
payload = self.spi.xfer([REG.LORA.FIFO] + [0] * (rx_nb_bytes ))[1:]
return payload
""" Utility function that catches the cad_done interrupt. Note: CAD (Channel Acitivity Detection) analysis is not needed when duty cycled operation is used. """
def on_cad_done(self): # mapped to DIO3
print("\n(CadDone) CAD Done")
#read cadDetected IRQ flag to check if CAD was detected
cadDetected = self.get_irq_flags()['cad_detected']
#print self.get_irq_flags()
if (cadDetected):
print("*** Channel Activity Detected")
self.set_mode(MODE.SLEEP) # IRQ flags are reset in sleep mode
self.mode = MODE.SLEEP
#backoff strategy: run timer and try again; alternative: try different band/channel but receiver is not aware
sleep(1)
self.set_mode(MODE.CAD)
else:
print("*** No Channel Activity Detected")
self.set_irq_flags(cad_done=1,cad_detected=1)
#ok to transmit
if self.queue['delayed_write'] == True:
self.write(self.queue['band'], self.queue['timeOnAir'], self.queue['frame'])
""" Reads frame header and returns True or False if frame should be dropped or not based on header
"""
def processHeader(self, header):
type = (header >> 5) & 0x07
major = header & 0x03
if major != 0x03:
return False #LORAWAN FRAME
print ("*** LoRaWAN Frame Detected!! (or other non LoRaMAC frame")
else:
if (type == 0x00): #data up
print "*** type: beacon frame"
return True
elif (type == 0x01): #beacon frame
print "*** type: beacon frame"
return True
elif ((type == 0x02) & (self.state['wf_ack'] != False)): #ack frame
self.time_elapsed = self.time_elapsed_tmp
print "*** type: ack frame"
#print "*** timer (ms): %f" % (self.time_elapsed)
self.incoming['type'] = 'ACK' #set incoming data type
return True
elif ((type == 0x03) & (self.state['wf_p2p']== True)): #p2p frame
print "*** type: p2p frame"
return True
elif ((type == 0x04) & (self.state['wf_follow']== True)): #follow up frame
print "*** type: follow up frame"
return True
elif ((type == 0x05) & (self.state['wf_for_join']== True)): #join frame
print "*** type: join frame"
return True
elif (type == 0x06):
print "*** type: administration frame"
self.incoming['type'] = "ADMIN"
return True
else:
print "type not recognised"
print "type: %s" % type
print "major: %s" % major
print "state: %s" % self.state['wf_ack']
return False #drop packet and go to sleep
""" Packet filtering """
""" Reads frame header and returns True or False if frame should be dropped or not based on header
"""
def on_valid_header(self):
self.time_elapsed_tmp = time.clock() - self.timer
#print self.time_elapsed_tmp
print("\n\n(ValidHeader) Received a valid lora header")
#packet filtering based on preamble start (datasheet p. 42)
#iterative algorthm that processes MAC_HDR and MAC_ADDR to determine if packet is relevant; drop packet and go to sleep if not relevant in order to conserve energy
start_address = self.fifo['rx_current_addr']
numBytes_processed = 0
while numBytes_processed < 5 :
current_addr = self.get_fifo_rx_byte_addr()
numBytes_ready = current_addr-start_address
if ( numBytes_ready - numBytes_processed >=1):
#print "numBytes_ready %s" % numBytes_ready
#print "numBytes_processed %s" % numBytes_processed
self.set_fifo_addr_ptr(start_address+numBytes_processed)
frame = self.spi.xfer([REG.LORA.FIFO] + [0] * (numBytes_ready - numBytes_processed))[1:] #read first byte
#print "frame %s" % frame
add=0
if (numBytes_processed == 0): #process header
if (self.processHeader(frame[0]) == False):
print "*** bad payload header..putting transciever to sleep"
print "*** frame: %s" %frame[0]
#print self.state['wf_ack']
self.set_mode(MODE.SLEEP) #fifo data is cleared in sleep mode
sleep(0.1)
self.set_mode(MODE.RXCONT)
self.mode = MODE.RXCONT
break
else:
add +=1
if ((numBytes_processed < 3) & ((numBytes_ready) >=3)): #could potentially read more than 1 byte
#print "analy addr_high"
if (frame[1-numBytes_processed] != self.device ['addr_high_h'] or frame[2-numBytes_processed] != self.device ['addr_high_l'] ):
print "*** bad addr high..putting transciever to sleep"
print frame[1] != self.device ['addr_high_h'], frame[2] != self.device ['addr_high_l']
self.set_mode(MODE.SLEEP) #fifo data is cleared in sleep mode
self.set_mode(MODE.RXCONT)
self.mode = MODE.RXCONT
break
else:
add +=2
if (( numBytes_processed < 5) & ((numBytes_ready) >=5 )) :
#print "analy addr_low"
if (frame[3-numBytes_processed] != self.device ['addr_low_h'] or frame[4-numBytes_processed] != self.device ['addr_low_l'] ):
print "*** bad addr low..putting transciever to sleep"
self.set_mode(MODE.SLEEP) #fifo data is cleared in sleep mode
self.set_mode(MODE.RXCONT)
self.mode = MODE.RXCONT
break
else:
print "*** good packet"
add +=2
numBytes_processed += add
#still ok? then proceed to on_rx_done (still check for bad CRC!!)
self.set_irq_flags(valid_header=1) #clear interrupt flag
def __str__(self):
# don't use __str__ while in any mode other that SLEEP or STDBY
assert(self.mode == MODE.SLEEP or self.mode == MODE.STDBY)
onoff = lambda i: 'ON' if i else 'OFF'
f = self.get_freq()
cfg1 = self.get_modem_config_1()
cfg2 = self.get_modem_config_2()
cfg3 = self.get_modem_config_3()
pa_config = self.get_pa_config(convert_dBm=True)
ocp = self.get_ocp(convert_mA=True)
lna = self.get_lna()
s = "SX1276 LoRa registers:\n"
s += " channel %s\n" % self.transmission['channel']['name']
s += " band %s\n" % self.transmission['channel']['band']['name']
s += " mode %s\n" % MODE.lookup[self.get_mode()]
s += " center freq %f MHz\n" % f
s += " coding rate %s\n" % CODING_RATE.lookup[cfg1['coding_rate']]
s += " bandwidth %s\n" % BW.lookup[cfg1['bw']]
s += " chips %s chips/symb\n" % (1 << cfg2['spreading_factor'])
s += " spreading_factor %s " % (cfg2['spreading_factor'])
s += " header Off %s\n" % onoff(cfg1['implicit_header_mode'])
s += " payload crc %s\n" % onoff(cfg2['rx_crc'])
s += " preamble length %d\n" % self.get_preamble()
s += " low data rate opti %s\n" % onoff(cfg3['low_data_rate_optim'])
s += " symb_timeout %s\n" % self.get_symb_timeout()
s += " freq_hop_period %s\n" % self.get_hop_period()
s += " hop_channel %s\n" % self.get_hop_channel()
s += " payload_length %s\n" % self.get_payload_length()
s += " max_payload_length %s\n" % self.get_max_payload_length()
s += " irq_flags_mask %s\n" % self.get_irq_flags_mask()
s += " irq_flags %s\n" % self.get_irq_flags()
s += " rx_nb_byte %d\n" % self.get_rx_nb_bytes()
s += " rx_header_cnt %d\n" % self.get_rx_header_cnt()
s += " rx_packet_cnt %d\n" % self.get_rx_packet_cnt()
s += " pkt_snr_value %f\n" % self.get_pkt_snr_value()
s += " pkt_rssi_value %d\n" % self.get_pkt_rssi_value()
s += " rssi_value %d\n" % self.get_rssi_value()
s += " pa_select %s\n" % PA_SELECT.lookup[pa_config['pa_select']]
s += " max_power %f dBm\n" % pa_config['max_power']
s += " output_power %f dBm\n" % pa_config['output_power']
s += " sync_word %#02x\n" % self.get_sync_word()
s += " dio_mapping 0..5 %s\n" % self.get_dio_mapping()
s += " pa_dac %s\n" % ['default', 'PA_BOOST'][self.get_pa_dac()]
s += " status %s\n" % self.get_modem_status()
s += " version %#02x\n" % self.get_version()
return s