Skip to content

Commit 60e64f3

Browse files
committed
[tools] tcat: extend TCAT command list.
Add commands for WiFi connection management. Signed-off-by: Marcin Gasiorek <marcin.gasiorek@nordicsemi.no>
1 parent 47894d4 commit 60e64f3

File tree

3 files changed

+171
-3
lines changed

3 files changed

+171
-3
lines changed

tools/tcat_ble_client/ble/ble_stream_secure.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,14 @@ async def recv(self, buffersize, timeout=1):
103103

104104
self.incoming.write(data)
105105
while True:
106+
decode = []
106107
try:
107-
decode = self.ssl_object.read(4096)
108+
# try decode all available data
109+
while True:
110+
decode.append(self.ssl_object.read(4096))
111+
112+
if self.incoming.pending <= 0:
113+
break
108114
break
109115
# if recv called before entire message was received from the link
110116
except ssl.SSLWantReadError:
@@ -113,6 +119,8 @@ async def recv(self, buffersize, timeout=1):
113119
await asyncio.sleep(0.1)
114120
more = await self.ble_stream.recv(buffersize)
115121
self.incoming.write(more)
122+
if len(decode) == 1:
123+
return decode[0]
116124
return decode
117125

118126
async def send_with_resp(self, bytes):

tools/tcat_ble_client/cli/base_commands.py

+160-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
from tlv.tcat_tlv import TcatTLVType
3636
from cli.command import Command, CommandResultNone, CommandResultTLV
3737
from dataset.dataset import ThreadDataset
38-
from utils import select_device_by_user_input
38+
from utils import select_device_by_user_input, get_int_in_range
3939
from os import path
4040

4141

@@ -163,3 +163,162 @@ async def execute_default(self, args, context):
163163
await ble_sstream.do_handshake(hostname=SERVER_COMMON_NAME)
164164
print('Done')
165165
context['ble_sstream'] = ble_sstream
166+
167+
168+
class TbrWiFiScanSubCmd(Command):
169+
170+
def get_help_string(self) -> str:
171+
return 'Find available Wi-Fi access points and connect to one of them.'
172+
173+
def user_confirmation(self):
174+
while True:
175+
try:
176+
userAnswer = str(input('Do you want to continue? [Y]es/[n]o\n> '))
177+
if userAnswer.lower() == 'y' or userAnswer.lower() == 'yes':
178+
return True
179+
elif userAnswer.lower() == 'n' or userAnswer.lower() == 'no':
180+
return False
181+
else:
182+
print('\nTry again.')
183+
except KeyboardInterrupt:
184+
print('\nInterrupted by user.')
185+
return None
186+
187+
def get_password(self, networkName):
188+
while True:
189+
try:
190+
password = str(input(f'\nPassword for \"{networkName}\"\n> '))
191+
if len(password) > 0:
192+
return password
193+
else:
194+
print('\nThe password is too short. Try again.')
195+
except KeyboardInterrupt:
196+
print('\nInterrupted by user.')
197+
return None
198+
199+
async def execute_default(self, args, context):
200+
bless: BleStreamSecure = context['ble_sstream']
201+
202+
print('Wi-Fi scanning...\n')
203+
cmd = TLV(TcatTLVType.APPLICATION.value, bytes('wifi_scan', 'ascii')).to_bytes()
204+
205+
response = await bless.send_with_resp(cmd)
206+
if not response:
207+
return
208+
209+
tlv_response = TLV.from_bytes(response)
210+
if tlv_response.value.decode("ascii").find("RESP_NOT_SUPP") != -1:
211+
print('Command not supported\n')
212+
return
213+
elif tlv_response.value.decode("ascii").find("RESP_FAIL") != -1:
214+
print('Wi-Fi status get fail\n')
215+
return
216+
217+
wifiNetworks = []
218+
wifiNetworksDictKeys = ["id", "ssid", "chan", "rssi", "security"]
219+
220+
while True:
221+
response = await bless.recv(buffersize=4096, timeout=15)
222+
if not response:
223+
return
224+
for _resp in response:
225+
tlv_response = TLV.from_bytes(_resp)
226+
if tlv_response.value.decode("ascii").find("RESP_FAIL") != -1:
227+
print('Wi-Fi status get fail\n')
228+
break
229+
elif tlv_response.value.decode("ascii").find("RESP_OK") != -1:
230+
break
231+
wifiNetworks.append(dict(zip(wifiNetworksDictKeys, tlv_response.value.decode("ascii").split('|'))))
232+
break
233+
234+
if len(wifiNetworks) > 0:
235+
print('Found Wi-Fi networks:\n')
236+
print(f"{'Num' :<4}| {'SSID' :<30}| {'Chan' :<5}| {'RSSI' :<5}")
237+
for _net in wifiNetworks:
238+
print(f"{_net['id'] :<4}| {_net['ssid'] :<30}| {_net['chan'] :<5}| {_net['rssi'] :<5}")
239+
else:
240+
print('\nNo Wi-Fi networks found.')
241+
return None
242+
243+
print("""\nConnect to Wi-Fi network?\n"""
244+
"""After you continue, the network is saved as default """
245+
"""and TBR automatically connects to this network.""")
246+
conf = self.user_confirmation()
247+
if not conf:
248+
return
249+
250+
print('\nSelect Wi-Fi network number to connect to it')
251+
selected = get_int_in_range(1, len(wifiNetworks))
252+
ssid = wifiNetworks[selected - 1]['ssid']
253+
sec = wifiNetworks[selected - 1]['security']
254+
255+
pwd = self.get_password(ssid)
256+
if pwd is None:
257+
return
258+
259+
print('Store Wi-Fi network...\n')
260+
cmd = TLV(TcatTLVType.APPLICATION.value, bytes('wifi_add ' + ssid + ' ' + pwd + ' ' + sec, 'ascii')).to_bytes()
261+
response = await bless.send_with_resp(cmd)
262+
263+
if not response:
264+
return
265+
266+
tlv_response = TLV.from_bytes(response)
267+
if tlv_response.value.decode("ascii").find("RESP_FAIL") != -1:
268+
print('\nConnection fail\n')
269+
print('Wi-Fi network is saved')
270+
return
271+
272+
273+
class TbrWiFiStatusSubCmd(Command):
274+
275+
def get_help_string(self) -> str:
276+
return 'Get Wi-Fi connection status for TBR.'
277+
278+
async def execute_default(self, args, context):
279+
bless: BleStreamSecure = context['ble_sstream']
280+
281+
print('Wi-Fi status get...\n')
282+
command = TLV(TcatTLVType.APPLICATION.value, bytes('wifi_status', 'ascii')).to_bytes()
283+
await bless.send(command)
284+
285+
wifiStatus = {}
286+
wifiStatusDictKeys = ['state', 'ssid', 'rssi']
287+
288+
while True:
289+
response = await bless.recv(buffersize=4096, timeout=15)
290+
if not response:
291+
return
292+
for _resp in response:
293+
tlv_response = TLV.from_bytes(_resp)
294+
if tlv_response.value.decode("ascii").find("RESP_NOT_SUPP") != -1:
295+
print('Command not supported\n')
296+
break
297+
elif tlv_response.value.decode("ascii").find("RESP_FAIL") != -1:
298+
print('Wi-Fi status get fail\n')
299+
break
300+
elif tlv_response.value.decode("ascii").find("RESP_OK") != -1:
301+
break
302+
wifiStatus = dict(zip(wifiStatusDictKeys, tlv_response.value.decode("ascii").split('|')))
303+
break
304+
305+
if 'state' in wifiStatus:
306+
if wifiStatus['state'] == 'COMPLETED':
307+
print(f'\tWi-Fi connected to: \"{wifiStatus["ssid"]}\" [RSSI: {wifiStatus["rssi"]}]')
308+
else:
309+
print(f'\tWi-Fi {wifiStatus["state"].lower()}')
310+
311+
return
312+
313+
314+
class TbrWiFiCommand(Command):
315+
316+
def __init__(self):
317+
self._subcommands = {'scan': TbrWiFiScanSubCmd(), 'status': TbrWiFiStatusSubCmd()}
318+
319+
def get_help_string(self) -> str:
320+
return 'Manage Wi-Fi network connection for TBR.'
321+
322+
async def execute_default(self, args, context):
323+
print('Invalid usage. Provide a subcommand.')
324+
return CommandResultNone()

tools/tcat_ble_client/cli/cli.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import readline
3030
import shlex
3131
from ble.ble_stream_secure import BleStreamSecure
32-
from cli.base_commands import (HelpCommand, HelloCommand, CommissionCommand, ThreadStateCommand, ScanCommand)
32+
from cli.base_commands import (HelpCommand, HelloCommand, CommissionCommand, ThreadStateCommand, ScanCommand, TbrWiFiCommand)
3333
from cli.dataset_commands import (DatasetCommand)
3434
from dataset.dataset import ThreadDataset
3535
from typing import Optional
@@ -45,6 +45,7 @@ def __init__(self, dataset: ThreadDataset, ble_sstream: Optional[BleStreamSecure
4545
'dataset': DatasetCommand(),
4646
'thread': ThreadStateCommand(),
4747
'scan': ScanCommand(),
48+
'wifi': TbrWiFiCommand(),
4849
}
4950
self._context = {'ble_sstream': ble_sstream, 'dataset': dataset, 'commands': self._commands}
5051
readline.set_completer(self.completer)

0 commit comments

Comments
 (0)