diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..594e11c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,81 @@
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+*.pyc
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
+
+# emacs
+.dir-locals.el
+
+# emacs temp file suffixes
+*~
+.#*
+\#*#
+
+# eclipse setting
+.settings
+
+# MacOS directory files
+.DS_Store
+
+# Application project files
+**/sdkconfig
+**/sdkconfig.old
+**/build
+**/cloud_cfg/*.key
+**/cloud_cfg/*.crt
+**/cloud_cfg/*.info
+
+# node_modules
+docs/docusaurus/website/node_modules
+
+# cli logs
+**/logs
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..c08408b
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,69 @@
+stages:
+ - build
+
+variables:
+ BATCH_BUILD: "1"
+ V: "0"
+ MAKEFLAGS: "-j5 --no-keep-going"
+ IDF_PATH: "$CI_PROJECT_DIR/esp-idf"
+ IDF_CI_BUILD: "1"
+
+build_demo:
+ stage: build
+ image: $CI_DOCKER_REGISTRY/esp32-ci-env
+ tags:
+ - build
+ artifacts:
+ paths:
+ - examples/gpio/build/gpio.bin
+ - examples/switch/build/switch.bin
+ - examples/led_light/build/led_light.bin
+ - examples/fan/build/fan.bin
+ - examples/temperature_sensor/build/temperature_sensor.bin
+ - examples/multi_device/build/multi_device.bin
+ expire_in: 6 mos
+
+ script:
+ # add gitlab ssh key
+ - mkdir -p ~/.ssh
+ - chmod 700 ~/.ssh
+ - echo -n $GITLAB_KEY > ~/.ssh/id_rsa_base64
+ - base64 --decode --ignore-garbage ~/.ssh/id_rsa_base64 > ~/.ssh/id_rsa
+ - chmod 600 ~/.ssh/id_rsa
+ - echo -e "Host gitlab.espressif.cn\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
+ - git --version
+ - git clone --branch master --depth 1 $GITLAB_SSH_SERVER/idf/esp-idf.git
+ - cd esp-idf
+ - export IDF_PATH=$PWD
+ - ./install.sh
+ - . export.sh
+ - cd ..
+ - cd examples/gpio
+ - idf.py build
+ - rm -rf build/ sdkconfig
+ - make -j8
+ - cd ../switch
+ - idf.py build
+ - rm -rf build/ sdkconfig
+ - make defconfig
+ - make -j8
+ - cd ../led_light
+ - idf.py build
+ - rm -rf build/ sdkconfig
+ - make defconfig
+ - make -j8
+ - cd ../fan
+ - idf.py build
+ - rm -rf build/ sdkconfig
+ - make defconfig
+ - make -j8
+ - cd ../temperature_sensor
+ - idf.py build
+ - rm -rf build/ sdkconfig
+ - make defconfig
+ - make -j8
+ - cd ../multi_device
+ - idf.py build
+ - rm -rf build/ sdkconfig
+ - make defconfig
+ - make -j8
diff --git a/.readthedocs.yml b/.readthedocs.yml
new file mode 100644
index 0000000..f63ddd7
--- /dev/null
+++ b/.readthedocs.yml
@@ -0,0 +1,17 @@
+# .readthedocs.yml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Optionally build your docs in additional formats such as PDF and ePub
+formats:
+ - htmlzip
+ - pdf
+
+# Optionally set the version of Python and requirements required to build your docs
+python:
+ version: 3.7
+ install:
+ - requirements: docs/requirements.txt
diff --git a/README.md b/README.md
index 3441142..a2916a6 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,37 @@
-# ESP RainMaker
+# ESP RainMaker (Beta)
-Coming Soon!
+## Introduction
+
+ESP RainMaker is an end-to-end solution offered by Espressif to enable remote control and monitoring for ESP32-S2 and ESP32 based products without any configuration required in the Cloud. The primary components of this solution are:
+
+- Claiming Service (to get the Cloud connectivity credentials)
+- RainMaker Agent (i.e. this repo, to develop the firmware)
+- RainMaker Cloud (backend, offering remote connectivity)
+- RainMaker Phone App/CLI (Client utilities for remote access)
+
+
+The key features of ESP RainMaker are:
+
+1. Ability to define own devices and parameters, of any type, in the firmware.
+2. Zero configuration required on the Cloud.
+3. Phone apps that dynamically render the UI as per the device information.
+
+## Phone Apps
+
+### Android
+
+- [Google PlayStore](https://play.google.com/store/apps/details?id=com.espressif.rainmaker)
+- [Direct APK](https://github.com/espressif/esp-rainmaker/wiki)
+
+### iOS
+- [Apple App Store](https://apps.apple.com/app/esp-rainmaker/id1497491540)
+
+## Documentation
+
+Please check the ESP RainMaker documentation [here](http://rainmaker.espressif.com/docs/get-started.html) to get started.
+
+Each example has its own README with additional information about using the example.
+
+### API Documentation Build Status
+
+[](https://docs.espressif.com/projects/esp-rainmaker/en/latest/)
diff --git a/cli/html/welcome.html b/cli/html/welcome.html
new file mode 100644
index 0000000..c7bf4e6
--- /dev/null
+++ b/cli/html/welcome.html
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rainmaker Login
+
+
+
+
+
+
+
+
+
+
+
+
+

+
You have logged into ESP RainMaker.
+
You can now close this window and continue with the CLI, or check out the
+ Documentation.
+
+ Redirecting to ESP RainMaker Documentation in
+ seconds.
+
+
+
+
+
+
+
+
diff --git a/cli/rainmaker.py b/cli/rainmaker.py
new file mode 100755
index 0000000..f9334d8
--- /dev/null
+++ b/cli/rainmaker.py
@@ -0,0 +1,148 @@
+#!/usr/bin/env python3
+#
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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 argparse
+from rmaker_cmd.node import get_nodes, get_node_config, get_node_status,\
+ set_params, get_params, remove_node, get_mqtt_host
+from rmaker_cmd.user import signup, login, forgot_password
+from rmaker_cmd.provision import provision
+from rmaker_cmd.test import test
+from rmaker_lib.logger import log
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.set_defaults(func=None)
+ subparsers = parser.add_subparsers(help='Functions')
+
+ signup_parser = subparsers.add_parser("signup",
+ help="Sign up for ESP Rainmaker")
+ signup_parser.add_argument('email',
+ type=str,
+ metavar='',
+ help='Email address of the user')
+ signup_parser.set_defaults(func=signup)
+
+ login_parser = subparsers.add_parser("login",
+ help="Login to ESP Rainmaker")
+ login_parser.add_argument('--email',
+ type=str,
+ help='Email address of the user')
+ login_parser.set_defaults(func=login)
+
+ forgot_password_parser = subparsers.add_parser("forgotpassword",
+ help="Reset the password")
+ forgot_password_parser.add_argument('email',
+ type=str,
+ metavar='',
+ help='Email address of the user')
+ forgot_password_parser.set_defaults(func=forgot_password)
+
+ getnodes_parser = subparsers.add_parser('getnodes',
+ help='List all nodes associated\
+ with the user')
+ getnodes_parser.set_defaults(func=get_nodes)
+
+ getnodeconfig_parser = subparsers.add_parser('getnodeconfig',
+ help='Get node configuration')
+ getnodeconfig_parser.add_argument('nodeid',
+ type=str,
+ metavar='',
+ help='Node ID for the node')
+ getnodeconfig_parser.set_defaults(func=get_node_config)
+
+ getnodestatus_parser = subparsers.add_parser('getnodestatus',
+ help='Get online/offline\
+ status of the node')
+ getnodestatus_parser.add_argument('nodeid',
+ type=str,
+ metavar='',
+ help='Node ID for the node')
+ getnodestatus_parser.set_defaults(func=get_node_status)
+
+ setparams_parser = subparsers.add_parser('setparams',
+ help='Set node parameters.\
+ Note: Enter JSON data in\
+ single quotes')
+ setparams_parser.add_argument('nodeid',
+ metavar='',
+ help='Node ID for the node')
+ setparams_parser = setparams_parser.add_mutually_exclusive_group(
+ required=True)
+ setparams_parser.add_argument('--filepath',
+ help='Path of the JSON file\
+ containing parameters to be set')
+ setparams_parser.add_argument('--data',
+ help='JSON data containing parameters\
+ to be set. Note: Enter JSON data\
+ in single quotes')
+ setparams_parser.set_defaults(func=set_params)
+
+ getparams_parser = subparsers.add_parser('getparams',
+ help='Get node parameters')
+ getparams_parser.add_argument('nodeid',
+ type=str,
+ metavar='',
+ help='Node ID for the node')
+ getparams_parser.set_defaults(func=get_params)
+
+ remove_node_parser = subparsers.add_parser('removenode',
+ help='Remove user node mapping')
+ remove_node_parser.add_argument('nodeid',
+ type=str,
+ metavar='',
+ help='Node ID for the node')
+ remove_node_parser.set_defaults(func=remove_node)
+
+ provision_parser = subparsers.add_parser('provision',
+ help='Provision the node\
+ to join Wi-Fi network')
+ provision_parser.add_argument('pop',
+ type=str,
+ metavar='',
+ help='Proof of possesion for the node')
+ provision_parser.set_defaults(func=provision)
+
+ getmqtthost_parser = subparsers.add_parser('getmqtthost',
+ help='Get the MQTT Host URL\
+ to be used in the\
+ firmware')
+ getmqtthost_parser.set_defaults(func=get_mqtt_host)
+
+ test_parser = subparsers.add_parser('test',
+ help='Test commands to check\
+ user node mapping')
+ test_parser.add_argument('--addnode',
+ metavar='',
+ help='Add user node mapping')
+ test_parser.set_defaults(func=test)
+
+ args = parser.parse_args()
+
+ if args.func is not None:
+ try:
+ args.func(vars=vars(args))
+ except KeyboardInterrupt:
+ log.debug('KeyboardInterrupt occurred. Login session is aborted.')
+ print("\nExiting...")
+ except Exception as err:
+ log.error(err)
+ else:
+ parser.print_help()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/cli/requirements.txt b/cli/requirements.txt
new file mode 100644
index 0000000..3f8947a
--- /dev/null
+++ b/cli/requirements.txt
@@ -0,0 +1,12 @@
+argparse
+requests>=2.22.0
+cmd2>=0.9.18
+protobuf>=3.10.0
+packaging>=19.2
+oauth2client>=4.1.3
+click>=5.0
+pyserial>=3.0
+future>=0.15.2
+cryptography==2.4.2
+pyparsing>=2.0.3,<2.4.0
+pyelftools>=0.22
\ No newline at end of file
diff --git a/cli/rmaker_cmd/__init__.py b/cli/rmaker_cmd/__init__.py
new file mode 100644
index 0000000..3b0d78c
--- /dev/null
+++ b/cli/rmaker_cmd/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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.
diff --git a/cli/rmaker_cmd/browserlogin.py b/cli/rmaker_cmd/browserlogin.py
new file mode 100644
index 0000000..ebc8e1a
--- /dev/null
+++ b/cli/rmaker_cmd/browserlogin.py
@@ -0,0 +1,211 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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 webbrowser
+import socket
+import requests
+from oauth2client import _helpers
+from six.moves import BaseHTTPServer, http_client, urllib
+import os
+import sys
+
+try:
+ from rmaker_lib import serverconfig, configmanager
+ from rmaker_lib.exceptions import SSLError, NetworkError
+ from rmaker_lib.logger import log
+except ImportError as err:
+ print("Failed to import ESP Rainmaker library. " + str(err))
+ raise err
+
+
+class HttpdServer(BaseHTTPServer.HTTPServer):
+ """
+ A server to handle requests on localhost.
+
+ Waits for a single request and parses the query parameters
+ and then stops serving.
+ """
+ query_params = {}
+
+
+class HttpdRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ """
+ A HTTP handler of requests on localhost.
+ """
+
+ def do_GET(self):
+ """
+ Handle a GET request and
+ writes the ESP Rainmaker Welcome HTML page(response)
+ back to HTTP Client
+
+ :raises Exception: If there is any File Handling Issue
+
+ :return: None on Success and Failure
+ :rtype: None
+ """
+ log.debug('Loading the welcome page after successful login.')
+ self.send_response(http_client.OK)
+ self.send_header('Content-type', 'text/html')
+ self.send_header('Access-Control-Allow-Origin', '*')
+ self.end_headers()
+ parts = urllib.parse.urlparse(self.path)
+ query = _helpers.parse_unique_urlencoded(parts.query)
+ self.server.query_params = query
+ index_file = os.path.join(os.path.expanduser('.'), 'html/welcome.html')
+
+ try:
+ with open(index_file, 'rb') as home_page:
+ self.wfile.write(home_page.read())
+ except Exception as file_err:
+ log.error(file_err)
+ sys.exit(1)
+
+ def log_message(self, format, *args):
+ """
+ Do not log messages to the command prompt.
+ """
+
+
+def get_free_port():
+ """
+ Get Free port
+
+ :return: port on Success
+ :rtype: int
+ """
+ tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ tcp.bind(('', 0))
+ addr, port = tcp.getsockname()
+ tcp.close()
+ return port
+
+
+def browser_login():
+ """
+ Opens browser with login url using Httpd Server.
+
+ :raises Exception: If there is an HTTP issue while
+ logging in through browser
+
+ :return: None on Success and Failure
+ :rtype: None
+ """
+ log.info('Logging in through browser')
+ server_instance = None
+ for attempt in range(10):
+ try:
+ port = get_free_port()
+ server_instance = HttpdServer(('localhost', port),
+ HttpdRequestHandler)
+ # Added timeout to handle keyboard interrupts for browser login.
+ server_instance.timeout = 0.5
+ break
+ except socket.error as err:
+ log.warn('Error %s. Port %s is not available.'
+ 'Trying with next port.', err, port)
+
+ if server_instance is None:
+ log.error('Error: Could not launch local webserver.'
+ 'Use --email option instead.')
+ return
+
+ url = serverconfig.LOGIN_URL + str(port) +\
+ '&host_url=' + serverconfig.HOST + 'login' +\
+ '&github_url=' + serverconfig.EXTERNAL_LOGIN_URL +\
+ str(port)
+
+ print('Opening browser window for login...')
+ open_status = webbrowser.open(url)
+ if open_status is False:
+ log.error('Failed to open login page. Please try again.')
+ return
+ else:
+ print('Use the browser for login. Press ctrl+C to abort.')
+ log.debug('Web browser opened. Waiting for user login.')
+ try:
+ while True:
+ server_instance.handle_request()
+ if 'error' in server_instance.query_params:
+ log.error('Authentication Error: "%s". Description: "%s" ' +
+ server_instance.query_params['error'] +
+ server_instance.query_params.ge('error_description'))
+ return
+ if 'code' in server_instance.query_params:
+ log.debug('Login successful. Received authorization code.')
+ code = server_instance.query_params['code']
+ get_tokens(code)
+ print('Login successful')
+ return
+
+ if 'id_token' in server_instance.query_params and \
+ 'refresh_token' in server_instance.query_params:
+ log.debug('Login successful.'
+ 'Received idtoken and refresh token.')
+ config_data = {}
+ config_data['idtoken'] = server_instance.query_params[
+ 'id_token'
+ ]
+ config_data['refreshtoken'] = server_instance.query_params[
+ 'refresh_token'
+ ]
+ config_data['accesstoken'] = server_instance.query_params[
+ 'access_token'
+ ]
+ configmanager.Config().set_config(config_data)
+ print('Login successful')
+ return
+ except Exception as browser_login_err:
+ log.error(browser_login_err)
+
+
+def get_tokens(code):
+ """
+ Get access token and set the config file after successful browser login.
+
+ :raises Exception: If there is an HTTP issue in getting access token
+ :raises SystemExit: If Exception is raised
+
+ :return: None on Success and Failure
+ :rtype: None
+ """
+ log.info('Getting access tokens using authorization code.')
+ client_id = serverconfig.CLIENT_ID
+ request_data = 'grant_type=authorization_code&client_id=' + client_id +\
+ '&code=' + code + '&redirect_uri=' +\
+ serverconfig.REDIRECT_URL
+
+ request_header = {'content-type': 'application/x-www-form-urlencoded'}
+ try:
+ response = requests.post(url=serverconfig.TOKEN_URL,
+ data=request_data,
+ headers=request_header,
+ verify=configmanager.CERT_FILE)
+ response.raise_for_status()
+ except requests.exceptions.SSLError:
+ raise SSLError
+ except requests.exceptions.ConnectionError:
+ raise NetworkError
+ except Exception as get_token_err:
+ log.error(get_token_err)
+ sys.exit(1)
+ else:
+ config_data = {}
+ result = response.json()
+ config_data['idtoken'] = result['id_token']
+ config_data['refreshtoken'] = result['refresh_token']
+ config_data['accesstoken'] = result['access_token']
+ log.debug('Received access tokens using authorization code.')
+ configmanager.Config().set_config(config_data)
+ return
diff --git a/cli/rmaker_cmd/node.py b/cli/rmaker_cmd/node.py
new file mode 100644
index 0000000..1b232b3
--- /dev/null
+++ b/cli/rmaker_cmd/node.py
@@ -0,0 +1,252 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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 json
+import re
+import requests
+from pathlib import Path
+
+try:
+ from rmaker_lib import session, node, serverconfig, configmanager
+ from rmaker_lib.exceptions import NetworkError, InvalidJSONError, SSLError
+ from rmaker_lib.logger import log
+except ImportError as err:
+ print("Failed to import ESP Rainmaker library. " + str(err))
+ raise err
+
+
+def get_nodes(vars=None):
+ """
+ List all nodes associated with the user.
+
+ :param vars: No Parameters passed, defaults to `None`
+ :type vars: dict | None
+
+ :raises Exception: If there is an HTTP issue while getting nodes
+
+ :return: None on Success
+ :rtype: None
+ """
+ try:
+ s = session.Session()
+ nodes = s.get_nodes()
+ except Exception as get_nodes_err:
+ log.error(get_nodes_err)
+ else:
+ if len(nodes.keys()) == 0:
+ print('User is not associated with any nodes.')
+ return
+ for key in nodes.keys():
+ print(nodes[key].get_nodeid())
+ return
+
+
+def get_node_config(vars=None):
+ """
+ Shows the configuration of the node.
+
+ :param vars: `nodeid` as key - Node ID for the node, defaults to `None`
+ :type vars: dict | None
+
+ :raises Exception: If there is an HTTP issue while getting node config
+
+ :return: None on Success
+ :rtype: None
+ """
+ try:
+ n = node.Node(vars['nodeid'], session.Session())
+ node_config = n.get_node_config()
+ except Exception as get_nodes_err:
+ log.error(get_nodes_err)
+ else:
+ print(json.dumps(node_config, indent=4))
+ return
+
+
+def get_node_status(vars=None):
+ """
+ Shows the online/offline status of the node.
+
+ :param vars: `nodeid` as key - Node ID for the node, defaults to `None`
+ :type vars: dict | None
+
+ :raises Exception: If there is an HTTP issue while getting node status
+
+ :return: None on Success
+ :rtype: None
+ """
+ try:
+ n = node.Node(vars['nodeid'], session.Session())
+ node_status = n.get_node_status()
+ except Exception as get_node_status_err:
+ log.error(get_node_status_err)
+ else:
+ print(json.dumps(node_status, indent=4))
+ return
+
+
+def set_params(vars=None):
+ """
+ Set parameters of the node.
+
+ :param vars:
+ `nodeid` as key - Node ID for the node,\n
+ `data` as key - JSON data containing parameters to be set `or`\n
+ `filepath` as key - Path of the JSON file containing parameters
+ to be set,\n
+ defaults to `None`
+ :type vars: dict | None
+
+ :raises Exception: If there is an HTTP issue while setting params or
+ JSON format issue in HTTP response
+
+ :return: None on Success
+ :rtype: None
+ """
+ log.info('Setting params of the node with nodeid : ' + vars['nodeid'])
+ data = vars['data']
+ filepath = vars['filepath']
+
+ if data is not None:
+ log.debug('Setting node parameters using JSON data.')
+ # Trimming white spaces except the ones between two strings
+ data = re.sub(r"(? 0:
+ try:
+ # If session is expired then to initialise the new session
+ # internet connection is required.
+ node_object = node.Node(node_id, session.Session())
+ except SSLError:
+ log.error(SSLError())
+ print(PROVISION_FAILURE_MSG)
+ return
+ except NetworkError:
+ time.sleep(5)
+ log.warn("Session is expired. Initialising new session.")
+ pass
+ except Exception as node_init_err:
+ log.error(node_init_err)
+ print(PROVISION_FAILURE_MSG)
+ return
+ else:
+ break
+ retries -= 1
+
+ if node_object is None:
+ print('Please check the internet connectivity.')
+ print(PROVISION_FAILURE_MSG)
+ return
+ retries = MAX_HTTP_CONNECTION_RETRIES
+ request_id = None
+ while retries > 0:
+ try:
+ log.debug('Adding user-node association.')
+ request_id = node_object.add_user_node_mapping(secret_key)
+ except SSLError:
+ log.error(SSLError())
+ print(PROVISION_FAILURE_MSG)
+ return
+ except Exception as user_node_mapping_err:
+ print('Sending User-Node association request to '
+ 'ESP RainMaker Cloud - Failed\nRetrying...')
+ log.warn(user_node_mapping_err)
+ pass
+ else:
+ if request_id is not None:
+ log.debug('User-node mapping added successfully'
+ 'with request_id'
+ + request_id)
+ break
+ time.sleep(5)
+ retries -= 1
+
+ if request_id is None:
+ print('Sending User-Node association request to'
+ 'ESP RainMaker Cloud - Failed')
+ print(PROVISION_FAILURE_MSG)
+ return
+ print('Sending User-Node association request to'
+ 'ESP RainMaker Cloud - Successful')
+
+ status = None
+ while True:
+ log.debug('Checking user-node association status.')
+ try:
+ status = node_object.get_mapping_status(request_id)
+ except SSLError:
+ log.error(SSLError())
+ print(PROVISION_FAILURE_MSG)
+ return
+ except Exception as mapping_status_err:
+ log.warn(mapping_status_err)
+ pass
+ else:
+ log.debug('User-node association status ' + status)
+ if status == 'requested':
+ print('Checking User Node association status -'
+ 'Requested\nRetrying...')
+ elif status == 'confirmed':
+ print('Checking User Node association status - Confirmed')
+ print('Provisioning was Successful.')
+ return
+ elif status == 'timedout':
+ print('Checking User Node association status - Timeout')
+ print(PROVISION_FAILURE_MSG)
+ return
+ elif status == 'discarded':
+ print('Checking User Node association status - Discarded')
+ print(PROVISION_FAILURE_MSG)
+ return
+ time.sleep(5)
+
+ if status is None:
+ print(PROVISION_FAILURE_MSG)
+ print('Checking User Node association status failed. '
+ 'Please check the internet connectivity.')
+ return
+ return
diff --git a/cli/rmaker_cmd/test.py b/cli/rmaker_cmd/test.py
new file mode 100644
index 0000000..1ccd1b8
--- /dev/null
+++ b/cli/rmaker_cmd/test.py
@@ -0,0 +1,85 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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 uuid
+import time
+import sys
+
+try:
+ from rmaker_lib import session, node, configmanager
+ from rmaker_lib.logger import log
+except ImportError as err:
+ print("Failed to import ESP Rainmaker library. " + str(err))
+ raise err
+
+
+def add_node(node_object):
+ secret_key = str(uuid.uuid4())
+ request_id = node_object.add_user_node_mapping(secret_key)
+ return request_id, secret_key
+
+
+def check_status(node_object, request_id):
+ status = None
+ while True:
+ log.debug('Checking user-node association status.')
+ try:
+ status = node_object.get_mapping_status(request_id)
+ except Exception as mapping_status_err:
+ log.error(mapping_status_err)
+ return
+ else:
+ log.debug('User-node association status ' + status)
+ if status == 'requested':
+ print('Checking User Node association status - Requested\n'
+ 'Retrying...')
+ elif status == 'confirmed':
+ print('Checking User Node association status - Confirmed')
+ return
+ elif status == 'timedout':
+ print('Checking User Node association status - Timeout')
+ return
+ elif status == 'discarded':
+ print('Checking User Node association status - Discarded')
+ return
+ time.sleep(5)
+ return
+
+
+def test(vars=None):
+ """
+ Check user node mapping
+
+ :param vars:
+ `addnode` as key - Node ID of node to be mapped to user,\n
+ defaults to `None`
+ :type vars: dict
+
+ """
+ node_id = vars['addnode']
+ if node_id is None or not vars.get('addnode'):
+ print('Error: The following arguments are required: --addnode\n'
+ 'Check usage: rainmaker.py [-h]\n')
+ sys.exit(0)
+ node_object = node.Node(node_id, session.Session())
+ request_id, secret_key = add_node(node_object)
+ config = configmanager.Config()
+ user_id = config.get_user_id()
+ print('Use following command on node simulator or '
+ 'node CLI to confirm user node mapping:')
+ print(" add-user " + user_id + " " + secret_key)
+ print("---------------------------------------------------")
+ print("RequestId for user node mapping request : " + request_id)
+ check_status(node_object, request_id)
+ return
diff --git a/cli/rmaker_cmd/user.py b/cli/rmaker_cmd/user.py
new file mode 100644
index 0000000..0fe5479
--- /dev/null
+++ b/cli/rmaker_cmd/user.py
@@ -0,0 +1,163 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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 sys
+import re
+import getpass
+try:
+ from rmaker_lib import user
+ from rmaker_lib.logger import log
+except ImportError as err:
+ print("Failed to import ESP Rainmaker library. " + str(err))
+ raise err
+
+
+from rmaker_cmd.browserlogin import browser_login
+
+MAX_PASSWORD_CHANGE_ATTEMPTS = 3
+
+
+def signup(vars=None):
+ """
+ User signup to the ESP Rainmaker.
+
+ :param vars: `email` as key - Email address of the user, defaults to `None`
+ :type vars: dict
+
+ :raises Exception: If there is any issue in signup for user
+
+ :return: None on Success
+ :rtype: None
+ """
+ log.info('Signing up the user ' + vars['email'])
+ u = user.User(vars['email'])
+ password = get_password()
+ try:
+ status = u.signup_request(password)
+ except Exception as signup_err:
+ log.error(signup_err)
+ else:
+ if status is True:
+ verification_code = input('Enter verification code sent on your'
+ 'Email.\n Verification Code : ')
+ try:
+ status = u.signup(verification_code)
+ except Exception as signup_err:
+ log.error(signup_err)
+ return
+ print('Signup Successful\n'
+ 'Please login to continue with ESP Rainmaker CLI')
+ else:
+ log.error('Signup failed. Please try again.')
+ return
+
+
+def login(vars=None):
+ """
+ First time login of the user.
+
+ :param vars: `email` as key - Email address of the user, defaults to `None`
+ :type vars: dict
+
+ :raises Exception: If there is any issue in login for user
+
+ :return: None on Success
+ :rtype: None
+ """
+ log.info('Signing in the user. Username ' + str(vars['email']))
+ if vars['email'] is None:
+ browser_login()
+ return
+ u = user.User(vars['email'])
+ try:
+ u.login()
+ except Exception as login_err:
+ log.error(login_err)
+ else:
+ print('Login Successful')
+
+
+def forgot_password(vars=None):
+ """
+ Forgot password request to reset the password.
+
+ :param vars: `email` as key - Email address of the user, defaults to `None`
+ :type vars: dict
+
+ :raises Exception: If there is an HTTP issue while
+ changing password for user
+
+ :return: None on Success and Failure
+ :rtype: None
+ """
+ log.info('Changing user password. Username ' + vars['email'])
+ u = user.User(vars['email'])
+ status = False
+ try:
+ status = u.forgot_password()
+ except Exception as forgot_pwd_err:
+ log.error(forgot_pwd_err)
+ else:
+ verification_code = input('Enter verification code sent on your Email.'
+ '\n Verification Code : ')
+ password = get_password()
+ if status is True:
+ try:
+ log.debug('Received verification code on email ' +
+ vars['email'])
+ status = u.forgot_password(password, verification_code)
+ except Exception as forgot_pwd_err:
+ log.error(forgot_pwd_err)
+ else:
+ print('Password changed successfully.'
+ 'Please login with the new password.')
+ else:
+ log.error('Failed to reset password. Please try again.')
+ return
+
+
+def get_password():
+ """
+ Get Password as input and perform basic password validation checks.
+
+ :raises SystemExit: If there is an issue in getting password
+
+ :return: Password for User on Success
+ :rtype: str
+ """
+ log.info('Doing basic password confirmation checks.')
+ password_policy = '8 characters, 1 digit, 1 uppercase and 1 lowercase.'
+ password_change_attempt = 0
+
+ print('Choose a password')
+ while password_change_attempt < MAX_PASSWORD_CHANGE_ATTEMPTS:
+ log.debug('Password change attempt number ' +
+ str(password_change_attempt+1))
+ password = getpass.getpass('Password : ')
+ if len(password) < 8 or re.search(r"\d", password) is None or\
+ re.search(r"[A-Z]", password) is None or\
+ re.search(r"[a-z]", password) is None:
+ print('Password should contain at least', password_policy)
+ password_change_attempt += 1
+ continue
+ confirm_password = getpass.getpass('Confirm Password : ')
+ if password == confirm_password:
+ return password
+ else:
+ print('Passwords do not match!\n'
+ 'Please enter the password again ..')
+ password_change_attempt += 1
+
+ log.error('Maximum attempts to change password over. Please try again.')
+ sys.exit(1)
diff --git a/cli/rmaker_lib/__init__.py b/cli/rmaker_lib/__init__.py
new file mode 100644
index 0000000..3b0d78c
--- /dev/null
+++ b/cli/rmaker_lib/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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.
diff --git a/cli/rmaker_lib/configmanager.py b/cli/rmaker_lib/configmanager.py
new file mode 100644
index 0000000..73d080f
--- /dev/null
+++ b/cli/rmaker_lib/configmanager.py
@@ -0,0 +1,379 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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 json
+import errno
+import os
+import base64
+import time
+import requests
+from pathlib import Path
+from os import path
+
+from rmaker_lib import serverconfig
+from rmaker_lib.exceptions import NetworkError,\
+ InvalidConfigError,\
+ InvalidUserError,\
+ InvalidApiVersionError,\
+ ExpiredSessionError,\
+ SSLError
+from rmaker_lib.logger import log
+
+CONFIG_DIRECTORY = '.espressif/rainmaker'
+CONFIG_FILE = CONFIG_DIRECTORY + '/rainmaker_config.json'
+HOME_DIRECTORY = '~/'
+CURR_DIR = os.path.dirname(__file__)
+CERT_FILE = CURR_DIR + '/../server_cert/server_cert.pem'
+
+
+class Config:
+ """
+ Config class used to instantiate instances of config to
+ perform various get/set configuration operations
+ """
+ def set_config(self, data, config_file=CONFIG_FILE):
+ """
+ Set the configuration file.
+
+ :params data: Config Data to write to file
+ :type data: dict
+
+ :params config_file: Config filename to write config data to
+ :type data: str
+
+ :raises OSError: If there is an OS issue while creating new directory
+ for config file
+ :raises Exception: If there is a File Handling error while saving
+ config to file
+
+ :return: None on Success and Failure
+ :rtype: None
+ """
+ log.info("Configuring config file.")
+ file_dir = Path(path.expanduser(HOME_DIRECTORY + CONFIG_DIRECTORY))
+ file = Path(path.expanduser(HOME_DIRECTORY) + config_file)
+ if not file.exists():
+ try:
+ if not file_dir.exists():
+ log.debug('Config directory does not exist,'
+ 'creating new directory.')
+ os.makedirs(path.expanduser(HOME_DIRECTORY) +
+ CONFIG_DIRECTORY)
+ except OSError as set_config_err:
+ log.error(set_config_err)
+ if set_config_err.errno != errno.EEXIST:
+ raise set_config_err
+ try:
+ with open(path.join(path.expanduser(HOME_DIRECTORY),
+ config_file), 'w') as config_file:
+ json.dump(data, config_file)
+ except Exception as set_config_err:
+ raise set_config_err
+ log.info("Configured config file successfully.")
+
+ def get_config(self, node=None, config_file=CONFIG_FILE):
+ """
+ Get the configuration details from config file.
+
+ :params node: Name of node
+ :type node: str
+
+ :params config_file: Config filename to read config data from
+ :type data: str
+
+ :raises Exception: If there is a File Handling error while reading
+ from config file
+
+ :return:
+ idtoken - Id Token from config saved\n
+ refreshtoken - Refresh Token from config saved\n
+ accesstoken - Access Token from config saved\n
+ :rtype: str
+ """
+ file = Path(path.expanduser(HOME_DIRECTORY) + config_file)
+ if not file.exists():
+ if node and node.lower() == 'esp32':
+ return None
+ else:
+ raise InvalidUserError
+ try:
+ with open(path.join(path.expanduser(HOME_DIRECTORY),
+ config_file), 'r') as config_file:
+ data = json.load(config_file)
+ if node and node.lower() == "esp32":
+ return data
+ else:
+ idtoken = data['idtoken']
+ refresh_token = data['refreshtoken']
+ access_token = data['accesstoken']
+ except Exception as get_config_err:
+ raise get_config_err
+ return idtoken, refresh_token, access_token
+
+ def get_binary_config(self, node=None, config_file=CONFIG_FILE):
+ """
+ Get the configuration details from binary config file.
+
+ :params node: Name of node
+ :type node: str
+
+ :params config_file: Config filename to read config data from
+ :type data: str
+
+ :raises Exception: If there is a File Handling error while reading
+ from config file
+
+ :return: Config data read from file on Success, None on Failure
+ :rtype: str | None
+ """
+ file = Path(path.expanduser(HOME_DIRECTORY) + config_file)
+ if not file.exists():
+ if node and node.lower() == 'esp32':
+ return None
+ try:
+ with open(path.join(path.expanduser(HOME_DIRECTORY),
+ config_file), 'rb') as config_file:
+ data = config_file.read()
+ if node and node.lower() == "esp32":
+ return data
+ except Exception as get_config_err:
+ raise get_config_err
+ return
+
+ def update_config(self, access_token, id_token):
+ """
+ Update the configuration file.
+
+ :params access_token: Access Token to update in config file
+ :type access_token: str
+
+ :params id_token: Id Token to update in config file
+ :type id_token: str
+
+ :raises OSError: If there is an OS issue while creating new directory
+ for config file
+ :raises Exception: If there is a FILE Handling error while reading
+ from/writing config to file
+
+ :return: None on Success and Failure
+ :rtype: None
+ """
+ file = Path(path.expanduser(HOME_DIRECTORY) + CONFIG_FILE)
+ if not file.exists():
+ try:
+ os.makedirs(path.expanduser(HOME_DIRECTORY) + CONFIG_DIRECTORY)
+ except OSError as set_config_err:
+ if set_config_err.errno != errno.EEXIST:
+ raise set_config_err
+ try:
+ with open(path.join(path.expanduser(HOME_DIRECTORY),
+ CONFIG_FILE), 'r') as config_file:
+ config_data = json.load(config_file)
+ config_data['accesstoken'] = access_token
+ config_data['idtoken'] = id_token
+ with open(path.join(path.expanduser(HOME_DIRECTORY),
+ CONFIG_FILE), 'w') as config_file:
+ json.dump(config_data, config_file)
+ except Exception as set_config_err:
+ raise set_config_err
+
+ def get_token_attribute(self, attribute_name, is_access_token=False):
+ """
+ Get access token attributes.
+
+ :params attribute_name: Attribute Name
+ :type attribute_name: str
+
+ :params is_access_token: Is Access Token
+ :type is_access_token: bool
+
+ :raises InvalidConfigError: If there is an error in the config
+ :raises Exception: If there is a File Handling error while reading
+ from/writing config to file
+
+ :return: Attribute Value on Success, None on Failure
+ :rtype: int | str | None
+ """
+ if is_access_token:
+ log.debug('Getting access token for attribute ' + attribute_name)
+ _, _, token = self.get_config()
+ else:
+ log.debug('Getting idtoken for attribute ' + attribute_name)
+ token, _, _ = self.get_config()
+ token_payload = token.split('.')[1]
+ if len(token_payload) % 4:
+ token_payload += '=' * (4 - len(token_payload) % 4)
+ try:
+ str_token_payload = base64.b64decode(token_payload).decode("utf-8")
+ # If user is logged in through github then to extend session
+ # we need 'cognito:username' (Github generated username) as email
+ if attribute_name == 'email':
+ if 'identities' in json.loads(str_token_payload):
+ return json.loads(str_token_payload)['cognito:username']
+ attribute_value = json.loads(str_token_payload)[attribute_name]
+ except Exception:
+ raise InvalidConfigError
+ if attribute_value is None:
+ raise InvalidConfigError
+ return attribute_value
+
+ def get_access_token(self):
+ """
+ Get Access Token for User
+
+ :raises InvalidConfigError: If there is an issue in getting config
+ from file
+
+ :return: Access Token on Success
+ :rtype: str
+ """
+ _, _, access_token = self.get_config()
+ if access_token is None:
+ raise InvalidConfigError
+ if self.__is_valid_token() is False:
+ username = self.get_token_attribute('email')
+ refresh_token = self.get_refresh_token()
+ access_token, id_token = self.__get_new_token(username,
+ refresh_token)
+ self.update_config(access_token, id_token)
+ return access_token
+
+ def get_user_id(self):
+ """
+ Get User Id
+
+ :return: Attribute value for attribute name passed
+ :rtype: str
+ """
+ return self.get_token_attribute('custom:user_id')
+
+ def get_refresh_token(self):
+ """
+ Get Refresh Token
+
+ :raises InvalidApiVersionError: If current API version is not supported
+
+ :return: Refresh Token
+ :rtype: str
+ """
+ if self.__is_valid_version() is False:
+ raise InvalidApiVersionError
+ _, refresh_token, _ = self.get_config()
+ return refresh_token
+
+ def __is_valid_token(self):
+ """
+ Check if access token is valid i.e. login session is still active
+ or session is expired
+
+ :return True on Success and False on Failure
+ :rtype: bool
+ """
+ log.info("Checking for session timeout.")
+ exp_timestamp = self.get_token_attribute('exp', is_access_token=True)
+ current_timestamp = int(time.time())
+ if exp_timestamp > current_timestamp:
+ return True
+ log.info("Session expired.")
+ return False
+
+ def __is_valid_version(self):
+ """
+ Check if API Version is valid
+
+ :raises NetworkError: If there is a network connection issue during
+ HTTP request for getting version
+ :raises Exception: If there is an HTTP issue or JSON format issue in
+ HTTP response
+
+ :return: True on Success, False on Failure
+ :rtype: bool
+ """
+ log.info("Checking for supported version.")
+ path = 'apiversions'
+ request_url = serverconfig.HOST.split(serverconfig.VERSION)[0] + path
+ try:
+ log.debug("Version check request url : " + request_url)
+ response = requests.get(url=request_url, verify=CERT_FILE)
+ log.debug("Version check response : " + response.text)
+ response.raise_for_status()
+ except requests.exceptions.SSLError:
+ raise SSLError
+ except requests.exceptions.ConnectionError:
+ raise NetworkError
+ except Exception as ver_err:
+ raise ver_err
+
+ try:
+ response = json.loads(response.text)
+ except Exception as json_decode_err:
+ raise json_decode_err
+
+ if 'supported_versions' in response:
+ supported_versions = response['supported_versions']
+ if serverconfig.VERSION in supported_versions:
+ supported_versions.sort()
+ latest_version = supported_versions[len(supported_versions)
+ - 1]
+ if serverconfig.VERSION < latest_version:
+ print('Please check the updates on GitHub for newer'
+ 'functionality enabled by ' + latest_version +
+ ' APIs.')
+ return True
+ return False
+
+ def __get_new_token(self, username, refresh_token):
+ """
+ Get new token for User Login Session
+
+ :raises NetworkError: If there is a network connection issue during
+ HTTP request for getting token
+ :raises Exception: If there is an HTTP issue or JSON format issue in
+ HTTP response
+
+ :return: accesstoken and idtoken on Success, None on Failure
+ :rtype: str | None
+
+ """
+ log.info("Extending user login session.")
+ path = 'login'
+ request_payload = {
+ 'user_name': username,
+ 'refreshtoken': refresh_token
+ }
+
+ request_url = serverconfig.HOST + path
+ try:
+ log.debug("Extend session url : " + request_url)
+ response = requests.post(url=request_url,
+ data=json.dumps(request_payload),
+ verify=CERT_FILE)
+ response.raise_for_status()
+ log.debug("Extend session response : " + response.text)
+ except requests.exceptions.SSLError:
+ raise SSLError
+ except requests.exceptions.ConnectionError:
+ raise NetworkError
+ except Exception:
+ raise ExpiredSessionError
+
+ try:
+ response = json.loads(response.text)
+ except Exception:
+ raise ExpiredSessionError
+
+ if 'accesstoken' in response and 'idtoken' in response:
+ log.info("User session extended successfully.")
+ return response['accesstoken'], response['idtoken']
+ return None
diff --git a/cli/rmaker_lib/device.py b/cli/rmaker_lib/device.py
new file mode 100644
index 0000000..d010c5e
--- /dev/null
+++ b/cli/rmaker_lib/device.py
@@ -0,0 +1,61 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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 rmaker_lib.logger import log
+
+
+class Device:
+ """
+ Device class used to instantiate instances of device
+ to perform various device operations.
+ """
+ def __init__(self, node, device):
+ """
+ Instantiate device object.
+ """
+ log.info("Initialising device " + device['name'])
+ self.__node = node
+ self.__name = device['name']
+ self.__params = {}
+ for param in device['params']:
+ self.__params[param["name"]] = ''
+
+ def get_device_name(self):
+ """
+ Get the device name.
+ """
+ return self.__name
+
+ def get_params(self):
+ """
+ Get parameters of the device.
+ """
+ params = {}
+ node_params = self.__node.get_node_params()
+ if node_params is None:
+ return params
+ for key in self.__params.keys():
+ params[key] = node_params[self.__name + '.' + key]
+ return params
+
+ def set_params(self, data):
+
+ """
+ Set parameters of the device.
+ Input data contains the dictionary of device parameters.
+ """
+ request_payload = {}
+ for key in data:
+ request_payload[self.__name + '.' + key] = data[key]
+ return self.__node.set_node_params(request_payload)
diff --git a/cli/rmaker_lib/exceptions.py b/cli/rmaker_lib/exceptions.py
new file mode 100644
index 0000000..c88d920
--- /dev/null
+++ b/cli/rmaker_lib/exceptions.py
@@ -0,0 +1,72 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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.
+
+
+class NetworkError(Exception):
+ """ Raised when internet connection is not available """
+ def __str__(self):
+ return 'Please check the internet connectivity.\
+ No internet connection available.'
+
+
+class InvalidJSONError(Exception):
+ """ Raised for invalid JSON input """
+ def __str__(self):
+ return 'Invalid JSON received.'
+
+
+class ExpiredSessionError(Exception):
+ """ Raised when user session expires """
+ def __str__(self):
+ return 'User session is expired. Please login again.'
+
+
+class InvalidConfigError(Exception):
+ """ Raised for invalid configuration """
+ def __str__(self):
+ return 'Invalid configuration. Please login again.'
+
+
+class InvalidUserError(Exception):
+ """ Raised when config file does not exists """
+ def __str__(self):
+ return 'User not logged in. Please use login command.'
+
+
+class AuthenticationError(Exception):
+ """ Raised when user login fails """
+ def __str__(self):
+ return 'Login failed. Please try again'
+
+
+class InvalidApiVersionError(Exception):
+ """ Raised when current API version is not supported """
+ def __str__(self):
+ return 'API Version not supported. Please upgrade ESP Rainmaker CLI.'
+
+
+class InvalidClassInput(Exception):
+ """ Raised for invalid Session input """
+ def __init__(self, input_arg, err_str):
+ self.arg = input_arg
+ self.err_str = err_str
+
+ def __str__(self):
+ return '{} {}'.format(self.err_str, self.arg)
+
+
+class SSLError(Exception):
+ """ Raised when invalid SSL certificate is passed """
+ def __str__(self):
+ return 'Unable to verify SSL certificate.'
diff --git a/cli/rmaker_lib/logger.py b/cli/rmaker_lib/logger.py
new file mode 100644
index 0000000..14a5654
--- /dev/null
+++ b/cli/rmaker_lib/logger.py
@@ -0,0 +1,43 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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 os
+import logging
+from logging import handlers
+from datetime import datetime
+
+if not os.path.exists('logs'):
+ os.makedirs('logs')
+
+date_time_obj = datetime.now()
+log_filename = "logs/log_" + date_time_obj.strftime("%d-%m-%Y") + ".log"
+
+log = logging.getLogger("CLI_LOGS")
+file_formatter = logging.Formatter('%(asctime)s:[%(funcName)s]:\
+ [%(levelname)s]:%(message)s')
+console_formatter = logging.Formatter('[%(levelname)s]:%(message)s')
+log.setLevel(logging.DEBUG)
+
+console_handler = logging.StreamHandler()
+console_handler.setLevel(logging.ERROR)
+console_handler.setFormatter(console_formatter)
+
+file_handler = handlers.RotatingFileHandler(log_filename,
+ maxBytes=51200,
+ backupCount=16)
+file_handler.setFormatter(file_formatter)
+file_handler.setLevel(logging.DEBUG)
+
+log.addHandler(file_handler)
+log.addHandler(console_handler)
diff --git a/cli/rmaker_lib/node.py b/cli/rmaker_lib/node.py
new file mode 100644
index 0000000..a3cf9f2
--- /dev/null
+++ b/cli/rmaker_lib/node.py
@@ -0,0 +1,336 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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 requests
+import json
+from rmaker_lib import serverconfig, configmanager
+from rmaker_lib.exceptions import NetworkError, InvalidClassInput, SSLError
+from rmaker_lib.logger import log
+
+
+class Node:
+ """
+ Node class used to instantiate instances of node to perform various
+ node operations.
+
+ :param nodeid: Node Id of node
+ :type nodeid: str
+
+ :param session: :class:`rmaker_lib.session.Session`
+ :type session: object
+ """
+ def __init__(self, nodeid, session):
+ """
+ Instantiate node with nodeid and session object.
+ """
+ log.info("Initialising node with nodeid : " + nodeid)
+ self.__nodeid = nodeid
+ self.__session = session
+ try:
+ self.__request_header = {'content-type': 'application/json',
+ 'Authorization': session.id_token}
+ except AttributeError:
+ raise InvalidClassInput(session, 'Invalid Session Input.\
+ Expected: type .\
+ Received: ')
+
+ def get_nodeid(self):
+ """
+ Get nodeid of device
+
+ :return: Node Id of node on Success
+ :rtype: str
+ """
+ return self.__nodeid
+
+ def get_node_status(self):
+ """
+ Get online/offline status of the node.
+
+ :raises NetworkError: If there is a network connection issue while
+ getting node status
+ :raises Exception: If there is an HTTP issue while getting node status
+
+ :return: Status of node on Success
+ :rtype: dict
+ """
+ log.info("Getting online/offline status of the node : " +
+ self.__nodeid)
+ path = 'user/nodes/status'
+ query_parameters = 'nodeid=' + self.__nodeid
+ getnodestatus_url = serverconfig.HOST + path + '?' + query_parameters
+ try:
+ log.debug("Get node status request url : " + getnodestatus_url)
+ response = requests.get(url=getnodestatus_url,
+ headers=self.__request_header,
+ verify=configmanager.CERT_FILE)
+ log.debug("Get node status response : " + response.text)
+ response.raise_for_status()
+ except requests.exceptions.SSLError:
+ raise SSLError
+ except requests.exceptions.ConnectionError:
+ raise NetworkError
+ except Exception:
+ raise Exception(response.text)
+ log.info("Received node status successfully.")
+ return response.json()
+
+ def get_node_config(self):
+ """
+ Get node configuration.
+
+ :raises NetworkError: If there is a network connection issue while
+ getting node configuration
+ :raises Exception: If there is an HTTP issue while getting node config
+
+ :return: Configuration of node on Success
+ :rtype: dict
+ """
+ log.info("Getting node config for node : " + self.__nodeid)
+ path = 'user/nodes/config'
+ query_parameters = 'nodeid=' + self.__nodeid
+ getnodeconfig_url = serverconfig.HOST + path + '?' + query_parameters
+ try:
+ log.debug("Get node config request url : " + getnodeconfig_url)
+ response = requests.get(url=getnodeconfig_url,
+ headers=self.__request_header,
+ verify=configmanager.CERT_FILE)
+ log.debug("Get node config response : " + response.text)
+ response.raise_for_status()
+ except requests.exceptions.SSLError:
+ raise SSLError
+ except requests.exceptions.ConnectionError:
+ raise NetworkError
+ except Exception:
+ raise Exception(response.text)
+ log.info("Received node config successfully.")
+ return response.json()
+
+ def get_node_params(self):
+ """
+ Get parameters of the node.
+
+ :raises NetworkError: If there is a network connection issue while
+ getting node params
+ :raises Exception: If there is an HTTP issue while getting node params
+ or JSON format issue in HTTP response
+
+ :return: Node Parameters on Success, None on Failure
+ :rtype: dict | None
+
+ """
+ log.info("Getting parameters of the node with nodeid : " +
+ self.__nodeid)
+ path = 'user/nodes/params'
+ query_parameters = 'nodeid=' + self.__nodeid
+ getparams_url = serverconfig.HOST + path + '?' + query_parameters
+ try:
+ log.debug("Get node params request url : " + getparams_url)
+ response = requests.get(url=getparams_url,
+ headers=self.__request_header,
+ verify=configmanager.CERT_FILE)
+ log.debug("Get node params response : " + response.text)
+ response.raise_for_status()
+ except requests.exceptions.SSLError:
+ raise SSLError
+ except requests.exceptions.ConnectionError:
+ raise NetworkError
+ except Exception:
+ raise Exception(response.text)
+
+ response = json.loads(response.text)
+ if 'status' in response and response['status'] == 'failure':
+ return None
+ log.info("Received node parameters successfully.")
+ return response
+
+ def set_node_params(self, data):
+ """
+ Set parameters of the node.
+
+ :param data: Parameters to be set for the node
+ :type data: dict
+
+ :raises NetworkError: If there is a network connection issue while
+ setting node params
+ :raises Exception: If there is an HTTP issue while setting node params
+ or JSON format issue in HTTP response
+
+ :return: True on Success
+ :rtype: bool
+ """
+ log.info("Updating parameters of the node with nodeid : " +
+ self.__nodeid)
+ path = 'user/nodes/params'
+ query_parameters = 'nodeid=' + self.__nodeid
+ setparams_url = serverconfig.HOST + path + '?' + query_parameters
+ try:
+ log.debug("Set node params request url : " + setparams_url)
+ log.debug("Set node params request payload : " + json.dumps(data))
+ response = requests.put(url=setparams_url,
+ data=json.dumps(data),
+ headers=self.__request_header,
+ verify=configmanager.CERT_FILE)
+ log.debug("Set node params response : " + response.text)
+ response.raise_for_status()
+ except requests.exceptions.SSLError:
+ raise SSLError
+ except requests.exceptions.ConnectionError:
+ raise NetworkError
+ except Exception:
+ raise Exception(response.text)
+ log.info("Updated node parameters successfully.")
+ return True
+
+ def __user_node_mapping(self, secret_key, operation):
+ """
+ Add or remove the user node mapping.
+
+ :param secret_key: The randomly generated secret key that will be
+ used for User-Node mapping
+ :type secret_key: str
+
+ :param operation: Operation to be performed, can take values
+ 'add' or 'remove'
+ :type operation: str
+
+ :raises NetworkError: If there is a network connection issue
+ while adding user node mapping
+ :raises Exception: If there is an HTTP issue or JSON format issue
+ in HTTP response
+
+ :return: Request Id if Success, None if Failure
+ :rtype: str | None
+ """
+ path = 'user/nodes/mapping'
+ config = configmanager.Config()
+ userid = config.get_user_id()
+ request_payload = {
+ 'user_id': userid,
+ 'node_id': self.__nodeid,
+ 'secret_key': secret_key,
+ 'operation': operation
+ }
+
+ request_url = serverconfig.HOST + path
+ try:
+ log.debug("User node mapping request url : " + request_url)
+ log.debug("User node mapping request payload : " +
+ str(request_payload))
+ response = requests.put(url=request_url,
+ data=json.dumps(request_payload),
+ headers=self.__request_header,
+ verify=configmanager.CERT_FILE)
+ log.debug("User node mapping response : " + response.text)
+ response.raise_for_status()
+ except requests.exceptions.SSLError:
+ raise SSLError
+ except requests.exceptions.ConnectionError:
+ raise NetworkError
+ except Exception:
+ raise Exception(response.text)
+
+ try:
+ response = json.loads(response.text)
+ except Exception as user_node_mapping_err:
+ raise user_node_mapping_err
+
+ if 'request_id' in response:
+ return response['request_id']
+ return None
+
+ def add_user_node_mapping(self, secret_key):
+ """
+ Add user node mapping.
+
+ :param secret_key: The randomly generated secret key that will be
+ used for User-Node mapping
+ :type secret_key: str
+
+ :raises NetworkError: If there is a network connection issue while
+ adding user node mapping
+ :raises Exception: If there is an HTTP issue while
+ adding user node mapping or
+ JSON format issue in HTTP response
+
+ :return: Request Id on Success, None on Failure
+ :rtype: str | None
+ """
+ log.info("Adding user node mapping request with nodeid : " +
+ self.__nodeid)
+ return self.__user_node_mapping(secret_key, 'add')
+
+ def remove_user_node_mapping(self):
+ """
+ Remove user node mapping request.
+
+ :raises NetworkError: If there is a network connection issue while
+ removing user node mapping
+ :raises Exception: If there is an HTTP issue while
+ removing user node mapping or
+ JSON format issue in HTTP response
+
+ :return: Request Id on Success, None on Failure
+ :rtype: str | None
+ """
+ log.info("Removing user node mapping with nodeid : " + self.__nodeid)
+ secret_key = ""
+ return self.__user_node_mapping(secret_key, 'remove')
+
+ def get_mapping_status(self, request_id):
+ """
+ Check status of user node mapping request.
+
+ :param requestId: Request Id
+ :type requestId: str
+
+ :raises NetworkError: If there is a network connection issue while
+ getting user node mapping status
+ :raises Exception: If there is an HTTP issue while getting
+ user node mapping status or JSON format issue
+ in HTTP response
+
+ :return: Request Status on Success, None on Failure
+ :type: str | None
+ """
+ log.info("Checking status of user node mapping with request_id : " +
+ request_id)
+ path = 'user/nodes/mapping'
+ query_parameters = "&request_id=" + request_id
+
+ request_url = serverconfig.HOST + path + '?' + query_parameters
+ try:
+ log.debug("Check user node mapping status request url : " +
+ request_url)
+ response = requests.get(url=request_url,
+ headers=self.__request_header,
+ verify=configmanager.CERT_FILE)
+ log.debug("Check user node mapping status response : " +
+ response.text)
+ response.raise_for_status()
+ except requests.exceptions.SSLError:
+ raise SSLError
+ except requests.exceptions.ConnectionError:
+ raise NetworkError
+ except Exception as mapping_status_err:
+ raise mapping_status_err
+
+ try:
+ response = json.loads(response.text)
+ except Exception as mapping_status_err:
+ raise mapping_status_err
+
+ if 'request_status' in response:
+ return response['request_status']
+ return None
diff --git a/cli/rmaker_lib/serverconfig.py b/cli/rmaker_lib/serverconfig.py
new file mode 100644
index 0000000..d66f1c5
--- /dev/null
+++ b/cli/rmaker_lib/serverconfig.py
@@ -0,0 +1,34 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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.
+
+VERSION = 'v1'
+
+HOST = 'https://api.rainmaker.espressif.com/' + VERSION + '/'
+
+CLIENT_ID = '60i6kac5f9rjuetqnq5mnmaqv6'
+
+LOGIN_URL = 'https://rainmaker-login-ui.s3.amazonaws.com/index.html?port='
+
+TOKEN_URL = ('https://rainmaker-prod.auth.us-east-1.amazoncognito.com/'
+ 'oauth2/token')
+
+REDIRECT_URL = 'https://rainmaker-login-ui.s3.amazonaws.com/welcome.html'
+
+EXTERNAL_LOGIN_URL = (
+ 'https://rainmaker-prod.auth.us-east-1.amazoncognito.com/'
+ 'oauth2/authorize?&redirect_uri=' +
+ REDIRECT_URL + '&response_type=CODE&client_id=' +
+ CLIENT_ID + '&scope=aws.cognito.signin.user.'
+ 'admin%20email%20openid%20phone%20profile&state=port:'
+ )
diff --git a/cli/rmaker_lib/session.py b/cli/rmaker_lib/session.py
new file mode 100644
index 0000000..9487f16
--- /dev/null
+++ b/cli/rmaker_lib/session.py
@@ -0,0 +1,110 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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 requests
+import json
+from rmaker_lib import serverconfig, configmanager
+from rmaker_lib import node
+from rmaker_lib.exceptions import NetworkError, InvalidConfigError, SSLError
+from rmaker_lib.logger import log
+
+
+class Session:
+ """
+ Session class for logged in user.
+ """
+ def __init__(self):
+ """
+ Instantiate session for logged in user.
+ """
+ config = configmanager.Config()
+ log.info("Initialising session for user " +
+ config.get_token_attribute('email'))
+ self.id_token = config.get_access_token()
+ if self.id_token is None:
+ raise InvalidConfigError
+ self.request_header = {'content-type': 'application/json',
+ 'Authorization': self.id_token}
+
+ def get_nodes(self):
+ """
+ Get list of all nodes associated with the user.
+
+ :raises NetworkError: If there is a network connection issue
+ while getting nodes associated with user
+ :raises Exception: If there is an HTTP issue while getting nodes
+
+ :return: Nodes associated with user on Success
+ :rtype: dict
+ """
+ log.info("Getting nodes associated with the user.")
+ path = 'user/nodes'
+ getnodes_url = serverconfig.HOST + path
+ try:
+ log.debug("Get nodes request url : " + getnodes_url)
+ response = requests.get(url=getnodes_url,
+ headers=self.request_header,
+ verify=configmanager.CERT_FILE)
+ log.debug("Get nodes request response : " + response.text)
+ response.raise_for_status()
+ except requests.exceptions.SSLError:
+ raise SSLError
+ except requests.exceptions.ConnectionError:
+ raise NetworkError
+ except Exception:
+ raise Exception(response.text)
+
+ node_map = {}
+ for nodeid in json.loads(response.text)['nodes']:
+ node_map[nodeid] = node.Node(nodeid, self)
+ log.info("Received nodes for user successfully.")
+ return node_map
+
+ def get_mqtt_host(self):
+ """
+ Get the MQTT Host endpoint.
+
+ :raises NetworkError: If there is a network connection issue
+ while getting MQTT Host endpoint
+ :raises Exception: If there is an HTTP issue while getting MQTT host
+ or JSON format issue in HTTP response
+
+ :return: MQTT Host on Success, None on Failure
+ :rtype: str | None
+ """
+ log.info("Getting MQTT Host endpoint.")
+ path = 'mqtt_host'
+ request_url = serverconfig.HOST.split(serverconfig.VERSION)[0] + path
+ try:
+ log.debug("Get MQTT Host request url : " + request_url)
+ response = requests.get(url=request_url,
+ verify=configmanager.CERT_FILE)
+ log.debug("Get MQTT Host resonse : " + response.text)
+ response.raise_for_status()
+ except requests.exceptions.SSLError:
+ raise SSLError
+ except requests.exceptions.ConnectionError:
+ raise NetworkError
+ except Exception as mqtt_host_err:
+ raise mqtt_host_err
+
+ try:
+ response = json.loads(response.text)
+ except Exception as json_decode_err:
+ raise json_decode_err
+
+ if 'mqtt_host' in response:
+ log.info("Received MQTT Host endpoint successfully.")
+ return response['mqtt_host']
+ return None
diff --git a/cli/rmaker_lib/user.py b/cli/rmaker_lib/user.py
new file mode 100644
index 0000000..4d10963
--- /dev/null
+++ b/cli/rmaker_lib/user.py
@@ -0,0 +1,219 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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 json
+import requests
+import getpass
+from rmaker_lib import serverconfig, configmanager, session
+from rmaker_lib.exceptions import NetworkError, AuthenticationError, SSLError
+from rmaker_lib.logger import log
+
+
+class User:
+ """
+ User class used to instantiate instances of user to perform various
+ user signup/login operations.
+
+ :param username: Name of User
+ :type username: str
+ """
+ def __init__(self, username):
+ """
+ Instantiate user with username.
+ """
+ log.info("Initialising user " + username)
+ self.__username = username
+ self.__passwd_change_token = ''
+ self.__request_header = {'content-type': 'application/json'}
+
+ def signup_request(self, password):
+ """
+ Sign up request of new User for ESP Rainmaker.
+
+ :param password: Password to set for new user
+ :type password: str
+
+ :raises NetworkError: If there is a network connection issue
+ during signup request
+ :raises Exception: If there is an HTTP issue during signup request
+
+ :return: True on Success
+ :rtype: bool
+ """
+ log.info("Creating new user with username : " + self.__username)
+ path = 'user'
+ signup_info = {
+ 'user_name': self.__username,
+ "password": password
+ }
+ signup_url = serverconfig.HOST + path
+
+ try:
+ log.debug("Signup request url : " + signup_url)
+ response = requests.post(url=signup_url,
+ data=json.dumps(signup_info),
+ headers=self.__request_header,
+ verify=configmanager.CERT_FILE)
+ log.debug("Signup request response : " + response.text)
+ response.raise_for_status()
+ except requests.exceptions.SSLError:
+ raise SSLError
+ except requests.exceptions.ConnectionError:
+ raise NetworkError
+ except Exception:
+ raise Exception(response.text)
+ log.info("Signup request sent successfully.")
+ return True
+
+ def signup(self, code):
+ """
+ Sign up of new User for ESP Rainmaker.
+
+ :param code: Verification code received in signup request for user
+ :type code: int
+
+ :raises NetworkError: If there is a network connection issue
+ during signup
+ :raises Exception: If there is an HTTP issue during signup
+
+ :return: True on Success
+ :rtype: bool
+ """
+ log.info("Confirming user with username : " + self.__username)
+ path = 'user'
+ signup_info = {
+ 'user_name': self.__username,
+ "verification_code": code
+ }
+ signup_url = serverconfig.HOST + path
+
+ try:
+ log.debug("Confirm user request url : " + signup_url)
+ response = requests.post(url=signup_url,
+ data=json.dumps(signup_info),
+ headers=self.__request_header,
+ verify=configmanager.CERT_FILE)
+ log.debug("Confirm user response : " + response.text)
+ response.raise_for_status()
+ except requests.exceptions.SSLError:
+ raise SSLError
+ except requests.exceptions.ConnectionError:
+ raise NetworkError
+ except Exception:
+ raise Exception(response.text)
+ log.info("Signup successful.")
+ return True
+
+ def login(self, password=None):
+ """
+ User login to the ESP Rainmaker.
+
+ :param password: Password of user, defaults to `None`
+ :type password: str
+
+ :raises NetworkError: If there is a network connection issue
+ during login
+ :raises Exception: If there is an HTTP issue during login or
+ JSON format issue in HTTP response
+ :raises AuthenticationError: If login failed with the given parameters
+
+ :return: :class:`rmaker_lib.session.Session` on Success
+ :rtype: object
+ """
+ log.info("User login with username : " + self.__username)
+ if password is None:
+ password = getpass.getpass()
+ path = 'login/'
+ login_info = {
+ 'user_name': self.__username,
+ 'password': password
+ }
+ login_url = serverconfig.HOST + path
+
+ try:
+ log.debug("Login request url : " + login_url)
+ response = requests.post(url=login_url,
+ data=json.dumps(login_info),
+ headers=self.__request_header,
+ verify=configmanager.CERT_FILE)
+ log.debug("Login response : " + response.text)
+ response.raise_for_status()
+ except requests.exceptions.SSLError:
+ raise SSLError
+ except requests.exceptions.ConnectionError:
+ raise NetworkError
+ except Exception:
+ raise Exception(response.text)
+
+ try:
+ result = json.loads(response.text)
+ except Exception as json_decode_err:
+ raise json_decode_err
+
+ if 'status' in result and result['status'] == 'success':
+ log.info("Login successful.")
+ config_data = {}
+ config_data['idtoken'] = result['idtoken']
+ config_data['refreshtoken'] = result['refreshtoken']
+ config_data['accesstoken'] = result['accesstoken']
+ configmanager.Config().set_config(config_data)
+ return session.Session()
+ raise AuthenticationError
+
+ # User has to call forgot_password two times
+ # First call without arguments to request forgot password
+ # Second call to reset the password
+ # with arguments password and verification_code
+ def forgot_password(self, password=None, verification_code=None):
+ """
+ Forgot password request to reset the password.
+
+ :param password: Password of user, defaults to `None`
+ :type password: str
+
+ :param verification_code: Verification code received during
+ forgot password request, defaults to `None`
+ :type verification_code: int
+
+ :raises NetworkError: If there is a network connection issue
+ during password reset
+ :raises Exception: If there is an HTTP issue during forgot password
+
+ :return: True on Success
+ :rtype: bool
+ """
+ log.info("Forgot password request for user : " + self.__username)
+ path = 'forgotpassword'
+ forgot_password_info = {
+ 'user_name': self.__username,
+ "password": password,
+ "verification_code": verification_code
+ }
+ forgot_password_url = serverconfig.HOST + path
+ try:
+ log.debug("Forgot password request url : " + forgot_password_url)
+ response = requests.put(url=forgot_password_url,
+ data=json.dumps(forgot_password_info),
+ headers=self.__request_header,
+ verify=configmanager.CERT_FILE)
+ log.debug("Forgot password response : " + response.text)
+ response.raise_for_status()
+ except requests.exceptions.SSLError:
+ raise SSLError
+ except requests.exceptions.ConnectionError:
+ raise NetworkError
+ except Exception:
+ raise Exception(response.text)
+ log.info("Changed password successfully.")
+ return True
diff --git a/cli/rmaker_tools/__init__.py b/cli/rmaker_tools/__init__.py
new file mode 100644
index 0000000..3b0d78c
--- /dev/null
+++ b/cli/rmaker_tools/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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.
diff --git a/cli/rmaker_tools/rmaker_prov/README.md b/cli/rmaker_tools/rmaker_prov/README.md
new file mode 100644
index 0000000..f8785b6
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/README.md
@@ -0,0 +1,33 @@
+# Proto files copied from IDF to ESP Rainmaker
+
+This folder contains proto files copied from ESP-IDF for provisioning
+
+Files copied are:
+
+protocomm/
+├── proto
+│  ├── constants.proto
+│  ├── sec0.proto
+│  ├── sec1.proto
+│  └── session.proto
+└── python
+ ├── constants_pb2.py
+ ├── sec0_pb2.py
+ ├── sec1_pb2.py
+ └── session_pb2.py
+
+
+wifi_provisioning/
+├── proto
+│  ├── wifi_config.proto
+│  ├── wifi_constants.proto
+│  └── wifi_scan.proto
+└── python
+ ├── wifi_config_pb2.py
+ ├── wifi_constants_pb2.py
+ └── wifi_scan_pb2.py
+
+# Info
+
+IDF Branch Copied From: master
+IDF Branch Latest Commit: 604360b98c14e48e73b810cb01ca294c7d275ce3
\ No newline at end of file
diff --git a/cli/rmaker_tools/rmaker_prov/__init__.py b/cli/rmaker_tools/rmaker_prov/__init__.py
new file mode 100644
index 0000000..3b0d78c
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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.
diff --git a/cli/rmaker_tools/rmaker_prov/config/custom_cloud_config.proto b/cli/rmaker_tools/rmaker_prov/config/custom_cloud_config.proto
new file mode 100644
index 0000000..ac47dca
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/config/custom_cloud_config.proto
@@ -0,0 +1,31 @@
+syntax = "proto3";
+
+package cloud;
+
+enum CloudConfigStatus {
+ Success = 0;
+ InvalidParam = 1;
+ InvalidState = 2;
+}
+
+message CmdGetSetDetails {
+ string UserID = 1;
+ string SecretKey = 2;
+}
+message RespGetSetDetails{
+ CloudConfigStatus Status = 1;
+ string DeviceSecret = 2;
+}
+
+enum CloudConfigMsgType {
+ TypeCmdGetSetDetails = 0;
+ TypeRespGetSetDetails = 1;
+}
+
+message CloudConfigPayload {
+ CloudConfigMsgType msg = 1;
+ oneof payload {
+ CmdGetSetDetails cmd_get_set_details = 10;
+ RespGetSetDetails resp_get_set_details = 11;
+ }
+}
diff --git a/cli/rmaker_tools/rmaker_prov/config/custom_cloud_config_pb2.py b/cli/rmaker_tools/rmaker_prov/config/custom_cloud_config_pb2.py
new file mode 100644
index 0000000..f57e8cd
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/config/custom_cloud_config_pb2.py
@@ -0,0 +1,261 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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.
+
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: cloud.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+ name='cloud.proto',
+ package='cloud',
+ syntax='proto3',
+ serialized_options=None,
+ serialized_pb=_b('\n\x0b\x63loud.proto\x12\x05\x63loud\"5\n\x10\x43mdGetSetDetails\x12\x0e\n\x06UserID\x18\x01 \x01(\t\x12\x11\n\tSecretKey\x18\x02 \x01(\t\"S\n\x11RespGetSetDetails\x12(\n\x06Status\x18\x01 \x01(\x0e\x32\x18.cloud.CloudConfigStatus\x12\x14\n\x0c\x44\x65viceSecret\x18\x02 \x01(\t\"\xb9\x01\n\x12\x43loudConfigPayload\x12&\n\x03msg\x18\x01 \x01(\x0e\x32\x19.cloud.CloudConfigMsgType\x12\x36\n\x13\x63md_get_set_details\x18\n \x01(\x0b\x32\x17.cloud.CmdGetSetDetailsH\x00\x12\x38\n\x14resp_get_set_details\x18\x0b \x01(\x0b\x32\x18.cloud.RespGetSetDetailsH\x00\x42\t\n\x07payload*D\n\x11\x43loudConfigStatus\x12\x0b\n\x07Success\x10\x00\x12\x10\n\x0cInvalidParam\x10\x01\x12\x10\n\x0cInvalidState\x10\x02*I\n\x12\x43loudConfigMsgType\x12\x18\n\x14TypeCmdGetSetDetails\x10\x00\x12\x19\n\x15TypeRespGetSetDetails\x10\x01\x62\x06proto3')
+)
+
+_CLOUDCONFIGSTATUS = _descriptor.EnumDescriptor(
+ name='CloudConfigStatus',
+ full_name='cloud.CloudConfigStatus',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='Success', index=0, number=0,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='InvalidParam', index=1, number=1,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='InvalidState', index=2, number=2,
+ serialized_options=None,
+ type=None),
+ ],
+ containing_type=None,
+ serialized_options=None,
+ serialized_start=350,
+ serialized_end=418,
+)
+_sym_db.RegisterEnumDescriptor(_CLOUDCONFIGSTATUS)
+
+CloudConfigStatus = enum_type_wrapper.EnumTypeWrapper(_CLOUDCONFIGSTATUS)
+_CLOUDCONFIGMSGTYPE = _descriptor.EnumDescriptor(
+ name='CloudConfigMsgType',
+ full_name='cloud.CloudConfigMsgType',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='TypeCmdGetSetDetails', index=0, number=0,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='TypeRespGetSetDetails', index=1, number=1,
+ serialized_options=None,
+ type=None),
+ ],
+ containing_type=None,
+ serialized_options=None,
+ serialized_start=420,
+ serialized_end=493,
+)
+_sym_db.RegisterEnumDescriptor(_CLOUDCONFIGMSGTYPE)
+
+CloudConfigMsgType = enum_type_wrapper.EnumTypeWrapper(_CLOUDCONFIGMSGTYPE)
+Success = 0
+InvalidParam = 1
+InvalidState = 2
+TypeCmdGetSetDetails = 0
+TypeRespGetSetDetails = 1
+
+
+
+_CMDGETSETDETAILS = _descriptor.Descriptor(
+ name='CmdGetSetDetails',
+ full_name='cloud.CmdGetSetDetails',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='UserID', full_name='cloud.CmdGetSetDetails.UserID', index=0,
+ number=1, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='SecretKey', full_name='cloud.CmdGetSetDetails.SecretKey', index=1,
+ number=2, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=22,
+ serialized_end=75,
+)
+
+
+_RESPGETSETDETAILS = _descriptor.Descriptor(
+ name='RespGetSetDetails',
+ full_name='cloud.RespGetSetDetails',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='Status', full_name='cloud.RespGetSetDetails.Status', index=0,
+ number=1, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='DeviceSecret', full_name='cloud.RespGetSetDetails.DeviceSecret', index=1,
+ number=2, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=77,
+ serialized_end=160,
+)
+
+
+_CLOUDCONFIGPAYLOAD = _descriptor.Descriptor(
+ name='CloudConfigPayload',
+ full_name='cloud.CloudConfigPayload',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='msg', full_name='cloud.CloudConfigPayload.msg', index=0,
+ number=1, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='cmd_get_set_details', full_name='cloud.CloudConfigPayload.cmd_get_set_details', index=1,
+ number=10, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='resp_get_set_details', full_name='cloud.CloudConfigPayload.resp_get_set_details', index=2,
+ number=11, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ _descriptor.OneofDescriptor(
+ name='payload', full_name='cloud.CloudConfigPayload.payload',
+ index=0, containing_type=None, fields=[]),
+ ],
+ serialized_start=163,
+ serialized_end=348,
+)
+
+_RESPGETSETDETAILS.fields_by_name['Status'].enum_type = _CLOUDCONFIGSTATUS
+_CLOUDCONFIGPAYLOAD.fields_by_name['msg'].enum_type = _CLOUDCONFIGMSGTYPE
+_CLOUDCONFIGPAYLOAD.fields_by_name['cmd_get_set_details'].message_type = _CMDGETSETDETAILS
+_CLOUDCONFIGPAYLOAD.fields_by_name['resp_get_set_details'].message_type = _RESPGETSETDETAILS
+_CLOUDCONFIGPAYLOAD.oneofs_by_name['payload'].fields.append(
+ _CLOUDCONFIGPAYLOAD.fields_by_name['cmd_get_set_details'])
+_CLOUDCONFIGPAYLOAD.fields_by_name['cmd_get_set_details'].containing_oneof = _CLOUDCONFIGPAYLOAD.oneofs_by_name['payload']
+_CLOUDCONFIGPAYLOAD.oneofs_by_name['payload'].fields.append(
+ _CLOUDCONFIGPAYLOAD.fields_by_name['resp_get_set_details'])
+_CLOUDCONFIGPAYLOAD.fields_by_name['resp_get_set_details'].containing_oneof = _CLOUDCONFIGPAYLOAD.oneofs_by_name['payload']
+DESCRIPTOR.message_types_by_name['CmdGetSetDetails'] = _CMDGETSETDETAILS
+DESCRIPTOR.message_types_by_name['RespGetSetDetails'] = _RESPGETSETDETAILS
+DESCRIPTOR.message_types_by_name['CloudConfigPayload'] = _CLOUDCONFIGPAYLOAD
+DESCRIPTOR.enum_types_by_name['CloudConfigStatus'] = _CLOUDCONFIGSTATUS
+DESCRIPTOR.enum_types_by_name['CloudConfigMsgType'] = _CLOUDCONFIGMSGTYPE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+CmdGetSetDetails = _reflection.GeneratedProtocolMessageType('CmdGetSetDetails', (_message.Message,), dict(
+ DESCRIPTOR = _CMDGETSETDETAILS,
+ __module__ = 'cloud_pb2'
+ # @@protoc_insertion_point(class_scope:cloud.CmdGetSetDetails)
+ ))
+_sym_db.RegisterMessage(CmdGetSetDetails)
+
+RespGetSetDetails = _reflection.GeneratedProtocolMessageType('RespGetSetDetails', (_message.Message,), dict(
+ DESCRIPTOR = _RESPGETSETDETAILS,
+ __module__ = 'cloud_pb2'
+ # @@protoc_insertion_point(class_scope:cloud.RespGetSetDetails)
+ ))
+_sym_db.RegisterMessage(RespGetSetDetails)
+
+CloudConfigPayload = _reflection.GeneratedProtocolMessageType('CloudConfigPayload', (_message.Message,), dict(
+ DESCRIPTOR = _CLOUDCONFIGPAYLOAD,
+ __module__ = 'cloud_pb2'
+ # @@protoc_insertion_point(class_scope:cloud.CloudConfigPayload)
+ ))
+_sym_db.RegisterMessage(CloudConfigPayload)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/cli/rmaker_tools/rmaker_prov/esp_rainmaker_prov.py b/cli/rmaker_tools/rmaker_prov/esp_rainmaker_prov.py
new file mode 100644
index 0000000..3a9a465
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/esp_rainmaker_prov.py
@@ -0,0 +1,311 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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
+from builtins import input
+import argparse
+import textwrap
+import time
+import os
+import sys
+from getpass import getpass
+
+
+try:
+ prov_path = os.path.dirname(__file__)
+ sys.path.insert(0, prov_path)
+ import prov.user_mapping as cloud_config_prov
+ import prov.prov_util as esp_prov
+except ImportError as err:
+ raise err
+
+# Set this to true to allow exceptions to be thrown
+config_throw_except = True
+
+
+def on_except(err):
+ if config_throw_except:
+ raise RuntimeError(err)
+ else:
+ print(err)
+
+
+def custom_config(tp, sec, custom_id, custom_key):
+ """
+ Send custom config data
+ """
+ try:
+ message = cloud_config_prov.custom_cloud_config_request(sec,
+ custom_id,
+ custom_key)
+ response = tp.send_data('cloud_user_assoc', message)
+ return cloud_config_prov.custom_cloud_config_response(sec, response)
+ except RuntimeError as e:
+ on_except(e)
+ return None
+
+
+def desc_format(*args):
+ """
+ Text Format to print the CLI help section for arguments
+ """
+ desc = ''
+ for arg in args:
+ desc += textwrap.fill(replace_whitespace=False, text=arg) + "\n"
+ return desc
+
+
+def get_wifi_creds_from_scanlist(transport_mode, obj_transport,
+ obj_security, userid, secretkey):
+ """
+ Displays a Wi-Fi scanlist and gets Wi-Fi creds as input from user
+ """
+ if not esp_prov.has_capability(obj_transport, 'wifi_scan'):
+ print("Wi-Fi Scan List is not supported by provisioning service")
+ print("Rerun esp_prov with SSID and Passphrase as argument")
+ exit(3)
+
+ while True:
+ print("Scanning Wi-Fi AP's...")
+ access_points = esp_prov.scan_wifi_APs(transport_mode,
+ obj_transport,
+ obj_security)
+ len_access_points = len(access_points)
+ end_time = time.time()
+ if access_points is None:
+ print("Scanning Wi-Fi AP's - Failed")
+ exit(8)
+
+ if len_access_points == 0:
+ print("No access_points found")
+ exit(9)
+
+ print("Select the Wi-Fi network from the following list:")
+ print("{0: >4} {1: <33} {2: <12} {3: >4} {4: <4} {5: <16}".format(
+ "S.N.", "SSID", "BSSID", "CHN", "RSSI", "AUTH"))
+ for i in range(len_access_points):
+ print("[{0: >2}] {1: <33} {2: <12} {3: >4} {4: <4} {5: <16}"
+ .format(i + 1, access_points[i]["ssid"],
+ access_points[i]["bssid"],
+ access_points[i]["channel"],
+ access_points[i]["rssi"],
+ access_points[i]["auth"]))
+
+ # Add option to join a new network which is not part of scan list
+ print("[{0: >2}] {1: <33}".format(len_access_points + 1,
+ "Join another network"))
+ while True:
+ try:
+ select = int(input("Select AP by number (0 to rescan) : "))
+ if select < 0 or select > len_access_points + 1:
+ raise ValueError
+ break
+ except ValueError:
+ print("Invalid selection! Retry")
+
+ if select != 0:
+ break
+
+ if select == len_access_points + 1:
+ ssid = input("Enter ssid :")
+ else:
+ ssid = access_points[select - 1]["ssid"]
+ prompt_str = "Enter passphrase for {0} : ".format(ssid)
+ passphrase = getpass(prompt_str)
+
+ return ssid, passphrase
+
+
+def provision_device(transport_mode, pop, userid, secretkey,
+ ssid=None, passphrase=None):
+ """
+ Wi-Fi Provision a device
+
+ :param transport_mode: The transport mode for communicating
+ with the device.
+ Can be either ble or softap.
+ :type transport_mode: str
+
+ :param pop: The Proof of Possession pin for the device.
+ :type pop: str
+
+ :param userid: The User's ID that will be used for User-Node mapping.
+ :type userid: str
+
+ :param secretkey: The randomly generated secret key that will be used
+ for User-Node mapping.
+ :type secretkey: str
+
+ :param ssid: Target network SSID. Can be used if you want to
+ skip the Wi-Fi scan list.,
+ defaults to 'None' i.e. uses Wi-Fi Scan list
+ :type ssid: str, optional
+
+ :param passphrase: Password for the network whose SSID has been provided.\
+ Required only if an SSID has been provided and the network
+ is not Open.,
+ defaults to 'None'
+ :type passphrase: str, optional
+
+ :return: nodeid (Node Identifier) on Success, None on Failure
+ :rtype: str | None
+ """
+ security_version = 1 # this utility should run with Security1
+ service_name = None # will use default (192.168.4.1:80)
+
+ obj_transport = esp_prov.get_transport(transport_mode, service_name)
+ if obj_transport is None:
+ print("Establishing connection to node - Failed")
+ return None
+
+ # First check if capabilities are supported or not
+ if not esp_prov.has_capability(obj_transport):
+ print('Security capabilities could not be determined.')
+ return None
+
+ # Ensure no_pop capability is not supported for Security Version1
+ if not esp_prov.has_capability(obj_transport, 'no_pop'):
+ if len(pop) == 0:
+ print("Proof of Possession argument not provided")
+ return None
+ else:
+ print("Invalid: no_pop capability is supported for Security Version 1")
+ return None
+
+ obj_security = esp_prov.get_security(security_version, pop)
+ if obj_security is None:
+ print("Invalid Security Version")
+ return None
+
+ if not esp_prov.establish_session(obj_transport, obj_security):
+ print("Establishing session - Failed")
+ print("Ensure that security scheme and\
+ proof of possession are correct")
+ return None
+ print("Establishing session - Successful")
+
+ if not (ssid and passphrase):
+ ssid, passphrase = get_wifi_creds_from_scanlist(transport_mode,
+ obj_transport,
+ obj_security,
+ userid,
+ secretkey)
+
+ status, nodeid = custom_config(obj_transport,
+ obj_security,
+ userid,
+ secretkey)
+ if status != 0:
+ print("Sending user information to node - Failed")
+ return None
+ print("Sending user information to node - Successful")
+
+ if not esp_prov.send_wifi_config(obj_transport,
+ obj_security,
+ ssid,
+ passphrase):
+ print("Sending Wi-Fi credentials to node - Failed")
+ return None
+ print("Sending Wi-Fi credentials to node - Successful")
+
+ if not esp_prov.apply_wifi_config(obj_transport, obj_security):
+ print("Applying Wi-Fi config to node - Failed")
+ return None
+ print("Applying Wi-Fi config to node - Successful")
+
+ while True:
+ time.sleep(5)
+ ret = esp_prov.get_wifi_config(obj_transport, obj_security)
+ if (ret == 1):
+ continue
+ elif (ret == 0):
+ print("Wi-Fi Provisioning Successful.")
+ return nodeid
+ else:
+ print("Wi-Fi Provisioning Failed.")
+ return None
+ break
+ print("Exiting Wi-Fi Provisioning.")
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description=desc_format(
+ 'ESP RainMaker Provisioning tool\
+ for configuring node '
+ 'running protocomm based\
+ provisioning service.'),
+ formatter_class=argparse.
+ RawTextHelpFormatter)
+
+ parser.add_argument("--transport", required=True, dest='mode', type=str,
+ help=desc_format(
+ 'Mode of transport over which provisioning\
+ is to be performed.',
+ 'This should be one of "softap" or "ble"'))
+
+ parser.add_argument("--pop", required=True,
+ dest='pop', type=str, default='',
+ help=desc_format(
+ 'This specifies the Proof of possession (PoP)'))
+
+ parser.add_argument("--userid", dest='userid',
+ required=True, type=str, default='',
+ help=desc_format(
+ 'Custom config data to be sent\
+ to device: UserID'))
+
+ parser.add_argument("--secretkey", dest='secretkey',
+ required=True, type=str, default='',
+ help=desc_format(
+ 'Custom config data to be sent\
+ to device: SecretKey'))
+
+ parser.add_argument("--ssid", dest='ssid', type=str, default='',
+ help=desc_format(
+ 'This configures the device to use\
+ SSID of the Wi-Fi network to which '
+ 'we would like it to connect to permanently,\
+ once provisioning is complete. '
+ 'If would prefer to use Wi-Fi scanning\
+ if supported by the provisioning service,\
+ this need not '
+ 'be specified.'))
+ # Eg. --ssid "MySSID" (double quotes needed if ssid has special characters)
+
+ parser.add_argument("--passphrase", dest='passphrase',
+ type=str, default='',
+ help=desc_format(
+ 'This configures the device to use Passphrase\
+ for the Wi-Fi network to which '
+ 'we would like it to connect to permanently,\
+ once provisioning is complete. '
+ 'If would prefer to use Wi-Fi scanning\
+ if supported by the provisioning service,\
+ this need not '
+ 'be specified'))
+
+ args = parser.parse_args()
+ print(args.ssid)
+ print(args.passphrase)
+ print(args.ssid and args.passphrase)
+
+ if not (args.userid and args.secretkey):
+ parser.error("Error. --userid and --secretkey are required.")
+
+ if (args.ssid or args.passphrase) and not (args.ssid and args.passphrase):
+ parser.error("Error. --ssid and --passphrase are required.")
+
+ provision_device(args.mode, args.pop, args.userid,
+ args.secretkey, args.ssid,
+ args.passphrase)
diff --git a/cli/rmaker_tools/rmaker_prov/proto/__init__.py b/cli/rmaker_tools/rmaker_prov/proto/__init__.py
new file mode 100644
index 0000000..03d51e3
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/proto/__init__.py
@@ -0,0 +1,45 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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 os, sys
+from rmaker_lib.logger import log
+
+def _load_source(name, path):
+ try:
+ from importlib.machinery import SourceFileLoader
+ return SourceFileLoader(name, path).load_module()
+ except ImportError:
+ # importlib.machinery doesn't exists in Python 2 so we will use imp (deprecated in Python 3)
+ import imp
+ return imp.load_source(name, path)
+ except Exception as load_source_err:
+ log.error(load_source_err)
+ sys.exit(1)
+
+protocomm_path = os.path.dirname(__file__) + "/../protocomm/"
+wifi_prov_path = os.path.dirname(__file__) + "/../wifi_provisioning/"
+
+# protocomm component related python files generated from .proto files
+constants_pb2 = _load_source("constants_pb2", protocomm_path + "python/constants_pb2.py")
+sec0_pb2 = _load_source("sec0_pb2", protocomm_path + "python/sec0_pb2.py")
+sec1_pb2 = _load_source("sec1_pb2", protocomm_path + "python/sec1_pb2.py")
+session_pb2 = _load_source("session_pb2", protocomm_path + "python/session_pb2.py")
+
+# wifi_provisioning component related python files generated from .proto files
+wifi_constants_pb2 = _load_source("wifi_constants_pb2", wifi_prov_path + "python/wifi_constants_pb2.py")
+wifi_config_pb2 = _load_source("wifi_config_pb2", wifi_prov_path + "python/wifi_config_pb2.py")
+wifi_scan_pb2 = _load_source("wifi_scan_pb2", wifi_prov_path + "python/wifi_scan_pb2.py")
+
+# custom_provisioning component related python files generated from .proto files
+custom_cloud_config_pb2 = _load_source("custom_cloud_config_pb2", os.path.join(os.path.dirname(__file__),"../") + "config/custom_cloud_config_pb2.py")
diff --git a/cli/rmaker_tools/rmaker_prov/protocomm/proto/constants.proto b/cli/rmaker_tools/rmaker_prov/protocomm/proto/constants.proto
new file mode 100644
index 0000000..c80d250
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/protocomm/proto/constants.proto
@@ -0,0 +1,14 @@
+syntax = "proto3";
+
+/* Allowed values for the status
+ * of a protocomm instance */
+enum Status {
+ Success = 0;
+ InvalidSecScheme = 1;
+ InvalidProto = 2;
+ TooManySessions = 3;
+ InvalidArgument = 4;
+ InternalError = 5;
+ CryptoError = 6;
+ InvalidSession = 7;
+}
diff --git a/cli/rmaker_tools/rmaker_prov/protocomm/proto/sec0.proto b/cli/rmaker_tools/rmaker_prov/protocomm/proto/sec0.proto
new file mode 100644
index 0000000..299fce7
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/protocomm/proto/sec0.proto
@@ -0,0 +1,28 @@
+syntax = "proto3";
+
+import "constants.proto";
+
+/* Data structure of Session command/request packet */
+message S0SessionCmd {
+
+}
+
+/* Data structure of Session response packet */
+message S0SessionResp {
+ Status status = 1;
+}
+
+/* A message must be of type Cmd or Resp */
+enum Sec0MsgType {
+ S0_Session_Command = 0;
+ S0_Session_Response = 1;
+}
+
+/* Payload structure of session data */
+message Sec0Payload {
+ Sec0MsgType msg = 1; /*!< Type of message */
+ oneof payload {
+ S0SessionCmd sc = 20; /*!< Payload data interpreted as Cmd */
+ S0SessionResp sr = 21; /*!< Payload data interpreted as Resp */
+ }
+}
diff --git a/cli/rmaker_tools/rmaker_prov/protocomm/proto/sec1.proto b/cli/rmaker_tools/rmaker_prov/protocomm/proto/sec1.proto
new file mode 100644
index 0000000..97e05ea
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/protocomm/proto/sec1.proto
@@ -0,0 +1,45 @@
+syntax = "proto3";
+
+import "constants.proto";
+
+/* Data structure of Session command1 packet */
+message SessionCmd1 {
+ bytes client_verify_data = 2;
+}
+
+/* Data structure of Session response1 packet */
+message SessionResp1 {
+ Status status = 1;
+ bytes device_verify_data = 3;
+}
+
+/* Data structure of Session command0 packet */
+message SessionCmd0 {
+ bytes client_pubkey = 1;
+}
+
+/* Data structure of Session response0 packet */
+message SessionResp0 {
+ Status status = 1;
+ bytes device_pubkey = 2;
+ bytes device_random = 3;
+}
+
+/* A message must be of type Cmd0 / Cmd1 / Resp0 / Resp1 */
+enum Sec1MsgType {
+ Session_Command0 = 0;
+ Session_Response0 = 1;
+ Session_Command1 = 2;
+ Session_Response1 = 3;
+}
+
+/* Payload structure of session data */
+message Sec1Payload {
+ Sec1MsgType msg = 1; /*!< Type of message */
+ oneof payload {
+ SessionCmd0 sc0 = 20; /*!< Payload data interpreted as Cmd0 */
+ SessionResp0 sr0 = 21; /*!< Payload data interpreted as Resp0 */
+ SessionCmd1 sc1 = 22; /*!< Payload data interpreted as Cmd1 */
+ SessionResp1 sr1 = 23; /*!< Payload data interpreted as Resp1 */
+ }
+}
diff --git a/cli/rmaker_tools/rmaker_prov/protocomm/proto/session.proto b/cli/rmaker_tools/rmaker_prov/protocomm/proto/session.proto
new file mode 100644
index 0000000..de267b1
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/protocomm/proto/session.proto
@@ -0,0 +1,21 @@
+syntax = "proto3";
+
+import "sec0.proto";
+import "sec1.proto";
+
+/* Allowed values for the type of security
+ * being used in a protocomm session */
+enum SecSchemeVersion {
+ SecScheme0 = 0; /*!< Unsecured - plaintext communication */
+ SecScheme1 = 1; /*!< Security scheme 1 - Curve25519 + AES-256-CTR*/
+}
+
+/* Data structure exchanged when establishing
+ * secure session between Host and Client */
+message SessionData {
+ SecSchemeVersion sec_ver = 2; /*!< Type of security */
+ oneof proto {
+ Sec0Payload sec0 = 10; /*!< Payload data in case of security 0 */
+ Sec1Payload sec1 = 11; /*!< Payload data in case of security 1 */
+ }
+}
diff --git a/cli/rmaker_tools/rmaker_prov/protocomm/python/constants_pb2.py b/cli/rmaker_tools/rmaker_prov/protocomm/python/constants_pb2.py
new file mode 100644
index 0000000..ed5a47f
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/protocomm/python/constants_pb2.py
@@ -0,0 +1,87 @@
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: constants.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+ name='constants.proto',
+ package='',
+ syntax='proto3',
+ serialized_pb=_b('\n\x0f\x63onstants.proto*\x9f\x01\n\x06Status\x12\x0b\n\x07Success\x10\x00\x12\x14\n\x10InvalidSecScheme\x10\x01\x12\x10\n\x0cInvalidProto\x10\x02\x12\x13\n\x0fTooManySessions\x10\x03\x12\x13\n\x0fInvalidArgument\x10\x04\x12\x11\n\rInternalError\x10\x05\x12\x0f\n\x0b\x43ryptoError\x10\x06\x12\x12\n\x0eInvalidSession\x10\x07\x62\x06proto3')
+)
+
+_STATUS = _descriptor.EnumDescriptor(
+ name='Status',
+ full_name='Status',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='Success', index=0, number=0,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='InvalidSecScheme', index=1, number=1,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='InvalidProto', index=2, number=2,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='TooManySessions', index=3, number=3,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='InvalidArgument', index=4, number=4,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='InternalError', index=5, number=5,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='CryptoError', index=6, number=6,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='InvalidSession', index=7, number=7,
+ options=None,
+ type=None),
+ ],
+ containing_type=None,
+ options=None,
+ serialized_start=20,
+ serialized_end=179,
+)
+_sym_db.RegisterEnumDescriptor(_STATUS)
+
+Status = enum_type_wrapper.EnumTypeWrapper(_STATUS)
+Success = 0
+InvalidSecScheme = 1
+InvalidProto = 2
+TooManySessions = 3
+InvalidArgument = 4
+InternalError = 5
+CryptoError = 6
+InvalidSession = 7
+
+
+DESCRIPTOR.enum_types_by_name['Status'] = _STATUS
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/cli/rmaker_tools/rmaker_prov/protocomm/python/sec0_pb2.py b/cli/rmaker_tools/rmaker_prov/protocomm/python/sec0_pb2.py
new file mode 100644
index 0000000..123d1dc
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/protocomm/python/sec0_pb2.py
@@ -0,0 +1,196 @@
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: sec0.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import constants_pb2 as constants__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+ name='sec0.proto',
+ package='',
+ syntax='proto3',
+ serialized_pb=_b('\n\nsec0.proto\x1a\x0f\x63onstants.proto\"\x0e\n\x0cS0SessionCmd\"(\n\rS0SessionResp\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\"n\n\x0bSec0Payload\x12\x19\n\x03msg\x18\x01 \x01(\x0e\x32\x0c.Sec0MsgType\x12\x1b\n\x02sc\x18\x14 \x01(\x0b\x32\r.S0SessionCmdH\x00\x12\x1c\n\x02sr\x18\x15 \x01(\x0b\x32\x0e.S0SessionRespH\x00\x42\t\n\x07payload*>\n\x0bSec0MsgType\x12\x16\n\x12S0_Session_Command\x10\x00\x12\x17\n\x13S0_Session_Response\x10\x01\x62\x06proto3')
+ ,
+ dependencies=[constants__pb2.DESCRIPTOR,])
+
+_SEC0MSGTYPE = _descriptor.EnumDescriptor(
+ name='Sec0MsgType',
+ full_name='Sec0MsgType',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='S0_Session_Command', index=0, number=0,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='S0_Session_Response', index=1, number=1,
+ options=None,
+ type=None),
+ ],
+ containing_type=None,
+ options=None,
+ serialized_start=201,
+ serialized_end=263,
+)
+_sym_db.RegisterEnumDescriptor(_SEC0MSGTYPE)
+
+Sec0MsgType = enum_type_wrapper.EnumTypeWrapper(_SEC0MSGTYPE)
+S0_Session_Command = 0
+S0_Session_Response = 1
+
+
+
+_S0SESSIONCMD = _descriptor.Descriptor(
+ name='S0SessionCmd',
+ full_name='S0SessionCmd',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=31,
+ serialized_end=45,
+)
+
+
+_S0SESSIONRESP = _descriptor.Descriptor(
+ name='S0SessionResp',
+ full_name='S0SessionResp',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='status', full_name='S0SessionResp.status', index=0,
+ number=1, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=47,
+ serialized_end=87,
+)
+
+
+_SEC0PAYLOAD = _descriptor.Descriptor(
+ name='Sec0Payload',
+ full_name='Sec0Payload',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='msg', full_name='Sec0Payload.msg', index=0,
+ number=1, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='sc', full_name='Sec0Payload.sc', index=1,
+ number=20, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='sr', full_name='Sec0Payload.sr', index=2,
+ number=21, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ _descriptor.OneofDescriptor(
+ name='payload', full_name='Sec0Payload.payload',
+ index=0, containing_type=None, fields=[]),
+ ],
+ serialized_start=89,
+ serialized_end=199,
+)
+
+_S0SESSIONRESP.fields_by_name['status'].enum_type = constants__pb2._STATUS
+_SEC0PAYLOAD.fields_by_name['msg'].enum_type = _SEC0MSGTYPE
+_SEC0PAYLOAD.fields_by_name['sc'].message_type = _S0SESSIONCMD
+_SEC0PAYLOAD.fields_by_name['sr'].message_type = _S0SESSIONRESP
+_SEC0PAYLOAD.oneofs_by_name['payload'].fields.append(
+ _SEC0PAYLOAD.fields_by_name['sc'])
+_SEC0PAYLOAD.fields_by_name['sc'].containing_oneof = _SEC0PAYLOAD.oneofs_by_name['payload']
+_SEC0PAYLOAD.oneofs_by_name['payload'].fields.append(
+ _SEC0PAYLOAD.fields_by_name['sr'])
+_SEC0PAYLOAD.fields_by_name['sr'].containing_oneof = _SEC0PAYLOAD.oneofs_by_name['payload']
+DESCRIPTOR.message_types_by_name['S0SessionCmd'] = _S0SESSIONCMD
+DESCRIPTOR.message_types_by_name['S0SessionResp'] = _S0SESSIONRESP
+DESCRIPTOR.message_types_by_name['Sec0Payload'] = _SEC0PAYLOAD
+DESCRIPTOR.enum_types_by_name['Sec0MsgType'] = _SEC0MSGTYPE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+S0SessionCmd = _reflection.GeneratedProtocolMessageType('S0SessionCmd', (_message.Message,), dict(
+ DESCRIPTOR = _S0SESSIONCMD,
+ __module__ = 'sec0_pb2'
+ # @@protoc_insertion_point(class_scope:S0SessionCmd)
+ ))
+_sym_db.RegisterMessage(S0SessionCmd)
+
+S0SessionResp = _reflection.GeneratedProtocolMessageType('S0SessionResp', (_message.Message,), dict(
+ DESCRIPTOR = _S0SESSIONRESP,
+ __module__ = 'sec0_pb2'
+ # @@protoc_insertion_point(class_scope:S0SessionResp)
+ ))
+_sym_db.RegisterMessage(S0SessionResp)
+
+Sec0Payload = _reflection.GeneratedProtocolMessageType('Sec0Payload', (_message.Message,), dict(
+ DESCRIPTOR = _SEC0PAYLOAD,
+ __module__ = 'sec0_pb2'
+ # @@protoc_insertion_point(class_scope:Sec0Payload)
+ ))
+_sym_db.RegisterMessage(Sec0Payload)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/cli/rmaker_tools/rmaker_prov/protocomm/python/sec1_pb2.py b/cli/rmaker_tools/rmaker_prov/protocomm/python/sec1_pb2.py
new file mode 100644
index 0000000..cc305b8
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/protocomm/python/sec1_pb2.py
@@ -0,0 +1,335 @@
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: sec1.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import constants_pb2 as constants__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+ name='sec1.proto',
+ package='',
+ syntax='proto3',
+ serialized_pb=_b('\n\nsec1.proto\x1a\x0f\x63onstants.proto\")\n\x0bSessionCmd1\x12\x1a\n\x12\x63lient_verify_data\x18\x02 \x01(\x0c\"C\n\x0cSessionResp1\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\x12\x1a\n\x12\x64\x65vice_verify_data\x18\x03 \x01(\x0c\"$\n\x0bSessionCmd0\x12\x15\n\rclient_pubkey\x18\x01 \x01(\x0c\"U\n\x0cSessionResp0\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\x12\x15\n\rdevice_pubkey\x18\x02 \x01(\x0c\x12\x15\n\rdevice_random\x18\x03 \x01(\x0c\"\xa9\x01\n\x0bSec1Payload\x12\x19\n\x03msg\x18\x01 \x01(\x0e\x32\x0c.Sec1MsgType\x12\x1b\n\x03sc0\x18\x14 \x01(\x0b\x32\x0c.SessionCmd0H\x00\x12\x1c\n\x03sr0\x18\x15 \x01(\x0b\x32\r.SessionResp0H\x00\x12\x1b\n\x03sc1\x18\x16 \x01(\x0b\x32\x0c.SessionCmd1H\x00\x12\x1c\n\x03sr1\x18\x17 \x01(\x0b\x32\r.SessionResp1H\x00\x42\t\n\x07payload*g\n\x0bSec1MsgType\x12\x14\n\x10Session_Command0\x10\x00\x12\x15\n\x11Session_Response0\x10\x01\x12\x14\n\x10Session_Command1\x10\x02\x12\x15\n\x11Session_Response1\x10\x03\x62\x06proto3')
+ ,
+ dependencies=[constants__pb2.DESCRIPTOR,])
+
+_SEC1MSGTYPE = _descriptor.EnumDescriptor(
+ name='Sec1MsgType',
+ full_name='Sec1MsgType',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='Session_Command0', index=0, number=0,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='Session_Response0', index=1, number=1,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='Session_Command1', index=2, number=2,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='Session_Response1', index=3, number=3,
+ options=None,
+ type=None),
+ ],
+ containing_type=None,
+ options=None,
+ serialized_start=440,
+ serialized_end=543,
+)
+_sym_db.RegisterEnumDescriptor(_SEC1MSGTYPE)
+
+Sec1MsgType = enum_type_wrapper.EnumTypeWrapper(_SEC1MSGTYPE)
+Session_Command0 = 0
+Session_Response0 = 1
+Session_Command1 = 2
+Session_Response1 = 3
+
+
+
+_SESSIONCMD1 = _descriptor.Descriptor(
+ name='SessionCmd1',
+ full_name='SessionCmd1',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='client_verify_data', full_name='SessionCmd1.client_verify_data', index=0,
+ number=2, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=31,
+ serialized_end=72,
+)
+
+
+_SESSIONRESP1 = _descriptor.Descriptor(
+ name='SessionResp1',
+ full_name='SessionResp1',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='status', full_name='SessionResp1.status', index=0,
+ number=1, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='device_verify_data', full_name='SessionResp1.device_verify_data', index=1,
+ number=3, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=74,
+ serialized_end=141,
+)
+
+
+_SESSIONCMD0 = _descriptor.Descriptor(
+ name='SessionCmd0',
+ full_name='SessionCmd0',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='client_pubkey', full_name='SessionCmd0.client_pubkey', index=0,
+ number=1, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=143,
+ serialized_end=179,
+)
+
+
+_SESSIONRESP0 = _descriptor.Descriptor(
+ name='SessionResp0',
+ full_name='SessionResp0',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='status', full_name='SessionResp0.status', index=0,
+ number=1, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='device_pubkey', full_name='SessionResp0.device_pubkey', index=1,
+ number=2, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='device_random', full_name='SessionResp0.device_random', index=2,
+ number=3, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=181,
+ serialized_end=266,
+)
+
+
+_SEC1PAYLOAD = _descriptor.Descriptor(
+ name='Sec1Payload',
+ full_name='Sec1Payload',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='msg', full_name='Sec1Payload.msg', index=0,
+ number=1, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='sc0', full_name='Sec1Payload.sc0', index=1,
+ number=20, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='sr0', full_name='Sec1Payload.sr0', index=2,
+ number=21, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='sc1', full_name='Sec1Payload.sc1', index=3,
+ number=22, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='sr1', full_name='Sec1Payload.sr1', index=4,
+ number=23, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ _descriptor.OneofDescriptor(
+ name='payload', full_name='Sec1Payload.payload',
+ index=0, containing_type=None, fields=[]),
+ ],
+ serialized_start=269,
+ serialized_end=438,
+)
+
+_SESSIONRESP1.fields_by_name['status'].enum_type = constants__pb2._STATUS
+_SESSIONRESP0.fields_by_name['status'].enum_type = constants__pb2._STATUS
+_SEC1PAYLOAD.fields_by_name['msg'].enum_type = _SEC1MSGTYPE
+_SEC1PAYLOAD.fields_by_name['sc0'].message_type = _SESSIONCMD0
+_SEC1PAYLOAD.fields_by_name['sr0'].message_type = _SESSIONRESP0
+_SEC1PAYLOAD.fields_by_name['sc1'].message_type = _SESSIONCMD1
+_SEC1PAYLOAD.fields_by_name['sr1'].message_type = _SESSIONRESP1
+_SEC1PAYLOAD.oneofs_by_name['payload'].fields.append(
+ _SEC1PAYLOAD.fields_by_name['sc0'])
+_SEC1PAYLOAD.fields_by_name['sc0'].containing_oneof = _SEC1PAYLOAD.oneofs_by_name['payload']
+_SEC1PAYLOAD.oneofs_by_name['payload'].fields.append(
+ _SEC1PAYLOAD.fields_by_name['sr0'])
+_SEC1PAYLOAD.fields_by_name['sr0'].containing_oneof = _SEC1PAYLOAD.oneofs_by_name['payload']
+_SEC1PAYLOAD.oneofs_by_name['payload'].fields.append(
+ _SEC1PAYLOAD.fields_by_name['sc1'])
+_SEC1PAYLOAD.fields_by_name['sc1'].containing_oneof = _SEC1PAYLOAD.oneofs_by_name['payload']
+_SEC1PAYLOAD.oneofs_by_name['payload'].fields.append(
+ _SEC1PAYLOAD.fields_by_name['sr1'])
+_SEC1PAYLOAD.fields_by_name['sr1'].containing_oneof = _SEC1PAYLOAD.oneofs_by_name['payload']
+DESCRIPTOR.message_types_by_name['SessionCmd1'] = _SESSIONCMD1
+DESCRIPTOR.message_types_by_name['SessionResp1'] = _SESSIONRESP1
+DESCRIPTOR.message_types_by_name['SessionCmd0'] = _SESSIONCMD0
+DESCRIPTOR.message_types_by_name['SessionResp0'] = _SESSIONRESP0
+DESCRIPTOR.message_types_by_name['Sec1Payload'] = _SEC1PAYLOAD
+DESCRIPTOR.enum_types_by_name['Sec1MsgType'] = _SEC1MSGTYPE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+SessionCmd1 = _reflection.GeneratedProtocolMessageType('SessionCmd1', (_message.Message,), dict(
+ DESCRIPTOR = _SESSIONCMD1,
+ __module__ = 'sec1_pb2'
+ # @@protoc_insertion_point(class_scope:SessionCmd1)
+ ))
+_sym_db.RegisterMessage(SessionCmd1)
+
+SessionResp1 = _reflection.GeneratedProtocolMessageType('SessionResp1', (_message.Message,), dict(
+ DESCRIPTOR = _SESSIONRESP1,
+ __module__ = 'sec1_pb2'
+ # @@protoc_insertion_point(class_scope:SessionResp1)
+ ))
+_sym_db.RegisterMessage(SessionResp1)
+
+SessionCmd0 = _reflection.GeneratedProtocolMessageType('SessionCmd0', (_message.Message,), dict(
+ DESCRIPTOR = _SESSIONCMD0,
+ __module__ = 'sec1_pb2'
+ # @@protoc_insertion_point(class_scope:SessionCmd0)
+ ))
+_sym_db.RegisterMessage(SessionCmd0)
+
+SessionResp0 = _reflection.GeneratedProtocolMessageType('SessionResp0', (_message.Message,), dict(
+ DESCRIPTOR = _SESSIONRESP0,
+ __module__ = 'sec1_pb2'
+ # @@protoc_insertion_point(class_scope:SessionResp0)
+ ))
+_sym_db.RegisterMessage(SessionResp0)
+
+Sec1Payload = _reflection.GeneratedProtocolMessageType('Sec1Payload', (_message.Message,), dict(
+ DESCRIPTOR = _SEC1PAYLOAD,
+ __module__ = 'sec1_pb2'
+ # @@protoc_insertion_point(class_scope:Sec1Payload)
+ ))
+_sym_db.RegisterMessage(Sec1Payload)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/cli/rmaker_tools/rmaker_prov/protocomm/python/session_pb2.py b/cli/rmaker_tools/rmaker_prov/protocomm/python/session_pb2.py
new file mode 100644
index 0000000..a30e794
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/protocomm/python/session_pb2.py
@@ -0,0 +1,125 @@
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: session.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import sec0_pb2 as sec0__pb2
+import sec1_pb2 as sec1__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+ name='session.proto',
+ package='',
+ syntax='proto3',
+ serialized_pb=_b('\n\rsession.proto\x1a\nsec0.proto\x1a\nsec1.proto\"v\n\x0bSessionData\x12\"\n\x07sec_ver\x18\x02 \x01(\x0e\x32\x11.SecSchemeVersion\x12\x1c\n\x04sec0\x18\n \x01(\x0b\x32\x0c.Sec0PayloadH\x00\x12\x1c\n\x04sec1\x18\x0b \x01(\x0b\x32\x0c.Sec1PayloadH\x00\x42\x07\n\x05proto*2\n\x10SecSchemeVersion\x12\x0e\n\nSecScheme0\x10\x00\x12\x0e\n\nSecScheme1\x10\x01\x62\x06proto3')
+ ,
+ dependencies=[sec0__pb2.DESCRIPTOR,sec1__pb2.DESCRIPTOR,])
+
+_SECSCHEMEVERSION = _descriptor.EnumDescriptor(
+ name='SecSchemeVersion',
+ full_name='SecSchemeVersion',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='SecScheme0', index=0, number=0,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='SecScheme1', index=1, number=1,
+ options=None,
+ type=None),
+ ],
+ containing_type=None,
+ options=None,
+ serialized_start=161,
+ serialized_end=211,
+)
+_sym_db.RegisterEnumDescriptor(_SECSCHEMEVERSION)
+
+SecSchemeVersion = enum_type_wrapper.EnumTypeWrapper(_SECSCHEMEVERSION)
+SecScheme0 = 0
+SecScheme1 = 1
+
+
+
+_SESSIONDATA = _descriptor.Descriptor(
+ name='SessionData',
+ full_name='SessionData',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='sec_ver', full_name='SessionData.sec_ver', index=0,
+ number=2, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='sec0', full_name='SessionData.sec0', index=1,
+ number=10, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='sec1', full_name='SessionData.sec1', index=2,
+ number=11, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ _descriptor.OneofDescriptor(
+ name='proto', full_name='SessionData.proto',
+ index=0, containing_type=None, fields=[]),
+ ],
+ serialized_start=41,
+ serialized_end=159,
+)
+
+_SESSIONDATA.fields_by_name['sec_ver'].enum_type = _SECSCHEMEVERSION
+_SESSIONDATA.fields_by_name['sec0'].message_type = sec0__pb2._SEC0PAYLOAD
+_SESSIONDATA.fields_by_name['sec1'].message_type = sec1__pb2._SEC1PAYLOAD
+_SESSIONDATA.oneofs_by_name['proto'].fields.append(
+ _SESSIONDATA.fields_by_name['sec0'])
+_SESSIONDATA.fields_by_name['sec0'].containing_oneof = _SESSIONDATA.oneofs_by_name['proto']
+_SESSIONDATA.oneofs_by_name['proto'].fields.append(
+ _SESSIONDATA.fields_by_name['sec1'])
+_SESSIONDATA.fields_by_name['sec1'].containing_oneof = _SESSIONDATA.oneofs_by_name['proto']
+DESCRIPTOR.message_types_by_name['SessionData'] = _SESSIONDATA
+DESCRIPTOR.enum_types_by_name['SecSchemeVersion'] = _SECSCHEMEVERSION
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+SessionData = _reflection.GeneratedProtocolMessageType('SessionData', (_message.Message,), dict(
+ DESCRIPTOR = _SESSIONDATA,
+ __module__ = 'session_pb2'
+ # @@protoc_insertion_point(class_scope:SessionData)
+ ))
+_sym_db.RegisterMessage(SessionData)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/cli/rmaker_tools/rmaker_prov/prov/__init__.py b/cli/rmaker_tools/rmaker_prov/prov/__init__.py
new file mode 100644
index 0000000..e95cf46
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/prov/__init__.py
@@ -0,0 +1,16 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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 .wifi_prov import * # noqa F403
+from .wifi_scan import * # noqa F403
diff --git a/cli/rmaker_tools/rmaker_prov/prov/prov_util.py b/cli/rmaker_tools/rmaker_prov/prov/prov_util.py
new file mode 100644
index 0000000..ed83258
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/prov/prov_util.py
@@ -0,0 +1,281 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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
+from builtins import input
+import argparse
+import textwrap
+import time
+import os
+import sys
+import json
+from getpass import getpass
+
+try:
+ import security
+ import transport
+ import prov
+except ImportError as err:
+ raise err
+
+# Set this to true to allow exceptions to be thrown
+config_throw_except = True
+
+def on_except(err):
+ if config_throw_except:
+ raise RuntimeError(err)
+ else:
+ print(err)
+
+def get_security(secver, pop=None, verbose=False):
+ """
+ Get Security based on input parameters
+ `secver`: Security Version (Security0/Security1)
+ `pop`: Proof Of Possession
+ """
+ if secver == 1:
+ return security.Security1(pop, verbose)
+ elif secver == 0:
+ return security.Security0(verbose)
+ return None
+
+def get_transport(sel_transport, service_name):
+ """
+ Get object of class `Transport` based on input parameters
+ `sel_transport` - Transport Mode (softap/ble)
+ `service_name` - Service Name to connect to
+ """
+ try:
+ tp = None
+ if (sel_transport == 'softap'):
+ if service_name is None:
+ service_name = '192.168.4.1:80'
+ tp = transport.Transport_HTTP(service_name)
+ elif (sel_transport == 'ble'):
+ if service_name is None:
+ raise RuntimeError('"--service_name" must be specified for ble transport')
+ # BLE client is now capable of automatically figuring out
+ # the primary service from the advertisement data and the
+ # characteristics corresponding to each endpoint.
+ # Below, the service_uuid field and 16bit UUIDs in the nu_lookup
+ # table are provided only to support devices running older firmware,
+ # in which case, the automated discovery will fail and the client
+ # will fallback to using the provided UUIDs instead
+ nu_lookup = {'prov-session': 'ff51', 'prov-config': 'ff52', 'proto-ver': 'ff53'}
+ tp = transport.Transport_BLE(devname=service_name,
+ service_uuid='0000ffff-0000-1000-8000-00805f9b34fb',
+ nu_lookup=nu_lookup)
+ elif (sel_transport == 'console'):
+ tp = transport.Transport_Console()
+ return tp
+ except RuntimeError as e:
+ on_except(e)
+ return None
+
+def version_match(tp, protover, verbose=False):
+ """
+ Check version match
+ """
+ try:
+ response = tp.send_data('proto-ver', protover)
+
+ if verbose:
+ print("proto-ver response : ", response)
+
+ # First assume this to be a simple version string
+ if response.lower() == protover.lower():
+ return True
+
+ try:
+ # Else interpret this as JSON structure containing
+ # information with versions and capabilities of both
+ # provisioning service and application
+ info = json.loads(response)
+ if info['prov']['ver'].lower() == protover.lower():
+ return True
+
+ except ValueError:
+ # If decoding as JSON fails, it means that capabilities
+ # are not supported
+ return False
+
+ except Exception as e:
+ on_except(e)
+ return None
+
+def has_capability(tp, capability='none', verbose=False):
+ """
+ Check if Transport object `tp` has capabilities
+ as given in input parameter `capability`
+ """
+ # Note : default value of `capability` argument cannot be empty string
+ # because protocomm_httpd expects non zero content lengths
+ try:
+ response = tp.send_data('proto-ver', capability)
+
+ if verbose:
+ print("proto-ver response : ", response)
+
+ try:
+ # Interpret this as JSON structure containing
+ # information with versions and capabilities of both
+ # provisioning service and application
+ info = json.loads(response)
+ supported_capabilities = info['prov']['cap']
+ if capability.lower() == 'none':
+ # No specific capability to check, but capabilities
+ # feature is present so return True
+ return True
+ elif capability in supported_capabilities:
+ return True
+ return False
+
+ except ValueError:
+ # If decoding as JSON fails, it means that capabilities
+ # are not supported
+ return False
+
+ except RuntimeError as e:
+ on_except(e)
+
+ return False
+
+def get_version(tp):
+ """
+ Get Version based on input parameters
+ `tp` - Object of class: Transport
+ """
+ response = None
+ try:
+ response = tp.send_data('proto-ver', '---')
+ except RuntimeError as e:
+ on_except(e)
+ response = ''
+ return response
+
+def establish_session(tp, sec):
+ """
+ Establish Provisioning Session based on input parameters
+ `tp` - Object of class: Transport
+ `sec`- Object of class: Security
+ """
+ try:
+ response = None
+ while True:
+ request = sec.security_session(response)
+ if request is None:
+ break
+ response = tp.send_data('prov-session', request)
+ if (response is None):
+ return False
+ return True
+ except RuntimeError as e:
+ on_except(e)
+ return None
+
+def scan_wifi_APs(sel_transport, tp, sec):
+ """
+ Scans Wi-Fi AP's based on input parameters
+ `sel_transport` - Transport Mode (softap/ble)
+ `tp` - Object of class: Transport
+ `sec`- Object of class: Security
+ """
+ APs = []
+ group_channels = 0
+ readlen = 100
+ if sel_transport == 'softap':
+ # In case of softAP we must perform the scan on individual channels, one by one,
+ # so that the Wi-Fi controller gets ample time to send out beacons (necessary to
+ # maintain connectivity with authenticated stations. As scanning one channel at a
+ # time will be slow, we can group more than one channels to be scanned in quick
+ # succession, hence speeding up the scan process. Though if too many channels are
+ # present in a group, the controller may again miss out on sending beacons. Hence,
+ # the application must should use an optimum value. The following value usually
+ # works out in most cases
+ group_channels = 5
+ elif sel_transport == 'ble':
+ # Read at most 4 entries at a time. This is because if we are using BLE transport
+ # then the response packet size should not exceed the present limit of 256 bytes of
+ # characteristic value imposed by protocomm_ble. This limit may be removed in the
+ # future
+ readlen = 4
+ try:
+ message = prov.scan_start_request(sec, blocking=True, group_channels=group_channels)
+ start_time = time.time()
+ response = tp.send_data('prov-scan', message)
+ prov.scan_start_response(sec, response)
+
+ message = prov.scan_status_request(sec)
+ response = tp.send_data('prov-scan', message)
+ result = prov.scan_status_response(sec, response)
+ if result["count"] != 0:
+ index = 0
+ remaining = result["count"]
+ while remaining:
+ count = [remaining, readlen][remaining > readlen]
+ message = prov.scan_result_request(sec, index, count)
+ response = tp.send_data('prov-scan', message)
+ APs += prov.scan_result_response(sec, response)
+ remaining -= count
+ index += count
+
+ except RuntimeError as e:
+ on_except(e)
+ return None
+
+ return APs
+
+def send_wifi_config(tp, sec, ssid, passphrase):
+ """
+ Send Wi-Fi config based on input parameters
+ `tp` - Object of class: Transport
+ `sec`- Object of class: Security
+ `ssid` - ssid of Wi-Fi network to configure
+ `passphrase` - passphrase of Wi-Fi network to configure
+ """
+ try:
+ message = prov.config_set_config_request(sec, ssid, passphrase)
+ response = tp.send_data('prov-config', message)
+ return (prov.config_set_config_response(sec, response) == 0)
+ except RuntimeError as e:
+ on_except(e)
+ return None
+
+def apply_wifi_config(tp, sec):
+ """
+ Apply Wi-Fi config based on input parameters
+ `tp` - Object of class: Transport
+ `sec`- Object of class: Security
+ """
+ try:
+ message = prov.config_apply_config_request(sec)
+ response = tp.send_data('prov-config', message)
+ return (prov.config_apply_config_response(sec, response) == 0)
+ except RuntimeError as e:
+ on_except(e)
+ return None
+
+def get_wifi_config(tp, sec):
+ """
+ Get Wi-Fi config based on input parameters
+ `tp` - Object of class: Transport
+ `sec`- Object of class: Security
+ """
+ try:
+ message = prov.config_get_status_request(sec)
+ response = tp.send_data('prov-config', message)
+ return prov.config_get_status_response(sec, response)
+ except RuntimeError as e:
+ on_except(e)
+ return None
diff --git a/cli/rmaker_tools/rmaker_prov/prov/user_mapping.py b/cli/rmaker_tools/rmaker_prov/prov/user_mapping.py
new file mode 100644
index 0000000..8c3bd98
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/prov/user_mapping.py
@@ -0,0 +1,55 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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.
+
+# APIs for interpreting and creating protobuf packets for `custom-config` protocomm endpoint
+
+from __future__ import print_function
+from future.utils import tobytes
+import sys
+import os
+prov_path = os.path.join(os.path.dirname(__file__),"../")
+sys.path.insert(0, prov_path)
+
+import utils
+
+#import sys
+#sys.path.append('../')
+import proto
+
+
+def print_verbose(security_ctx, data):
+ if (security_ctx.verbose):
+ print("++++ " + data + " ++++")
+
+
+def custom_cloud_config_request(security_ctx, userid, secretkey):
+ # Form protobuf request packet from custom-config data
+ cmd = proto.custom_cloud_config_pb2.CloudConfigPayload()
+ cmd.msg = proto.custom_cloud_config_pb2.TypeCmdGetSetDetails
+ cmd.cmd_get_set_details.UserID = tobytes(userid)
+ cmd.cmd_get_set_details.SecretKey = tobytes(secretkey)
+
+ enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()).decode('latin-1')
+ print_verbose(security_ctx, "Client -> Device (CustomConfig cmd) " + utils.str_to_hexstr(enc_cmd))
+ return enc_cmd
+
+def custom_cloud_config_response(security_ctx, response_data):
+ # Interpret protobuf response packet
+ decrypt = security_ctx.decrypt_data(tobytes(response_data))
+ cmd_resp = proto.custom_cloud_config_pb2.CloudConfigPayload()
+ cmd_resp.ParseFromString(decrypt)
+ print_verbose(security_ctx, "CustomConfig msg value " + str(cmd_resp.msg))
+ print_verbose(security_ctx, "CustomConfig Status " + str(cmd_resp.resp_get_set_details.Status))
+ print_verbose(security_ctx, "CustomConfig Device Secret " + str(cmd_resp.resp_get_set_details.DeviceSecret))
+ return cmd_resp.resp_get_set_details.Status, cmd_resp.resp_get_set_details.DeviceSecret
diff --git a/cli/rmaker_tools/rmaker_prov/prov/wifi_prov.py b/cli/rmaker_tools/rmaker_prov/prov/wifi_prov.py
new file mode 100644
index 0000000..eea45a5
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/prov/wifi_prov.py
@@ -0,0 +1,97 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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.
+
+# APIs for interpreting and creating protobuf packets for Wi-Fi provisioning
+
+from __future__ import print_function
+from future.utils import tobytes
+
+import utils
+import proto
+
+
+def print_verbose(security_ctx, data):
+ if (security_ctx.verbose):
+ print("++++ " + data + " ++++")
+
+
+def config_get_status_request(security_ctx):
+ # Form protobuf request packet for GetStatus command
+ cfg1 = proto.wifi_config_pb2.WiFiConfigPayload()
+ cfg1.msg = proto.wifi_config_pb2.TypeCmdGetStatus
+ cmd_get_status = proto.wifi_config_pb2.CmdGetStatus()
+ cfg1.cmd_get_status.MergeFrom(cmd_get_status)
+ encrypted_cfg = security_ctx.encrypt_data(cfg1.SerializeToString()).decode('latin-1')
+ print_verbose(security_ctx, "Client -> Device (Encrypted CmdGetStatus) " + utils.str_to_hexstr(encrypted_cfg))
+ return encrypted_cfg
+
+
+def config_get_status_response(security_ctx, response_data):
+ # Interpret protobuf response packet from GetStatus command
+ decrypted_message = security_ctx.decrypt_data(tobytes(response_data))
+ cmd_resp1 = proto.wifi_config_pb2.WiFiConfigPayload()
+ cmd_resp1.ParseFromString(decrypted_message)
+ print_verbose(security_ctx, "Response type " + str(cmd_resp1.msg))
+ print_verbose(security_ctx, "Response status " + str(cmd_resp1.resp_get_status.status))
+ if cmd_resp1.resp_get_status.sta_state == 0:
+ print("Checking Wi-Fi connection - Connected")
+ elif cmd_resp1.resp_get_status.sta_state == 1:
+ print("Checking Wi-Fi connection - Connecting")
+ elif cmd_resp1.resp_get_status.sta_state == 2:
+ print("Checking Wi-Fi connection - Disconnected")
+ elif cmd_resp1.resp_get_status.sta_state == 3:
+ print("Checking Wi-Fi connection - Failed")
+ if cmd_resp1.resp_get_status.fail_reason == 0:
+ print("Failure reason - Incorrect Password")
+ elif cmd_resp1.resp_get_status.fail_reason == 1:
+ print("Failure reason - Incorrect SSID")
+ return cmd_resp1.resp_get_status.sta_state
+
+
+def config_set_config_request(security_ctx, ssid, passphrase):
+ # Form protobuf request packet for SetConfig command
+ cmd = proto.wifi_config_pb2.WiFiConfigPayload()
+ cmd.msg = proto.wifi_config_pb2.TypeCmdSetConfig
+ cmd.cmd_set_config.ssid = tobytes(ssid)
+ cmd.cmd_set_config.passphrase = tobytes(passphrase)
+ enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()).decode('latin-1')
+ print_verbose(security_ctx, "Client -> Device (SetConfig cmd) " + utils.str_to_hexstr(enc_cmd))
+ return enc_cmd
+
+
+def config_set_config_response(security_ctx, response_data):
+ # Interpret protobuf response packet from SetConfig command
+ decrypt = security_ctx.decrypt_data(tobytes(response_data))
+ cmd_resp4 = proto.wifi_config_pb2.WiFiConfigPayload()
+ cmd_resp4.ParseFromString(decrypt)
+ print_verbose(security_ctx, "SetConfig status " + str(cmd_resp4.resp_set_config.status))
+ return cmd_resp4.resp_set_config.status
+
+
+def config_apply_config_request(security_ctx):
+ # Form protobuf request packet for ApplyConfig command
+ cmd = proto.wifi_config_pb2.WiFiConfigPayload()
+ cmd.msg = proto.wifi_config_pb2.TypeCmdApplyConfig
+ enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()).decode('latin-1')
+ print_verbose(security_ctx, "Client -> Device (ApplyConfig cmd) " + utils.str_to_hexstr(enc_cmd))
+ return enc_cmd
+
+
+def config_apply_config_response(security_ctx, response_data):
+ # Interpret protobuf response packet from ApplyConfig command
+ decrypt = security_ctx.decrypt_data(tobytes(response_data))
+ cmd_resp5 = proto.wifi_config_pb2.WiFiConfigPayload()
+ cmd_resp5.ParseFromString(decrypt)
+ print_verbose(security_ctx, "ApplyConfig status " + str(cmd_resp5.resp_apply_config.status))
+ return cmd_resp5.resp_apply_config.status
diff --git a/cli/rmaker_tools/rmaker_prov/prov/wifi_scan.py b/cli/rmaker_tools/rmaker_prov/prov/wifi_scan.py
new file mode 100644
index 0000000..472f0a7
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/prov/wifi_scan.py
@@ -0,0 +1,104 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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.
+
+# APIs for interpreting and creating protobuf packets for Wi-Fi Scanning
+
+from __future__ import print_function
+from future.utils import tobytes
+
+import utils
+import proto
+
+
+def print_verbose(security_ctx, data):
+ if (security_ctx.verbose):
+ print("++++ " + data + " ++++")
+
+
+def scan_start_request(security_ctx, blocking=True, passive=False, group_channels=5, period_ms=120):
+ # Form protobuf request packet for ScanStart command
+ cmd = proto.wifi_scan_pb2.WiFiScanPayload()
+ cmd.msg = proto.wifi_scan_pb2.TypeCmdScanStart
+ cmd.cmd_scan_start.blocking = blocking
+ cmd.cmd_scan_start.passive = passive
+ cmd.cmd_scan_start.group_channels = group_channels
+ cmd.cmd_scan_start.period_ms = period_ms
+ enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()).decode('latin-1')
+ print_verbose(security_ctx, "Client -> Device (Encrypted CmdScanStart) " + utils.str_to_hexstr(enc_cmd))
+ return enc_cmd
+
+
+def scan_start_response(security_ctx, response_data):
+ # Interpret protobuf response packet from ScanStart command
+ dec_resp = security_ctx.decrypt_data(tobytes(response_data))
+ resp = proto.wifi_scan_pb2.WiFiScanPayload()
+ resp.ParseFromString(dec_resp)
+ print_verbose(security_ctx, "ScanStart status " + str(resp.status))
+ if resp.status != 0:
+ raise RuntimeError
+
+
+def scan_status_request(security_ctx):
+ # Form protobuf request packet for ScanStatus command
+ cmd = proto.wifi_scan_pb2.WiFiScanPayload()
+ cmd.msg = proto.wifi_scan_pb2.TypeCmdScanStatus
+ enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()).decode('latin-1')
+ print_verbose(security_ctx, "Client -> Device (Encrypted CmdScanStatus) " + utils.str_to_hexstr(enc_cmd))
+ return enc_cmd
+
+
+def scan_status_response(security_ctx, response_data):
+ # Interpret protobuf response packet from ScanStatus command
+ dec_resp = security_ctx.decrypt_data(tobytes(response_data))
+ resp = proto.wifi_scan_pb2.WiFiScanPayload()
+ resp.ParseFromString(dec_resp)
+ print_verbose(security_ctx, "ScanStatus status " + str(resp.status))
+ if resp.status != 0:
+ raise RuntimeError
+ return {"finished": resp.resp_scan_status.scan_finished, "count": resp.resp_scan_status.result_count}
+
+
+def scan_result_request(security_ctx, index, count):
+ # Form protobuf request packet for ScanResult command
+ cmd = proto.wifi_scan_pb2.WiFiScanPayload()
+ cmd.msg = proto.wifi_scan_pb2.TypeCmdScanResult
+ cmd.cmd_scan_result.start_index = index
+ cmd.cmd_scan_result.count = count
+ enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()).decode('latin-1')
+ print_verbose(security_ctx, "Client -> Device (Encrypted CmdScanResult) " + utils.str_to_hexstr(enc_cmd))
+ return enc_cmd
+
+
+def scan_result_response(security_ctx, response_data):
+ # Interpret protobuf response packet from ScanResult command
+ dec_resp = security_ctx.decrypt_data(tobytes(response_data))
+ resp = proto.wifi_scan_pb2.WiFiScanPayload()
+ resp.ParseFromString(dec_resp)
+ print_verbose(security_ctx, "ScanResult status " + str(resp.status))
+ if resp.status != 0:
+ raise RuntimeError
+ authmode_str = ["Open", "WEP", "WPA_PSK", "WPA2_PSK", "WPA_WPA2_PSK", "WPA2_ENTERPRISE"]
+ results = []
+ for entry in resp.resp_scan_result.entries:
+ results += [{"ssid": entry.ssid.decode('latin-1').rstrip('\x00'),
+ "bssid": utils.str_to_hexstr(entry.bssid.decode('latin-1')),
+ "channel": entry.channel,
+ "rssi": entry.rssi,
+ "auth": authmode_str[entry.auth]}]
+ print_verbose(security_ctx, "ScanResult SSID : " + str(results[-1]["ssid"]))
+ print_verbose(security_ctx, "ScanResult BSSID : " + str(results[-1]["bssid"]))
+ print_verbose(security_ctx, "ScanResult Channel : " + str(results[-1]["channel"]))
+ print_verbose(security_ctx, "ScanResult RSSI : " + str(results[-1]["rssi"]))
+ print_verbose(security_ctx, "ScanResult AUTH : " + str(results[-1]["auth"]))
+ return results
diff --git a/cli/rmaker_tools/rmaker_prov/security/__init__.py b/cli/rmaker_tools/rmaker_prov/security/__init__.py
new file mode 100644
index 0000000..1f34260
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/security/__init__.py
@@ -0,0 +1,16 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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 .security0 import * # noqa: F403, F401
+from .security1 import * # noqa: F403, F401
diff --git a/cli/rmaker_tools/rmaker_prov/security/security.py b/cli/rmaker_tools/rmaker_prov/security/security.py
new file mode 100644
index 0000000..acf8a35
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/security/security.py
@@ -0,0 +1,20 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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.
+
+# Base class for protocomm security
+
+
+class Security:
+ def __init__(self, security_session):
+ self.security_session = security_session
diff --git a/cli/rmaker_tools/rmaker_prov/security/security0.py b/cli/rmaker_tools/rmaker_prov/security/security0.py
new file mode 100644
index 0000000..c19bbfe
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/security/security0.py
@@ -0,0 +1,64 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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.
+
+# APIs for interpreting and creating protobuf packets for
+# protocomm endpoint with security type protocomm_security0
+
+from __future__ import print_function
+from future.utils import tobytes
+
+import proto
+from .security import Security
+
+
+class Security0(Security):
+ def __init__(self, verbose):
+ # Initialize state of the security1 FSM
+ self.session_state = 0
+ self.verbose = verbose
+ Security.__init__(self, self.security0_session)
+
+ def security0_session(self, response_data):
+ # protocomm security0 FSM which interprets/forms
+ # protobuf packets according to present state of session
+ if (self.session_state == 0):
+ self.session_state = 1
+ return self.setup0_request()
+ if (self.session_state == 1):
+ self.setup0_response(response_data)
+ return None
+
+ def setup0_request(self):
+ # Form protocomm security0 request packet
+ setup_req = proto.session_pb2.SessionData()
+ setup_req.sec_ver = 0
+ session_cmd = proto.sec0_pb2.S0SessionCmd()
+ setup_req.sec0.sc.MergeFrom(session_cmd)
+ return setup_req.SerializeToString().decode('latin-1')
+
+ def setup0_response(self, response_data):
+ # Interpret protocomm security0 response packet
+ setup_resp = proto.session_pb2.SessionData()
+ setup_resp.ParseFromString(tobytes(response_data))
+ # Check if security scheme matches
+ if setup_resp.sec_ver != proto.session_pb2.SecScheme0:
+ print("Incorrect sec scheme")
+
+ def encrypt_data(self, data):
+ # Passive. No encryption when security0 used
+ return data
+
+ def decrypt_data(self, data):
+ # Passive. No encryption when security0 used
+ return data
diff --git a/cli/rmaker_tools/rmaker_prov/security/security1.py b/cli/rmaker_tools/rmaker_prov/security/security1.py
new file mode 100644
index 0000000..d6c72bb
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/security/security1.py
@@ -0,0 +1,170 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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.
+
+# APIs for interpreting and creating protobuf packets for
+# protocomm endpoint with security type protocomm_security1
+
+from __future__ import print_function
+
+try:
+ from future.utils import tobytes
+
+ import sys
+ import utils
+ import proto
+ from .security import Security
+
+ from cryptography.hazmat.backends import default_backend
+ from cryptography.hazmat.primitives import hashes
+ from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+
+ import session_pb2
+except ImportError as err:
+ raise err
+
+# Enum for state of protocomm_security1 FSM
+class security_state:
+ REQUEST1 = 0
+ RESPONSE1_REQUEST2 = 1
+ RESPONSE2 = 2
+ FINISHED = 3
+
+
+def xor(a, b):
+ # XOR two inputs of type `bytes`
+ ret = bytearray()
+ # Decode the input bytes to strings
+ a = a.decode('latin-1')
+ b = b.decode('latin-1')
+ for i in range(max(len(a), len(b))):
+ # Convert the characters to corresponding 8-bit ASCII codes
+ # then XOR them and store in bytearray
+ ret.append(([0, ord(a[i])][i < len(a)]) ^ ([0, ord(b[i])][i < len(b)]))
+ # Convert bytearray to bytes
+ return bytes(ret)
+
+
+class Security1(Security):
+ def __init__(self, pop, verbose):
+ # Initialize state of the security1 FSM
+ self.session_state = security_state.REQUEST1
+ self.pop = tobytes(pop)
+ self.verbose = verbose
+ Security.__init__(self, self.security1_session)
+
+ def security1_session(self, response_data):
+ # protocomm security1 FSM which interprets/forms
+ # protobuf packets according to present state of session
+ if (self.session_state == security_state.REQUEST1):
+ self.session_state = security_state.RESPONSE1_REQUEST2
+ return self.setup0_request()
+ if (self.session_state == security_state.RESPONSE1_REQUEST2):
+ self.session_state = security_state.RESPONSE2
+ self.setup0_response(response_data)
+ return self.setup1_request()
+ if (self.session_state == security_state.RESPONSE2):
+ self.session_state = security_state.FINISHED
+ self.setup1_response(response_data)
+ return None
+ else:
+ print("Unexpected state")
+ return None
+
+ def __generate_key(self):
+ # Generate private and public key pair for client
+ self.client_private_key = X25519PrivateKey.generate()
+ self.client_public_key = self.client_private_key.public_key().public_bytes()
+
+ def _print_verbose(self, data):
+ if (self.verbose):
+ print("++++ " + data + " ++++")
+
+ def setup0_request(self):
+ # Form SessionCmd0 request packet using client public key
+ setup_req = session_pb2.SessionData()
+ setup_req.sec_ver = session_pb2.SecScheme1
+ self.__generate_key()
+ setup_req.sec1.sc0.client_pubkey = self.client_public_key
+ self._print_verbose("Client Public Key:\t" + utils.str_to_hexstr(self.client_public_key.decode('latin-1')))
+ return setup_req.SerializeToString().decode('latin-1')
+
+ def setup0_response(self, response_data):
+ # Interpret SessionResp0 response packet
+ setup_resp = proto.session_pb2.SessionData()
+ setup_resp.ParseFromString(tobytes(response_data))
+ self._print_verbose("Security version:\t" + str(setup_resp.sec_ver))
+ if setup_resp.sec_ver != session_pb2.SecScheme1:
+ print("Incorrect sec scheme")
+ exit(1)
+ self.device_public_key = setup_resp.sec1.sr0.device_pubkey
+ # Device random is the initialization vector
+ device_random = setup_resp.sec1.sr0.device_random
+ self._print_verbose("Device Public Key:\t" + utils.str_to_hexstr(self.device_public_key.decode('latin-1')))
+ self._print_verbose("Device Random:\t" + utils.str_to_hexstr(device_random.decode('latin-1')))
+
+ # Calculate Curve25519 shared key using Client private key and Device public key
+ sharedK = self.client_private_key.exchange(X25519PublicKey.from_public_bytes(self.device_public_key))
+ self._print_verbose("Shared Key:\t" + utils.str_to_hexstr(sharedK.decode('latin-1')))
+
+ # If PoP is provided, XOR SHA256 of PoP with the previously
+ # calculated Shared Key to form the actual Shared Key
+ if len(self.pop) > 0:
+ # Calculate SHA256 of PoP
+ h = hashes.Hash(hashes.SHA256(), backend=default_backend())
+ h.update(self.pop)
+ digest = h.finalize()
+ # XOR with and update Shared Key
+ sharedK = xor(sharedK, digest)
+ self._print_verbose("New Shared Key XORed with PoP:\t" + utils.str_to_hexstr(sharedK.decode('latin-1')))
+ # Initialize the encryption engine with Shared Key and initialization vector
+ cipher = Cipher(algorithms.AES(sharedK), modes.CTR(device_random), backend=default_backend())
+ self.cipher = cipher.encryptor()
+
+ def setup1_request(self):
+ # Form SessionCmd1 request packet using encrypted device public key
+ setup_req = proto.session_pb2.SessionData()
+ setup_req.sec_ver = session_pb2.SecScheme1
+ setup_req.sec1.msg = proto.sec1_pb2.Session_Command1
+ # Encrypt device public key and attach to the request packet
+ client_verify = self.cipher.update(self.device_public_key)
+ self._print_verbose("Client Verify:\t" + utils.str_to_hexstr(client_verify.decode('latin-1')))
+ setup_req.sec1.sc1.client_verify_data = client_verify
+ return setup_req.SerializeToString().decode('latin-1')
+
+ def setup1_response(self, response_data):
+ # Interpret SessionResp1 response packet
+ setup_resp = proto.session_pb2.SessionData()
+ setup_resp.ParseFromString(tobytes(response_data))
+ # Ensure security scheme matches
+ if setup_resp.sec_ver == session_pb2.SecScheme1:
+ # Read encrypyed device verify string
+ device_verify = setup_resp.sec1.sr1.device_verify_data
+ self._print_verbose("Device verify:\t" + utils.str_to_hexstr(device_verify.decode('latin-1')))
+ # Decrypt the device verify string
+ enc_client_pubkey = self.cipher.update(setup_resp.sec1.sr1.device_verify_data)
+ self._print_verbose("Enc client pubkey:\t " + utils.str_to_hexstr(enc_client_pubkey.decode('latin-1')))
+ # Match decryped string with client public key
+ if enc_client_pubkey != self.client_public_key:
+ print("Mismatch in device verify")
+ return -2
+ else:
+ print("Unsupported security protocol")
+ return -1
+
+ def encrypt_data(self, data):
+ return self.cipher.update(data)
+
+ def decrypt_data(self, data):
+ return self.cipher.update(data)
diff --git a/cli/rmaker_tools/rmaker_prov/transport/__init__.py b/cli/rmaker_tools/rmaker_prov/transport/__init__.py
new file mode 100644
index 0000000..d54614f
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/transport/__init__.py
@@ -0,0 +1,17 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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 .transport_console import * # noqa: F403, F401
+from .transport_http import * # noqa: F403, F401
+from .transport_ble import * # noqa: F403, F401
diff --git a/cli/rmaker_tools/rmaker_prov/transport/ble_cli.py b/cli/rmaker_tools/rmaker_prov/transport/ble_cli.py
new file mode 100644
index 0000000..92c080e
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/transport/ble_cli.py
@@ -0,0 +1,292 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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
+from builtins import input
+from future.utils import iteritems
+
+import platform
+
+import utils
+
+fallback = True
+
+
+# Check if platform is Linux and required packages are installed
+# else fallback to console mode
+if platform.system() == 'Linux':
+ try:
+ import dbus
+ import dbus.mainloop.glib
+ import time
+ fallback = False
+ except ImportError:
+ pass
+
+
+# --------------------------------------------------------------------
+
+
+# BLE client (Linux Only) using Bluez and DBus
+class BLE_Bluez_Client:
+ def connect(self, devname, iface, chrc_names, fallback_srv_uuid):
+ self.devname = devname
+ self.srv_uuid_fallback = fallback_srv_uuid
+ self.chrc_names = [name.lower() for name in chrc_names]
+ self.device = None
+ self.adapter = None
+ self.adapter_props = None
+ self.services = None
+ self.nu_lookup = None
+ self.characteristics = dict()
+ self.srv_uuid_adv = None
+
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+ bus = dbus.SystemBus()
+ manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager")
+ objects = manager.GetManagedObjects()
+
+ for path, interfaces in iteritems(objects):
+ adapter = interfaces.get("org.bluez.Adapter1")
+ if adapter is not None:
+ if path.endswith(iface):
+ self.adapter = dbus.Interface(bus.get_object("org.bluez", path), "org.bluez.Adapter1")
+ self.adapter_props = dbus.Interface(bus.get_object("org.bluez", path), "org.freedesktop.DBus.Properties")
+ break
+
+ if self.adapter is None:
+ raise RuntimeError("Bluetooth adapter not found")
+
+ self.adapter_props.Set("org.bluez.Adapter1", "Powered", dbus.Boolean(1))
+ self.adapter.StartDiscovery()
+
+ retry = 10
+ while (retry > 0):
+ try:
+ if self.device is None:
+ print("Connecting...")
+ # Wait for device to be discovered
+ time.sleep(5)
+ self._connect_()
+ print("Connected")
+ print("Getting Services...")
+ # Wait for services to be discovered
+ time.sleep(5)
+ self._get_services_()
+ return True
+ except Exception as e:
+ print(e)
+ retry -= 1
+ print("Retries left", retry)
+ continue
+ self.adapter.StopDiscovery()
+ return False
+
+ def _connect_(self):
+ bus = dbus.SystemBus()
+ manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager")
+ objects = manager.GetManagedObjects()
+ dev_path = None
+ for path, interfaces in iteritems(objects):
+ if "org.bluez.Device1" not in interfaces:
+ continue
+ if interfaces["org.bluez.Device1"].get("Name") == self.devname:
+ dev_path = path
+ break
+
+ if dev_path is None:
+ raise RuntimeError("BLE device not found")
+
+ try:
+ self.device = bus.get_object("org.bluez", dev_path)
+ try:
+ uuids = self.device.Get('org.bluez.Device1', 'UUIDs',
+ dbus_interface='org.freedesktop.DBus.Properties')
+ # There should be 1 service UUID in advertising data
+ # If bluez had cached an old version of the advertisement data
+ # the list of uuids may be incorrect, in which case connection
+ # or service discovery may fail the first time. If that happens
+ # the cache will be refreshed before next retry
+ if len(uuids) == 1:
+ self.srv_uuid_adv = uuids[0]
+ except dbus.exceptions.DBusException as e:
+ print(e)
+
+ self.device.Connect(dbus_interface='org.bluez.Device1')
+ except Exception as e:
+ print(e)
+ self.device = None
+ raise RuntimeError("BLE device could not connect")
+
+ def _get_services_(self):
+ bus = dbus.SystemBus()
+ manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager")
+ objects = manager.GetManagedObjects()
+ service_found = False
+ for srv_path, srv_interfaces in iteritems(objects):
+ if "org.bluez.GattService1" not in srv_interfaces:
+ continue
+ if not srv_path.startswith(self.device.object_path):
+ continue
+ service = bus.get_object("org.bluez", srv_path)
+ srv_uuid = service.Get('org.bluez.GattService1', 'UUID',
+ dbus_interface='org.freedesktop.DBus.Properties')
+
+ # If service UUID doesn't match the one found in advertisement data
+ # then also check if it matches the fallback UUID
+ if srv_uuid not in [self.srv_uuid_adv, self.srv_uuid_fallback]:
+ continue
+
+ nu_lookup = dict()
+ characteristics = dict()
+ for chrc_path, chrc_interfaces in iteritems(objects):
+ if "org.bluez.GattCharacteristic1" not in chrc_interfaces:
+ continue
+ if not chrc_path.startswith(service.object_path):
+ continue
+ chrc = bus.get_object("org.bluez", chrc_path)
+ uuid = chrc.Get('org.bluez.GattCharacteristic1', 'UUID',
+ dbus_interface='org.freedesktop.DBus.Properties')
+ characteristics[uuid] = chrc
+ for desc_path, desc_interfaces in iteritems(objects):
+ if "org.bluez.GattDescriptor1" not in desc_interfaces:
+ continue
+ if not desc_path.startswith(chrc.object_path):
+ continue
+ desc = bus.get_object("org.bluez", desc_path)
+ desc_uuid = desc.Get('org.bluez.GattDescriptor1', 'UUID',
+ dbus_interface='org.freedesktop.DBus.Properties')
+ if desc_uuid[4:8] != '2901':
+ continue
+ try:
+ readval = desc.ReadValue({}, dbus_interface='org.bluez.GattDescriptor1')
+ except dbus.exceptions.DBusException:
+ break
+ found_name = ''.join(chr(b) for b in readval).lower()
+ nu_lookup[found_name] = uuid
+ break
+
+ match_found = True
+ for name in self.chrc_names:
+ if name not in nu_lookup:
+ # Endpoint name not present
+ match_found = False
+ break
+
+ # Create lookup table only if all endpoint names found
+ self.nu_lookup = [None, nu_lookup][match_found]
+ self.characteristics = characteristics
+ service_found = True
+
+ # If the service UUID matches that in the advertisement
+ # we can stop the search now. If it doesn't match, we
+ # have found the service corresponding to the fallback
+ # UUID, in which case don't break and keep searching
+ # for the advertised service
+ if srv_uuid == self.srv_uuid_adv:
+ break
+
+ if not service_found:
+ self.device.Disconnect(dbus_interface='org.bluez.Device1')
+ if self.adapter:
+ self.adapter.RemoveDevice(self.device)
+ self.device = None
+ self.nu_lookup = None
+ self.characteristics = dict()
+ raise RuntimeError("Provisioning service not found")
+
+ def get_nu_lookup(self):
+ return self.nu_lookup
+
+ def has_characteristic(self, uuid):
+ if uuid in self.characteristics:
+ return True
+ return False
+
+ def disconnect(self):
+ if self.device:
+ self.device.Disconnect(dbus_interface='org.bluez.Device1')
+ if self.adapter:
+ self.adapter.RemoveDevice(self.device)
+ self.device = None
+ self.nu_lookup = None
+ self.characteristics = dict()
+ if self.adapter_props:
+ self.adapter_props.Set("org.bluez.Adapter1", "Powered", dbus.Boolean(0))
+
+ def send_data(self, characteristic_uuid, data):
+ try:
+ path = self.characteristics[characteristic_uuid]
+ except KeyError:
+ raise RuntimeError("Invalid characteristic : " + characteristic_uuid)
+
+ try:
+ path.WriteValue([ord(c) for c in data], {}, dbus_interface='org.bluez.GattCharacteristic1')
+ except dbus.exceptions.DBusException as e:
+ raise RuntimeError("Failed to write value to characteristic " + characteristic_uuid + ": " + str(e))
+
+ try:
+ readval = path.ReadValue({}, dbus_interface='org.bluez.GattCharacteristic1')
+ except dbus.exceptions.DBusException as e:
+ raise RuntimeError("Failed to read value from characteristic " + characteristic_uuid + ": " + str(e))
+ return ''.join(chr(b) for b in readval)
+
+
+# --------------------------------------------------------------------
+
+
+# Console based BLE client for Cross Platform support
+class BLE_Console_Client:
+ def connect(self, devname, iface, chrc_names, fallback_srv_uuid):
+ print("BLE client is running in console mode")
+ print("\tThis could be due to your platform not being supported or dependencies not being met")
+ print("\tPlease ensure all pre-requisites are met to run the full fledged client")
+ print("BLECLI >> Please connect to BLE device `" + devname + "` manually using your tool of choice")
+ resp = input("BLECLI >> Was the device connected successfully? [y/n] ")
+ if resp != 'Y' and resp != 'y':
+ return False
+ print("BLECLI >> List available attributes of the connected device")
+ resp = input("BLECLI >> Is the service UUID '" + fallback_srv_uuid + "' listed among available attributes? [y/n] ")
+ if resp != 'Y' and resp != 'y':
+ return False
+ return True
+
+ def get_nu_lookup(self):
+ return None
+
+ def has_characteristic(self, uuid):
+ resp = input("BLECLI >> Is the characteristic UUID '" + uuid + "' listed among available attributes? [y/n] ")
+ if resp != 'Y' and resp != 'y':
+ return False
+ return True
+
+ def disconnect(self):
+ pass
+
+ def send_data(self, characteristic_uuid, data):
+ print("BLECLI >> Write following data to characteristic with UUID '" + characteristic_uuid + "' :")
+ print("\t>> " + utils.str_to_hexstr(data))
+ print("BLECLI >> Enter data read from characteristic (in hex) :")
+ resp = input("\t<< ")
+ return utils.hexstr_to_str(resp)
+
+
+# --------------------------------------------------------------------
+
+
+# Function to get client instance depending upon platform
+def get_client():
+ if fallback:
+ return BLE_Console_Client()
+ return BLE_Bluez_Client()
diff --git a/cli/rmaker_tools/rmaker_prov/transport/transport.py b/cli/rmaker_tools/rmaker_prov/transport/transport.py
new file mode 100644
index 0000000..4525eeb
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/transport/transport.py
@@ -0,0 +1,28 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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.
+
+# Base class for protocomm transport
+
+import abc
+
+
+class Transport():
+
+ @abc.abstractmethod
+ def send_session_data(self, data):
+ pass
+
+ @abc.abstractmethod
+ def send_config_data(self, data):
+ pass
diff --git a/cli/rmaker_tools/rmaker_prov/transport/transport_ble.py b/cli/rmaker_tools/rmaker_prov/transport/transport_ble.py
new file mode 100644
index 0000000..fa64416
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/transport/transport_ble.py
@@ -0,0 +1,66 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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
+
+from .transport import Transport
+
+from . import ble_cli
+
+
+class Transport_BLE(Transport):
+ def __init__(self, devname, service_uuid, nu_lookup):
+ # Expect service UUID like '0000ffff-0000-1000-8000-00805f9b34fb'
+ for name in nu_lookup.keys():
+ # Calculate characteristic UUID for each endpoint
+ nu_lookup[name] = service_uuid[:4] + '{:02x}'.format(
+ int(nu_lookup[name], 16) & int(service_uuid[4:8], 16)) + service_uuid[8:]
+
+ # Get BLE client module
+ self.cli = ble_cli.get_client()
+
+ # Use client to connect to BLE device and bind to service
+ if not self.cli.connect(devname=devname, iface='hci0',
+ chrc_names=nu_lookup.keys(),
+ fallback_srv_uuid=service_uuid):
+ raise RuntimeError("Failed to initialize transport")
+
+ # Irrespective of provided parameters, let the client
+ # generate a lookup table by reading advertisement data
+ # and characteristic user descriptors
+ self.name_uuid_lookup = self.cli.get_nu_lookup()
+
+ # If that doesn't work, use the lookup table provided as parameter
+ if self.name_uuid_lookup is None:
+ self.name_uuid_lookup = nu_lookup
+ # Check if expected characteristics are provided by the service
+ for name in self.name_uuid_lookup.keys():
+ if not self.cli.has_characteristic(self.name_uuid_lookup[name]):
+ raise RuntimeError("'" + name + "' endpoint not found")
+
+ def __del__(self):
+ # Make sure device is disconnected before application gets closed
+ try:
+ self.disconnect()
+ except Exception:
+ pass
+
+ def disconnect(self):
+ self.cli.disconnect()
+
+ def send_data(self, ep_name, data):
+ # Write (and read) data to characteristic corresponding to the endpoint
+ if ep_name not in self.name_uuid_lookup.keys():
+ raise RuntimeError("Invalid endpoint : " + ep_name)
+ return self.cli.send_data(self.name_uuid_lookup[ep_name], data)
diff --git a/cli/rmaker_tools/rmaker_prov/transport/transport_console.py b/cli/rmaker_tools/rmaker_prov/transport/transport_console.py
new file mode 100644
index 0000000..20220e1
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/transport/transport_console.py
@@ -0,0 +1,32 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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
+from builtins import input
+
+import utils
+
+from .transport import Transport
+
+
+class Transport_Console(Transport):
+
+ def send_data(self, path, data, session_id=0):
+ print("Client->Device msg :", path, session_id, utils.str_to_hexstr(data))
+ try:
+ resp = input("Enter device->client msg : ")
+ except Exception as err:
+ print("error:", err)
+ return None
+ return utils.hexstr_to_str(resp)
diff --git a/cli/rmaker_tools/rmaker_prov/transport/transport_http.py b/cli/rmaker_tools/rmaker_prov/transport/transport_http.py
new file mode 100644
index 0000000..066975a
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/transport/transport_http.py
@@ -0,0 +1,55 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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
+from future.utils import tobytes
+
+import socket
+import http.client
+import ssl
+
+from .transport import Transport
+
+
+class Transport_HTTP(Transport):
+ def __init__(self, hostname, certfile=None):
+ try:
+ socket.gethostbyname(hostname.split(':')[0])
+ except socket.gaierror:
+ raise RuntimeError("Unable to resolve hostname :" + hostname)
+
+ if certfile is None:
+ self.conn = http.client.HTTPConnection(hostname, timeout=30)
+ else:
+ ssl_ctx = ssl.create_default_context(cafile=certfile)
+ self.conn = http.client.HTTPSConnection(hostname, context=ssl_ctx, timeout=30)
+ try:
+ print("Connecting to " + hostname)
+ self.conn.connect()
+ except Exception as err:
+ raise RuntimeError("Connection Failure : " + str(err))
+ self.headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain"}
+
+ def _send_post_request(self, path, data):
+ try:
+ self.conn.request("POST", path, tobytes(data), self.headers)
+ response = self.conn.getresponse()
+ if response.status == 200:
+ return response.read().decode('latin-1')
+ except Exception as err:
+ raise RuntimeError("Connection Failure : " + str(err))
+ raise RuntimeError("Server responded with error code " + str(response.status))
+
+ def send_data(self, ep_name, data):
+ return self._send_post_request('/' + ep_name, data)
diff --git a/cli/rmaker_tools/rmaker_prov/utils/__init__.py b/cli/rmaker_tools/rmaker_prov/utils/__init__.py
new file mode 100644
index 0000000..be22f6e
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/utils/__init__.py
@@ -0,0 +1,15 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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 .convenience import * # noqa: F403, F401
diff --git a/cli/rmaker_tools/rmaker_prov/utils/convenience.py b/cli/rmaker_tools/rmaker_prov/utils/convenience.py
new file mode 100644
index 0000000..953236c
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/utils/convenience.py
@@ -0,0 +1,30 @@
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# 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.
+
+# Convenience functions for commonly used data type conversions
+
+
+def str_to_hexstr(string):
+ # Form hexstr by appending ASCII codes (in hex) corresponding to
+ # each character in the input string
+ return ''.join('{:02x}'.format(ord(c)) for c in string)
+
+
+def hexstr_to_str(hexstr):
+ # Prepend 0 (if needed) to make the hexstr length an even number
+ if len(hexstr) % 2 == 1:
+ hexstr = '0' + hexstr
+ # Interpret consecutive pairs of hex characters as 8 bit ASCII codes
+ # and append characters corresponding to each code to form the string
+ return ''.join(chr(int(hexstr[2 * i: 2 * i + 2], 16)) for i in range(len(hexstr) // 2))
diff --git a/cli/rmaker_tools/rmaker_prov/wifi_provisioning/proto/wifi_config.proto b/cli/rmaker_tools/rmaker_prov/wifi_provisioning/proto/wifi_config.proto
new file mode 100644
index 0000000..e273dc8
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/wifi_provisioning/proto/wifi_config.proto
@@ -0,0 +1,57 @@
+syntax = "proto3";
+
+import "constants.proto";
+import "wifi_constants.proto";
+
+message CmdGetStatus {
+
+}
+
+message RespGetStatus {
+ Status status = 1;
+ WifiStationState sta_state = 2;
+ oneof state {
+ WifiConnectFailedReason fail_reason = 10;
+ WifiConnectedState connected = 11;
+ }
+}
+
+message CmdSetConfig {
+ bytes ssid = 1;
+ bytes passphrase = 2;
+ bytes bssid = 3;
+ int32 channel = 4;
+}
+
+message RespSetConfig {
+ Status status = 1;
+}
+
+message CmdApplyConfig {
+
+}
+
+message RespApplyConfig {
+ Status status = 1;
+}
+
+enum WiFiConfigMsgType {
+ TypeCmdGetStatus = 0;
+ TypeRespGetStatus = 1;
+ TypeCmdSetConfig = 2;
+ TypeRespSetConfig = 3;
+ TypeCmdApplyConfig = 4;
+ TypeRespApplyConfig = 5;
+}
+
+message WiFiConfigPayload {
+ WiFiConfigMsgType msg = 1;
+ oneof payload {
+ CmdGetStatus cmd_get_status = 10;
+ RespGetStatus resp_get_status = 11;
+ CmdSetConfig cmd_set_config = 12;
+ RespSetConfig resp_set_config = 13;
+ CmdApplyConfig cmd_apply_config = 14;
+ RespApplyConfig resp_apply_config = 15;
+ }
+}
diff --git a/cli/rmaker_tools/rmaker_prov/wifi_provisioning/proto/wifi_constants.proto b/cli/rmaker_tools/rmaker_prov/wifi_provisioning/proto/wifi_constants.proto
new file mode 100644
index 0000000..95c7612
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/wifi_provisioning/proto/wifi_constants.proto
@@ -0,0 +1,31 @@
+syntax = "proto3";
+
+enum WifiStationState {
+ Connected = 0;
+ Connecting = 1;
+ Disconnected = 2;
+ ConnectionFailed = 3;
+}
+
+enum WifiConnectFailedReason {
+ AuthError = 0;
+ NetworkNotFound = 1;
+}
+
+enum WifiAuthMode {
+ Open = 0;
+ WEP = 1;
+ WPA_PSK = 2;
+ WPA2_PSK = 3;
+ WPA_WPA2_PSK = 4;
+ WPA2_ENTERPRISE = 5;
+}
+
+message WifiConnectedState {
+ string ip4_addr = 1;
+ WifiAuthMode auth_mode = 2;
+ bytes ssid = 3;
+ bytes bssid = 4;
+ int32 channel = 5;
+}
+
diff --git a/cli/rmaker_tools/rmaker_prov/wifi_provisioning/proto/wifi_scan.proto b/cli/rmaker_tools/rmaker_prov/wifi_provisioning/proto/wifi_scan.proto
new file mode 100644
index 0000000..ea240f2
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/wifi_provisioning/proto/wifi_scan.proto
@@ -0,0 +1,63 @@
+syntax = "proto3";
+
+import "constants.proto";
+import "wifi_constants.proto";
+
+message CmdScanStart {
+ bool blocking = 1;
+ bool passive = 2;
+ uint32 group_channels = 3;
+ uint32 period_ms = 4;
+}
+
+message RespScanStart {
+
+}
+
+message CmdScanStatus {
+
+}
+
+message RespScanStatus {
+ bool scan_finished = 1;
+ uint32 result_count = 2;
+}
+
+message CmdScanResult {
+ uint32 start_index = 1;
+ uint32 count = 2;
+}
+
+message WiFiScanResult {
+ bytes ssid = 1;
+ uint32 channel = 2;
+ int32 rssi = 3;
+ bytes bssid = 4;
+ WifiAuthMode auth = 5;
+}
+
+message RespScanResult {
+ repeated WiFiScanResult entries = 1;
+}
+
+enum WiFiScanMsgType {
+ TypeCmdScanStart = 0;
+ TypeRespScanStart = 1;
+ TypeCmdScanStatus = 2;
+ TypeRespScanStatus = 3;
+ TypeCmdScanResult = 4;
+ TypeRespScanResult = 5;
+}
+
+message WiFiScanPayload {
+ WiFiScanMsgType msg = 1;
+ Status status = 2;
+ oneof payload {
+ CmdScanStart cmd_scan_start = 10;
+ RespScanStart resp_scan_start = 11;
+ CmdScanStatus cmd_scan_status = 12;
+ RespScanStatus resp_scan_status = 13;
+ CmdScanResult cmd_scan_result = 14;
+ RespScanResult resp_scan_result = 15;
+ }
+}
diff --git a/cli/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_config_pb2.py b/cli/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_config_pb2.py
new file mode 100644
index 0000000..0dd6d43
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_config_pb2.py
@@ -0,0 +1,466 @@
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: wifi_config.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import constants_pb2 as constants__pb2
+import wifi_constants_pb2 as wifi__constants__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+ name='wifi_config.proto',
+ package='',
+ syntax='proto3',
+ serialized_pb=_b('\n\x11wifi_config.proto\x1a\x0f\x63onstants.proto\x1a\x14wifi_constants.proto\"\x0e\n\x0c\x43mdGetStatus\"\xb2\x01\n\rRespGetStatus\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\x12$\n\tsta_state\x18\x02 \x01(\x0e\x32\x11.WifiStationState\x12/\n\x0b\x66\x61il_reason\x18\n \x01(\x0e\x32\x18.WifiConnectFailedReasonH\x00\x12(\n\tconnected\x18\x0b \x01(\x0b\x32\x13.WifiConnectedStateH\x00\x42\x07\n\x05state\"P\n\x0c\x43mdSetConfig\x12\x0c\n\x04ssid\x18\x01 \x01(\x0c\x12\x12\n\npassphrase\x18\x02 \x01(\x0c\x12\r\n\x05\x62ssid\x18\x03 \x01(\x0c\x12\x0f\n\x07\x63hannel\x18\x04 \x01(\x05\"(\n\rRespSetConfig\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\"\x10\n\x0e\x43mdApplyConfig\"*\n\x0fRespApplyConfig\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\"\xc3\x02\n\x11WiFiConfigPayload\x12\x1f\n\x03msg\x18\x01 \x01(\x0e\x32\x12.WiFiConfigMsgType\x12\'\n\x0e\x63md_get_status\x18\n \x01(\x0b\x32\r.CmdGetStatusH\x00\x12)\n\x0fresp_get_status\x18\x0b \x01(\x0b\x32\x0e.RespGetStatusH\x00\x12\'\n\x0e\x63md_set_config\x18\x0c \x01(\x0b\x32\r.CmdSetConfigH\x00\x12)\n\x0fresp_set_config\x18\r \x01(\x0b\x32\x0e.RespSetConfigH\x00\x12+\n\x10\x63md_apply_config\x18\x0e \x01(\x0b\x32\x0f.CmdApplyConfigH\x00\x12-\n\x11resp_apply_config\x18\x0f \x01(\x0b\x32\x10.RespApplyConfigH\x00\x42\t\n\x07payload*\x9e\x01\n\x11WiFiConfigMsgType\x12\x14\n\x10TypeCmdGetStatus\x10\x00\x12\x15\n\x11TypeRespGetStatus\x10\x01\x12\x14\n\x10TypeCmdSetConfig\x10\x02\x12\x15\n\x11TypeRespSetConfig\x10\x03\x12\x16\n\x12TypeCmdApplyConfig\x10\x04\x12\x17\n\x13TypeRespApplyConfig\x10\x05\x62\x06proto3')
+ ,
+ dependencies=[constants__pb2.DESCRIPTOR,wifi__constants__pb2.DESCRIPTOR,])
+
+_WIFICONFIGMSGTYPE = _descriptor.EnumDescriptor(
+ name='WiFiConfigMsgType',
+ full_name='WiFiConfigMsgType',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='TypeCmdGetStatus', index=0, number=0,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='TypeRespGetStatus', index=1, number=1,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='TypeCmdSetConfig', index=2, number=2,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='TypeRespSetConfig', index=3, number=3,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='TypeCmdApplyConfig', index=4, number=4,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='TypeRespApplyConfig', index=5, number=5,
+ options=None,
+ type=None),
+ ],
+ containing_type=None,
+ options=None,
+ serialized_start=770,
+ serialized_end=928,
+)
+_sym_db.RegisterEnumDescriptor(_WIFICONFIGMSGTYPE)
+
+WiFiConfigMsgType = enum_type_wrapper.EnumTypeWrapper(_WIFICONFIGMSGTYPE)
+TypeCmdGetStatus = 0
+TypeRespGetStatus = 1
+TypeCmdSetConfig = 2
+TypeRespSetConfig = 3
+TypeCmdApplyConfig = 4
+TypeRespApplyConfig = 5
+
+
+
+_CMDGETSTATUS = _descriptor.Descriptor(
+ name='CmdGetStatus',
+ full_name='CmdGetStatus',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=60,
+ serialized_end=74,
+)
+
+
+_RESPGETSTATUS = _descriptor.Descriptor(
+ name='RespGetStatus',
+ full_name='RespGetStatus',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='status', full_name='RespGetStatus.status', index=0,
+ number=1, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='sta_state', full_name='RespGetStatus.sta_state', index=1,
+ number=2, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='fail_reason', full_name='RespGetStatus.fail_reason', index=2,
+ number=10, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='connected', full_name='RespGetStatus.connected', index=3,
+ number=11, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ _descriptor.OneofDescriptor(
+ name='state', full_name='RespGetStatus.state',
+ index=0, containing_type=None, fields=[]),
+ ],
+ serialized_start=77,
+ serialized_end=255,
+)
+
+
+_CMDSETCONFIG = _descriptor.Descriptor(
+ name='CmdSetConfig',
+ full_name='CmdSetConfig',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='ssid', full_name='CmdSetConfig.ssid', index=0,
+ number=1, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='passphrase', full_name='CmdSetConfig.passphrase', index=1,
+ number=2, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='bssid', full_name='CmdSetConfig.bssid', index=2,
+ number=3, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='channel', full_name='CmdSetConfig.channel', index=3,
+ number=4, type=5, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=257,
+ serialized_end=337,
+)
+
+
+_RESPSETCONFIG = _descriptor.Descriptor(
+ name='RespSetConfig',
+ full_name='RespSetConfig',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='status', full_name='RespSetConfig.status', index=0,
+ number=1, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=339,
+ serialized_end=379,
+)
+
+
+_CMDAPPLYCONFIG = _descriptor.Descriptor(
+ name='CmdApplyConfig',
+ full_name='CmdApplyConfig',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=381,
+ serialized_end=397,
+)
+
+
+_RESPAPPLYCONFIG = _descriptor.Descriptor(
+ name='RespApplyConfig',
+ full_name='RespApplyConfig',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='status', full_name='RespApplyConfig.status', index=0,
+ number=1, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=399,
+ serialized_end=441,
+)
+
+
+_WIFICONFIGPAYLOAD = _descriptor.Descriptor(
+ name='WiFiConfigPayload',
+ full_name='WiFiConfigPayload',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='msg', full_name='WiFiConfigPayload.msg', index=0,
+ number=1, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='cmd_get_status', full_name='WiFiConfigPayload.cmd_get_status', index=1,
+ number=10, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='resp_get_status', full_name='WiFiConfigPayload.resp_get_status', index=2,
+ number=11, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='cmd_set_config', full_name='WiFiConfigPayload.cmd_set_config', index=3,
+ number=12, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='resp_set_config', full_name='WiFiConfigPayload.resp_set_config', index=4,
+ number=13, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='cmd_apply_config', full_name='WiFiConfigPayload.cmd_apply_config', index=5,
+ number=14, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='resp_apply_config', full_name='WiFiConfigPayload.resp_apply_config', index=6,
+ number=15, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ _descriptor.OneofDescriptor(
+ name='payload', full_name='WiFiConfigPayload.payload',
+ index=0, containing_type=None, fields=[]),
+ ],
+ serialized_start=444,
+ serialized_end=767,
+)
+
+_RESPGETSTATUS.fields_by_name['status'].enum_type = constants__pb2._STATUS
+_RESPGETSTATUS.fields_by_name['sta_state'].enum_type = wifi__constants__pb2._WIFISTATIONSTATE
+_RESPGETSTATUS.fields_by_name['fail_reason'].enum_type = wifi__constants__pb2._WIFICONNECTFAILEDREASON
+_RESPGETSTATUS.fields_by_name['connected'].message_type = wifi__constants__pb2._WIFICONNECTEDSTATE
+_RESPGETSTATUS.oneofs_by_name['state'].fields.append(
+ _RESPGETSTATUS.fields_by_name['fail_reason'])
+_RESPGETSTATUS.fields_by_name['fail_reason'].containing_oneof = _RESPGETSTATUS.oneofs_by_name['state']
+_RESPGETSTATUS.oneofs_by_name['state'].fields.append(
+ _RESPGETSTATUS.fields_by_name['connected'])
+_RESPGETSTATUS.fields_by_name['connected'].containing_oneof = _RESPGETSTATUS.oneofs_by_name['state']
+_RESPSETCONFIG.fields_by_name['status'].enum_type = constants__pb2._STATUS
+_RESPAPPLYCONFIG.fields_by_name['status'].enum_type = constants__pb2._STATUS
+_WIFICONFIGPAYLOAD.fields_by_name['msg'].enum_type = _WIFICONFIGMSGTYPE
+_WIFICONFIGPAYLOAD.fields_by_name['cmd_get_status'].message_type = _CMDGETSTATUS
+_WIFICONFIGPAYLOAD.fields_by_name['resp_get_status'].message_type = _RESPGETSTATUS
+_WIFICONFIGPAYLOAD.fields_by_name['cmd_set_config'].message_type = _CMDSETCONFIG
+_WIFICONFIGPAYLOAD.fields_by_name['resp_set_config'].message_type = _RESPSETCONFIG
+_WIFICONFIGPAYLOAD.fields_by_name['cmd_apply_config'].message_type = _CMDAPPLYCONFIG
+_WIFICONFIGPAYLOAD.fields_by_name['resp_apply_config'].message_type = _RESPAPPLYCONFIG
+_WIFICONFIGPAYLOAD.oneofs_by_name['payload'].fields.append(
+ _WIFICONFIGPAYLOAD.fields_by_name['cmd_get_status'])
+_WIFICONFIGPAYLOAD.fields_by_name['cmd_get_status'].containing_oneof = _WIFICONFIGPAYLOAD.oneofs_by_name['payload']
+_WIFICONFIGPAYLOAD.oneofs_by_name['payload'].fields.append(
+ _WIFICONFIGPAYLOAD.fields_by_name['resp_get_status'])
+_WIFICONFIGPAYLOAD.fields_by_name['resp_get_status'].containing_oneof = _WIFICONFIGPAYLOAD.oneofs_by_name['payload']
+_WIFICONFIGPAYLOAD.oneofs_by_name['payload'].fields.append(
+ _WIFICONFIGPAYLOAD.fields_by_name['cmd_set_config'])
+_WIFICONFIGPAYLOAD.fields_by_name['cmd_set_config'].containing_oneof = _WIFICONFIGPAYLOAD.oneofs_by_name['payload']
+_WIFICONFIGPAYLOAD.oneofs_by_name['payload'].fields.append(
+ _WIFICONFIGPAYLOAD.fields_by_name['resp_set_config'])
+_WIFICONFIGPAYLOAD.fields_by_name['resp_set_config'].containing_oneof = _WIFICONFIGPAYLOAD.oneofs_by_name['payload']
+_WIFICONFIGPAYLOAD.oneofs_by_name['payload'].fields.append(
+ _WIFICONFIGPAYLOAD.fields_by_name['cmd_apply_config'])
+_WIFICONFIGPAYLOAD.fields_by_name['cmd_apply_config'].containing_oneof = _WIFICONFIGPAYLOAD.oneofs_by_name['payload']
+_WIFICONFIGPAYLOAD.oneofs_by_name['payload'].fields.append(
+ _WIFICONFIGPAYLOAD.fields_by_name['resp_apply_config'])
+_WIFICONFIGPAYLOAD.fields_by_name['resp_apply_config'].containing_oneof = _WIFICONFIGPAYLOAD.oneofs_by_name['payload']
+DESCRIPTOR.message_types_by_name['CmdGetStatus'] = _CMDGETSTATUS
+DESCRIPTOR.message_types_by_name['RespGetStatus'] = _RESPGETSTATUS
+DESCRIPTOR.message_types_by_name['CmdSetConfig'] = _CMDSETCONFIG
+DESCRIPTOR.message_types_by_name['RespSetConfig'] = _RESPSETCONFIG
+DESCRIPTOR.message_types_by_name['CmdApplyConfig'] = _CMDAPPLYCONFIG
+DESCRIPTOR.message_types_by_name['RespApplyConfig'] = _RESPAPPLYCONFIG
+DESCRIPTOR.message_types_by_name['WiFiConfigPayload'] = _WIFICONFIGPAYLOAD
+DESCRIPTOR.enum_types_by_name['WiFiConfigMsgType'] = _WIFICONFIGMSGTYPE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+CmdGetStatus = _reflection.GeneratedProtocolMessageType('CmdGetStatus', (_message.Message,), dict(
+ DESCRIPTOR = _CMDGETSTATUS,
+ __module__ = 'wifi_config_pb2'
+ # @@protoc_insertion_point(class_scope:CmdGetStatus)
+ ))
+_sym_db.RegisterMessage(CmdGetStatus)
+
+RespGetStatus = _reflection.GeneratedProtocolMessageType('RespGetStatus', (_message.Message,), dict(
+ DESCRIPTOR = _RESPGETSTATUS,
+ __module__ = 'wifi_config_pb2'
+ # @@protoc_insertion_point(class_scope:RespGetStatus)
+ ))
+_sym_db.RegisterMessage(RespGetStatus)
+
+CmdSetConfig = _reflection.GeneratedProtocolMessageType('CmdSetConfig', (_message.Message,), dict(
+ DESCRIPTOR = _CMDSETCONFIG,
+ __module__ = 'wifi_config_pb2'
+ # @@protoc_insertion_point(class_scope:CmdSetConfig)
+ ))
+_sym_db.RegisterMessage(CmdSetConfig)
+
+RespSetConfig = _reflection.GeneratedProtocolMessageType('RespSetConfig', (_message.Message,), dict(
+ DESCRIPTOR = _RESPSETCONFIG,
+ __module__ = 'wifi_config_pb2'
+ # @@protoc_insertion_point(class_scope:RespSetConfig)
+ ))
+_sym_db.RegisterMessage(RespSetConfig)
+
+CmdApplyConfig = _reflection.GeneratedProtocolMessageType('CmdApplyConfig', (_message.Message,), dict(
+ DESCRIPTOR = _CMDAPPLYCONFIG,
+ __module__ = 'wifi_config_pb2'
+ # @@protoc_insertion_point(class_scope:CmdApplyConfig)
+ ))
+_sym_db.RegisterMessage(CmdApplyConfig)
+
+RespApplyConfig = _reflection.GeneratedProtocolMessageType('RespApplyConfig', (_message.Message,), dict(
+ DESCRIPTOR = _RESPAPPLYCONFIG,
+ __module__ = 'wifi_config_pb2'
+ # @@protoc_insertion_point(class_scope:RespApplyConfig)
+ ))
+_sym_db.RegisterMessage(RespApplyConfig)
+
+WiFiConfigPayload = _reflection.GeneratedProtocolMessageType('WiFiConfigPayload', (_message.Message,), dict(
+ DESCRIPTOR = _WIFICONFIGPAYLOAD,
+ __module__ = 'wifi_config_pb2'
+ # @@protoc_insertion_point(class_scope:WiFiConfigPayload)
+ ))
+_sym_db.RegisterMessage(WiFiConfigPayload)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/cli/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_constants_pb2.py b/cli/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_constants_pb2.py
new file mode 100644
index 0000000..8090568
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_constants_pb2.py
@@ -0,0 +1,207 @@
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: wifi_constants.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+ name='wifi_constants.proto',
+ package='',
+ syntax='proto3',
+ serialized_pb=_b('\n\x14wifi_constants.proto\"v\n\x12WifiConnectedState\x12\x10\n\x08ip4_addr\x18\x01 \x01(\t\x12 \n\tauth_mode\x18\x02 \x01(\x0e\x32\r.WifiAuthMode\x12\x0c\n\x04ssid\x18\x03 \x01(\x0c\x12\r\n\x05\x62ssid\x18\x04 \x01(\x0c\x12\x0f\n\x07\x63hannel\x18\x05 \x01(\x05*Y\n\x10WifiStationState\x12\r\n\tConnected\x10\x00\x12\x0e\n\nConnecting\x10\x01\x12\x10\n\x0c\x44isconnected\x10\x02\x12\x14\n\x10\x43onnectionFailed\x10\x03*=\n\x17WifiConnectFailedReason\x12\r\n\tAuthError\x10\x00\x12\x13\n\x0fNetworkNotFound\x10\x01*c\n\x0cWifiAuthMode\x12\x08\n\x04Open\x10\x00\x12\x07\n\x03WEP\x10\x01\x12\x0b\n\x07WPA_PSK\x10\x02\x12\x0c\n\x08WPA2_PSK\x10\x03\x12\x10\n\x0cWPA_WPA2_PSK\x10\x04\x12\x13\n\x0fWPA2_ENTERPRISE\x10\x05\x62\x06proto3')
+)
+
+_WIFISTATIONSTATE = _descriptor.EnumDescriptor(
+ name='WifiStationState',
+ full_name='WifiStationState',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='Connected', index=0, number=0,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='Connecting', index=1, number=1,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='Disconnected', index=2, number=2,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='ConnectionFailed', index=3, number=3,
+ options=None,
+ type=None),
+ ],
+ containing_type=None,
+ options=None,
+ serialized_start=144,
+ serialized_end=233,
+)
+_sym_db.RegisterEnumDescriptor(_WIFISTATIONSTATE)
+
+WifiStationState = enum_type_wrapper.EnumTypeWrapper(_WIFISTATIONSTATE)
+_WIFICONNECTFAILEDREASON = _descriptor.EnumDescriptor(
+ name='WifiConnectFailedReason',
+ full_name='WifiConnectFailedReason',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='AuthError', index=0, number=0,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='NetworkNotFound', index=1, number=1,
+ options=None,
+ type=None),
+ ],
+ containing_type=None,
+ options=None,
+ serialized_start=235,
+ serialized_end=296,
+)
+_sym_db.RegisterEnumDescriptor(_WIFICONNECTFAILEDREASON)
+
+WifiConnectFailedReason = enum_type_wrapper.EnumTypeWrapper(_WIFICONNECTFAILEDREASON)
+_WIFIAUTHMODE = _descriptor.EnumDescriptor(
+ name='WifiAuthMode',
+ full_name='WifiAuthMode',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='Open', index=0, number=0,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='WEP', index=1, number=1,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='WPA_PSK', index=2, number=2,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='WPA2_PSK', index=3, number=3,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='WPA_WPA2_PSK', index=4, number=4,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='WPA2_ENTERPRISE', index=5, number=5,
+ options=None,
+ type=None),
+ ],
+ containing_type=None,
+ options=None,
+ serialized_start=298,
+ serialized_end=397,
+)
+_sym_db.RegisterEnumDescriptor(_WIFIAUTHMODE)
+
+WifiAuthMode = enum_type_wrapper.EnumTypeWrapper(_WIFIAUTHMODE)
+Connected = 0
+Connecting = 1
+Disconnected = 2
+ConnectionFailed = 3
+AuthError = 0
+NetworkNotFound = 1
+Open = 0
+WEP = 1
+WPA_PSK = 2
+WPA2_PSK = 3
+WPA_WPA2_PSK = 4
+WPA2_ENTERPRISE = 5
+
+
+
+_WIFICONNECTEDSTATE = _descriptor.Descriptor(
+ name='WifiConnectedState',
+ full_name='WifiConnectedState',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='ip4_addr', full_name='WifiConnectedState.ip4_addr', index=0,
+ number=1, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='auth_mode', full_name='WifiConnectedState.auth_mode', index=1,
+ number=2, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='ssid', full_name='WifiConnectedState.ssid', index=2,
+ number=3, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='bssid', full_name='WifiConnectedState.bssid', index=3,
+ number=4, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='channel', full_name='WifiConnectedState.channel', index=4,
+ number=5, type=5, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=24,
+ serialized_end=142,
+)
+
+_WIFICONNECTEDSTATE.fields_by_name['auth_mode'].enum_type = _WIFIAUTHMODE
+DESCRIPTOR.message_types_by_name['WifiConnectedState'] = _WIFICONNECTEDSTATE
+DESCRIPTOR.enum_types_by_name['WifiStationState'] = _WIFISTATIONSTATE
+DESCRIPTOR.enum_types_by_name['WifiConnectFailedReason'] = _WIFICONNECTFAILEDREASON
+DESCRIPTOR.enum_types_by_name['WifiAuthMode'] = _WIFIAUTHMODE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+WifiConnectedState = _reflection.GeneratedProtocolMessageType('WifiConnectedState', (_message.Message,), dict(
+ DESCRIPTOR = _WIFICONNECTEDSTATE,
+ __module__ = 'wifi_constants_pb2'
+ # @@protoc_insertion_point(class_scope:WifiConnectedState)
+ ))
+_sym_db.RegisterMessage(WifiConnectedState)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/cli/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_scan_pb2.py b/cli/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_scan_pb2.py
new file mode 100644
index 0000000..2e95d8f
--- /dev/null
+++ b/cli/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_scan_pb2.py
@@ -0,0 +1,522 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: wifi_scan.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import constants_pb2 as constants__pb2
+import wifi_constants_pb2 as wifi__constants__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+ name='wifi_scan.proto',
+ package='',
+ syntax='proto3',
+ serialized_options=None,
+ serialized_pb=_b('\n\x0fwifi_scan.proto\x1a\x0f\x63onstants.proto\x1a\x14wifi_constants.proto\"\\\n\x0c\x43mdScanStart\x12\x10\n\x08\x62locking\x18\x01 \x01(\x08\x12\x0f\n\x07passive\x18\x02 \x01(\x08\x12\x16\n\x0egroup_channels\x18\x03 \x01(\r\x12\x11\n\tperiod_ms\x18\x04 \x01(\r\"\x0f\n\rRespScanStart\"\x0f\n\rCmdScanStatus\"=\n\x0eRespScanStatus\x12\x15\n\rscan_finished\x18\x01 \x01(\x08\x12\x14\n\x0cresult_count\x18\x02 \x01(\r\"3\n\rCmdScanResult\x12\x13\n\x0bstart_index\x18\x01 \x01(\r\x12\r\n\x05\x63ount\x18\x02 \x01(\r\"i\n\x0eWiFiScanResult\x12\x0c\n\x04ssid\x18\x01 \x01(\x0c\x12\x0f\n\x07\x63hannel\x18\x02 \x01(\r\x12\x0c\n\x04rssi\x18\x03 \x01(\x05\x12\r\n\x05\x62ssid\x18\x04 \x01(\x0c\x12\x1b\n\x04\x61uth\x18\x05 \x01(\x0e\x32\r.WifiAuthMode\"2\n\x0eRespScanResult\x12 \n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x0f.WiFiScanResult\"\xd8\x02\n\x0fWiFiScanPayload\x12\x1d\n\x03msg\x18\x01 \x01(\x0e\x32\x10.WiFiScanMsgType\x12\x17\n\x06status\x18\x02 \x01(\x0e\x32\x07.Status\x12\'\n\x0e\x63md_scan_start\x18\n \x01(\x0b\x32\r.CmdScanStartH\x00\x12)\n\x0fresp_scan_start\x18\x0b \x01(\x0b\x32\x0e.RespScanStartH\x00\x12)\n\x0f\x63md_scan_status\x18\x0c \x01(\x0b\x32\x0e.CmdScanStatusH\x00\x12+\n\x10resp_scan_status\x18\r \x01(\x0b\x32\x0f.RespScanStatusH\x00\x12)\n\x0f\x63md_scan_result\x18\x0e \x01(\x0b\x32\x0e.CmdScanResultH\x00\x12+\n\x10resp_scan_result\x18\x0f \x01(\x0b\x32\x0f.RespScanResultH\x00\x42\t\n\x07payload*\x9c\x01\n\x0fWiFiScanMsgType\x12\x14\n\x10TypeCmdScanStart\x10\x00\x12\x15\n\x11TypeRespScanStart\x10\x01\x12\x15\n\x11TypeCmdScanStatus\x10\x02\x12\x16\n\x12TypeRespScanStatus\x10\x03\x12\x15\n\x11TypeCmdScanResult\x10\x04\x12\x16\n\x12TypeRespScanResult\x10\x05\x62\x06proto3')
+ ,
+ dependencies=[constants__pb2.DESCRIPTOR,wifi__constants__pb2.DESCRIPTOR,])
+
+_WIFISCANMSGTYPE = _descriptor.EnumDescriptor(
+ name='WiFiScanMsgType',
+ full_name='WiFiScanMsgType',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='TypeCmdScanStart', index=0, number=0,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='TypeRespScanStart', index=1, number=1,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='TypeCmdScanStatus', index=2, number=2,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='TypeRespScanStatus', index=3, number=3,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='TypeCmdScanResult', index=4, number=4,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='TypeRespScanResult', index=5, number=5,
+ serialized_options=None,
+ type=None),
+ ],
+ containing_type=None,
+ serialized_options=None,
+ serialized_start=809,
+ serialized_end=965,
+)
+_sym_db.RegisterEnumDescriptor(_WIFISCANMSGTYPE)
+
+WiFiScanMsgType = enum_type_wrapper.EnumTypeWrapper(_WIFISCANMSGTYPE)
+TypeCmdScanStart = 0
+TypeRespScanStart = 1
+TypeCmdScanStatus = 2
+TypeRespScanStatus = 3
+TypeCmdScanResult = 4
+TypeRespScanResult = 5
+
+
+
+_CMDSCANSTART = _descriptor.Descriptor(
+ name='CmdScanStart',
+ full_name='CmdScanStart',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='blocking', full_name='CmdScanStart.blocking', index=0,
+ number=1, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='passive', full_name='CmdScanStart.passive', index=1,
+ number=2, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='group_channels', full_name='CmdScanStart.group_channels', index=2,
+ number=3, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='period_ms', full_name='CmdScanStart.period_ms', index=3,
+ number=4, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=58,
+ serialized_end=150,
+)
+
+
+_RESPSCANSTART = _descriptor.Descriptor(
+ name='RespScanStart',
+ full_name='RespScanStart',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=152,
+ serialized_end=167,
+)
+
+
+_CMDSCANSTATUS = _descriptor.Descriptor(
+ name='CmdScanStatus',
+ full_name='CmdScanStatus',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=169,
+ serialized_end=184,
+)
+
+
+_RESPSCANSTATUS = _descriptor.Descriptor(
+ name='RespScanStatus',
+ full_name='RespScanStatus',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='scan_finished', full_name='RespScanStatus.scan_finished', index=0,
+ number=1, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='result_count', full_name='RespScanStatus.result_count', index=1,
+ number=2, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=186,
+ serialized_end=247,
+)
+
+
+_CMDSCANRESULT = _descriptor.Descriptor(
+ name='CmdScanResult',
+ full_name='CmdScanResult',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='start_index', full_name='CmdScanResult.start_index', index=0,
+ number=1, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='count', full_name='CmdScanResult.count', index=1,
+ number=2, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=249,
+ serialized_end=300,
+)
+
+
+_WIFISCANRESULT = _descriptor.Descriptor(
+ name='WiFiScanResult',
+ full_name='WiFiScanResult',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='ssid', full_name='WiFiScanResult.ssid', index=0,
+ number=1, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='channel', full_name='WiFiScanResult.channel', index=1,
+ number=2, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='rssi', full_name='WiFiScanResult.rssi', index=2,
+ number=3, type=5, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='bssid', full_name='WiFiScanResult.bssid', index=3,
+ number=4, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='auth', full_name='WiFiScanResult.auth', index=4,
+ number=5, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=302,
+ serialized_end=407,
+)
+
+
+_RESPSCANRESULT = _descriptor.Descriptor(
+ name='RespScanResult',
+ full_name='RespScanResult',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='entries', full_name='RespScanResult.entries', index=0,
+ number=1, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=409,
+ serialized_end=459,
+)
+
+
+_WIFISCANPAYLOAD = _descriptor.Descriptor(
+ name='WiFiScanPayload',
+ full_name='WiFiScanPayload',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='msg', full_name='WiFiScanPayload.msg', index=0,
+ number=1, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='status', full_name='WiFiScanPayload.status', index=1,
+ number=2, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='cmd_scan_start', full_name='WiFiScanPayload.cmd_scan_start', index=2,
+ number=10, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='resp_scan_start', full_name='WiFiScanPayload.resp_scan_start', index=3,
+ number=11, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='cmd_scan_status', full_name='WiFiScanPayload.cmd_scan_status', index=4,
+ number=12, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='resp_scan_status', full_name='WiFiScanPayload.resp_scan_status', index=5,
+ number=13, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='cmd_scan_result', full_name='WiFiScanPayload.cmd_scan_result', index=6,
+ number=14, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='resp_scan_result', full_name='WiFiScanPayload.resp_scan_result', index=7,
+ number=15, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ _descriptor.OneofDescriptor(
+ name='payload', full_name='WiFiScanPayload.payload',
+ index=0, containing_type=None, fields=[]),
+ ],
+ serialized_start=462,
+ serialized_end=806,
+)
+
+_WIFISCANRESULT.fields_by_name['auth'].enum_type = wifi__constants__pb2._WIFIAUTHMODE
+_RESPSCANRESULT.fields_by_name['entries'].message_type = _WIFISCANRESULT
+_WIFISCANPAYLOAD.fields_by_name['msg'].enum_type = _WIFISCANMSGTYPE
+_WIFISCANPAYLOAD.fields_by_name['status'].enum_type = constants__pb2._STATUS
+_WIFISCANPAYLOAD.fields_by_name['cmd_scan_start'].message_type = _CMDSCANSTART
+_WIFISCANPAYLOAD.fields_by_name['resp_scan_start'].message_type = _RESPSCANSTART
+_WIFISCANPAYLOAD.fields_by_name['cmd_scan_status'].message_type = _CMDSCANSTATUS
+_WIFISCANPAYLOAD.fields_by_name['resp_scan_status'].message_type = _RESPSCANSTATUS
+_WIFISCANPAYLOAD.fields_by_name['cmd_scan_result'].message_type = _CMDSCANRESULT
+_WIFISCANPAYLOAD.fields_by_name['resp_scan_result'].message_type = _RESPSCANRESULT
+_WIFISCANPAYLOAD.oneofs_by_name['payload'].fields.append(
+ _WIFISCANPAYLOAD.fields_by_name['cmd_scan_start'])
+_WIFISCANPAYLOAD.fields_by_name['cmd_scan_start'].containing_oneof = _WIFISCANPAYLOAD.oneofs_by_name['payload']
+_WIFISCANPAYLOAD.oneofs_by_name['payload'].fields.append(
+ _WIFISCANPAYLOAD.fields_by_name['resp_scan_start'])
+_WIFISCANPAYLOAD.fields_by_name['resp_scan_start'].containing_oneof = _WIFISCANPAYLOAD.oneofs_by_name['payload']
+_WIFISCANPAYLOAD.oneofs_by_name['payload'].fields.append(
+ _WIFISCANPAYLOAD.fields_by_name['cmd_scan_status'])
+_WIFISCANPAYLOAD.fields_by_name['cmd_scan_status'].containing_oneof = _WIFISCANPAYLOAD.oneofs_by_name['payload']
+_WIFISCANPAYLOAD.oneofs_by_name['payload'].fields.append(
+ _WIFISCANPAYLOAD.fields_by_name['resp_scan_status'])
+_WIFISCANPAYLOAD.fields_by_name['resp_scan_status'].containing_oneof = _WIFISCANPAYLOAD.oneofs_by_name['payload']
+_WIFISCANPAYLOAD.oneofs_by_name['payload'].fields.append(
+ _WIFISCANPAYLOAD.fields_by_name['cmd_scan_result'])
+_WIFISCANPAYLOAD.fields_by_name['cmd_scan_result'].containing_oneof = _WIFISCANPAYLOAD.oneofs_by_name['payload']
+_WIFISCANPAYLOAD.oneofs_by_name['payload'].fields.append(
+ _WIFISCANPAYLOAD.fields_by_name['resp_scan_result'])
+_WIFISCANPAYLOAD.fields_by_name['resp_scan_result'].containing_oneof = _WIFISCANPAYLOAD.oneofs_by_name['payload']
+DESCRIPTOR.message_types_by_name['CmdScanStart'] = _CMDSCANSTART
+DESCRIPTOR.message_types_by_name['RespScanStart'] = _RESPSCANSTART
+DESCRIPTOR.message_types_by_name['CmdScanStatus'] = _CMDSCANSTATUS
+DESCRIPTOR.message_types_by_name['RespScanStatus'] = _RESPSCANSTATUS
+DESCRIPTOR.message_types_by_name['CmdScanResult'] = _CMDSCANRESULT
+DESCRIPTOR.message_types_by_name['WiFiScanResult'] = _WIFISCANRESULT
+DESCRIPTOR.message_types_by_name['RespScanResult'] = _RESPSCANRESULT
+DESCRIPTOR.message_types_by_name['WiFiScanPayload'] = _WIFISCANPAYLOAD
+DESCRIPTOR.enum_types_by_name['WiFiScanMsgType'] = _WIFISCANMSGTYPE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+CmdScanStart = _reflection.GeneratedProtocolMessageType('CmdScanStart', (_message.Message,), dict(
+ DESCRIPTOR = _CMDSCANSTART,
+ __module__ = 'wifi_scan_pb2'
+ # @@protoc_insertion_point(class_scope:CmdScanStart)
+ ))
+_sym_db.RegisterMessage(CmdScanStart)
+
+RespScanStart = _reflection.GeneratedProtocolMessageType('RespScanStart', (_message.Message,), dict(
+ DESCRIPTOR = _RESPSCANSTART,
+ __module__ = 'wifi_scan_pb2'
+ # @@protoc_insertion_point(class_scope:RespScanStart)
+ ))
+_sym_db.RegisterMessage(RespScanStart)
+
+CmdScanStatus = _reflection.GeneratedProtocolMessageType('CmdScanStatus', (_message.Message,), dict(
+ DESCRIPTOR = _CMDSCANSTATUS,
+ __module__ = 'wifi_scan_pb2'
+ # @@protoc_insertion_point(class_scope:CmdScanStatus)
+ ))
+_sym_db.RegisterMessage(CmdScanStatus)
+
+RespScanStatus = _reflection.GeneratedProtocolMessageType('RespScanStatus', (_message.Message,), dict(
+ DESCRIPTOR = _RESPSCANSTATUS,
+ __module__ = 'wifi_scan_pb2'
+ # @@protoc_insertion_point(class_scope:RespScanStatus)
+ ))
+_sym_db.RegisterMessage(RespScanStatus)
+
+CmdScanResult = _reflection.GeneratedProtocolMessageType('CmdScanResult', (_message.Message,), dict(
+ DESCRIPTOR = _CMDSCANRESULT,
+ __module__ = 'wifi_scan_pb2'
+ # @@protoc_insertion_point(class_scope:CmdScanResult)
+ ))
+_sym_db.RegisterMessage(CmdScanResult)
+
+WiFiScanResult = _reflection.GeneratedProtocolMessageType('WiFiScanResult', (_message.Message,), dict(
+ DESCRIPTOR = _WIFISCANRESULT,
+ __module__ = 'wifi_scan_pb2'
+ # @@protoc_insertion_point(class_scope:WiFiScanResult)
+ ))
+_sym_db.RegisterMessage(WiFiScanResult)
+
+RespScanResult = _reflection.GeneratedProtocolMessageType('RespScanResult', (_message.Message,), dict(
+ DESCRIPTOR = _RESPSCANRESULT,
+ __module__ = 'wifi_scan_pb2'
+ # @@protoc_insertion_point(class_scope:RespScanResult)
+ ))
+_sym_db.RegisterMessage(RespScanResult)
+
+WiFiScanPayload = _reflection.GeneratedProtocolMessageType('WiFiScanPayload', (_message.Message,), dict(
+ DESCRIPTOR = _WIFISCANPAYLOAD,
+ __module__ = 'wifi_scan_pb2'
+ # @@protoc_insertion_point(class_scope:WiFiScanPayload)
+ ))
+_sym_db.RegisterMessage(WiFiScanPayload)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/cli/server_cert/server_cert.pem b/cli/server_cert/server_cert.pem
new file mode 100644
index 0000000..61ae256
--- /dev/null
+++ b/cli/server_cert/server_cert.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF
+ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6
+b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL
+MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv
+b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj
+ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM
+9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw
+IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6
+VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L
+93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm
+jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA
+A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI
+U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs
+N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv
+o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU
+5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy
+rqXRfboQnoZsG4q5WTP468SQvvG5
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/components/button/CMakeLists.txt b/components/button/CMakeLists.txt
new file mode 100644
index 0000000..0f94704
--- /dev/null
+++ b/components/button/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(COMPONENT_ADD_INCLUDEDIRS button/include)
+set(COMPONENT_SRCS "button/button.c"
+ "button/button_obj.cpp")
+
+register_component()
diff --git a/components/button/Kconfig b/components/button/Kconfig
new file mode 100644
index 0000000..8125118
--- /dev/null
+++ b/components/button/Kconfig
@@ -0,0 +1,6 @@
+menu "Button"
+ config IO_GLITCH_FILTER_TIME_MS
+ int "IO glitch filter timer ms (10~100)"
+ range 10 100
+ default 50
+endmenu
\ No newline at end of file
diff --git a/components/button/button/button.c b/components/button/button/button.c
new file mode 100644
index 0000000..7adb0da
--- /dev/null
+++ b/components/button/button/button.c
@@ -0,0 +1,363 @@
+/*
+ * ESPRESSIF MIT License
+ *
+ * Copyright (c) 2018
+ *
+ * Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case,
+ * it is free of charge, to any person obtaining a copy of this software and associated
+ * documentation files (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define IOT_CHECK(tag, a, ret) if(!(a)) { \
+ ESP_LOGE(tag,"%s:%d (%s)", __FILE__, __LINE__, __FUNCTION__); \
+ return (ret); \
+ }
+#define ERR_ASSERT(tag, param) IOT_CHECK(tag, (param) == ESP_OK, ESP_FAIL)
+#define POINT_ASSERT(tag, param, ret) IOT_CHECK(tag, (param) != NULL, (ret))
+
+typedef enum {
+ BUTTON_STATE_IDLE = 0,
+ BUTTON_STATE_PUSH,
+ BUTTON_STATE_PRESSED,
+} button_status_t;
+
+typedef struct button_dev button_dev_t;
+typedef struct btn_cb button_cb_t;
+
+struct btn_cb{
+ TickType_t interval;
+ button_cb cb;
+ void* arg;
+ uint8_t on_press;
+ TimerHandle_t tmr;
+ button_dev_t *pbtn;
+ button_cb_t *next_cb;
+};
+
+struct button_dev{
+ uint8_t io_num;
+ uint8_t active_level;
+ uint32_t serial_thres_sec;
+ uint8_t taskq_on;
+ QueueHandle_t taskq;
+ QueueHandle_t argq;
+ button_status_t state;
+ button_cb_t tap_short_cb;
+ button_cb_t tap_psh_cb;
+ button_cb_t tap_rls_cb;
+ button_cb_t press_serial_cb;
+ button_cb_t* cb_head;
+};
+
+#define BUTTON_GLITCH_FILTER_TIME_MS CONFIG_IO_GLITCH_FILTER_TIME_MS
+static const char* TAG = "button";
+
+static void button_press_cb(xTimerHandle tmr)
+{
+ button_cb_t* btn_cb = (button_cb_t*) pvTimerGetTimerID(tmr);
+ button_dev_t* btn = btn_cb->pbtn;
+ // low, then restart
+ if (btn->active_level == gpio_get_level(btn->io_num)) {
+ btn->state = BUTTON_STATE_PRESSED;
+ if (btn->taskq != NULL && btn->argq != NULL && btn->taskq_on && !btn_cb->on_press) {
+ void *tmp = btn_cb->cb;
+ xQueueOverwrite(btn->taskq, &tmp);
+ xQueueOverwrite(btn->argq, &btn_cb->arg);
+ } else if (btn_cb->cb) {
+ btn_cb->cb(btn_cb->arg);
+ }
+ }
+}
+
+static void button_tap_psh_cb(xTimerHandle tmr)
+{
+ button_cb_t* btn_cb = (button_cb_t*) pvTimerGetTimerID(tmr);
+ button_dev_t* btn = btn_cb->pbtn;
+ xTimerStop(btn->tap_rls_cb.tmr, portMAX_DELAY);
+ int lv = gpio_get_level(btn->io_num);
+
+ if (btn->active_level == lv) {
+ // True implies key is pressed
+ btn->state = BUTTON_STATE_PUSH;
+ if (btn->press_serial_cb.tmr) {
+ xTimerChangePeriod(btn->press_serial_cb.tmr, btn->serial_thres_sec*1000 / portTICK_PERIOD_MS, portMAX_DELAY);
+ xTimerReset(btn->press_serial_cb.tmr, portMAX_DELAY);
+ }
+ if (btn->tap_psh_cb.cb) {
+ btn->tap_psh_cb.cb(btn->tap_psh_cb.arg);
+ }
+ } else {
+ // 50ms, check if this is a real key up
+ if (btn->tap_rls_cb.tmr) {
+ xTimerStop(btn->tap_rls_cb.tmr, portMAX_DELAY);
+ xTimerReset(btn->tap_rls_cb.tmr, portMAX_DELAY);
+ }
+ }
+}
+
+static void button_tap_rls_cb(xTimerHandle tmr)
+{
+ button_cb_t* btn_cb = (button_cb_t*) pvTimerGetTimerID(tmr);
+ button_dev_t* btn = btn_cb->pbtn;
+ xTimerStop(btn->tap_rls_cb.tmr, portMAX_DELAY);
+ if (btn->active_level == gpio_get_level(btn->io_num)) {
+
+ } else {
+ // high, then key is up
+ button_cb_t *pcb = btn->cb_head;
+ while (pcb != NULL) {
+ if (pcb->tmr != NULL) {
+ xTimerStop(pcb->tmr, portMAX_DELAY);
+ }
+ pcb = pcb->next_cb;
+ }
+ if (btn->taskq != NULL && btn->argq != NULL && btn->taskq_on && uxQueueMessagesWaiting(btn->taskq) != 0 && btn->state != BUTTON_STATE_IDLE) {
+ void (*task)(void*);
+ void *arg;
+ xQueueReceive(btn->taskq, &task, 0);
+ xQueueReceive(btn->argq, &arg, 0);
+ task(arg);
+ }
+ if (btn->press_serial_cb.tmr && btn->press_serial_cb.tmr != NULL) {
+ xTimerStop(btn->press_serial_cb.tmr, portMAX_DELAY);
+ }
+ if (btn->tap_short_cb.cb && btn->state == BUTTON_STATE_PUSH) {
+ btn->tap_short_cb.cb(btn->tap_short_cb.arg);
+ }
+ if(btn->tap_rls_cb.cb && btn->state != BUTTON_STATE_IDLE) {
+ btn->tap_rls_cb.cb(btn->tap_rls_cb.arg);
+ }
+ btn->state = BUTTON_STATE_IDLE;
+ }
+}
+
+static void button_press_serial_cb(xTimerHandle tmr)
+{
+ button_dev_t* btn = (button_dev_t*) pvTimerGetTimerID(tmr);
+ if (btn->press_serial_cb.cb) {
+ btn->press_serial_cb.cb(btn->press_serial_cb.arg);
+ }
+ xTimerChangePeriod(btn->press_serial_cb.tmr, btn->press_serial_cb.interval, portMAX_DELAY);
+ xTimerReset(btn->press_serial_cb.tmr, portMAX_DELAY);
+}
+
+static void button_gpio_isr_handler(void* arg)
+{
+ button_dev_t* btn = (button_dev_t*) arg;
+ portBASE_TYPE HPTaskAwoken = pdFALSE;
+ int level = gpio_get_level(btn->io_num);
+ if (level == btn->active_level) {
+ if (btn->tap_psh_cb.tmr) {
+ xTimerStopFromISR(btn->tap_psh_cb.tmr, &HPTaskAwoken);
+ xTimerResetFromISR(btn->tap_psh_cb.tmr, &HPTaskAwoken);
+ }
+
+ button_cb_t *pcb = btn->cb_head;
+ while (pcb != NULL) {
+ if (pcb->tmr != NULL) {
+ xTimerStopFromISR(pcb->tmr, &HPTaskAwoken);
+ xTimerResetFromISR(pcb->tmr, &HPTaskAwoken);
+ }
+ pcb = pcb->next_cb;
+ }
+ } else {
+ // 50ms, check if this is a real key up
+ if (btn->tap_rls_cb.tmr) {
+ xTimerStopFromISR(btn->tap_rls_cb.tmr, &HPTaskAwoken);
+ xTimerResetFromISR(btn->tap_rls_cb.tmr, &HPTaskAwoken);
+ }
+ }
+ if(HPTaskAwoken == pdTRUE) {
+ portYIELD_FROM_ISR();
+ }
+}
+
+static void button_free_tmr(xTimerHandle* tmr)
+{
+ if (tmr && *tmr) {
+ xTimerStop(*tmr, portMAX_DELAY);
+ xTimerDelete(*tmr, portMAX_DELAY);
+ *tmr = NULL;
+ }
+}
+
+esp_err_t iot_button_delete(button_handle_t btn_handle)
+{
+ POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG);
+ button_dev_t* btn = (button_dev_t*) btn_handle;
+ gpio_set_intr_type(btn->io_num, GPIO_INTR_DISABLE);
+ gpio_isr_handler_remove(btn->io_num);
+
+ button_free_tmr(&btn->tap_rls_cb.tmr);
+ button_free_tmr(&btn->tap_psh_cb.tmr);
+ button_free_tmr(&btn->tap_short_cb.tmr);
+ button_free_tmr(&btn->press_serial_cb.tmr);
+
+ button_cb_t *pcb = btn->cb_head;
+ while (pcb != NULL) {
+ button_cb_t *cb_next = pcb->next_cb;
+ button_free_tmr(&pcb->tmr);
+ free(pcb);
+ pcb = cb_next;
+ }
+ free(btn);
+ return ESP_OK;
+}
+
+button_handle_t iot_button_create(gpio_num_t gpio_num, button_active_t active_level)
+{
+ IOT_CHECK(TAG, gpio_num < GPIO_NUM_MAX, NULL);
+ button_dev_t* btn = (button_dev_t*) calloc(1, sizeof(button_dev_t));
+ POINT_ASSERT(TAG, btn, NULL);
+ btn->active_level = active_level;
+ btn->io_num = gpio_num;
+ btn->state = BUTTON_STATE_IDLE;
+ btn->taskq_on = 0;
+ btn->taskq = xQueueCreate(1, sizeof(void*));
+ btn->argq = xQueueCreate(1, sizeof(void *));
+ btn->tap_rls_cb.arg = NULL;
+ btn->tap_rls_cb.cb = NULL;
+ btn->tap_rls_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_PERIOD_MS;
+ btn->tap_rls_cb.pbtn = btn;
+ btn->tap_rls_cb.tmr = xTimerCreate("btn_rls_tmr", btn->tap_rls_cb.interval, pdFALSE,
+ &btn->tap_rls_cb, button_tap_rls_cb);
+ btn->tap_psh_cb.arg = NULL;
+ btn->tap_psh_cb.cb = NULL;
+ btn->tap_psh_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_PERIOD_MS;
+ btn->tap_psh_cb.pbtn = btn;
+ btn->tap_psh_cb.tmr = xTimerCreate("btn_psh_tmr", btn->tap_psh_cb.interval, pdFALSE,
+ &btn->tap_psh_cb, button_tap_psh_cb);
+ gpio_install_isr_service(0);
+ gpio_config_t gpio_conf;
+ gpio_conf.intr_type = GPIO_INTR_ANYEDGE;
+ gpio_conf.mode = GPIO_MODE_INPUT;
+ gpio_conf.pin_bit_mask = (1 << gpio_num);
+ gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&gpio_conf);
+ gpio_isr_handler_add(gpio_num, button_gpio_isr_handler, btn);
+ return (button_handle_t) btn;
+}
+
+esp_err_t iot_button_rm_cb(button_handle_t btn_handle, button_cb_type_t type)
+{
+ button_dev_t* btn = (button_dev_t*) btn_handle;
+ button_cb_t* btn_cb = NULL;
+ if (type == BUTTON_CB_PUSH) {
+ btn_cb = &btn->tap_psh_cb;
+ } else if (type == BUTTON_CB_RELEASE) {
+ btn_cb = &btn->tap_rls_cb;
+ } else if (type == BUTTON_CB_TAP) {
+ btn_cb = &btn->tap_short_cb;
+ } else if (type == BUTTON_CB_SERIAL) {
+ btn_cb = &btn->press_serial_cb;
+ }
+ btn_cb->cb = NULL;
+ btn_cb->arg = NULL;
+ btn_cb->pbtn = btn;
+ button_free_tmr(&btn_cb->tmr);
+ return ESP_OK;
+}
+
+esp_err_t iot_button_set_serial_cb(button_handle_t btn_handle, uint32_t start_after_sec, TickType_t interval_tick, button_cb cb, void* arg)
+{
+ button_dev_t* btn = (button_dev_t*) btn_handle;
+ btn->serial_thres_sec = start_after_sec;
+ if (btn->press_serial_cb.tmr == NULL) {
+ btn->press_serial_cb.tmr = xTimerCreate("btn_serial_tmr", btn->serial_thres_sec*1000 / portTICK_PERIOD_MS,
+ pdFALSE, btn, button_press_serial_cb);
+ }
+ btn->press_serial_cb.arg = arg;
+ btn->press_serial_cb.cb = cb;
+ btn->press_serial_cb.interval = interval_tick;
+ btn->press_serial_cb.pbtn = btn;
+ xTimerChangePeriod(btn->press_serial_cb.tmr, btn->serial_thres_sec*1000 / portTICK_PERIOD_MS, portMAX_DELAY);
+ return ESP_OK;
+}
+
+esp_err_t iot_button_set_evt_cb(button_handle_t btn_handle, button_cb_type_t type, button_cb cb, void* arg)
+{
+ POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG);
+ button_dev_t* btn = (button_dev_t*) btn_handle;
+ if (type == BUTTON_CB_PUSH) {
+ btn->tap_psh_cb.arg = arg;
+ btn->tap_psh_cb.cb = cb;
+ btn->tap_psh_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_RATE_MS;
+ btn->tap_psh_cb.pbtn = btn;
+ xTimerChangePeriod(btn->tap_psh_cb.tmr, btn->tap_psh_cb.interval, portMAX_DELAY);
+ } else if (type == BUTTON_CB_RELEASE) {
+ btn->tap_rls_cb.arg = arg;
+ btn->tap_rls_cb.cb = cb;
+ btn->tap_rls_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_RATE_MS;
+ btn->tap_rls_cb.pbtn = btn;
+ xTimerChangePeriod(btn->tap_rls_cb.tmr, btn->tap_psh_cb.interval, portMAX_DELAY);
+ } else if (type == BUTTON_CB_TAP) {
+ btn->tap_short_cb.arg = arg;
+ btn->tap_short_cb.cb = cb;
+ btn->tap_short_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_RATE_MS;
+ btn->tap_short_cb.pbtn = btn;
+ } else if (type == BUTTON_CB_SERIAL) {
+ iot_button_set_serial_cb(btn_handle, 1, 1000 / portTICK_RATE_MS, cb, arg);
+ }
+ return ESP_OK;
+}
+
+esp_err_t iot_button_add_on_press_cb(button_handle_t btn_handle, uint32_t press_sec, button_cb cb, void* arg)
+{
+ POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG);
+ IOT_CHECK(TAG, press_sec != 0, ESP_ERR_INVALID_ARG);
+ button_dev_t* btn = (button_dev_t*) btn_handle;
+ button_cb_t* cb_new = (button_cb_t*) calloc(1, sizeof(button_cb_t));
+ POINT_ASSERT(TAG, cb_new, ESP_FAIL);
+ cb_new->on_press = 1;
+ cb_new->arg = arg;
+ cb_new->cb = cb;
+ cb_new->interval = press_sec * 1000 / portTICK_PERIOD_MS;
+ cb_new->pbtn = btn;
+ cb_new->tmr = xTimerCreate("btn_press_tmr", cb_new->interval, pdFALSE, cb_new, button_press_cb);
+ cb_new->next_cb = btn->cb_head;
+ btn->cb_head = cb_new;
+ return ESP_OK;
+}
+
+esp_err_t iot_button_add_on_release_cb(button_handle_t btn_handle, uint32_t press_sec, button_cb cb, void* arg)
+{
+ POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG);
+ IOT_CHECK(TAG, press_sec != 0, ESP_ERR_INVALID_ARG);
+ button_dev_t* btn = (button_dev_t*) btn_handle;
+ button_cb_t* cb_new = (button_cb_t*) calloc(1, sizeof(button_cb_t));
+ POINT_ASSERT(TAG, cb_new, ESP_FAIL);
+ btn->taskq_on = 1;
+ cb_new->arg = arg;
+ cb_new->cb = cb;
+ cb_new->interval = press_sec * 1000 / portTICK_PERIOD_MS;
+ cb_new->pbtn = btn;
+ cb_new->tmr = xTimerCreate("btn_press_tmr", cb_new->interval, pdFALSE, cb_new, button_press_cb);
+ cb_new->next_cb = btn->cb_head;
+ btn->cb_head = cb_new;
+ return ESP_OK;
+}
+
diff --git a/components/button/button/button_obj.cpp b/components/button/button/button_obj.cpp
new file mode 100644
index 0000000..8a1f287
--- /dev/null
+++ b/components/button/button/button_obj.cpp
@@ -0,0 +1,64 @@
+/*
+ * ESPRESSIF MIT License
+ *
+ * Copyright (c) 2017
+ *
+ * Permission is hereby granted for use on ESPRESSIF SYSTEMS products only, in which case,
+ * it is free of charge, to any person obtaining a copy of this software and associated
+ * documentation files (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#include
+#include
+#include
+#include
+
+CButton::CButton(gpio_num_t gpio_num, button_active_t active_level)
+{
+ m_btn_handle = iot_button_create(gpio_num, active_level);
+}
+
+CButton::~CButton()
+{
+ iot_button_delete(m_btn_handle);
+ m_btn_handle = NULL;
+}
+
+esp_err_t CButton::set_evt_cb(button_cb_type_t type, button_cb cb, void* arg)
+{
+ return iot_button_set_evt_cb(m_btn_handle, type, cb, arg);
+}
+
+esp_err_t CButton::set_serial_cb(button_cb cb, void* arg, TickType_t interval_tick, uint32_t start_after_sec)
+{
+ return iot_button_set_serial_cb(m_btn_handle, start_after_sec, interval_tick, cb, arg);
+}
+
+esp_err_t CButton::add_on_press_cb(uint32_t press_sec, button_cb cb, void* arg)
+{
+ return iot_button_add_on_press_cb(m_btn_handle, press_sec, cb, arg);
+}
+
+esp_err_t CButton::add_on_release_cb(uint32_t press_sec, button_cb cb, void* arg)
+{
+ return iot_button_add_on_release_cb(m_btn_handle, press_sec, cb, arg);
+}
+
+esp_err_t CButton::rm_cb(button_cb_type_t type)
+{
+ return iot_button_rm_cb(m_btn_handle, type);
+}
diff --git a/components/button/button/include/iot_button.h b/components/button/button/include/iot_button.h
new file mode 100644
index 0000000..287959b
--- /dev/null
+++ b/components/button/button/include/iot_button.h
@@ -0,0 +1,281 @@
+/*
+ * ESPRESSIF MIT License
+ *
+ * Copyright (c) 2018
+ *
+ * Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case,
+ * it is free of charge, to any person obtaining a copy of this software and associated
+ * documentation files (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+
+#ifndef _IOT_BUTTON_H_
+#define _IOT_BUTTON_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#include
+typedef void (* button_cb)(void*);
+typedef void* button_handle_t;
+
+typedef enum {
+ BUTTON_ACTIVE_HIGH = 1, /*!