Merge branch 'fix/esp_local_ctrl_example_test' into 'master'

esp_local_ctrl/scripts: Update the script to use async methods

Closes IDFCI-1336

See merge request espressif/esp-idf!18766
This commit is contained in:
Mahavir Jain 2022-07-14 13:13:09 +08:00
commit 3069759486
3 changed files with 65 additions and 89 deletions

View File

@ -1,38 +1,23 @@
#!/usr/bin/env python #!/usr/bin/env python
# #
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD # SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# #
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from __future__ import print_function
import argparse import argparse
import asyncio
import json import json
import os import os
import ssl import ssl
import struct import struct
import sys import sys
import textwrap import textwrap
from builtins import input
import proto_lc import proto_lc
from future.utils import tobytes
try: try:
import esp_prov import esp_prov
import security import security
except ImportError: except ImportError:
idf_path = os.environ['IDF_PATH'] idf_path = os.environ['IDF_PATH']
sys.path.insert(0, idf_path + '/components/protocomm/python') sys.path.insert(0, idf_path + '/components/protocomm/python')
@ -77,7 +62,7 @@ def encode_prop_value(prop, value):
elif prop['type'] == PROP_TYPE_BOOLEAN: elif prop['type'] == PROP_TYPE_BOOLEAN:
return struct.pack('?', value) return struct.pack('?', value)
elif prop['type'] == PROP_TYPE_STRING: elif prop['type'] == PROP_TYPE_STRING:
return tobytes(value) return bytes(value, encoding='latin-1')
return value return value
except struct.error as e: except struct.error as e:
print(e) print(e)
@ -135,7 +120,7 @@ def get_security(secver, pop=None, verbose=False):
return None return None
def get_transport(sel_transport, service_name, check_hostname): async def get_transport(sel_transport, service_name, check_hostname):
try: try:
tp = None tp = None
if (sel_transport == 'http'): if (sel_transport == 'http'):
@ -151,15 +136,16 @@ def get_transport(sel_transport, service_name, check_hostname):
'esp_local_ctrl/session': '0002', 'esp_local_ctrl/session': '0002',
'esp_local_ctrl/control': '0003'} 'esp_local_ctrl/control': '0003'}
) )
await tp.connect(devname=service_name)
return tp return tp
except RuntimeError as e: except RuntimeError as e:
on_except(e) on_except(e)
return None return None
def version_match(tp, protover, verbose=False): async def version_match(tp, protover, verbose=False):
try: try:
response = tp.send_data('proto-ver', protover) response = await tp.send_data('proto-ver', protover)
if verbose: if verbose:
print('proto-ver response : ', response) print('proto-ver response : ', response)
@ -186,11 +172,11 @@ def version_match(tp, protover, verbose=False):
return None return None
def has_capability(tp, capability='none', verbose=False): async def has_capability(tp, capability='none', verbose=False):
# Note : default value of `capability` argument cannot be empty string # Note : default value of `capability` argument cannot be empty string
# because protocomm_httpd expects non zero content lengths # because protocomm_httpd expects non zero content lengths
try: try:
response = tp.send_data('proto-ver', capability) response = await tp.send_data('proto-ver', capability)
if verbose: if verbose:
print('proto-ver response : ', response) print('proto-ver response : ', response)
@ -220,14 +206,14 @@ def has_capability(tp, capability='none', verbose=False):
return False return False
def establish_session(tp, sec): async def establish_session(tp, sec):
try: try:
response = None response = None
while True: while True:
request = sec.security_session(response) request = sec.security_session(response)
if request is None: if request is None:
break break
response = tp.send_data('esp_local_ctrl/session', request) response = await tp.send_data('esp_local_ctrl/session', request)
if (response is None): if (response is None):
return False return False
return True return True
@ -236,17 +222,17 @@ def establish_session(tp, sec):
return None return None
def get_all_property_values(tp, security_ctx): async def get_all_property_values(tp, security_ctx):
try: try:
props = [] props = []
message = proto_lc.get_prop_count_request(security_ctx) message = proto_lc.get_prop_count_request(security_ctx)
response = tp.send_data('esp_local_ctrl/control', message) response = await tp.send_data('esp_local_ctrl/control', message)
count = proto_lc.get_prop_count_response(security_ctx, response) count = proto_lc.get_prop_count_response(security_ctx, response)
if count == 0: if count == 0:
raise RuntimeError('No properties found!') raise RuntimeError('No properties found!')
indices = [i for i in range(count)] indices = [i for i in range(count)]
message = proto_lc.get_prop_vals_request(security_ctx, indices) message = proto_lc.get_prop_vals_request(security_ctx, indices)
response = tp.send_data('esp_local_ctrl/control', message) response = await tp.send_data('esp_local_ctrl/control', message)
props = proto_lc.get_prop_vals_response(security_ctx, response) props = proto_lc.get_prop_vals_response(security_ctx, response)
if len(props) != count: if len(props) != count:
raise RuntimeError('Incorrect count of properties!', len(props), count) raise RuntimeError('Incorrect count of properties!', len(props), count)
@ -258,14 +244,14 @@ def get_all_property_values(tp, security_ctx):
return [] return []
def set_property_values(tp, security_ctx, props, indices, values, check_readonly=False): async def set_property_values(tp, security_ctx, props, indices, values, check_readonly=False):
try: try:
if check_readonly: if check_readonly:
for index in indices: for index in indices:
if prop_is_readonly(props[index]): if prop_is_readonly(props[index]):
raise RuntimeError('Cannot set value of Read-Only property') raise RuntimeError('Cannot set value of Read-Only property')
message = proto_lc.set_prop_vals_request(security_ctx, indices, values) message = proto_lc.set_prop_vals_request(security_ctx, indices, values)
response = tp.send_data('esp_local_ctrl/control', message) response = await tp.send_data('esp_local_ctrl/control', message)
return proto_lc.set_prop_vals_response(security_ctx, response) return proto_lc.set_prop_vals_response(security_ctx, response)
except RuntimeError as e: except RuntimeError as e:
on_except(e) on_except(e)
@ -279,7 +265,7 @@ def desc_format(*args):
return desc return desc
if __name__ == '__main__': async def main():
parser = argparse.ArgumentParser(add_help=False) parser = argparse.ArgumentParser(add_help=False)
parser = argparse.ArgumentParser(description='Control an ESP32 running esp_local_ctrl service') parser = argparse.ArgumentParser(description='Control an ESP32 running esp_local_ctrl service')
@ -315,34 +301,33 @@ if __name__ == '__main__':
help=argparse.SUPPRESS) help=argparse.SUPPRESS)
parser.add_argument('-v', '--verbose', dest='verbose', help='increase output verbosity', action='store_true') parser.add_argument('-v', '--verbose', dest='verbose', help='increase output verbosity', action='store_true')
args = parser.parse_args() args = parser.parse_args()
if args.version != '': if args.version != '':
print('==== Esp_Ctrl Version: ' + args.version + ' ====') print(f'==== Esp_Ctrl Version: {args.version} ====')
if args.service_name == '': if args.service_name == '':
args.service_name = 'my_esp_ctrl_device' args.service_name = 'my_esp_ctrl_device'
if args.transport == 'http': if args.transport == 'http':
args.service_name += '.local' args.service_name += '.local'
obj_transport = get_transport(args.transport, args.service_name, not args.dont_check_hostname) obj_transport = await get_transport(args.transport, args.service_name, not args.dont_check_hostname)
if obj_transport is None: if obj_transport is None:
print('---- Invalid transport ----') raise RuntimeError('Failed to establish connection')
exit(1)
# If security version not specified check in capabilities # If security version not specified check in capabilities
if args.secver is None: if args.secver is None:
# First check if capabilities are supported or not # First check if capabilities are supported or not
if not has_capability(obj_transport): if not await has_capability(obj_transport):
print('Security capabilities could not be determined. Please specify \'--sec_ver\' explicitly') print('Security capabilities could not be determined, please specify "--sec_ver" explicitly')
print('---- Invalid Security Version ----') raise ValueError('Invalid Security Version')
exit(2)
# When no_sec is present, use security 0, else security 1 # When no_sec is present, use security 0, else security 1
args.secver = int(not has_capability(obj_transport, 'no_sec')) args.secver = int(not await has_capability(obj_transport, 'no_sec'))
print('Security scheme determined to be :', args.secver) print(f'==== Security Scheme: {args.secver} ====')
if (args.secver != 0) and not has_capability(obj_transport, 'no_pop'): if (args.secver != 0) and not await has_capability(obj_transport, 'no_pop'):
if len(args.pop) == 0: if len(args.pop) == 0:
print('---- Proof of Possession argument not provided ----') print('---- Proof of Possession argument not provided ----')
exit(2) exit(2)
@ -350,30 +335,26 @@ if __name__ == '__main__':
print('---- Proof of Possession will be ignored ----') print('---- Proof of Possession will be ignored ----')
args.pop = '' args.pop = ''
obj_security = get_security(args.secver, args.pop, False) obj_security = get_security(args.secver, args.pop, args.verbose)
if obj_security is None: if obj_security is None:
print('---- Invalid Security Version ----') raise ValueError('Invalid Security Version')
exit(2)
if args.version != '': if args.version != '':
print('\n==== Verifying protocol version ====') print('\n==== Verifying protocol version ====')
if not version_match(obj_transport, args.version, args.verbose): if not await version_match(obj_transport, args.version, args.verbose):
print('---- Error in protocol version matching ----') raise RuntimeError('Error in protocol version matching')
exit(2)
print('==== Verified protocol version successfully ====') print('==== Verified protocol version successfully ====')
print('\n==== Starting Session ====') print('\n==== Starting Session ====')
if not establish_session(obj_transport, obj_security): if not await establish_session(obj_transport, obj_security):
print('Failed to establish session. Ensure that security scheme and proof of possession are correct') print('Failed to establish session. Ensure that security scheme and proof of possession are correct')
print('---- Error in establishing session ----') raise RuntimeError('Error in establishing session')
exit(3)
print('==== Session Established ====') print('==== Session Established ====')
while True: while True:
properties = get_all_property_values(obj_transport, obj_security) properties = await get_all_property_values(obj_transport, obj_security)
if len(properties) == 0: if len(properties) == 0:
print('---- Error in reading property values ----') raise RuntimeError('Error in reading property value')
exit(4)
print('\n==== Available Properties ====') print('\n==== Available Properties ====')
print('{0: >4} {1: <16} {2: <10} {3: <16} {4: <16}'.format( print('{0: >4} {1: <16} {2: <10} {3: <16} {4: <16}'.format(
@ -390,7 +371,7 @@ if __name__ == '__main__':
inval = input('\nSelect properties to set (0 to re-read, \'q\' to quit) : ') inval = input('\nSelect properties to set (0 to re-read, \'q\' to quit) : ')
if inval.lower() == 'q': if inval.lower() == 'q':
print('Quitting...') print('Quitting...')
exit(5) exit(0)
invals = inval.split(',') invals = inval.split(',')
selections = [int(val) for val in invals] selections = [int(val) for val in invals]
if min(selections) < 0 or max(selections) > len(properties): if min(selections) < 0 or max(selections) > len(properties):
@ -416,5 +397,8 @@ if __name__ == '__main__':
set_values += [value] set_values += [value]
set_indices += [select - 1] set_indices += [select - 1]
if not set_property_values(obj_transport, obj_security, properties, set_indices, set_values): if not await set_property_values(obj_transport, obj_security, properties, set_indices, set_values):
print('Failed to set values!') print('Failed to set values!')
if __name__ == '__main__':
asyncio.run(main())

View File

@ -1,34 +1,24 @@
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD # SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD
# # SPDX-License-Identifier: Apache-2.0
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# #
import importlib.util
from __future__ import print_function
import os import os
import sys
from future.utils import tobytes from importlib.abc import Loader
from typing import Any
def _load_source(name, path): def _load_source(name: str, path: str) -> Any:
try: spec = importlib.util.spec_from_file_location(name, path)
from importlib.machinery import SourceFileLoader if not spec:
return SourceFileLoader(name, path).load_module() return None
except ImportError:
# importlib.machinery doesn't exists in Python 2 so we will use imp (deprecated in Python 3) module = importlib.util.module_from_spec(spec)
import imp sys.modules[spec.name] = module
return imp.load_source(name, path) assert isinstance(spec.loader, Loader)
spec.loader.exec_module(module)
return module
idf_path = os.environ['IDF_PATH'] idf_path = os.environ['IDF_PATH']
@ -36,6 +26,10 @@ constants_pb2 = _load_source('constants_pb2', idf_path + '/components/protocomm/
local_ctrl_pb2 = _load_source('esp_local_ctrl_pb2', idf_path + '/components/esp_local_ctrl/python/esp_local_ctrl_pb2.py') local_ctrl_pb2 = _load_source('esp_local_ctrl_pb2', idf_path + '/components/esp_local_ctrl/python/esp_local_ctrl_pb2.py')
def to_bytes(s: str) -> bytes:
return bytes(s, encoding='latin-1')
def get_prop_count_request(security_ctx): def get_prop_count_request(security_ctx):
req = local_ctrl_pb2.LocalCtrlMessage() req = local_ctrl_pb2.LocalCtrlMessage()
req.msg = local_ctrl_pb2.TypeCmdGetPropertyCount req.msg = local_ctrl_pb2.TypeCmdGetPropertyCount
@ -46,7 +40,7 @@ def get_prop_count_request(security_ctx):
def get_prop_count_response(security_ctx, response_data): def get_prop_count_response(security_ctx, response_data):
decrypt = security_ctx.decrypt_data(tobytes(response_data)) decrypt = security_ctx.decrypt_data(to_bytes(response_data))
resp = local_ctrl_pb2.LocalCtrlMessage() resp = local_ctrl_pb2.LocalCtrlMessage()
resp.ParseFromString(decrypt) resp.ParseFromString(decrypt)
if (resp.resp_get_prop_count.status == 0): if (resp.resp_get_prop_count.status == 0):
@ -66,7 +60,7 @@ def get_prop_vals_request(security_ctx, indices):
def get_prop_vals_response(security_ctx, response_data): def get_prop_vals_response(security_ctx, response_data):
decrypt = security_ctx.decrypt_data(tobytes(response_data)) decrypt = security_ctx.decrypt_data(to_bytes(response_data))
resp = local_ctrl_pb2.LocalCtrlMessage() resp = local_ctrl_pb2.LocalCtrlMessage()
resp.ParseFromString(decrypt) resp.ParseFromString(decrypt)
results = [] results = []
@ -76,7 +70,7 @@ def get_prop_vals_response(security_ctx, response_data):
'name': prop.name, 'name': prop.name,
'type': prop.type, 'type': prop.type,
'flags': prop.flags, 'flags': prop.flags,
'value': tobytes(prop.value) 'value': prop.value
}] }]
return results return results
@ -95,7 +89,7 @@ def set_prop_vals_request(security_ctx, indices, values):
def set_prop_vals_response(security_ctx, response_data): def set_prop_vals_response(security_ctx, response_data):
decrypt = security_ctx.decrypt_data(tobytes(response_data)) decrypt = security_ctx.decrypt_data(to_bytes(response_data))
resp = local_ctrl_pb2.LocalCtrlMessage() resp = local_ctrl_pb2.LocalCtrlMessage()
resp.ParseFromString(decrypt) resp.ParseFromString(decrypt)
return (resp.resp_set_prop_vals.status == 0) return (resp.resp_set_prop_vals.status == 0)

View File

@ -1876,8 +1876,6 @@ examples/protocols/esp_http_client/main/esp_http_client_example.c
examples/protocols/esp_local_ctrl/example_test.py examples/protocols/esp_local_ctrl/example_test.py
examples/protocols/esp_local_ctrl/main/app_main.c examples/protocols/esp_local_ctrl/main/app_main.c
examples/protocols/esp_local_ctrl/main/esp_local_ctrl_service.c examples/protocols/esp_local_ctrl/main/esp_local_ctrl_service.c
examples/protocols/esp_local_ctrl/scripts/esp_local_ctrl.py
examples/protocols/esp_local_ctrl/scripts/proto_lc.py
examples/protocols/http2_request/main/http2_request_example_main.c examples/protocols/http2_request/main/http2_request_example_main.c
examples/protocols/http_request/main/http_request_example_main.c examples/protocols/http_request/main/http_request_example_main.c
examples/protocols/http_server/advanced_tests/http_server_advanced_test.py examples/protocols/http_server/advanced_tests/http_server_advanced_test.py