AlLoRa 1.2.0__tar.gz → 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- allora-2.0.0/AlLoRa/Connectors/Connector.py +323 -0
- allora-2.0.0/AlLoRa/Connectors/E5_connector.py +339 -0
- {AlLoRa-1.2.0 → allora-2.0.0}/AlLoRa/Connectors/LoPy4_connector.py +1 -0
- allora-2.0.0/AlLoRa/Connectors/SX1262_connector.py +119 -0
- allora-2.0.0/AlLoRa/Connectors/SX127x_connector.py +125 -0
- allora-2.0.0/AlLoRa/Connectors/Serial_connector.py +262 -0
- allora-2.0.0/AlLoRa/Connectors/Wifi_connector.py +124 -0
- allora-2.0.0/AlLoRa/Digital_Endpoint.py +189 -0
- allora-2.0.0/AlLoRa/File.py +147 -0
- allora-2.0.0/AlLoRa/Interfaces/Serial_interface.py +250 -0
- allora-2.0.0/AlLoRa/Interfaces/WiFi_interface.py +268 -0
- {AlLoRa-1.2.0 → allora-2.0.0}/AlLoRa/Nodes/Adapter.py +16 -5
- allora-2.0.0/AlLoRa/Nodes/Gateway.py +103 -0
- allora-2.0.0/AlLoRa/Nodes/Node.py +167 -0
- allora-2.0.0/AlLoRa/Nodes/Requester.py +490 -0
- {AlLoRa-1.2.0 → allora-2.0.0}/AlLoRa/Nodes/Source.py +117 -35
- {AlLoRa-1.2.0 → allora-2.0.0}/AlLoRa/Packet.py +164 -50
- allora-2.0.0/AlLoRa/utils/debug_utils.py +6 -0
- allora-2.0.0/AlLoRa/utils/json_utils.py +4 -0
- allora-2.0.0/AlLoRa/utils/os_utils.py +6 -0
- allora-2.0.0/AlLoRa/utils/time_utils.py +90 -0
- allora-2.0.0/AlLoRa.egg-info/PKG-INFO +377 -0
- allora-2.0.0/AlLoRa.egg-info/SOURCES.txt +77 -0
- allora-2.0.0/PKG-INFO +377 -0
- allora-2.0.0/README.md +351 -0
- allora-2.0.0/examples/Adapters/WiFi_adapters/WiFi-Client/boot.py +4 -0
- allora-2.0.0/examples/Adapters/WiFi_adapters/WiFi-Client/main_ESP32.py +7 -0
- allora-2.0.0/examples/Adapters/WiFi_adapters/WiFi-Client/main_LoPy4.py +7 -0
- allora-2.0.0/examples/Adapters/WiFi_adapters/WiFi-Hotspot/boot.py +4 -0
- allora-2.0.0/examples/Adapters/WiFi_adapters/WiFi-Hotspot/main_ESP32.py +9 -0
- allora-2.0.0/examples/Adapters/WiFi_adapters/WiFi-Hotspot/main_LoPy4.py +7 -0
- allora-2.0.0/examples/Gateways/Serial_Gateway/main.py +31 -0
- allora-2.0.0/examples/Gateways/Serial_Gateway/rf_changer.py +57 -0
- {AlLoRa-1.2.0/examples/RaspberryGateway → allora-2.0.0/examples/Gateways}/Wifi_Gateway/main.py +5 -5
- allora-2.0.0/examples/Requesters/LopyRequester/__init__.py +0 -0
- {AlLoRa-1.2.0/examples/LoPyReceiver → allora-2.0.0/examples/Requesters/LopyRequester}/main.py +1 -1
- allora-2.0.0/examples/Requesters/T3S3/__init__.py +0 -0
- allora-2.0.0/examples/Requesters/T3S3/main.py +34 -0
- allora-2.0.0/examples/Requesters/T3S3/main_pro.py +52 -0
- allora-2.0.0/examples/Requesters/__init__.py +0 -0
- allora-2.0.0/examples/Sources/ESP32/__init__.py +0 -0
- allora-2.0.0/examples/Sources/ESP32/boot.py +3 -0
- allora-2.0.0/examples/Sources/ESP32/main_Heltec.py +62 -0
- allora-2.0.0/examples/Sources/ESP32/main_TTGO.py +80 -0
- allora-2.0.0/examples/Sources/LopySource/__init__.py +0 -0
- {AlLoRa-1.2.0/examples/LoPySender → allora-2.0.0/examples/Sources/LopySource}/main.py +1 -1
- allora-2.0.0/examples/Sources/T3S3/__init__.py +0 -0
- allora-2.0.0/examples/Sources/T3S3/main.py +57 -0
- allora-2.0.0/examples/Sources/T3S3/main_pro.py +125 -0
- allora-2.0.0/examples/Sources/T3S3-Tester/__init__.py +0 -0
- allora-2.0.0/examples/Sources/T3S3-Tester/main.py +75 -0
- allora-2.0.0/examples/Sources/XIAO-ESP32S3/__init__.py +0 -0
- allora-2.0.0/examples/Sources/XIAO-ESP32S3/main.py +57 -0
- allora-2.0.0/examples/Sources/XIAO-ESP32S3/main_pro.py +121 -0
- allora-2.0.0/examples/Sources/__init__.py +0 -0
- allora-2.0.0/examples/__init__.py +0 -0
- {AlLoRa-1.2.0 → allora-2.0.0}/setup.py +4 -4
- AlLoRa-1.2.0/AlLoRa/Connectors/Connector.py +0 -120
- AlLoRa-1.2.0/AlLoRa/Connectors/SX127x_connector.py +0 -63
- AlLoRa-1.2.0/AlLoRa/Connectors/Serial_connector.py +0 -125
- AlLoRa-1.2.0/AlLoRa/Connectors/Wifi_connector.py +0 -88
- AlLoRa-1.2.0/AlLoRa/Digital_Endpoint.py +0 -117
- AlLoRa-1.2.0/AlLoRa/File.py +0 -113
- AlLoRa-1.2.0/AlLoRa/Interfaces/Serial_interface.py +0 -150
- AlLoRa-1.2.0/AlLoRa/Interfaces/Wifi_client.py +0 -109
- AlLoRa-1.2.0/AlLoRa/Interfaces/Wifi_hotspot.py +0 -73
- AlLoRa-1.2.0/AlLoRa/Nodes/Gateway.py +0 -73
- AlLoRa-1.2.0/AlLoRa/Nodes/Node.py +0 -102
- AlLoRa-1.2.0/AlLoRa/Nodes/Requester.py +0 -170
- AlLoRa-1.2.0/AlLoRa.egg-info/PKG-INFO +0 -638
- AlLoRa-1.2.0/AlLoRa.egg-info/SOURCES.txt +0 -45
- AlLoRa-1.2.0/PKG-INFO +0 -638
- AlLoRa-1.2.0/README.md +0 -619
- AlLoRa-1.2.0/examples/RaspberryGateway/Serial_Gateway/main.py +0 -16
- {AlLoRa-1.2.0 → allora-2.0.0}/AlLoRa/Connectors/__init__.py +0 -0
- {AlLoRa-1.2.0 → allora-2.0.0}/AlLoRa/DataSource.py +0 -0
- {AlLoRa-1.2.0 → allora-2.0.0}/AlLoRa/Interfaces/Interface.py +0 -0
- {AlLoRa-1.2.0 → allora-2.0.0}/AlLoRa/Interfaces/__init__.py +0 -0
- {AlLoRa-1.2.0 → allora-2.0.0}/AlLoRa/Nodes/__init__.py +0 -0
- {AlLoRa-1.2.0 → allora-2.0.0}/AlLoRa/__init__.py +0 -0
- {AlLoRa-1.2.0/examples/Adapters/Serial_adapter → allora-2.0.0/AlLoRa/utils}/__init__.py +0 -0
- {AlLoRa-1.2.0 → allora-2.0.0}/AlLoRa.egg-info/dependency_links.txt +0 -0
- {AlLoRa-1.2.0 → allora-2.0.0}/AlLoRa.egg-info/top_level.txt +0 -0
- {AlLoRa-1.2.0 → allora-2.0.0}/LICENSE +0 -0
- {AlLoRa-1.2.0/examples/Adapters/WiFi_adapters → allora-2.0.0/examples/Adapters/Serial_adapter}/__init__.py +0 -0
- {AlLoRa-1.2.0 → allora-2.0.0}/examples/Adapters/Serial_adapter/main.py +0 -0
- {AlLoRa-1.2.0/examples/Adapters → allora-2.0.0/examples/Adapters/WiFi_adapters/WiFi-Client}/__init__.py +0 -0
- {AlLoRa-1.2.0/examples/LoPyReceiver → allora-2.0.0/examples/Adapters/WiFi_adapters/WiFi-Hotspot}/__init__.py +0 -0
- {AlLoRa-1.2.0/examples/LoPySender → allora-2.0.0/examples/Adapters/WiFi_adapters}/__init__.py +0 -0
- {AlLoRa-1.2.0/examples/RaspberryGateway/Serial_Gateway → allora-2.0.0/examples/Adapters}/__init__.py +0 -0
- {AlLoRa-1.2.0/examples/RaspberryGateway/Wifi_Gateway → allora-2.0.0/examples/Gateways/Serial_Gateway}/__init__.py +0 -0
- {AlLoRa-1.2.0/examples/RaspberryGateway → allora-2.0.0/examples/Gateways/Wifi_Gateway}/__init__.py +0 -0
- {AlLoRa-1.2.0/examples → allora-2.0.0/examples/Gateways}/__init__.py +0 -0
- {AlLoRa-1.2.0/examples/LoPyReceiver → allora-2.0.0/examples/Requesters/LopyRequester}/boot.py +0 -0
- {AlLoRa-1.2.0/examples/LoPySender → allora-2.0.0/examples/Sources/LopySource}/boot.py +0 -0
- {AlLoRa-1.2.0 → allora-2.0.0}/setup.cfg +0 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
from AlLoRa.Packet import Packet
|
|
2
|
+
import gc
|
|
3
|
+
from math import ceil
|
|
4
|
+
from AlLoRa.utils.time_utils import get_time, current_time_ms as time, sleep, sleep_ms
|
|
5
|
+
from AlLoRa.utils.debug_utils import print
|
|
6
|
+
from AlLoRa.utils.os_utils import os
|
|
7
|
+
from os import urandom
|
|
8
|
+
|
|
9
|
+
class Connector:
|
|
10
|
+
MAX_LENGTH_MESSAGE = 255
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.MAC = "00000000"
|
|
14
|
+
self.observed_min_timeout = float('inf')
|
|
15
|
+
self.debug = False
|
|
16
|
+
|
|
17
|
+
def config(self, config_json):
|
|
18
|
+
# JSON Example:
|
|
19
|
+
# {
|
|
20
|
+
# "name": "N",
|
|
21
|
+
# "frequency": 868,
|
|
22
|
+
# "sf": 7,
|
|
23
|
+
# "mesh_mode": false,
|
|
24
|
+
# "debug": false,
|
|
25
|
+
# "min_timeout": 0.5,
|
|
26
|
+
# "max_timeout": 6
|
|
27
|
+
# }
|
|
28
|
+
self.config_parameters = config_json
|
|
29
|
+
if self.config_parameters:
|
|
30
|
+
self.name = self.config_parameters.get('name', "N")
|
|
31
|
+
self.debug = self.config_parameters.get('debug', False)
|
|
32
|
+
|
|
33
|
+
self.frequency = self.config_parameters.get('freq', 868) # 868 MHz
|
|
34
|
+
self.sf = self.config_parameters.get('sf', 7) # SF7
|
|
35
|
+
self.bw = self.config_parameters.get("bandwidth", 125) # 125 kHz
|
|
36
|
+
self.cr = self.config_parameters.get("coding_rate", 1) # 4/5
|
|
37
|
+
self.tx_power = self.config_parameters.get("tx_power", 14) # 14 dBm
|
|
38
|
+
|
|
39
|
+
self.mesh_mode = self.config_parameters.get('mesh_mode', False)
|
|
40
|
+
self.short_mac = self.config_parameters.get('short_mac', False)
|
|
41
|
+
|
|
42
|
+
self.min_timeout = self.config_parameters.get('min_timeout', 0.5)
|
|
43
|
+
self.max_timeout = self.config_parameters.get('max_timeout', 6)
|
|
44
|
+
self.timeout_delta = self.config_parameters.get('timeout_delta', 1) # Delta for processing times
|
|
45
|
+
|
|
46
|
+
# Calculate initial adaptive timeouts
|
|
47
|
+
self.update_timeouts()
|
|
48
|
+
|
|
49
|
+
self.adaptive_timeout = self.max_timeout
|
|
50
|
+
self.backup_timeout = self.adaptive_timeout
|
|
51
|
+
self.backup_rf_config()
|
|
52
|
+
else:
|
|
53
|
+
if self.debug:
|
|
54
|
+
print("Error: No config parameters")
|
|
55
|
+
|
|
56
|
+
def get_max_payload_size(self):
|
|
57
|
+
if self.sf < 11:
|
|
58
|
+
return 255
|
|
59
|
+
elif self.sf == 11:
|
|
60
|
+
return 111
|
|
61
|
+
elif self.sf == 12:
|
|
62
|
+
return 30 #51
|
|
63
|
+
|
|
64
|
+
def update_timeouts(self):
|
|
65
|
+
# Calculate the min and max timeouts based on the ToA for the current RF settings
|
|
66
|
+
self.max_payload_size = self.get_max_payload_size()
|
|
67
|
+
min_toa = self.calculate_toa(self.sf, self.bw, self.cr, self.max_payload_size) # Max payload
|
|
68
|
+
max_toa = min_toa * 2
|
|
69
|
+
self.min_timeout = min_toa + self.timeout_delta # Convert ms to seconds
|
|
70
|
+
self.max_timeout = max_toa + self.timeout_delta # Convert ms to seconds and add delta for processing times
|
|
71
|
+
if self.debug:
|
|
72
|
+
print("Updated timeouts: Min: {} s, Max: {} s".format(self.min_timeout, self.max_timeout))
|
|
73
|
+
|
|
74
|
+
def calculate_toa(self, sf, bw, cr, payload_size):
|
|
75
|
+
if self.debug:
|
|
76
|
+
print("TOA with SF:", sf, "BW:", bw, "CR:", cr, "Payload:", payload_size)
|
|
77
|
+
crc = 1 # CRC enabled
|
|
78
|
+
bw_hz = bw * 1000 # Convert bandwidth to Hz
|
|
79
|
+
t_symbol = (2 ** sf) / bw_hz # Symbol duration
|
|
80
|
+
t_preamble = t_symbol * (8 + 4.25) # Preamble duration
|
|
81
|
+
h = 0 # Implicit header disabled
|
|
82
|
+
de = 1 if (sf >= 11 and bw == 125) else 0 # Low data rate optimization enabled for SF11 and SF12 with 125kHz BW
|
|
83
|
+
cr_rate = cr / 4.0 # Coding rate
|
|
84
|
+
# Payload Symbol Calculation
|
|
85
|
+
payload_bits = 8 * payload_size - 4 * sf + 28 + 16 * (1 if crc else 0) - 20 * h
|
|
86
|
+
bits_per_symbol = 4 * (sf - 2 * de)
|
|
87
|
+
n_payload = 8 + max(0, int(ceil(payload_bits / bits_per_symbol) * (cr_rate + 4)))
|
|
88
|
+
|
|
89
|
+
# Payload Duration
|
|
90
|
+
t_payload = t_symbol * n_payload
|
|
91
|
+
|
|
92
|
+
# Total Time on Air
|
|
93
|
+
t_air = t_preamble + t_payload
|
|
94
|
+
if self.debug:
|
|
95
|
+
print("TOA:", t_air)
|
|
96
|
+
|
|
97
|
+
return t_air
|
|
98
|
+
|
|
99
|
+
def backup_config(self):
|
|
100
|
+
return self.config_parameters
|
|
101
|
+
|
|
102
|
+
def get_mac(self):
|
|
103
|
+
return self.MAC
|
|
104
|
+
|
|
105
|
+
def set_mesh_mode(self, mesh_mode=False):
|
|
106
|
+
self.mesh_mode = mesh_mode
|
|
107
|
+
|
|
108
|
+
def send(self, packet: Packet):
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
def recv(self, focus_time=12):
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
def increase_adaptive_timeout(self):
|
|
115
|
+
random_factor = int.from_bytes(urandom(2), "little") / 2**16
|
|
116
|
+
self.adaptive_timeout = min(self.adaptive_timeout * (1 + random_factor), self.max_timeout)
|
|
117
|
+
|
|
118
|
+
def decrease_adaptive_timeout(self, td):
|
|
119
|
+
smoothing_factor = 0.2
|
|
120
|
+
new_timeout = self.adaptive_timeout * (1 - smoothing_factor) + td * smoothing_factor
|
|
121
|
+
self.observed_min_timeout = min(self.observed_min_timeout, td)
|
|
122
|
+
self.adaptive_timeout = max(new_timeout, max(self.min_timeout, self.observed_min_timeout))
|
|
123
|
+
|
|
124
|
+
def send_and_wait_response(self, packet):
|
|
125
|
+
focus_time = self.adaptive_timeout
|
|
126
|
+
packet_size_sent = len(packet.get_content())
|
|
127
|
+
try:
|
|
128
|
+
send_success = self.send(packet)
|
|
129
|
+
if not send_success:
|
|
130
|
+
error_info = {
|
|
131
|
+
"type": "SEND_ERROR",
|
|
132
|
+
"message": "Error sending packet",
|
|
133
|
+
"focus_time": focus_time,
|
|
134
|
+
"adaptive_timeout": self.adaptive_timeout,
|
|
135
|
+
}
|
|
136
|
+
if self.debug:
|
|
137
|
+
print(error_info["message"])
|
|
138
|
+
return error_info, packet_size_sent, 0, 0
|
|
139
|
+
except Exception as e:
|
|
140
|
+
error_info = {
|
|
141
|
+
"type": "EXCEPTION",
|
|
142
|
+
"message": "Exception during send: {}".format(e),
|
|
143
|
+
"focus_time": focus_time,
|
|
144
|
+
"adaptive_timeout": self.adaptive_timeout,
|
|
145
|
+
}
|
|
146
|
+
if self.debug:
|
|
147
|
+
print(error_info["message"])
|
|
148
|
+
self.increase_adaptive_timeout()
|
|
149
|
+
return error_info, packet_size_sent, 0, 0
|
|
150
|
+
|
|
151
|
+
while focus_time > 0:
|
|
152
|
+
t0 = time()
|
|
153
|
+
try:
|
|
154
|
+
received_data = self.recv(focus_time)
|
|
155
|
+
except Exception as e:
|
|
156
|
+
error_info = {
|
|
157
|
+
"type": "EXCEPTION",
|
|
158
|
+
"message": "Exception during recv: {}".format(e),
|
|
159
|
+
"focus_time": focus_time,
|
|
160
|
+
"adaptive_timeout": self.adaptive_timeout,
|
|
161
|
+
}
|
|
162
|
+
if self.debug:
|
|
163
|
+
print(error_info["message"])
|
|
164
|
+
return error_info, packet_size_sent, 0, 0
|
|
165
|
+
|
|
166
|
+
td = (time() - t0) / 1000 # Calculate the time difference in seconds
|
|
167
|
+
packet_size_received = len(received_data) if received_data else 0
|
|
168
|
+
|
|
169
|
+
if not received_data:
|
|
170
|
+
error_info = {
|
|
171
|
+
"type": "TIMEOUT",
|
|
172
|
+
"message": "No response received",
|
|
173
|
+
"focus_time": focus_time,
|
|
174
|
+
"time_difference": td,
|
|
175
|
+
"adaptive_timeout": self.adaptive_timeout,
|
|
176
|
+
}
|
|
177
|
+
if self.debug:
|
|
178
|
+
print(error_info["message"])
|
|
179
|
+
self.increase_adaptive_timeout()
|
|
180
|
+
return error_info, packet_size_sent, packet_size_received, td
|
|
181
|
+
|
|
182
|
+
response_packet = Packet(self.mesh_mode, self.short_mac)
|
|
183
|
+
if self.debug:
|
|
184
|
+
print("WAIT_RESPONSE({}) at: {}|| source_reply: {}".format(td, self.adaptive_timeout, received_data))
|
|
185
|
+
try:
|
|
186
|
+
if response_packet.load(received_data):
|
|
187
|
+
if response_packet.get_source() == packet.get_destination() and response_packet.get_destination() == self.get_mac():
|
|
188
|
+
if len(received_data) > response_packet.HEADER_SIZE + 60: # Hardcoded for only chunks
|
|
189
|
+
self.decrease_adaptive_timeout(td)
|
|
190
|
+
if response_packet.get_debug_hops():
|
|
191
|
+
response_packet.add_hop(self.name, self.get_rssi(), 0)
|
|
192
|
+
return response_packet, packet_size_sent, packet_size_received, td
|
|
193
|
+
else:
|
|
194
|
+
if len(received_data) > 0:
|
|
195
|
+
error_info = {
|
|
196
|
+
"type": "CORRUPTED_PACKET",
|
|
197
|
+
"message": "{}".format(received_data),
|
|
198
|
+
}
|
|
199
|
+
else:
|
|
200
|
+
error_info = {
|
|
201
|
+
"type": "NO_PACKET",
|
|
202
|
+
"message": "No packet received",
|
|
203
|
+
}
|
|
204
|
+
if self.debug:
|
|
205
|
+
print(error_info["message"])
|
|
206
|
+
return error_info, packet_size_sent, packet_size_received, td
|
|
207
|
+
|
|
208
|
+
except Exception as e:
|
|
209
|
+
error_info = {
|
|
210
|
+
"type": "EXCEPTION",
|
|
211
|
+
"message": "Exception during packet load: {}, data: {}".format(e, received_data),
|
|
212
|
+
}
|
|
213
|
+
if self.debug:
|
|
214
|
+
print(error_info["message"])
|
|
215
|
+
return error_info, packet_size_sent, packet_size_received, td
|
|
216
|
+
|
|
217
|
+
focus_time = self.adaptive_timeout - td
|
|
218
|
+
if focus_time < self.min_timeout:
|
|
219
|
+
focus_time = self.min_timeout
|
|
220
|
+
error_info = {
|
|
221
|
+
"type": "MIN_TIMEOUT_REACHED",
|
|
222
|
+
"message": "Minimum timeout reached, can't wait more",
|
|
223
|
+
"focus_time": focus_time,
|
|
224
|
+
}
|
|
225
|
+
if self.debug:
|
|
226
|
+
print(error_info["message"])
|
|
227
|
+
return error_info, packet_size_sent, packet_size_received, td
|
|
228
|
+
|
|
229
|
+
# This function returns the RSSI of the last received packet
|
|
230
|
+
def get_rssi(self):
|
|
231
|
+
return 0
|
|
232
|
+
|
|
233
|
+
# This function returns the SNR of the last received packet
|
|
234
|
+
def get_snr(self):
|
|
235
|
+
return 0
|
|
236
|
+
|
|
237
|
+
def signal_estimation(self):
|
|
238
|
+
percentage = 0
|
|
239
|
+
rssi = self.get_rssi()
|
|
240
|
+
if (rssi >= -50):
|
|
241
|
+
percentage = 100
|
|
242
|
+
elif (rssi <= -50) and (rssi >= -100):
|
|
243
|
+
percentage = 2 * (rssi + 100)
|
|
244
|
+
elif (rssi < 100):
|
|
245
|
+
percentage = 0
|
|
246
|
+
if self.debug:
|
|
247
|
+
print('SIGNAL STRENGTH', percentage, '%')
|
|
248
|
+
return percentage
|
|
249
|
+
|
|
250
|
+
def get_rf_config(self):
|
|
251
|
+
return [self.frequency, self.sf, self.bw, self.cr, self.tx_power]
|
|
252
|
+
|
|
253
|
+
def change_rf_config(self, frequency=None, sf=None, bw=None, cr=None, tx_power=None, backup=True):
|
|
254
|
+
if backup:
|
|
255
|
+
self.backup_rf_config()
|
|
256
|
+
try:
|
|
257
|
+
if frequency is not None:
|
|
258
|
+
self.set_frequency(frequency)
|
|
259
|
+
if sf is not None:
|
|
260
|
+
self.set_sf(sf)
|
|
261
|
+
if bw is not None:
|
|
262
|
+
self.set_bw(bw)
|
|
263
|
+
if cr is not None:
|
|
264
|
+
self.set_cr(cr)
|
|
265
|
+
if tx_power is not None:
|
|
266
|
+
self.set_transmission_power(tx_power)
|
|
267
|
+
self.update_timeouts()
|
|
268
|
+
self.adaptive_timeout = self.max_timeout
|
|
269
|
+
return True
|
|
270
|
+
except Exception as e:
|
|
271
|
+
if self.debug:
|
|
272
|
+
print("Error changing RF config: ", e)
|
|
273
|
+
self.restore_rf_config()
|
|
274
|
+
return False
|
|
275
|
+
|
|
276
|
+
def backup_rf_config(self):
|
|
277
|
+
self.last_rf_config = [self.frequency,
|
|
278
|
+
self.sf,
|
|
279
|
+
self.bw,
|
|
280
|
+
self.cr,
|
|
281
|
+
self.tx_power]
|
|
282
|
+
|
|
283
|
+
def update_rf_params(self, params):
|
|
284
|
+
"""
|
|
285
|
+
Updates the connector's RF parameters.
|
|
286
|
+
Override in derived classes if needed.
|
|
287
|
+
"""
|
|
288
|
+
self.frequency = params.get("frequency", self.frequency)
|
|
289
|
+
self.sf = params.get("sf", self.sf)
|
|
290
|
+
self.bw = params.get("bw", self.bw)
|
|
291
|
+
self.cr = params.get("cr", self.cr)
|
|
292
|
+
self.tx_power = params.get("tx_power", self.tx_power)
|
|
293
|
+
if self.debug:
|
|
294
|
+
print("Updated RF parameters:", self.get_rf_config())
|
|
295
|
+
|
|
296
|
+
def restore_rf_config(self):
|
|
297
|
+
frequency = self.last_rf_config[0]
|
|
298
|
+
sf = self.last_rf_config[1]
|
|
299
|
+
bw = self.last_rf_config[2]
|
|
300
|
+
cr = self.last_rf_config[3]
|
|
301
|
+
tx_power = self.last_rf_config[4]
|
|
302
|
+
self.change_rf_config(frequency=frequency,
|
|
303
|
+
sf=sf,
|
|
304
|
+
bw=bw,
|
|
305
|
+
cr=cr,
|
|
306
|
+
tx_power=tx_power,
|
|
307
|
+
backup=False)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def set_frequency(self, frequency):
|
|
311
|
+
pass
|
|
312
|
+
|
|
313
|
+
def set_sf(self, sf):
|
|
314
|
+
pass
|
|
315
|
+
|
|
316
|
+
def set_bw(self, bw):
|
|
317
|
+
pass
|
|
318
|
+
|
|
319
|
+
def set_cr(self, cr):
|
|
320
|
+
pass
|
|
321
|
+
|
|
322
|
+
def set_transmission_power(self, tx_power):
|
|
323
|
+
pass
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import utime
|
|
2
|
+
from machine import UART
|
|
3
|
+
import ubinascii
|
|
4
|
+
# import network
|
|
5
|
+
import re
|
|
6
|
+
from math import ceil
|
|
7
|
+
|
|
8
|
+
from AlLoRa.Packet import Packet
|
|
9
|
+
from AlLoRa.Connectors.Connector import Connector
|
|
10
|
+
from AlLoRa.utils.debug_utils import print
|
|
11
|
+
|
|
12
|
+
class E5_connector(Connector):
|
|
13
|
+
def __init__(self):
|
|
14
|
+
super().__init__()
|
|
15
|
+
|
|
16
|
+
def config(self, config_json):
|
|
17
|
+
super().config(config_json)
|
|
18
|
+
if self.config_parameters:
|
|
19
|
+
connector_config = self.config_parameters
|
|
20
|
+
|
|
21
|
+
self.debug = connector_config.get("debug", True)
|
|
22
|
+
|
|
23
|
+
self.uart_id = connector_config.get("uart_id", 1)
|
|
24
|
+
self.baudrate = connector_config.get("baudrate", 115200)
|
|
25
|
+
self.tx_pin = connector_config.get("tx", 43)
|
|
26
|
+
self.rx_pin = connector_config.get("rx", 44)
|
|
27
|
+
self.bits = connector_config.get("bits", 8)
|
|
28
|
+
self.parity = connector_config.get("parity", None)
|
|
29
|
+
self.stop = connector_config.get("stop", 1)
|
|
30
|
+
self.timeout = connector_config.get("timeout", 300) # Set the UART timeout to 300 ms
|
|
31
|
+
self.txbuf = connector_config.get("txbuf", 150) # TX buffer size
|
|
32
|
+
self.rxbuf = connector_config.get("rxbuf", 150) # RX buffer size
|
|
33
|
+
|
|
34
|
+
self.tx_preamble = connector_config.get("tx_preamble", 8)
|
|
35
|
+
self.rx_preamble = connector_config.get("rx_preamble", 8)
|
|
36
|
+
|
|
37
|
+
self.crc = connector_config.get("crc", "ON")
|
|
38
|
+
self.iq = connector_config.get("iq", "OFF")
|
|
39
|
+
self.net = connector_config.get("net", "ON")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Initialize UART with the default baud rate
|
|
43
|
+
self.uart = UART(self.uart_id, 9600)
|
|
44
|
+
self.uart.init(baudrate=9600, tx=self.tx_pin, rx=self.rx_pin, bits=self.bits,
|
|
45
|
+
parity=self.parity, stop=self.stop, timeout=self.timeout,
|
|
46
|
+
txbuf=self.txbuf, rxbuf=self.rxbuf)
|
|
47
|
+
|
|
48
|
+
# Set the baud rate to the desired value and restart the module
|
|
49
|
+
self.set_uart_baudrate(self.baudrate)
|
|
50
|
+
self.restart_module()
|
|
51
|
+
# Reinitialize UART with the new baud rate
|
|
52
|
+
self.uart.init(baudrate=self.baudrate, tx=self.tx_pin, rx=self.rx_pin, bits=self.bits, parity=self.parity, stop=self.stop, timeout=self.timeout)
|
|
53
|
+
|
|
54
|
+
# Set the UART timeout
|
|
55
|
+
self.set_uart_timeout(0)
|
|
56
|
+
|
|
57
|
+
# Enter test mode and configure RF settings
|
|
58
|
+
self.enter_test_mode()
|
|
59
|
+
self.set_rf_config(self.frequency, self.sf, self.bw, self.tx_preamble, self.rx_preamble, self.tx_power, self.crc, self.iq, self.net)
|
|
60
|
+
if self.debug:
|
|
61
|
+
print("Configuration: uart_id={}, baudrate={}, tx={}, rx={}, bits={}, parity={}, stop={}, timeout={}".format(
|
|
62
|
+
self.uart_id, self.baudrate, self.tx_pin, self.rx_pin, self.bits, self.parity, self.stop, self.timeout))
|
|
63
|
+
print("RF Configuration: frequency={}, sf={}, bandwidth={}, tx_preamble={}, rx_preamble={}, tx_power={}, crc={}, iq={}, net={}".format(
|
|
64
|
+
self.frequency, self.sf, self.bw, self.tx_preamble, self.rx_preamble, self.tx_power, self.crc, self.iq, self.net))
|
|
65
|
+
self.get_mac_from_module()
|
|
66
|
+
|
|
67
|
+
def get_mac_from_module(self):
|
|
68
|
+
cmd = "AT+ID\r\n"
|
|
69
|
+
expected_response = "+ID: DevEui,"
|
|
70
|
+
default_APP_EUI = "80:00:00:00:00:00:00:06"
|
|
71
|
+
success, response = self.send_command(cmd, expected_response, 500)
|
|
72
|
+
if success:
|
|
73
|
+
if self.debug:
|
|
74
|
+
print("Response:", response.decode())
|
|
75
|
+
mac = response.decode()#.split('+ID: ')[-1].strip()
|
|
76
|
+
obtained_APP_EUI = mac.split('DevEui, ')[1][:23]
|
|
77
|
+
self.MAC = obtained_APP_EUI.replace(':', '').strip()
|
|
78
|
+
if self.debug:
|
|
79
|
+
print("Obtained APP_EUI:", obtained_APP_EUI)
|
|
80
|
+
print("MAC:", self.MAC)
|
|
81
|
+
|
|
82
|
+
def set_dynamic_uart_timeout(self, timeout):
|
|
83
|
+
self.uart.init(baudrate=self.baudrate, tx=self.tx_pin, rx=self.rx_pin, bits=self.bits, parity=self.parity, stop=self.stop, timeout=timeout)
|
|
84
|
+
if self.debug:
|
|
85
|
+
print("UART timeout set to:", timeout)
|
|
86
|
+
|
|
87
|
+
# AT Commands
|
|
88
|
+
def send_command(self, cmd, expected_response, timeout_ms):
|
|
89
|
+
start_time_total = utime.ticks_ms()
|
|
90
|
+
|
|
91
|
+
self.uart.write(cmd)
|
|
92
|
+
utime.sleep_ms(10) # Short sleep to ensure command is processed
|
|
93
|
+
|
|
94
|
+
start_time = utime.ticks_ms()
|
|
95
|
+
response = b''
|
|
96
|
+
while utime.ticks_diff(utime.ticks_ms(), start_time) < timeout_ms:
|
|
97
|
+
if self.uart.any():
|
|
98
|
+
response += self.uart.read()
|
|
99
|
+
if expected_response in response:
|
|
100
|
+
end_time_total = utime.ticks_ms()
|
|
101
|
+
if self.debug:
|
|
102
|
+
print("send_command Total Time:", utime.ticks_diff(end_time_total, start_time_total), "ms")
|
|
103
|
+
return True, response
|
|
104
|
+
end_time_total = utime.ticks_ms()
|
|
105
|
+
if self.debug:
|
|
106
|
+
print("send_command Total Time:", utime.ticks_diff(end_time_total, start_time_total), "ms")
|
|
107
|
+
return False, response
|
|
108
|
+
|
|
109
|
+
def set_uart_baudrate(self, baudrate):
|
|
110
|
+
cmd = "AT+UART=BR,{}\r\n".format(baudrate)
|
|
111
|
+
print("Set Baud Rate Command:", cmd)
|
|
112
|
+
expected_response = "+UART=BR"
|
|
113
|
+
success, response = self.send_command(cmd, expected_response, 500)
|
|
114
|
+
if self.debug:
|
|
115
|
+
print("Set Baud Rate Response:", response.decode())
|
|
116
|
+
return success
|
|
117
|
+
|
|
118
|
+
def set_uart_timeout(self, timeout):
|
|
119
|
+
cmd = "AT+UART=TIMEOUT,{}\r\n".format(timeout)
|
|
120
|
+
if self.debug:
|
|
121
|
+
print("Set Timeout Command:", cmd)
|
|
122
|
+
expected_response = "+UART: TIMEOUT, {}".format(timeout)
|
|
123
|
+
success, response = self.send_command(cmd, expected_response, 500)
|
|
124
|
+
if self.debug:
|
|
125
|
+
print("Set Timeout Response:", response.decode(), "Success:", success)
|
|
126
|
+
return success
|
|
127
|
+
|
|
128
|
+
def restart_module(self):
|
|
129
|
+
# Send a command to reset the module (if available) or reinitialize the UART
|
|
130
|
+
cmd = "AT+RESET\r\n"
|
|
131
|
+
if self.debug:
|
|
132
|
+
print("Restart Module Command:", cmd)
|
|
133
|
+
expected_response = "+RESET"
|
|
134
|
+
success, response = self.send_command(cmd, expected_response, 1000)
|
|
135
|
+
if self.debug:
|
|
136
|
+
print("Restart Module Response:", response.decode())
|
|
137
|
+
utime.sleep(1) # Wait for the module to reset
|
|
138
|
+
return success
|
|
139
|
+
|
|
140
|
+
def enter_test_mode(self):
|
|
141
|
+
cmd = "AT+MODE=TEST\r\n"
|
|
142
|
+
expected_response = "+MODE: TEST"
|
|
143
|
+
success, response = self.send_command(cmd, expected_response, 2000)
|
|
144
|
+
if self.debug:
|
|
145
|
+
print("Enter Test Mode Response:", response.decode())
|
|
146
|
+
return success
|
|
147
|
+
|
|
148
|
+
def receive_packet(self, timeout=10000, chunk_size=124):
|
|
149
|
+
cmd = "AT+TEST=RXLRPKT\r\n"
|
|
150
|
+
success = self.uart.write(cmd)
|
|
151
|
+
|
|
152
|
+
t0_ack = utime.ticks_ms()
|
|
153
|
+
ack = self.uart.read(32)
|
|
154
|
+
start_time_total = utime.ticks_ms()
|
|
155
|
+
response = b''
|
|
156
|
+
|
|
157
|
+
while utime.ticks_diff(utime.ticks_ms(), start_time_total) < timeout:
|
|
158
|
+
if self.uart.any():
|
|
159
|
+
response += self.uart.read(min(chunk_size, self.uart.any()))
|
|
160
|
+
if response.endswith(b'"\r\n'):
|
|
161
|
+
break
|
|
162
|
+
else:
|
|
163
|
+
utime.sleep_ms(1)
|
|
164
|
+
|
|
165
|
+
end_time_total = utime.ticks_ms()
|
|
166
|
+
if self.debug:
|
|
167
|
+
print("receive_packet Total Time:", utime.ticks_diff(end_time_total, start_time_total), "ms")
|
|
168
|
+
|
|
169
|
+
return response if response else None
|
|
170
|
+
|
|
171
|
+
def send_packet(self, data):
|
|
172
|
+
cmd = 'AT+TEST=TXLRPKT,"{}"\r\n'.format(data)
|
|
173
|
+
start_time_total = utime.ticks_ms()
|
|
174
|
+
expected_response = "+TEST: TXLRPKT" #expected_response = "+TEST: TX DONE\r\n" #
|
|
175
|
+
sfs = {8: 2, 9: 4, 10: 8, 11: 16, 12: 32}
|
|
176
|
+
sf_changed = False
|
|
177
|
+
if self.sf > 7:
|
|
178
|
+
self.set_dynamic_uart_timeout(ceil((self.timeout * sfs[self.sf])*1.1))
|
|
179
|
+
sf_changed = True
|
|
180
|
+
|
|
181
|
+
wt0 = utime.ticks_ms()
|
|
182
|
+
success = self.uart.write(cmd)
|
|
183
|
+
wt1 = utime.ticks_ms()
|
|
184
|
+
print("Time to write:", utime.ticks_diff(wt1, wt0), "ms")
|
|
185
|
+
start_time = utime.ticks_ms()
|
|
186
|
+
#response = b''
|
|
187
|
+
response = self.uart.read()
|
|
188
|
+
if sf_changed:
|
|
189
|
+
self.set_dynamic_uart_timeout(self.timeout) # Set the UART timeout back to the default value
|
|
190
|
+
#expected_response = "+TEST: TXLRPKT"
|
|
191
|
+
|
|
192
|
+
end_time_total = utime.ticks_ms()
|
|
193
|
+
if self.debug:
|
|
194
|
+
print("send_packet Total Time:", utime.ticks_diff(end_time_total, start_time_total), "ms")
|
|
195
|
+
try:
|
|
196
|
+
if expected_response in response:
|
|
197
|
+
return True
|
|
198
|
+
else:
|
|
199
|
+
return False
|
|
200
|
+
except:
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
# Packet Handling
|
|
204
|
+
def hex_to_bytes(self, hex_str):
|
|
205
|
+
hex_str = hex_str.replace('"', '').replace('\n', '').replace(' ', '').strip()
|
|
206
|
+
if len(hex_str) % 2 != 0:
|
|
207
|
+
raise ValueError("odd-length string")
|
|
208
|
+
return bytes.fromhex(hex_str)
|
|
209
|
+
|
|
210
|
+
def bytes_to_hex(self, byte_str):
|
|
211
|
+
return byte_str.hex()
|
|
212
|
+
|
|
213
|
+
def extract_packet_info(self, response):
|
|
214
|
+
# time_0 = utime.ticks_ms()
|
|
215
|
+
rssi_match = re.search(r'RSSI:(-?\d+)', response)
|
|
216
|
+
snr_match = re.search(r'SNR:(-?\d+)', response)
|
|
217
|
+
|
|
218
|
+
if rssi_match:
|
|
219
|
+
try:
|
|
220
|
+
self.rssi = int(rssi_match.group(1))
|
|
221
|
+
except ValueError:
|
|
222
|
+
self.rssi = None
|
|
223
|
+
else:
|
|
224
|
+
self.rssi = None
|
|
225
|
+
|
|
226
|
+
if snr_match:
|
|
227
|
+
try:
|
|
228
|
+
self.snr = int(snr_match.group(1))
|
|
229
|
+
except ValueError:
|
|
230
|
+
self.snr = None
|
|
231
|
+
|
|
232
|
+
else:
|
|
233
|
+
self.snr = None
|
|
234
|
+
|
|
235
|
+
# Connector Methods
|
|
236
|
+
def send(self, packet):
|
|
237
|
+
data = packet.get_content()
|
|
238
|
+
hex_data = self.bytes_to_hex(data)
|
|
239
|
+
hex_data = hex_data.strip().strip('"')
|
|
240
|
+
|
|
241
|
+
success = self.send_packet(hex_data)
|
|
242
|
+
|
|
243
|
+
return success
|
|
244
|
+
|
|
245
|
+
def recv(self, focus_time=12):
|
|
246
|
+
rt_0 = utime.ticks_ms()
|
|
247
|
+
packet_data = self.receive_packet(focus_time * 1000)
|
|
248
|
+
if packet_data:
|
|
249
|
+
try:
|
|
250
|
+
self.extract_packet_info(packet_data)
|
|
251
|
+
hex_data = packet_data.decode().split('RX ')[-1].strip().strip('"')
|
|
252
|
+
bytes_data = self.hex_to_bytes(hex_data)
|
|
253
|
+
return bytes_data
|
|
254
|
+
except (ValueError, IndexError) as e:
|
|
255
|
+
if self.debug:
|
|
256
|
+
print("Error decoding packet:", e)
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
def get_rssi(self):
|
|
260
|
+
return self.rssi
|
|
261
|
+
|
|
262
|
+
def get_snr(self):
|
|
263
|
+
return self.snr
|
|
264
|
+
|
|
265
|
+
def change_rf_config(self, frequency=None, sf=None, bw=None, cr=None, tx_power=None, backup=True):
|
|
266
|
+
if self.debug:
|
|
267
|
+
print("Changing RF Config to: ", frequency, sf, bw, cr, tx_power)
|
|
268
|
+
if backup:
|
|
269
|
+
self.backup_rf_config()
|
|
270
|
+
freq = frequency if frequency is not None else self.frequency
|
|
271
|
+
sf = sf if sf is not None else self.sf
|
|
272
|
+
bandwidth = bw if bw is not None else self.bw
|
|
273
|
+
cr = cr if cr is not None else self.cr
|
|
274
|
+
tx_power = tx_power if tx_power is not None else self.tx_power
|
|
275
|
+
success = self.set_rf_config(freq, sf, bandwidth, self.tx_preamble, self.rx_preamble, tx_power, self.crc, self.iq, self.net)
|
|
276
|
+
if success:
|
|
277
|
+
if self.debug:
|
|
278
|
+
print("RF Config Changed to: ", freq, sf, bandwidth, cr, tx_power)
|
|
279
|
+
self.update_timeouts()
|
|
280
|
+
self.adaptive_timeout = self.max_timeout
|
|
281
|
+
return True
|
|
282
|
+
else:
|
|
283
|
+
self.restore_rf_config()
|
|
284
|
+
return False
|
|
285
|
+
|
|
286
|
+
def set_rf_config(self, frequency, sf, bandwidth, tx_preamble, rx_preamble, tx_power, crc, iq, net):
|
|
287
|
+
cmd = "AT+TEST=RFCFG,{},SF{},{},{},{},{},{},{},{}\r\n".format(frequency, sf, bandwidth, tx_preamble, rx_preamble, tx_power, crc, iq, net)
|
|
288
|
+
print(cmd)
|
|
289
|
+
expected_response = "+TEST: RFCFG"
|
|
290
|
+
success, response = self.send_command(cmd, expected_response, 2000)
|
|
291
|
+
if self.debug:
|
|
292
|
+
print("Set RF Config Response:", response.decode())
|
|
293
|
+
if success:
|
|
294
|
+
if self.debug:
|
|
295
|
+
print("RF Config Set to:", frequency, sf, bandwidth, tx_preamble, rx_preamble, tx_power, crc, iq, net)
|
|
296
|
+
self.frequency = frequency
|
|
297
|
+
self.sf = sf
|
|
298
|
+
self.bw = bandwidth
|
|
299
|
+
self.tx_preamble = tx_preamble
|
|
300
|
+
self.rx_preamble = rx_preamble
|
|
301
|
+
self.tx_power = tx_power
|
|
302
|
+
self.crc = crc
|
|
303
|
+
self.iq = iq
|
|
304
|
+
self.net = net
|
|
305
|
+
return success
|
|
306
|
+
|
|
307
|
+
def set_frequency(self, frequency):
|
|
308
|
+
success = self.set_rf_config(frequency, "SF" + str(self.sf), self.bw, self.tx_preamble, self.rx_preamble, self.tx_power, self.crc, self.iq, self.net)
|
|
309
|
+
if success:
|
|
310
|
+
if self.debug:
|
|
311
|
+
print("Frequency Set to:", frequency)
|
|
312
|
+
return success
|
|
313
|
+
|
|
314
|
+
def set_sf(self, sf):
|
|
315
|
+
success = self.set_rf_config(self.frequency, "SF" + str(self.sf), self.bw, self.tx_preamble, self.rx_preamble, self.tx_power, self.crc, self.iq, self.net)
|
|
316
|
+
if success:
|
|
317
|
+
if self.debug:
|
|
318
|
+
print("SF Changed to:", sf)
|
|
319
|
+
return success
|
|
320
|
+
|
|
321
|
+
def set_bw(self, bw):
|
|
322
|
+
success = self.set_rf_config(self.frequency, "SF" + str(self.sf), bw, self.tx_preamble, self.rx_preamble, self.tx_power, self.crc, self.iq, self.net)
|
|
323
|
+
if success:
|
|
324
|
+
if self.debug:
|
|
325
|
+
print("Bandwidth Set to:", bw)
|
|
326
|
+
return success
|
|
327
|
+
|
|
328
|
+
def set_cr(self, cr):
|
|
329
|
+
# CR is not configurable in E5 module
|
|
330
|
+
if self.debug:
|
|
331
|
+
print("CR is not configurable in E5 module")
|
|
332
|
+
return False
|
|
333
|
+
|
|
334
|
+
def set_transmission_power(self, desired_power):
|
|
335
|
+
success = self.set_rf_config(self.frequency, "SF" + str(self.sf), self.bw, self.tx_preamble, self.rx_preamble, desired_power, self.crc, self.iq, self.net)
|
|
336
|
+
if success:
|
|
337
|
+
if self.debug:
|
|
338
|
+
print("Transmission Power Set to:", desired_power)
|
|
339
|
+
return success
|