wiliot-certificate 1.3.0a1__py3-none-any.whl
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.
- gw_certificate/__init__.py +0 -0
- gw_certificate/ag/ut_defines.py +361 -0
- gw_certificate/ag/wlt_types.py +85 -0
- gw_certificate/ag/wlt_types_ag.py +5310 -0
- gw_certificate/ag/wlt_types_data.py +64 -0
- gw_certificate/api/extended_api.py +1547 -0
- gw_certificate/api_if/__init__.py +0 -0
- gw_certificate/api_if/api_validation.py +40 -0
- gw_certificate/api_if/gw_capabilities.py +18 -0
- gw_certificate/common/analysis_data_bricks.py +1455 -0
- gw_certificate/common/debug.py +63 -0
- gw_certificate/common/utils.py +219 -0
- gw_certificate/common/utils_defines.py +102 -0
- gw_certificate/common/wltPb_pb2.py +72 -0
- gw_certificate/common/wltPb_pb2.pyi +227 -0
- gw_certificate/gw_certificate.py +138 -0
- gw_certificate/gw_certificate_cli.py +70 -0
- gw_certificate/interface/ble_simulator.py +91 -0
- gw_certificate/interface/ble_sniffer.py +189 -0
- gw_certificate/interface/if_defines.py +35 -0
- gw_certificate/interface/mqtt.py +469 -0
- gw_certificate/interface/packet_error.py +22 -0
- gw_certificate/interface/pkt_generator.py +720 -0
- gw_certificate/interface/uart_if.py +193 -0
- gw_certificate/interface/uart_ports.py +20 -0
- gw_certificate/templates/results.html +241 -0
- gw_certificate/templates/stage.html +22 -0
- gw_certificate/templates/table.html +6 -0
- gw_certificate/templates/test.html +38 -0
- gw_certificate/tests/__init__.py +11 -0
- gw_certificate/tests/actions.py +131 -0
- gw_certificate/tests/bad_crc_to_PER_quantization.csv +51 -0
- gw_certificate/tests/connection.py +181 -0
- gw_certificate/tests/downlink.py +174 -0
- gw_certificate/tests/generic.py +161 -0
- gw_certificate/tests/registration.py +288 -0
- gw_certificate/tests/static/__init__.py +0 -0
- gw_certificate/tests/static/connection_defines.py +9 -0
- gw_certificate/tests/static/downlink_defines.py +9 -0
- gw_certificate/tests/static/generated_packet_table.py +209 -0
- gw_certificate/tests/static/packet_table.csv +10051 -0
- gw_certificate/tests/static/references.py +4 -0
- gw_certificate/tests/static/uplink_defines.py +20 -0
- gw_certificate/tests/throughput.py +244 -0
- gw_certificate/tests/uplink.py +683 -0
- wiliot_certificate-1.3.0a1.dist-info/LICENSE +21 -0
- wiliot_certificate-1.3.0a1.dist-info/METADATA +113 -0
- wiliot_certificate-1.3.0a1.dist-info/RECORD +51 -0
- wiliot_certificate-1.3.0a1.dist-info/WHEEL +5 -0
- wiliot_certificate-1.3.0a1.dist-info/entry_points.txt +2 -0
- wiliot_certificate-1.3.0a1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
|
|
2
|
+
GW_ACTIONS_DOC = "https://community.wiliot.com/customers/s/article/Wiliot-Gateway-Actions"
|
|
3
|
+
GW_REGISTER_DOC = "https://community.wiliot.com/customers/s/article/Registering-Third-Party-Gateways"
|
|
4
|
+
GW_MQTT_DOC = "https://community.wiliot.com/customers/s/article/Sending-Wiliot-Packets-to-the-Wiliot-Cloud"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from gw_certificate.interface.if_defines import *
|
|
2
|
+
from gw_certificate.ag.ut_defines import *
|
|
3
|
+
|
|
4
|
+
UPLINK_BRG_ID = 'FFFFFFFFFFFF'
|
|
5
|
+
RECEIVED = 'received'
|
|
6
|
+
SEQ_ID = "sequenceId"
|
|
7
|
+
SHARED_COLUMNS = [PAYLOAD]
|
|
8
|
+
INT64_COLUMNS = [RSSI]
|
|
9
|
+
OBJECT_COLUMNS = [PAYLOAD]
|
|
10
|
+
INIT_STAGES_DUPLICATIONS = [i for i in range(2,9)]
|
|
11
|
+
REPORT_COLUMNS = ['pkt_id', 'duplication', 'time_delay']
|
|
12
|
+
INCREMENTAL_TIME_DELAYS = [10, 50, 100, 255]
|
|
13
|
+
TAG_STAGE_ADVA = '0000000000C0'
|
|
14
|
+
TAG_STAGE_PACKETS = range(40)
|
|
15
|
+
INCREMENTAL_PACKETS = range(255)
|
|
16
|
+
|
|
17
|
+
ADV_TIMESTAMP = 'adv_timestamp'
|
|
18
|
+
TS_DEVIATION = 1500
|
|
19
|
+
TS_TOLERANCE = 2500
|
|
20
|
+
REC_TIMESTAMP = 'rec_timestamp'
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import plotly.express as px
|
|
6
|
+
import tabulate
|
|
7
|
+
|
|
8
|
+
from gw_certificate.ag.ut_defines import PAYLOAD
|
|
9
|
+
from gw_certificate.common.debug import debug_print
|
|
10
|
+
from gw_certificate.api_if.gw_capabilities import GWCapabilities
|
|
11
|
+
from gw_certificate.interface.ble_simulator import BLESimulator
|
|
12
|
+
from gw_certificate.tests.static.uplink_defines import *
|
|
13
|
+
from gw_certificate.tests.uplink import TimestampsHelper
|
|
14
|
+
from gw_certificate.interface.mqtt import MqttClient
|
|
15
|
+
from gw_certificate.tests.static.generated_packet_table import StressRunData
|
|
16
|
+
from gw_certificate.tests.generic import PassCriteria, PERFECT_SCORE, GenericTest, GenericStage, INFORMATIVE
|
|
17
|
+
from gw_certificate.interface.packet_error import PacketError
|
|
18
|
+
|
|
19
|
+
# HELPER DEFINES
|
|
20
|
+
ONE_SECOND_MS = 1000
|
|
21
|
+
STRESS_DEFAULT_DELAYS = [50, 25, 16.66, 12.5, 10, 8.33, 7.14, 6.25, 5.55, 5, 4.5, 4, 3.5, 3]
|
|
22
|
+
STRESS_DEFAULT_PPS = [int(1000 / delay) for delay in STRESS_DEFAULT_DELAYS]
|
|
23
|
+
TIME_PER_DELAY = 30
|
|
24
|
+
TIME_PER_DELAY_FIRST = 50
|
|
25
|
+
|
|
26
|
+
# HELPER FUNCTIONS
|
|
27
|
+
|
|
28
|
+
def process_payload(packet:dict):
|
|
29
|
+
payload = packet[PAYLOAD]
|
|
30
|
+
payload = payload.upper()
|
|
31
|
+
if len(payload) == 62 and payload[:4] == '1E16':
|
|
32
|
+
payload = payload [4:]
|
|
33
|
+
# big2little endian
|
|
34
|
+
if payload[:4] == 'FCC6':
|
|
35
|
+
payload = 'C6FC' + payload[4:]
|
|
36
|
+
packet[PAYLOAD] = payload
|
|
37
|
+
return packet
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# TEST STAGES
|
|
41
|
+
|
|
42
|
+
class StressTestError(Exception):
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
class GenericStressStage(GenericStage):
|
|
46
|
+
def __init__(self, mqttc:MqttClient, ble_sim:BLESimulator, gw_capabilities:GWCapabilities, stage_name,
|
|
47
|
+
**kwargs):
|
|
48
|
+
self.__dict__.update(kwargs)
|
|
49
|
+
super().__init__(stage_name=stage_name, **self.__dict__)
|
|
50
|
+
|
|
51
|
+
self.result_indication = INFORMATIVE
|
|
52
|
+
|
|
53
|
+
# Clients
|
|
54
|
+
self.mqttc = mqttc
|
|
55
|
+
self.ble_sim = ble_sim
|
|
56
|
+
|
|
57
|
+
# Packets list
|
|
58
|
+
self.local_pkts = []
|
|
59
|
+
self.mqtt_pkts = []
|
|
60
|
+
self.full_test_pkts = pd.DataFrame()
|
|
61
|
+
|
|
62
|
+
# GW Capabilities
|
|
63
|
+
self.gw_capabilities = gw_capabilities
|
|
64
|
+
|
|
65
|
+
# Packet Error
|
|
66
|
+
self.packet_error = PacketError()
|
|
67
|
+
|
|
68
|
+
#Run Data
|
|
69
|
+
self.run_stress_data = StressRunData()
|
|
70
|
+
|
|
71
|
+
# Data extracted from the test csv
|
|
72
|
+
self.all_test_payloads = None
|
|
73
|
+
|
|
74
|
+
def prepare_stage(self, reset_ble_sim=True):
|
|
75
|
+
super().prepare_stage()
|
|
76
|
+
self.mqttc.flush_messages()
|
|
77
|
+
if reset_ble_sim:
|
|
78
|
+
self.ble_sim.set_sim_mode(True)
|
|
79
|
+
|
|
80
|
+
def fetch_mqtt_from_stage(self):
|
|
81
|
+
mqtt_pkts = self.mqttc.get_all_tags_pkts()
|
|
82
|
+
self.mqtt_pkts = list(map(lambda p: process_payload(p), mqtt_pkts))
|
|
83
|
+
|
|
84
|
+
def compare_local_mqtt(self):
|
|
85
|
+
self.fetch_mqtt_from_stage()
|
|
86
|
+
local_pkts_df = pd.DataFrame(self.local_pkts, columns=[PAYLOAD])
|
|
87
|
+
mqtt_pkts_df = pd.DataFrame(self.mqtt_pkts)
|
|
88
|
+
comparison = local_pkts_df
|
|
89
|
+
|
|
90
|
+
if PAYLOAD not in mqtt_pkts_df.columns:
|
|
91
|
+
mqtt_pkts_df[PAYLOAD] = ''
|
|
92
|
+
received_pkts_df = pd.merge(local_pkts_df[PAYLOAD], mqtt_pkts_df[PAYLOAD], how='inner')
|
|
93
|
+
|
|
94
|
+
received_pkts = set(received_pkts_df[PAYLOAD])
|
|
95
|
+
|
|
96
|
+
self.pkts_received_count = pd.Series.count(received_pkts_df)
|
|
97
|
+
unique_received_count = len(received_pkts)
|
|
98
|
+
self.pkts_filtered_out_count = self.pkts_received_count - unique_received_count
|
|
99
|
+
|
|
100
|
+
comparison[RECEIVED] = comparison[PAYLOAD].isin(received_pkts)
|
|
101
|
+
self.comparison = comparison
|
|
102
|
+
|
|
103
|
+
class StressTestStage(GenericStressStage):
|
|
104
|
+
|
|
105
|
+
def __init__(self, mqttc, ble_sim, gw_capabilities, **kwargs):
|
|
106
|
+
super().__init__(mqttc, ble_sim, gw_capabilities, stage_name=type(self).__name__, **kwargs)
|
|
107
|
+
self.duplicates = 1
|
|
108
|
+
self.report_data = {}
|
|
109
|
+
self.stage_tooltip = "Attempts different PPS (packets per second) rates to examine the gateway limit (actual PPS may vary per OS)"
|
|
110
|
+
desired_pps = kwargs.get('stress_pps', None)
|
|
111
|
+
|
|
112
|
+
def pps_to_delay(pps):
|
|
113
|
+
delay = 1000 / pps
|
|
114
|
+
trunctuated = int(delay * 100) / 100
|
|
115
|
+
return trunctuated
|
|
116
|
+
|
|
117
|
+
self.delays = STRESS_DEFAULT_DELAYS if desired_pps == None else [pps_to_delay(desired_pps)]
|
|
118
|
+
self.ts_records_arr = [TimestampsHelper() for delay in self.delays]
|
|
119
|
+
debug_print(f"StressTest delays configured: {self.delays}")
|
|
120
|
+
|
|
121
|
+
def run(self):
|
|
122
|
+
super().run()
|
|
123
|
+
for idx, delay in enumerate(self.delays):
|
|
124
|
+
self.stress_delay = delay
|
|
125
|
+
debug_print(f"---Running Stress Test with delay of {delay}")
|
|
126
|
+
self.run_full_stress_test(idx, delay)
|
|
127
|
+
|
|
128
|
+
def run_full_stress_test(self, delay_idx, delay):
|
|
129
|
+
self.prepare_stage()
|
|
130
|
+
self.run_stress_test_with_delay(delay_idx, delay)
|
|
131
|
+
self.generate_stage_report(delay_idx)
|
|
132
|
+
self.teardown_stage()
|
|
133
|
+
|
|
134
|
+
def prepare_stage(self):
|
|
135
|
+
super().prepare_stage()
|
|
136
|
+
self.local_pkts = []
|
|
137
|
+
|
|
138
|
+
def run_stress_test_with_delay(self, delay_idx, delay):
|
|
139
|
+
run_data = self.run_stress_data.data
|
|
140
|
+
end_time = datetime.datetime.now() + datetime.timedelta(seconds=TIME_PER_DELAY_FIRST if delay == STRESS_DEFAULT_DELAYS[0] else TIME_PER_DELAY)
|
|
141
|
+
last_sent_time = time.perf_counter_ns()
|
|
142
|
+
for index, row in run_data.iterrows():
|
|
143
|
+
if datetime.datetime.now() > end_time:
|
|
144
|
+
debug_print(f"Timeout for PPS rate {int(ONE_SECOND_MS / delay)} reached")
|
|
145
|
+
break
|
|
146
|
+
data = row[ADVA_PAYLOAD]
|
|
147
|
+
self.local_pkts.append(row[PAYLOAD])
|
|
148
|
+
while True:
|
|
149
|
+
if time.perf_counter_ns() - last_sent_time >= delay * 10**6:
|
|
150
|
+
self.ble_sim.send_packet(data, duplicates=self.duplicates, delay=0)
|
|
151
|
+
last_sent_time = time.perf_counter_ns()
|
|
152
|
+
break
|
|
153
|
+
self.ts_records_arr[delay_idx].set_adv_timestamp_current(data)
|
|
154
|
+
# Since stress entries in packet_table.csv are reused with different delays, we set it here.
|
|
155
|
+
# For each iteration a different ts_records instance that holds a different copy of the original dataframe.
|
|
156
|
+
self.ts_records_arr[delay_idx].table.loc[pd.DataFrame.notna(self.ts_records_arr[delay_idx].table[ADV_TIMESTAMP]), 'time_delay'] = delay
|
|
157
|
+
time.sleep(15)
|
|
158
|
+
|
|
159
|
+
def teardown_stage(self):
|
|
160
|
+
self.ble_sim.set_sim_mode(False)
|
|
161
|
+
self.mqttc.flush_messages()
|
|
162
|
+
|
|
163
|
+
def generate_stage_report(self, delay_idx):
|
|
164
|
+
def save_to_csv(df, csv_path):
|
|
165
|
+
"""
|
|
166
|
+
Save a DataFrame to a CSV file without overwriting existing content.
|
|
167
|
+
|
|
168
|
+
:param df: The DataFrame to be saved.
|
|
169
|
+
:param csv_path: The path to the CSV file.
|
|
170
|
+
"""
|
|
171
|
+
if os.path.exists(csv_path) and os.path.getsize(csv_path) > 0:
|
|
172
|
+
df.to_csv(csv_path, mode='a', header=False, index=False)
|
|
173
|
+
else:
|
|
174
|
+
df.to_csv(csv_path, mode='w', header=True, index=False)
|
|
175
|
+
|
|
176
|
+
self.compare_local_mqtt()
|
|
177
|
+
report = []
|
|
178
|
+
num_pkts_sent = len(self.comparison)
|
|
179
|
+
num_pkts_received = self.comparison['received'].eq(True).sum()
|
|
180
|
+
self.stage_pass = PERFECT_SCORE
|
|
181
|
+
self.comparison['duplications'] = self.duplicates
|
|
182
|
+
self.comparison['time_delay'] = self.stress_delay
|
|
183
|
+
self.full_test_pkts = pd.concat([self.full_test_pkts, self.comparison], ignore_index=True)
|
|
184
|
+
save_to_csv(self.comparison, self.csv_path)
|
|
185
|
+
|
|
186
|
+
if str(self.stress_delay) not in self.report_data:
|
|
187
|
+
self.report_data[str(self.stress_delay)] = {}
|
|
188
|
+
self.report_data[str(self.stress_delay)]['pkts_sent'] = num_pkts_sent
|
|
189
|
+
self.report_data[str(self.stress_delay)]['pkts_recieved'] = num_pkts_received
|
|
190
|
+
self.report_data[str(self.stress_delay)]['pkts_per_sec_desired'] = int(1000 / (self.stress_delay * self.duplicates))
|
|
191
|
+
self.report_data[str(self.stress_delay)]['pkts_per_sec_actual'] = (num_pkts_sent / (TIME_PER_DELAY_FIRST if self.stress_delay == STRESS_DEFAULT_DELAYS[0] else TIME_PER_DELAY))
|
|
192
|
+
self.report_data[str(self.stress_delay)]['percent_of_false_recieved'] = 100 - (num_pkts_received * 100 / num_pkts_sent)
|
|
193
|
+
|
|
194
|
+
self.ts_records_arr[delay_idx].validate_timestamps(self.mqtt_pkts)
|
|
195
|
+
|
|
196
|
+
if self.stress_delay == self.delays[-1]:
|
|
197
|
+
for delay in self.delays:
|
|
198
|
+
delay = str(delay)
|
|
199
|
+
report.append((f'Test - {self.report_data[delay]["pkts_per_sec_desired"]} packets per second. Actual packet per second - ', self.report_data[delay]["pkts_per_sec_actual"]))
|
|
200
|
+
report.append(((f'Number of packets sent'), self.report_data[delay]['pkts_sent']))
|
|
201
|
+
report.append(((f'Number of packets received'), self.report_data[delay]['pkts_recieved']))
|
|
202
|
+
self.add_report_header()
|
|
203
|
+
self.add_to_stage_report(tabulate.tabulate(pd.DataFrame(report), showindex=False))
|
|
204
|
+
for idx, delay in enumerate(self.delays):
|
|
205
|
+
self.ts_records_arr[idx].add_ts_errs_to_report(self)
|
|
206
|
+
if self.ts_records_arr[idx].is_ts_error():
|
|
207
|
+
self.add_report_line_separator()
|
|
208
|
+
self.add_to_stage_report(f'Stage data saved - {self.csv_path}')
|
|
209
|
+
|
|
210
|
+
fig_data = pd.DataFrame.from_dict(self.report_data, orient='index')
|
|
211
|
+
fig_data.reset_index(inplace=True)
|
|
212
|
+
fig_data.rename(columns={'index': 'time_delay'}, inplace=True)
|
|
213
|
+
fig = px.line(fig_data, x='pkts_per_sec_actual', y='percent_of_false_recieved', title='Percentage of packets not recieved by packets per second sent',
|
|
214
|
+
labels={'pkts_per_sec_actual': 'Packets Sent Per Second', 'false_percentage': 'Percentage of packets not recieved'})
|
|
215
|
+
graph_div = fig.to_html(full_html=False, include_plotlyjs='cdn')
|
|
216
|
+
|
|
217
|
+
# Generate HTML
|
|
218
|
+
table_html = self.template_engine.render_template('table.html', dataframe=self.comparison.to_html(table_id=self.stage_name), table_id=self.stage_name)
|
|
219
|
+
self.report_html = self.template_engine.render_template('stage.html', stage=self,
|
|
220
|
+
stage_report=self.report.split('\n'), graph=graph_div)
|
|
221
|
+
|
|
222
|
+
return self.report
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
STAGES = [StressTestStage]
|
|
226
|
+
|
|
227
|
+
class StressTest(GenericTest):
|
|
228
|
+
def __init__(self, **kwargs):
|
|
229
|
+
self.__dict__.update(kwargs)
|
|
230
|
+
super().__init__(**self.__dict__, test_name=type(self).__name__)
|
|
231
|
+
self.all_messages_in_test = []
|
|
232
|
+
self.stages = [stage(**self.__dict__) for stage in STAGES]
|
|
233
|
+
self.result_indication = INFORMATIVE
|
|
234
|
+
self.test_tooltip = "Stages attempting to stress test a maximum throughput"
|
|
235
|
+
|
|
236
|
+
def run(self):
|
|
237
|
+
super().run()
|
|
238
|
+
self.test_pass = PERFECT_SCORE
|
|
239
|
+
for stage in self.stages:
|
|
240
|
+
stage.prepare_stage()
|
|
241
|
+
stage.run()
|
|
242
|
+
self.test_pass = PassCriteria.calc_for_test(self.test_pass, stage)
|
|
243
|
+
self.all_messages_in_test.extend(self.mqttc.get_all_messages_from_topic('data'))
|
|
244
|
+
|