wiliot-certificate 1.3.0a1__py3-none-any.whl → 1.4.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 (184) hide show
  1. brg_certificate/__init__.py +0 -0
  2. brg_certificate/ag/energous_v0_defines.py +925 -0
  3. brg_certificate/ag/energous_v1_defines.py +931 -0
  4. brg_certificate/ag/energous_v2_defines.py +925 -0
  5. brg_certificate/ag/energous_v3_defines.py +925 -0
  6. brg_certificate/ag/energous_v4_defines.py +925 -0
  7. brg_certificate/ag/fanstel_lan_v0_defines.py +925 -0
  8. brg_certificate/ag/fanstel_lte_v0_defines.py +925 -0
  9. brg_certificate/ag/fanstel_wifi_v0_defines.py +925 -0
  10. brg_certificate/ag/minew_lte_v0_defines.py +925 -0
  11. brg_certificate/ag/wlt_cmd_if.html +102 -0
  12. brg_certificate/ag/wlt_types.html +6114 -0
  13. brg_certificate/ag/wlt_types_ag.py +7840 -0
  14. brg_certificate/ag/wlt_types_ag_jsons/brg2brg_ota.json +142 -0
  15. brg_certificate/ag/wlt_types_ag_jsons/brg2gw_hb.json +785 -0
  16. brg_certificate/ag/wlt_types_ag_jsons/brg2gw_hb_sleep.json +139 -0
  17. brg_certificate/ag/wlt_types_ag_jsons/calibration.json +394 -0
  18. brg_certificate/ag/wlt_types_ag_jsons/custom.json +515 -0
  19. brg_certificate/ag/wlt_types_ag_jsons/datapath.json +672 -0
  20. brg_certificate/ag/wlt_types_ag_jsons/energy2400.json +550 -0
  21. brg_certificate/ag/wlt_types_ag_jsons/energySub1g.json +595 -0
  22. brg_certificate/ag/wlt_types_ag_jsons/externalSensor.json +598 -0
  23. brg_certificate/ag/wlt_types_ag_jsons/interface.json +938 -0
  24. brg_certificate/ag/wlt_types_ag_jsons/powerManagement.json +1234 -0
  25. brg_certificate/ag/wlt_types_ag_jsons/side_info_sensor.json +105 -0
  26. brg_certificate/ag/wlt_types_ag_jsons/signal_indicator_data.json +77 -0
  27. brg_certificate/ag/wlt_types_ag_jsons/unified_echo_ext_pkt.json +61 -0
  28. brg_certificate/ag/wlt_types_ag_jsons/unified_echo_pkt.json +110 -0
  29. brg_certificate/brg_certificate.py +191 -0
  30. brg_certificate/brg_certificate_cli.py +47 -0
  31. brg_certificate/cert_common.py +828 -0
  32. brg_certificate/cert_config.py +395 -0
  33. brg_certificate/cert_data_sim.py +188 -0
  34. brg_certificate/cert_defines.py +337 -0
  35. brg_certificate/cert_gw_sim.py +285 -0
  36. brg_certificate/cert_mqtt.py +373 -0
  37. brg_certificate/cert_prints.py +181 -0
  38. brg_certificate/cert_protobuf.py +88 -0
  39. brg_certificate/cert_results.py +300 -0
  40. brg_certificate/cert_utils.py +358 -0
  41. brg_certificate/certificate_sanity_test_list.txt +36 -0
  42. brg_certificate/certificate_test_list.txt +43 -0
  43. brg_certificate/config/eclipse.json +10 -0
  44. brg_certificate/config/hivemq.json +10 -0
  45. brg_certificate/config/mosquitto.json +10 -0
  46. brg_certificate/config/mosquitto.md +95 -0
  47. brg_certificate/config/wiliot-dev.json +10 -0
  48. brg_certificate/restore_brg.py +59 -0
  49. brg_certificate/tests/calibration/interval_test/interval_test.json +13 -0
  50. brg_certificate/tests/calibration/interval_test/interval_test.py +28 -0
  51. brg_certificate/tests/calibration/output_power_test/output_power_test.json +13 -0
  52. brg_certificate/tests/calibration/output_power_test/output_power_test.py +28 -0
  53. brg_certificate/tests/calibration/pattern_test/pattern_test.json +13 -0
  54. brg_certificate/tests/calibration/pattern_test/pattern_test.py +70 -0
  55. brg_certificate/tests/datapath/adaptive_pacer_algo_test/adaptive_pacer_algo_test.json +13 -0
  56. brg_certificate/tests/datapath/adaptive_pacer_algo_test/adaptive_pacer_algo_test.py +76 -0
  57. brg_certificate/tests/datapath/num_of_tags_test/num_of_tags_test.json +13 -0
  58. brg_certificate/tests/datapath/num_of_tags_test/num_of_tags_test.py +83 -0
  59. brg_certificate/tests/datapath/output_power_test/output_power_test.json +13 -0
  60. brg_certificate/tests/datapath/output_power_test/output_power_test.py +27 -0
  61. brg_certificate/tests/datapath/pacer_interval_ble5_test/pacer_interval_ble5_test.json +13 -0
  62. brg_certificate/tests/datapath/pacer_interval_ble5_test/pacer_interval_ble5_test.py +43 -0
  63. brg_certificate/tests/datapath/pacer_interval_tags_count_test/pacer_interval_tags_count_test.json +13 -0
  64. brg_certificate/tests/datapath/pacer_interval_tags_count_test/pacer_interval_tags_count_test.py +63 -0
  65. brg_certificate/tests/datapath/pacer_interval_test/pacer_interval_test.json +13 -0
  66. brg_certificate/tests/datapath/pacer_interval_test/pacer_interval_test.py +50 -0
  67. brg_certificate/tests/datapath/pattern_test/pattern_test.json +13 -0
  68. brg_certificate/tests/datapath/pattern_test/pattern_test.py +28 -0
  69. brg_certificate/tests/datapath/pkt_filter_ble5_test/pkt_filter_ble5_test.json +13 -0
  70. brg_certificate/tests/datapath/pkt_filter_ble5_test/pkt_filter_ble5_test.py +51 -0
  71. brg_certificate/tests/datapath/pkt_filter_gen3_test/pkt_filter_gen3_test.json +13 -0
  72. brg_certificate/tests/datapath/pkt_filter_gen3_test/pkt_filter_gen3_test.py +54 -0
  73. brg_certificate/tests/datapath/pkt_filter_test/pkt_filter_test.json +13 -0
  74. brg_certificate/tests/datapath/pkt_filter_test/pkt_filter_test.py +55 -0
  75. brg_certificate/tests/datapath/rssi_threshold_test/rssi_threshold_test.json +13 -0
  76. brg_certificate/tests/datapath/rssi_threshold_test/rssi_threshold_test.py +73 -0
  77. brg_certificate/tests/datapath/rx_channel_test/rx_channel_test.json +13 -0
  78. brg_certificate/tests/datapath/rx_channel_test/rx_channel_test.py +41 -0
  79. brg_certificate/tests/datapath/rx_rate_gen2_test/rx_rate_gen2_test.json +21 -0
  80. brg_certificate/tests/datapath/rx_rate_gen2_test/rx_rate_gen2_test.py +184 -0
  81. brg_certificate/tests/datapath/rx_rate_gen3_test/rx_rate_gen3_test.json +21 -0
  82. brg_certificate/tests/datapath/rx_rate_gen3_test/rx_rate_gen3_test.py +210 -0
  83. brg_certificate/tests/datapath/stress_gen3_test/stress_gen3_test.json +30 -0
  84. brg_certificate/tests/datapath/stress_gen3_test/stress_gen3_test.py +203 -0
  85. brg_certificate/tests/datapath/stress_test/stress_test.json +30 -0
  86. brg_certificate/tests/datapath/stress_test/stress_test.py +210 -0
  87. brg_certificate/tests/datapath/tx_repetition_algo_test/tx_repetition_algo_test.json +13 -0
  88. brg_certificate/tests/datapath/tx_repetition_algo_test/tx_repetition_algo_test.py +113 -0
  89. brg_certificate/tests/datapath/tx_repetition_test/tx_repetition_test.json +13 -0
  90. brg_certificate/tests/datapath/tx_repetition_test/tx_repetition_test.py +79 -0
  91. brg_certificate/tests/edge_mgmt/actions_test/actions_test.json +13 -0
  92. brg_certificate/tests/edge_mgmt/actions_test/actions_test.py +432 -0
  93. brg_certificate/tests/edge_mgmt/brg2brg_ota_ble5_test/brg2brg_ota_ble5_test.json +13 -0
  94. brg_certificate/tests/edge_mgmt/brg2brg_ota_ble5_test/brg2brg_ota_ble5_test.py +94 -0
  95. brg_certificate/tests/edge_mgmt/brg2brg_ota_test/brg2brg_ota_test.json +13 -0
  96. brg_certificate/tests/edge_mgmt/brg2brg_ota_test/brg2brg_ota_test.py +87 -0
  97. brg_certificate/tests/edge_mgmt/leds_test/leds_test.json +13 -0
  98. brg_certificate/tests/edge_mgmt/leds_test/leds_test.py +210 -0
  99. brg_certificate/tests/edge_mgmt/ota_test/ota_test.json +13 -0
  100. brg_certificate/tests/edge_mgmt/ota_test/ota_test.py +83 -0
  101. brg_certificate/tests/edge_mgmt/stat_test/stat_test.json +13 -0
  102. brg_certificate/tests/edge_mgmt/stat_test/stat_test.py +48 -0
  103. brg_certificate/tests/energy2400/duty_cycle_test/duty_cycle_test.json +13 -0
  104. brg_certificate/tests/energy2400/duty_cycle_test/duty_cycle_test.py +26 -0
  105. brg_certificate/tests/energy2400/output_power_test/output_power_test.json +13 -0
  106. brg_certificate/tests/energy2400/output_power_test/output_power_test.py +27 -0
  107. brg_certificate/tests/energy2400/pattern_test/pattern_test.json +13 -0
  108. brg_certificate/tests/energy2400/pattern_test/pattern_test.py +28 -0
  109. brg_certificate/tests/energy2400/signal_indicator_ble5_test/signal_indicator_ble5_test.json +13 -0
  110. brg_certificate/tests/energy2400/signal_indicator_ble5_test/signal_indicator_ble5_test.py +398 -0
  111. brg_certificate/tests/energy2400/signal_indicator_sub1g_2_4_test/signal_indicator_sub1g_2_4_test.json +13 -0
  112. brg_certificate/tests/energy2400/signal_indicator_sub1g_2_4_test/signal_indicator_sub1g_2_4_test.py +153 -0
  113. brg_certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.json +13 -0
  114. brg_certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.py +264 -0
  115. brg_certificate/tests/energy_sub1g/duty_cycle_test/duty_cycle_test.json +13 -0
  116. brg_certificate/tests/energy_sub1g/duty_cycle_test/duty_cycle_test.py +27 -0
  117. brg_certificate/tests/energy_sub1g/pattern_test/pattern_test.json +13 -0
  118. brg_certificate/tests/energy_sub1g/pattern_test/pattern_test.py +26 -0
  119. brg_certificate/tests/energy_sub1g/signal_indicator_functionality_test/signal_indicator_functionality_test.json +13 -0
  120. brg_certificate/tests/energy_sub1g/signal_indicator_functionality_test/signal_indicator_functionality_test.py +397 -0
  121. brg_certificate/tests/energy_sub1g/signal_indicator_test/signal_indicator_test.json +13 -0
  122. brg_certificate/tests/energy_sub1g/signal_indicator_test/signal_indicator_test.py +27 -0
  123. brg_certificate/wltPb_pb2.py +72 -0
  124. brg_certificate/wltPb_pb2.pyi +227 -0
  125. brg_certificate/wlt_types.py +114 -0
  126. gw_certificate/api/extended_api.py +7 -1531
  127. gw_certificate/api_if/200/data.json +106 -0
  128. gw_certificate/api_if/200/logs.json +12 -0
  129. gw_certificate/api_if/200/status.json +47 -0
  130. gw_certificate/api_if/201/data.json +98 -0
  131. gw_certificate/api_if/201/logs.json +12 -0
  132. gw_certificate/api_if/201/status.json +53 -0
  133. gw_certificate/api_if/202/data.json +83 -0
  134. gw_certificate/api_if/202/logs.json +12 -0
  135. gw_certificate/api_if/202/status.json +60 -0
  136. gw_certificate/api_if/203/data.json +85 -0
  137. gw_certificate/api_if/203/logs.json +12 -0
  138. gw_certificate/api_if/203/status.json +63 -0
  139. gw_certificate/api_if/204/data.json +85 -0
  140. gw_certificate/api_if/204/logs.json +12 -0
  141. gw_certificate/api_if/204/status.json +63 -0
  142. gw_certificate/api_if/205/data.json +85 -0
  143. gw_certificate/api_if/205/logs.json +12 -0
  144. gw_certificate/api_if/205/status.json +63 -0
  145. gw_certificate/api_if/api_validation.py +0 -2
  146. gw_certificate/common/analysis_data_bricks.py +18 -1413
  147. gw_certificate/common/debug.py +0 -21
  148. gw_certificate/common/utils.py +1 -212
  149. gw_certificate/common/utils_defines.py +0 -87
  150. gw_certificate/gw_certificate.py +9 -7
  151. gw_certificate/gw_certificate_cli.py +39 -23
  152. gw_certificate/interface/4.4.52_app.zip +0 -0
  153. gw_certificate/interface/4.4.52_sd_bl_app.zip +0 -0
  154. gw_certificate/interface/ble_simulator.py +0 -32
  155. gw_certificate/interface/if_defines.py +1 -0
  156. gw_certificate/interface/mqtt.py +96 -19
  157. gw_certificate/interface/nrfutil-linux +0 -0
  158. gw_certificate/interface/nrfutil-mac +0 -0
  159. gw_certificate/interface/nrfutil.exe +0 -0
  160. gw_certificate/interface/pkt_generator.py +0 -82
  161. gw_certificate/interface/uart_if.py +73 -43
  162. gw_certificate/templates/results.html +1 -1
  163. gw_certificate/tests/__init__.py +1 -2
  164. gw_certificate/tests/actions.py +134 -9
  165. gw_certificate/tests/connection.py +10 -5
  166. gw_certificate/tests/downlink.py +2 -4
  167. gw_certificate/tests/generic.py +62 -12
  168. gw_certificate/tests/registration.py +78 -27
  169. gw_certificate/tests/static/generated_packet_table.py +12 -48
  170. gw_certificate/tests/static/packet_table.csv +10048 -10048
  171. gw_certificate/tests/static/references.py +2 -1
  172. gw_certificate/tests/static/uplink_defines.py +0 -7
  173. gw_certificate/tests/throughput.py +7 -12
  174. gw_certificate/tests/uplink.py +83 -43
  175. {wiliot_certificate-1.3.0a1.dist-info → wiliot_certificate-1.4.0a1.dist-info}/METADATA +59 -8
  176. wiliot_certificate-1.4.0a1.dist-info/RECORD +198 -0
  177. {wiliot_certificate-1.3.0a1.dist-info → wiliot_certificate-1.4.0a1.dist-info}/WHEEL +1 -1
  178. wiliot_certificate-1.4.0a1.dist-info/entry_points.txt +3 -0
  179. wiliot_certificate-1.4.0a1.dist-info/top_level.txt +2 -0
  180. gw_certificate/interface/packet_error.py +0 -22
  181. wiliot_certificate-1.3.0a1.dist-info/RECORD +0 -51
  182. wiliot_certificate-1.3.0a1.dist-info/entry_points.txt +0 -2
  183. wiliot_certificate-1.3.0a1.dist-info/top_level.txt +0 -1
  184. {wiliot_certificate-1.3.0a1.dist-info → wiliot_certificate-1.4.0a1.dist-info}/LICENSE +0 -0
@@ -1,97 +1,5 @@
1
- # External Imports
2
- import pandas as pd
3
- import math
4
- import pytz
5
- from requests import JSONDecodeError
6
- from enum import Enum
7
- from time import sleep
8
- import datetime
9
- import random
10
- import urllib.parse
11
- from packaging import version
12
- from typing import Literal
13
1
 
14
- from wiliot_api.api_client import WiliotCloudError
15
- from wiliot_api.platform.platform import PlatformClient
16
- from wiliot_api.edge.edge import EdgeClient, BridgeAction
17
-
18
- # Internal Imports
19
- from gw_certificate.common.debug import debug_print
20
- from gw_certificate.common.utils_defines import BO_DICT, BROADCAST_DST_MAC, GW_DATA_MODE, GW_DATA_SRC, SEP, gw_rx_channel
21
- from gw_certificate.interface.mqtt import get_broker_url
22
-
23
- EXCEPTIONS_TO_CATCH = (AttributeError, WiliotCloudError, JSONDecodeError)
24
-
25
-
26
- # Enum Classes
27
- class GatewayType(Enum):
28
- UNKNOWN = 'unknown'
29
- WIFI = 'wifi'
30
- LTE = 'lte'
31
- MOBILE = 'mobile'
32
- IOS = 'ios'
33
- ANDROID = 'android'
34
- ERM = 'erm'
35
- RIGADO = 'rigado'
36
- OTHER = 'other'
37
- FANSTEL_LAN_V0 = 'fansel-lan-v0'
38
- MINEW_POE_V0 = 'minew-poe-v0'
39
-
40
-
41
- class GatewayAction(Enum):
42
- ADD_SSID = 'addSsid'
43
- TOGGLE_SSID = 'toggleDefaultSsid'
44
- REBOOT_GW = 'rebootGw'
45
- START_BRIDGE_OTA = '!send_msg_to_brg'
46
- ENTER_DEV_MODE = 'DevModeEnable'
47
- UART_UP_STREAM = '!us'
48
-
49
- class UartPacketOrigin(Enum):
50
- TRANSPARENT = 'p0'
51
- TAG = 'p1'
52
- COUPLED = 'p2'
53
- SENSOR = 'p3'
54
-
55
- class AndroidGatewayAction(Enum):
56
- DISABLE_UPLINK = -2
57
- ENABLE_UPLINK = -3
58
- DISABLE_BLE_LOGS = -4
59
- ENABLE_BLE_LOGS = -5
60
-
61
- class BridgeThroughGatewayAction(Enum):
62
- REBOOT = '01'
63
- BLINK = '02'
64
- POWER_MGMT = '03'
65
- RESTORE_DEFAULTS = '04'
66
- SEND_HB = '05'
67
- PRODUCTION_MODE = '06'
68
- SPARSE_37 = '07'
69
- GW_HB = '08'
70
-
71
-
72
- class BoardTypes(Enum):
73
- FANSTEL_SINGLE = 'FanstelSingleBandV0'
74
- FANSTEL_DUAL = 'FanstelDualBandV0'
75
- MINEW_SINGLE = 'MinewSingleBandV0'
76
- MINEW_DUAL = 'MinewDualBandV0'
77
- ENERGOUS = 'EnergousV0'
78
-
79
- class BoardTypesActive(Enum):
80
- ENERGOUS = 'Energous Dual-Band (v0)'
81
- FANSTEL_SINGLE = 'Fanstel Single-Band (v0)'
82
- FANSTEL_DUAL = 'Fanstel Dual-Band (v0)'
83
- MINEW_SINGLE = 'Minew Single-Band (v0)'
84
- MINEW_DUAL = 'Minew Dual-Band (v0)'
85
- MOKO = 'Moko Dual-Band (v0)'
86
-
87
-
88
- # API Clients
89
-
90
- class ParamMissingError(Exception):
91
- pass
92
-
93
- class ExtendedEdgeClientError(Exception):
94
- pass
2
+ from wiliot_api.edge.edge import EdgeClient
95
3
 
96
4
 
97
5
  class ExtendedEdgeClient(EdgeClient):
@@ -99,1449 +7,17 @@ class ExtendedEdgeClient(EdgeClient):
99
7
  # Support for GCP
100
8
  region='us-central1' if cloud=='gcp' else region
101
9
  super().__init__(api_key=api_key, owner_id=owner_id, env=env, region=region, cloud=cloud, log_file=log_file, logger_=logger_)
102
-
103
- # Get Info
104
- def get_connected_brgs(self, gw, ignore_bridges=None):
105
- """
106
- returned all bridges connected to gateway and claimed by owner
107
- :type gw: string
108
- :param gw: desired gw to get its connected bridges
109
- :type ignore_bridges: list
110
- :param ignore_bridges: list of bridges ID to ignore
111
- output type: array of dictionaries
112
- output param: array with a dictionary to every connected bridge.
113
- dictionary includes its current configurations
114
- example:
115
- print(wiliot.get_connected_brgs("GW0CDC7EDB1674")):
116
- TODO: add output
117
- """
118
- ignore_bridges = ignore_bridges if ignore_bridges is not None else []
119
- brg_list = self.get_bridges_connected_to_gateway(gw)
120
-
121
- for brg in brg_list:
122
- if brg["id"] in ignore_bridges:
123
- brg_list.remove(brg)
124
- brg_list_connected = [b for b in brg_list if
125
- any([c["connected"] and c["gatewayId"] == gw for c in b["connections"]])]
126
- brg_list_connected = [b for b in brg_list_connected if b['owned']]
127
- return brg_list_connected
128
-
129
- def get_bridge_status(self, bridge_id):
130
- """
131
- Get bridge status: online or offline
132
- :param : A string- the target bridge
133
- :return: A string: online or offline
134
- """
135
- online_brgs = super().get_bridges(online = True)
136
- for brg in online_brgs:
137
- if bridge_id in brg["id"]:
138
- return 'online'
139
- return 'offline'
140
-
141
- def get_bridges(self, online=None, gateway_id=None):
142
- """
143
- Get all bridges "seen" by gateways owned by the owner
144
- :param online: A boolean - optional. Allows to filter only online (True) or offline (False) bridges
145
- :param gateway_id: A string / list - optional. Allows to filter only bridges currently connected to the gateway / gateways
146
- :return: A list of bridges
147
- """
148
- # check type
149
- if gateway_id is not None and type(gateway_id) not in [str, list]:
150
- raise TypeError('Gateway ID must be either str/list!')
151
- gws_list = None
152
- if type(gateway_id) == list:
153
- gws_list = gateway_id
154
- if type(gateway_id) == str:
155
- gws_list = [gateway_id]
156
-
157
- bridges = super().get_bridges(online)
158
- if gws_list is not None:
159
- bridges = [b for b in bridges if any([c["connected"] and c["gatewayId"] in gws_list for c
160
- in b["connections"]])]
161
- return bridges
162
-
163
- def get_gateways_from_bridges(self, brg_ids=None):
164
- """
165
- gets connected gateways from bridge IDs
166
- :type brg_ids: list
167
- :param brg_ids: bridge IDs
168
- :rtype: dict
169
- :return: dict of {gatewayId: [list of bridgeIds]} of all connected bridges / specified brg_ids
170
- """
171
- brgs = self.get_bridges()
172
- owners_gws = [g['gatewayId'] for g in self.get_gateways()]
173
- gws_list = {}
174
- for brg_dict in brgs:
175
- brg_id = brg_dict['id']
176
- # Filter only bridges in brg_ids
177
- if brg_ids is not None:
178
- if brg_dict['id'] not in brg_ids:
179
- continue
180
- for connection in brg_dict['connections']:
181
- if connection['connected']:
182
- gw = connection['gatewayId']
183
- if gw not in owners_gws:
184
- continue
185
- if gw not in gws_list.keys():
186
- gws_list[gw] = [brg_id]
187
- else:
188
- gws_list[gw].append(brg_id)
189
- return gws_list
190
-
191
-
192
- def get_bridge_relevant_gw(self, bridge_id, get_gw_list = False):
193
- """
194
- Returns relevant GW for sending actions to bridge
195
- :param bridge_id: Bridge ID
196
- :rtype: str
197
- :return: relevant Gateway ID
198
- """
199
-
200
- # TODO - return list of potential GWs
201
- potential_gws = list()
202
- owner_gws = self.get_gateways()
203
- owner_gw_ids = []
204
- owner_gw_ids.extend(g['gatewayId'] for g in owner_gws)
205
- for gw in self.get_bridge(bridge_id)['connections']:
206
- if gw['gatewayId'] not in owner_gw_ids:
207
- continue
208
- gw_id = gw['gatewayId']
209
- gw_dict = self.get_gateway(gw_id)
210
- if gw['connected'] and \
211
- bridge_id in self.get_seen_bridges(gw_id) and \
212
- self.check_gw_compatible_for_action(gw_id):
213
- if bridge_id in self.get_seen_bridges(gw['gatewayId']):
214
- potential_gws.append(gw['gatewayId'])
215
- if len(potential_gws) == 0:
216
- raise ExtendedEdgeClientError(f'No relevant GW connected to bridge! Check deployment')
217
- if get_gw_list:
218
- return potential_gws
219
- return potential_gws[0]
220
-
221
- def get_seen_bridges(self, gw, ignore_bridges=None):
222
- """
223
- return all bridges 'seen' by GW
224
- :type gw: str
225
- :param gw: gateway ID
226
- :type ignore_bridges: list
227
- :param ignore_bridges: list of bridge IDs to ignore
228
- :rtype: list
229
- :return: list of Bridge IDs
230
- """
231
- bridges = [b['bridgeId'] for b in self.get_gateway(gw)['connections']]
232
- if ignore_bridges is not None:
233
- bridges = list(set(bridges) - set(ignore_bridges))
234
- return bridges
235
-
236
- def get_gateway_type(self, gateway_id):
237
- """
238
- Returns GatewayType of gateway ID
239
- :param gateway_id: String - gateway ID
240
- :rtype: GatewayType
241
- """
242
- type = self.get_gateway(gateway_id)['gatewayType'].upper()
243
- type = type.replace('-','_')
244
- return GatewayType.__getitem__(type)
245
-
246
- def get_gateways_types(self, gw_ids):
247
- """
248
- returns dict of gatewaytype:[gatewayIds]
249
- :param gw_ids: gateways IDs
250
- :type gw_ids: list
251
- """
252
- res = {}
253
- for gw in gw_ids:
254
- try:
255
- gw_type = self.get_gateway_type(gw)
256
- except WiliotCloudError:
257
- gw_type = GatewayType.UNKNOWN
258
- if gw_type not in res.keys():
259
- res[gw_type] = []
260
- res[gw_type].append(gw)
261
- return res
262
-
263
- def get_owner_gateways_types(self):
264
- gateways = self.get_gateways()
265
- gateways_types = {}
266
- for gw in gateways:
267
- gw_id = gw['gatewayId']
268
- gw_type = GatewayType.UNKNOWN
269
- try:
270
- gw_type = GatewayType.__getitem__(gw['gatewayType'].upper())
271
- if gw_type == GatewayType.MOBILE:
272
- if '-' in gw_id:
273
- gw_type = GatewayType.IOS
274
- else:
275
- gw_type = GatewayType.ANDROID
276
- except KeyError as e:
277
- debug_print(f'KeyError {e} when checking GW {gw_id} type')
278
- gateways_types[gw_id] = gw_type
279
- return gateways_types
280
-
281
- def check_gw_online(self, gw_ids):
282
- gws_online = True
283
- for gw_id in gw_ids:
284
- gw = self.get_gateway(gw_id)
285
- if not gw['online']:
286
- debug_print("Gateway {} is offline".format(gw_id))
287
- gws_online = False
288
- else:
289
- debug_print(f'Gateway {gw_id} is online')
290
- return gws_online
291
-
292
- def test_if_bridge_connected(self, brg):
293
- """
294
- :type brg: dictionary
295
- :param brg: bridge data structure
296
- :return: True if bridge is connected, False otherwise
297
- """
298
- for connection in brg['connections']:
299
- if connection['connected']:
300
- return True
301
- return False
302
-
303
- def get_bridge_board(self, brg_id):
304
- """
305
- :type brg_id: string
306
- :param brg_id: bridge id
307
- """
308
- board_type = self.get_bridge(brg_id)['boardType']
309
- if board_type in [member.value for member in BoardTypes.__members__.values()]:
310
- return board_type
311
- else:
312
- try:
313
- return (BoardTypes[BoardTypesActive(board_type).name]).value
314
- except:
315
- return None
316
-
317
- # Gateway & Bridge Actions
318
- def send_action_to_gateway(self, gateway_id, action, **kwargs):
319
- """
320
- Send an action to a gateway
321
- :param gateway_id: String - the ID of the gateway to send the action to
322
- :param action: GatewayAction - Required
323
- :return: True if the cloud successfully sent the action to the gateway, False otherwise
324
- """
325
- gw_type = self.get_gateway_type(gateway_id)
326
- assert gw_type in [GatewayType.WIFI, GatewayType.MOBILE], 'gateway does not support action API!'
327
- if gw_type == GatewayType.WIFI:
328
- assert action in GatewayAction, 'action not valid for WIFI GW!'
329
- if gw_type == GatewayType.MOBILE:
330
- assert action in AndroidGatewayAction, 'action not valid for Mobile GW!'
331
- if action == GatewayAction.ADD_SSID:
332
- assert 'ssid' in kwargs.keys(), "Missing a 'ssid' parameter"
333
- if action == GatewayAction.START_BRIDGE_OTA:
334
- assert 'bridge_id' in kwargs.keys(), "Missing a 'bridge_id' parameter"
335
- path = "gateway/{}/action".format(gateway_id)
336
- action_payload = action.value
337
- if action == GatewayAction.START_BRIDGE_OTA:
338
- action_payload = action_payload + f' {kwargs["bridge_id"]} 1'
339
- if action == GatewayAction.UART_UP_STREAM:
340
- action_payload = action_payload + ' ' + ' '.join(str(value) for value in kwargs.values())
341
- else:
342
- for key, value in kwargs.items():
343
- action_payload = action_payload + f' {key}:{value}'
344
- payload = {
345
- "action": action_payload
346
- }
347
- try:
348
- res = self._post(path, payload)
349
- return res['data'].lower().find("ok") != -1
350
- except WiliotCloudError as e:
351
- print("Failed to send action to gateway")
352
- raise WiliotCloudError(
353
- "Failed to send action to gateway. Received the following error: {}".format(e.args[0]))
354
-
355
- def enter_custom_mqtt(self, gateway_id, mqtt_mode:Literal['automatic', 'manual' ,'legacy'] = 'automatic',
356
- broker:Literal['hive', 'emqx', 'eclipse'] = 'eclipse'):
357
- """
358
- enter GW Dev Mode
359
- :type gateway_id: string
360
- :param gateway_id: gateway id
361
- :type mqtt_mode: string
362
- :param mqtt_mode: 'automatic', 'manual' or 'legacy'
363
- :type broker: string
364
- :param broker: custom broker
365
- :return: True if sent successfully to GW
366
- :rtype: bool
367
- """
368
- if mqtt_mode == 'legacy':
369
- return self.send_action_to_gateway(gateway_id, GatewayAction.ENTER_DEV_MODE)
370
- elif mqtt_mode == 'automatic':
371
- broker_url = get_broker_url(broker)
372
- dev_mode = {
373
- "customBroker": True,
374
- "brokerUrl": f'mqtts://{broker_url}',
375
- "port": 8883,
376
- "username": "",
377
- "password": "",
378
- "updateTopic": f"update/{self.owner_id}/{gateway_id}",
379
- "statusTopic": f"status/{self.owner_id}/{gateway_id}",
380
- "dataTopic": f"data/{self.owner_id}/{gateway_id}"
381
- }
382
- return self.send_custom_message_to_gateway(gateway_id, dev_mode)
383
- elif mqtt_mode == 'manual':
384
- debug_print(f"Make sure GW {gateway_id} is set to HiveMQ MQTT broker")
385
- return True
386
-
387
- def send_uart_packet_through_gw(self, gateway_id,packet_type,raw_packet,repetitions = 1000,delay_ms = 10):
388
- """
389
- send uart upstream packet to gate
390
- :param gateway_id:
391
- :param packet_type: must be one of UartPacketOrigin
392
- :raw_packet: raw packet to send, without adva
393
- :param repetitions: repetitions of evety packet
394
- :param delay_ms: time interval between packets
395
- :return: True if sent successfully to GW
396
- :rtype: bool
397
- """
398
- if not self.check_uart_sim_support(gateway_id):
399
- debug_print(f"gateway:{gateway_id} doesn't support uart simulator")
400
- return False
401
- if not any(packet_type == item for item in UartPacketOrigin):
402
- debug_print("Invalid argument for packet_type, munst be a value of UartPacketOrigin")
403
- return False
404
- packet_args_dict = {}
405
- packet_args_dict['repetitions'] = repetitions
406
- packet_args_dict['delay_ms'] = delay_ms
407
- packet_args_dict['packet_type'] = packet_type.value
408
- packet_args_dict['raw_packet'] = raw_packet
409
-
410
- return self.send_action_to_gateway(gateway_id=gateway_id, action=GatewayAction.UART_UP_STREAM,**packet_args_dict)
411
-
412
-
413
-
414
- def send_packet_through_gw(self, gateway_id, raw_packet, is_ota=False, brg_id=None,
415
- repetitions=8, tx_rate=None, tx_max_duration = None, debug=False, return_payload=False):
416
- """
417
- send packet through GW
418
- :param gateway_id: gateway ID
419
- :type gateway_id: str
420
-
421
- :param raw_packet: raw packet
422
- :type raw_packet: str
423
- :type is_ota: bool, optional
424
- :param is_ota: start OTA with bridge, defaults to False
425
- :type brg_id: bridge ID, optional
426
- :param brg_id: str, defaults to None
427
- :type repetitions: int, optional
428
- :param repetitions: number of times to send the packet, defaults to 8
429
- :type tx_rate: int, optional
430
- :param tx_rate: tx rate to send the packet (used to calculate txMaxDurationMs):
431
- ANDROID GW - 140ms
432
- ERM GW - 50ms
433
- :type tx_max_duration: int
434
- :param tx_max_duration: tx max duration to encode in the packet.
435
- if this parameter is given the tx_rate and repetitions will be ignored
436
- :type debug: bool
437
- :param debug: if True, debug_print payload
438
- :type return_payload: bool
439
- :param return_payload: if True, return sent payload as dict
440
- :return: True if successful / False if not, payload(dict) if return_payload is True
441
- :rtype: bool
442
- """
443
-
444
- if len(raw_packet) < 62:
445
- if len(raw_packet) == 54:
446
- raw_packet = 'C6FC' + raw_packet
447
- if len(raw_packet) == 58:
448
- raw_packet = '1E16' + raw_packet
449
- if len(raw_packet) > 62:
450
- raw_packet = raw_packet[-62:]
451
-
452
- assert len(raw_packet) == 62, 'Raw Packet must be 62 chars long!'
453
- # Android tx rate - 140ms
454
- # ERM StarLink tx rate - 50ms
455
- if tx_rate is None:
456
- if self.get_gateway_type(gateway_id) == GatewayType.MOBILE:
457
- tx_rate = 140
458
- elif self.get_gateway_type(gateway_id) == GatewayType.LTE:
459
- tx_rate = 50
460
- else:
461
- tx_rate = 140
462
- payload = {'txPacket': raw_packet,
463
- 'txMaxDurationMs': tx_rate * repetitions,
464
- 'txMaxRetries': repetitions,
465
- 'action': 0}
466
- if tx_max_duration is not None:
467
- payload['txMaxDurationMs'] = tx_max_duration
468
- if is_ota:
469
- payload.update({'action': 1, 'bridgeId': brg_id})
470
- if debug:
471
- debug_print(f'{payload} sent to {gateway_id}')
472
- res = self.send_custom_message_to_gateway(gateway_id, payload)
473
- if return_payload:
474
- return payload
475
- return res
476
-
477
- @staticmethod
478
- def generate_bridge_action_packet(action_type, payload='0', bridge_id=None, broadcast=False):
479
- assert action_type in BridgeThroughGatewayAction, 'Not valid action type'
480
- assert (bridge_id is not None and broadcast is False) or (bridge_id is None and broadcast is True), \
481
- 'Must supply bridgeId / set broadcast to True'
482
- assert len(bridge_id) == 12
483
- if broadcast:
484
- bridge_id = BROADCAST_DST_MAC
485
- payload = str(payload).ljust(28, '0')
486
- # Packet Header: 0-5 ADVA, 6 Length, 7 AD Type, 8-9 UUID, 10-12 Group ID, 13 MSG type, 14 API Ver.
487
- header = '1E16C6FC0000ED0707'
488
- # Randomize Sequence ID
489
- seq_id = random.getrandbits(8).to_bytes(1, 'big').hex().upper()
490
- # Assemble raw packet
491
- return f'{header}{seq_id}{bridge_id}{action_type.value}{payload}'
492
-
493
- def send_bridge_action_through_gw(self, gateway_id, action_type, payload=None, bridge_id=None, broadcast=False, reps=8):
494
- """
495
- Send an action to a bridge through a gateway
496
- :param broadcast: whether to broadcast
497
- :param gateway_id: String - the ID of the gateway to send the action Through
498
- :param bridge_id: String - the ID of the bridge to send the action to
499
- :param action_type: BridgeThroughGatewayAction - Required
500
- :param payload: 28 bytes (hex string)
501
- :param reps: repetitions to send
502
- :return: True if the cloud successfully sent the action to the gateway, False otherwise
503
- """
504
- if payload is None:
505
- payload = '0'
506
- raw_packet = self.generate_bridge_action_packet(action_type, payload, bridge_id, broadcast)
507
- self.send_packet_through_gw(gateway_id, raw_packet, repetitions=reps)
508
-
509
- def reboot_gateway(self, gateway_id):
510
- """
511
- Reboots specified GW
512
- :param gateway_id: Gateway ID
513
- :return: True if the cloud successfully sent the action to the gateway, False otherwise
514
- """
515
- return self.send_action_to_gateway(gateway_id, GatewayAction.REBOOT_GW)
516
-
517
- def reboot_bridge(self, bridge_id):
518
- """
519
- Reboots specified GW
520
- :param bridge_id: Bridge ID
521
- :return: True if the cloud successfully sent the action to the gateway, False otherwise
522
- """
523
- return self.send_action_to_bridge(bridge_id, BridgeAction.REBOOT)
524
-
525
- # Logs & Acks
526
- def fetch_logs(self, gateway_id, hrs_back=24):
527
- """
528
- fetches gateway logs from cloud
529
- :param gateway_id: String - the ID of the gateway
530
- :param hrs_back: Int - How many hours back to query
531
- :return: List of Dicts of gateway log entries
532
- """
533
- assert hrs_back < 144, 'Cannot query for more than 144 hours back!'
534
- end_timestamp = int(datetime.datetime.now().timestamp())
535
- start_timestamp = int((datetime.datetime.now() - datetime.timedelta(hours=hrs_back)).timestamp())
536
- path = f'gateway/{gateway_id}/logs?' + urllib.parse.urlencode(
537
- {"start": start_timestamp, "end": end_timestamp, 'step': 60})
538
- try:
539
- res = self._get(path)
540
- return res['data']
541
- except WiliotCloudError as e:
542
- print("Failed to fetch gateway logs")
543
- raise WiliotCloudError(
544
- "Failed to fetch gateway logs. Received the following error: {}".format(e.args[0]))
545
-
546
- def get_gateway_logs(self, gateway_id, hours_back=24):
547
- """
548
- fetches gateway logs from gateway
549
- :type gateway_id: str
550
- :param gateway_id: Gateway ID
551
- :type hours_back: int
552
- :param hours_back: How many hours back to query
553
- :rtype: Pandas DataFrame
554
- :return: Gateway Logs
555
- """
556
-
557
- logs = pd.DataFrame.from_dict(self.fetch_logs(gateway_id, hours_back))
558
- if len(logs) == 0:
559
- return None
560
- logs['type'] = logs['message'].apply(lambda x: x.split(':')[0])
561
- logs['message'] = logs['message'].apply(lambda x: ''.join(x.split(':')[1:]))
562
- return logs
563
-
564
- def print_gateway_logs(self, gateway_id):
565
- """
566
- prints gateway logs nicely
567
- :type gateway_id: str
568
- :param gateway_id: Gateway ID
569
- """
570
- logs = self.get_gateway_logs(gateway_id)
571
- if logs is None:
572
- print(f'---No logs for GW {gateway_id}---')
573
- return None
574
- logs = logs.reindex(index=logs.index[::-1])
575
- logs = logs.reset_index()
576
- datetime_format = "%Y-%m-%dT%H:%M:%SZ"
577
-
578
- for row in logs.itertuples():
579
- timestamp = datetime.datetime.strptime(row.timestamp, datetime_format).replace(tzinfo=pytz.UTC).timestamp()
580
- local_datetime = datetime.datetime.fromtimestamp(timestamp)
581
- print(f'---Log No. {row.Index} | {row.level} | {local_datetime}---')
582
- print(f'*{row.message}')
583
- print(f'')
584
-
585
- def get_gateway_info(self, gateway_id):
586
- """
587
- get gateway info from gateway
588
- :param gateway_id: Gateway ID
589
- :type gateway_id: str
590
- :return: gateway info
591
- :rtype: dict
592
- """
593
- res = self.get_gateway(gateway_id)
594
- return res['gatewayInfo']
595
-
596
- def get_acks(self, gateway_id, hours_back=3):
597
- """
598
- function gets latest acknowledge packets for each bridge ID (after sending GW action)
599
- :type gateway_id: str
600
- :param gateway_id: gateway ID
601
- :type hours_back: int
602
- :param hours_back: hours back to query
603
- :return: last acknowledge packet
604
- """
605
-
606
- try:
607
- logs = self.get_gateway_logs(gateway_id, hours_back=hours_back)
608
- except Exception as e:
609
- debug_print(f'Exception {e} Caught when getting Acks from GW {gateway_id}')
610
- logs = None
611
- if logs is None:
612
- return None
613
- gw_type = self.get_gateway_type(gateway_id)
614
- if gw_type == GatewayType.MOBILE:
615
- logs = logs[list(map(lambda x: x.startswith(' ReceivedAction'), logs['message']))]
616
- logs['rawPacket'] = logs['message'].apply(lambda x: x.split('=')[1][16:-1]).str.upper()
617
- else:
618
- logs = logs[list(map(lambda x: x.startswith(' RecievedAction') or x.startswith(' ReceivedAction'), logs['message']))]
619
- logs['rawPacket'] = logs['message'].apply(lambda x: x.split('=')[1][6:-1])
620
- logs['bridgeId'] = logs['rawPacket'].apply(lambda x: x[:12])
621
- logs['actionType'] = logs['rawPacket'].apply(lambda x: BridgeThroughGatewayAction(str(x[12:14])))
622
- logs['payload'] = logs['rawPacket'].apply(lambda x: x[14:])
623
- return logs
624
-
625
- # Versions & Version-Dependent
626
- def get_gw_version(self, gw_id):
627
- """
628
- :type gw_id: string
629
- :param gw_id: gateway id
630
- """
631
- # TODO - version bug
632
- cnt = 0
633
- while cnt <= 30:
634
- gw = self.get_gateway(gw_id)
635
- if 'version' in gw['reportedConf']:
636
- return gw['reportedConf']['version']
637
- else:
638
- debug_print('version not in GW reported conf! sleeping for 10 seconds...')
639
- sleep(10)
640
-
641
- def get_gw_ble_version(self, gw_id):
642
- """
643
- :type gw_id: string
644
- :param gw_id: gateway id
645
- """
646
- gw = self.get_gateway(gw_id)
647
- return gw["reportedConf"]["bleChipSwVersion"]
648
-
649
- def get_gw_interface_version(self, gw_id):
650
- """
651
- :type gw_id: string
652
- :param gw_id: gateway id
653
- """
654
- gw = self.get_gateway(gw_id)
655
- return gw["reportedConf"]["interfaceChipSwVersion"]
656
-
657
- def get_brg_ble_version(self, brg_id):
658
- """
659
- :type brg_id: string
660
- :param brg_id: bridge id
661
- """
662
- brg = self.get_bridge(brg_id)
663
- return brg["version"]
664
-
665
- def check_energous_ble_reboot_needed(self, brg_id):
666
- """
667
- checks if energous reboot after config change is needed for BLE version
668
- :rtype: bool
669
- :return: True if supported, False otherwise
670
- """
671
- ble_ver = self.get_brg_ble_version(brg_id).split('.')
672
- # TODO - use packaging lib
673
- if ((int(ble_ver[0]) > 3) or (int(ble_ver[0]) > 2 and int(ble_ver[1]) > 11) or \
674
- (int(ble_ver[0]) > 2 and int(ble_ver[1]) > 10 and int(ble_ver[2]) > 38)):
675
- return False
676
- else:
677
- return True
678
-
679
- def check_gw_datasource_name(self, gw_id):
680
- interface_ver = version.parse(self.get_gw_interface_version(gw_id))
681
- FIRST_SUPPORTED = version.parse('3.15.38')
682
- if interface_ver >= FIRST_SUPPORTED:
683
- return GW_DATA_MODE
684
- else:
685
- return GW_DATA_SRC
686
-
687
-
688
-
689
- def get_max_sub1ghzoutputpower(self, brg_id):
690
- brg = self.get_bridge(brg_id)
691
- version = brg["version"].split(".")
692
- # TODO - use packaging lib
693
- if (int(version[0]) > 3) or (int(version[0]) > 2 and int(version[1]) > 7) or \
694
- (int(version[0]) > 2 and int(version[1]) > 6 and int(version[2]) > 65):
695
- return 32
696
- return None
697
-
698
-
699
- def is_global_pacing_zone(self, brg_id):
700
- """
701
- check if bridge supports global pacing by zone
702
- :param brg_id: bridge ID
703
- :type brg_id: str
704
- :return: True if supports, False if not
705
- :rtype: bool
706
- """
707
- brg = self.get_bridge(brg_id)
708
- version = brg["version"].split(".")
709
- # TODO - use packaging lib
710
- if (int(version[0]) > 3) or ((int(version[0]) == 3) and int(version[1]) > 12) or \
711
- (int(version[0]) == 3 and int(version[1]) == 12 and int(version[2]) > 35):
712
- return True
713
- return False
714
-
715
-
716
- def get_pacing_param_name(self, gw_id):
717
- """
718
- gets name of global pacing / pacing group parameter name in platform
719
- :rtype: str
720
- :return: parameter name
721
- """
722
- ble_ver = self.get_gw_ble_version(gw_id).split('.')
723
- # TODO - use packaging lib
724
- if ((int(ble_ver[0]) > 3) or (int(ble_ver[0]) > 2 and int(ble_ver[1]) > 12) or \
725
- (int(ble_ver[0]) > 2 and int(ble_ver[1]) > 11 and int(ble_ver[2]) > 34)):
726
- return 'globalPacingGroup'
727
- else:
728
- return 'globalPacingEnabled'
729
-
730
- def print_brgs_versions(brg_list, ignore_bridges=None):
731
- """
732
- :type brg_list: list of dictionaries
733
- :param brg_list: bridges to print
734
- :type ignore_bridges: list
735
- :param ignore_bridges: list of bridges ID to ignore
736
- """
737
- ignore_bridges = ignore_bridges if ignore_bridges is not None else []
738
- debug_print(SEP)
739
- debug_print("Bridges versions:")
740
- for brg in brg_list:
741
- if brg["id"] in ignore_bridges:
742
- continue
743
- debug_print("{} : {}".format(brg["id"], brg["version"]))
744
- debug_print(SEP)
745
-
746
- def get_tx_period(self, brg_id, rx_tx_period, d_c, is_single=False):
747
- if is_single:
748
- return math.ceil(d_c * rx_tx_period + 0.45)
749
- brg = self.get_bridge(brg_id)
750
- version = brg["version"].split(".")
751
- # TODO - use packaging lib
752
- if (int(version[0]) > 3) or (int(version[0]) > 2 and int(version[1]) > 7) or \
753
- (int(version[0]) > 2 and int(version[1]) > 6 and int(version[2]) > 56):
754
- return math.ceil(d_c * rx_tx_period)
755
- return math.ceil(d_c * rx_tx_period + 1.5)
756
-
757
- def is_brg_updated_to_ble_ver(self, brg_id, ble_ver):
758
- """
759
- :type brg_id: str
760
- :param brg_id: bridge ID
761
- :type ble_ver: str
762
- :param ble_ver: BLE version
763
- :rtype: bool
764
- :return: True if bridge is updated to BLE version, False otherwise
765
- """
766
- brg_ble_ver = self.get_brg_ble_version(brg_id)
767
- if brg_ble_ver == ble_ver:
768
- return True
769
- else:
770
- return False
771
-
772
- def is_brg_updated_to_gw_ver(self, gw_id, brg_id):
773
- """
774
- :type gw_id: str
775
- :param gw_id: gateway id
776
- :type brg_id: str
777
- :param brg_id: bridge id
778
- :rtype: bool
779
- :returns: true if bridge is updated to gw version, else returns false
780
- """
781
- gw_ver = self.get_gw_ble_version(gw_id)
782
- brg_ver = self.get_brg_ble_version(brg_id)
783
- if gw_ver == brg_ver:
784
- debug_print(f'GW {gw_id} and BRG {brg_id} are both updated to version {self.get_gw_version(gw_id)}')
785
- return True
786
- else:
787
- debug_print(f'GW {gw_id} ver. {gw_ver}, BRG {brg_id} ver. {brg_ver}')
788
- return False
789
-
790
- def check_thin_gw_support(self, gw_id):
791
- if self.get_gateway_type(gw_id) == GatewayType.WIFI:
792
- return self.check_ble_thin_gw_support(self.get_gw_ble_version(gw_id))
793
- else:
794
- return True
795
-
796
- @staticmethod
797
- def check_ble_thin_gw_support(ble_ver):
798
- """
799
- checks if thin GW is supported for BLE version
800
- :rtype: bool
801
- :return: True if supported, False otherwise
802
- """
803
- ble_ver = ble_ver.split('.')
804
- # TODO - use packaging lib
805
- if not ((int(ble_ver[0]) > 3) or
806
- (int(ble_ver[0]) > 2 and int(ble_ver[1]) > 11) or \
807
- (int(ble_ver[0]) > 2 and int(ble_ver[1]) > 10 and int(ble_ver[2]) > 39)):
808
- return False
809
- else:
810
- return True
811
-
812
- def check_brg_mel_mod_supprt(self,brg_id):
813
- """
814
- checks if thin brg is support mel modules
815
- :rtype: bool
816
- :return: True if supported, False otherwise
817
- """
818
- brg_ble_ver = version.parse(self.get_brg_ble_version(brg_id))
819
- FIRST_SUPPORTED_BLE = version.parse('3.16.00')
820
- if brg_ble_ver >= FIRST_SUPPORTED_BLE:
821
- return True
822
- return False
823
-
824
- def check_brg_ver4(self,brg_id):
825
- """
826
- checks if GW is at ver 4 or above
827
- :rtype: bool
828
- :return: True if supported, False otherwise
829
- """
830
- brg_ble_ver = version.parse(self.get_brg_ble_version(brg_id))
831
- FIRST_SUPPORTED_BLE = version.parse('4.0.0')
832
- if brg_ble_ver >= FIRST_SUPPORTED_BLE:
833
- return True
834
- return False
835
-
836
- def check_gw_ver4(self,gateway_id):
837
- """
838
- checks if GW is at ver 4 or above
839
- :rtype: bool
840
- :return: True if supported, False otherwise
841
- """
842
- gw_ble_ver = version.parse(self.get_gw_ble_version(gateway_id))
843
- FIRST_SUPPORTED_WIFI = version.parse('4.0.0')
844
- if gw_ble_ver >= FIRST_SUPPORTED_WIFI:
845
- return True
846
- return False
847
-
848
- def check_uart_sim_support(self,gateway_id):
849
- """
850
- checks if thin GW is support uart_sim
851
- :rtype: bool
852
- :return: True if supported, False otherwise
853
- """
854
- gw_type = self.get_gateway_type(gateway_id)
855
- if not gw_type == GatewayType.WIFI:
856
- debug_print(f"gateway type: {gw_type} doesn't support uart simulator")
857
- return False
858
- gw_ble_ver = version.parse(self.get_gw_ble_version(gateway_id))
859
- interface_ver = version.parse(self.get_gw_interface_version(gateway_id))
860
- debug_print(f'gw_ble_ver={gw_ble_ver} interface_ver={interface_ver}')
861
- FIRST_SUPPORTED_WIFI = version.parse('3.15.0')
862
- FIRST_SUPPORTED_BLE = version.parse('3.16.20')
863
- if interface_ver >= FIRST_SUPPORTED_WIFI and gw_ble_ver >= FIRST_SUPPORTED_BLE:
864
- return True
865
- else:
866
- debug_print('Error! gateway versions do not support uart simulator')
867
- return False
868
-
869
- @staticmethod
870
- def ep_36_exist(version):
871
- """
872
- :type version: string
873
- :param version: FW version of bridge
874
- """
875
- major, minor, build = [int(v) for v in version]
876
- # TODO - use packaging lib
877
- if major > 3 or major == 3 and minor > 6 or major == 3 and minor == 6 and build > 28:
878
- return True
879
- else:
880
- return False
881
-
882
- # Configuration Changes
883
- def change_gw_config(self, gws_list, config_dict, minutes_timeout=5, ignore_missing_params=False, validate=True):
884
- """
885
- change configuration for multiple GWs
886
- :type gws_list: every iterable type
887
- :param gws_list: desired gateways to configure
888
- :type config_dict: dict
889
- :param config_dict: dictionary of parameters and values to configure
890
- :type ignore_missing_params: bool
891
- :param ignore_missing_params: if True, will return all GWs as updated as long as all available params have been changed
892
- """
893
- if not config_dict: # check there are parameters to configure
894
- return gws_list
895
-
896
- def check_updated(gw_id, config_dict):
897
- """
898
- :param gw_id: Gateway ID
899
- :param config_dict: config dictionary
900
- """
901
- gw = self.get_gateway(gw_id)
902
- if 'version' in config_dict.keys():
903
- try:
904
- gw_version = gw['version']
905
- if gw_version != config_dict['version']:
906
- return False
907
- except KeyError:
908
- debug_print(f'Version not yet updated for {gw_id}... sleeping for 30 seconds')
909
- sleep(30)
910
- if 'additional' in gw['reportedConf'].keys():
911
- gw_dict = gw['reportedConf']['additional']
912
- else:
913
- gw_dict = {}
914
- relevant_dict = {}
915
- missing_keys = []
916
- if GW_DATA_MODE in config_dict or GW_DATA_SRC in config_dict:
917
- if GW_DATA_MODE in config_dict:
918
- config_dict[GW_DATA_SRC] = config_dict[GW_DATA_MODE]
919
- else:
920
- config_dict[GW_DATA_MODE] = config_dict[GW_DATA_SRC]
921
- if self.check_gw_datasource_name(gw_id) == GW_DATA_SRC:
922
- config_dict.pop(GW_DATA_MODE)
923
- else:
924
- config_dict.pop(GW_DATA_SRC)
925
- for key in config_dict.keys():
926
- if key not in gw_dict.keys():
927
- missing_keys.append(key)
928
- else:
929
- relevant_dict.update({key: gw_dict[key]})
930
- if relevant_dict == config_dict:
931
- return True
932
- if len(missing_keys) > 0:
933
- if ignore_missing_params:
934
- relevant_config_dict = config_dict.copy()
935
- for key in missing_keys:
936
- relevant_config_dict.pop(key)
937
- if relevant_dict == relevant_config_dict:
938
- return True
939
- return False
940
- raise ParamMissingError(
941
- f"{missing_keys} not in GW {gw_id} Parameters! Check FW Version or change desired config")
942
- return False
943
-
944
- desired_version = None
945
- if 'version' in config_dict.keys():
946
- desired_version = config_dict.pop('version')
947
- config_dict_altered = {'additional': config_dict}
948
- if desired_version is not None:
949
- config_dict_altered.update({'version': desired_version})
950
- updated_gws = []
951
- params_missing = []
952
- config_sent = False
953
- timeout = datetime.datetime.now() + datetime.timedelta(minutes=minutes_timeout)
954
- sleep_needed = False
955
- while datetime.datetime.now() <= timeout and set(updated_gws) != set(gws_list):
956
- if sleep_needed:
957
- sleep(10)
958
- sleep_needed = False
959
- # Configure Gateways
960
- if not config_sent:
961
- try:
962
- self.update_gateways_configuration(gws_list, config_dict_altered)
963
- debug_print(f'Configuration sent to {gws_list}')
964
- config_sent = True
965
- if not validate:
966
- updated_gws = gws_list
967
- except EXCEPTIONS_TO_CATCH as e:
968
- debug_print(f'{gws_list}: {e}')
969
- sleep_needed = True
970
-
971
- # Check if GWs updated
972
- for gw_id in set(gws_list) - set(updated_gws):
973
- try:
974
- gw_updated = check_updated(gw_id, config_dict.copy())
975
- if gw_updated:
976
- updated_gws.append(gw_id)
977
- except EXCEPTIONS_TO_CATCH as e:
978
- debug_print(f'{gw_id}: {e}')
979
- sleep_needed = True
980
- except ParamMissingError as e:
981
- params_missing.append((e, gw_id))
982
- updated_gws.append(gw_id)
983
- for error, gw in params_missing:
984
- debug_print(error)
985
- updated_gws.remove(gw)
986
- debug_print(f'{updated_gws} updated to desired configuration:')
987
- debug_print(config_dict, pretty=True)
988
- return updated_gws
989
-
990
- def change_brg_config(self, connected_bridges, config_dict, minutes_timeout=5, ignore_bridges=[None]):
991
- """
992
- change configuration for all bridge in connected_bridges
993
- hange configuration to all in connected_bridges
994
- :type connected_bridges: every iterable type
995
- :param connected_bridges: desired bridges to configure
996
- :type config_dict: dict
997
- :param config_dict: dictionary of parameters and values to configure
998
- :type ignore_bridges: list
999
- :param ignore_bridges: list of bridges ID to ignore
1000
- :rtype: list
1001
- :return: list of updated bridges
1002
- """
1003
- brgs_list = list(set(connected_bridges) - set(ignore_bridges))
1004
- updated_brgs = []
1005
- if len(brgs_list) == 0:
1006
- return updated_brgs
1007
- if not config_dict:
1008
- return brgs_list
1009
-
1010
- def is_dict_subset_of_dict(dict1, dict2, path=""):
1011
- """
1012
- Checks if dict1 is a subset of dict2. If not, returns the missing keys.
1013
- """
1014
- missing_keys = []
1015
-
1016
- def helper(d1, d2, p):
1017
- nonlocal missing_keys
1018
10
 
1019
- for key in d1:
1020
- full_key = f"{p}.{key}" if p else key
1021
-
1022
- if key not in d2:
1023
- missing_keys.append(full_key)
1024
- else:
1025
- value1 = d1[key]
1026
- value2 = d2[key]
1027
- if isinstance(value1, dict) and isinstance(value2, dict):
1028
- helper(value1, value2, full_key)
1029
- elif value1 != value2:
1030
- missing_keys.append(full_key)
1031
-
1032
- helper(dict1, dict2, path)
1033
-
1034
- is_subset = len(missing_keys) == 0
1035
- return is_subset, missing_keys
1036
-
1037
- def update_bridge(brg_id, config_dict):
1038
- """
1039
- :param brg_id: Bridge ID
1040
- :param config_dict: config dictionary
1041
- """
1042
- res = self.update_bridge_configuration(brg_id, config_dict)
1043
- if self.get_bridge_board(brg_id) == BoardTypes.ENERGOUS.value and self.check_energous_ble_reboot_needed(
1044
- brg_id):
1045
- debug_print(f'Rebooting energous Bridge {brg_id}...')
1046
- self.send_action_to_bridge(brg_id, BridgeAction.REBOOT)
1047
- return res
1048
-
1049
- def get_brg_cfg_of_brg_info(bridge_info):
1050
- """
1051
- Extracting bridge configuration for a bridge id while supporing mel modules and previouse versions
1052
- :param bridge_id: String - the ID of the bridge to get information about
1053
- :return: A dictionary containing bridge configuration
1054
- """
1055
- bridge_info_received = bridge_info
1056
- brg_config = {}
1057
- def recursive_extract_config(dictionary):
1058
- if "config" in dictionary:
1059
- brg_config.update(dictionary["config"])
1060
- for key, value in dictionary.items():
1061
- if isinstance(value, dict):
1062
- recursive_extract_config(value)
1063
-
1064
- recursive_extract_config(bridge_info)
1065
- if not brg_config:
1066
- return bridge_info
1067
- return brg_config
1068
-
1069
- def check_updated(brg_id, config_dict):
1070
- """
1071
- :param brg_id: Bridge ID
1072
- :param config_dict: config dictionary
1073
- """
1074
- return True
1075
- # bridge_info = self.get_bridge(brg_id)
1076
- # if 'modules' in bridge_info and len(bridge_info['config'])>0:
1077
- # is_subset, keys_missing = is_dict_subset_of_dict(config_dict, bridge_info['config'])
1078
- # elif 'modules' in bridge_info:
1079
- # is_subset, keys_missing = is_dict_subset_of_dict(config_dict, bridge_info['modules'])
1080
- # if is_subset:
1081
- # debug_print(f'{brg_id} updated')
1082
- # return True
1083
- # if len(keys_missing) > 0:
1084
- # print(bridge_info)
1085
- # raise ParamMissingError(
1086
- # f"{keys_missing} not in BRG {brg_id} Parameters! Check FW Version or change desired config")
1087
- # return False
1088
-
1089
- timeout = datetime.datetime.now() + datetime.timedelta(minutes=minutes_timeout)
1090
- brgs_to_config = {brg: True for brg in brgs_list}
1091
- params_missing = []
1092
- sleep_needed = False
1093
- while datetime.datetime.now() <= timeout and set(updated_brgs) != set(brgs_list):
1094
- if sleep_needed:
1095
- sleep(10)
1096
- sleep_needed = False
1097
- # Update bridges
1098
- for brg_id in filter(brgs_to_config.get, brgs_to_config):
1099
- try:
1100
- res = update_bridge(brg_id, config_dict)
1101
- debug_print(f'Sent configuration to bridge {brg_id}')
1102
- brgs_to_config[brg_id] = not res
1103
- except EXCEPTIONS_TO_CATCH as e:
1104
- debug_print(f'{brg_id}: {e}')
1105
- ueded = True
1106
- # Check if bridges updated
1107
- for brg_id in set(brgs_list) - set(updated_brgs):
1108
- try:
1109
- brg_updated = check_updated(brg_id, config_dict)
1110
- if brg_updated:
1111
- updated_brgs.append(brg_id)
1112
- else:
1113
- pass
1114
- ## TODO TEMP
1115
- # debug_print(f'Sending configuration again to bridge {brg_id}')
1116
- # sleep(3)
1117
- # update_bridge(brg_id, config_dict)
1118
- except (EXCEPTIONS_TO_CATCH) as e:
1119
- debug_print(f'{brg_id}: {e}')
1120
- sleep_needed = True
1121
- except ParamMissingError as e:
1122
- params_missing.append((e, brg_id))
1123
- updated_brgs.append(brg_id)
1124
- for error, brg in params_missing:
1125
- debug_print(error)
1126
- updated_brgs.remove(brg)
1127
- if minutes_timeout>0:
1128
- if updated_brgs:
1129
- debug_print(f'{(updated_brgs)} updated to desired configuration:')
1130
- else:
1131
- debug_print('No bridge is updated to desired configuration:')
1132
- else:
1133
- debug_print(f'{brgs_list} will update to desired configuration when back online:')
1134
- debug_print(config_dict, pretty=True)
1135
- return updated_brgs
1136
-
1137
- # Connect & Brownout
1138
- def change_to_brownout(self, connected_bridges, seconds_in_bo=0, ignore_bridges=None):
1139
- """
1140
- change all bridges in connected_bridges to brownout mode and sleeps for seconds_in_bo seconds.
1141
- :type connected_bridges: every iterable type
1142
- :param connected_bridges: desired bridges to configure
1143
- :type seconds_in_bo: int
1144
- :param seconds_in_bo: time to sleep in brownout [seconds]
1145
- :type ignore_bridges: list
1146
- :param ignore_bridges: list of bridges ID to ignore
1147
- example:
1148
- wiliot.change_to_brownout(["4CEE79FA3523", "D0269CFEB518"], seconds_in_bo=300)
1149
- """
1150
- ignore_bridges = list() if ignore_bridges is None else ignore_bridges
1151
- relevant_brgs = list(set(connected_bridges) - set(ignore_bridges))
1152
- ver4_support_brgs = [brg for brg in relevant_brgs if self.check_brg_ver4(brg)]
1153
- not_ver4_support_brgs = [brg for brg in relevant_brgs if not self.check_brg_ver4(brg)]
1154
- brgs_ver4_updated = self.change_brg_config(ver4_support_brgs, BO_DICT)
1155
- brgs_not_ver4_updated = self.change_brg_config(not_ver4_support_brgs, {'energyPattern': 36})
1156
- if seconds_in_bo > 0:
1157
- debug_print(f'Sleeping for {datetime.timedelta(seconds=seconds_in_bo)}')
1158
- sleep(seconds_in_bo)
1159
- return brgs_ver4_updated + brgs_not_ver4_updated
1160
-
1161
- def connect_gw_bridges(self, gw_ids=None, minutes_timeout=5, expected_num_brgs=None, ignore_bridges=None,
1162
- do_brown_out=True, expected_brgs=None):
1163
- """
1164
- Iteratively connects to bridges and change them to brownout.
1165
- Function can take either GW IDs as input and then connect to all brgs connected to them
1166
- and if needed change them to brownout,
1167
- OR takes expected brgs and then connect to those and if needed change them to brownout.
1168
- brgs listed in ignore bridges will be completely ignored.
1169
- :type gw_ids: list
1170
- :param gw_ids: list of GW ids
1171
- :type minutes_timeout: int
1172
- :param minutes_timeout: timeout to connect to bridges
1173
- :type expected_num_brgs: int
1174
- :param expected_num_brgs: maximum bridges in the deployment.
1175
- if None, the function will scan for new bridges until minutes_timeout is over / wait for all expected brgs
1176
- :type ignore_bridges: list
1177
- :param ignore_bridges: list of bridges ID to ignore
1178
- :type do_brown_out: bool
1179
- :param do_brown_out: brown out bridges (change to EP 36)
1180
- :type expected_brgs: list
1181
- :param expected_brgs: list of expected BRG ids
1182
- :rtype board_type_dict: dict
1183
- :return board_type_dict: dictionary of bridge and board type
1184
- :rtype connected_brgs type: list
1185
- :return connected_brgs param: list of all connected bridges from csv
1186
- """
1187
-
1188
- def get_bridge_type(brg_dict):
1189
- if 'single' in brg_dict['boardType'].lower():
1190
- return 'single'
1191
- return 'dual'
1192
-
1193
- def get_connected_bridges(gw_ids, ignore_bridges, expected_brgs, timeout):
1194
- connected_bridges = list()
1195
- connected_bridges_dicts = None
1196
- sleep_needed = False
1197
- while connected_bridges_dicts is None and datetime.datetime.now() < timeout:
1198
- if sleep_needed:
1199
- sleep(5)
1200
- try:
1201
- connected_bridges_dicts = self.get_bridges(online=True, gateway_id=gw_ids)
1202
- except EXCEPTIONS_TO_CATCH as e:
1203
- print(e)
1204
- sleep_needed = True
1205
- board_type_dict = {'dual': [], 'single': []}
1206
- for brg_dict in connected_bridges_dicts:
1207
- brg_id = brg_dict['id']
1208
- # skip ignore bridges
1209
- if brg_id in ignore_bridges:
1210
- continue
1211
- # skip bridges not in expected brgs
1212
- if len(expected_brgs) > 0:
1213
- if brg_id not in expected_brgs:
1214
- continue
1215
- # claim unclaimed bridges
1216
- if not brg_dict['claimed']:
1217
- self.claim_bridge(brg_id)
1218
- board_type_dict[get_bridge_type(brg_dict)].append(brg_id)
1219
- connected_bridges.append(brg_id)
1220
- return connected_bridges, board_type_dict
1221
- if gw_ids is None:
1222
- if len(expected_brgs) > 0:
1223
- gw_ids = list(self.get_gateways_from_bridges(expected_brgs).keys())
1224
- else:
1225
- raise ValueError('Must input either GW IDs or expected bridges!')
1226
- ignore_bridges = list() if ignore_bridges is None else ignore_bridges
1227
- expected_brgs = list() if expected_brgs is None else expected_brgs
1228
- # remove ignore bridges from expected_brgs
1229
- expected_brgs = list(set(expected_brgs) - set(ignore_bridges))
1230
- expected_num_brgs = -1 if expected_num_brgs is None else expected_num_brgs
1231
- timeout = datetime.datetime.now() + datetime.timedelta(minutes=minutes_timeout)
1232
- connected_bridges, board_type_dict = get_connected_bridges(
1233
- gw_ids, ignore_bridges, expected_brgs, timeout)
1234
- brgs_brownout_status = dict()
1235
- if do_brown_out:
1236
- brgs_brownout_status = dict(map(lambda x: (x, False), connected_bridges))
1237
- if expected_num_brgs < 0:
1238
- debug_print(
1239
- f'Connecting bridges | Timeout at {timeout.time()}')
1240
- else:
1241
- debug_print(
1242
- f'Connecting{" and browning out" if do_brown_out else ""} {expected_num_brgs} bridges | Timeout at {timeout.time()}')
1243
- if len(expected_brgs) > 0:
1244
- debug_print(f'Expecting bridges {expected_brgs}')
1245
- if expected_num_brgs == -1:
1246
- expected_num_brgs = len(expected_brgs)
1247
- last_print = datetime.datetime.now() - datetime.timedelta(minutes=1)
1248
- while (datetime.datetime.now() < timeout and \
1249
- (False in brgs_brownout_status.values() or (len(connected_bridges) < expected_num_brgs
1250
- and not (set(expected_brgs) <= set(connected_bridges))))):
1251
- connected_bridges, board_type_dict = get_connected_bridges(
1252
- gw_ids, ignore_bridges, expected_brgs, timeout)
1253
- # update new brgs to brownout
1254
- new_brgs = list(set(connected_bridges) - set(brgs_brownout_status.keys()))
1255
- brgs_brownout_status.update({brg: False for brg in new_brgs})
1256
- if (datetime.datetime.now() - last_print).total_seconds() > 5:
1257
- debug_print(
1258
- f'{len(connected_bridges)}{(" / " + str(expected_num_brgs)) if expected_num_brgs > 0 else ""} connected bridges')
1259
- last_print = datetime.datetime.now()
1260
- # do brown out for needed bridges
1261
- if do_brown_out and False in brgs_brownout_status.values():
1262
- # update only needed bridges
1263
- brgs_to_update = [brg for brg, status in brgs_brownout_status.items() if status is False]
1264
- brgs_updated = self.change_to_brownout(brgs_to_update)
1265
- # change brownout status to true
1266
- brgs_brownout_status.update({brg: True for brg in brgs_updated})
1267
- if False in brgs_brownout_status.values():
1268
- browned_out_brgs = list(filter(brgs_brownout_status.get, brgs_brownout_status))
1269
- non_browned_out_brgs = list(set(connected_bridges) - set(browned_out_brgs))
1270
- raise WiliotCloudError(f'Cannot change connected BRGs {non_browned_out_brgs} to brownout! Check deployment!')
1271
-
1272
- debug_print(f'{len(gw_ids)} GWs {gw_ids} Connected')
1273
- debug_print(f'{len(connected_bridges)} BRGs {connected_bridges} Connected')
1274
- return connected_bridges, board_type_dict
1275
-
1276
-
1277
- def check_gw_compatible_for_action(self, gateway_id):
1278
- """
1279
- checks if gateway is compatible to send and receive power management configurations
1280
- :type gateway_id: str
1281
- :param gateway_id: gateway ID
1282
- :rtype: bool
1283
- """
1284
- gateway_type = self.get_gateway_type(gateway_id)
1285
- if gateway_type != GatewayType.WIFI:
1286
- if gateway_type == GatewayType.MOBILE:
1287
- return True
1288
- else:
1289
- return False
1290
- gw_dict = self.get_gateway(gateway_id)
1291
- ble_ver = gw_dict["reportedConf"]["bleChipSwVersion"]
1292
- if gw_dict['online']:
1293
- if 'gwMgmtMode' in gw_dict['reportedConf']['additional']:
1294
- if gw_dict['reportedConf']['additional']['gwMgmtMode'] == 'transparent':
1295
- support = self.check_gw_ble_transparent_support(ble_ver)
1296
- if not support:
1297
- debug_print(f'Please update GW {gateway_id} or change to active mode for power management support!')
1298
- return support
1299
- return True
1300
- return True
1301
- return False
1302
-
1303
- @staticmethod
1304
- def check_gw_ble_transparent_support(ble_ver):
1305
- """
1306
- checks if power management is supported for transparent GW BLE version
1307
- :rtype: bool
1308
- :return: True if supported, False otherwise
1309
- """
1310
- ble_ver = ble_ver.split('.')
1311
- # TODO - use packaging lib
1312
- if not ((int(ble_ver[0]) > 3) or
1313
- (int(ble_ver[0]) > 2 and int(ble_ver[1]) > 14) or \
1314
- (int(ble_ver[0]) > 2 and int(ble_ver[1]) > 13 and int(ble_ver[2]) > 27)):
1315
- return False
1316
- else:
1317
- return True
1318
-
1319
- def brg_cfg_to_mel_module_v4(self,cfg_dict):
1320
- def remove_empty(dict={}):
1321
- res = {}
1322
- for k,v in dict.items():
1323
- if v != None:
1324
- res[k] = v
1325
- return res
1326
- energy2400 = {}
1327
- datapath = {}
1328
- energySub1g = {}
1329
- calibration = {}
1330
- powerManagement = {}
1331
- externalSensor = {}
1332
- energy2400['dutyCycle'] = cfg_dict.get("dutyCycle2400")
1333
- energy2400['outputPower'] = cfg_dict.get("outputPower2400")
1334
- energy2400['energyPattern2400'] = cfg_dict.get("energyPattern2400")
1335
- energy2400 = remove_empty(energy2400)
1336
- calibrationParams = ['calibPattern','calibInterval','calibOutputPower']
1337
- for param in calibrationParams:
1338
- calibration[param] = cfg_dict.get(param)
1339
- calibration = remove_empty(calibration)
1340
- datapathParams = ['pktFilter','txRepetition','adaptivePacer','pacerInterval','unifiedEchoPkt','commOutputPower','globalPacingGroup']
1341
- for param in datapathParams:
1342
- datapath[param] = cfg_dict.get(param)
1343
- datapath = remove_empty(datapath)
1344
- energySub1g['cycle'] = cfg_dict.get("cycle")
1345
- energySub1g['dutyCycle'] = cfg_dict.get("sub1gdutyCycle")
1346
- energySub1g['sub1gEnergyPattern'] = cfg_dict.get("sub1gEnergyPattern")
1347
- energySub1g['outputPower'] = cfg_dict.get("sub1GhzOutputPower")
1348
- energySub1g = remove_empty(energySub1g)
1349
- powerManagementParams = ['staticLedsOn','dynamicLedsOn','staticOnDuration','dynamicOnDuration',
1350
- 'staticKeepAliveScan','staticSleepDuration','dynamicKeepAliveScan',
1351
- 'dynamicSleepDuration','staticKeepAlivePeriod','dynamicKeepAlivePeriod']
1352
- externalSensorParams = ['adType0','adType1','uuidLsb0','uuidLsb1','uuidMsb0','uuidMsb1','sensor0Scramble','sensor1Scramble']
1353
- for param in powerManagementParams:
1354
- powerManagement[param] = cfg_dict.get(param)
1355
- powerManagement = remove_empty(powerManagement)
1356
- for param in externalSensorParams:
1357
- externalSensor[param] = cfg_dict.get(param)
1358
- externalSensor = remove_empty(externalSensor)
1359
-
1360
-
1361
- mel_mod_cfg = {
1362
- "energy2400": {
1363
- "config": energy2400
1364
- },
1365
- "energySub1g": {
1366
- "config": energySub1g
1367
- },
1368
- "datapath": {
1369
- "config": datapath
1370
- },
1371
- "powerManagement": {
1372
- "config": powerManagement
1373
- },
1374
- "externalSensor": {
1375
- "config": externalSensor
1376
- },
1377
- "calibration": {
1378
- "config": calibration
1379
- }
1380
- }
1381
- return mel_mod_cfg
1382
-
1383
- def firmware_update(self, timeout_minutes=3,gws=None, brgs=None, version=None):
1384
- """
1385
- Update firmware for a gateway or a list of bridges.
1386
-
1387
- Args:
1388
- timeout_minutes (int, optional): Maximum time to approve a gateway. Defaults to 3.
1389
- gw (str, optional): Gateway ID. Defaults to None.
1390
- brgs (list, optional): List of bridge IDs. Defaults to None.
1391
- version (str, optional): Desired version to update to. Defaults to None.
1392
- api_version (str, optional): Desired gateway API version. Defaults to None.
1393
-
1394
- Raises:
1395
- ValueError: If neither or both gateway and bridge are provided.
1396
- TypeError: If bridges are not provided as a list.
1397
-
1398
- Returns:
1399
- bool: True if the update was successful, False otherwise.
1400
- """
1401
- def compare_versions(version1, version2):
1402
- # Split the version strings into parts
1403
- parts1 = version1.split('.')
1404
- parts2 = version2.split('.')
1405
-
1406
- length = max(len(parts1), len(parts2))
1407
- int_parts1 = [int(parts1[i]) if i < len(parts1) else 0 for i in range(length)]
1408
- int_parts2 = [int(parts2[i]) if i < len(parts2) else 0 for i in range(length)]
1409
-
1410
- for v1, v2 in zip(int_parts1, int_parts2):
1411
- if v1 < v2:
1412
- return False
1413
- elif v1 > v2:
1414
- return True
1415
- # If all parts are equal, return 0
1416
- return 0
1417
-
1418
- if gws is None and brgs is None:
1419
- raise KeyError("Must enter Gateway or Bridge!")
1420
- elif gws is not None and brgs is not None:
1421
- raise KeyError("Enter only Gateway or only Bridge!")
1422
- elif gws is not None:
1423
- if not isinstance(gws, list):
1424
- raise TypeError("Gateways must be entered as a list")
1425
- for gw in gws:
1426
- debug_print(f"Updating Gateway '{gw}' to version '{version}'")
1427
- gw_conf = self.get_gateway(gw)
1428
- if compare_versions(gw_conf['reportedConf']['version'], version):
1429
- debug_print(f"Cannot downgrade {gw}, not supported")
1430
- return False
1431
- res = self.update_gateway_configuration(gw, {"version": version})
1432
- if res:
1433
- debug_print(f"Gateway '{gw}' updated to desired configuration")
1434
- return True
1435
- else:
1436
- debug_print(f"Failed to update '{gw}'")
1437
- elif brgs is not None:
1438
- if not isinstance(brgs, list):
1439
- raise TypeError("Bridges must be entered as a list")
1440
- for bridge in brgs:
1441
- path = "bridge/{}".format(bridge)
1442
- payload = {'desiredVersion' : version}
1443
- try:
1444
- res = self._put(path, payload)
1445
- return res["message"].lower().find("updated bridge success") != -1
1446
- except Exception as e:
1447
- print(f"Failed to update bridge configuration: {e}")
1448
-
1449
11
  def kick_gw_from_mqtt(self, gw_id):
1450
12
  path = f"gateway/{gw_id}/kick-mqtt-connection"
1451
13
  response = self._post(path, None)
1452
14
  return response
1453
-
1454
-
1455
- class ExtendedPlatformClient(PlatformClient):
1456
- def __init__(self, api_key, owner_id, env='prod', region='us-east-2', cloud='', log_file=None, logger_=None):
1457
- super().__init__(api_key=api_key, owner_id=owner_id, env=env, region=region, cloud=cloud, log_file=log_file, logger_=logger_)
1458
-
1459
- def get_locations(self, locations=None):
1460
- """
1461
- :rtype: dict
1462
- :return: Dictionary of locations and zones with relevant associations for each
1463
- """
1464
- in_locations = locations
1465
- locations = super().get_locations()
1466
- res = []
1467
- for location in locations:
1468
- if in_locations is not None and location['name'] not in in_locations:
1469
- continue
1470
- loc_id = location['id']
1471
- location['associations'] = super().get_location_associations(loc_id)
1472
- zones = super().get_zones(loc_id)
1473
- for zone in zones:
1474
- zone_id = zone['id']
1475
- zone['associations'] = super().get_zone_associations(zone_id)
1476
- location['zones'] = zones
1477
- res.append(location)
1478
- return res
1479
-
1480
- def get_locations_bridges(self, locations=None):
1481
- """
1482
- change a list of bridges associated to location
1483
- if location is None, reffer all owner's locations
1484
- :type locations: every iterable type
1485
- :param locations: desired locations
1486
- returns a list of dictionaries
1487
- Each dictionary includes bridgeId, location properties and zone properties if a bridge is associated to zone
1488
- """
1489
- locations = self.get_locations(locations=locations)
1490
- bridges = []
1491
- for location in locations:
1492
- # Add bridges directly associated with location
1493
- for bridge in location['associations']:
1494
- if bridge['associationType'] == 'bridge':
1495
- bridges.append({'bridgeId': bridge['associationValue'],
1496
- 'locationId': location['id'],
1497
- 'locationName': location['name'],
1498
- 'locationType': location['locationType']})
1499
- if 'location' in location.keys():
1500
- bridges[-1].update({
1501
- 'locationLat': location['lat'],
1502
- 'locationLng': location['lng'],
1503
- 'location': location['location'],
1504
- 'locationAddress': location['address']})
1505
- # add bridges associated to zone (inside location)
1506
- for zone in location['zones']:
1507
- for bridge in zone['associations']:
1508
- if bridge['associationType'] == 'bridge':
1509
- bridges.append({'bridgeId': bridge['associationValue'],
1510
- 'locationId': location['id'],
1511
- 'locationName': location['name'],
1512
- 'zoneId': zone['id'],
1513
- 'zoneName': zone['name'],
1514
- 'locationType': location['locationType']})
1515
- if 'location' in location.keys():
1516
- bridges[-1].update({
1517
- 'locationLat': location['lat'],
1518
- 'locationLng': location['lng'],
1519
- 'location': location['location'],
1520
- 'locationAddress': location['address']})
1521
- return bridges
1522
-
1523
- def get_location_id(self, location_name):
1524
- locations = self.get_locations()
1525
- for loc_dict in locations:
1526
- if loc_dict['name'] == location_name:
1527
- return loc_dict['id']
1528
-
1529
- def get_location_bridges(self, location_name):
15
+
16
+ def get_kong_logs(self, gw_id):
1530
17
  """
1531
- get brgs from location name
1532
- :param location_name: name of location
1533
- :type location_name: str
1534
- :return: list of brgs in location
1535
- :rtype: list
18
+ Only available under the certificate registration account
1536
19
  """
1537
- location_id = self.get_location_id(location_name)
1538
- loc_associations = self.get_location_associations(location_id)
1539
- brgs_list = [assoc['associationValue'] for assoc in loc_associations if (assoc['associationType'] == 'bridge')]
1540
- return brgs_list
20
+ path = f"gateway/{gw_id}/auth-logs"
1541
21
 
1542
- def get_locations_names(self):
1543
- locations = super().get_locations()
1544
- names = []
1545
- for loc in locations:
1546
- names.append(loc['name'])
1547
- return names
22
+ return self._get(path)
23
+