wiliot-certificate 1.3.0a1__py3-none-any.whl → 1.4.0a2__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.
- brg_certificate/__init__.py +0 -0
- brg_certificate/ag/energous_v0_defines.py +925 -0
- brg_certificate/ag/energous_v1_defines.py +931 -0
- brg_certificate/ag/energous_v2_defines.py +925 -0
- brg_certificate/ag/energous_v3_defines.py +925 -0
- brg_certificate/ag/energous_v4_defines.py +925 -0
- brg_certificate/ag/fanstel_lan_v0_defines.py +925 -0
- brg_certificate/ag/fanstel_lte_v0_defines.py +925 -0
- brg_certificate/ag/fanstel_wifi_v0_defines.py +925 -0
- brg_certificate/ag/minew_lte_v0_defines.py +925 -0
- brg_certificate/ag/wlt_cmd_if.html +102 -0
- brg_certificate/ag/wlt_types.html +6114 -0
- brg_certificate/ag/wlt_types_ag.py +7840 -0
- brg_certificate/ag/wlt_types_ag_jsons/brg2brg_ota.json +142 -0
- brg_certificate/ag/wlt_types_ag_jsons/brg2gw_hb.json +785 -0
- brg_certificate/ag/wlt_types_ag_jsons/brg2gw_hb_sleep.json +139 -0
- brg_certificate/ag/wlt_types_ag_jsons/calibration.json +394 -0
- brg_certificate/ag/wlt_types_ag_jsons/custom.json +515 -0
- brg_certificate/ag/wlt_types_ag_jsons/datapath.json +672 -0
- brg_certificate/ag/wlt_types_ag_jsons/energy2400.json +550 -0
- brg_certificate/ag/wlt_types_ag_jsons/energySub1g.json +595 -0
- brg_certificate/ag/wlt_types_ag_jsons/externalSensor.json +598 -0
- brg_certificate/ag/wlt_types_ag_jsons/interface.json +938 -0
- brg_certificate/ag/wlt_types_ag_jsons/powerManagement.json +1234 -0
- brg_certificate/ag/wlt_types_ag_jsons/side_info_sensor.json +105 -0
- brg_certificate/ag/wlt_types_ag_jsons/signal_indicator_data.json +77 -0
- brg_certificate/ag/wlt_types_ag_jsons/unified_echo_ext_pkt.json +61 -0
- brg_certificate/ag/wlt_types_ag_jsons/unified_echo_pkt.json +110 -0
- brg_certificate/brg_certificate.py +191 -0
- brg_certificate/brg_certificate_cli.py +47 -0
- brg_certificate/cert_common.py +828 -0
- brg_certificate/cert_config.py +395 -0
- brg_certificate/cert_data_sim.py +188 -0
- brg_certificate/cert_defines.py +337 -0
- brg_certificate/cert_gw_sim.py +285 -0
- brg_certificate/cert_mqtt.py +373 -0
- brg_certificate/cert_prints.py +181 -0
- brg_certificate/cert_protobuf.py +88 -0
- brg_certificate/cert_results.py +300 -0
- brg_certificate/cert_utils.py +358 -0
- brg_certificate/certificate_sanity_test_list.txt +36 -0
- brg_certificate/certificate_test_list.txt +43 -0
- brg_certificate/config/eclipse.json +10 -0
- brg_certificate/config/hivemq.json +10 -0
- brg_certificate/config/mosquitto.json +10 -0
- brg_certificate/config/mosquitto.md +95 -0
- brg_certificate/config/wiliot-dev.json +10 -0
- brg_certificate/restore_brg.py +59 -0
- brg_certificate/tests/calibration/interval_test/interval_test.json +13 -0
- brg_certificate/tests/calibration/interval_test/interval_test.py +28 -0
- brg_certificate/tests/calibration/output_power_test/output_power_test.json +13 -0
- brg_certificate/tests/calibration/output_power_test/output_power_test.py +28 -0
- brg_certificate/tests/calibration/pattern_test/pattern_test.json +13 -0
- brg_certificate/tests/calibration/pattern_test/pattern_test.py +70 -0
- brg_certificate/tests/datapath/adaptive_pacer_algo_test/adaptive_pacer_algo_test.json +13 -0
- brg_certificate/tests/datapath/adaptive_pacer_algo_test/adaptive_pacer_algo_test.py +76 -0
- brg_certificate/tests/datapath/num_of_tags_test/num_of_tags_test.json +13 -0
- brg_certificate/tests/datapath/num_of_tags_test/num_of_tags_test.py +83 -0
- brg_certificate/tests/datapath/output_power_test/output_power_test.json +13 -0
- brg_certificate/tests/datapath/output_power_test/output_power_test.py +27 -0
- brg_certificate/tests/datapath/pacer_interval_ble5_test/pacer_interval_ble5_test.json +13 -0
- brg_certificate/tests/datapath/pacer_interval_ble5_test/pacer_interval_ble5_test.py +43 -0
- brg_certificate/tests/datapath/pacer_interval_tags_count_test/pacer_interval_tags_count_test.json +13 -0
- brg_certificate/tests/datapath/pacer_interval_tags_count_test/pacer_interval_tags_count_test.py +63 -0
- brg_certificate/tests/datapath/pacer_interval_test/pacer_interval_test.json +13 -0
- brg_certificate/tests/datapath/pacer_interval_test/pacer_interval_test.py +50 -0
- brg_certificate/tests/datapath/pattern_test/pattern_test.json +13 -0
- brg_certificate/tests/datapath/pattern_test/pattern_test.py +28 -0
- brg_certificate/tests/datapath/pkt_filter_ble5_test/pkt_filter_ble5_test.json +13 -0
- brg_certificate/tests/datapath/pkt_filter_ble5_test/pkt_filter_ble5_test.py +51 -0
- brg_certificate/tests/datapath/pkt_filter_gen3_test/pkt_filter_gen3_test.json +13 -0
- brg_certificate/tests/datapath/pkt_filter_gen3_test/pkt_filter_gen3_test.py +54 -0
- brg_certificate/tests/datapath/pkt_filter_test/pkt_filter_test.json +13 -0
- brg_certificate/tests/datapath/pkt_filter_test/pkt_filter_test.py +55 -0
- brg_certificate/tests/datapath/rssi_threshold_test/rssi_threshold_test.json +13 -0
- brg_certificate/tests/datapath/rssi_threshold_test/rssi_threshold_test.py +73 -0
- brg_certificate/tests/datapath/rx_channel_test/rx_channel_test.json +13 -0
- brg_certificate/tests/datapath/rx_channel_test/rx_channel_test.py +41 -0
- brg_certificate/tests/datapath/rx_rate_gen2_test/rx_rate_gen2_test.json +21 -0
- brg_certificate/tests/datapath/rx_rate_gen2_test/rx_rate_gen2_test.py +184 -0
- brg_certificate/tests/datapath/rx_rate_gen3_test/rx_rate_gen3_test.json +21 -0
- brg_certificate/tests/datapath/rx_rate_gen3_test/rx_rate_gen3_test.py +210 -0
- brg_certificate/tests/datapath/stress_gen3_test/stress_gen3_test.json +30 -0
- brg_certificate/tests/datapath/stress_gen3_test/stress_gen3_test.py +203 -0
- brg_certificate/tests/datapath/stress_test/stress_test.json +30 -0
- brg_certificate/tests/datapath/stress_test/stress_test.py +210 -0
- brg_certificate/tests/datapath/tx_repetition_algo_test/tx_repetition_algo_test.json +13 -0
- brg_certificate/tests/datapath/tx_repetition_algo_test/tx_repetition_algo_test.py +113 -0
- brg_certificate/tests/datapath/tx_repetition_test/tx_repetition_test.json +13 -0
- brg_certificate/tests/datapath/tx_repetition_test/tx_repetition_test.py +79 -0
- brg_certificate/tests/edge_mgmt/actions_test/actions_test.json +13 -0
- brg_certificate/tests/edge_mgmt/actions_test/actions_test.py +432 -0
- brg_certificate/tests/edge_mgmt/brg2brg_ota_ble5_test/brg2brg_ota_ble5_test.json +13 -0
- brg_certificate/tests/edge_mgmt/brg2brg_ota_ble5_test/brg2brg_ota_ble5_test.py +94 -0
- brg_certificate/tests/edge_mgmt/brg2brg_ota_test/brg2brg_ota_test.json +13 -0
- brg_certificate/tests/edge_mgmt/brg2brg_ota_test/brg2brg_ota_test.py +87 -0
- brg_certificate/tests/edge_mgmt/leds_test/leds_test.json +13 -0
- brg_certificate/tests/edge_mgmt/leds_test/leds_test.py +210 -0
- brg_certificate/tests/edge_mgmt/ota_test/ota_test.json +13 -0
- brg_certificate/tests/edge_mgmt/ota_test/ota_test.py +83 -0
- brg_certificate/tests/edge_mgmt/stat_test/stat_test.json +13 -0
- brg_certificate/tests/edge_mgmt/stat_test/stat_test.py +48 -0
- brg_certificate/tests/energy2400/duty_cycle_test/duty_cycle_test.json +13 -0
- brg_certificate/tests/energy2400/duty_cycle_test/duty_cycle_test.py +26 -0
- brg_certificate/tests/energy2400/output_power_test/output_power_test.json +13 -0
- brg_certificate/tests/energy2400/output_power_test/output_power_test.py +27 -0
- brg_certificate/tests/energy2400/pattern_test/pattern_test.json +13 -0
- brg_certificate/tests/energy2400/pattern_test/pattern_test.py +28 -0
- brg_certificate/tests/energy2400/signal_indicator_ble5_test/signal_indicator_ble5_test.json +13 -0
- brg_certificate/tests/energy2400/signal_indicator_ble5_test/signal_indicator_ble5_test.py +398 -0
- brg_certificate/tests/energy2400/signal_indicator_sub1g_2_4_test/signal_indicator_sub1g_2_4_test.json +13 -0
- brg_certificate/tests/energy2400/signal_indicator_sub1g_2_4_test/signal_indicator_sub1g_2_4_test.py +153 -0
- brg_certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.json +13 -0
- brg_certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.py +264 -0
- brg_certificate/tests/energy_sub1g/duty_cycle_test/duty_cycle_test.json +13 -0
- brg_certificate/tests/energy_sub1g/duty_cycle_test/duty_cycle_test.py +27 -0
- brg_certificate/tests/energy_sub1g/pattern_test/pattern_test.json +13 -0
- brg_certificate/tests/energy_sub1g/pattern_test/pattern_test.py +26 -0
- brg_certificate/tests/energy_sub1g/signal_indicator_functionality_test/signal_indicator_functionality_test.json +13 -0
- brg_certificate/tests/energy_sub1g/signal_indicator_functionality_test/signal_indicator_functionality_test.py +397 -0
- brg_certificate/tests/energy_sub1g/signal_indicator_test/signal_indicator_test.json +13 -0
- brg_certificate/tests/energy_sub1g/signal_indicator_test/signal_indicator_test.py +27 -0
- brg_certificate/wltPb_pb2.py +72 -0
- brg_certificate/wltPb_pb2.pyi +227 -0
- brg_certificate/wlt_types.py +114 -0
- gw_certificate/api/extended_api.py +7 -1531
- gw_certificate/api_if/200/data.json +106 -0
- gw_certificate/api_if/200/logs.json +12 -0
- gw_certificate/api_if/200/status.json +47 -0
- gw_certificate/api_if/201/data.json +98 -0
- gw_certificate/api_if/201/logs.json +12 -0
- gw_certificate/api_if/201/status.json +53 -0
- gw_certificate/api_if/202/data.json +83 -0
- gw_certificate/api_if/202/logs.json +12 -0
- gw_certificate/api_if/202/status.json +60 -0
- gw_certificate/api_if/203/data.json +85 -0
- gw_certificate/api_if/203/logs.json +12 -0
- gw_certificate/api_if/203/status.json +63 -0
- gw_certificate/api_if/204/data.json +85 -0
- gw_certificate/api_if/204/logs.json +12 -0
- gw_certificate/api_if/204/status.json +63 -0
- gw_certificate/api_if/205/data.json +85 -0
- gw_certificate/api_if/205/logs.json +12 -0
- gw_certificate/api_if/205/status.json +63 -0
- gw_certificate/api_if/api_validation.py +0 -2
- gw_certificate/common/analysis_data_bricks.py +18 -1413
- gw_certificate/common/debug.py +0 -21
- gw_certificate/common/utils.py +1 -212
- gw_certificate/common/utils_defines.py +0 -87
- gw_certificate/gw_certificate.py +9 -7
- gw_certificate/gw_certificate_cli.py +39 -23
- gw_certificate/interface/4.4.52_app.zip +0 -0
- gw_certificate/interface/4.4.52_sd_bl_app.zip +0 -0
- gw_certificate/interface/ble_simulator.py +0 -32
- gw_certificate/interface/if_defines.py +1 -0
- gw_certificate/interface/mqtt.py +96 -19
- gw_certificate/interface/nrfutil-linux +0 -0
- gw_certificate/interface/nrfutil-mac +0 -0
- gw_certificate/interface/nrfutil.exe +0 -0
- gw_certificate/interface/pkt_generator.py +0 -82
- gw_certificate/interface/uart_if.py +73 -43
- gw_certificate/templates/results.html +1 -1
- gw_certificate/tests/__init__.py +1 -2
- gw_certificate/tests/actions.py +134 -9
- gw_certificate/tests/connection.py +10 -5
- gw_certificate/tests/downlink.py +2 -4
- gw_certificate/tests/generic.py +62 -12
- gw_certificate/tests/registration.py +78 -27
- gw_certificate/tests/static/generated_packet_table.py +12 -48
- gw_certificate/tests/static/packet_table.csv +10048 -10048
- gw_certificate/tests/static/references.py +2 -1
- gw_certificate/tests/static/uplink_defines.py +0 -7
- gw_certificate/tests/throughput.py +7 -12
- gw_certificate/tests/uplink.py +83 -43
- {wiliot_certificate-1.3.0a1.dist-info → wiliot_certificate-1.4.0a2.dist-info}/METADATA +59 -8
- wiliot_certificate-1.4.0a2.dist-info/RECORD +198 -0
- {wiliot_certificate-1.3.0a1.dist-info → wiliot_certificate-1.4.0a2.dist-info}/WHEEL +1 -1
- wiliot_certificate-1.4.0a2.dist-info/entry_points.txt +3 -0
- wiliot_certificate-1.4.0a2.dist-info/top_level.txt +2 -0
- gw_certificate/interface/packet_error.py +0 -22
- wiliot_certificate-1.3.0a1.dist-info/RECORD +0 -51
- wiliot_certificate-1.3.0a1.dist-info/entry_points.txt +0 -2
- wiliot_certificate-1.3.0a1.dist-info/top_level.txt +0 -1
- {wiliot_certificate-1.3.0a1.dist-info → wiliot_certificate-1.4.0a2.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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1543
|
-
|
|
1544
|
-
names = []
|
|
1545
|
-
for loc in locations:
|
|
1546
|
-
names.append(loc['name'])
|
|
1547
|
-
return names
|
|
22
|
+
return self._get(path)
|
|
23
|
+
|