-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathlorasim_engine.py
More file actions
328 lines (250 loc) · 8.74 KB
/
lorasim_engine.py
File metadata and controls
328 lines (250 loc) · 8.74 KB
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import simpy
import weakref
import numpy as np
import multiprocessing as mp
##### simulator options #####
useAimModel = True
### The idea is to modularize the simulator without global variables
class loraNetwork():
"""[summary]
[description]
Variables:
"""
def __init__(self, nThreads):
"""[summary]
[description]
Arguments:
nthreads {int} -- The number of CPU threads to use. Use <= nCPUs
"""
# initialize as many environments as required by multiprocessing
self.nThreads = nThreads
print("CPU Threads to use: {}".format(self.nThreads))
self.bsDict = []
self.nodeDict = []
for t in range(nThreads):
# initialize dictionaries for node storage
self.bsDict.extend({})
self.nodeDict.extend({})
# lorasimenv = simpy.Environment()
def addRadios(self):
pass
def resetEnvironment(self):
pass
def simulate(self, endTime):
pass
class myBS_AIM():
"""base station object for AIM
[description]
Variables:
"""
def __init__(self, bsid, position, fset, networkid):
self.bsid = bsid
self.x, self.y = position
self.fset = fset
self.networkid = networkid
self.packets = {}
freqBuckets = lora_tools.getFreqBucketsFromSet(fset)
self.packetsInBucket = {}
self.signalLevel = {}
for freq in freqBuckets:
self.packetsInBucket[freq] = {}
self.signalLevel[freq] = np.zeros((6,1))
self.demodulator = set() # the set of demodulators being occupied
def addPacket(self, nodeid, packet):
"""Send a packet to the base station"""
for fbucket in packet.signalLevel.keys():
self.signalLevel[fbucket] = self.signalLevel[fbucket] + packet.signalLevel[fbucket]
self.evaluateFreqBucket(fbucket)
self.packetsInBucket[fbucket][nodeid] = packet
self.packets[nodeid] = packet
def evaluateFreqBucket(self, fbucket):
signalInBucket = np.dot(interactionMatrix, self.signalLevel[fbucket])
for nodeid, pkt in self.packetsInBucket[fbucket].viewitems():
if not pkt.isLost and pkt.isCritical:
if (1 + dBmtonW(6))*(pkt.signalLevel[fbucket][pkt.sf - 7]) < signalInBucket[pkt.sf - 7]:
# if (dBmtonW(6))*(pkt.signalLevel[fbucket][pkt.sf - 7]) < signalInBucket[pkt.sf - 7]:
pkt.isLost = True
def makeCritical(self, nodeid):
"""Packet from node enters critical section"""
pkt = self.packets[nodeid]
if not pkt.isLost:
if self.evaluatePacket(nodeid) and len(self.demodulator) <= nDemodulator and (pkt.fc, pkt.bw, pkt.sf) not in self.demodulator:
self.demodulator.add((pkt.fc, pkt.bw, pkt.sf))
pkt.isCritical = True
else:
pkt.isLost = True
pkt.isCritical = False
def evaluatePacket(self, nodeid):
pkt = self.packets[nodeid]
if pkt.isLost:
return False
else:
lostFlag = False
for fbucket in pkt.signalLevel.viewkeys():
# if np.random.randint(0,10000) == 747:
# print self.signalLevel[fbucket]
signalInBucket = np.dot(interactionMatrix[pkt.sf - 7].reshape(1,6), self.signalLevel[fbucket])
if (1 + dBmtonW(6))*(pkt.signalLevel[fbucket][pkt.sf - 7]) < signalInBucket:
# if (dBmtonW(6))*(pkt.signalLevel[fbucket][pkt.sf - 7]) < signalInBucket:
lostFlag = True
return not lostFlag
def removePacket(self, nodeid):
"""Stop sending a packet to the base station i.e. Remove it from all relevant lists"""
pkt = self.packets[nodeid]
# if packet was being demodulated, free the demodulator
if pkt.isCritical and (pkt.fc, pkt.bw, pkt.sf) in self.demodulator:
# only successfully demodulated packets i.e. Those that are critical are considered to be received
self.demodulator.remove((pkt.fc, pkt.bw, pkt.sf))
for fbucket in pkt.signalLevel.viewkeys():
self.signalLevel[fbucket] = self.signalLevel[fbucket] - pkt.signalLevel[fbucket]
# if np.random.randint(0,10000) == 747:
# print self.signalLevel[fbucket]
foo = self.packetsInBucket[fbucket].pop(nodeid)
foo = self.packets.pop(nodeid)
return pkt.isCritical and not pkt.isLost
class myNode_AIM():
"""node object for AIM
[description]
Variables:
"""
def __init__(self, nodeid, position, fset, bw, sf, cr, pTX, period, bsList):
self.nodeid = nodeid
self.x, self.y = position
self.fset = fset
self.bw = bw
self.sf = sf
self.cr = cr
self.pTX = pTX
self.period = period
self.packetNumber = 0
# counters for performance metrics
self.packetsTransmitted = 0
self.packetsSuccessful = 0
self.proximateBS = self.generateProximateBS(bsList)
self.packets = self.generatePacketsToBS()
def generateProximateBS(self, bsList):
# generate dictionary of base-stations in proximity
maxInterferenceDist = getDistanceFromPL(self.pTX, interferenceThreshold)
dist = np.sqrt((bsList[:,1] - self.x)**2 + (bsList[:,2] - self.y)**2)
index = np.nonzero(dist <= maxInterferenceDist)
proximateBS = {} # create empty dictionary
for i in index[0]:
proximateBS[int(bsList[i,0])] = dist[i]
return proximateBS
def generatePacketsToBS(self):
packets = {} # empty dictionary to store packets originating at a node
for bsid, dist in self.proximateBS.viewitems():
packets[bsid] = myPacket_AIM(self.nodeid, bsid, dist, self.fset, self.bw, self.sf, self.cr, self.pTX)
return packets
def updateTXSettings(self):
pass
class myPacket_AIM():
"""[summary]
[description]
Variables:
"""
def __init__(self, nodeid, bsid, dist, fset, bw, sf, cr, pTX):
self.nodeif = nodeid
self.bsid = bsid
self.dist = dist
self.fset = fset
self.bw = bw
self.sf = sf
self.cr = cr
self.pTX = pTX
self.pRX = getRXPower(pTX, self.dist)
self.signalLevel = None
# self.hoppingSequence = self.generateHoppingSequence()
self.fc = None
self.packetNumber = 0
self.isLost = False
self.isCritical = False
def computePowerDist(self):
global bsDict
signalLevel = self.getPowerContribution()
signalLevel = {x:signalLevel[x] for x in signalLevel.viewkeys() & bsDict[self.bsid].signalLevel.viewkeys()}
return signalLevel
def updateTXSettings(self, seedNo):
self.packetNumber += 1
self.fc = 902300 + 200*8*self.fset
# self.fc = self.hoppingSequence[seedNo % len(self.hoppingSequence)]
self.signalLevel = self.computePowerDist()
if self.pRX >= sensi[self.sf-7, 1+int(self.bw/250)]:
self.isLost = False
else:
self.isLost = True
self.isCritical = False
def getAffectedFreqBuckets(self):
"""This funtion returns a list of affected buckets"""
low = self.fc - self.bw/2 # Note: this is approx due to integer division for 125
high = self.fc + self.bw/2 # Note: this is approx due to integer division for 125
lowBucketStart = low - (low % 200) + 100
highBucketEnd = high + 200 - (high % 200) - 100
# the +1 ensures that the last value is included in the set
return xrange(lowBucketStart, highBucketEnd + 1, 200)
def getPowerContribution(self):
"""This function should return the power contribution of a packet in various frequency buckets"""
freqBuckets = self.getAffectedFreqBuckets()
nBuckets = len(freqBuckets)
powermW = dBmtonW(self.pRX)
if nBuckets == 1:
# this is the most common case with 125 kHz BW in the center of the bucket
signal = np.zeros((nSF,1))
signal[self.sf-7] = powermW
return {freqBuckets[0]:signal}
elif nBuckets == 4 and self.fc == freqBuckets[0] + 300:
# this is the second most common case with 500 kHz BW spread between 4 channels
signalDict = {}
for i,freq in enumerate(freqBuckets):
signal = np.zeros((nSF,1))
if i == 0 or i == 3:
signal[self.sf-7] = 0.1 * powermW
else:
signal[self.sf-7] = 0.4 * powermW
signalDict[freq] = signal
return signalDict
else:
raise NotImplementedError("non-centered frequencies and 250 kHz not implemented")
def generateHoppingSequence(self):
freqSetList = np.unique(self.fset)
freqBuckets = []
if self.bw == 125:
for i in freqSetList:
if i < 8:
freqBuckets.extend(np.linspace(902300 + 200*8*i, 903700 + 200*8*i, 8, dtype=int))
else:
# assuming channel 5 is used for white spaces
freqBuckets.extend(np.linspace(72100 + 200*8*(i-8), 73700 + 200*8*(i-8), 8, dtype=int))
elif self.bw == 500:
if self.fset < 8:
freqBuckets.extend([903000 + 1600*i])
else:
# white space
freqBuckets.extend([72900 + 1600*(i-8)])
pass
return np.random.permutation(freqBuckets)
class myBS_IIM():
"""base station object for IIM
[description]
Variables:
"""
def __init__(self):
pass
class myNode_IIM():
"""node object for IIM
[description]
Variables:
"""
def __init__(self):
raise NotImplementedError("myNode_IIM not yet implemented")
class myPacket_IIM():
"""[summary]
[description]
Variables:
"""
def __init__(self):
pass
if __name__ == "__main__":
print("This module is meant to be imported, not run directly")