-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathloractp.py
364 lines (302 loc) · 14.1 KB
/
loractp.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
"""
LoRa CTP (Content Transfer Protocol)
based on stop & wait like protocol and adapted to a LoRa raw channel
Tested on a LoPy4 quadruple bearer MicroPython enabled development board.
Inspired by https://github.com/arturenault/reliable-transport-protocol by Artur Upton Renault
First version by: Pietro GRC dic2017
Modified by: Pietro GRC apr2020
Updated by: Kiyoshy Nakamura GRC may2020
"""
from network import LoRa
import binascii
import gc
import hashlib
import machine
import socket
import struct
import sys
import time
import network
__version__ = '0'
class CTPendpoint:
# Set to True for debugging messages
DEBUG_MODE = True
HARD_DEBUG_MODE = True
MAX_PKT_SIZE = 230 # Maximum pkt size in LoRa with Spread Factor 7
HEADER_SIZE = 20
PAYLOAD_SIZE = MAX_PKT_SIZE - HEADER_SIZE
# header structure:
# 8 bytes: source addr (last 8 bytes)
# 8 bytes: dest addr (last 8 bytes)
# 1 byte: flags
# 3 bytes: checksum
HEADER_FORMAT = "!8s8sB3s"
PAYLOAD_FORMAT = "!202s"
ITS_DATA_PACKET = False
ITS_ACK_PACKET = True
ANY_ADDR = b'\x00\x00\x00\x00\x00\x00\x00\x00'
ONE = 1
ZERO = 0
def __init__(self):
self.lora = LoRa(mode = LoRa.LORA,
frequency = 868000000,
tx_power = 14,
bandwidth = LoRa.BW_125KHZ,
sf = 7,
preamble = 8,
coding_rate = LoRa.CODING_4_5,
power_mode = LoRa.ALWAYS_ON,
tx_iq = False,
rx_iq = False,
public = True,
tx_retries = 1,
device_class = LoRa.CLASS_A)
# Get lora mac address
self.lora_mac = binascii.hexlify(network.LoRa().mac())
self.my_addr = self.lora_mac[8:]
# Create a raw LoRa socket
self.s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
#
# BEGIN: Utility functions
#
# Create a packet from the necessary parameters
def __make_packet(self, s_addr, d_addr, seqnum, acknum, pkt_type, is_last, content):
# s_addr: bytes, d_addr: bytes, seqnum: int, acknum: int, pkt_type: boolean, is_last: boolean, content: bytes
if self.HARD_DEBUG_MODE: print("DEBUG 080:", s_addr, d_addr, seqnum, acknum, pkt_type, is_last, content)
flags = 0
if seqnum == self.ONE:
flags = flags | (1<<0)
if acknum == self.ONE:
flags = flags | (1<<2)
if is_last:
flags = flags | (1<<4)
if pkt_type == self.ITS_ACK_PACKET:
flags = flags | (1<<6)
if (len(content)>0 and (pkt_type == self.ITS_DATA_PACKET)):
# p = struct.pack(self.PAYLOAD_FORMAT, content)
p = content
check = self.__get_checksum(p)
h = struct.pack(self.HEADER_FORMAT, s_addr, d_addr, flags, check)
if self.HARD_DEBUG_MODE: print("DEBUG 096:", h+p)
else:
p = b''
h = struct.pack(self.HEADER_FORMAT, s_addr, d_addr, flags, b'')
if self.HARD_DEBUG_MODE: print("DEBUG 100:", h+p)
return h + p
# Break a packet into its component parts
def __unpack(self, packet):
header = packet[:self.HEADER_SIZE]
content = packet[self.HEADER_SIZE:]
sp, dp, flags, check = struct.unpack(self.HEADER_FORMAT, header)
seqnum = self.ONE if ((flags) & 1) & 1 else self.ZERO
acknum = self.ONE if (flags >> 2) & 1 else self.ZERO
is_last = (flags >> 4) & 1 == 1
pkt_type = (flags >> 6) & 1 == 1
if (content == b''):
payload = b''
else:
payload = content
return sp, dp, seqnum, acknum, pkt_type, is_last, check, payload
def __get_checksum(self, data):
# data: byte -> byte:
if self.HARD_DEBUG_MODE: print("DEBUG 123: in get_checksum->", data)
print("-----LEN DATA: ", len(data))
h = hashlib.sha256(data)
ha = binascii.hexlify(h.digest())
if self.HARD_DEBUG_MODE: print("DEBUG 126: in get_checksum->", ha[-3:])
return (ha[-3:])
def __debug_printpacket(self, msg, packet, cont=False):
sp, dp, seqnum, acknum, pkt_type, is_last, check, content = self.__unpack(packet)
if cont:
print ("DEBUG {}: s_a: {}, d_a: {}, seqn: {}, ackn: {}, is-ack: {}, fin: {}, check: {}, cont: {}".format(msg, sp, dp, seqnum, acknum, pkt_type, is_last, check, content))
else:
print ("DEBUG {}: s_a: {}, d_a: {}, seqn: {}, ackn: {}, is_ack: {}, fin: {}, check: {}".format(msg, sp, dp, seqnum, acknum, pkt_type, is_last, check))
def __timeout(self, signum, frame):
raise socket.timeout
#
# END: Utility functions
#
def _csend(self, payload, the_sock, sndr_addr, rcvr_addr):
# Shortening addresses to last 8 bytes to save space in packet
sndr_addr = sndr_addr[8:]
rcvr_addr = rcvr_addr[8:]
if self.DEBUG_MODE: print ("DEBUG 148: sndr_addr, rcvr_addr", sndr_addr, rcvr_addr)
# computing payload (content) size as "totptbs" = total packets to be sent
if (len(payload)==0): print ("WARNING csend: payload size == 0... continuing")
totptbs = int(len(payload) / self.PAYLOAD_SIZE)
if ((len(payload) % self.PAYLOAD_SIZE)!=0): totptbs += 1
if self.DEBUG_MODE: print ("DEBUG 155: Total packages to be send: ", totptbs) ###
timeout_value = 5 ###
# Initialize stats counters
FAILED = 0
stats_psent = 0
stats_retrans = 0
# RTT estimators
timeout_time = 1 # 1 second
estimated_rtt = -1
dev_rtt = 1
the_sock.settimeout(5) # 5 seconds initial timeout... LoRa is slow
# stop and wait
seqnum = self.ZERO
acknum = self.ONE
# Enabling garbage collection
gc.enable()
gc.collect()
for cp in range(totptbs):
if self.DEBUG_MODE: print ("DEBUG 178: Packet counter: ", cp) ###
last_pkt = True if (cp == (totptbs-1)) else False
# Getting a block of max self.PAYLOAD_SIZE from "payload"
blocktbs = payload[0:self.PAYLOAD_SIZE] # Taking self.PAYLOAD_SIZE bytes ToBeSent
payload = payload[self.PAYLOAD_SIZE:] # Shifting the input string
packet = self.__make_packet(sndr_addr, rcvr_addr, seqnum, acknum, self.ITS_DATA_PACKET, last_pkt, blocktbs)
if self.DEBUG_MODE: self.__debug_printpacket("186: sending packet", packet)
# trying 3 times
keep_trying = 3
while (keep_trying > 0):
try:
time.sleep((3-keep_trying))
the_sock.setblocking(True)
send_time = time.time()
the_sock.send(packet)
# waiting for the ack
the_sock.settimeout(timeout_value)
if self.DEBUG_MODE: print("DEBUG 200: waiting ACK")
ack = the_sock.recv(self.HEADER_SIZE)
recv_time = time.time()
if self.DEBUG_MODE: print("DEBUG 203: received ack", ack)
# self.__unpack packet information
ack_saddr, ack_daddr, ack_seqnum, ack_acknum, ack_is_ack, ack_final, ack_check, ack_content = self.__unpack(ack)
if (rcvr_addr == self.ANY_ADDR) or (rcvr_addr == b''):
rcvr_addr = ack_saddr # in case rcvr_addr was self.ANY_ADDR and payload needs many packets
# Check if valid...
if (ack_is_ack) and (ack_acknum == seqnum) and (sndr_addr == ack_daddr) and (rcvr_addr == ack_saddr):
stats_psent += 1
# No more need to retry
break
else:
# Received packet not valid
if self.DEBUG_MODE: print ("ERROR: ACK received not valid")
except socket.timeout:
if self.DEBUG_MODE: print("EXCEPTION!! Socket timeout: ", time.time())
if self.DEBUG_MODE: self.__debug_printpacket("re-sending packet", packet)
if self.DEBUG_MODE: print ("DEBUG 222: attempt number: ", keep_trying)
stats_psent += 1
stats_retrans += 1
keep_trying -= 1
if(keep_trying == 0):
FAILED = -1
break
# Check if last packet or failed to send a packet...
if last_pkt or (FAILED<0): break
# RTT calculations
sample_rtt = recv_time - send_time
if estimated_rtt == -1:
estimated_rtt = sample_rtt
else:
estimated_rtt = estimated_rtt * 0.875 + sample_rtt * 0.125
dev_rtt = 0.75 * dev_rtt + 0.25 * abs(sample_rtt - estimated_rtt)
timeout_value = (estimated_rtt + 4 * dev_rtt) ###
if self.DEBUG_MODE: print ("241: setting timeout to", estimated_rtt + 4 * dev_rtt)
# Increment sequence and ack numbers
seqnum = (seqnum + self.ONE) % 2 # self.ONE if seqnum == self.ZERO else self.ZERO
acknum = (acknum + self.ONE) % 2 # self.ONE if acknum == self.ZERO else self.ZERO
if self.DEBUG_MODE: print ("DEBUG 247: RETURNING tsend")
if self.DEBUG_MODE: print ("DEBUG 248: Retrans: ", stats_retrans) ###
# Enabling garbage collection
gc.enable()
gc.collect()
payload = ""
blocktbs = []
payload = []
packet = ""
return rcvr_addr, stats_psent, stats_retrans, FAILED
def _crecv(self, the_sock, my_addr, snd_addr):
# Shortening addresses to last 8 bytes
my_addr = my_addr[8:]
snd_addr = snd_addr[8:]
if self.DEBUG_MODE: print ("DEBUG 264: my_addr, snd_addr: ", my_addr, snd_addr)
# Buffer storing the received data to be returned
rcvd_data = b''
last_check = 0
next_acknum = self.ONE
the_sock.settimeout(5)
if (snd_addr == self.ANY_ADDR) or (snd_addr == b''): SENDER_ADDR_KNOWN = False
self.p_resend = 0
# Enabling garbage collection
gc.enable()
gc.collect()
while True:
try:
the_sock.setblocking(True)
packet = the_sock.recv(self.MAX_PKT_SIZE)
if self.DEBUG_MODE: print ("DEBUG 283: packet received: ", packet)
inp_src_addr, inp_dst_addr, inp_seqnum, inp_acknum, is_ack, last_pkt, check, content = self.__unpack(packet)
# getting sender address, if unknown, with the first packet
if (not SENDER_ADDR_KNOWN):
snd_addr = inp_src_addr
SENDER_ID_KNOWN = True
# Checking if a "valid" packet... i.e., either for me or broadcast
if (inp_dst_addr != my_addr) and (inp_dst_addr != self.ANY_ADDR):
if self.DEBUG_MODE: print("DISCARDED received packet not for me!!")
continue
except socket.timeout:
if self.DEBUG_MODE: print ("EXCEPTION!! Socket timeout: ", time.time())
continue
except Exception as e:
print ("EXCEPTION!! Packet not valid: ", e)
continue
if self.DEBUG_MODE: print("DEBUG 300: get_checksum(content)", self.__get_checksum(content))
checksum_OK = (check == self.__get_checksum(content))
if (checksum_OK) and (next_acknum == inp_acknum) and (snd_addr == inp_src_addr):
rcvd_data += content
last_check = check
# Sending ACK
next_acknum = (inp_acknum + self.ONE) % 2
ack_segment = self.__make_packet(my_addr, inp_src_addr, inp_seqnum, next_acknum, self.ITS_ACK_PACKET, last_pkt, b'')
if self.DEBUG_MODE: print ("DEBUG 310: ACK sent: ", self.p_resend)
self.p_resend = self.p_resend + 1
the_sock.setblocking(False)
the_sock.send(ack_segment)
if self.DEBUG_MODE: print("DEBUG 314: Sent ACK: ", ack_segment)
if (last_pkt):
break
# KN: Handlig ACK lost
elif (checksum_OK) and (last_check == check) and (snd_addr == inp_src_addr):
rcvd_data += content
# KN: Re-Sending ACK
next_acknum = (inp_acknum + self.ZERO) % 2
ack_segment = self.__make_packet(my_addr, inp_src_addr, inp_seqnum, next_acknum, self.ITS_ACK_PACKET, last_pkt, b'')
self.p_resend = self.p_resend -1
if self.DEBUG_MODE: print ("DEBUG 324: Forwarded ACK: ", self.p_resend)
the_sock.setblocking(False)
the_sock.send(ack_segment)
if self.DEBUG_MODE: print("DEBUG 327: re-sending ACK: ", ack_segment)
if (last_pkt):
break
else:
if self.DEBUG_MODE: print ("DEBUG 331: packet not valid", packet)
# Enabling garbage collection
gc.enable()
gc.collect()
# rcvd_data = b''
last_check = 0
packet = ""
content = ""
return rcvd_data, snd_addr
def connect(self, dest=ANY_ADDR):
print("loractp: connecting to... ", dest)
rcvr_addr, stats_psent, stats_retrans, FAILED = self._csend(b"CONNECT", self.s, self.lora_mac, dest)
return self.my_addr, rcvr_addr, stats_retrans, FAILED
def listen(self, sender=ANY_ADDR):
print("loractp: listening for...", sender)
rcvd_data, snd_addr = self._crecv(self.s, self.lora_mac, sender)
if (rcvd_data==b"CONNECT"):
return self.my_addr, snd_addr, 0
else:
return self.my_addr, snd_addr, -1
def sendit(self, addr=ANY_ADDR, payload=b''):
rcvr_addr, stats_psent, stats_retrans, FAILED = self._csend(payload, self.s, self.lora_mac, addr)
return rcvr_addr, stats_retrans, FAILED
def recvit(self, addr=ANY_ADDR):
rcvd_data, snd_addr = self._crecv(self.s, self.lora_mac, addr)
return rcvd_data, snd_addr