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,227 @@
|
|
|
1
|
+
from google.protobuf.internal import containers as _containers
|
|
2
|
+
from google.protobuf import descriptor as _descriptor
|
|
3
|
+
from google.protobuf import message as _message
|
|
4
|
+
from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union
|
|
5
|
+
|
|
6
|
+
DESCRIPTOR: _descriptor.FileDescriptor
|
|
7
|
+
|
|
8
|
+
class Location(_message.Message):
|
|
9
|
+
__slots__ = ("lat", "lng")
|
|
10
|
+
LAT_FIELD_NUMBER: _ClassVar[int]
|
|
11
|
+
LNG_FIELD_NUMBER: _ClassVar[int]
|
|
12
|
+
lat: float
|
|
13
|
+
lng: float
|
|
14
|
+
def __init__(self, lat: _Optional[float] = ..., lng: _Optional[float] = ...) -> None: ...
|
|
15
|
+
|
|
16
|
+
class Value(_message.Message):
|
|
17
|
+
__slots__ = ("integerValue", "numberValue", "stringValue", "boolValue")
|
|
18
|
+
INTEGERVALUE_FIELD_NUMBER: _ClassVar[int]
|
|
19
|
+
NUMBERVALUE_FIELD_NUMBER: _ClassVar[int]
|
|
20
|
+
STRINGVALUE_FIELD_NUMBER: _ClassVar[int]
|
|
21
|
+
BOOLVALUE_FIELD_NUMBER: _ClassVar[int]
|
|
22
|
+
integerValue: int
|
|
23
|
+
numberValue: float
|
|
24
|
+
stringValue: str
|
|
25
|
+
boolValue: bool
|
|
26
|
+
def __init__(self, integerValue: _Optional[int] = ..., numberValue: _Optional[float] = ..., stringValue: _Optional[str] = ..., boolValue: bool = ...) -> None: ...
|
|
27
|
+
|
|
28
|
+
class GatewayData(_message.Message):
|
|
29
|
+
__slots__ = ("gatewayId", "timestamp", "packets", "location")
|
|
30
|
+
class Packet(_message.Message):
|
|
31
|
+
__slots__ = ("payload", "timestamp", "sequenceId", "rssi", "aliasBridgeId")
|
|
32
|
+
PAYLOAD_FIELD_NUMBER: _ClassVar[int]
|
|
33
|
+
TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
|
|
34
|
+
SEQUENCEID_FIELD_NUMBER: _ClassVar[int]
|
|
35
|
+
RSSI_FIELD_NUMBER: _ClassVar[int]
|
|
36
|
+
ALIASBRIDGEID_FIELD_NUMBER: _ClassVar[int]
|
|
37
|
+
payload: bytes
|
|
38
|
+
timestamp: int
|
|
39
|
+
sequenceId: int
|
|
40
|
+
rssi: int
|
|
41
|
+
aliasBridgeId: str
|
|
42
|
+
def __init__(self, payload: _Optional[bytes] = ..., timestamp: _Optional[int] = ..., sequenceId: _Optional[int] = ..., rssi: _Optional[int] = ..., aliasBridgeId: _Optional[str] = ...) -> None: ...
|
|
43
|
+
GATEWAYID_FIELD_NUMBER: _ClassVar[int]
|
|
44
|
+
TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
|
|
45
|
+
PACKETS_FIELD_NUMBER: _ClassVar[int]
|
|
46
|
+
LOCATION_FIELD_NUMBER: _ClassVar[int]
|
|
47
|
+
gatewayId: str
|
|
48
|
+
timestamp: int
|
|
49
|
+
packets: _containers.RepeatedCompositeFieldContainer[GatewayData.Packet]
|
|
50
|
+
location: Location
|
|
51
|
+
def __init__(self, gatewayId: _Optional[str] = ..., timestamp: _Optional[int] = ..., packets: _Optional[_Iterable[_Union[GatewayData.Packet, _Mapping]]] = ..., location: _Optional[_Union[Location, _Mapping]] = ...) -> None: ...
|
|
52
|
+
|
|
53
|
+
class UplinkMessage(_message.Message):
|
|
54
|
+
__slots__ = ("gatewayStatus", "gatewayInfo", "gatewayLogs", "actionStatus")
|
|
55
|
+
GATEWAYSTATUS_FIELD_NUMBER: _ClassVar[int]
|
|
56
|
+
GATEWAYINFO_FIELD_NUMBER: _ClassVar[int]
|
|
57
|
+
GATEWAYLOGS_FIELD_NUMBER: _ClassVar[int]
|
|
58
|
+
ACTIONSTATUS_FIELD_NUMBER: _ClassVar[int]
|
|
59
|
+
gatewayStatus: GatewayStatus
|
|
60
|
+
gatewayInfo: GatewayInfo
|
|
61
|
+
gatewayLogs: GatewayLogs
|
|
62
|
+
actionStatus: ActionStatus
|
|
63
|
+
def __init__(self, gatewayStatus: _Optional[_Union[GatewayStatus, _Mapping]] = ..., gatewayInfo: _Optional[_Union[GatewayInfo, _Mapping]] = ..., gatewayLogs: _Optional[_Union[GatewayLogs, _Mapping]] = ..., actionStatus: _Optional[_Union[ActionStatus, _Mapping]] = ...) -> None: ...
|
|
64
|
+
|
|
65
|
+
class GatewayStatus(_message.Message):
|
|
66
|
+
__slots__ = ("gatewayId", "gatewayType", "downlinkSupported", "bridgeOtaUpgradeSupported", "apiVersion", "version", "bleSwVersion", "interfaceSwVersion", "location", "config", "bleAddress")
|
|
67
|
+
class ConfigEntry(_message.Message):
|
|
68
|
+
__slots__ = ("key", "value")
|
|
69
|
+
KEY_FIELD_NUMBER: _ClassVar[int]
|
|
70
|
+
VALUE_FIELD_NUMBER: _ClassVar[int]
|
|
71
|
+
key: str
|
|
72
|
+
value: Value
|
|
73
|
+
def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[Value, _Mapping]] = ...) -> None: ...
|
|
74
|
+
GATEWAYID_FIELD_NUMBER: _ClassVar[int]
|
|
75
|
+
GATEWAYTYPE_FIELD_NUMBER: _ClassVar[int]
|
|
76
|
+
DOWNLINKSUPPORTED_FIELD_NUMBER: _ClassVar[int]
|
|
77
|
+
BRIDGEOTAUPGRADESUPPORTED_FIELD_NUMBER: _ClassVar[int]
|
|
78
|
+
APIVERSION_FIELD_NUMBER: _ClassVar[int]
|
|
79
|
+
VERSION_FIELD_NUMBER: _ClassVar[int]
|
|
80
|
+
BLESWVERSION_FIELD_NUMBER: _ClassVar[int]
|
|
81
|
+
INTERFACESWVERSION_FIELD_NUMBER: _ClassVar[int]
|
|
82
|
+
LOCATION_FIELD_NUMBER: _ClassVar[int]
|
|
83
|
+
CONFIG_FIELD_NUMBER: _ClassVar[int]
|
|
84
|
+
BLEADDRESS_FIELD_NUMBER: _ClassVar[int]
|
|
85
|
+
gatewayId: str
|
|
86
|
+
gatewayType: str
|
|
87
|
+
downlinkSupported: bool
|
|
88
|
+
bridgeOtaUpgradeSupported: bool
|
|
89
|
+
apiVersion: int
|
|
90
|
+
version: str
|
|
91
|
+
bleSwVersion: str
|
|
92
|
+
interfaceSwVersion: str
|
|
93
|
+
location: Location
|
|
94
|
+
config: _containers.MessageMap[str, Value]
|
|
95
|
+
bleAddress: str
|
|
96
|
+
def __init__(self, gatewayId: _Optional[str] = ..., gatewayType: _Optional[str] = ..., downlinkSupported: bool = ..., bridgeOtaUpgradeSupported: bool = ..., apiVersion: _Optional[int] = ..., version: _Optional[str] = ..., bleSwVersion: _Optional[str] = ..., interfaceSwVersion: _Optional[str] = ..., location: _Optional[_Union[Location, _Mapping]] = ..., config: _Optional[_Mapping[str, Value]] = ..., bleAddress: _Optional[str] = ...) -> None: ...
|
|
97
|
+
|
|
98
|
+
class GatewayInfo(_message.Message):
|
|
99
|
+
__slots__ = ("entries",)
|
|
100
|
+
class EntriesEntry(_message.Message):
|
|
101
|
+
__slots__ = ("key", "value")
|
|
102
|
+
KEY_FIELD_NUMBER: _ClassVar[int]
|
|
103
|
+
VALUE_FIELD_NUMBER: _ClassVar[int]
|
|
104
|
+
key: str
|
|
105
|
+
value: Value
|
|
106
|
+
def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[Value, _Mapping]] = ...) -> None: ...
|
|
107
|
+
ENTRIES_FIELD_NUMBER: _ClassVar[int]
|
|
108
|
+
entries: _containers.MessageMap[str, Value]
|
|
109
|
+
def __init__(self, entries: _Optional[_Mapping[str, Value]] = ...) -> None: ...
|
|
110
|
+
|
|
111
|
+
class GatewayLogs(_message.Message):
|
|
112
|
+
__slots__ = ("logs",)
|
|
113
|
+
LOGS_FIELD_NUMBER: _ClassVar[int]
|
|
114
|
+
logs: _containers.RepeatedScalarFieldContainer[str]
|
|
115
|
+
def __init__(self, logs: _Optional[_Iterable[str]] = ...) -> None: ...
|
|
116
|
+
|
|
117
|
+
class ActionStatus(_message.Message):
|
|
118
|
+
__slots__ = ("action", "status")
|
|
119
|
+
ACTION_FIELD_NUMBER: _ClassVar[int]
|
|
120
|
+
STATUS_FIELD_NUMBER: _ClassVar[int]
|
|
121
|
+
action: int
|
|
122
|
+
status: int
|
|
123
|
+
def __init__(self, action: _Optional[int] = ..., status: _Optional[int] = ...) -> None: ...
|
|
124
|
+
|
|
125
|
+
class DownlinkMessage(_message.Message):
|
|
126
|
+
__slots__ = ("txPacket", "gatewayAction", "bridgeUpgrade", "gatewayConfig", "customBroker", "customMessage")
|
|
127
|
+
TXPACKET_FIELD_NUMBER: _ClassVar[int]
|
|
128
|
+
GATEWAYACTION_FIELD_NUMBER: _ClassVar[int]
|
|
129
|
+
BRIDGEUPGRADE_FIELD_NUMBER: _ClassVar[int]
|
|
130
|
+
GATEWAYCONFIG_FIELD_NUMBER: _ClassVar[int]
|
|
131
|
+
CUSTOMBROKER_FIELD_NUMBER: _ClassVar[int]
|
|
132
|
+
CUSTOMMESSAGE_FIELD_NUMBER: _ClassVar[int]
|
|
133
|
+
txPacket: TxPacket
|
|
134
|
+
gatewayAction: GatewayAction
|
|
135
|
+
bridgeUpgrade: BridgeUpgrade
|
|
136
|
+
gatewayConfig: GatewayConfig
|
|
137
|
+
customBroker: CustomBroker
|
|
138
|
+
customMessage: CustomMessage
|
|
139
|
+
def __init__(self, txPacket: _Optional[_Union[TxPacket, _Mapping]] = ..., gatewayAction: _Optional[_Union[GatewayAction, _Mapping]] = ..., bridgeUpgrade: _Optional[_Union[BridgeUpgrade, _Mapping]] = ..., gatewayConfig: _Optional[_Union[GatewayConfig, _Mapping]] = ..., customBroker: _Optional[_Union[CustomBroker, _Mapping]] = ..., customMessage: _Optional[_Union[CustomMessage, _Mapping]] = ...) -> None: ...
|
|
140
|
+
|
|
141
|
+
class TxPacket(_message.Message):
|
|
142
|
+
__slots__ = ("payload", "maxRetries", "maxDurationMs")
|
|
143
|
+
PAYLOAD_FIELD_NUMBER: _ClassVar[int]
|
|
144
|
+
MAXRETRIES_FIELD_NUMBER: _ClassVar[int]
|
|
145
|
+
MAXDURATIONMS_FIELD_NUMBER: _ClassVar[int]
|
|
146
|
+
payload: bytes
|
|
147
|
+
maxRetries: int
|
|
148
|
+
maxDurationMs: int
|
|
149
|
+
def __init__(self, payload: _Optional[bytes] = ..., maxRetries: _Optional[int] = ..., maxDurationMs: _Optional[int] = ...) -> None: ...
|
|
150
|
+
|
|
151
|
+
class GatewayAction(_message.Message):
|
|
152
|
+
__slots__ = ("action",)
|
|
153
|
+
ACTION_FIELD_NUMBER: _ClassVar[int]
|
|
154
|
+
action: str
|
|
155
|
+
def __init__(self, action: _Optional[str] = ...) -> None: ...
|
|
156
|
+
|
|
157
|
+
class BridgeUpgrade(_message.Message):
|
|
158
|
+
__slots__ = ("rebootPacket", "txMaxDurationMs", "txMaxRetries", "bridgeId", "versionUuid", "upgradeBlSd", "imageDirUrl")
|
|
159
|
+
REBOOTPACKET_FIELD_NUMBER: _ClassVar[int]
|
|
160
|
+
TXMAXDURATIONMS_FIELD_NUMBER: _ClassVar[int]
|
|
161
|
+
TXMAXRETRIES_FIELD_NUMBER: _ClassVar[int]
|
|
162
|
+
BRIDGEID_FIELD_NUMBER: _ClassVar[int]
|
|
163
|
+
VERSIONUUID_FIELD_NUMBER: _ClassVar[int]
|
|
164
|
+
UPGRADEBLSD_FIELD_NUMBER: _ClassVar[int]
|
|
165
|
+
IMAGEDIRURL_FIELD_NUMBER: _ClassVar[int]
|
|
166
|
+
rebootPacket: bytes
|
|
167
|
+
txMaxDurationMs: int
|
|
168
|
+
txMaxRetries: int
|
|
169
|
+
bridgeId: str
|
|
170
|
+
versionUuid: str
|
|
171
|
+
upgradeBlSd: bool
|
|
172
|
+
imageDirUrl: str
|
|
173
|
+
def __init__(self, rebootPacket: _Optional[bytes] = ..., txMaxDurationMs: _Optional[int] = ..., txMaxRetries: _Optional[int] = ..., bridgeId: _Optional[str] = ..., versionUuid: _Optional[str] = ..., upgradeBlSd: bool = ..., imageDirUrl: _Optional[str] = ...) -> None: ...
|
|
174
|
+
|
|
175
|
+
class GatewayConfig(_message.Message):
|
|
176
|
+
__slots__ = ("version", "bleSwVersion", "interfaceSwVersion", "location", "config")
|
|
177
|
+
class ConfigEntry(_message.Message):
|
|
178
|
+
__slots__ = ("key", "value")
|
|
179
|
+
KEY_FIELD_NUMBER: _ClassVar[int]
|
|
180
|
+
VALUE_FIELD_NUMBER: _ClassVar[int]
|
|
181
|
+
key: str
|
|
182
|
+
value: Value
|
|
183
|
+
def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[Value, _Mapping]] = ...) -> None: ...
|
|
184
|
+
VERSION_FIELD_NUMBER: _ClassVar[int]
|
|
185
|
+
BLESWVERSION_FIELD_NUMBER: _ClassVar[int]
|
|
186
|
+
INTERFACESWVERSION_FIELD_NUMBER: _ClassVar[int]
|
|
187
|
+
LOCATION_FIELD_NUMBER: _ClassVar[int]
|
|
188
|
+
CONFIG_FIELD_NUMBER: _ClassVar[int]
|
|
189
|
+
version: str
|
|
190
|
+
bleSwVersion: str
|
|
191
|
+
interfaceSwVersion: str
|
|
192
|
+
location: Location
|
|
193
|
+
config: _containers.MessageMap[str, Value]
|
|
194
|
+
def __init__(self, version: _Optional[str] = ..., bleSwVersion: _Optional[str] = ..., interfaceSwVersion: _Optional[str] = ..., location: _Optional[_Union[Location, _Mapping]] = ..., config: _Optional[_Mapping[str, Value]] = ...) -> None: ...
|
|
195
|
+
|
|
196
|
+
class CustomBroker(_message.Message):
|
|
197
|
+
__slots__ = ("customBroker", "port", "brokerUrl", "username", "password", "updateTopic", "statusTopic", "dataTopic")
|
|
198
|
+
CUSTOMBROKER_FIELD_NUMBER: _ClassVar[int]
|
|
199
|
+
PORT_FIELD_NUMBER: _ClassVar[int]
|
|
200
|
+
BROKERURL_FIELD_NUMBER: _ClassVar[int]
|
|
201
|
+
USERNAME_FIELD_NUMBER: _ClassVar[int]
|
|
202
|
+
PASSWORD_FIELD_NUMBER: _ClassVar[int]
|
|
203
|
+
UPDATETOPIC_FIELD_NUMBER: _ClassVar[int]
|
|
204
|
+
STATUSTOPIC_FIELD_NUMBER: _ClassVar[int]
|
|
205
|
+
DATATOPIC_FIELD_NUMBER: _ClassVar[int]
|
|
206
|
+
customBroker: bool
|
|
207
|
+
port: int
|
|
208
|
+
brokerUrl: str
|
|
209
|
+
username: str
|
|
210
|
+
password: str
|
|
211
|
+
updateTopic: str
|
|
212
|
+
statusTopic: str
|
|
213
|
+
dataTopic: str
|
|
214
|
+
def __init__(self, customBroker: bool = ..., port: _Optional[int] = ..., brokerUrl: _Optional[str] = ..., username: _Optional[str] = ..., password: _Optional[str] = ..., updateTopic: _Optional[str] = ..., statusTopic: _Optional[str] = ..., dataTopic: _Optional[str] = ...) -> None: ...
|
|
215
|
+
|
|
216
|
+
class CustomMessage(_message.Message):
|
|
217
|
+
__slots__ = ("entries",)
|
|
218
|
+
class EntriesEntry(_message.Message):
|
|
219
|
+
__slots__ = ("key", "value")
|
|
220
|
+
KEY_FIELD_NUMBER: _ClassVar[int]
|
|
221
|
+
VALUE_FIELD_NUMBER: _ClassVar[int]
|
|
222
|
+
key: str
|
|
223
|
+
value: Value
|
|
224
|
+
def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[Value, _Mapping]] = ...) -> None: ...
|
|
225
|
+
ENTRIES_FIELD_NUMBER: _ClassVar[int]
|
|
226
|
+
entries: _containers.MessageMap[str, Value]
|
|
227
|
+
def __init__(self, entries: _Optional[_Mapping[str, Value]] = ...) -> None: ...
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# External Imports
|
|
2
|
+
import time
|
|
3
|
+
import datetime
|
|
4
|
+
import os
|
|
5
|
+
import webbrowser
|
|
6
|
+
from jinja2 import Environment, FileSystemLoader
|
|
7
|
+
import pkg_resources
|
|
8
|
+
import time
|
|
9
|
+
import importlib.metadata
|
|
10
|
+
from wiliot_core.utils.utils import WiliotDir
|
|
11
|
+
|
|
12
|
+
# Internal Imports
|
|
13
|
+
from gw_certificate.common.analysis_data_bricks import initialize_logger
|
|
14
|
+
from gw_certificate.common.debug import debug_print
|
|
15
|
+
from gw_certificate.interface.ble_sniffer import BLESniffer
|
|
16
|
+
from gw_certificate.interface.uart_if import UARTError, UARTInterface
|
|
17
|
+
from gw_certificate.interface.ble_simulator import BLESimulator
|
|
18
|
+
from gw_certificate.interface.mqtt import MqttClient
|
|
19
|
+
from gw_certificate.tests import *
|
|
20
|
+
from gw_certificate.interface.uart_ports import get_uart_ports
|
|
21
|
+
from gw_certificate.api_if.gw_capabilities import GWCapabilities
|
|
22
|
+
from gw_certificate.tests import TESTS_NO_UART
|
|
23
|
+
|
|
24
|
+
GW_CERT_VERSION = importlib.metadata.version("wiliot-certificate")
|
|
25
|
+
|
|
26
|
+
class GWCertificateError(Exception):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TemplateEngine:
|
|
31
|
+
def __init__(self):
|
|
32
|
+
self.template_dir = pkg_resources.resource_filename(__name__, 'templates')
|
|
33
|
+
self.env = Environment(loader=FileSystemLoader(self.template_dir))
|
|
34
|
+
|
|
35
|
+
def get_template(self, template_name):
|
|
36
|
+
return self.env.get_template(template_name)
|
|
37
|
+
|
|
38
|
+
def render_template(self, template_name, **kwargs):
|
|
39
|
+
template = self.get_template(template_name)
|
|
40
|
+
return template.render(**kwargs)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class GWCertificate:
|
|
44
|
+
def __init__(self, gw_id, owner_id, tests:list = TESTS_DEFAULT, topic_suffix='', update_fw=False, stress_pps=None, aggregation_time=0, env='prod'):
|
|
45
|
+
# Runtime
|
|
46
|
+
self.env_dirs = WiliotDir()
|
|
47
|
+
self.current_datetime = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
48
|
+
self.certificate_dir = os.path.join(self.env_dirs.get_wiliot_root_app_dir(), 'gw-certificate', self.current_datetime)
|
|
49
|
+
self.env_dirs.create_dir(self.certificate_dir)
|
|
50
|
+
self.start_timestamp = initialize_logger(self.certificate_dir)
|
|
51
|
+
self.logger_filepath = os.path.join(self.certificate_dir, f'{self.start_timestamp}.log')
|
|
52
|
+
self.mqtt_logger_filepath = os.path.join(self.certificate_dir, f'{self.start_timestamp}_mqtt.log')
|
|
53
|
+
self.sniffer_logger_filepath = os.path.join(self.certificate_dir, f'{self.start_timestamp}_sniffer.log')
|
|
54
|
+
self.result_html_path = os.path.join(self.certificate_dir, f'results_{self.current_datetime}.html')
|
|
55
|
+
self.template_engine = TemplateEngine()
|
|
56
|
+
self.env = env
|
|
57
|
+
|
|
58
|
+
# Version
|
|
59
|
+
self.gw_cert_version = GW_CERT_VERSION
|
|
60
|
+
|
|
61
|
+
# GW & specific tests related
|
|
62
|
+
self.gw_id = gw_id
|
|
63
|
+
self.owner_id = owner_id
|
|
64
|
+
self.topic_suffix = topic_suffix
|
|
65
|
+
self.mqttc = MqttClient(gw_id, owner_id, self.mqtt_logger_filepath, topic_suffix=topic_suffix, broker='eclipse')
|
|
66
|
+
self.gw_capabilities = GWCapabilities()
|
|
67
|
+
self.stress_pps = stress_pps
|
|
68
|
+
self.aggregation_time = aggregation_time
|
|
69
|
+
|
|
70
|
+
# UART-related. Require only when running tests that need it
|
|
71
|
+
self.use_uart = not all(test in TESTS_NO_UART for test in tests)
|
|
72
|
+
if self.use_uart:
|
|
73
|
+
self.uart = None
|
|
74
|
+
self.uart_comports = get_uart_ports()
|
|
75
|
+
debug_print(f'UART Ports:{self.uart_comports}')
|
|
76
|
+
if len(self.uart_comports) < 1:
|
|
77
|
+
raise GWCertificateError('A Wiliot tester board must be connected to USB!')
|
|
78
|
+
|
|
79
|
+
for port in self.uart_comports:
|
|
80
|
+
try:
|
|
81
|
+
# TODO Fix UARTInterface firmware update and then call it with the flag set to True
|
|
82
|
+
self.uart = UARTInterface(port, update_fw=update_fw)
|
|
83
|
+
break
|
|
84
|
+
except UARTError as e:
|
|
85
|
+
debug_print(f'Port: {port} - {e}')
|
|
86
|
+
if type(self.uart) is not UARTInterface:
|
|
87
|
+
raise GWCertificateError("Cannot initialize any port!")
|
|
88
|
+
self.ble_sim = BLESimulator(self.uart)
|
|
89
|
+
self.sniffer = BLESniffer(self.uart, logger_filepath=self.sniffer_logger_filepath)
|
|
90
|
+
|
|
91
|
+
# Tests
|
|
92
|
+
self.tests = [t(**self.__dict__) for t in tests]
|
|
93
|
+
debug_print(f'Running Tests: {self.tests}')
|
|
94
|
+
|
|
95
|
+
def run_tests(self):
|
|
96
|
+
debug_print("Sleeping 20 seconds after mqtt connect")
|
|
97
|
+
time.sleep(20)
|
|
98
|
+
|
|
99
|
+
# capabilities_received = ConnectionTest in self.tests
|
|
100
|
+
for test in self.tests:
|
|
101
|
+
# if capabilities_received:
|
|
102
|
+
# if (type(test) == DownlinkTest and self.gw_capabilities.downlinkSupported == False):
|
|
103
|
+
# debug_print(f'# Skipping {type(test)} since it is not a supported capability. #')
|
|
104
|
+
# continue
|
|
105
|
+
test.run()
|
|
106
|
+
test.end_test()
|
|
107
|
+
|
|
108
|
+
def create_results_html(self):
|
|
109
|
+
with open(self.logger_filepath, 'r') as f:
|
|
110
|
+
log = f.read().split('\n')
|
|
111
|
+
with open(self.mqtt_logger_filepath, 'r') as f:
|
|
112
|
+
mqtt_log = f.read().split('\n')
|
|
113
|
+
if self.use_uart:
|
|
114
|
+
with open(self.sniffer_logger_filepath, 'r') as f:
|
|
115
|
+
sniffer_log = f.read().split('\n')
|
|
116
|
+
else:
|
|
117
|
+
sniffer_log = []
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
html = self.template_engine.render_template('results.html', tests=self.tests,
|
|
121
|
+
log = log,
|
|
122
|
+
mqtt_log = mqtt_log,
|
|
123
|
+
sniffer_log = sniffer_log,
|
|
124
|
+
gw_id = self.gw_id,
|
|
125
|
+
version = self.gw_cert_version,
|
|
126
|
+
datetime = self.current_datetime)
|
|
127
|
+
with open(self.result_html_path, 'w', encoding="utf-8") as f:
|
|
128
|
+
f.write(html)
|
|
129
|
+
debug_print("Test Finished. Results HTML Saved: " + self.result_html_path)
|
|
130
|
+
webbrowser.open('file://' + os.path.realpath(self.result_html_path))
|
|
131
|
+
|
|
132
|
+
if __name__ == "__main__":
|
|
133
|
+
from api_secrets import *
|
|
134
|
+
gw_id = 'gw_id'
|
|
135
|
+
owner_id = 'owner_id'
|
|
136
|
+
gwc = GWCertificate(gw_id=gw_id,owner_id=owner_id, tests=[ConnectionTest, DownlinkTest, UplinkTest, StressTest])
|
|
137
|
+
gwc.run_tests()
|
|
138
|
+
gwc.create_results_html()
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
|
|
2
|
+
import time
|
|
3
|
+
from argparse import ArgumentParser
|
|
4
|
+
|
|
5
|
+
from gw_certificate.gw_certificate import GWCertificate, GW_CERT_VERSION
|
|
6
|
+
from gw_certificate.tests import TESTS, TESTS_DEFAULT
|
|
7
|
+
from gw_certificate.tests.throughput import STRESS_DEFAULT_PPS
|
|
8
|
+
from gw_certificate.tests.registration import REG_CERT_OWNER_ID, RegistrationTest
|
|
9
|
+
from gw_certificate.tests.uplink import UplinkTest
|
|
10
|
+
from gw_certificate.tests.throughput import StressTest
|
|
11
|
+
|
|
12
|
+
def filter_tests(tests_names):
|
|
13
|
+
chosen_tests = []
|
|
14
|
+
if tests_names == []:
|
|
15
|
+
return TESTS_DEFAULT
|
|
16
|
+
for test_class in TESTS:
|
|
17
|
+
for test_name in tests_names:
|
|
18
|
+
if test_name in test_class.__name__.lower() and test_class not in chosen_tests:
|
|
19
|
+
chosen_tests.append(test_class)
|
|
20
|
+
return chosen_tests
|
|
21
|
+
|
|
22
|
+
def main():
|
|
23
|
+
usage = (
|
|
24
|
+
"usage: wlt-gw-certificate [-h] -owner OWNER -gw GW\n"
|
|
25
|
+
f" [-tests {{connection, uplink, downlink, stress}}] [-update] [-pps {STRESS_DEFAULT_PPS}]\n"
|
|
26
|
+
" [-agg AGG] [-env {prod, test, dev}]"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
parser = ArgumentParser(prog='wlt-gw-certificate',
|
|
30
|
+
description=f'Gateway Certificate {GW_CERT_VERSION} - CLI Tool to test Wiliot GWs', usage=usage)
|
|
31
|
+
|
|
32
|
+
required = parser.add_argument_group('required arguments')
|
|
33
|
+
required.add_argument('-gw', type=str, help="Gateway ID", required=True)
|
|
34
|
+
optional = parser.add_argument_group('optional arguments')
|
|
35
|
+
optional.add_argument('-owner', type=str, help="Owner ID", required=False, default=REG_CERT_OWNER_ID)
|
|
36
|
+
optional.add_argument('-suffix', type=str, help="Topic suffix", default='', required=False)
|
|
37
|
+
optional.add_argument('-tests', type=str, choices=['registration', 'connection', 'uplink', 'downlink', 'actions', 'stress'],
|
|
38
|
+
help="Tests to run. Registration omitted by default.", required=False, nargs='+', default=[])
|
|
39
|
+
optional.add_argument('-update', action='store_true', help='Update test board firmware', default=False, required=False)
|
|
40
|
+
optional.add_argument('-pps', type=int, help='Single packets-per-second rate to simulate in the stress test',
|
|
41
|
+
choices=STRESS_DEFAULT_PPS, default=None, required=False)
|
|
42
|
+
optional.add_argument('-agg', type=int, help='Aggregation time [seconds] the Uplink stages wait before processing results',
|
|
43
|
+
default=0, required=False)
|
|
44
|
+
optional.add_argument('-env', type=str, help='Environment for the registration test (Internal usage)',
|
|
45
|
+
choices=['prod', 'test', 'dev'], default='prod', required=False)
|
|
46
|
+
args = parser.parse_args()
|
|
47
|
+
|
|
48
|
+
tests = filter_tests(args.tests)
|
|
49
|
+
topic_suffix = '' if args.suffix == '' else '-'+args.suffix
|
|
50
|
+
|
|
51
|
+
if args.pps != None and StressTest not in tests:
|
|
52
|
+
parser.error("Packets per second (-pps) flag can only be used when 'stress' is included in test list (e.g. -tests stress)")
|
|
53
|
+
if args.agg != 0 and UplinkTest not in tests:
|
|
54
|
+
parser.error("Aggregation time (-agg) flag can only be used when 'uplink' is included in test list (e.g. -tests uplink)")
|
|
55
|
+
if args.owner == REG_CERT_OWNER_ID and not all(test == RegistrationTest for test in tests):
|
|
56
|
+
print(f"Note: using default owner ID (-owner) - {REG_CERT_OWNER_ID}..")
|
|
57
|
+
time.sleep(2)
|
|
58
|
+
# parser.error("The -owner flag is required when running any test other than the RegistrationTest.")
|
|
59
|
+
|
|
60
|
+
gwc = GWCertificate(gw_id=args.gw, owner_id=args.owner, topic_suffix=topic_suffix, tests=tests, update_fw=args.update,
|
|
61
|
+
stress_pps=args.pps, aggregation_time=args.agg, env=args.env)
|
|
62
|
+
gwc.run_tests()
|
|
63
|
+
gwc.create_results_html()
|
|
64
|
+
|
|
65
|
+
def main_cli():
|
|
66
|
+
main()
|
|
67
|
+
|
|
68
|
+
if __name__ == '__main__':
|
|
69
|
+
main()
|
|
70
|
+
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from gw_certificate.interface.uart_if import UARTInterface
|
|
5
|
+
from gw_certificate.common.debug import debug_print
|
|
6
|
+
from gw_certificate.interface.if_defines import *
|
|
7
|
+
|
|
8
|
+
class BLESimulator():
|
|
9
|
+
def __init__(self, uart:UARTInterface):
|
|
10
|
+
self.uart = uart
|
|
11
|
+
self.sim_mode = False
|
|
12
|
+
|
|
13
|
+
def set_sim_mode(self, sim_mode):
|
|
14
|
+
self.uart.flush()
|
|
15
|
+
mode_dict = {True: 1, False: 0}
|
|
16
|
+
self.sim_mode = sim_mode
|
|
17
|
+
self.uart.reset_gw()
|
|
18
|
+
self.uart.write_ble_command(f"!ble_sim_init {mode_dict[sim_mode]}")
|
|
19
|
+
if not sim_mode:
|
|
20
|
+
self.uart.reset_gw()
|
|
21
|
+
time.sleep(3)
|
|
22
|
+
|
|
23
|
+
def send_packet(self, raw_packet, duplicates=DEFAULT_DUPLICATES, output_power=DEFAULT_OUTPUT_POWER, channel=SEND_ALL_ADV_CHANNELS, delay=DEFAULT_DELAY):
|
|
24
|
+
assert self.sim_mode is True, 'BLE Sim not initialized!'
|
|
25
|
+
if len(raw_packet) == 62:
|
|
26
|
+
# Add ADVA
|
|
27
|
+
raw_packet = DEFAULT_ADVA + raw_packet
|
|
28
|
+
if len(raw_packet) != 74:
|
|
29
|
+
raise ValueError('Raw Packet must be 62/74 chars long!')
|
|
30
|
+
self.uart.write_ble_command(f"!ble_sim {str(raw_packet)} {str(duplicates)} {str(output_power)} {str(channel)} {str(delay)}")
|
|
31
|
+
if delay > 0:
|
|
32
|
+
diff = time.perf_counter()
|
|
33
|
+
time.sleep((delay/1000) * duplicates)
|
|
34
|
+
diff = time.perf_counter() - diff
|
|
35
|
+
debug_print(f'Desired Delay: {(delay/1000) * duplicates} Actual Delay {diff}')
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def send_data_si_pair(self, data_packet, si_packet, duplicates, output_power=DEFAULT_OUTPUT_POWER, delay=DEFAULT_DELAY, packet_error=None):
|
|
39
|
+
if packet_error is None:
|
|
40
|
+
packet_error = [True for i in range (duplicates * 2)]
|
|
41
|
+
# debug_print(packet_error)
|
|
42
|
+
# print(f'delay {delay}')
|
|
43
|
+
packet_to_send = data_packet
|
|
44
|
+
def switch_packet(packet_to_send):
|
|
45
|
+
if packet_to_send == data_packet:
|
|
46
|
+
return si_packet
|
|
47
|
+
else:
|
|
48
|
+
return data_packet
|
|
49
|
+
for dup in range(duplicates * 2):
|
|
50
|
+
diff = time.perf_counter()
|
|
51
|
+
if packet_error[dup]:
|
|
52
|
+
debug_print(f'Sending Packet {dup}')
|
|
53
|
+
self.send_packet(packet_to_send, duplicates=1, output_power=output_power, channel=SEND_ALL_ADV_CHANNELS, delay=0)
|
|
54
|
+
else:
|
|
55
|
+
debug_print(f'Dropping Packet {dup}')
|
|
56
|
+
time.sleep(delay/1000)
|
|
57
|
+
diff = time.perf_counter() - diff
|
|
58
|
+
debug_print(f'Desired Delay: {delay/1000} Actual Delay {diff}')
|
|
59
|
+
packet_to_send = switch_packet(packet_to_send)
|
|
60
|
+
|
|
61
|
+
def trigger_by_time_stamp(self, ts):
|
|
62
|
+
if ts == None:
|
|
63
|
+
return
|
|
64
|
+
current_time = datetime.datetime.timestamp(datetime.datetime.now()) * 1000
|
|
65
|
+
time_difference = ts-current_time
|
|
66
|
+
print(f"The test will start in: {time_difference/1000} secondes")
|
|
67
|
+
time.sleep(time_difference/1000)
|
|
68
|
+
|
|
69
|
+
def send_brg_network_pkts(self, pkts, duplicates, output_power=DEFAULT_OUTPUT_POWER):
|
|
70
|
+
num_brgs = len(pkts)
|
|
71
|
+
total_pkts_to_send = num_brgs * 2 * duplicates
|
|
72
|
+
for pkt_idx in range(total_pkts_to_send):
|
|
73
|
+
brg_idx = pkt_idx % 3
|
|
74
|
+
pkt = pkts[brg_idx]
|
|
75
|
+
pkt_idx_per_brg = pkt_idx // num_brgs
|
|
76
|
+
if not bool(pkt_idx % 2):
|
|
77
|
+
packet_to_send = pkt['data_packet']
|
|
78
|
+
else:
|
|
79
|
+
packet_to_send = pkt['si_packet']
|
|
80
|
+
packet_error = pkt['packet_error']
|
|
81
|
+
if packet_error[pkt_idx_per_brg]:
|
|
82
|
+
debug_print(f'BRG {pkt["bridge_id"]}: Sending Packet {pkt_idx_per_brg}')
|
|
83
|
+
self.send_packet(packet_to_send, duplicates=1, output_power=output_power,
|
|
84
|
+
channel=SEND_ALL_ADV_CHANNELS, delay=0)
|
|
85
|
+
else:
|
|
86
|
+
debug_print(f'BRG {pkt["bridge_id"]}: Dropping Packet {pkt_idx_per_brg}')
|
|
87
|
+
diff = time.perf_counter()
|
|
88
|
+
delay = pkt['time_delay']
|
|
89
|
+
time.sleep(delay/1000)
|
|
90
|
+
diff = time.perf_counter() - diff
|
|
91
|
+
debug_print(f'Desired Delay: {delay/1000} Actual Delay {diff}')
|