添加智能灯固件代码

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

View File

@@ -0,0 +1,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.

View File

@@ -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)

View File

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

View File

@@ -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.

View File

@@ -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()

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "chat_client.cpp"
INCLUDE_DIRS ".")

View File

@@ -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

View File

@@ -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());
}

View File

@@ -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

View File

@@ -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.
#

View File

@@ -0,0 +1 @@
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192

View File

@@ -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)

View File

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

View File

@@ -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.

View File

@@ -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()

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "chat_server.cpp"
INCLUDE_DIRS ".")

View File

@@ -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

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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.
#

View File

@@ -0,0 +1 @@
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192

View File

@@ -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)

View File

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

View File

@@ -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.

View File

@@ -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()

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "echo_server.cpp"
INCLUDE_DIRS ".")

View File

@@ -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

View File

@@ -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.
#

View File

@@ -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();
}

View File

@@ -0,0 +1 @@
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192

View File

@@ -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)

View File

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

View File

@@ -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.

View File

@@ -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()

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "udp_echo_server.cpp"
INCLUDE_DIRS ".")

View File

@@ -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

View File

@@ -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.
#

View File

@@ -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();
}

View File

@@ -0,0 +1 @@
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192

View File

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

View File

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

View File

@@ -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.)

View File

@@ -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()

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "cbor_example_main.c"
INCLUDE_DIRS "")

View File

@@ -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);
}

View File

@@ -0,0 +1,5 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -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);
}

View File

@@ -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

View File

@@ -0,0 +1,3 @@
CONFIG_MBEDTLS_SSL_PROTO_DTLS=y
CONFIG_MBEDTLS_PSK_MODES=y
CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -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);
}

View File

@@ -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

View File

@@ -0,0 +1,3 @@
CONFIG_MBEDTLS_SSL_PROTO_DTLS=y
CONFIG_MBEDTLS_PSK_MODES=y
CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y

View File

@@ -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)

View File

@@ -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

View File

@@ -0,0 +1,3 @@
# ESP HTTP Client Example
See the README.md file in the upper level 'examples' directory for more information about examples.

View File

@@ -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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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-----

View File

@@ -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

View File

@@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,93 @@
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from __future__ import print_function
from future.utils import tobytes
import os
def _load_source(name, path):
try:
from importlib.machinery import SourceFileLoader
return SourceFileLoader(name, path).load_module()
except ImportError:
# importlib.machinery doesn't exists in Python 2 so we will use imp (deprecated in Python 3)
import imp
return imp.load_source(name, path)
idf_path = os.environ['IDF_PATH']
constants_pb2 = _load_source("constants_pb2", idf_path + "/components/protocomm/python/constants_pb2.py")
local_ctrl_pb2 = _load_source("esp_local_ctrl_pb2", idf_path + "/components/esp_local_ctrl/python/esp_local_ctrl_pb2.py")
def get_prop_count_request():
req = local_ctrl_pb2.LocalCtrlMessage()
req.msg = local_ctrl_pb2.TypeCmdGetPropertyCount
payload = local_ctrl_pb2.CmdGetPropertyCount()
req.cmd_get_prop_count.MergeFrom(payload)
return req.SerializeToString()
def get_prop_count_response(response_data):
resp = local_ctrl_pb2.LocalCtrlMessage()
resp.ParseFromString(tobytes(response_data))
if (resp.resp_get_prop_count.status == 0):
return resp.resp_get_prop_count.count
else:
return 0
def get_prop_vals_request(indices):
req = local_ctrl_pb2.LocalCtrlMessage()
req.msg = local_ctrl_pb2.TypeCmdGetPropertyValues
payload = local_ctrl_pb2.CmdGetPropertyValues()
payload.indices.extend(indices)
req.cmd_get_prop_vals.MergeFrom(payload)
return req.SerializeToString()
def get_prop_vals_response(response_data):
resp = local_ctrl_pb2.LocalCtrlMessage()
resp.ParseFromString(tobytes(response_data))
results = []
if (resp.resp_get_prop_vals.status == 0):
for prop in resp.resp_get_prop_vals.props:
results += [{
"name": prop.name,
"type": prop.type,
"flags": prop.flags,
"value": tobytes(prop.value)
}]
return results
def set_prop_vals_request(indices, values):
req = local_ctrl_pb2.LocalCtrlMessage()
req.msg = local_ctrl_pb2.TypeCmdSetPropertyValues
payload = local_ctrl_pb2.CmdSetPropertyValues()
for i, v in zip(indices, values):
prop = payload.props.add()
prop.index = i
prop.value = v
req.cmd_set_prop_vals.MergeFrom(payload)
return req.SerializeToString()
def set_prop_vals_response(response_data):
resp = local_ctrl_pb2.LocalCtrlMessage()
resp.ParseFromString(tobytes(response_data))
return (resp.resp_set_prop_vals.status == 0)

View File

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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,4 @@
idf_component_register(SRCS "sh2lib.c"
INCLUDE_DIRS .
REQUIRES nghttp
PRIV_REQUIRES lwip esp-tls)

View File

@@ -0,0 +1 @@
COMPONENT_ADD_INCLUDEDIRS := .

View File

@@ -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);
}

View File

@@ -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