mirror of
https://gitee.com/beecue/fastbee.git
synced 2025-12-19 17:35:54 +08:00
添加智能灯固件代码
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
# Protocols Examples
|
||||
|
||||
Implementation of internet communication protocols and services.
|
||||
|
||||
See the [README.md](../README.md) file in the upper level [examples](../) directory for more information about examples.
|
||||
|
||||
## Establishing Wi-Fi or Ethernet Connection
|
||||
|
||||
### About the `example_connect()` Function
|
||||
|
||||
Protocols examples use a simple helper function, `example_connect()`, to establish Wi-Fi and/or Ethernet connection. This function is implemented in [examples/common_components/protocol_examples/common/connect.c](../common_components/protocol_examples_common/connect.c), and has a very simple behavior: block until connection is established and IP address is obtained, then return. This function is used to reduce the amount of boilerplate and to keep the example code focused on the protocol or library being demonstrated.
|
||||
|
||||
The simple `example_connect()` function does not handle timeouts, does not gracefully handle various error conditions, and is only suited for use in examples. When developing real applications, this helper function needs to be replaced with full Wi-Fi / Ethernet connection handling code. Such code can be found in [examples/wifi/getting_started/](../wifi/getting_started) and [examples/ethernet/basic/](../ethernet/basic) examples.
|
||||
|
||||
### Configuring the Example
|
||||
|
||||
To configure the example to use Wi-Fi, Ethernet or both connections, open the project configuration menu (`idf.py menuconfig`) and navigate to "Example Connection Configuration" menu. Select either "Wi-Fi" or "Ethernet" or both in the "Connect using" choice.
|
||||
|
||||
When connecting using Wi-Fi, enter SSID and password of your Wi-Fi access point into the corresponding fields. If connecting to an open Wi-Fi network, keep the password field empty.
|
||||
|
||||
When connecting using Ethernet, set up PHY type and configuration in the provided fields. If using Ethernet for the first time, it is recommended to start with the [Ethernet example readme](../ethernet/basic/README.md), which contains instructions for connecting and configuring the PHY. Once Ethernet example obtains IP address successfully, proceed to the protocols example and set the same configuration options.
|
||||
|
||||
### Disabling IPv6
|
||||
|
||||
By default, `example_connect()` function waits until Wi-Fi or Ethernet connection is established, and IPv4 address and IPv6 link-local address are obtained. In network environments where IPv6 link-local address cannot be obtained, disable "Obtain IPv6 link-local address" option found in "Example Connection Configuration" menu.
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(asio_chat_client)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
PROJECT_NAME := asio_chat_client
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,20 @@
|
||||
# Asio chat client example
|
||||
|
||||
Simple Asio chat client using WiFi STA or Ethernet.
|
||||
|
||||
## Example workflow
|
||||
|
||||
- Wi-Fi or Ethernet connection is established, and IP address is obtained.
|
||||
- Asio chat client connects to the corresponding server whose port number and IP are defined through the project configuration menu.
|
||||
- Chat client receives all messages from other chat clients, also it sends message received from stdin using `idf.py -p PORT monitor`.
|
||||
|
||||
## Running the example
|
||||
|
||||
- Open the project configuration menu (`idf.py menuconfig`) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
|
||||
- Set server IP address and port number in menuconfig, "Example configuration".
|
||||
- Start chat server either on host machine or as another ESP device running chat_server example.
|
||||
- Run `idf.py -p PORT flash monitor` to build and upload the example to your board and connect to it's serial terminal.
|
||||
- Wait for the board to connect to WiFi or Ethernet.
|
||||
- Receive and send messages to/from other clients on stdin/stdout via serial terminal.
|
||||
|
||||
See the README.md file in the upper level 'examples' directory for more information about examples.
|
||||
@@ -0,0 +1,95 @@
|
||||
import re
|
||||
import os
|
||||
import socket
|
||||
from threading import Thread
|
||||
import time
|
||||
|
||||
import ttfw_idf
|
||||
|
||||
global g_client_response
|
||||
global g_msg_to_client
|
||||
|
||||
g_client_response = b""
|
||||
g_msg_to_client = b" 3XYZ"
|
||||
|
||||
|
||||
def get_my_ip():
|
||||
s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s1.connect(("8.8.8.8", 80))
|
||||
my_ip = s1.getsockname()[0]
|
||||
s1.close()
|
||||
return my_ip
|
||||
|
||||
|
||||
def chat_server_sketch(my_ip):
|
||||
global g_client_response
|
||||
print("Starting the server on {}".format(my_ip))
|
||||
port = 2222
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(600)
|
||||
s.bind((my_ip, port))
|
||||
s.listen(1)
|
||||
q,addr = s.accept()
|
||||
print("connection accepted")
|
||||
q.settimeout(30)
|
||||
q.send(g_msg_to_client)
|
||||
data = q.recv(1024)
|
||||
# check if received initial empty message
|
||||
if (len(data) > 4):
|
||||
g_client_response = data
|
||||
else:
|
||||
g_client_response = q.recv(1024)
|
||||
print("received from client {}".format(g_client_response))
|
||||
s.close()
|
||||
print("server closed")
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_asio_chat_client(env, extra_data):
|
||||
"""
|
||||
steps: |
|
||||
1. Test to start simple tcp server
|
||||
2. `dut1` joins AP
|
||||
3. Test injects server IP to `dut1`via stdin
|
||||
4. Test evaluates `dut1` receives a message server placed
|
||||
5. Test injects a message to `dut1` to be sent as chat_client message
|
||||
6. Test evaluates received test message in host server
|
||||
"""
|
||||
global g_client_response
|
||||
global g_msg_to_client
|
||||
test_msg = "ABC"
|
||||
dut1 = env.get_dut("chat_client", "examples/protocols/asio/chat_client", dut_class=ttfw_idf.ESP32DUT)
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, "asio_chat_client.bin")
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("asio_chat_client_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("asio_chat_client_size", bin_size // 1024, dut1.TARGET)
|
||||
# 1. start a tcp server on the host
|
||||
host_ip = get_my_ip()
|
||||
thread1 = Thread(target=chat_server_sketch, args=(host_ip,))
|
||||
thread1.start()
|
||||
# 2. start the dut test and wait till client gets IP address
|
||||
dut1.start_app()
|
||||
dut1.expect(re.compile(r" IPv4 address: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)"), timeout=30)
|
||||
# 3. send host's IP to the client i.e. the `dut1`
|
||||
dut1.write(host_ip)
|
||||
# 4. client `dut1` should receive a message
|
||||
dut1.expect(g_msg_to_client[4:].decode()) # Strip out the front 4 bytes of message len (see chat_message protocol)
|
||||
# 5. write test message from `dut1` chat_client to the server
|
||||
dut1.write(test_msg)
|
||||
while len(g_client_response) == 0:
|
||||
time.sleep(1)
|
||||
g_client_response = g_client_response.decode()
|
||||
print(g_client_response)
|
||||
# 6. evaluate host_server received this message
|
||||
if (g_client_response[4:7] == test_msg):
|
||||
print("PASS: Received correct message")
|
||||
pass
|
||||
else:
|
||||
print("Failure!")
|
||||
raise ValueError('Wrong data received from asi tcp server: {} (expected:{})'.format(g_client_response[4:7], test_msg))
|
||||
thread1.join()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_protocol_asio_chat_client()
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "chat_client.cpp"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,16 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_PORT
|
||||
string "Asio example server port number"
|
||||
default "2222"
|
||||
help
|
||||
Port number used by Asio example.
|
||||
|
||||
config EXAMPLE_SERVER_IP
|
||||
string "Asio example server ip"
|
||||
default "FROM_STDIN"
|
||||
help
|
||||
Asio example server ip for this client to connect to.
|
||||
Leave default "FROM_STDIN" to enter the server address via serial terminal.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,183 @@
|
||||
//
|
||||
// chat_client.cpp
|
||||
// ~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include <cstdlib>
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include "asio.hpp"
|
||||
#include "chat_message.hpp"
|
||||
#include "protocol_examples_common.h"
|
||||
#include "esp_event.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
using asio::ip::tcp;
|
||||
|
||||
typedef std::deque<chat_message> chat_message_queue;
|
||||
|
||||
class chat_client
|
||||
{
|
||||
public:
|
||||
chat_client(asio::io_context& io_context,
|
||||
const tcp::resolver::results_type& endpoints)
|
||||
: io_context_(io_context),
|
||||
socket_(io_context)
|
||||
{
|
||||
do_connect(endpoints);
|
||||
}
|
||||
|
||||
void write(const chat_message& msg)
|
||||
{
|
||||
asio::post(io_context_,
|
||||
[this, msg]()
|
||||
{
|
||||
bool write_in_progress = !write_msgs_.empty();
|
||||
write_msgs_.push_back(msg);
|
||||
if (!write_in_progress)
|
||||
{
|
||||
do_write();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
asio::post(io_context_, [this]() { socket_.close(); });
|
||||
}
|
||||
|
||||
private:
|
||||
void do_connect(const tcp::resolver::results_type& endpoints)
|
||||
{
|
||||
asio::async_connect(socket_, endpoints,
|
||||
[this](std::error_code ec, tcp::endpoint)
|
||||
{
|
||||
if (!ec)
|
||||
{
|
||||
do_read_header();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void do_read_header()
|
||||
{
|
||||
asio::async_read(socket_,
|
||||
asio::buffer(read_msg_.data(), chat_message::header_length),
|
||||
[this](std::error_code ec, std::size_t /*length*/)
|
||||
{
|
||||
if (!ec && read_msg_.decode_header())
|
||||
{
|
||||
do_read_body();
|
||||
}
|
||||
else
|
||||
{
|
||||
socket_.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void do_read_body()
|
||||
{
|
||||
asio::async_read(socket_,
|
||||
asio::buffer(read_msg_.body(), read_msg_.body_length()),
|
||||
[this](std::error_code ec, std::size_t /*length*/)
|
||||
{
|
||||
if (!ec)
|
||||
{
|
||||
std::cout.write(read_msg_.body(), read_msg_.body_length());
|
||||
std::cout << "\n";
|
||||
do_read_header();
|
||||
}
|
||||
else
|
||||
{
|
||||
socket_.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void do_write()
|
||||
{
|
||||
asio::async_write(socket_,
|
||||
asio::buffer(write_msgs_.front().data(),
|
||||
write_msgs_.front().length()),
|
||||
[this](std::error_code ec, std::size_t /*length*/)
|
||||
{
|
||||
if (!ec)
|
||||
{
|
||||
write_msgs_.pop_front();
|
||||
if (!write_msgs_.empty())
|
||||
{
|
||||
do_write();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
socket_.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
asio::io_context& io_context_;
|
||||
tcp::socket socket_;
|
||||
chat_message read_msg_;
|
||||
chat_message_queue write_msgs_;
|
||||
};
|
||||
|
||||
void read_line(char * line, int max_chars);
|
||||
|
||||
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
esp_netif_init();
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
/* This helper function configures blocking UART I/O */
|
||||
ESP_ERROR_CHECK(example_configure_stdin_stdout());
|
||||
|
||||
std::string name(CONFIG_EXAMPLE_SERVER_IP);
|
||||
std::string port(CONFIG_EXAMPLE_PORT);
|
||||
char line[chat_message::max_body_length + 1] = { 0 };
|
||||
|
||||
if (name == "FROM_STDIN") {
|
||||
std::cout << "Please enter ip address of chat server" << std::endl;
|
||||
if (std::cin.getline(line, chat_message::max_body_length + 1)) {
|
||||
name = line;
|
||||
std::cout << "Chat server IP:" << name << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
asio::io_context io_context;
|
||||
tcp::resolver resolver(io_context);
|
||||
auto endpoints = resolver.resolve(name, port);
|
||||
|
||||
chat_client c(io_context, endpoints);
|
||||
|
||||
std::thread t([&io_context](){ io_context.run(); });
|
||||
|
||||
while (std::cin.getline(line, chat_message::max_body_length + 1) && std::string(line) != "exit") {
|
||||
chat_message msg;
|
||||
msg.body_length(std::strlen(line));
|
||||
std::memcpy(msg.body(), line, msg.body_length());
|
||||
msg.encode_header();
|
||||
c.write(msg);
|
||||
}
|
||||
|
||||
c.close();
|
||||
t.join();
|
||||
|
||||
ESP_ERROR_CHECK(example_disconnect());
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// chat_message.hpp
|
||||
// ~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef CHAT_MESSAGE_HPP
|
||||
#define CHAT_MESSAGE_HPP
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
class chat_message
|
||||
{
|
||||
public:
|
||||
enum { header_length = 4 };
|
||||
enum { max_body_length = 512 };
|
||||
|
||||
chat_message()
|
||||
: body_length_(0)
|
||||
{
|
||||
}
|
||||
|
||||
const char* data() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
char* data()
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
std::size_t length() const
|
||||
{
|
||||
return header_length + body_length_;
|
||||
}
|
||||
|
||||
const char* body() const
|
||||
{
|
||||
return data_ + header_length;
|
||||
}
|
||||
|
||||
char* body()
|
||||
{
|
||||
return data_ + header_length;
|
||||
}
|
||||
|
||||
std::size_t body_length() const
|
||||
{
|
||||
return body_length_;
|
||||
}
|
||||
|
||||
void body_length(std::size_t new_length)
|
||||
{
|
||||
body_length_ = new_length;
|
||||
if (body_length_ > max_body_length)
|
||||
body_length_ = max_body_length;
|
||||
}
|
||||
|
||||
bool decode_header()
|
||||
{
|
||||
char header[header_length + 1] = "";
|
||||
std::strncat(header, data_, header_length);
|
||||
body_length_ = std::atoi(header);
|
||||
if (body_length_ > max_body_length)
|
||||
{
|
||||
body_length_ = 0;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void encode_header()
|
||||
{
|
||||
char header[header_length + 1] = "";
|
||||
std::sprintf(header, "%4d", static_cast<int>(body_length_));
|
||||
std::memcpy(data_, header, header_length);
|
||||
}
|
||||
|
||||
private:
|
||||
char data_[header_length + max_body_length];
|
||||
std::size_t body_length_;
|
||||
};
|
||||
|
||||
#endif // CHAT_MESSAGE_HPP
|
||||
@@ -0,0 +1,8 @@
|
||||
#
|
||||
# Main component makefile.
|
||||
#
|
||||
# This Makefile can be left empty. By default, it will take the sources in the
|
||||
# src/ directory, compile them and link them into lib(subdirectory_name).a
|
||||
# in the build directory. This behaviour is entirely configurable,
|
||||
# please read the ESP-IDF documents if you need to do this.
|
||||
#
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(asio_chat_server)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
PROJECT_NAME := asio_chat_server
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,23 @@
|
||||
# Asio chat server example
|
||||
|
||||
Simple Asio chat server using WiFi STA or Ethernet.
|
||||
|
||||
## Example workflow
|
||||
|
||||
- Wi-Fi or Ethernet connection is established, and IP address is obtained.
|
||||
- Asio chat server is started on port number defined through the project configuration.
|
||||
- Chat server echoes a message (received from any client) to all connected clients.
|
||||
|
||||
## Running the example
|
||||
|
||||
- Open project configuration menu (`idf.py menuconfig`) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
|
||||
- Set server port number in menuconfig, "Example configuration".
|
||||
- Run `idf.py -p PORT flash monitor` to build and upload the example to your board and connect to it's serial terminal.
|
||||
- Wait for the board to connect to WiFi or Ethernet (note the IP address).
|
||||
- Connect to the server using multiple clients, for example using any option below.
|
||||
- build and run asi chat client on your host machine
|
||||
- run chat_client asio example on ESP platform
|
||||
- since chat message consist of ascii size and message, it is possible to
|
||||
netcat `nc IP PORT` and type for example ` 4ABC<CR>` to transmit 'ABC\n'
|
||||
|
||||
See the README.md file in the upper level 'examples' directory for more information about examples.
|
||||
@@ -0,0 +1,44 @@
|
||||
import re
|
||||
import os
|
||||
import socket
|
||||
|
||||
import ttfw_idf
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_asio_chat_server(env, extra_data):
|
||||
"""
|
||||
steps: |
|
||||
1. join AP
|
||||
2. Start server
|
||||
3. Test connects to server and sends a test message
|
||||
4. Test evaluates received test message from server
|
||||
"""
|
||||
test_msg = b" 4ABC\n"
|
||||
dut1 = env.get_dut("chat_server", "examples/protocols/asio/chat_server", dut_class=ttfw_idf.ESP32DUT)
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, "asio_chat_server.bin")
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("asio_chat_server_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("asio_chat_server_size", bin_size // 1024, dut1.TARGET)
|
||||
# 1. start test
|
||||
dut1.start_app()
|
||||
# 2. get the server IP address
|
||||
data = dut1.expect(re.compile(r" IPv4 address: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)"), timeout=30)
|
||||
# 3. create tcp client and connect to server
|
||||
cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
cli.settimeout(30)
|
||||
cli.connect((data[0], 2222))
|
||||
cli.send(test_msg)
|
||||
data = cli.recv(1024)
|
||||
# 4. check the message received back from the server
|
||||
if (data == test_msg):
|
||||
print("PASS: Received correct message {}".format(data))
|
||||
pass
|
||||
else:
|
||||
print("Failure!")
|
||||
raise ValueError('Wrong data received from asi tcp server: {} (expoected:{})'.format(data, test_msg))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_protocol_asio_chat_server()
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "chat_server.cpp"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,9 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_PORT
|
||||
string "Asio example server port number"
|
||||
default "2222"
|
||||
help
|
||||
Port number used by Asio example
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// chat_message.hpp
|
||||
// ~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef CHAT_MESSAGE_HPP
|
||||
#define CHAT_MESSAGE_HPP
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
class chat_message
|
||||
{
|
||||
public:
|
||||
enum { header_length = 4 };
|
||||
enum { max_body_length = 512 };
|
||||
|
||||
chat_message()
|
||||
: body_length_(0)
|
||||
{
|
||||
}
|
||||
|
||||
const char* data() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
char* data()
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
std::size_t length() const
|
||||
{
|
||||
return header_length + body_length_;
|
||||
}
|
||||
|
||||
const char* body() const
|
||||
{
|
||||
return data_ + header_length;
|
||||
}
|
||||
|
||||
char* body()
|
||||
{
|
||||
return data_ + header_length;
|
||||
}
|
||||
|
||||
std::size_t body_length() const
|
||||
{
|
||||
return body_length_;
|
||||
}
|
||||
|
||||
void body_length(std::size_t new_length)
|
||||
{
|
||||
body_length_ = new_length;
|
||||
if (body_length_ > max_body_length)
|
||||
body_length_ = max_body_length;
|
||||
}
|
||||
|
||||
bool decode_header()
|
||||
{
|
||||
char header[header_length + 1] = "";
|
||||
std::strncat(header, data_, header_length);
|
||||
body_length_ = std::atoi(header);
|
||||
if (body_length_ > max_body_length)
|
||||
{
|
||||
body_length_ = 0;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void encode_header()
|
||||
{
|
||||
char header[header_length + 1] = "";
|
||||
std::sprintf(header, "%4d", static_cast<int>(body_length_));
|
||||
std::memcpy(data_, header, header_length);
|
||||
}
|
||||
|
||||
private:
|
||||
char data_[header_length + max_body_length];
|
||||
std::size_t body_length_;
|
||||
};
|
||||
|
||||
#endif // CHAT_MESSAGE_HPP
|
||||
@@ -0,0 +1,229 @@
|
||||
//
|
||||
// chat_server.cpp
|
||||
// ~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include <cstdlib>
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include "asio.hpp"
|
||||
#include "chat_message.hpp"
|
||||
#include "protocol_examples_common.h"
|
||||
#include "esp_event.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
|
||||
using asio::ip::tcp;
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
typedef std::deque<chat_message> chat_message_queue;
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
class chat_participant
|
||||
{
|
||||
public:
|
||||
virtual ~chat_participant() {}
|
||||
virtual void deliver(const chat_message& msg) = 0;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<chat_participant> chat_participant_ptr;
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
class chat_room
|
||||
{
|
||||
public:
|
||||
void join(chat_participant_ptr participant)
|
||||
{
|
||||
participants_.insert(participant);
|
||||
for (auto msg: recent_msgs_)
|
||||
participant->deliver(msg);
|
||||
}
|
||||
|
||||
void leave(chat_participant_ptr participant)
|
||||
{
|
||||
participants_.erase(participant);
|
||||
}
|
||||
|
||||
void deliver(const chat_message& msg)
|
||||
{
|
||||
recent_msgs_.push_back(msg);
|
||||
while (recent_msgs_.size() > max_recent_msgs)
|
||||
recent_msgs_.pop_front();
|
||||
|
||||
for (auto participant: participants_)
|
||||
participant->deliver(msg);
|
||||
}
|
||||
|
||||
private:
|
||||
std::set<chat_participant_ptr> participants_;
|
||||
enum { max_recent_msgs = 100 };
|
||||
chat_message_queue recent_msgs_;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
class chat_session
|
||||
: public chat_participant,
|
||||
public std::enable_shared_from_this<chat_session>
|
||||
{
|
||||
public:
|
||||
chat_session(tcp::socket socket, chat_room& room)
|
||||
: socket_(std::move(socket)),
|
||||
room_(room)
|
||||
{
|
||||
}
|
||||
|
||||
void start()
|
||||
{
|
||||
room_.join(shared_from_this());
|
||||
do_read_header();
|
||||
}
|
||||
|
||||
void deliver(const chat_message& msg)
|
||||
{
|
||||
bool write_in_progress = !write_msgs_.empty();
|
||||
write_msgs_.push_back(msg);
|
||||
if (!write_in_progress)
|
||||
{
|
||||
do_write();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void do_read_header()
|
||||
{
|
||||
auto self(shared_from_this());
|
||||
asio::async_read(socket_,
|
||||
asio::buffer(read_msg_.data(), chat_message::header_length),
|
||||
[this, self](std::error_code ec, std::size_t /*length*/)
|
||||
{
|
||||
if (!ec && read_msg_.decode_header())
|
||||
{
|
||||
do_read_body();
|
||||
}
|
||||
else
|
||||
{
|
||||
room_.leave(shared_from_this());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void do_read_body()
|
||||
{
|
||||
auto self(shared_from_this());
|
||||
asio::async_read(socket_,
|
||||
asio::buffer(read_msg_.body(), read_msg_.body_length()),
|
||||
[this, self](std::error_code ec, std::size_t /*length*/)
|
||||
{
|
||||
if (!ec)
|
||||
{
|
||||
room_.deliver(read_msg_);
|
||||
do_read_header();
|
||||
}
|
||||
else
|
||||
{
|
||||
room_.leave(shared_from_this());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void do_write()
|
||||
{
|
||||
auto self(shared_from_this());
|
||||
asio::async_write(socket_,
|
||||
asio::buffer(write_msgs_.front().data(),
|
||||
write_msgs_.front().length()),
|
||||
[this, self](std::error_code ec, std::size_t /*length*/)
|
||||
{
|
||||
if (!ec)
|
||||
{
|
||||
write_msgs_.pop_front();
|
||||
if (!write_msgs_.empty())
|
||||
{
|
||||
do_write();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
room_.leave(shared_from_this());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tcp::socket socket_;
|
||||
chat_room& room_;
|
||||
chat_message read_msg_;
|
||||
chat_message_queue write_msgs_;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
class chat_server
|
||||
{
|
||||
public:
|
||||
chat_server(asio::io_context& io_context,
|
||||
const tcp::endpoint& endpoint)
|
||||
: acceptor_(io_context, endpoint)
|
||||
{
|
||||
do_accept();
|
||||
}
|
||||
|
||||
private:
|
||||
void do_accept()
|
||||
{
|
||||
acceptor_.async_accept(
|
||||
[this](std::error_code ec, tcp::socket socket)
|
||||
{
|
||||
if (!ec)
|
||||
{
|
||||
std::make_shared<chat_session>(std::move(socket), room_)->start();
|
||||
}
|
||||
|
||||
do_accept();
|
||||
});
|
||||
}
|
||||
|
||||
tcp::acceptor acceptor_;
|
||||
chat_room room_;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
esp_netif_init();
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
/* This helper function configures blocking UART I/O */
|
||||
ESP_ERROR_CHECK(example_configure_stdin_stdout());
|
||||
|
||||
asio::io_context io_context;
|
||||
|
||||
std::list<chat_server> servers;
|
||||
|
||||
{
|
||||
tcp::endpoint endpoint(tcp::v4(), std::atoi(CONFIG_EXAMPLE_PORT));
|
||||
servers.emplace_back(io_context, endpoint);
|
||||
}
|
||||
|
||||
io_context.run();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#
|
||||
# Main component makefile.
|
||||
#
|
||||
# This Makefile can be left empty. By default, it will take the sources in the
|
||||
# src/ directory, compile them and link them into lib(subdirectory_name).a
|
||||
# in the build directory. This behaviour is entirely configurable,
|
||||
# please read the ESP-IDF documents if you need to do this.
|
||||
#
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(asio_tcp_echo_server)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
PROJECT_NAME := asio_tcp_echo_server
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,19 @@
|
||||
# Asio TCP echo server example
|
||||
|
||||
Simple Asio TCP echo server using WiFi STA or Ethernet.
|
||||
|
||||
## Example workflow
|
||||
|
||||
- Wi-Fi or Ethernet connection is established, and IP address is obtained.
|
||||
- Asio TCP server is started on port number defined through the project configuration.
|
||||
- Server receives and echoes back messages transmitted from client.
|
||||
|
||||
## Running the example
|
||||
|
||||
- Open the project configuration menu (`idf.py menuconfig`) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
|
||||
- Set server port number in menuconfig, "Example configuration".
|
||||
- Run `idf.py -p PORT flash monitor` to build and upload the example to your board and connect to it's serial terminal.
|
||||
- Wait for the board to connect to WiFi or Ethernet (note the IP address).
|
||||
- You can now send a TCP message and check it is repeated, for example using netcat `nc IP PORT`.
|
||||
|
||||
See the README.md file in the upper level 'examples' directory for more information about examples.
|
||||
@@ -0,0 +1,47 @@
|
||||
import re
|
||||
import os
|
||||
import socket
|
||||
|
||||
import ttfw_idf
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_asio_tcp_server(env, extra_data):
|
||||
"""
|
||||
steps: |
|
||||
1. join AP
|
||||
2. Start server
|
||||
3. Test connects to server and sends a test message
|
||||
4. Test evaluates received test message from server
|
||||
5. Test evaluates received test message on server stdout
|
||||
"""
|
||||
test_msg = b"echo message from client to server"
|
||||
dut1 = env.get_dut("tcp_echo_server", "examples/protocols/asio/tcp_echo_server", dut_class=ttfw_idf.ESP32DUT)
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, "asio_tcp_echo_server.bin")
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("asio_tcp_echo_server_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("asio_tcp_echo_server_size", bin_size // 1024, dut1.TARGET)
|
||||
# 1. start test
|
||||
dut1.start_app()
|
||||
# 2. get the server IP address
|
||||
data = dut1.expect(re.compile(r" IPv4 address: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)"), timeout=30)
|
||||
# 3. create tcp client and connect to server
|
||||
cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
cli.settimeout(30)
|
||||
cli.connect((data[0], 2222))
|
||||
cli.send(test_msg)
|
||||
data = cli.recv(1024)
|
||||
# 4. check the message received back from the server
|
||||
if (data == test_msg):
|
||||
print("PASS: Received correct message")
|
||||
pass
|
||||
else:
|
||||
print("Failure!")
|
||||
raise ValueError('Wrong data received from asi tcp server: {} (expected:{})'.format(data, test_msg))
|
||||
# 5. check the client message appears also on server terminal
|
||||
dut1.expect(test_msg.decode())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_protocol_asio_tcp_server()
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "echo_server.cpp"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,9 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_PORT
|
||||
string "Asio example port number"
|
||||
default "2222"
|
||||
help
|
||||
Port number used by Asio example.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# Main component makefile.
|
||||
#
|
||||
# This Makefile can be left empty. By default, it will take the sources in the
|
||||
# src/ directory, compile them and link them into lib(subdirectory_name).a
|
||||
# in the build directory. This behaviour is entirely configurable,
|
||||
# please read the ESP-IDF documents if you need to do this.
|
||||
#
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
#include "asio.hpp"
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include "protocol_examples_common.h"
|
||||
#include "esp_event.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
using asio::ip::tcp;
|
||||
|
||||
class session
|
||||
: public std::enable_shared_from_this<session>
|
||||
{
|
||||
public:
|
||||
session(tcp::socket socket)
|
||||
: socket_(std::move(socket))
|
||||
{
|
||||
}
|
||||
|
||||
void start()
|
||||
{
|
||||
do_read();
|
||||
}
|
||||
|
||||
private:
|
||||
void do_read()
|
||||
{
|
||||
auto self(shared_from_this());
|
||||
socket_.async_read_some(asio::buffer(data_, max_length),
|
||||
[this, self](std::error_code ec, std::size_t length)
|
||||
{
|
||||
if (!ec)
|
||||
{
|
||||
data_[length] = 0;
|
||||
std::cout << data_ << std::endl;
|
||||
do_write(length);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void do_write(std::size_t length)
|
||||
{
|
||||
auto self(shared_from_this());
|
||||
asio::async_write(socket_, asio::buffer(data_, length),
|
||||
[this, self](std::error_code ec, std::size_t length)
|
||||
{
|
||||
if (!ec)
|
||||
{
|
||||
do_read();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tcp::socket socket_;
|
||||
enum { max_length = 1024 };
|
||||
char data_[max_length];
|
||||
};
|
||||
|
||||
class server
|
||||
{
|
||||
public:
|
||||
server(asio::io_context& io_context, short port)
|
||||
: acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
|
||||
{
|
||||
do_accept();
|
||||
}
|
||||
|
||||
private:
|
||||
void do_accept()
|
||||
{
|
||||
acceptor_.async_accept(
|
||||
[this](std::error_code ec, tcp::socket socket)
|
||||
{
|
||||
if (!ec)
|
||||
{
|
||||
std::make_shared<session>(std::move(socket))->start();
|
||||
}
|
||||
|
||||
do_accept();
|
||||
});
|
||||
}
|
||||
|
||||
tcp::acceptor acceptor_;
|
||||
};
|
||||
|
||||
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
esp_netif_init();
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
/* This helper function configures blocking UART I/O */
|
||||
ESP_ERROR_CHECK(example_configure_stdin_stdout());
|
||||
|
||||
asio::io_context io_context;
|
||||
|
||||
server s(io_context, std::atoi(CONFIG_EXAMPLE_PORT));
|
||||
|
||||
io_context.run();
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(asio_udp_echo_server)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
PROJECT_NAME := asio_udp_echo_server
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,19 @@
|
||||
# Asio UDP echo server example
|
||||
|
||||
Simple Asio UDP echo server using WiFi STA or Ethernet.
|
||||
|
||||
## Example workflow
|
||||
|
||||
- Wi-Fi or Ethernet connection is established, and IP address is obtained.
|
||||
- Asio UDP server is started on port number defined through the project configuration
|
||||
- Server receives and echoes back messages transmitted from client
|
||||
|
||||
## Running the example
|
||||
|
||||
- Open the project configuration menu (`idf.py menuconfig`) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
|
||||
- Set server port number in menuconfig, "Example configuration".
|
||||
- Run `idf.py -p PORT flash monitor` to build and upload the example to your board and connect to it's serial terminal.
|
||||
- Wait for the board to connect to WiFi or Ethernet (note the IP address).
|
||||
- You can now send a UDP message and check it is repeated, for example using netcat `nc -u IP PORT`.
|
||||
|
||||
See the README.md file in the upper level 'examples' directory for more information about examples.
|
||||
@@ -0,0 +1,47 @@
|
||||
import re
|
||||
import os
|
||||
import socket
|
||||
|
||||
import ttfw_idf
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_asio_udp_server(env, extra_data):
|
||||
"""
|
||||
steps: |
|
||||
1. join AP
|
||||
2. Start server
|
||||
3. Test connects to server and sends a test message
|
||||
4. Test evaluates received test message from server
|
||||
5. Test evaluates received test message on server stdout
|
||||
"""
|
||||
test_msg = b"echo message from client to server"
|
||||
dut1 = env.get_dut("udp_echo_server", "examples/protocols/asio/udp_echo_server", dut_class=ttfw_idf.ESP32DUT)
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, "asio_udp_echo_server.bin")
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("asio_udp_echo_server_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("asio_udp_echo_server_size", bin_size // 1024, dut1.TARGET)
|
||||
# 1. start test
|
||||
dut1.start_app()
|
||||
# 2. get the server IP address
|
||||
data = dut1.expect(re.compile(r" IPv4 address: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)"), timeout=30)
|
||||
# 3. create tcp client and connect to server
|
||||
cli = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
cli.settimeout(30)
|
||||
cli.connect((data[0], 2222))
|
||||
cli.send(test_msg)
|
||||
data = cli.recv(1024)
|
||||
# 4. check the message received back from the server
|
||||
if (data == test_msg):
|
||||
print("PASS: Received correct message")
|
||||
pass
|
||||
else:
|
||||
print("Failure!")
|
||||
raise ValueError('Wrong data received from asio udp server: {} (expected:{})'.format(data, test_msg))
|
||||
# 5. check the client message appears also on server terminal
|
||||
dut1.expect(test_msg.decode())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_protocol_asio_udp_server()
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "udp_echo_server.cpp"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,9 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_PORT
|
||||
string "Asio example port number"
|
||||
default "2222"
|
||||
help
|
||||
Port number used by Asio example.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,8 @@
|
||||
#
|
||||
# Main component makefile.
|
||||
#
|
||||
# This Makefile can be left empty. By default, it will take the sources in the
|
||||
# src/ directory, compile them and link them into lib(subdirectory_name).a
|
||||
# in the build directory. This behaviour is entirely configurable,
|
||||
# please read the ESP-IDF documents if you need to do this.
|
||||
#
|
||||
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// async_udp_echo_server.cpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
#include "asio.hpp"
|
||||
|
||||
#include "protocol_examples_common.h"
|
||||
#include "esp_event.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
|
||||
using asio::ip::udp;
|
||||
|
||||
class server
|
||||
{
|
||||
public:
|
||||
server(asio::io_context& io_context, short port)
|
||||
: socket_(io_context, udp::endpoint(udp::v4(), port))
|
||||
{
|
||||
do_receive();
|
||||
}
|
||||
|
||||
void do_receive()
|
||||
{
|
||||
socket_.async_receive_from(
|
||||
asio::buffer(data_, max_length), sender_endpoint_,
|
||||
[this](std::error_code ec, std::size_t bytes_recvd)
|
||||
{
|
||||
if (!ec && bytes_recvd > 0)
|
||||
{
|
||||
data_[bytes_recvd] = 0;
|
||||
std::cout << data_ << std::endl;
|
||||
do_send(bytes_recvd);
|
||||
}
|
||||
else
|
||||
{
|
||||
do_receive();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void do_send(std::size_t length)
|
||||
{
|
||||
socket_.async_send_to(
|
||||
asio::buffer(data_, length), sender_endpoint_,
|
||||
[this](std::error_code /*ec*/, std::size_t bytes /*bytes_sent*/)
|
||||
{
|
||||
do_receive();
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
udp::socket socket_;
|
||||
udp::endpoint sender_endpoint_;
|
||||
enum { max_length = 1024 };
|
||||
char data_[max_length];
|
||||
};
|
||||
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
esp_netif_init();
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
/* This helper function configures blocking UART I/O */
|
||||
ESP_ERROR_CHECK(example_configure_stdin_stdout());
|
||||
|
||||
asio::io_context io_context;
|
||||
|
||||
server s(io_context, std::atoi(CONFIG_EXAMPLE_PORT));
|
||||
|
||||
io_context.run();
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
@@ -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(cbor)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := cbor
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# CBOR Example
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
## Overview
|
||||
|
||||
The [CBOR](https://en.wikipedia.org/wiki/CBOR)(Concise Binary Object Representation) is a binary data serialization format which is similar to JSON but with smaller footprint. This example will illustrate how to encode and decode CBOR data using the APIs provided by [tinycbor](https://github.com/intel/tinycbor).
|
||||
|
||||
For detailed information about how CBOR encoding and decoding works, please refer to [REF7049](https://tools.ietf.org/html/rfc7049) or [cbor.io](http://cbor.io/);
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example should be able to run on any commonly available ESP32 development board.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Run `idf.py -p PORT flash monitor` to build and flash the project.
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
```bash
|
||||
I (320) example: encoded buffer size 67
|
||||
I (320) example: convert CBOR to JSON
|
||||
[{"chip":"esp32","unicore":false,"ip":[192,168,1,100]},3.1400001049041748,"simple(99)","2019-07-10 09:00:00+0000","undefined"]
|
||||
I (340) example: decode CBOR manually
|
||||
Array[
|
||||
Map{
|
||||
chip
|
||||
esp32
|
||||
unicore
|
||||
false
|
||||
ip
|
||||
Array[
|
||||
192
|
||||
168
|
||||
1
|
||||
100
|
||||
]
|
||||
}
|
||||
3.14
|
||||
simple(99)
|
||||
2019-07-10 09:00:00+0000
|
||||
undefined
|
||||
]
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
For more API usage, please refer to [tinycbor API](https://intel.github.io/tinycbor/current/).
|
||||
|
||||
(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.)
|
||||
@@ -0,0 +1,42 @@
|
||||
from __future__ import unicode_literals
|
||||
import re
|
||||
import textwrap
|
||||
import ttfw_idf
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_WIFI')
|
||||
def test_examples_cbor(env, extra_data):
|
||||
|
||||
dut = env.get_dut('cbor', 'examples/protocols/cbor')
|
||||
dut.start_app()
|
||||
dut.expect(re.compile(r'example: encoded buffer size \d+'))
|
||||
dut.expect('example: convert CBOR to JSON')
|
||||
parsed_info = dut.expect(re.compile(r'\[\{"chip":"(\w+)","unicore":(\w+),"ip":\[(\d+),(\d+),(\d+),(\d+)\]\},'
|
||||
r'3.1400001049041748'
|
||||
r',"simple\(99\)","2019-07-10 09:00:00\+0000","undefined"\]'))
|
||||
dut.expect('example: decode CBOR manually')
|
||||
|
||||
dut.expect(re.compile(textwrap.dedent(r'''
|
||||
Array\[\s+
|
||||
Map{{\s+
|
||||
chip\s+
|
||||
{}\s+
|
||||
unicore\s+
|
||||
{}\s+
|
||||
ip\s+
|
||||
Array\[\s+
|
||||
{}\s+
|
||||
{}\s+
|
||||
{}\s+
|
||||
{}\s+
|
||||
\]\s+
|
||||
}}\s+
|
||||
3.14\s+
|
||||
simple\(99\)\s+
|
||||
2019-07-10 09:00:00\+0000\s+
|
||||
undefined\s+
|
||||
\]'''.format(*parsed_info)).replace('{', r'\{').replace('}', r'\}')))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_cbor()
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "cbor_example_main.c"
|
||||
INCLUDE_DIRS "")
|
||||
@@ -0,0 +1,226 @@
|
||||
/* CBOR 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 <stdio.h>
|
||||
#include "esp_log.h"
|
||||
#include "cbor.h"
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
#define CBOR_CHECK(a, str, goto_tag, ret_value, ...) \
|
||||
do \
|
||||
{ \
|
||||
if ((a) != CborNoError) \
|
||||
{ \
|
||||
ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
|
||||
ret = ret_value; \
|
||||
goto goto_tag; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static void indent(int nestingLevel)
|
||||
{
|
||||
while (nestingLevel--) {
|
||||
printf(" ");
|
||||
}
|
||||
}
|
||||
|
||||
static void dumpbytes(const uint8_t *buf, size_t len)
|
||||
{
|
||||
while (len--) {
|
||||
printf("%02X ", *buf++);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode CBOR data manuallly
|
||||
*/
|
||||
static CborError example_dump_cbor_buffer(CborValue *it, int nestingLevel)
|
||||
{
|
||||
CborError ret = CborNoError;
|
||||
while (!cbor_value_at_end(it)) {
|
||||
CborType type = cbor_value_get_type(it);
|
||||
|
||||
indent(nestingLevel);
|
||||
switch (type) {
|
||||
case CborArrayType: {
|
||||
CborValue recursed;
|
||||
assert(cbor_value_is_container(it));
|
||||
puts("Array[");
|
||||
ret = cbor_value_enter_container(it, &recursed);
|
||||
CBOR_CHECK(ret, "enter container failed", err, ret);
|
||||
ret = example_dump_cbor_buffer(&recursed, nestingLevel + 1);
|
||||
CBOR_CHECK(ret, "recursive dump failed", err, ret);
|
||||
ret = cbor_value_leave_container(it, &recursed);
|
||||
CBOR_CHECK(ret, "leave container failed", err, ret);
|
||||
indent(nestingLevel);
|
||||
puts("]");
|
||||
continue;
|
||||
}
|
||||
case CborMapType: {
|
||||
CborValue recursed;
|
||||
assert(cbor_value_is_container(it));
|
||||
puts("Map{");
|
||||
ret = cbor_value_enter_container(it, &recursed);
|
||||
CBOR_CHECK(ret, "enter container failed", err, ret);
|
||||
ret = example_dump_cbor_buffer(&recursed, nestingLevel + 1);
|
||||
CBOR_CHECK(ret, "recursive dump failed", err, ret);
|
||||
ret = cbor_value_leave_container(it, &recursed);
|
||||
CBOR_CHECK(ret, "leave container failed", err, ret);
|
||||
indent(nestingLevel);
|
||||
puts("}");
|
||||
continue;
|
||||
}
|
||||
case CborIntegerType: {
|
||||
int64_t val;
|
||||
ret = cbor_value_get_int64(it, &val);
|
||||
CBOR_CHECK(ret, "parse int64 failed", err, ret);
|
||||
printf("%lld\n", (long long)val);
|
||||
break;
|
||||
}
|
||||
case CborByteStringType: {
|
||||
uint8_t *buf;
|
||||
size_t n;
|
||||
ret = cbor_value_dup_byte_string(it, &buf, &n, it);
|
||||
CBOR_CHECK(ret, "parse byte string failed", err, ret);
|
||||
dumpbytes(buf, n);
|
||||
puts("");
|
||||
free(buf);
|
||||
continue;
|
||||
}
|
||||
case CborTextStringType: {
|
||||
char *buf;
|
||||
size_t n;
|
||||
ret = cbor_value_dup_text_string(it, &buf, &n, it);
|
||||
CBOR_CHECK(ret, "parse text string failed", err, ret);
|
||||
puts(buf);
|
||||
free(buf);
|
||||
continue;
|
||||
}
|
||||
case CborTagType: {
|
||||
CborTag tag;
|
||||
ret = cbor_value_get_tag(it, &tag);
|
||||
CBOR_CHECK(ret, "parse tag failed", err, ret);
|
||||
printf("Tag(%lld)\n", (long long)tag);
|
||||
break;
|
||||
}
|
||||
case CborSimpleType: {
|
||||
uint8_t type;
|
||||
ret = cbor_value_get_simple_type(it, &type);
|
||||
CBOR_CHECK(ret, "parse simple type failed", err, ret);
|
||||
printf("simple(%u)\n", type);
|
||||
break;
|
||||
}
|
||||
case CborNullType:
|
||||
puts("null");
|
||||
break;
|
||||
case CborUndefinedType:
|
||||
puts("undefined");
|
||||
break;
|
||||
case CborBooleanType: {
|
||||
bool val;
|
||||
ret = cbor_value_get_boolean(it, &val);
|
||||
CBOR_CHECK(ret, "parse boolean type failed", err, ret);
|
||||
puts(val ? "true" : "false");
|
||||
break;
|
||||
}
|
||||
case CborHalfFloatType: {
|
||||
uint16_t val;
|
||||
ret = cbor_value_get_half_float(it, &val);
|
||||
CBOR_CHECK(ret, "parse half float type failed", err, ret);
|
||||
printf("__f16(%04x)\n", val);
|
||||
break;
|
||||
}
|
||||
case CborFloatType: {
|
||||
float val;
|
||||
ret = cbor_value_get_float(it, &val);
|
||||
CBOR_CHECK(ret, "parse float type failed", err, ret);
|
||||
printf("%g\n", val);
|
||||
break;
|
||||
}
|
||||
case CborDoubleType: {
|
||||
double val;
|
||||
ret = cbor_value_get_double(it, &val);
|
||||
CBOR_CHECK(ret, "parse double float type failed", err, ret);
|
||||
printf("%g\n", val);
|
||||
break;
|
||||
}
|
||||
case CborInvalidType: {
|
||||
ret = CborErrorUnknownType;
|
||||
CBOR_CHECK(ret, "unknown cbor type", err, ret);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret = cbor_value_advance_fixed(it);
|
||||
CBOR_CHECK(ret, "fix value failed", err, ret);
|
||||
}
|
||||
return CborNoError;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
CborEncoder root_encoder;
|
||||
CborParser root_parser;
|
||||
CborValue it;
|
||||
uint8_t buf[100];
|
||||
|
||||
// Initialize the outermost cbor encoder
|
||||
cbor_encoder_init(&root_encoder, buf, sizeof(buf), 0);
|
||||
|
||||
// Create an array containing several items
|
||||
CborEncoder array_encoder;
|
||||
CborEncoder map_encoder;
|
||||
cbor_encoder_create_array(&root_encoder, &array_encoder, 5); // [
|
||||
// 1. Create a map containing several pairs
|
||||
cbor_encoder_create_map(&array_encoder, &map_encoder, 3); // {
|
||||
// chip:esp32
|
||||
cbor_encode_text_stringz(&map_encoder, "chip");
|
||||
cbor_encode_text_stringz(&map_encoder, "esp32");
|
||||
// unicore:false
|
||||
cbor_encode_text_stringz(&map_encoder, "unicore");
|
||||
cbor_encode_boolean(&map_encoder, false);
|
||||
// ip:[192,168,1,100]
|
||||
cbor_encode_text_stringz(&map_encoder, "ip");
|
||||
CborEncoder array2;
|
||||
cbor_encoder_create_array(&map_encoder, &array2, 4); // [
|
||||
// Encode several numbers
|
||||
cbor_encode_uint(&array2, 192);
|
||||
cbor_encode_uint(&array2, 168);
|
||||
cbor_encode_uint(&array2, 1);
|
||||
cbor_encode_uint(&array2, 100);
|
||||
cbor_encoder_close_container(&map_encoder, &array2); // ]
|
||||
cbor_encoder_close_container(&array_encoder, &map_encoder); // }
|
||||
// 2. Encode float number
|
||||
cbor_encode_float(&array_encoder, 3.14);
|
||||
// 3. Encode simple value
|
||||
cbor_encode_simple_value(&array_encoder, 99);
|
||||
// 4. Encode a string
|
||||
cbor_encode_text_stringz(&array_encoder, "2019-07-10 09:00:00+0000");
|
||||
// 5. Encode a undefined value
|
||||
cbor_encode_undefined(&array_encoder);
|
||||
cbor_encoder_close_container(&root_encoder, &array_encoder); // ]
|
||||
|
||||
// If error happend when encoding, then this value should be meaningless
|
||||
ESP_LOGI(TAG, "encoded buffer size %d", cbor_encoder_get_buffer_size(&root_encoder, buf));
|
||||
|
||||
// Initialize the cbor parser and the value iterator
|
||||
cbor_parser_init(buf, sizeof(buf), 0, &root_parser, &it);
|
||||
|
||||
ESP_LOGI(TAG, "convert CBOR to JSON");
|
||||
// Dump the values in JSON format
|
||||
cbor_value_to_json(stdout, &it, 0);
|
||||
puts("");
|
||||
|
||||
ESP_LOGI(TAG, "decode CBOR manually");
|
||||
// Decode CBOR data manully
|
||||
example_dump_cbor_buffer(&it, 0);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(coap_client)
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := coap_client
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
|
||||
# CoAP client example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
This CoAP client example is very simplified adaptation of one of the
|
||||
[libcoap](https://github.com/obgm/libcoap) examples.
|
||||
|
||||
CoAP client example will connect your ESP32 device to a CoAP server, send off a GET request and
|
||||
fetch the response data from CoAP server. The client can be extended to PUT / POST / DELETE requests,
|
||||
as well as supporting the Observer extensions [RFC7641](https://tools.ietf.org/html/rfc7641).
|
||||
|
||||
If the URI is prefixed with coaps:// instead of coap://, then the CoAP client will attempt to use
|
||||
the DTLS protocol using the defined Pre-Shared Keys(PSK) or Public Key Infrastructure (PKI) which the
|
||||
CoAP server needs to know about.
|
||||
|
||||
If the URI is prefixed with coap+tcp://, then the CoAP will try to use TCP for the communication.
|
||||
|
||||
NOTE: coaps+tcp:// is not currently supported, even though both libcoap and MbedTLS support it.
|
||||
|
||||
The Constrained Application Protocol (CoAP) is a specialized web transfer protocol for use with
|
||||
constrained nodes and constrained networks in the Internet of Things.
|
||||
The protocol is designed for machine-to-machine (M2M) applications such as smart energy and
|
||||
building automation.
|
||||
|
||||
Please refer to [RFC7252](https://www.rfc-editor.org/rfc/pdfrfc/rfc7252.txt.pdf) for more details.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
Example Connection Configuration --->
|
||||
* Set WiFi SSID under Example Configuration
|
||||
* Set WiFi Password under Example Configuration
|
||||
Example CoAP Client Configuration --->
|
||||
* Set CoAP Target Uri
|
||||
* If PSK, Set CoAP Preshared Key to use in connection to the server
|
||||
* If PSK, Set CoAP PSK Client identity (username)
|
||||
Component config --->
|
||||
CoAP Configuration --->
|
||||
* Set encryption method definition, PSK (default) or PKI
|
||||
* Enable CoAP debugging if required
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py build
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
Prerequisite: we startup a CoAP server on coap server example,
|
||||
or use the default of coap://californium.eclipse.org.
|
||||
|
||||
and you could receive data from CoAP server if succeed,
|
||||
such as the following log:
|
||||
|
||||
```
|
||||
...
|
||||
I (332) wifi: mode : sta (30:ae:a4:04:1b:7c)
|
||||
I (1672) wifi: n:11 0, o:1 0, ap:255 255, sta:11 0, prof:1
|
||||
I (1672) wifi: state: init -> auth (b0)
|
||||
I (1682) wifi: state: auth -> assoc (0)
|
||||
I (1692) wifi: state: assoc -> run (10)
|
||||
I (1692) wifi: connected with huawei_cw, channel 11
|
||||
I (1692) wifi: pm start, type: 1
|
||||
|
||||
I (2582) event: sta ip: 192.168.3.89, mask: 255.255.255.0, gw: 192.168.3.1
|
||||
I (2582) CoAP_client: Connected to AP
|
||||
I (2582) CoAP_client: DNS lookup succeeded. IP=104.196.15.150
|
||||
Received:
|
||||
************************************************************
|
||||
CoAP RFC 7252 Cf 2.0.0-SNAPSHOT
|
||||
************************************************************
|
||||
This server is using the Eclipse Californium (Cf) CoAP framework
|
||||
published under EPL+EDL: http://www.eclipse.org/californium/
|
||||
|
||||
(c) 2014, 2015, 2016 Institute for Pervasive Computing, ETH Zurich and others
|
||||
************************************************************
|
||||
...
|
||||
```
|
||||
|
||||
## libcoap Documentation
|
||||
This can be found at https://libcoap.net/doc/reference/4.2.0/
|
||||
|
||||
## Troubleshooting
|
||||
* Please make sure Target Url includes valid `host`, optional `port`,
|
||||
optional `path`, and begins with `coap://`, `coaps://` or `coap+tcp://`
|
||||
for a coap server that supports TCP
|
||||
(not all do including coap+tcp://californium.eclipse.org).
|
||||
|
||||
* CoAP logging can be enabled by running 'idf.py menuconfig -> Component config -> CoAP Configuration' and setting appropriate log level
|
||||
@@ -0,0 +1,4 @@
|
||||
# Embed CA, certificate & key directly into binary
|
||||
idf_component_register(SRCS "coap_client_example_main.c"
|
||||
INCLUDE_DIRS "."
|
||||
EMBED_TXTFILES certs/coap_ca.pem certs/coap_client.crt certs/coap_client.key)
|
||||
@@ -0,0 +1,27 @@
|
||||
menu "Example CoAP Client Configuration"
|
||||
|
||||
config EXAMPLE_TARGET_DOMAIN_URI
|
||||
string "Target Uri"
|
||||
default "coaps://californium.eclipse.org"
|
||||
help
|
||||
Target uri for the example to use. Use coaps:// prefix for encrypted traffic
|
||||
using Pre-Shared Key (PSK) or Public Key Infrastructure (PKI).
|
||||
|
||||
config EXAMPLE_COAP_PSK_KEY
|
||||
string "Preshared Key (PSK) to used in the connection to the CoAP server"
|
||||
depends on COAP_MBEDTLS_PSK
|
||||
default "sesame"
|
||||
help
|
||||
The Preshared Key to use to encrypt the communicatons. The same key must be
|
||||
used at both ends of the CoAP connection, and the CoaP client must request
|
||||
an URI prefixed with coaps:// instead of coap:// for DTLS to be used.
|
||||
|
||||
config EXAMPLE_COAP_PSK_IDENTITY
|
||||
string "PSK Client identity (username)"
|
||||
depends on COAP_MBEDTLS_PSK
|
||||
default "password"
|
||||
help
|
||||
The identity (or username) to use to identify to the CoAP server which
|
||||
PSK key to use.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,23 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID3DCCA0WgAwIBAgIJAMnlgL1czsmjMA0GCSqGSIb3DQEBCwUAMIGTMQswCQYD
|
||||
VQQGEwJGUjEPMA0GA1UECAwGUmFkaXVzMRIwEAYDVQQHDAlTb21ld2hlcmUxFTAT
|
||||
BgNVBAoMDEV4YW1wbGUgSW5jLjEgMB4GCSqGSIb3DQEJARYRYWRtaW5AZXhhbXBs
|
||||
ZS5jb20xJjAkBgNVBAMMHUV4YW1wbGUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4X
|
||||
DTE3MDYwNzA4MDY0OVoXDTI3MDYwNTA4MDY0OVowgZMxCzAJBgNVBAYTAkZSMQ8w
|
||||
DQYDVQQIDAZSYWRpdXMxEjAQBgNVBAcMCVNvbWV3aGVyZTEVMBMGA1UECgwMRXhh
|
||||
bXBsZSBJbmMuMSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBleGFtcGxlLmNvbTEmMCQG
|
||||
A1UEAwwdRXhhbXBsZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwgZ8wDQYJKoZIhvcN
|
||||
AQEBBQADgY0AMIGJAoGBALpWR23fn/TmHxsXsHdrydzPSd17fZkc71WsaicgQR66
|
||||
1tIVYb22UWGfj9KPM8THMsV74ew4ZkaQ39qvU0iuQIRrKARFHFok+vbaecgWMeWe
|
||||
vGIqdnmyB9gJYaFOKgtSkfXsu2ddsqdvLYwcDbczrq8X9yEXpN6mnxXeCcPG4F0p
|
||||
AgMBAAGjggE0MIIBMDAdBgNVHQ4EFgQUgigpdAUpONoDq0pQ3yfxrslCSpcwgcgG
|
||||
A1UdIwSBwDCBvYAUgigpdAUpONoDq0pQ3yfxrslCSpehgZmkgZYwgZMxCzAJBgNV
|
||||
BAYTAkZSMQ8wDQYDVQQIDAZSYWRpdXMxEjAQBgNVBAcMCVNvbWV3aGVyZTEVMBMG
|
||||
A1UECgwMRXhhbXBsZSBJbmMuMSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBleGFtcGxl
|
||||
LmNvbTEmMCQGA1UEAwwdRXhhbXBsZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCCQDJ
|
||||
5YC9XM7JozAMBgNVHRMEBTADAQH/MDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly93
|
||||
d3cuZXhhbXBsZS5jb20vZXhhbXBsZV9jYS5jcmwwDQYJKoZIhvcNAQELBQADgYEA
|
||||
euxOBPInSJRKAIseMxPmAabtAqKNslZSmpG4He3lkKt+HM3jfznUt3psmD7j1hFW
|
||||
S4l7KXzzajvaGYybDq5N9MqrDjhGn3VXZqOLMUNDL7OQq96TzgqsTBT1dmVSbNlt
|
||||
PQgiAeKAk3tmH4lRRi9MTBSyJ6I92JYcS5H6Bs4ZwCc=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,70 @@
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number: 48 (0x30)
|
||||
Signature Algorithm: sha1WithRSAEncryption
|
||||
Issuer: C=FR, ST=Radius, L=Somewhere, O=Example Inc./emailAddress=admin@example.com, CN=Example Certificate Authority
|
||||
Validity
|
||||
Not Before: Jun 7 08:06:49 2017 GMT
|
||||
Not After : Jun 5 08:06:49 2027 GMT
|
||||
Subject: C=FR, ST=Radius, O=Example Inc., CN=user@example.com/emailAddress=user@example.com
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (2048 bit)
|
||||
Modulus:
|
||||
00:d2:f6:be:72:a5:ab:2e:56:0c:dd:f2:3b:2c:7c:
|
||||
e0:5d:05:40:af:0c:8c:f3:82:0c:d0:18:34:b4:e3:
|
||||
7d:5f:8d:0a:3e:aa:79:02:f9:96:ad:10:00:ec:51:
|
||||
e9:dc:3f:fb:ea:b0:57:eb:48:c7:ca:ef:e8:05:ab:
|
||||
ee:3f:66:ba:5c:9e:7f:40:85:9f:25:a0:e0:e3:7c:
|
||||
cf:b6:e6:31:f5:fd:24:03:c8:f4:fb:d8:a4:f3:92:
|
||||
29:05:aa:55:43:80:f7:3e:13:10:43:3a:89:24:be:
|
||||
d8:01:86:d1:69:73:44:7d:f8:b9:46:2b:6b:51:d0:
|
||||
11:31:4b:06:ae:9f:45:fa:12:17:0c:ef:6a:fa:d0:
|
||||
f7:36:46:eb:2e:db:4e:20:46:01:33:ac:b1:f7:4a:
|
||||
e6:18:3d:53:22:dc:e8:4a:12:78:11:2f:e4:3b:92:
|
||||
bd:d7:07:5a:c9:81:5d:48:58:c8:0f:9b:e9:a4:0f:
|
||||
bb:89:b1:ad:38:07:6f:93:d0:a6:12:56:f9:07:48:
|
||||
d2:23:2f:a3:a9:93:b0:11:0a:27:4c:48:0a:8d:70:
|
||||
41:68:76:7a:dd:bc:54:c3:42:33:b0:7b:f6:ae:1f:
|
||||
e7:95:5e:11:ca:f2:b4:4b:5c:ba:47:64:f0:f3:d7:
|
||||
87:95:7f:93:06:a1:72:c9:81:12:a5:b7:8f:9d:7e:
|
||||
d1:ef
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Extended Key Usage:
|
||||
TLS Web Client Authentication
|
||||
X509v3 CRL Distribution Points:
|
||||
|
||||
Full Name:
|
||||
URI:http://www.example.com/example_ca.crl
|
||||
|
||||
Signature Algorithm: sha1WithRSAEncryption
|
||||
2d:02:bc:7b:88:b8:5c:e1:07:b8:bb:ba:b2:f3:98:14:8f:cb:
|
||||
b0:21:13:b5:e5:6f:05:4f:92:fa:ac:c0:53:a7:b0:cd:7e:ba:
|
||||
87:36:85:25:d7:41:c5:29:84:22:74:af:bf:3e:34:36:d5:24:
|
||||
7a:81:e2:1b:54:52:85:6f:76:de:dc:63:98:45:fc:2c:31:fa:
|
||||
22:a4:72:3a:8d:d4:6a:2e:de:33:10:41:eb:94:1d:e3:59:cd:
|
||||
b2:be:ab:f0:b6:20:86:9c:b8:46:ee:c5:64:ba:b6:6c:cc:53:
|
||||
44:7a:80:12:77:7c:e7:51:67:91:32:2f:88:9d:93:a8:ef:d6:
|
||||
cd:de
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDTjCCAregAwIBAgIBMDANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UEBhMCRlIx
|
||||
DzANBgNVBAgMBlJhZGl1czESMBAGA1UEBwwJU29tZXdoZXJlMRUwEwYDVQQKDAxF
|
||||
eGFtcGxlIEluYy4xIDAeBgkqhkiG9w0BCQEWEWFkbWluQGV4YW1wbGUuY29tMSYw
|
||||
JAYDVQQDDB1FeGFtcGxlIENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0xNzA2MDcw
|
||||
ODA2NDlaFw0yNzA2MDUwODA2NDlaMHExCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZS
|
||||
YWRpdXMxFTATBgNVBAoMDEV4YW1wbGUgSW5jLjEZMBcGA1UEAwwQdXNlckBleGFt
|
||||
cGxlLmNvbTEfMB0GCSqGSIb3DQEJARYQdXNlckBleGFtcGxlLmNvbTCCASIwDQYJ
|
||||
KoZIhvcNAQEBBQADggEPADCCAQoCggEBANL2vnKlqy5WDN3yOyx84F0FQK8MjPOC
|
||||
DNAYNLTjfV+NCj6qeQL5lq0QAOxR6dw/++qwV+tIx8rv6AWr7j9mulyef0CFnyWg
|
||||
4ON8z7bmMfX9JAPI9PvYpPOSKQWqVUOA9z4TEEM6iSS+2AGG0WlzRH34uUYra1HQ
|
||||
ETFLBq6fRfoSFwzvavrQ9zZG6y7bTiBGATOssfdK5hg9UyLc6EoSeBEv5DuSvdcH
|
||||
WsmBXUhYyA+b6aQPu4mxrTgHb5PQphJW+QdI0iMvo6mTsBEKJ0xICo1wQWh2et28
|
||||
VMNCM7B79q4f55VeEcrytEtcukdk8PPXh5V/kwahcsmBEqW3j51+0e8CAwEAAaNP
|
||||
ME0wEwYDVR0lBAwwCgYIKwYBBQUHAwIwNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDov
|
||||
L3d3dy5leGFtcGxlLmNvbS9leGFtcGxlX2NhLmNybDANBgkqhkiG9w0BAQUFAAOB
|
||||
gQAtArx7iLhc4Qe4u7qy85gUj8uwIRO15W8FT5L6rMBTp7DNfrqHNoUl10HFKYQi
|
||||
dK+/PjQ21SR6geIbVFKFb3be3GOYRfwsMfoipHI6jdRqLt4zEEHrlB3jWc2yvqvw
|
||||
tiCGnLhG7sVkurZszFNEeoASd3znUWeRMi+InZOo79bN3g==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA0va+cqWrLlYM3fI7LHzgXQVArwyM84IM0Bg0tON9X40KPqp5
|
||||
AvmWrRAA7FHp3D/76rBX60jHyu/oBavuP2a6XJ5/QIWfJaDg43zPtuYx9f0kA8j0
|
||||
+9ik85IpBapVQ4D3PhMQQzqJJL7YAYbRaXNEffi5RitrUdARMUsGrp9F+hIXDO9q
|
||||
+tD3NkbrLttOIEYBM6yx90rmGD1TItzoShJ4ES/kO5K91wdayYFdSFjID5vppA+7
|
||||
ibGtOAdvk9CmElb5B0jSIy+jqZOwEQonTEgKjXBBaHZ63bxUw0IzsHv2rh/nlV4R
|
||||
yvK0S1y6R2Tw89eHlX+TBqFyyYESpbePnX7R7wIDAQABAoIBAQC5PncO3tBIeMEF
|
||||
pu007FZq9/DLhP7D2B9+HrMxX0y4uXUUf8aQyS74ukPFP0xV3U1M0BnzfU4KscyQ
|
||||
Jl+nBoKAT6C3vF15wiGXQAJ4vPuD4Ate03fjKWH2ixJAakhCZR01QbIXBnBkdrvf
|
||||
401BBjlPUDcIGZo8FbLzEMlGTo84vE9v3Qmkbi+PzPCh2YC+NDmsOcIW1zpmwyYC
|
||||
ZYCpoWgl4++kqXXn0NGhuaOgB0JLsJOBpx/hOOjBU/wXCKaXZ1vchYqfbvvx2gf2
|
||||
WX4P0CiTH1z7MEAHanaZkcnNyxV/oF1EIMY5p0vDDzgrKtppvPOqspjydje03+CE
|
||||
t0wKGPi5AoGBAPAG2Y4efgwLcoWdPjKZtsHLhDhLJnvxkqnNkzdPnLZojNi8pKkV
|
||||
/Yu++pPemJZZa4YAp+OnqyEfhcha+HYqKMwRC8t3YrEVOlRQTfW/OoSrp059JIRV
|
||||
jTvq/u7DdYGJRRgMLUJiEI+7xj1WbTc2EceJAgn0qfKvbvBtVJP0LH1TAoGBAOEA
|
||||
xZB7SwyX+zDGRTugqMYg+sYobbQHJ7utLyoX+ckeG+sPEjEYLpQQfshET/gwF8ZK
|
||||
4aILkACx/tna799xCjQdmyyc338NO9WULlY1xF+65WfeaxrtTAsqVikX3p19McRI
|
||||
ijX8k7Msy3gYWJXev3MCtPT2+g68IgbL/W2wY+l1AoGAT7xGy0Jv5vpqid5pig+s
|
||||
OYatHrJAT445hXUIQaiNy77Bg0JvhMgMWT8RKMwabl+4K2TOYP8TB0bcf2lQ/pgU
|
||||
w22qOGYpf+AoZ1fh/hAPlYEcbCOAXQG6kDwJgjGmOGjsbgelhVbkX4smWLv8PgoV
|
||||
L+7goYQIbNlAhlgbb6b+nIcCgYBB7Zr2Cdpkt0en9ACnRx0M6O7yDziNzqbqzAUM
|
||||
3XeYYZUmnATlk8NaKTcs8S9JdrYQqTJR6/dm7MDTDt7IZvPpb19fhBvMu5DztPaa
|
||||
1ihTMI01kStq+WsVvnL+mXrmRJ/HdsXgqcCReKep6eBTEbChP4LMYG3G0YNa4HzC
|
||||
njO4XQKBgQDRnbqqg2CNTnS94BN2D3uzzELtwsIG6aVCtl09ZsLnGaBKVVDtP6BI
|
||||
j2hGD7xw4g5JeSPIJU5J03nALTY3hz1JyI7AJCX7+JRtUTX2A8C4mlbeul7ilGaU
|
||||
A7MFT8GqhjYYa84GzNcA1mK8ynlixpL8+yzTT/8lWInWRBa69SkktA==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -0,0 +1,453 @@
|
||||
/* CoAP client 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* WARNING
|
||||
* libcoap is not multi-thread safe, so only this thread must make any coap_*()
|
||||
* calls. Any external (to this thread) data transmitted in/out via libcoap
|
||||
* therefore has to be passed in/out by xQueue*() via this thread.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#if 1
|
||||
/* Needed until coap_dtls.h becomes a part of libcoap proper */
|
||||
#include "libcoap.h"
|
||||
#include "coap_dtls.h"
|
||||
#endif
|
||||
#include "coap.h"
|
||||
|
||||
#define COAP_DEFAULT_TIME_SEC 5
|
||||
|
||||
/* The examples use simple Pre-Shared-Key configuration that you can set via
|
||||
'make menuconfig'.
|
||||
|
||||
If you'd rather not, just change the below entries to strings with
|
||||
the config you want - ie #define EXAMPLE_COAP_PSK_KEY "some-agreed-preshared-key"
|
||||
|
||||
Note: PSK will only be used if the URI is prefixed with coaps://
|
||||
instead of coap:// and the PSK must be one that the server supports
|
||||
(potentially associated with the IDENTITY)
|
||||
*/
|
||||
#define EXAMPLE_COAP_PSK_KEY CONFIG_EXAMPLE_COAP_PSK_KEY
|
||||
#define EXAMPLE_COAP_PSK_IDENTITY CONFIG_EXAMPLE_COAP_PSK_IDENTITY
|
||||
|
||||
/* The examples use uri Logging Level that
|
||||
you can set via 'make menuconfig'.
|
||||
|
||||
If you'd rather not, just change the below entry to a value
|
||||
that is between 0 and 7 with
|
||||
the config you want - ie #define EXAMPLE_COAP_LOG_DEFAULT_LEVEL 7
|
||||
*/
|
||||
#define EXAMPLE_COAP_LOG_DEFAULT_LEVEL CONFIG_COAP_LOG_DEFAULT_LEVEL
|
||||
|
||||
/* The examples use uri "coap://californium.eclipse.org" that
|
||||
you can set via the project configuration (idf.py menuconfig)
|
||||
|
||||
If you'd rather not, just change the below entries to strings with
|
||||
the config you want - ie #define COAP_DEFAULT_DEMO_URI "coaps://californium.eclipse.org"
|
||||
*/
|
||||
#define COAP_DEFAULT_DEMO_URI CONFIG_EXAMPLE_TARGET_DOMAIN_URI
|
||||
|
||||
const static char *TAG = "CoAP_client";
|
||||
|
||||
static int resp_wait = 1;
|
||||
static coap_optlist_t *optlist = NULL;
|
||||
static int wait_ms;
|
||||
|
||||
#ifdef CONFIG_COAP_MBEDTLS_PKI
|
||||
/* CA cert, taken from coap_ca.pem
|
||||
Client cert, taken from coap_client.crt
|
||||
Client key, taken from coap_client.key
|
||||
|
||||
The PEM, CRT and KEY file are examples taken from the wpa2 enterprise
|
||||
example.
|
||||
|
||||
To embed it in the app binary, the PEM, CRT and KEY file is named
|
||||
in the component.mk COMPONENT_EMBED_TXTFILES variable.
|
||||
*/
|
||||
extern uint8_t ca_pem_start[] asm("_binary_coap_ca_pem_start");
|
||||
extern uint8_t ca_pem_end[] asm("_binary_coap_ca_pem_end");
|
||||
extern uint8_t client_crt_start[] asm("_binary_coap_client_crt_start");
|
||||
extern uint8_t client_crt_end[] asm("_binary_coap_client_crt_end");
|
||||
extern uint8_t client_key_start[] asm("_binary_coap_client_key_start");
|
||||
extern uint8_t client_key_end[] asm("_binary_coap_client_key_end");
|
||||
#endif /* CONFIG_COAP_MBEDTLS_PKI */
|
||||
|
||||
static void message_handler(coap_context_t *ctx, coap_session_t *session,
|
||||
coap_pdu_t *sent, coap_pdu_t *received,
|
||||
const coap_tid_t id)
|
||||
{
|
||||
unsigned char *data = NULL;
|
||||
size_t data_len;
|
||||
coap_pdu_t *pdu = NULL;
|
||||
coap_opt_t *block_opt;
|
||||
coap_opt_iterator_t opt_iter;
|
||||
unsigned char buf[4];
|
||||
coap_optlist_t *option;
|
||||
coap_tid_t tid;
|
||||
|
||||
if (COAP_RESPONSE_CLASS(received->code) == 2) {
|
||||
/* Need to see if blocked response */
|
||||
block_opt = coap_check_option(received, COAP_OPTION_BLOCK2, &opt_iter);
|
||||
if (block_opt) {
|
||||
uint16_t blktype = opt_iter.type;
|
||||
|
||||
if (coap_opt_block_num(block_opt) == 0) {
|
||||
printf("Received:\n");
|
||||
}
|
||||
if (coap_get_data(received, &data_len, &data)) {
|
||||
printf("%.*s", (int)data_len, data);
|
||||
}
|
||||
if (COAP_OPT_BLOCK_MORE(block_opt)) {
|
||||
/* more bit is set */
|
||||
|
||||
/* create pdu with request for next block */
|
||||
pdu = coap_new_pdu(session);
|
||||
if (!pdu) {
|
||||
ESP_LOGE(TAG, "coap_new_pdu() failed");
|
||||
goto clean_up;
|
||||
}
|
||||
pdu->type = COAP_MESSAGE_CON;
|
||||
pdu->tid = coap_new_message_id(session);
|
||||
pdu->code = COAP_REQUEST_GET;
|
||||
|
||||
/* add URI components from optlist */
|
||||
for (option = optlist; option; option = option->next ) {
|
||||
switch (option->number) {
|
||||
case COAP_OPTION_URI_HOST :
|
||||
case COAP_OPTION_URI_PORT :
|
||||
case COAP_OPTION_URI_PATH :
|
||||
case COAP_OPTION_URI_QUERY :
|
||||
coap_add_option(pdu, option->number, option->length,
|
||||
option->data);
|
||||
break;
|
||||
default:
|
||||
; /* skip other options */
|
||||
}
|
||||
}
|
||||
|
||||
/* finally add updated block option from response, clear M bit */
|
||||
/* blocknr = (blocknr & 0xfffffff7) + 0x10; */
|
||||
coap_add_option(pdu,
|
||||
blktype,
|
||||
coap_encode_var_safe(buf, sizeof(buf),
|
||||
((coap_opt_block_num(block_opt) + 1) << 4) |
|
||||
COAP_OPT_BLOCK_SZX(block_opt)), buf);
|
||||
|
||||
tid = coap_send(session, pdu);
|
||||
|
||||
if (tid != COAP_INVALID_TID) {
|
||||
resp_wait = 1;
|
||||
wait_ms = COAP_DEFAULT_TIME_SEC * 1000;
|
||||
return;
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
} else {
|
||||
if (coap_get_data(received, &data_len, &data)) {
|
||||
printf("Received: %.*s\n", (int)data_len, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
clean_up:
|
||||
resp_wait = 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COAP_MBEDTLS_PKI
|
||||
|
||||
static int
|
||||
verify_cn_callback(const char *cn,
|
||||
const uint8_t *asn1_public_cert,
|
||||
size_t asn1_length,
|
||||
coap_session_t *session,
|
||||
unsigned depth,
|
||||
int validated,
|
||||
void *arg
|
||||
)
|
||||
{
|
||||
coap_log(LOG_INFO, "CN '%s' presented by server (%s)\n",
|
||||
cn, depth ? "CA" : "Certificate");
|
||||
return 1;
|
||||
}
|
||||
#endif /* CONFIG_COAP_MBEDTLS_PKI */
|
||||
|
||||
static void coap_example_client(void *p)
|
||||
{
|
||||
struct hostent *hp;
|
||||
coap_address_t dst_addr;
|
||||
static coap_uri_t uri;
|
||||
const char *server_uri = COAP_DEFAULT_DEMO_URI;
|
||||
char *phostname = NULL;
|
||||
|
||||
coap_set_log_level(EXAMPLE_COAP_LOG_DEFAULT_LEVEL);
|
||||
|
||||
while (1) {
|
||||
#define BUFSIZE 40
|
||||
unsigned char _buf[BUFSIZE];
|
||||
unsigned char *buf;
|
||||
size_t buflen;
|
||||
int res;
|
||||
coap_context_t *ctx = NULL;
|
||||
coap_session_t *session = NULL;
|
||||
coap_pdu_t *request = NULL;
|
||||
|
||||
optlist = NULL;
|
||||
if (coap_split_uri((const uint8_t *)server_uri, strlen(server_uri), &uri) == -1) {
|
||||
ESP_LOGE(TAG, "CoAP server uri error");
|
||||
break;
|
||||
}
|
||||
|
||||
if (uri.scheme == COAP_URI_SCHEME_COAPS && !coap_dtls_is_supported()) {
|
||||
ESP_LOGE(TAG, "MbedTLS (D)TLS Client Mode not configured");
|
||||
break;
|
||||
}
|
||||
if (uri.scheme == COAP_URI_SCHEME_COAPS_TCP && !coap_tls_is_supported()) {
|
||||
ESP_LOGE(TAG, "CoAP server uri coaps+tcp:// scheme is not supported");
|
||||
break;
|
||||
}
|
||||
|
||||
phostname = (char *)calloc(1, uri.host.length + 1);
|
||||
if (phostname == NULL) {
|
||||
ESP_LOGE(TAG, "calloc failed");
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(phostname, uri.host.s, uri.host.length);
|
||||
hp = gethostbyname(phostname);
|
||||
free(phostname);
|
||||
|
||||
if (hp == NULL) {
|
||||
ESP_LOGE(TAG, "DNS lookup failed");
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
free(phostname);
|
||||
continue;
|
||||
}
|
||||
char tmpbuf[INET6_ADDRSTRLEN];
|
||||
coap_address_init(&dst_addr);
|
||||
switch (hp->h_addrtype) {
|
||||
case AF_INET:
|
||||
dst_addr.addr.sin.sin_family = AF_INET;
|
||||
dst_addr.addr.sin.sin_port = htons(uri.port);
|
||||
memcpy(&dst_addr.addr.sin.sin_addr, hp->h_addr, sizeof(dst_addr.addr.sin.sin_addr));
|
||||
inet_ntop(AF_INET, &dst_addr.addr.sin.sin_addr, tmpbuf, sizeof(tmpbuf));
|
||||
ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", tmpbuf);
|
||||
break;
|
||||
case AF_INET6:
|
||||
dst_addr.addr.sin6.sin6_family = AF_INET6;
|
||||
dst_addr.addr.sin6.sin6_port = htons(uri.port);
|
||||
memcpy(&dst_addr.addr.sin6.sin6_addr, hp->h_addr, sizeof(dst_addr.addr.sin6.sin6_addr));
|
||||
inet_ntop(AF_INET6, &dst_addr.addr.sin6.sin6_addr, tmpbuf, sizeof(tmpbuf));
|
||||
ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", tmpbuf);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "DNS lookup response failed");
|
||||
goto clean_up;
|
||||
}
|
||||
|
||||
if (uri.path.length) {
|
||||
buflen = BUFSIZE;
|
||||
buf = _buf;
|
||||
res = coap_split_path(uri.path.s, uri.path.length, buf, &buflen);
|
||||
|
||||
while (res--) {
|
||||
coap_insert_optlist(&optlist,
|
||||
coap_new_optlist(COAP_OPTION_URI_PATH,
|
||||
coap_opt_length(buf),
|
||||
coap_opt_value(buf)));
|
||||
|
||||
buf += coap_opt_size(buf);
|
||||
}
|
||||
}
|
||||
|
||||
if (uri.query.length) {
|
||||
buflen = BUFSIZE;
|
||||
buf = _buf;
|
||||
res = coap_split_query(uri.query.s, uri.query.length, buf, &buflen);
|
||||
|
||||
while (res--) {
|
||||
coap_insert_optlist(&optlist,
|
||||
coap_new_optlist(COAP_OPTION_URI_QUERY,
|
||||
coap_opt_length(buf),
|
||||
coap_opt_value(buf)));
|
||||
|
||||
buf += coap_opt_size(buf);
|
||||
}
|
||||
}
|
||||
|
||||
ctx = coap_new_context(NULL);
|
||||
if (!ctx) {
|
||||
ESP_LOGE(TAG, "coap_new_context() failed");
|
||||
goto clean_up;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note that if the URI starts with just coap:// (not coaps://) the
|
||||
* session will still be plain text.
|
||||
*
|
||||
* coaps+tcp:// is NOT supported by the libcoap->mbedtls interface
|
||||
* so COAP_URI_SCHEME_COAPS_TCP will have failed in a test above,
|
||||
* but the code is left in for completeness.
|
||||
*/
|
||||
if (uri.scheme == COAP_URI_SCHEME_COAPS || uri.scheme == COAP_URI_SCHEME_COAPS_TCP) {
|
||||
#ifndef CONFIG_MBEDTLS_TLS_CLIENT
|
||||
ESP_LOGE(TAG, "MbedTLS (D)TLS Client Mode not configured");
|
||||
goto clean_up;
|
||||
#endif /* CONFIG_MBEDTLS_TLS_CLIENT */
|
||||
#ifdef CONFIG_COAP_MBEDTLS_PSK
|
||||
session = coap_new_client_session_psk(ctx, NULL, &dst_addr,
|
||||
uri.scheme == COAP_URI_SCHEME_COAPS ? COAP_PROTO_DTLS : COAP_PROTO_TLS,
|
||||
EXAMPLE_COAP_PSK_IDENTITY,
|
||||
(const uint8_t *)EXAMPLE_COAP_PSK_KEY,
|
||||
sizeof(EXAMPLE_COAP_PSK_KEY) - 1);
|
||||
#endif /* CONFIG_COAP_MBEDTLS_PSK */
|
||||
|
||||
#ifdef CONFIG_COAP_MBEDTLS_PKI
|
||||
unsigned int ca_pem_bytes = ca_pem_end - ca_pem_start;
|
||||
unsigned int client_crt_bytes = client_crt_end - client_crt_start;
|
||||
unsigned int client_key_bytes = client_key_end - client_key_start;
|
||||
coap_dtls_pki_t dtls_pki;
|
||||
static char client_sni[256];
|
||||
|
||||
memset (&dtls_pki, 0, sizeof(dtls_pki));
|
||||
dtls_pki.version = COAP_DTLS_PKI_SETUP_VERSION;
|
||||
if (ca_pem_bytes) {
|
||||
/*
|
||||
* Add in additional certificate checking.
|
||||
* This list of enabled can be tuned for the specific
|
||||
* requirements - see 'man coap_encryption'.
|
||||
*
|
||||
* Note: A list of root ca file can be setup separately using
|
||||
* coap_context_set_pki_root_cas(), but the below is used to
|
||||
* define what checking actually takes place.
|
||||
*/
|
||||
dtls_pki.verify_peer_cert = 1;
|
||||
dtls_pki.require_peer_cert = 1;
|
||||
dtls_pki.allow_self_signed = 1;
|
||||
dtls_pki.allow_expired_certs = 1;
|
||||
dtls_pki.cert_chain_validation = 1;
|
||||
dtls_pki.cert_chain_verify_depth = 2;
|
||||
dtls_pki.check_cert_revocation = 1;
|
||||
dtls_pki.allow_no_crl = 1;
|
||||
dtls_pki.allow_expired_crl = 1;
|
||||
dtls_pki.allow_bad_md_hash = 1;
|
||||
dtls_pki.allow_short_rsa_length = 1;
|
||||
dtls_pki.validate_cn_call_back = verify_cn_callback;
|
||||
dtls_pki.cn_call_back_arg = NULL;
|
||||
dtls_pki.validate_sni_call_back = NULL;
|
||||
dtls_pki.sni_call_back_arg = NULL;
|
||||
memset(client_sni, 0, sizeof(client_sni));
|
||||
if (uri.host.length) {
|
||||
memcpy(client_sni, uri.host.s, MIN(uri.host.length, sizeof(client_sni)));
|
||||
} else {
|
||||
memcpy(client_sni, "localhost", 9);
|
||||
}
|
||||
dtls_pki.client_sni = client_sni;
|
||||
}
|
||||
dtls_pki.pki_key.key_type = COAP_PKI_KEY_PEM_BUF;
|
||||
dtls_pki.pki_key.key.pem_buf.public_cert = client_crt_start;
|
||||
dtls_pki.pki_key.key.pem_buf.public_cert_len = client_crt_bytes;
|
||||
dtls_pki.pki_key.key.pem_buf.private_key = client_key_start;
|
||||
dtls_pki.pki_key.key.pem_buf.private_key_len = client_key_bytes;
|
||||
dtls_pki.pki_key.key.pem_buf.ca_cert = ca_pem_start;
|
||||
dtls_pki.pki_key.key.pem_buf.ca_cert_len = ca_pem_bytes;
|
||||
|
||||
session = coap_new_client_session_pki(ctx, NULL, &dst_addr,
|
||||
uri.scheme == COAP_URI_SCHEME_COAPS ? COAP_PROTO_DTLS : COAP_PROTO_TLS,
|
||||
&dtls_pki);
|
||||
#endif /* CONFIG_COAP_MBEDTLS_PKI */
|
||||
} else {
|
||||
session = coap_new_client_session(ctx, NULL, &dst_addr,
|
||||
uri.scheme == COAP_URI_SCHEME_COAP_TCP ? COAP_PROTO_TCP :
|
||||
COAP_PROTO_UDP);
|
||||
}
|
||||
if (!session) {
|
||||
ESP_LOGE(TAG, "coap_new_client_session() failed");
|
||||
goto clean_up;
|
||||
}
|
||||
|
||||
coap_register_response_handler(ctx, message_handler);
|
||||
|
||||
request = coap_new_pdu(session);
|
||||
if (!request) {
|
||||
ESP_LOGE(TAG, "coap_new_pdu() failed");
|
||||
goto clean_up;
|
||||
}
|
||||
request->type = COAP_MESSAGE_CON;
|
||||
request->tid = coap_new_message_id(session);
|
||||
request->code = COAP_REQUEST_GET;
|
||||
coap_add_optlist_pdu(request, &optlist);
|
||||
|
||||
resp_wait = 1;
|
||||
coap_send(session, request);
|
||||
|
||||
wait_ms = COAP_DEFAULT_TIME_SEC * 1000;
|
||||
|
||||
while (resp_wait) {
|
||||
int result = coap_run_once(ctx, wait_ms > 1000 ? 1000 : wait_ms);
|
||||
if (result >= 0) {
|
||||
if (result >= wait_ms) {
|
||||
ESP_LOGE(TAG, "select timeout");
|
||||
break;
|
||||
} else {
|
||||
wait_ms -= result;
|
||||
}
|
||||
}
|
||||
}
|
||||
clean_up:
|
||||
if (optlist) {
|
||||
coap_delete_optlist(optlist);
|
||||
optlist = NULL;
|
||||
}
|
||||
if (session) {
|
||||
coap_session_release(session);
|
||||
}
|
||||
if (ctx) {
|
||||
coap_free_context(ctx);
|
||||
}
|
||||
coap_cleanup();
|
||||
/*
|
||||
* change the following line to something like sleep(2)
|
||||
* if you want the request to continually be sent
|
||||
*/
|
||||
break;
|
||||
}
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_ERROR_CHECK( nvs_flash_init() );
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
xTaskCreate(coap_example_client, "coap", 8 * 1024, NULL, 5, NULL);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
# embed files from the "certs" directory as binary data symbols
|
||||
# in the app
|
||||
COMPONENT_EMBED_TXTFILES := certs/coap_ca.pem certs/coap_client.crt certs/coap_client.key
|
||||
@@ -0,0 +1,3 @@
|
||||
CONFIG_MBEDTLS_SSL_PROTO_DTLS=y
|
||||
CONFIG_MBEDTLS_PSK_MODES=y
|
||||
CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(coap_server)
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := coap_server
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
|
||||
# CoAP server example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
This CoAP server example is very simplified adaptation of one of the
|
||||
[libcoap](https://github.com/obgm/libcoap) examples.
|
||||
|
||||
CoAP server example will startup a daemon task, receive requests / data from CoAP client and transmit
|
||||
data to CoAP client.
|
||||
|
||||
If the incoming request requests the use of DTLS (connecting to port 5684), then the CoAP server will
|
||||
try to establish a DTLS session using the previously defined Pre-Shared Key (PSK) - which
|
||||
must be the same as the one that the CoAP client is using, or Public Key Infrastructure (PKI) where
|
||||
the PKI information must match as requested.
|
||||
|
||||
NOTE: Client sessions trying to use coaps+tcp:// are not currently supported, even though both
|
||||
libcoap and MbedTLS support it.
|
||||
|
||||
The Constrained Application Protocol (CoAP) is a specialized web transfer protocol for use with
|
||||
constrained nodes and constrained networks in the Internet of Things.
|
||||
The protocol is designed for machine-to-machine (M2M) applications such as smart energy and
|
||||
building automation.
|
||||
|
||||
Please refer to [RFC7252](https://www.rfc-editor.org/rfc/pdfrfc/rfc7252.txt.pdf) for more details.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
Example Connection Configuration --->
|
||||
* Set WiFi SSID under Example Configuration
|
||||
* Set WiFi Password under Example Configuration
|
||||
Example CoAP Client Configuration --->
|
||||
* If PSK, Set CoAP Preshared Key to use in connection to the server
|
||||
Component config --->
|
||||
CoAP Configuration --->
|
||||
* Set encryption method definition, PSK (default) or PKI
|
||||
* Enable CoAP debugging if required
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py build
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
current CoAP server would startup a daemon task,
|
||||
and the log is such as the following:
|
||||
|
||||
```
|
||||
...
|
||||
I (332) wifi: mode : sta (30:ae:a4:04:1b:7c)
|
||||
I (1672) wifi: n:11 0, o:1 0, ap:255 255, sta:11 0, prof:1
|
||||
I (1672) wifi: state: init -> auth (b0)
|
||||
I (1682) wifi: state: auth -> assoc (0)
|
||||
I (1692) wifi: state: assoc -> run (10)
|
||||
I (1692) wifi: connected with huawei_cw, channel 11
|
||||
I (1692) wifi: pm start, type: 1
|
||||
|
||||
I (2622) event: sta ip: 192.168.3.84, mask: 255.255.255.0, gw: 192.168.3.1
|
||||
I (2622) CoAP_server: Connected to AP
|
||||
...
|
||||
```
|
||||
|
||||
If a CoAP client queries the `/Espressif` resource, CoAP server will return `"Hello World!"`
|
||||
until a CoAP client does a PUT with different data.
|
||||
|
||||
## libcoap Documentation
|
||||
This can be found at https://libcoap.net/doc/reference/4.2.0/
|
||||
|
||||
## Troubleshooting
|
||||
* Please make sure CoAP client fetchs or puts data under path: `/Espressif` or
|
||||
fetches `/.well-known/core`
|
||||
|
||||
* CoAP logging can be enabled by running 'idf.py menuconfig -> Component config -> CoAP Configuration' and setting appropriate log level
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "coap_server_example_main.c"
|
||||
INCLUDE_DIRS "."
|
||||
EMBED_TXTFILES certs/coap_ca.pem certs/coap_server.crt certs/coap_server.key)
|
||||
@@ -0,0 +1,11 @@
|
||||
menu "Example CoAP Server Configuration"
|
||||
|
||||
config EXAMPLE_COAP_PSK_KEY
|
||||
string "Preshared Key (PSK) to used in the connection from the CoAP client"
|
||||
depends on COAP_MBEDTLS_PSK
|
||||
default "secret-key"
|
||||
help
|
||||
The Preshared Key to use to encrypt the communicatons. The same key must be
|
||||
used at both ends of the CoAP connection, and the CoaP client must request
|
||||
an URI prefixed with coaps:// instead of coap:// for DTLS to be used.
|
||||
endmenu
|
||||
@@ -0,0 +1,23 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID3DCCA0WgAwIBAgIJAMnlgL1czsmjMA0GCSqGSIb3DQEBCwUAMIGTMQswCQYD
|
||||
VQQGEwJGUjEPMA0GA1UECAwGUmFkaXVzMRIwEAYDVQQHDAlTb21ld2hlcmUxFTAT
|
||||
BgNVBAoMDEV4YW1wbGUgSW5jLjEgMB4GCSqGSIb3DQEJARYRYWRtaW5AZXhhbXBs
|
||||
ZS5jb20xJjAkBgNVBAMMHUV4YW1wbGUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4X
|
||||
DTE3MDYwNzA4MDY0OVoXDTI3MDYwNTA4MDY0OVowgZMxCzAJBgNVBAYTAkZSMQ8w
|
||||
DQYDVQQIDAZSYWRpdXMxEjAQBgNVBAcMCVNvbWV3aGVyZTEVMBMGA1UECgwMRXhh
|
||||
bXBsZSBJbmMuMSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBleGFtcGxlLmNvbTEmMCQG
|
||||
A1UEAwwdRXhhbXBsZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwgZ8wDQYJKoZIhvcN
|
||||
AQEBBQADgY0AMIGJAoGBALpWR23fn/TmHxsXsHdrydzPSd17fZkc71WsaicgQR66
|
||||
1tIVYb22UWGfj9KPM8THMsV74ew4ZkaQ39qvU0iuQIRrKARFHFok+vbaecgWMeWe
|
||||
vGIqdnmyB9gJYaFOKgtSkfXsu2ddsqdvLYwcDbczrq8X9yEXpN6mnxXeCcPG4F0p
|
||||
AgMBAAGjggE0MIIBMDAdBgNVHQ4EFgQUgigpdAUpONoDq0pQ3yfxrslCSpcwgcgG
|
||||
A1UdIwSBwDCBvYAUgigpdAUpONoDq0pQ3yfxrslCSpehgZmkgZYwgZMxCzAJBgNV
|
||||
BAYTAkZSMQ8wDQYDVQQIDAZSYWRpdXMxEjAQBgNVBAcMCVNvbWV3aGVyZTEVMBMG
|
||||
A1UECgwMRXhhbXBsZSBJbmMuMSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBleGFtcGxl
|
||||
LmNvbTEmMCQGA1UEAwwdRXhhbXBsZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCCQDJ
|
||||
5YC9XM7JozAMBgNVHRMEBTADAQH/MDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly93
|
||||
d3cuZXhhbXBsZS5jb20vZXhhbXBsZV9jYS5jcmwwDQYJKoZIhvcNAQELBQADgYEA
|
||||
euxOBPInSJRKAIseMxPmAabtAqKNslZSmpG4He3lkKt+HM3jfznUt3psmD7j1hFW
|
||||
S4l7KXzzajvaGYybDq5N9MqrDjhGn3VXZqOLMUNDL7OQq96TzgqsTBT1dmVSbNlt
|
||||
PQgiAeKAk3tmH4lRRi9MTBSyJ6I92JYcS5H6Bs4ZwCc=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,70 @@
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number: 47 (0x2f)
|
||||
Signature Algorithm: sha1WithRSAEncryption
|
||||
Issuer: C=FR, ST=Radius, L=Somewhere, O=Example Inc./emailAddress=admin@example.com, CN=Example Certificate Authority
|
||||
Validity
|
||||
Not Before: Jun 7 08:06:49 2017 GMT
|
||||
Not After : Jun 5 08:06:49 2027 GMT
|
||||
Subject: C=FR, ST=Radius, O=Example Inc., CN=Example Server Certificate/emailAddress=admin@example.com
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (2048 bit)
|
||||
Modulus:
|
||||
00:c9:d8:e2:e0:75:91:83:87:d8:c8:80:c6:20:4d:
|
||||
e9:14:24:30:98:33:53:fa:56:0e:ec:9a:43:7f:87:
|
||||
a9:22:94:26:06:c7:ac:b5:d9:ec:55:06:81:b7:0d:
|
||||
c9:24:51:49:fa:47:fb:4b:4e:fc:ed:75:8a:e1:28:
|
||||
32:bc:c5:e0:4c:45:c4:58:60:15:67:1e:6b:40:19:
|
||||
3f:f0:ab:92:61:92:2d:71:10:2e:f2:eb:bc:81:2f:
|
||||
5a:3b:74:ca:5f:fd:e0:ee:d1:d9:07:6a:6c:20:c0:
|
||||
07:88:b4:8b:0f:ad:1e:c9:4f:7c:11:98:37:89:15:
|
||||
de:24:b1:11:1a:7c:97:4a:cf:f3:c8:cb:79:9e:9c:
|
||||
c3:71:da:a6:94:97:f5:95:fd:61:06:44:e2:3f:12:
|
||||
43:0b:1d:33:48:91:d2:ce:4f:97:a1:ed:6a:30:c7:
|
||||
5d:98:b5:6e:0a:b7:4f:d9:03:ec:80:76:09:b0:40:
|
||||
a1:a1:af:ab:2a:59:c4:0f:56:22:bc:be:14:be:18:
|
||||
df:10:7d:5d:22:bf:e5:04:77:7a:75:6b:3e:eb:6d:
|
||||
20:a1:a7:60:d4:f1:87:9d:9f:60:b9:d3:db:2c:25:
|
||||
f4:91:4a:f1:d2:40:e5:a1:10:88:a0:41:5a:98:40:
|
||||
ca:15:d7:e3:e6:3e:c0:6a:d5:46:b2:b4:90:b4:ae:
|
||||
3b:e3
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Extended Key Usage:
|
||||
TLS Web Server Authentication
|
||||
X509v3 CRL Distribution Points:
|
||||
|
||||
Full Name:
|
||||
URI:http://www.example.com/example_ca.crl
|
||||
|
||||
Signature Algorithm: sha1WithRSAEncryption
|
||||
a4:25:21:51:0b:22:6c:63:8d:a9:c1:4f:04:33:69:79:34:f0:
|
||||
36:dd:8f:6a:27:5f:07:a2:1d:ef:8b:f0:96:e6:e7:a3:b8:3b:
|
||||
85:5e:3f:26:43:8a:8e:95:58:9c:a6:db:9c:51:bf:ea:53:16:
|
||||
3e:c1:a8:11:1a:c6:cf:0e:a1:17:18:64:d2:05:f1:c0:9c:a6:
|
||||
2b:16:c4:29:54:03:d2:17:bd:15:74:d6:ad:8a:8f:2d:cc:27:
|
||||
3b:88:88:f2:ea:d0:a2:cb:e9:42:57:df:26:9f:8a:a2:02:2f:
|
||||
35:b6:19:1d:26:43:44:af:12:4b:bc:b9:84:50:02:fd:1d:fa:
|
||||
50:e8
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDWTCCAsKgAwIBAgIBLzANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UEBhMCRlIx
|
||||
DzANBgNVBAgMBlJhZGl1czESMBAGA1UEBwwJU29tZXdoZXJlMRUwEwYDVQQKDAxF
|
||||
eGFtcGxlIEluYy4xIDAeBgkqhkiG9w0BCQEWEWFkbWluQGV4YW1wbGUuY29tMSYw
|
||||
JAYDVQQDDB1FeGFtcGxlIENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0xNzA2MDcw
|
||||
ODA2NDlaFw0yNzA2MDUwODA2NDlaMHwxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZS
|
||||
YWRpdXMxFTATBgNVBAoMDEV4YW1wbGUgSW5jLjEjMCEGA1UEAwwaRXhhbXBsZSBT
|
||||
ZXJ2ZXIgQ2VydGlmaWNhdGUxIDAeBgkqhkiG9w0BCQEWEWFkbWluQGV4YW1wbGUu
|
||||
Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAydji4HWRg4fYyIDG
|
||||
IE3pFCQwmDNT+lYO7JpDf4epIpQmBsestdnsVQaBtw3JJFFJ+kf7S0787XWK4Sgy
|
||||
vMXgTEXEWGAVZx5rQBk/8KuSYZItcRAu8uu8gS9aO3TKX/3g7tHZB2psIMAHiLSL
|
||||
D60eyU98EZg3iRXeJLERGnyXSs/zyMt5npzDcdqmlJf1lf1hBkTiPxJDCx0zSJHS
|
||||
zk+Xoe1qMMddmLVuCrdP2QPsgHYJsEChoa+rKlnED1YivL4UvhjfEH1dIr/lBHd6
|
||||
dWs+620goadg1PGHnZ9gudPbLCX0kUrx0kDloRCIoEFamEDKFdfj5j7AatVGsrSQ
|
||||
tK474wIDAQABo08wTTATBgNVHSUEDDAKBggrBgEFBQcDATA2BgNVHR8ELzAtMCug
|
||||
KaAnhiVodHRwOi8vd3d3LmV4YW1wbGUuY29tL2V4YW1wbGVfY2EuY3JsMA0GCSqG
|
||||
SIb3DQEBBQUAA4GBAKQlIVELImxjjanBTwQzaXk08Dbdj2onXweiHe+L8Jbm56O4
|
||||
O4VePyZDio6VWJym25xRv+pTFj7BqBEaxs8OoRcYZNIF8cCcpisWxClUA9IXvRV0
|
||||
1q2Kjy3MJzuIiPLq0KLL6UJX3yafiqICLzW2GR0mQ0SvEku8uYRQAv0d+lDo
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAydji4HWRg4fYyIDGIE3pFCQwmDNT+lYO7JpDf4epIpQmBses
|
||||
tdnsVQaBtw3JJFFJ+kf7S0787XWK4SgyvMXgTEXEWGAVZx5rQBk/8KuSYZItcRAu
|
||||
8uu8gS9aO3TKX/3g7tHZB2psIMAHiLSLD60eyU98EZg3iRXeJLERGnyXSs/zyMt5
|
||||
npzDcdqmlJf1lf1hBkTiPxJDCx0zSJHSzk+Xoe1qMMddmLVuCrdP2QPsgHYJsECh
|
||||
oa+rKlnED1YivL4UvhjfEH1dIr/lBHd6dWs+620goadg1PGHnZ9gudPbLCX0kUrx
|
||||
0kDloRCIoEFamEDKFdfj5j7AatVGsrSQtK474wIDAQABAoIBAQC2kGDEPBJdMSW2
|
||||
VCLfXRiPixwYzXQLXIMrJWwfkQg9qlmqkDd6U50aWkRA2UswegW7RhfYSZ0i+cmf
|
||||
VMhvTVpOIlwwwtcY6b5/v1bBy60eaySGuuh79xQMlFO8qynQIMStvUfbGTqrdIRb
|
||||
9VBB4YeS9T12fILejtTZwv2BQ2dj1Y1SCay6Ri85UzJqSClRKgHISybvVdLNjPvP
|
||||
0TRFBr57zyjL6WE8teKiKchzQko2u86No5uBCdKGsrAkrsdcR0YqlM/pZxd3VKNm
|
||||
+eny0k+dZZlvcPxzkzP4hEp9+Rw5rP9/s3s/cCwvuuC5JO32ATBWKCbTvPv/XPDb
|
||||
MdSJtOshAoGBAPzk0eswkcbFYtpnpBNmBAr1dtAdW1lfjUI2ucMMwt7Wns0P/tt+
|
||||
gq6Hi1wTaGP0l/dIECgeHwjtWj31ZJjQtFJ1y/kafxo4o9cA8vCydpdvSZaldAfg
|
||||
sbLlDTDYzEpelaDIbNQBBXFoC5U9JlBhBsIFCL5Z8ZuIeFPsb7t5wwuHAoGBAMxT
|
||||
jyWfNm1uNxp1xgCnrRsLPQPVnURrSFAqcHrECqRu3F7sozTN7q/cZViemxPvVDGQ
|
||||
p9c+9bHwaYvW4trO5qDHJ++gGwm5L52bMAY1VUfeTt67fqrey43XpdmzcTX1V9Uj
|
||||
QWawPUCSDzFjL1MjfCIejtyYf5ash53vj+T8r/vFAoGAA/OPVB1uKazr3n3AEo2F
|
||||
gqZTNO1AgCT+EArK3EFWyiSQVqPpV4SihheYFdg3yVgJB9QYbIgL9BfBUTaEW97m
|
||||
8mLkzP+c/Mvlw3ZAVYJ0V+llPPVY2saoACOUES9SAdd4fwqiqK1baGo3xB0wfBEI
|
||||
CgAKIu9E1ylKuAT5ufQtGAECgYEAtP/kU5h5N3El4QupTdU7VDSdZTMqsHw0v8cI
|
||||
gsf9AXKvRmtrnBA8u46KPHmruHoO5CVXeSZtsaXdaaH+rYQQ6yXg67WxnehtFLlv
|
||||
TmCaXiLBTS9cYvMf8FOyuGnsBLeEietEOTov2G5KhR5uwsAxa2wUc7endor5S9/2
|
||||
YQuyvV0CgYALbiFpILd5l1ip65eE6JdA3hfttUbV2j2NSW12ej69vqbeOfaSgNse
|
||||
uYCcXFsBbQPhNPwA+4d1oCe8SyXZg1f7gE812z2Tyr/3vdVnNZlitoxhsHmGiyS7
|
||||
gZdaTYCb78l9z0EBdaCVvA16owEle4SR6f9eCwzSI0WPOUra+x/hrA==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -0,0 +1,319 @@
|
||||
/* CoAP server 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* WARNING
|
||||
* libcoap is not multi-thread safe, so only this thread must make any coap_*()
|
||||
* calls. Any external (to this thread) data transmitted in/out via libcoap
|
||||
* therefore has to be passed in/out by xQueue*() via this thread.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#if 1
|
||||
/* Needed until coap_dtls.h becomes a part of libcoap proper */
|
||||
#include "libcoap.h"
|
||||
#include "coap_dtls.h"
|
||||
#endif
|
||||
#include "coap.h"
|
||||
|
||||
/* The examples use simple Pre-Shared-Key configuration that you can set via
|
||||
'make menuconfig'.
|
||||
|
||||
If you'd rather not, just change the below entries to strings with
|
||||
the config you want - ie #define EXAMPLE_COAP_PSK_KEY "some-agreed-preshared-key"
|
||||
|
||||
Note: PSK will only be used if the URI is prefixed with coaps://
|
||||
instead of coap:// and the PSK must be one that the server supports
|
||||
(potentially associated with the IDENTITY)
|
||||
*/
|
||||
#define EXAMPLE_COAP_PSK_KEY CONFIG_EXAMPLE_COAP_PSK_KEY
|
||||
|
||||
/* The examples use CoAP Logging Level that
|
||||
you can set via 'make menuconfig'.
|
||||
|
||||
If you'd rather not, just change the below entry to a value
|
||||
that is between 0 and 7 with
|
||||
the config you want - ie #define EXAMPLE_COAP_LOG_DEFAULT_LEVEL 7
|
||||
*/
|
||||
#define EXAMPLE_COAP_LOG_DEFAULT_LEVEL CONFIG_COAP_LOG_DEFAULT_LEVEL
|
||||
|
||||
const static char *TAG = "CoAP_server";
|
||||
|
||||
static char espressif_data[100];
|
||||
static int espressif_data_len = 0;
|
||||
|
||||
#ifdef CONFIG_COAP_MBEDTLS_PKI
|
||||
/* CA cert, taken from coap_ca.pem
|
||||
Server cert, taken from coap_server.crt
|
||||
Server key, taken from coap_server.key
|
||||
|
||||
The PEM, CRT and KEY file are examples taken from the wpa2 enterprise
|
||||
example.
|
||||
|
||||
To embed it in the app binary, the PEM, CRT and KEY file is named
|
||||
in the component.mk COMPONENT_EMBED_TXTFILES variable.
|
||||
*/
|
||||
extern uint8_t ca_pem_start[] asm("_binary_coap_ca_pem_start");
|
||||
extern uint8_t ca_pem_end[] asm("_binary_coap_ca_pem_end");
|
||||
extern uint8_t server_crt_start[] asm("_binary_coap_server_crt_start");
|
||||
extern uint8_t server_crt_end[] asm("_binary_coap_server_crt_end");
|
||||
extern uint8_t server_key_start[] asm("_binary_coap_server_key_start");
|
||||
extern uint8_t server_key_end[] asm("_binary_coap_server_key_end");
|
||||
#endif /* CONFIG_COAP_MBEDTLS_PKI */
|
||||
|
||||
#define INITIAL_DATA "Hello World!"
|
||||
|
||||
/*
|
||||
* The resource handler
|
||||
*/
|
||||
static void
|
||||
hnd_espressif_get(coap_context_t *ctx, coap_resource_t *resource,
|
||||
coap_session_t *session,
|
||||
coap_pdu_t *request, coap_binary_t *token,
|
||||
coap_string_t *query, coap_pdu_t *response)
|
||||
{
|
||||
coap_add_data_blocked_response(resource, session, request, response, token,
|
||||
COAP_MEDIATYPE_TEXT_PLAIN, 0,
|
||||
(size_t)espressif_data_len,
|
||||
(const u_char *)espressif_data);
|
||||
}
|
||||
|
||||
static void
|
||||
hnd_espressif_put(coap_context_t *ctx,
|
||||
coap_resource_t *resource,
|
||||
coap_session_t *session,
|
||||
coap_pdu_t *request,
|
||||
coap_binary_t *token,
|
||||
coap_string_t *query,
|
||||
coap_pdu_t *response)
|
||||
{
|
||||
size_t size;
|
||||
unsigned char *data;
|
||||
|
||||
coap_resource_notify_observers(resource, NULL);
|
||||
|
||||
if (strcmp (espressif_data, INITIAL_DATA) == 0) {
|
||||
response->code = COAP_RESPONSE_CODE(201);
|
||||
} else {
|
||||
response->code = COAP_RESPONSE_CODE(204);
|
||||
}
|
||||
|
||||
/* coap_get_data() sets size to 0 on error */
|
||||
(void)coap_get_data(request, &size, &data);
|
||||
|
||||
if (size == 0) { /* re-init */
|
||||
snprintf(espressif_data, sizeof(espressif_data), INITIAL_DATA);
|
||||
espressif_data_len = strlen(espressif_data);
|
||||
} else {
|
||||
espressif_data_len = size > sizeof (espressif_data) ? sizeof (espressif_data) : size;
|
||||
memcpy (espressif_data, data, espressif_data_len);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
hnd_espressif_delete(coap_context_t *ctx,
|
||||
coap_resource_t *resource,
|
||||
coap_session_t *session,
|
||||
coap_pdu_t *request,
|
||||
coap_binary_t *token,
|
||||
coap_string_t *query,
|
||||
coap_pdu_t *response)
|
||||
{
|
||||
coap_resource_notify_observers(resource, NULL);
|
||||
snprintf(espressif_data, sizeof(espressif_data), INITIAL_DATA);
|
||||
espressif_data_len = strlen(espressif_data);
|
||||
response->code = COAP_RESPONSE_CODE(202);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COAP_MBEDTLS_PKI
|
||||
|
||||
static int
|
||||
verify_cn_callback(const char *cn,
|
||||
const uint8_t *asn1_public_cert,
|
||||
size_t asn1_length,
|
||||
coap_session_t *session,
|
||||
unsigned depth,
|
||||
int validated,
|
||||
void *arg
|
||||
)
|
||||
{
|
||||
coap_log(LOG_INFO, "CN '%s' presented by server (%s)\n",
|
||||
cn, depth ? "CA" : "Certificate");
|
||||
return 1;
|
||||
}
|
||||
#endif /* CONFIG_COAP_MBEDTLS_PKI */
|
||||
|
||||
static void coap_example_server(void *p)
|
||||
{
|
||||
coap_context_t *ctx = NULL;
|
||||
coap_address_t serv_addr;
|
||||
coap_resource_t *resource = NULL;
|
||||
|
||||
snprintf(espressif_data, sizeof(espressif_data), INITIAL_DATA);
|
||||
espressif_data_len = strlen(espressif_data);
|
||||
coap_set_log_level(EXAMPLE_COAP_LOG_DEFAULT_LEVEL);
|
||||
|
||||
while (1) {
|
||||
coap_endpoint_t *ep = NULL;
|
||||
unsigned wait_ms;
|
||||
|
||||
/* Prepare the CoAP server socket */
|
||||
coap_address_init(&serv_addr);
|
||||
serv_addr.addr.sin.sin_family = AF_INET;
|
||||
serv_addr.addr.sin.sin_addr.s_addr = INADDR_ANY;
|
||||
serv_addr.addr.sin.sin_port = htons(COAP_DEFAULT_PORT);
|
||||
|
||||
ctx = coap_new_context(NULL);
|
||||
if (!ctx) {
|
||||
ESP_LOGE(TAG, "coap_new_context() failed");
|
||||
continue;
|
||||
}
|
||||
#ifdef CONFIG_COAP_MBEDTLS_PSK
|
||||
/* Need PSK setup before we set up endpoints */
|
||||
coap_context_set_psk(ctx, "CoAP",
|
||||
(const uint8_t *)EXAMPLE_COAP_PSK_KEY,
|
||||
sizeof(EXAMPLE_COAP_PSK_KEY) - 1);
|
||||
#endif /* CONFIG_COAP_MBEDTLS_PSK */
|
||||
|
||||
#ifdef CONFIG_COAP_MBEDTLS_PKI
|
||||
unsigned int ca_pem_bytes = ca_pem_end - ca_pem_start;
|
||||
unsigned int server_crt_bytes = server_crt_end - server_crt_start;
|
||||
unsigned int server_key_bytes = server_key_end - server_key_start;
|
||||
coap_dtls_pki_t dtls_pki;
|
||||
|
||||
memset (&dtls_pki, 0, sizeof(dtls_pki));
|
||||
dtls_pki.version = COAP_DTLS_PKI_SETUP_VERSION;
|
||||
if (ca_pem_bytes) {
|
||||
/*
|
||||
* Add in additional certificate checking.
|
||||
* This list of enabled can be tuned for the specific
|
||||
* requirements - see 'man coap_encryption'.
|
||||
*
|
||||
* Note: A list of root ca file can be setup separately using
|
||||
* coap_context_set_pki_root_cas(), but the below is used to
|
||||
* define what checking actually takes place.
|
||||
*/
|
||||
dtls_pki.verify_peer_cert = 1;
|
||||
dtls_pki.require_peer_cert = 1;
|
||||
dtls_pki.allow_self_signed = 1;
|
||||
dtls_pki.allow_expired_certs = 1;
|
||||
dtls_pki.cert_chain_validation = 1;
|
||||
dtls_pki.cert_chain_verify_depth = 2;
|
||||
dtls_pki.check_cert_revocation = 1;
|
||||
dtls_pki.allow_no_crl = 1;
|
||||
dtls_pki.allow_expired_crl = 1;
|
||||
dtls_pki.allow_bad_md_hash = 1;
|
||||
dtls_pki.allow_short_rsa_length = 1;
|
||||
dtls_pki.validate_cn_call_back = verify_cn_callback;
|
||||
dtls_pki.cn_call_back_arg = NULL;
|
||||
dtls_pki.validate_sni_call_back = NULL;
|
||||
dtls_pki.sni_call_back_arg = NULL;
|
||||
}
|
||||
dtls_pki.pki_key.key_type = COAP_PKI_KEY_PEM_BUF;
|
||||
dtls_pki.pki_key.key.pem_buf.public_cert = server_crt_start;
|
||||
dtls_pki.pki_key.key.pem_buf.public_cert_len = server_crt_bytes;
|
||||
dtls_pki.pki_key.key.pem_buf.private_key = server_key_start;
|
||||
dtls_pki.pki_key.key.pem_buf.private_key_len = server_key_bytes;
|
||||
dtls_pki.pki_key.key.pem_buf.ca_cert = ca_pem_start;
|
||||
dtls_pki.pki_key.key.pem_buf.ca_cert_len = ca_pem_bytes;
|
||||
|
||||
coap_context_set_pki(ctx, &dtls_pki);
|
||||
#endif /* CONFIG_COAP_MBEDTLS_PKI */
|
||||
|
||||
ep = coap_new_endpoint(ctx, &serv_addr, COAP_PROTO_UDP);
|
||||
if (!ep) {
|
||||
ESP_LOGE(TAG, "udp: coap_new_endpoint() failed");
|
||||
goto clean_up;
|
||||
}
|
||||
ep = coap_new_endpoint(ctx, &serv_addr, COAP_PROTO_TCP);
|
||||
if (!ep) {
|
||||
ESP_LOGE(TAG, "tcp: coap_new_endpoint() failed");
|
||||
goto clean_up;
|
||||
}
|
||||
#if defined(CONFIG_COAP_MBEDTLS_PSK) || defined(CONFIG_COAP_MBEDTLS_PKI)
|
||||
if (coap_dtls_is_supported()) {
|
||||
#ifndef CONFIG_MBEDTLS_TLS_SERVER
|
||||
/* This is not critical as unencrypted support is still available */
|
||||
ESP_LOGI(TAG, "MbedTLS (D)TLS Server Mode not configured");
|
||||
#else /* CONFIG_MBEDTLS_TLS_SERVER */
|
||||
serv_addr.addr.sin.sin_port = htons(COAPS_DEFAULT_PORT);
|
||||
ep = coap_new_endpoint(ctx, &serv_addr, COAP_PROTO_DTLS);
|
||||
if (!ep) {
|
||||
ESP_LOGE(TAG, "dtls: coap_new_endpoint() failed");
|
||||
goto clean_up;
|
||||
}
|
||||
#endif /* CONFIG_MBEDTLS_TLS_SERVER */
|
||||
} else {
|
||||
/* This is not critical as unencrypted support is still available */
|
||||
ESP_LOGI(TAG, "MbedTLS (D)TLS Server Mode not configured");
|
||||
}
|
||||
#endif /* CONFIG_COAP_MBEDTLS_PSK CONFIG_COAP_MBEDTLS_PKI */
|
||||
resource = coap_resource_init(coap_make_str_const("Espressif"), 0);
|
||||
if (!resource) {
|
||||
ESP_LOGE(TAG, "coap_resource_init() failed");
|
||||
goto clean_up;
|
||||
}
|
||||
coap_register_handler(resource, COAP_REQUEST_GET, hnd_espressif_get);
|
||||
coap_register_handler(resource, COAP_REQUEST_PUT, hnd_espressif_put);
|
||||
coap_register_handler(resource, COAP_REQUEST_DELETE, hnd_espressif_delete);
|
||||
/* We possibly want to Observe the GETs */
|
||||
coap_resource_set_get_observable(resource, 1);
|
||||
coap_add_resource(ctx, resource);
|
||||
|
||||
wait_ms = COAP_RESOURCE_CHECK_TIME * 1000;
|
||||
|
||||
while (1) {
|
||||
int result = coap_run_once(ctx, wait_ms);
|
||||
if (result < 0) {
|
||||
break;
|
||||
} else if (result && (unsigned)result < wait_ms) {
|
||||
/* decrement if there is a result wait time returned */
|
||||
wait_ms -= result;
|
||||
}
|
||||
if (result) {
|
||||
/* result must have been >= wait_ms, so reset wait_ms */
|
||||
wait_ms = COAP_RESOURCE_CHECK_TIME * 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
clean_up:
|
||||
coap_free_context(ctx);
|
||||
coap_cleanup();
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_ERROR_CHECK( nvs_flash_init() );
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
xTaskCreate(coap_example_server, "coap", 8 * 1024, NULL, 5, NULL);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
# embed files from the "certs" directory as binary data symbols
|
||||
# in the app
|
||||
COMPONENT_EMBED_TXTFILES := certs/coap_ca.pem certs/coap_server.crt certs/coap_server.key
|
||||
@@ -0,0 +1,3 @@
|
||||
CONFIG_MBEDTLS_SSL_PROTO_DTLS=y
|
||||
CONFIG_MBEDTLS_PSK_MODES=y
|
||||
CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y
|
||||
@@ -0,0 +1,10 @@
|
||||
# The following five 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)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(esp-http-client-example)
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := esp-http-client-example
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# ESP HTTP Client Example
|
||||
|
||||
See the README.md file in the upper level 'examples' directory for more information about examples.
|
||||
@@ -0,0 +1,67 @@
|
||||
import re
|
||||
import os
|
||||
|
||||
import ttfw_idf
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_EthKitV1")
|
||||
def test_examples_protocol_esp_http_client(env, extra_data):
|
||||
"""
|
||||
steps: |
|
||||
1. join AP
|
||||
2. Send HTTP request to httpbin.org
|
||||
"""
|
||||
dut1 = env.get_dut("esp_http_client", "examples/protocols/esp_http_client", dut_class=ttfw_idf.ESP32DUT)
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, "esp-http-client-example.bin")
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("esp_http_client_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("esp_http_client_bin_size", bin_size // 1024, dut1.TARGET)
|
||||
# start test
|
||||
dut1.start_app()
|
||||
dut1.expect("Connected to AP, begin http example", timeout=30)
|
||||
dut1.expect(re.compile(r"HTTP GET Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTP POST Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTP PUT Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTP PATCH Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTP DELETE Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTP HEAD Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTP Basic Auth Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTP Basic Auth redirect Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTP Digest Auth Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTPS Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTP chunk encoding Status = 200, content_length = (-?\d)"))
|
||||
# content-len for chunked encoding is typically -1, could be a positive length in some cases
|
||||
dut1.expect(re.compile(r"HTTP Stream reader Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"Last esp error code: 0x8001"))
|
||||
dut1.expect("Finish http example")
|
||||
|
||||
# test mbedtls dynamic resource
|
||||
dut1 = env.get_dut("esp_http_client", "examples/protocols/esp_http_client", dut_class=ttfw_idf.ESP32DUT, app_config_name='ssldyn')
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, "esp-http-client-example.bin")
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("esp_http_client_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("esp_http_client_bin_size", bin_size // 1024, dut1.TARGET)
|
||||
# start test
|
||||
dut1.start_app()
|
||||
dut1.expect("Connected to AP, begin http example", timeout=30)
|
||||
dut1.expect(re.compile(r"HTTP GET Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTP POST Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTP PUT Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTP PATCH Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTP DELETE Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTP HEAD Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTP Basic Auth Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTP Basic Auth redirect Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTP Digest Auth Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTPS Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"HTTP chunk encoding Status = 200, content_length = (-?\d)"))
|
||||
# content-len for chunked encoding is typically -1, could be a positive length in some cases
|
||||
dut1.expect(re.compile(r"HTTP Stream reader Status = 200, content_length = (\d)"))
|
||||
dut1.expect(re.compile(r"Last esp error code: 0x8001"))
|
||||
dut1.expect("Finish http example")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_protocol_esp_http_client()
|
||||
@@ -0,0 +1,6 @@
|
||||
# Embed the server root certificate into the final binary
|
||||
#
|
||||
# (If this was a component, we would set COMPONENT_EMBED_TXTFILES here.)
|
||||
idf_component_register(SRCS "esp_http_client_example.c"
|
||||
INCLUDE_DIRS "."
|
||||
EMBED_TXTFILES howsmyssl_com_root_cert.pem)
|
||||
@@ -0,0 +1,8 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
# embed files from the "certs" directory as binary data symbols
|
||||
# in the app
|
||||
COMPONENT_EMBED_TXTFILES := howsmyssl_com_root_cert.pem
|
||||
@@ -0,0 +1,666 @@
|
||||
/* ESP HTTP Client 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 <stdlib.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_netif.h"
|
||||
#include "protocol_examples_common.h"
|
||||
#include "esp_tls.h"
|
||||
|
||||
#include "esp_http_client.h"
|
||||
|
||||
#define MAX_HTTP_RECV_BUFFER 512
|
||||
#define MAX_HTTP_OUTPUT_BUFFER 2048
|
||||
static const char *TAG = "HTTP_CLIENT";
|
||||
|
||||
/* Root cert for howsmyssl.com, taken from howsmyssl_com_root_cert.pem
|
||||
|
||||
The PEM file was extracted from the output of this command:
|
||||
openssl s_client -showcerts -connect www.howsmyssl.com:443 </dev/null
|
||||
|
||||
The CA root cert is the last cert given in the chain of certs.
|
||||
|
||||
To embed it in the app binary, the PEM file is named
|
||||
in the component.mk COMPONENT_EMBED_TXTFILES variable.
|
||||
*/
|
||||
extern const char howsmyssl_com_root_cert_pem_start[] asm("_binary_howsmyssl_com_root_cert_pem_start");
|
||||
extern const char howsmyssl_com_root_cert_pem_end[] asm("_binary_howsmyssl_com_root_cert_pem_end");
|
||||
|
||||
esp_err_t _http_event_handler(esp_http_client_event_t *evt)
|
||||
{
|
||||
static char *output_buffer; // Buffer to store response of http request from event handler
|
||||
static int output_len; // Stores number of bytes read
|
||||
switch(evt->event_id) {
|
||||
case HTTP_EVENT_ERROR:
|
||||
ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
|
||||
break;
|
||||
case HTTP_EVENT_ON_CONNECTED:
|
||||
ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
|
||||
break;
|
||||
case HTTP_EVENT_HEADER_SENT:
|
||||
ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
|
||||
break;
|
||||
case HTTP_EVENT_ON_HEADER:
|
||||
ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
|
||||
break;
|
||||
case HTTP_EVENT_ON_DATA:
|
||||
ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
|
||||
/*
|
||||
* Check for chunked encoding is added as the URL for chunked encoding used in this example returns binary data.
|
||||
* However, event handler can also be used in case chunked encoding is used.
|
||||
*/
|
||||
if (!esp_http_client_is_chunked_response(evt->client)) {
|
||||
// If user_data buffer is configured, copy the response into the buffer
|
||||
if (evt->user_data) {
|
||||
memcpy(evt->user_data + output_len, evt->data, evt->data_len);
|
||||
} else {
|
||||
if (output_buffer == NULL) {
|
||||
output_buffer = (char *) malloc(esp_http_client_get_content_length(evt->client));
|
||||
output_len = 0;
|
||||
if (output_buffer == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory for output buffer");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
memcpy(output_buffer + output_len, evt->data, evt->data_len);
|
||||
}
|
||||
output_len += evt->data_len;
|
||||
}
|
||||
|
||||
break;
|
||||
case HTTP_EVENT_ON_FINISH:
|
||||
ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
|
||||
if (output_buffer != NULL) {
|
||||
// Response is accumulated in output_buffer. Uncomment the below line to print the accumulated response
|
||||
// ESP_LOG_BUFFER_HEX(TAG, output_buffer, output_len);
|
||||
free(output_buffer);
|
||||
output_buffer = NULL;
|
||||
output_len = 0;
|
||||
}
|
||||
break;
|
||||
case HTTP_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
|
||||
int mbedtls_err = 0;
|
||||
esp_err_t err = esp_tls_get_and_clear_last_error(evt->data, &mbedtls_err, NULL);
|
||||
if (err != 0) {
|
||||
if (output_buffer != NULL) {
|
||||
free(output_buffer);
|
||||
output_buffer = NULL;
|
||||
output_len = 0;
|
||||
}
|
||||
ESP_LOGI(TAG, "Last esp error code: 0x%x", err);
|
||||
ESP_LOGI(TAG, "Last mbedtls failure: 0x%x", mbedtls_err);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void http_rest_with_url(void)
|
||||
{
|
||||
char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};
|
||||
/**
|
||||
* NOTE: All the configuration parameters for http_client must be spefied either in URL or as host and path parameters.
|
||||
* If host and path parameters are not set, query parameter will be ignored. In such cases,
|
||||
* query parameter should be specified in URL.
|
||||
*
|
||||
* If URL as well as host and path parameters are specified, values of host and path will be considered.
|
||||
*/
|
||||
esp_http_client_config_t config = {
|
||||
.host = "httpbin.org",
|
||||
.path = "/get",
|
||||
.query = "esp",
|
||||
.event_handler = _http_event_handler,
|
||||
.user_data = local_response_buffer, // Pass address of local buffer to get response
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
|
||||
// GET
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "HTTP GET request failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
ESP_LOG_BUFFER_HEX(TAG, local_response_buffer, strlen(local_response_buffer));
|
||||
|
||||
// POST
|
||||
const char *post_data = "{\"field1\":\"value1\"}";
|
||||
esp_http_client_set_url(client, "http://httpbin.org/post");
|
||||
esp_http_client_set_method(client, HTTP_METHOD_POST);
|
||||
esp_http_client_set_header(client, "Content-Type", "application/json");
|
||||
esp_http_client_set_post_field(client, post_data, strlen(post_data));
|
||||
err = esp_http_client_perform(client);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP POST Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "HTTP POST request failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
//PUT
|
||||
esp_http_client_set_url(client, "http://httpbin.org/put");
|
||||
esp_http_client_set_method(client, HTTP_METHOD_PUT);
|
||||
err = esp_http_client_perform(client);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP PUT Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "HTTP PUT request failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
//PATCH
|
||||
esp_http_client_set_url(client, "http://httpbin.org/patch");
|
||||
esp_http_client_set_method(client, HTTP_METHOD_PATCH);
|
||||
esp_http_client_set_post_field(client, NULL, 0);
|
||||
err = esp_http_client_perform(client);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP PATCH Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "HTTP PATCH request failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
//DELETE
|
||||
esp_http_client_set_url(client, "http://httpbin.org/delete");
|
||||
esp_http_client_set_method(client, HTTP_METHOD_DELETE);
|
||||
err = esp_http_client_perform(client);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP DELETE Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "HTTP DELETE request failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
//HEAD
|
||||
esp_http_client_set_url(client, "http://httpbin.org/get");
|
||||
esp_http_client_set_method(client, HTTP_METHOD_HEAD);
|
||||
err = esp_http_client_perform(client);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP HEAD Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "HTTP HEAD request failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
|
||||
static void http_rest_with_hostname_path(void)
|
||||
{
|
||||
esp_http_client_config_t config = {
|
||||
.host = "httpbin.org",
|
||||
.path = "/get",
|
||||
.transport_type = HTTP_TRANSPORT_OVER_TCP,
|
||||
.event_handler = _http_event_handler,
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
|
||||
// GET
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "HTTP GET request failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// POST
|
||||
const char *post_data = "field1=value1&field2=value2";
|
||||
esp_http_client_set_url(client, "/post");
|
||||
esp_http_client_set_method(client, HTTP_METHOD_POST);
|
||||
esp_http_client_set_post_field(client, post_data, strlen(post_data));
|
||||
err = esp_http_client_perform(client);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP POST Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "HTTP POST request failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
//PUT
|
||||
esp_http_client_set_url(client, "/put");
|
||||
esp_http_client_set_method(client, HTTP_METHOD_PUT);
|
||||
err = esp_http_client_perform(client);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP PUT Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "HTTP PUT request failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
//PATCH
|
||||
esp_http_client_set_url(client, "/patch");
|
||||
esp_http_client_set_method(client, HTTP_METHOD_PATCH);
|
||||
esp_http_client_set_post_field(client, NULL, 0);
|
||||
err = esp_http_client_perform(client);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP PATCH Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "HTTP PATCH request failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
//DELETE
|
||||
esp_http_client_set_url(client, "/delete");
|
||||
esp_http_client_set_method(client, HTTP_METHOD_DELETE);
|
||||
err = esp_http_client_perform(client);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP DELETE Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "HTTP DELETE request failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
//HEAD
|
||||
esp_http_client_set_url(client, "/get");
|
||||
esp_http_client_set_method(client, HTTP_METHOD_HEAD);
|
||||
err = esp_http_client_perform(client);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP HEAD Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "HTTP HEAD request failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
|
||||
#if CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH
|
||||
static void http_auth_basic(void)
|
||||
{
|
||||
esp_http_client_config_t config = {
|
||||
.url = "http://user:passwd@httpbin.org/basic-auth/user/passwd",
|
||||
.event_handler = _http_event_handler,
|
||||
.auth_type = HTTP_AUTH_TYPE_BASIC,
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP Basic Auth Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
|
||||
}
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
|
||||
static void http_auth_basic_redirect(void)
|
||||
{
|
||||
esp_http_client_config_t config = {
|
||||
.url = "http://user:passwd@httpbin.org/basic-auth/user/passwd",
|
||||
.event_handler = _http_event_handler,
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP Basic Auth redirect Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
|
||||
}
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void http_auth_digest(void)
|
||||
{
|
||||
esp_http_client_config_t config = {
|
||||
.url = "http://user:passwd@httpbin.org/digest-auth/auth/user/passwd/MD5/never",
|
||||
.event_handler = _http_event_handler,
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP Digest Auth Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
|
||||
}
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
|
||||
static void https_with_url(void)
|
||||
{
|
||||
esp_http_client_config_t config = {
|
||||
.url = "https://www.howsmyssl.com",
|
||||
.event_handler = _http_event_handler,
|
||||
.cert_pem = howsmyssl_com_root_cert_pem_start,
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTPS Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
|
||||
}
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
|
||||
static void https_with_hostname_path(void)
|
||||
{
|
||||
esp_http_client_config_t config = {
|
||||
.host = "www.howsmyssl.com",
|
||||
.path = "/",
|
||||
.transport_type = HTTP_TRANSPORT_OVER_SSL,
|
||||
.event_handler = _http_event_handler,
|
||||
.cert_pem = howsmyssl_com_root_cert_pem_start,
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTPS Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
|
||||
}
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
|
||||
static void http_relative_redirect(void)
|
||||
{
|
||||
esp_http_client_config_t config = {
|
||||
.url = "http://httpbin.org/relative-redirect/3",
|
||||
.event_handler = _http_event_handler,
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP Relative path redirect Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
|
||||
}
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
|
||||
static void http_absolute_redirect(void)
|
||||
{
|
||||
esp_http_client_config_t config = {
|
||||
.url = "http://httpbin.org/absolute-redirect/3",
|
||||
.event_handler = _http_event_handler,
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP Absolute path redirect Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
|
||||
}
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
|
||||
static void http_redirect_to_https(void)
|
||||
{
|
||||
esp_http_client_config_t config = {
|
||||
.url = "http://httpbin.org/redirect-to?url=https%3A%2F%2Fwww.howsmyssl.com",
|
||||
.event_handler = _http_event_handler,
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP redirect to HTTPS Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
|
||||
}
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
|
||||
|
||||
static void http_download_chunk(void)
|
||||
{
|
||||
esp_http_client_config_t config = {
|
||||
.url = "http://httpbin.org/stream-bytes/8912",
|
||||
.event_handler = _http_event_handler,
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP chunk encoding Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
|
||||
}
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
|
||||
static void http_perform_as_stream_reader(void)
|
||||
{
|
||||
char *buffer = malloc(MAX_HTTP_RECV_BUFFER + 1);
|
||||
if (buffer == NULL) {
|
||||
ESP_LOGE(TAG, "Cannot malloc http receive buffer");
|
||||
return;
|
||||
}
|
||||
esp_http_client_config_t config = {
|
||||
.url = "http://httpbin.org/get",
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
esp_err_t err;
|
||||
if ((err = esp_http_client_open(client, 0)) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
|
||||
free(buffer);
|
||||
return;
|
||||
}
|
||||
int content_length = esp_http_client_fetch_headers(client);
|
||||
int total_read_len = 0, read_len;
|
||||
if (total_read_len < content_length && content_length <= MAX_HTTP_RECV_BUFFER) {
|
||||
read_len = esp_http_client_read(client, buffer, content_length);
|
||||
if (read_len <= 0) {
|
||||
ESP_LOGE(TAG, "Error read data");
|
||||
}
|
||||
buffer[read_len] = 0;
|
||||
ESP_LOGD(TAG, "read_len = %d", read_len);
|
||||
}
|
||||
ESP_LOGI(TAG, "HTTP Stream reader Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
static void https_async(void)
|
||||
{
|
||||
esp_http_client_config_t config = {
|
||||
.url = "https://postman-echo.com/post",
|
||||
.event_handler = _http_event_handler,
|
||||
.is_async = true,
|
||||
.timeout_ms = 5000,
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
esp_err_t err;
|
||||
const char *post_data = "Using a Palantír requires a person with great strength of will and wisdom. The Palantíri were meant to "
|
||||
"be used by the Dúnedain to communicate throughout the Realms in Exile. During the War of the Ring, "
|
||||
"the Palantíri were used by many individuals. Sauron used the Ithil-stone to take advantage of the users "
|
||||
"of the other two stones, the Orthanc-stone and Anor-stone, but was also susceptible to deception himself.";
|
||||
esp_http_client_set_method(client, HTTP_METHOD_POST);
|
||||
esp_http_client_set_post_field(client, post_data, strlen(post_data));
|
||||
while (1) {
|
||||
err = esp_http_client_perform(client);
|
||||
if (err != ESP_ERR_HTTP_EAGAIN) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTPS Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
|
||||
}
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
|
||||
static void https_with_invalid_url(void)
|
||||
{
|
||||
esp_http_client_config_t config = {
|
||||
.url = "https://not.existent.url",
|
||||
.event_handler = _http_event_handler,
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTPS Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
|
||||
}
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
|
||||
/*
|
||||
* http_native_request() demonstrates use of low level APIs to connect to a server,
|
||||
* make a http request and read response. Event handler is not used in this case.
|
||||
* Note: This approach should only be used in case use of low level APIs is required.
|
||||
* The easiest way is to use esp_http_perform()
|
||||
*/
|
||||
static void http_native_request(void)
|
||||
{
|
||||
char output_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0}; // Buffer to store response of http request
|
||||
int content_length = 0;
|
||||
esp_http_client_config_t config = {
|
||||
.url = "http://httpbin.org/get",
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
|
||||
// GET Request
|
||||
esp_http_client_set_method(client, HTTP_METHOD_GET);
|
||||
esp_err_t err = esp_http_client_open(client, 0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
|
||||
} else {
|
||||
content_length = esp_http_client_fetch_headers(client);
|
||||
if (content_length < 0) {
|
||||
ESP_LOGE(TAG, "HTTP client fetch headers failed");
|
||||
} else {
|
||||
int data_read = esp_http_client_read_response(client, output_buffer, MAX_HTTP_OUTPUT_BUFFER);
|
||||
if (data_read >= 0) {
|
||||
ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
ESP_LOG_BUFFER_HEX(TAG, output_buffer, strlen(output_buffer));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to read response");
|
||||
}
|
||||
}
|
||||
}
|
||||
esp_http_client_close(client);
|
||||
|
||||
// POST Request
|
||||
const char *post_data = "{\"field1\":\"value1\"}";
|
||||
esp_http_client_set_url(client, "http://httpbin.org/post");
|
||||
esp_http_client_set_method(client, HTTP_METHOD_POST);
|
||||
esp_http_client_set_header(client, "Content-Type", "application/json");
|
||||
err = esp_http_client_open(client, strlen(post_data));
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
|
||||
} else {
|
||||
int wlen = esp_http_client_write(client, post_data, strlen(post_data));
|
||||
if (wlen < 0) {
|
||||
ESP_LOGE(TAG, "Write failed");
|
||||
}
|
||||
int data_read = esp_http_client_read_response(client, output_buffer, MAX_HTTP_OUTPUT_BUFFER);
|
||||
if (data_read >= 0) {
|
||||
ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
ESP_LOG_BUFFER_HEX(TAG, output_buffer, strlen(output_buffer));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to read response");
|
||||
}
|
||||
}
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
|
||||
static void http_test_task(void *pvParameters)
|
||||
{
|
||||
http_rest_with_url();
|
||||
http_rest_with_hostname_path();
|
||||
#if CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH
|
||||
http_auth_basic();
|
||||
http_auth_basic_redirect();
|
||||
#endif
|
||||
http_auth_digest();
|
||||
http_relative_redirect();
|
||||
http_absolute_redirect();
|
||||
https_with_url();
|
||||
https_with_hostname_path();
|
||||
http_redirect_to_https();
|
||||
http_download_chunk();
|
||||
http_perform_as_stream_reader();
|
||||
https_async();
|
||||
https_with_invalid_url();
|
||||
http_native_request();
|
||||
|
||||
ESP_LOGI(TAG, "Finish http example");
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
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_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
ESP_LOGI(TAG, "Connected to AP, begin http example");
|
||||
|
||||
xTaskCreate(&http_test_task, "http_test_task", 8192, NULL, 5, NULL);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
|
||||
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
|
||||
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
|
||||
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
|
||||
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
|
||||
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
|
||||
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
|
||||
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
|
||||
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
|
||||
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
|
||||
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
|
||||
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
|
||||
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
|
||||
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
|
||||
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
|
||||
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
|
||||
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
|
||||
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
|
||||
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
|
||||
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
|
||||
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
|
||||
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
|
||||
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
|
||||
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
|
||||
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,10 @@
|
||||
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
|
||||
CONFIG_EXAMPLE_CONNECT_WIFI=n
|
||||
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
|
||||
CONFIG_EXAMPLE_ETH_PHY_IP101=y
|
||||
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
|
||||
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
|
||||
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
|
||||
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
|
||||
CONFIG_EXAMPLE_CONNECT_IPV6=y
|
||||
CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH=y
|
||||
@@ -0,0 +1,13 @@
|
||||
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
|
||||
CONFIG_EXAMPLE_CONNECT_WIFI=n
|
||||
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
|
||||
CONFIG_EXAMPLE_ETH_PHY_IP101=y
|
||||
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
|
||||
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
|
||||
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
|
||||
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
|
||||
CONFIG_EXAMPLE_CONNECT_IPV6=y
|
||||
CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH=y
|
||||
CONFIG_MBEDTLS_DYNAMIC_BUFFER=y
|
||||
CONFIG_MBEDTLS_DYNAMIC_FREE_PEER_CERT=y
|
||||
CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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()
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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
|
||||
@@ -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(×tamp));
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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!")
|
||||
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_ESP_HTTPS_SERVER_ENABLE=y
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(http2-request)
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := http2-request
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# HTTP/2 Request Example
|
||||
|
||||
Established HTTP/2 connection with https://http2.golang.org
|
||||
- Performs a GET on /clockstream
|
||||
- Performs a PUT on /ECHO
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
idf_component_register(SRCS "sh2lib.c"
|
||||
INCLUDE_DIRS .
|
||||
REQUIRES nghttp
|
||||
PRIV_REQUIRES lwip esp-tls)
|
||||
@@ -0,0 +1 @@
|
||||
COMPONENT_ADD_INCLUDEDIRS := .
|
||||
@@ -0,0 +1,369 @@
|
||||
// Copyright 2017 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.
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <netdb.h>
|
||||
#include <esp_log.h>
|
||||
#include <http_parser.h>
|
||||
|
||||
#include "sh2lib.h"
|
||||
|
||||
static const char *TAG = "sh2lib";
|
||||
|
||||
#define DBG_FRAME_SEND 1
|
||||
|
||||
/*
|
||||
* The implementation of nghttp2_send_callback type. Here we write
|
||||
* |data| with size |length| to the network and return the number of
|
||||
* bytes actually written. See the documentation of
|
||||
* nghttp2_send_callback for the details.
|
||||
*/
|
||||
static ssize_t callback_send_inner(struct sh2lib_handle *hd, const uint8_t *data,
|
||||
size_t length)
|
||||
{
|
||||
int rv = esp_tls_conn_write(hd->http2_tls, data, length);
|
||||
if (rv <= 0) {
|
||||
if (rv == ESP_TLS_ERR_SSL_WANT_READ || rv == ESP_TLS_ERR_SSL_WANT_WRITE) {
|
||||
rv = NGHTTP2_ERR_WOULDBLOCK;
|
||||
} else {
|
||||
rv = NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
static ssize_t callback_send(nghttp2_session *session, const uint8_t *data,
|
||||
size_t length, int flags, void *user_data)
|
||||
{
|
||||
int rv = 0;
|
||||
struct sh2lib_handle *hd = user_data;
|
||||
|
||||
int copy_offset = 0;
|
||||
int pending_data = length;
|
||||
|
||||
/* Send data in 1000 byte chunks */
|
||||
while (copy_offset != length) {
|
||||
int chunk_len = pending_data > 1000 ? 1000 : pending_data;
|
||||
int subrv = callback_send_inner(hd, data + copy_offset, chunk_len);
|
||||
if (subrv <= 0) {
|
||||
if (copy_offset == 0) {
|
||||
/* If no data is transferred, send the error code */
|
||||
rv = subrv;
|
||||
}
|
||||
break;
|
||||
}
|
||||
copy_offset += subrv;
|
||||
pending_data -= subrv;
|
||||
rv += subrv;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*
|
||||
* The implementation of nghttp2_recv_callback type. Here we read data
|
||||
* from the network and write them in |buf|. The capacity of |buf| is
|
||||
* |length| bytes. Returns the number of bytes stored in |buf|. See
|
||||
* the documentation of nghttp2_recv_callback for the details.
|
||||
*/
|
||||
static ssize_t callback_recv(nghttp2_session *session, uint8_t *buf,
|
||||
size_t length, int flags, void *user_data)
|
||||
{
|
||||
struct sh2lib_handle *hd = user_data;
|
||||
int rv;
|
||||
rv = esp_tls_conn_read(hd->http2_tls, (char *)buf, (int)length);
|
||||
if (rv < 0) {
|
||||
if (rv == ESP_TLS_ERR_SSL_WANT_READ || rv == ESP_TLS_ERR_SSL_WANT_WRITE) {
|
||||
rv = NGHTTP2_ERR_WOULDBLOCK;
|
||||
} else {
|
||||
rv = NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
} else if (rv == 0) {
|
||||
rv = NGHTTP2_ERR_EOF;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
const char *sh2lib_frame_type_str(int type)
|
||||
{
|
||||
switch (type) {
|
||||
case NGHTTP2_HEADERS:
|
||||
return "HEADERS";
|
||||
break;
|
||||
case NGHTTP2_RST_STREAM:
|
||||
return "RST_STREAM";
|
||||
break;
|
||||
case NGHTTP2_GOAWAY:
|
||||
return "GOAWAY";
|
||||
break;
|
||||
case NGHTTP2_DATA:
|
||||
return "DATA";
|
||||
break;
|
||||
case NGHTTP2_SETTINGS:
|
||||
return "SETTINGS";
|
||||
break;
|
||||
case NGHTTP2_PUSH_PROMISE:
|
||||
return "PUSH_PROMISE";
|
||||
break;
|
||||
case NGHTTP2_PING:
|
||||
return "PING";
|
||||
break;
|
||||
default:
|
||||
return "other";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int callback_on_frame_send(nghttp2_session *session,
|
||||
const nghttp2_frame *frame, void *user_data)
|
||||
{
|
||||
ESP_LOGD(TAG, "[frame-send] frame type %s", sh2lib_frame_type_str(frame->hd.type));
|
||||
switch (frame->hd.type) {
|
||||
case NGHTTP2_HEADERS:
|
||||
if (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) {
|
||||
ESP_LOGD(TAG, "[frame-send] C ----------------------------> S (HEADERS)");
|
||||
#if DBG_FRAME_SEND
|
||||
ESP_LOGD(TAG, "[frame-send] headers nv-len = %d", frame->headers.nvlen);
|
||||
const nghttp2_nv *nva = frame->headers.nva;
|
||||
size_t i;
|
||||
for (i = 0; i < frame->headers.nvlen; ++i) {
|
||||
ESP_LOGD(TAG, "[frame-send] %s : %s", nva[i].name, nva[i].value);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int callback_on_frame_recv(nghttp2_session *session,
|
||||
const nghttp2_frame *frame, void *user_data)
|
||||
{
|
||||
ESP_LOGD(TAG, "[frame-recv][sid: %d] frame type %s", frame->hd.stream_id, sh2lib_frame_type_str(frame->hd.type));
|
||||
if (frame->hd.type != NGHTTP2_DATA) {
|
||||
return 0;
|
||||
}
|
||||
/* Subsequent processing only for data frame */
|
||||
sh2lib_frame_data_recv_cb_t data_recv_cb = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
|
||||
if (data_recv_cb) {
|
||||
struct sh2lib_handle *h2 = user_data;
|
||||
(*data_recv_cb)(h2, NULL, 0, DATA_RECV_FRAME_COMPLETE);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int callback_on_stream_close(nghttp2_session *session, int32_t stream_id,
|
||||
uint32_t error_code, void *user_data)
|
||||
{
|
||||
|
||||
ESP_LOGD(TAG, "[stream-close][sid %d]", stream_id);
|
||||
sh2lib_frame_data_recv_cb_t data_recv_cb = nghttp2_session_get_stream_user_data(session, stream_id);
|
||||
if (data_recv_cb) {
|
||||
struct sh2lib_handle *h2 = user_data;
|
||||
(*data_recv_cb)(h2, NULL, 0, DATA_RECV_RST_STREAM);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int callback_on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
|
||||
int32_t stream_id, const uint8_t *data,
|
||||
size_t len, void *user_data)
|
||||
{
|
||||
sh2lib_frame_data_recv_cb_t data_recv_cb;
|
||||
ESP_LOGD(TAG, "[data-chunk][sid:%d]", stream_id);
|
||||
data_recv_cb = nghttp2_session_get_stream_user_data(session, stream_id);
|
||||
if (data_recv_cb) {
|
||||
ESP_LOGD(TAG, "[data-chunk] C <---------------------------- S (DATA chunk)"
|
||||
"%lu bytes",
|
||||
(unsigned long int)len);
|
||||
struct sh2lib_handle *h2 = user_data;
|
||||
(*data_recv_cb)(h2, (char *)data, len, 0);
|
||||
/* TODO: What to do with the return value: look for pause/abort */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int callback_on_header(nghttp2_session *session, const nghttp2_frame *frame,
|
||||
const uint8_t *name, size_t namelen, const uint8_t *value,
|
||||
size_t valuelen, uint8_t flags, void *user_data)
|
||||
{
|
||||
ESP_LOGD(TAG, "[hdr-recv][sid:%d] %s : %s", frame->hd.stream_id, name, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_http2_connect(struct sh2lib_handle *hd)
|
||||
{
|
||||
int ret;
|
||||
nghttp2_session_callbacks *callbacks;
|
||||
nghttp2_session_callbacks_new(&callbacks);
|
||||
nghttp2_session_callbacks_set_send_callback(callbacks, callback_send);
|
||||
nghttp2_session_callbacks_set_recv_callback(callbacks, callback_recv);
|
||||
nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, callback_on_frame_send);
|
||||
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, callback_on_frame_recv);
|
||||
nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, callback_on_stream_close);
|
||||
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, callback_on_data_chunk_recv);
|
||||
nghttp2_session_callbacks_set_on_header_callback(callbacks, callback_on_header);
|
||||
ret = nghttp2_session_client_new(&hd->http2_sess, callbacks, hd);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(TAG, "[sh2-connect] New http2 session failed");
|
||||
nghttp2_session_callbacks_del(callbacks);
|
||||
return -1;
|
||||
}
|
||||
nghttp2_session_callbacks_del(callbacks);
|
||||
|
||||
/* Create the SETTINGS frame */
|
||||
ret = nghttp2_submit_settings(hd->http2_sess, NGHTTP2_FLAG_NONE, NULL, 0);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(TAG, "[sh2-connect] Submit settings failed");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sh2lib_connect(struct sh2lib_handle *hd, const char *uri)
|
||||
{
|
||||
memset(hd, 0, sizeof(*hd));
|
||||
const char *proto[] = {"h2", NULL};
|
||||
esp_tls_cfg_t tls_cfg = {
|
||||
.alpn_protos = proto,
|
||||
.non_block = true,
|
||||
.timeout_ms = 10 * 1000,
|
||||
};
|
||||
if ((hd->http2_tls = esp_tls_conn_http_new(uri, &tls_cfg)) == NULL) {
|
||||
ESP_LOGE(TAG, "[sh2-connect] esp-tls connection failed");
|
||||
goto error;
|
||||
}
|
||||
struct http_parser_url u;
|
||||
http_parser_url_init(&u);
|
||||
http_parser_parse_url(uri, strlen(uri), 0, &u);
|
||||
hd->hostname = strndup(&uri[u.field_data[UF_HOST].off], u.field_data[UF_HOST].len);
|
||||
|
||||
/* HTTP/2 Connection */
|
||||
if (do_http2_connect(hd) != 0) {
|
||||
ESP_LOGE(TAG, "[sh2-connect] HTTP2 Connection failed with %s", uri);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
sh2lib_free(hd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void sh2lib_free(struct sh2lib_handle *hd)
|
||||
{
|
||||
if (hd->http2_sess) {
|
||||
nghttp2_session_del(hd->http2_sess);
|
||||
hd->http2_sess = NULL;
|
||||
}
|
||||
if (hd->http2_tls) {
|
||||
esp_tls_conn_delete(hd->http2_tls);
|
||||
hd->http2_tls = NULL;
|
||||
}
|
||||
if (hd->hostname) {
|
||||
free(hd->hostname);
|
||||
hd->hostname = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int sh2lib_execute(struct sh2lib_handle *hd)
|
||||
{
|
||||
int ret;
|
||||
ret = nghttp2_session_send(hd->http2_sess);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(TAG, "[sh2-execute] HTTP2 session send failed %d", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = nghttp2_session_recv(hd->http2_sess);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(TAG, "[sh2-execute] HTTP2 session recv failed %d", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sh2lib_do_get_with_nv(struct sh2lib_handle *hd, const nghttp2_nv *nva, size_t nvlen, sh2lib_frame_data_recv_cb_t recv_cb)
|
||||
{
|
||||
int ret = nghttp2_submit_request(hd->http2_sess, NULL, nva, nvlen, NULL, recv_cb);
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "[sh2-do-get] HEADERS call failed");
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int sh2lib_do_get(struct sh2lib_handle *hd, const char *path, sh2lib_frame_data_recv_cb_t recv_cb)
|
||||
{
|
||||
const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "GET"),
|
||||
SH2LIB_MAKE_NV(":scheme", "https"),
|
||||
SH2LIB_MAKE_NV(":authority", hd->hostname),
|
||||
SH2LIB_MAKE_NV(":path", path),
|
||||
};
|
||||
return sh2lib_do_get_with_nv(hd, nva, sizeof(nva) / sizeof(nva[0]), recv_cb);
|
||||
}
|
||||
|
||||
ssize_t sh2lib_data_provider_cb(nghttp2_session *session, int32_t stream_id, uint8_t *buf,
|
||||
size_t length, uint32_t *data_flags,
|
||||
nghttp2_data_source *source, void *user_data)
|
||||
{
|
||||
struct sh2lib_handle *h2 = user_data;
|
||||
sh2lib_putpost_data_cb_t data_cb = source->ptr;
|
||||
return (*data_cb)(h2, (char *)buf, length, data_flags);
|
||||
}
|
||||
|
||||
int sh2lib_do_putpost_with_nv(struct sh2lib_handle *hd, const nghttp2_nv *nva, size_t nvlen,
|
||||
sh2lib_putpost_data_cb_t send_cb,
|
||||
sh2lib_frame_data_recv_cb_t recv_cb)
|
||||
{
|
||||
|
||||
nghttp2_data_provider sh2lib_data_provider;
|
||||
sh2lib_data_provider.read_callback = sh2lib_data_provider_cb;
|
||||
sh2lib_data_provider.source.ptr = send_cb;
|
||||
int ret = nghttp2_submit_request(hd->http2_sess, NULL, nva, nvlen, &sh2lib_data_provider, recv_cb);
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "[sh2-do-putpost] HEADERS call failed");
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int sh2lib_do_post(struct sh2lib_handle *hd, const char *path,
|
||||
sh2lib_putpost_data_cb_t send_cb,
|
||||
sh2lib_frame_data_recv_cb_t recv_cb)
|
||||
{
|
||||
const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "POST"),
|
||||
SH2LIB_MAKE_NV(":scheme", "https"),
|
||||
SH2LIB_MAKE_NV(":authority", hd->hostname),
|
||||
SH2LIB_MAKE_NV(":path", path),
|
||||
};
|
||||
return sh2lib_do_putpost_with_nv(hd, nva, sizeof(nva) / sizeof(nva[0]), send_cb, recv_cb);
|
||||
}
|
||||
|
||||
int sh2lib_do_put(struct sh2lib_handle *hd, const char *path,
|
||||
sh2lib_putpost_data_cb_t send_cb,
|
||||
sh2lib_frame_data_recv_cb_t recv_cb)
|
||||
{
|
||||
const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "PUT"),
|
||||
SH2LIB_MAKE_NV(":scheme", "https"),
|
||||
SH2LIB_MAKE_NV(":authority", hd->hostname),
|
||||
SH2LIB_MAKE_NV(":path", path),
|
||||
};
|
||||
return sh2lib_do_putpost_with_nv(hd, nva, sizeof(nva) / sizeof(nva[0]), send_cb, recv_cb);
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
// Copyright 2017 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.
|
||||
#ifndef __ESP_EXAMPLE_SH2_LIB_H_
|
||||
#define __ESP_EXAMPLE_SH2_LIB_H_
|
||||
|
||||
#include "esp_tls.h"
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
/*
|
||||
* This is a thin API wrapper over nghttp2 that offers simplified APIs for usage
|
||||
* in the application. The intention of this wrapper is to act as a stepping
|
||||
* stone to quickly get started with using the HTTP/2 client. Since the focus is
|
||||
* on simplicity, not all the features of HTTP/2 are supported through this
|
||||
* wrapper. Once you are fairly comfortable with nghttp2, feel free to directly
|
||||
* use nghttp2 APIs or make changes to sh2lib.c for realising your objectives.
|
||||
*
|
||||
* TODO:
|
||||
* - Allowing to query response code, content-type etc in the receive callback
|
||||
* - A simple function for per-stream header callback
|
||||
*/
|
||||
/**
|
||||
* @brief Handle for working with sh2lib APIs
|
||||
*/
|
||||
struct sh2lib_handle {
|
||||
nghttp2_session *http2_sess; /*!< Pointer to the HTTP2 session handle */
|
||||
char *hostname; /*!< The hostname we are connected to */
|
||||
struct esp_tls *http2_tls; /*!< Pointer to the TLS session handle */
|
||||
};
|
||||
|
||||
/** Flag indicating receive stream is reset */
|
||||
#define DATA_RECV_RST_STREAM 1
|
||||
/** Flag indicating frame is completely received */
|
||||
#define DATA_RECV_FRAME_COMPLETE 2
|
||||
|
||||
/**
|
||||
* @brief Function Prototype for data receive callback
|
||||
*
|
||||
* This function gets called whenever data is received on any stream. The
|
||||
* function is also called for indicating events like frame receive complete, or
|
||||
* end of stream. The function may get called multiple times as long as data is
|
||||
* received on the stream.
|
||||
*
|
||||
* @param[in] handle Pointer to the sh2lib handle.
|
||||
* @param[in] data Pointer to a buffer that contains the data received.
|
||||
* @param[in] len The length of valid data stored at the 'data' pointer.
|
||||
* @param[in] flags Flags indicating whether the stream is reset (DATA_RECV_RST_STREAM) or
|
||||
* this particularly frame is completely received
|
||||
* DATA_RECV_FRAME_COMPLETE).
|
||||
*
|
||||
* @return The function should return 0
|
||||
*/
|
||||
typedef int (*sh2lib_frame_data_recv_cb_t)(struct sh2lib_handle *handle, const char *data, size_t len, int flags);
|
||||
|
||||
/**
|
||||
* @brief Function Prototype for callback to send data in PUT/POST
|
||||
*
|
||||
* This function gets called whenever nghttp2 wishes to send data, like for
|
||||
* PUT/POST requests to the server. The function keeps getting called until this
|
||||
* function sets the flag NGHTTP2_DATA_FLAG_EOF to indicate end of data.
|
||||
*
|
||||
* @param[in] handle Pointer to the sh2lib handle.
|
||||
* @param[out] data Pointer to a buffer that should contain the data to send.
|
||||
* @param[in] len The maximum length of data that can be sent out by this function.
|
||||
* @param[out] data_flags Pointer to the data flags. The NGHTTP2_DATA_FLAG_EOF
|
||||
* should be set in the data flags to indicate end of new data.
|
||||
*
|
||||
* @return The function should return the number of valid bytes stored in the
|
||||
* data pointer
|
||||
*/
|
||||
typedef int (*sh2lib_putpost_data_cb_t)(struct sh2lib_handle *handle, char *data, size_t len, uint32_t *data_flags);
|
||||
|
||||
/**
|
||||
* @brief Connect to a URI using HTTP/2
|
||||
*
|
||||
* This API opens an HTTP/2 connection with the provided URI. If successful, the
|
||||
* hd pointer is populated with a valid handle for subsequent communication.
|
||||
*
|
||||
* Only 'https' URIs are supported.
|
||||
*
|
||||
* @param[out] hd Pointer to a variable of the type 'struct sh2lib_handle'.
|
||||
* @param[in] uri Pointer to the URI that should be connected to.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK if the connection was successful
|
||||
* - ESP_FAIL if the connection fails
|
||||
*/
|
||||
int sh2lib_connect(struct sh2lib_handle *hd, const char *uri);
|
||||
|
||||
/**
|
||||
* @brief Free a sh2lib handle
|
||||
*
|
||||
* This API frees-up an sh2lib handle, thus closing any open connections that
|
||||
* may be associated with this handle, and freeing up any resources.
|
||||
*
|
||||
* @param[in] hd Pointer to a variable of the type 'struct sh2lib_handle'.
|
||||
*
|
||||
*/
|
||||
void sh2lib_free(struct sh2lib_handle *hd);
|
||||
|
||||
/**
|
||||
* @brief Setup an HTTP GET request stream
|
||||
*
|
||||
* This API sets up an HTTP GET request to be sent out to the server. A new
|
||||
* stream is created for handling the request. Once the request is setup, the
|
||||
* API sh2lib_execute() must be called to actually perform the socket I/O with
|
||||
* the server.
|
||||
*
|
||||
* @param[in] hd Pointer to a variable of the type 'struct sh2lib_handle'.
|
||||
* @param[in] path Pointer to the string that contains the resource to
|
||||
* perform the HTTP GET operation on (for example, /users).
|
||||
* @param[in] recv_cb The callback function that should be called for
|
||||
* processing the request's response
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK if request setup is successful
|
||||
* - ESP_FAIL if the request setup fails
|
||||
*/
|
||||
int sh2lib_do_get(struct sh2lib_handle *hd, const char *path, sh2lib_frame_data_recv_cb_t recv_cb);
|
||||
|
||||
/**
|
||||
* @brief Setup an HTTP POST request stream
|
||||
*
|
||||
* This API sets up an HTTP POST request to be sent out to the server. A new
|
||||
* stream is created for handling the request. Once the request is setup, the
|
||||
* API sh2lib_execute() must be called to actually perform the socket I/O with
|
||||
* the server.
|
||||
*
|
||||
* @param[in] hd Pointer to a variable of the type 'struct sh2lib_handle'.
|
||||
* @param[in] path Pointer to the string that contains the resource to
|
||||
* perform the HTTP POST operation on (for example, /users).
|
||||
* @param[in] send_cb The callback function that should be called for
|
||||
* sending data as part of this request.
|
||||
* @param[in] recv_cb The callback function that should be called for
|
||||
* processing the request's response
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK if request setup is successful
|
||||
* - ESP_FAIL if the request setup fails
|
||||
*/
|
||||
int sh2lib_do_post(struct sh2lib_handle *hd, const char *path,
|
||||
sh2lib_putpost_data_cb_t send_cb,
|
||||
sh2lib_frame_data_recv_cb_t recv_cb);
|
||||
|
||||
/**
|
||||
* @brief Setup an HTTP PUT request stream
|
||||
*
|
||||
* This API sets up an HTTP PUT request to be sent out to the server. A new
|
||||
* stream is created for handling the request. Once the request is setup, the
|
||||
* API sh2lib_execute() must be called to actually perform the socket I/O with
|
||||
* the server.
|
||||
*
|
||||
* @param[in] hd Pointer to a variable of the type 'struct sh2lib_handle'.
|
||||
* @param[in] path Pointer to the string that contains the resource to
|
||||
* perform the HTTP PUT operation on (for example, /users).
|
||||
* @param[in] send_cb The callback function that should be called for
|
||||
* sending data as part of this request.
|
||||
* @param[in] recv_cb The callback function that should be called for
|
||||
* processing the request's response
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK if request setup is successful
|
||||
* - ESP_FAIL if the request setup fails
|
||||
*/
|
||||
int sh2lib_do_put(struct sh2lib_handle *hd, const char *path,
|
||||
sh2lib_putpost_data_cb_t send_cb,
|
||||
sh2lib_frame_data_recv_cb_t recv_cb);
|
||||
|
||||
/**
|
||||
* @brief Execute send/receive on an HTTP/2 connection
|
||||
*
|
||||
* While the API sh2lib_do_get(), sh2lib_do_post() setup the requests to be
|
||||
* initiated with the server, this API performs the actual data send/receive
|
||||
* operations on the HTTP/2 connection. The callback functions are accordingly
|
||||
* called during the processing of these requests.
|
||||
*
|
||||
* @param[in] hd Pointer to a variable of the type 'struct sh2lib_handle'
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK if the connection was successful
|
||||
* - ESP_FAIL if the connection fails
|
||||
*/
|
||||
int sh2lib_execute(struct sh2lib_handle *hd);
|
||||
|
||||
#define SH2LIB_MAKE_NV(NAME, VALUE) \
|
||||
{ \
|
||||
(uint8_t *)NAME, (uint8_t *)VALUE, strlen(NAME), strlen(VALUE), \
|
||||
NGHTTP2_NV_FLAG_NONE \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Setup an HTTP GET request stream with custom name-value pairs
|
||||
*
|
||||
* For a simpler version of the API, please refer to sh2lib_do_get().
|
||||
*
|
||||
* This API sets up an HTTP GET request to be sent out to the server. A new
|
||||
* stream is created for handling the request. Once the request is setup, the
|
||||
* API sh2lib_execute() must be called to actually perform the socket I/O with
|
||||
* the server.
|
||||
*
|
||||
* Please note that the following name value pairs MUST be a part of the request
|
||||
* - name:value
|
||||
* - ":method":"GET"
|
||||
* - ":scheme":"https"
|
||||
* - ":path":<the-path-for-the-GET-operation> (for example, /users)
|
||||
*
|
||||
* @param[in] hd Pointer to a variable of the type 'struct sh2lib_handle'.
|
||||
* @param[in] nva An array of name-value pairs that should be part of the request.
|
||||
* @param[in] nvlen The number of elements in the array pointed to by 'nva'.
|
||||
* @param[in] recv_cb The callback function that should be called for
|
||||
* processing the request's response
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK if request setup is successful
|
||||
* - ESP_FAIL if the request setup fails
|
||||
*/
|
||||
int sh2lib_do_get_with_nv(struct sh2lib_handle *hd, const nghttp2_nv *nva, size_t nvlen, sh2lib_frame_data_recv_cb_t recv_cb);
|
||||
|
||||
/**
|
||||
* @brief Setup an HTTP PUT/POST request stream with custom name-value pairs
|
||||
*
|
||||
* For a simpler version of the API, please refer to sh2lib_do_put() or
|
||||
* sh2lib_do_post().
|
||||
*
|
||||
* This API sets up an HTTP PUT/POST request to be sent out to the server. A new
|
||||
* stream is created for handling the request. Once the request is setup, the
|
||||
* API sh2lib_execute() must be called to actually perform the socket I/O with
|
||||
* the server.
|
||||
*
|
||||
* Please note that the following name value pairs MUST be a part of the request
|
||||
* - name:value
|
||||
* - ":method":"PUT" (or POST)
|
||||
* - ":scheme":"https"
|
||||
* - ":path":<the-path-for-the-PUT-operation> (for example, /users)
|
||||
*
|
||||
* @param[in] hd Pointer to a variable of the type 'struct sh2lib_handle'.
|
||||
* @param[in] nva An array of name-value pairs that should be part of the request.
|
||||
* @param[in] nvlen The number of elements in the array pointed to by 'nva'.
|
||||
* @param[in] send_cb The callback function that should be called for
|
||||
* sending data as part of this request.
|
||||
* @param[in] recv_cb The callback function that should be called for
|
||||
* processing the request's response
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK if request setup is successful
|
||||
* - ESP_FAIL if the request setup fails
|
||||
*/
|
||||
int sh2lib_do_putpost_with_nv(struct sh2lib_handle *hd, const nghttp2_nv *nva, size_t nvlen,
|
||||
sh2lib_putpost_data_cb_t send_cb,
|
||||
sh2lib_frame_data_recv_cb_t recv_cb);
|
||||
|
||||
#endif /* ! __ESP_EXAMPLE_SH2_LIB_H_ */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user