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.
Files changed (51) hide show
  1. gw_certificate/__init__.py +0 -0
  2. gw_certificate/ag/ut_defines.py +361 -0
  3. gw_certificate/ag/wlt_types.py +85 -0
  4. gw_certificate/ag/wlt_types_ag.py +5310 -0
  5. gw_certificate/ag/wlt_types_data.py +64 -0
  6. gw_certificate/api/extended_api.py +1547 -0
  7. gw_certificate/api_if/__init__.py +0 -0
  8. gw_certificate/api_if/api_validation.py +40 -0
  9. gw_certificate/api_if/gw_capabilities.py +18 -0
  10. gw_certificate/common/analysis_data_bricks.py +1455 -0
  11. gw_certificate/common/debug.py +63 -0
  12. gw_certificate/common/utils.py +219 -0
  13. gw_certificate/common/utils_defines.py +102 -0
  14. gw_certificate/common/wltPb_pb2.py +72 -0
  15. gw_certificate/common/wltPb_pb2.pyi +227 -0
  16. gw_certificate/gw_certificate.py +138 -0
  17. gw_certificate/gw_certificate_cli.py +70 -0
  18. gw_certificate/interface/ble_simulator.py +91 -0
  19. gw_certificate/interface/ble_sniffer.py +189 -0
  20. gw_certificate/interface/if_defines.py +35 -0
  21. gw_certificate/interface/mqtt.py +469 -0
  22. gw_certificate/interface/packet_error.py +22 -0
  23. gw_certificate/interface/pkt_generator.py +720 -0
  24. gw_certificate/interface/uart_if.py +193 -0
  25. gw_certificate/interface/uart_ports.py +20 -0
  26. gw_certificate/templates/results.html +241 -0
  27. gw_certificate/templates/stage.html +22 -0
  28. gw_certificate/templates/table.html +6 -0
  29. gw_certificate/templates/test.html +38 -0
  30. gw_certificate/tests/__init__.py +11 -0
  31. gw_certificate/tests/actions.py +131 -0
  32. gw_certificate/tests/bad_crc_to_PER_quantization.csv +51 -0
  33. gw_certificate/tests/connection.py +181 -0
  34. gw_certificate/tests/downlink.py +174 -0
  35. gw_certificate/tests/generic.py +161 -0
  36. gw_certificate/tests/registration.py +288 -0
  37. gw_certificate/tests/static/__init__.py +0 -0
  38. gw_certificate/tests/static/connection_defines.py +9 -0
  39. gw_certificate/tests/static/downlink_defines.py +9 -0
  40. gw_certificate/tests/static/generated_packet_table.py +209 -0
  41. gw_certificate/tests/static/packet_table.csv +10051 -0
  42. gw_certificate/tests/static/references.py +4 -0
  43. gw_certificate/tests/static/uplink_defines.py +20 -0
  44. gw_certificate/tests/throughput.py +244 -0
  45. gw_certificate/tests/uplink.py +683 -0
  46. wiliot_certificate-1.3.0a1.dist-info/LICENSE +21 -0
  47. wiliot_certificate-1.3.0a1.dist-info/METADATA +113 -0
  48. wiliot_certificate-1.3.0a1.dist-info/RECORD +51 -0
  49. wiliot_certificate-1.3.0a1.dist-info/WHEEL +5 -0
  50. wiliot_certificate-1.3.0a1.dist-info/entry_points.txt +2 -0
  51. 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
+