添加智能灯固件代码

This commit is contained in:
kerwincui
2021-07-13 17:14:51 +08:00
parent 332f74dd17
commit ecc0b91b8b
2568 changed files with 229441 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(esp_local_ctrl)

View File

@@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := esp_local_ctrl
include $(IDF_PATH)/make/project.mk

View File

@@ -0,0 +1,108 @@
# ESP Local Control using HTTPS server
This example creates a `esp_local_ctrl` service over HTTPS transport, for securely controlling the device over local network. In this case the device name is resolved through `mDNS`, which in this example is `my_esp_ctrl_device.local`.
See the `esp_local_ctrl` component documentation for details.
Before using the example, run `idf.py menuconfig` (or `idf.py menuconfig` if using CMake build system) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../README.md) for more details.
## Client Side Implementation
A python test script `scripts/esp_local_ctrl.py` has been provided for as a client side application for controlling the device over the same Wi-Fi network. The script relies on a pre-generated `main/certs/rootCA.pem` to verify the server certificate. The server side private key and certificate can also be found under `main/certs`, namely `prvtkey.pem` and `cacert.pem`.
After configuring the Wi-Fi, flashing and booting the device, run the following command to test the device name
resolution through mDNS:
```
ping my_esp_ctrl_device.local
```
Sample output:
```
64 bytes from 192.168.32.156 (192.168.32.156): icmp_seq=1 ttl=255 time=58.1 ms
64 bytes from 192.168.32.156 (192.168.32.156): icmp_seq=2 ttl=255 time=89.9 ms
64 bytes from 192.168.32.156 (192.168.32.156): icmp_seq=3 ttl=255 time=123 ms
```
After you've tested the name resolution, run:
```
python scripts/esp_local_ctrl.py
```
Sample output:
```
python scripts/esp_local_ctrl.py
==== Acquiring properties information ====
==== Acquired properties information ====
==== Available Properties ====
S.N. Name Type Flags Value
[ 1] timestamp (us) TIME(us) Read-Only 168561481
[ 2] property1 INT32 123456
[ 3] property2 BOOLEAN Read-Only True
[ 4] property3 STRING
Select properties to set (0 to re-read, 'q' to quit) : 0
==== Available Properties ====
S.N. Name Type Flags Value
[ 1] timestamp (us) TIME(us) Read-Only 22380117
[ 2] property1 INT32 123456
[ 3] property2 BOOLEAN Read-Only False
[ 4] property3 STRING
Select properties to set (0 to re-read, 'q' to quit) : 2,4
Enter value to set for property (property1) : -5555
Enter value to set for property (property3) : hello world!
==== Available Properties ====
S.N. Name Type Flags Value
[ 1] timestamp (us) TIME(us) Read-Only 55110859
[ 2] property1 INT32 -5555
[ 3] property2 BOOLEAN Read-Only False
[ 4] property3 STRING hello world!
Select properties to set (0 to re-read, 'q' to quit) : q
Quitting...
```
The script also allows to connect over BLE, and provide a custom service name. To display the list of supported parameters, run:
```
python scripts/esp_local_ctrl.py --help
```
## Certificates
You can generate a new server certificate using the OpenSSL command line tool.
For the purpose of this example, lets generate a rootCA, which we will use to sign the server certificates and which the client will use to verify the server certificate during SSL handshake. You will need to set a password for encrypting the generated `rootkey.pem`.
```
openssl req -new -x509 -subj "/CN=root" -days 3650 -sha256 -out rootCA.pem -keyout rootkey.pem
```
Now generate a certificate signing request for the server, along with its private key `prvtkey.pem`.
```
openssl req -newkey rsa:2048 -nodes -keyout prvtkey.pem -days 3650 -out server.csr -subj "/CN=my_esp_ctrl_device.local"
```
Now use the previously generated rootCA to process the server's certificate signing request, and generate a signed certificate `cacert.pem`. The password set for encrypting `rootkey.pem` earlier, has to be entered during this step.
```
openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootkey.pem -CAcreateserial -out cacert.pem -days 500 -sha256
```
Now that we have `rootCA.pem`, `cacert.pem` and `prvtkey.pem`, copy these into main/certs. Note that only the server related files (`cacert.pem` and `prvtkey.pem`) are embedded into the firmware.
Expiry time and metadata fields can be adjusted in the invocation.
Please see the `openssl` man pages (man `openssl-req`) for more details.
It is **strongly recommended** to not reuse the example certificate in your application;
it is included only for demonstration.

View File

@@ -0,0 +1,78 @@
from __future__ import unicode_literals
import os
import re
import sys
import ttfw_idf
@ttfw_idf.idf_example_test(env_tag='Example_WIFI')
def test_examples_esp_local_ctrl(env, extra_data):
rel_project_path = os.path.join('examples', 'protocols', 'esp_local_ctrl')
dut = env.get_dut('esp_local_ctrl', rel_project_path)
idf_path = dut.app.get_sdk_path()
dut.start_app()
dut_ip = dut.expect(re.compile(r'esp_netif_handlers: sta ip: (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'))[0]
dut.expect('esp_https_server: Starting server')
dut.expect('esp_https_server: Server listening on port 443')
dut.expect('control: esp_local_ctrl service started with name : my_esp_ctrl_device')
def dut_expect_read():
dut.expect('control: Reading property : timestamp (us)')
dut.expect('control: Reading property : property1')
dut.expect('control: Reading property : property2')
dut.expect('control: Reading property : property3')
# Running mDNS services in docker is not a trivial task. Therefore, the script won't connect to the host name but
# to IP address. However, the certificates were generated for the host name and will be rejected.
cmd = ' '.join([sys.executable, os.path.join(idf_path, rel_project_path, 'scripts/esp_local_ctrl.py'),
'--name', dut_ip,
'--dont-check-hostname']) # don't reject the certificate because of the hostname
esp_local_ctrl_log = os.path.join(idf_path, rel_project_path, 'esp_local_ctrl.log')
with ttfw_idf.CustomProcess(cmd, esp_local_ctrl_log) as ctrl_py:
def expect_properties(prop1, prop3):
dut_expect_read()
ctrl_py.pexpect_proc.expect_exact('==== Available Properties ====')
ctrl_py.pexpect_proc.expect(re.compile(r'S.N. Name\s+Type\s+Flags\s+Value'))
ctrl_py.pexpect_proc.expect(re.compile(r'\[ 1\] timestamp \(us\)\s+TIME\(us\)\s+Read-Only\s+\d+'))
ctrl_py.pexpect_proc.expect(re.compile(r'\[ 2\] property1\s+INT32\s+{}'.format(prop1)))
ctrl_py.pexpect_proc.expect(re.compile(r'\[ 3\] property2\s+BOOLEAN\s+Read-Only\s+(True)|(False)'))
ctrl_py.pexpect_proc.expect(re.compile(r'\[ 4\] property3\s+STRING\s+{}'.format(prop3)))
ctrl_py.pexpect_proc.expect_exact('Select properties to set (0 to re-read, \'q\' to quit) :')
property1 = 123456789
property3 = ''
ctrl_py.pexpect_proc.expect_exact('Connecting to {}'.format(dut_ip))
dut.expect('esp_https_server: performing session handshake', timeout=60)
expect_properties(property1, property3)
ctrl_py.pexpect_proc.sendline('1')
ctrl_py.pexpect_proc.expect_exact('Enter value to set for property (timestamp (us)) :')
ctrl_py.pexpect_proc.sendline('2')
ctrl_py.pexpect_proc.expect_exact('Failed to set values!')
dut.expect('control: timestamp (us) is read-only')
expect_properties(property1, property3)
property1 = 638
ctrl_py.pexpect_proc.sendline('2')
ctrl_py.pexpect_proc.expect_exact('Enter value to set for property (property1) :')
ctrl_py.pexpect_proc.sendline(str(property1))
dut.expect('control: Setting property1 value to {}'.format(property1))
expect_properties(property1, property3)
property3 = 'test'
ctrl_py.pexpect_proc.sendline('4')
ctrl_py.pexpect_proc.expect_exact('Enter value to set for property (property3) :')
ctrl_py.pexpect_proc.sendline(property3)
dut.expect('control: Setting property3 value to {}'.format(property3))
expect_properties(property1, property3)
ctrl_py.pexpect_proc.sendline('q')
ctrl_py.pexpect_proc.expect_exact('Quitting...')
if __name__ == '__main__':
test_examples_esp_local_ctrl()

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "app_main.c" "esp_local_ctrl_service.c"
INCLUDE_DIRS "."
EMBED_TXTFILES "certs/cacert.pem" "certs/prvtkey.pem")

View File

@@ -0,0 +1,20 @@
menu "Example Configuration"
config EXAMPLE_WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config EXAMPLE_WIFI_PASSWORD
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2) for the example to use.
config EXAMPLE_MAXIMUM_RETRY
int "Maximum retry"
default 5
help
Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent.
endmenu

View File

@@ -0,0 +1,145 @@
/* Local Ctrl Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "sdkconfig.h"
#include "lwip/err.h"
#include "lwip/sys.h"
/* The examples use WiFi configuration that you can set via 'idf.py menuconfig'.
If you'd rather not, just change the below entries to strings with
the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
*/
#define EXAMPLE_ESP_WIFI_SSID CONFIG_EXAMPLE_WIFI_SSID
#define EXAMPLE_ESP_WIFI_PASS CONFIG_EXAMPLE_WIFI_PASSWORD
#define EXAMPLE_ESP_MAXIMUM_RETRY CONFIG_EXAMPLE_MAXIMUM_RETRY
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
/* The event group allows multiple bits for each event, but we only care about two events:
* - we are connected to the AP with an IP
* - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static const char *TAG = "local_ctrl_example";
static int s_retry_num = 0;
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG,"connect to the AP fail");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
esp_err_t wifi_init_sta(void)
{
esp_err_t ret_value = ESP_OK;
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
assert(sta_netif);
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.password = EXAMPLE_ESP_WIFI_PASS
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
ESP_ERROR_CHECK(esp_wifi_start() );
ESP_LOGI(TAG, "wifi_init_sta finished.");
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
* number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
* happened. */
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
ret_value = ESP_FAIL;
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
ret_value = ESP_ERR_INVALID_STATE;
}
ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler));
ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler));
vEventGroupDelete(s_wifi_event_group);
return ret_value;
}
/* Function responsible for configuring and starting the esp_local_ctrl service.
* See local_ctrl_service.c for implementation */
extern void start_esp_local_ctrl_service(void);
void app_main(void)
{
//Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
if (wifi_init_sta() == ESP_OK) {
start_esp_local_ctrl_service();
} else {
ESP_LOGI(TAG, "Connection failed, not starting esp_local_ctrl service");
}
}

View File

@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICrjCCAZYCCQDGnK9OU3UN2TANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARy
b290MB4XDTIwMTExMDExMzExNVoXDTMwMTExMDExMzExNVowIzEhMB8GA1UEAwwY
bXlfZXNwX2N0cmxfZGV2aWNlLmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEA2NgeOgHTX6yURoB8u3BAphDMTlp/Ar8oAtoO+xqIPw1sZKmhJLAS
bfKkHKhi7pr/h31xOHqzTxlPkUzWpfszFx5YiDFYtiIlcObrgk83u3CtvBw7wuZ6
BA/01hkiSGgkAFD/xnRNLKgidTu1tCIa2QY7Jnp+HdJz6yJws1/WAzn2lsXcJwSd
6tPu2U0lhE2w6ylCdLYD3upveo/80WArQqNg6bv6Wbz8iL18E87enpwfHMA7ZN+S
sDq7HACRjapAkcimjbzkrh7/f9Nr6c8KpPyeiWyHFxVTbmEj4NMG9IpbTKp9CMAt
ysmiPYAYNFXsTHjoRVf4EbfHbxGHobUwewIDAQABMA0GCSqGSIb3DQEBCwUAA4IB
AQBWg9Xh1MG4d4gGGx920OJBm+qQ9XY9oV2Aap81ZYshgBlnUKoKLhYolp/CHXyr
IXy7YA01ASX2wzohguqakdo0ghYkhwuRoly+0+uzphmqyMqXnTUDCgEcZF4l90xl
jRdMenqEgfOXDNk2VAK/rmAZ2jZsaGpBI4NRbEdwH1MVd61g2NVBk0nEI73cW6Ki
BPxMw2aGFizTwcPT9gwbQgLdLZeEuvcPrdzK5swqccZ+MBHMcwW/qvcmwqJGeLL2
zmx7o2ODQyElIKLKUDWAFIYrb7DXR4oajjhUa0+SOj9Ydj/5+eZ+Wx7NJoG+oH7N
DB0jK2qB8eexplQj1KLWS2Un
-----END CERTIFICATE-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDY2B46AdNfrJRG
gHy7cECmEMxOWn8CvygC2g77Gog/DWxkqaEksBJt8qQcqGLumv+HfXE4erNPGU+R
TNal+zMXHliIMVi2IiVw5uuCTze7cK28HDvC5noED/TWGSJIaCQAUP/GdE0sqCJ1
O7W0IhrZBjsmen4d0nPrInCzX9YDOfaWxdwnBJ3q0+7ZTSWETbDrKUJ0tgPe6m96
j/zRYCtCo2Dpu/pZvPyIvXwTzt6enB8cwDtk35KwOrscAJGNqkCRyKaNvOSuHv9/
02vpzwqk/J6JbIcXFVNuYSPg0wb0iltMqn0IwC3KyaI9gBg0VexMeOhFV/gRt8dv
EYehtTB7AgMBAAECggEBAJSvM6Kgp9fdVNo2tdAsOxfjQsOjB53RhtTVwhnpamyZ
fq5TJZwrYqejDWZdC2ECRJ4ZpG2OrK5a85T0s+Whpbl/ZEMWWvaf2T5eCDQUr2lF
7MqkLVIJiLaKXl4DY990EONqpsbj7hrluqLZ61B1ZiVTQXGz4g/+wt8CgXZtCyiv
7XOTTmQueugq4f54JBX5isdB7/xLaXV3kycaEK1b6ZVFYB3ii5IKKsX7RK/ksA6O
fRrQ8702prqphPfbjZ9wPHif/zLiyiF2FG6OX1Y3aZe1npRsvuH2c3M2h+HGAQUR
3lDxMTNbsE8E+XKZFVAVdMqot2RfxENSHoJHcp1R2YECgYEA9qe1+eOZKd0w5lC1
PuG6FLAAbK1nuv/ovESEHtILTLFkMijgAqcWjtp1klS86IBJLnjv+GYxZu2n1WI9
QLnh++NNTjRGCMM2Adf5SBJ/5F85rpgzz7Yur1guqkUQx/2dmErOaWQ4IO304VlM
vrJB8+XmAiysEgJOkK0Mx8xRVcECgYEA4Q9GBADLryvwjisp/PdTRXOvd7pJRGH3
SdC1k/nBsmpmbouc0ihqzOiiN0kUjE2yLSlhwxxWBJqNSzOk9z5/LB4TNRqH9gCL
rUN67FgzwR5H70OblWpcjWRurFq34+ZWEmCG+1qUwZMT7dYe4CiDYnVjcwfUpQwN
qRpjeMLDrTsCgYEAgo1CRIGzD/WDbGRLinzvgQOnNd6SiOfqx7t8MtP6Jx29as83
wi+uQO5gTJONaYJ9OZvJaDCu9UvVCZx1z0yT0D7/K+V/LCQm8dLenscr6jR802y7
/7TuAOEr0fO8bh5Oy8zMc/wXuVY5xwz9EfJH9lA47e23JdESxIDTwuziIAECgYEA
qKQdPtqpxbUTKDTH3bomN6CcFwcL56XQ+wrdROidb+eyoZsUA5YtkSWwh+TG9Osz
XAvqKZ2OBx0YSwWD05CNEq3mjqA2yOtXvpkV/wuInGjoVi0+5BMzDu/2zkecC7WJ
QXP7MVWKqhJfmJQdxrIU4S49OvDfMl15zwDrEI5AugkCgYBn5+/KvrA8GGWD9p1r
qwjoojGBvCJn1Kj/s+IQYePYeiRe6/eSPGFHRyON9aMGjZmeiqyBA4bW71Wf8Rs1
X5LSwZhJpCTjO4B92w40u0r86Jxmp5Wz+zHUeM3mO2E6lAF+15YjhxpMT0YOmHFE
+oKD8U6dMjkTqntavBauz8M8fQ==
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICmjCCAYICCQCOEQkjYe2QMTANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARy
b290MB4XDTIwMTExMDExMjgyOVoXDTMwMTExMDExMjgyOVowDzENMAsGA1UEAwwE
cm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOqS7H7+XeFNcf5m
qlH04t0ru56MCDYv9JV3byILgUnk1j+ld74m2q4T+Xxiw5ruMXh41W2xryMLF3+3
jql8b7isJFwCXud4/WLr4KzCEKgqvr6Nez9Hb9rIBbQsGWtDTjfe06F/D9Zioyt3
RnoT+5ItpX0+9IJn3TmAx7g1wU2dlXeaTp48RWPtJBqxp80Lq4SR3CdxI9+eVHv9
sRA3sI9ggqFWzDNJDiTLZoJU1Z+n/MnHTUBt7WRZcMToMsHbj2Gtd4LruB3J46qO
bjoL4im9oUrfXJZh87nW9KQ/+gOVv8t0zU70A/JMrazb/YnE6xO7+40JfrGNuFMm
ZyylUyECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAvCJMjXDNO/zUv7SBlr8hlHse
KprCDEp91DlXsewpTB3h6s1gyZzDCygPtz80qRD6zy+T4r1veaYQeLecsIyfYNV1
qnhNPpHnxjuXrrVwpEYOk/aP0yVlv0PiHsjyxzblLQPomX4m43Ec8/wW0Nlw0Aau
K0sD5+Mv/3NNQIneGFsLF4JPRkJwLjSbjPdKLpjWdLsTKQwVg0FIslzI9RmBIQIq
Nz2RWNHSqfGzsRpne9deqx9/9M4N8URUcmo0j7Ly7mYuxTkF7sft6sxbWDYQx1S1
4GjAEFWe4352O0sFl0PWr+o8rd245yAu5SEahRFvjvnSNg8VlYcnezBmsp2rbQ==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,7 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
COMPONENT_EMBED_TXTFILES := certs/cacert.pem
COMPONENT_EMBED_TXTFILES += certs/prvtkey.pem

View File

@@ -0,0 +1,274 @@
/* Local Ctrl Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdlib.h>
#include <stdint.h>
#include <sys/param.h>
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <mdns.h>
#include <esp_log.h>
#include <esp_timer.h>
#include <esp_local_ctrl.h>
#include <esp_https_server.h>
static const char *TAG = "control";
#define SERVICE_NAME "my_esp_ctrl_device"
/* Custom allowed property types */
enum property_types {
PROP_TYPE_TIMESTAMP = 0,
PROP_TYPE_INT32,
PROP_TYPE_BOOLEAN,
PROP_TYPE_STRING,
};
/* Custom flags that can be set for a property */
enum property_flags {
PROP_FLAG_READONLY = (1 << 0)
};
/********* Handler functions for responding to control requests / commands *********/
static esp_err_t get_property_values(size_t props_count,
const esp_local_ctrl_prop_t props[],
esp_local_ctrl_prop_val_t prop_values[],
void *usr_ctx)
{
for (uint32_t i = 0; i < props_count; i++) {
ESP_LOGI(TAG, "Reading property : %s", props[i].name);
/* For the purpose of this example, to keep things simple
* we have set the context pointer of each property to
* point to its value (except for timestamp) */
switch (props[i].type) {
case PROP_TYPE_INT32:
case PROP_TYPE_BOOLEAN:
/* No need to set size for these types as sizes where
* specified when declaring the properties, unlike for
* string type. */
prop_values[i].data = props[i].ctx;
break;
case PROP_TYPE_TIMESTAMP: {
/* Get the time stamp */
static int64_t ts = 0;
ts = esp_timer_get_time();
/* Set the current time. Since this is statically
* allocated, we don't need to provide a free_fn */
prop_values[i].data = &ts;
break;
}
case PROP_TYPE_STRING: {
char **prop3_value = (char **) props[i].ctx;
if (*prop3_value == NULL) {
prop_values[i].size = 0;
prop_values[i].data = NULL;
} else {
/* We could try dynamically allocating the output value,
* and it should get freed automatically after use, as
* `esp_local_ctrl` internally calls the provided `free_fn` */
prop_values[i].size = strlen(*prop3_value);
prop_values[i].data = strdup(*prop3_value);
if (!prop_values[i].data) {
return ESP_ERR_NO_MEM;
}
prop_values[i].free_fn = free;
}
}
default:
break;
}
}
return ESP_OK;
}
static esp_err_t set_property_values(size_t props_count,
const esp_local_ctrl_prop_t props[],
const esp_local_ctrl_prop_val_t prop_values[],
void *usr_ctx)
{
for (uint32_t i = 0; i < props_count; i++) {
/* Cannot set the value of a read-only property */
if (props[i].flags & PROP_FLAG_READONLY) {
ESP_LOGE(TAG, "%s is read-only", props[i].name);
return ESP_ERR_INVALID_ARG;
}
/* For the purpose of this example, to keep things simple
* we have set the context pointer of each property to
* point to its value (except for timestamp) */
switch (props[i].type) {
case PROP_TYPE_STRING: {
/* Free the previously set string */
char **prop3_value = (char **) props[i].ctx;
free(*prop3_value);
*prop3_value = NULL;
/* Copy the input string */
if (prop_values[i].size) {
*prop3_value = strndup((const char *)prop_values[i].data, prop_values[i].size);
if (*prop3_value == NULL) {
return ESP_ERR_NO_MEM;
}
ESP_LOGI(TAG, "Setting %s value to %s", props[i].name, (const char*)*prop3_value);
}
}
break;
case PROP_TYPE_INT32: {
const int32_t *new_value = (const int32_t *) prop_values[i].data;
ESP_LOGI(TAG, "Setting %s value to %d", props[i].name, *new_value);
memcpy(props[i].ctx, new_value, sizeof(int32_t));
}
break;
case PROP_TYPE_BOOLEAN: {
const bool *value = (const bool *) prop_values[i].data;
ESP_LOGI(TAG, "Setting %s value to %d", props[i].name, *value);
memcpy(props[i].ctx, value, sizeof(bool));
}
break;
default:
break;
}
}
return ESP_OK;
}
/******************************************************************************/
/* A custom free_fn to free a pointer to a string as
* well as the string being pointed to */
static void free_str(void *arg)
{
char **ptr_to_strptr = (char **)arg;
if (ptr_to_strptr) {
free(*ptr_to_strptr);
free(ptr_to_strptr);
}
}
/* Function used by app_main to start the esp_local_ctrl service */
void start_esp_local_ctrl_service(void)
{
/* Set the configuration */
httpd_ssl_config_t https_conf = HTTPD_SSL_CONFIG_DEFAULT();
/* Load server certificate */
extern const unsigned char cacert_pem_start[] asm("_binary_cacert_pem_start");
extern const unsigned char cacert_pem_end[] asm("_binary_cacert_pem_end");
https_conf.cacert_pem = cacert_pem_start;
https_conf.cacert_len = cacert_pem_end - cacert_pem_start;
/* Load server private key */
extern const unsigned char prvtkey_pem_start[] asm("_binary_prvtkey_pem_start");
extern const unsigned char prvtkey_pem_end[] asm("_binary_prvtkey_pem_end");
https_conf.prvtkey_pem = prvtkey_pem_start;
https_conf.prvtkey_len = prvtkey_pem_end - prvtkey_pem_start;
esp_local_ctrl_config_t config = {
.transport = ESP_LOCAL_CTRL_TRANSPORT_HTTPD,
.transport_config = {
.httpd = &https_conf
},
.handlers = {
/* User defined handler functions */
.get_prop_values = get_property_values,
.set_prop_values = set_property_values,
.usr_ctx = NULL,
.usr_ctx_free_fn = NULL
},
/* Maximum number of properties that may be set */
.max_properties = 10
};
mdns_init();
mdns_hostname_set(SERVICE_NAME);
/* Start esp_local_ctrl service */
ESP_ERROR_CHECK(esp_local_ctrl_start(&config));
ESP_LOGI(TAG, "esp_local_ctrl service started with name : %s", SERVICE_NAME);
/* Create a timestamp property. The client should see this as a read-only property.
* Property value is fetched using `esp_timer_get_time()` in the `get_prop_values`
* handler */
esp_local_ctrl_prop_t timestamp = {
.name = "timestamp (us)",
.type = PROP_TYPE_TIMESTAMP,
.size = sizeof(int64_t),
.flags = PROP_FLAG_READONLY,
.ctx = NULL,
.ctx_free_fn = NULL
};
/* Create a writable integer property. Use dynamically allocated memory
* for storing its value and pass it as context, so that it can be accessed
* inside the set / get handlers. */
int32_t *prop1_value = malloc(sizeof(int32_t));
assert(prop1_value != NULL);
/* Initialize the property value */
*prop1_value = 123456789;
/* Populate the property structure accordingly. Since, we would want the memory
* occupied by the property value to be freed automatically upon call to
* `esp_local_ctrl_stop()` or `esp_local_ctrl_remove_property()`, the `ctx_free_fn`
* field will need to be set with the appropriate de-allocation function,
* which in this case is simply `free()` */
esp_local_ctrl_prop_t property1 = {
.name = "property1",
.type = PROP_TYPE_INT32,
.size = sizeof(int32_t),
.flags = 0,
.ctx = prop1_value,
.ctx_free_fn = free
};
/* Create another read-only property. Just for demonstration, we use statically
* allocated value. No `ctx_free_fn` needs to be set for this */
static bool prop2_value = false;
esp_local_ctrl_prop_t property2 = {
.name = "property2",
.type = PROP_TYPE_BOOLEAN,
.size = sizeof(bool),
.flags = PROP_FLAG_READONLY,
.ctx = &prop2_value,
.ctx_free_fn = NULL
};
/* Create a variable sized property. Its context is a pointer for storing the
* pointer to a dynamically allocate string, therefore it will require a
* customized free function `free_str()` */
char **prop3_value = calloc(1, sizeof(char *));
assert(prop3_value != NULL);
esp_local_ctrl_prop_t property3 = {
.name = "property3",
.type = PROP_TYPE_STRING,
.size = 0, // When zero, this is assumed to be of variable size
.flags = 0,
.ctx = prop3_value,
.ctx_free_fn = free_str
};
/* Now register the properties */
ESP_ERROR_CHECK(esp_local_ctrl_add_property(&timestamp));
ESP_ERROR_CHECK(esp_local_ctrl_add_property(&property1));
ESP_ERROR_CHECK(esp_local_ctrl_add_property(&property2));
ESP_ERROR_CHECK(esp_local_ctrl_add_property(&property3));
/* Just for fun, let us keep toggling the value
* of the boolean property2, every 1 second */
while (1) {
vTaskDelay(1000 / portTICK_RATE_MS);
prop2_value = !prop2_value;
}
}

View File

@@ -0,0 +1,278 @@
#!/usr/bin/env python
#
# Copyright 2018 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
from builtins import input
import os
import sys
import struct
import argparse
import ssl
import proto
# The tools directory is already in the PATH in environment prepared by install.sh which would allow to import
# esp_prov as file but not as complete module.
sys.path.insert(0, os.path.join(os.environ['IDF_PATH'], 'tools/esp_prov'))
import esp_prov # noqa: E402
# Set this to true to allow exceptions to be thrown
config_throw_except = False
# Property types enum
PROP_TYPE_TIMESTAMP = 0
PROP_TYPE_INT32 = 1
PROP_TYPE_BOOLEAN = 2
PROP_TYPE_STRING = 3
# Property flags enum
PROP_FLAG_READONLY = (1 << 0)
def prop_typestr(prop):
if prop["type"] == PROP_TYPE_TIMESTAMP:
return "TIME(us)"
elif prop["type"] == PROP_TYPE_INT32:
return "INT32"
elif prop["type"] == PROP_TYPE_BOOLEAN:
return "BOOLEAN"
elif prop["type"] == PROP_TYPE_STRING:
return "STRING"
return "UNKNOWN"
def encode_prop_value(prop, value):
try:
if prop["type"] == PROP_TYPE_TIMESTAMP:
return struct.pack('q', value)
elif prop["type"] == PROP_TYPE_INT32:
return struct.pack('i', value)
elif prop["type"] == PROP_TYPE_BOOLEAN:
return struct.pack('?', value)
elif prop["type"] == PROP_TYPE_STRING:
return tobytes(value)
return value
except struct.error as e:
print(e)
return None
def decode_prop_value(prop, value):
try:
if prop["type"] == PROP_TYPE_TIMESTAMP:
return struct.unpack('q', value)[0]
elif prop["type"] == PROP_TYPE_INT32:
return struct.unpack('i', value)[0]
elif prop["type"] == PROP_TYPE_BOOLEAN:
return struct.unpack('?', value)[0]
elif prop["type"] == PROP_TYPE_STRING:
return value.decode('latin-1')
return value
except struct.error as e:
print(e)
return None
def str_to_prop_value(prop, strval):
try:
if prop["type"] == PROP_TYPE_TIMESTAMP:
return int(strval)
elif prop["type"] == PROP_TYPE_INT32:
return int(strval)
elif prop["type"] == PROP_TYPE_BOOLEAN:
return bool(strval)
elif prop["type"] == PROP_TYPE_STRING:
return strval
return strval
except ValueError as e:
print(e)
return None
def prop_is_readonly(prop):
return (prop["flags"] & PROP_FLAG_READONLY) != 0
def on_except(err):
if config_throw_except:
raise RuntimeError(err)
else:
print(err)
def get_transport(sel_transport, service_name, check_hostname):
try:
tp = None
if (sel_transport == 'http'):
example_path = os.environ['IDF_PATH'] + "/examples/protocols/esp_local_ctrl"
cert_path = example_path + "/main/certs/rootCA.pem"
ssl_ctx = ssl.create_default_context(cafile=cert_path)
ssl_ctx.check_hostname = check_hostname
tp = esp_prov.transport.Transport_HTTP(service_name, ssl_ctx)
elif (sel_transport == 'ble'):
tp = esp_prov.transport.Transport_BLE(
devname=service_name, service_uuid='0000ffff-0000-1000-8000-00805f9b34fb',
nu_lookup={'esp_local_ctrl/version': '0001',
'esp_local_ctrl/session': '0002',
'esp_local_ctrl/control': '0003'}
)
return tp
except RuntimeError as e:
on_except(e)
return None
def version_match(tp, expected, verbose=False):
try:
response = tp.send_data('esp_local_ctrl/version', expected)
return (response.lower() == expected.lower())
except Exception as e:
on_except(e)
return None
def get_all_property_values(tp):
try:
props = []
message = proto.get_prop_count_request()
response = tp.send_data('esp_local_ctrl/control', message)
count = proto.get_prop_count_response(response)
if count == 0:
raise RuntimeError("No properties found!")
indices = [i for i in range(count)]
message = proto.get_prop_vals_request(indices)
response = tp.send_data('esp_local_ctrl/control', message)
props = proto.get_prop_vals_response(response)
if len(props) != count:
raise RuntimeError("Incorrect count of properties!")
for p in props:
p["value"] = decode_prop_value(p, p["value"])
return props
except RuntimeError as e:
on_except(e)
return []
def set_property_values(tp, props, indices, values, check_readonly=False):
try:
if check_readonly:
for index in indices:
if prop_is_readonly(props[index]):
raise RuntimeError("Cannot set value of Read-Only property")
message = proto.set_prop_vals_request(indices, values)
response = tp.send_data('esp_local_ctrl/control', message)
return proto.set_prop_vals_response(response)
except RuntimeError as e:
on_except(e)
return False
if __name__ == '__main__':
parser = argparse.ArgumentParser(add_help=False)
parser = argparse.ArgumentParser(description="Control an ESP32 running esp_local_ctrl service")
parser.add_argument("--version", dest='version', type=str,
help="Protocol version", default='')
parser.add_argument("--transport", dest='transport', type=str,
help="transport i.e http or ble", default='http')
parser.add_argument("--name", dest='service_name', type=str,
help="BLE Device Name / HTTP Server hostname or IP", default='')
parser.add_argument("--dont-check-hostname", action="store_true",
# If enabled, the certificate won't be rejected for hostname mismatch.
# This option is hidden because it should be used only for testing purposes.
help=argparse.SUPPRESS)
parser.add_argument("-v", "--verbose", dest='verbose', help="increase output verbosity", action="store_true")
args = parser.parse_args()
if args.version != '':
print("==== Esp_Ctrl Version: " + args.version + " ====")
if args.service_name == '':
args.service_name = 'my_esp_ctrl_device'
if args.transport == 'http':
args.service_name += '.local'
obj_transport = get_transport(args.transport, args.service_name, not args.dont_check_hostname)
if obj_transport is None:
print("---- Invalid transport ----")
exit(1)
if args.version != '':
print("\n==== Verifying protocol version ====")
if not version_match(obj_transport, args.version, args.verbose):
print("---- Error in protocol version matching ----")
exit(2)
print("==== Verified protocol version successfully ====")
while True:
properties = get_all_property_values(obj_transport)
if len(properties) == 0:
print("---- Error in reading property values ----")
exit(4)
print("\n==== Available Properties ====")
print("{0: >4} {1: <16} {2: <10} {3: <16} {4: <16}".format(
"S.N.", "Name", "Type", "Flags", "Value"))
for i in range(len(properties)):
print("[{0: >2}] {1: <16} {2: <10} {3: <16} {4: <16}".format(
i + 1, properties[i]["name"], prop_typestr(properties[i]),
["","Read-Only"][prop_is_readonly(properties[i])],
str(properties[i]["value"])))
select = 0
while True:
try:
inval = input("\nSelect properties to set (0 to re-read, 'q' to quit) : ")
if inval.lower() == 'q':
print("Quitting...")
exit(5)
invals = inval.split(',')
selections = [int(val) for val in invals]
if min(selections) < 0 or max(selections) > len(properties):
raise ValueError("Invalid input")
break
except ValueError as e:
print(str(e) + "! Retry...")
if len(selections) == 1 and selections[0] == 0:
continue
set_values = []
set_indices = []
for select in selections:
while True:
inval = input("Enter value to set for property (" + properties[select - 1]["name"] + ") : ")
value = encode_prop_value(properties[select - 1],
str_to_prop_value(properties[select - 1], inval))
if value is None:
print("Invalid input! Retry...")
continue
break
set_values += [value]
set_indices += [select - 1]
if not set_property_values(obj_transport, properties, set_indices, set_values):
print("Failed to set values!")

View File

@@ -0,0 +1,93 @@
# Copyright 2018 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 os
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)
idf_path = os.environ['IDF_PATH']
constants_pb2 = _load_source("constants_pb2", idf_path + "/components/protocomm/python/constants_pb2.py")
local_ctrl_pb2 = _load_source("esp_local_ctrl_pb2", idf_path + "/components/esp_local_ctrl/python/esp_local_ctrl_pb2.py")
def get_prop_count_request():
req = local_ctrl_pb2.LocalCtrlMessage()
req.msg = local_ctrl_pb2.TypeCmdGetPropertyCount
payload = local_ctrl_pb2.CmdGetPropertyCount()
req.cmd_get_prop_count.MergeFrom(payload)
return req.SerializeToString()
def get_prop_count_response(response_data):
resp = local_ctrl_pb2.LocalCtrlMessage()
resp.ParseFromString(tobytes(response_data))
if (resp.resp_get_prop_count.status == 0):
return resp.resp_get_prop_count.count
else:
return 0
def get_prop_vals_request(indices):
req = local_ctrl_pb2.LocalCtrlMessage()
req.msg = local_ctrl_pb2.TypeCmdGetPropertyValues
payload = local_ctrl_pb2.CmdGetPropertyValues()
payload.indices.extend(indices)
req.cmd_get_prop_vals.MergeFrom(payload)
return req.SerializeToString()
def get_prop_vals_response(response_data):
resp = local_ctrl_pb2.LocalCtrlMessage()
resp.ParseFromString(tobytes(response_data))
results = []
if (resp.resp_get_prop_vals.status == 0):
for prop in resp.resp_get_prop_vals.props:
results += [{
"name": prop.name,
"type": prop.type,
"flags": prop.flags,
"value": tobytes(prop.value)
}]
return results
def set_prop_vals_request(indices, values):
req = local_ctrl_pb2.LocalCtrlMessage()
req.msg = local_ctrl_pb2.TypeCmdSetPropertyValues
payload = local_ctrl_pb2.CmdSetPropertyValues()
for i, v in zip(indices, values):
prop = payload.props.add()
prop.index = i
prop.value = v
req.cmd_set_prop_vals.MergeFrom(payload)
return req.SerializeToString()
def set_prop_vals_response(response_data):
resp = local_ctrl_pb2.LocalCtrlMessage()
resp.ParseFromString(tobytes(response_data))
return (resp.resp_set_prop_vals.status == 0)

View File

@@ -0,0 +1 @@
CONFIG_ESP_HTTPS_SERVER_ENABLE=y