From c3caff0e16536d449fe44b001857e6fb3cc5bef8 Mon Sep 17 00:00:00 2001 From: rgoliver Date: Wed, 30 Jun 2021 14:43:39 -0400 Subject: [PATCH] IPV6-only test app for esp32 (examples/ipv6only-app) (#7289) Adds RPCs that conform to NDM/GDM WiFi capabilities. Disables IPV4 DHCP client. Assigns link-local IPV6 address. Starts a UDP echo server on an IPV6 UDP socket. Test: Ran on M5Stack and verified it scanned and connected to wifi, after connected verified it echoed UDP messages. --- .../common/gdm_wifi_base_rpc.options | 12 + .../common/gdm_wifi_base_rpc.proto | 171 +++++++++ examples/ipv6only-app/esp32/.gitignore | 5 + examples/ipv6only-app/esp32/CMakeLists.txt | 38 ++ examples/ipv6only-app/esp32/README.md | 103 ++++++ .../esp32/include/gdm_wifi_service.h | 163 +++++++++ .../ipv6only-app/esp32/main/CMakeLists.txt | 88 +++++ .../ipv6only-app/esp32/main/Kconfig.projbuild | 35 ++ .../esp32/main/gdm_wifi_service.cpp | 341 ++++++++++++++++++ examples/ipv6only-app/esp32/main/main.cpp | 152 ++++++++ examples/ipv6only-app/esp32/partitions.csv | 6 + .../ipv6only-app/esp32/sdkconfig.defaults | 43 +++ .../esp32/third_party/connectedhomeip | 1 + examples/lock-app/esp32/main/Rpc.cpp | 19 - examples/pigweed-app/esp32/main/main.cpp | 22 -- examples/platform/esp32/PigweedLogger.cpp | 24 ++ 16 files changed, 1182 insertions(+), 41 deletions(-) create mode 100644 examples/ipv6only-app/common/gdm_wifi_base_rpc.options create mode 100644 examples/ipv6only-app/common/gdm_wifi_base_rpc.proto create mode 100644 examples/ipv6only-app/esp32/.gitignore create mode 100644 examples/ipv6only-app/esp32/CMakeLists.txt create mode 100644 examples/ipv6only-app/esp32/README.md create mode 100644 examples/ipv6only-app/esp32/include/gdm_wifi_service.h create mode 100644 examples/ipv6only-app/esp32/main/CMakeLists.txt create mode 100644 examples/ipv6only-app/esp32/main/Kconfig.projbuild create mode 100644 examples/ipv6only-app/esp32/main/gdm_wifi_service.cpp create mode 100644 examples/ipv6only-app/esp32/main/main.cpp create mode 100644 examples/ipv6only-app/esp32/partitions.csv create mode 100644 examples/ipv6only-app/esp32/sdkconfig.defaults create mode 120000 examples/ipv6only-app/esp32/third_party/connectedhomeip diff --git a/examples/ipv6only-app/common/gdm_wifi_base_rpc.options b/examples/ipv6only-app/common/gdm_wifi_base_rpc.options new file mode 100644 index 00000000000000..08adc57b6dd583 --- /dev/null +++ b/examples/ipv6only-app/common/gdm_wifi_base_rpc.options @@ -0,0 +1,12 @@ +chip.rpc.Ssid.ssid max_size:32 +chip.rpc.ScanConfig.ssid max_count:1;max_size:32 +chip.rpc.ScanConfig.bssid max_count:1;max_size:6 +chip.rpc.ScanResult.ssid max_size:32 +chip.rpc.ScanResult.bssid max_size:6 +chip.rpc.ScanResults.aps max_count:20 +chip.rpc.MacAddress.mac_address max_size:18 +chip.rpc.WiFiInterface.interface max_size:20 +chip.rpc.IP4Address.address max_size:16 +chip.rpc.IP6Address.address max_size:46 +chip.rpc.ConnectionData.ssid max_size:32 +chip.rpc.ConnectionData.secret max_size:64 diff --git a/examples/ipv6only-app/common/gdm_wifi_base_rpc.proto b/examples/ipv6only-app/common/gdm_wifi_base_rpc.proto new file mode 100644 index 00000000000000..73075c93c1230a --- /dev/null +++ b/examples/ipv6only-app/common/gdm_wifi_base_rpc.proto @@ -0,0 +1,171 @@ +// Copyright 2021 Google LLC +// +// 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. +// +// RPC proto API for wifi capabilities, these roughly map to GDM's wifi +// capabilities. + +syntax = "proto3"; + +package chip.rpc; + +// TODO: Use pw.protobuf.Empty; currently not using it so that this file has no +// dependencies and can be loaded into the hdlc console python tool. +message Empty { +} + +message Channel { + uint32 channel = 1; +} + +message Ssid { + bytes ssid = 1; +} + +message State { + bool connected = 1; +} + +message MacAddress { + string mac_address = 1; +} + +message WiFiInterface { + string interface = 1; +} + +message IP4Address { + string address = 1; +} + +message IP6Address { + string address = 1; +} + +message ScanConfig { + repeated bytes ssid = 1; // if not null only that SSID is scanned for + repeated bytes bssid = 2; // if not null only that BSSID is scanned for + uint32 channel = 3; // if 0 then all channels are scanned + bool show_hidden = 4; // if false then hidden SSIDs are not returned + bool active_scan = 5; + + // These fields are used to control how long the scan dwells + // on each channel. + // For passive scans, scan_time_min_ms designates + // the dwell time for each channel. + // For active scans, dwell times for each channel are listed + // in the table below. Here, min is short for scan_time_min_ms + // and max is short for scan_time_max_ms. + // + // min>=0, max=0: scan dwells on each channel for the default + // length of time. + // min=0, max>0: scan dwells on each channel for max ms. + // min>0, max>0: the minimum time the scan dwells on each + // channel is min ms. If no AP is found during + // this time frame, the scan switches to the + // next channel. Otherwise, the scan dwells on + // the channel for max ms. + uint32 scan_time_min_ms = 6; + uint32 scan_time_max_ms = 7; +} + +enum WIFI_SECURITY_TYPE { + WIFI_AUTH_OPEN = 0; + WIFI_AUTH_WEP = 1; + WIFI_AUTH_WPA_PSK = 2; + WIFI_AUTH_WPA2_PSK = 3; + WIFI_AUTH_WPA_WPA2_PSK = 4; + WIFI_AUTH_WPA2_ENTERPRISE = 5; + WIFI_AUTH_WPA3_PSK = 6; + WIFI_AUTH_WPA2_WPA3_PSK = 7; + WIFI_AUTH_WAPI_PSK = 8; +} + +message ScanResult { + bytes ssid = 1; // empty SSID means there are no more results + bytes bssid = 2; + WIFI_SECURITY_TYPE security_type = 3; + uint32 frequency = 4; + uint32 channel = 5; + int32 signal = 6; +} + +message ScanResults { + repeated ScanResult aps = 1; +} + +message ConnectionData { + bytes ssid = 1; + WIFI_SECURITY_TYPE security_type = 2; + bytes secret = 3; // e.g. passphrase or psk +} + +enum CONNECTION_ERROR { + OK = 0; + UNSPECIFIED = 1; + AUTH_EXPIRE = 2; + AUTH_LEAVE = 3; + ASSOC_EXPIRE = 4; + ASSOC_TOOMANY = 5; + NOT_AUTHED = 6; + NOT_ASSOCED = 7; + ASSOC_LEAVE = 8; + ASSOC_NOT_AUTHED = 9; + DISASSOC_PWRCAP_BAD = 10; + DISASSOC_SUPCHAN_BAD = 11; + IE_INVALID = 13; + MIC_FAILURE = 14; + FOURWAY_HANDSHAKE_TIMEOUT = 15; + GROUP_KEY_UPDATE_TIMEOUT = 16; + IE_IN_4WAY_DIFFERS = 17; + GROUP_CIPHER_INVALID = 18; + PAIRWISE_CIPHER_INVALID = 19; + AKMP_INVALID = 20; + UNSUPP_RSN_IE_VERSION = 21; + INVALID_RSN_IE_CAP = 22; + IEEE802_1X_AUTH_FAILED = 23; + CIPHER_SUITE_REJECTED = 24; + INVALID_PMKID = 53; + BEACON_TIMEOUT = 200; + NO_AP_FOUND = 201; + AUTH_FAIL = 202; + ASSOC_FAIL = 203; + HANDSHAKE_TIMEOUT = 204; + CONNECTION_FAIL = 205; + AP_TSF_RESET = 206; + ROAMING = 207; +} + +message ConnectionResult { + CONNECTION_ERROR error = 1; +} + +// The GDMWifiBase service provides the common RPC interface for interacting +// with a WIFI capable CHIP device. +// The current state can be retrieved using the various 'Get' RPCs. +// A device can be connected to an AP using the StartScan, and Connect RPCs. +service GDMWifiBase { + rpc GetChannel(Empty) returns (Channel) {} + rpc GetSsid(Empty) returns (Ssid) {} + rpc GetState(Empty) returns (State) {} + rpc GetMacAddress(Empty) returns (MacAddress) {} + + rpc GetWiFiInterface(Empty) returns (WiFiInterface) {} + rpc GetIP4Address(Empty) returns (IP4Address) {} + rpc GetIP6Address(Empty) returns (IP6Address) {} + + rpc StartScan(ScanConfig) returns (stream ScanResults) {} + rpc StopScan(Empty) returns (Empty) {} + rpc Connect(ConnectionData) returns (ConnectionResult) {} + rpc Disconnect(Empty) returns (Empty) {} +} \ No newline at end of file diff --git a/examples/ipv6only-app/esp32/.gitignore b/examples/ipv6only-app/esp32/.gitignore new file mode 100644 index 00000000000000..234526a082ad26 --- /dev/null +++ b/examples/ipv6only-app/esp32/.gitignore @@ -0,0 +1,5 @@ +*.vscode + +/build/ +/sdkconfig +/sdkconfig.old diff --git a/examples/ipv6only-app/esp32/CMakeLists.txt b/examples/ipv6only-app/esp32/CMakeLists.txt new file mode 100644 index 00000000000000..b5f8d8ed6b0c4b --- /dev/null +++ b/examples/ipv6only-app/esp32/CMakeLists.txt @@ -0,0 +1,38 @@ +# +# Copyright (c) 2021 Project CHIP Authors +# All rights reserved. +# +# 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. + +# 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) + +set(EXTRA_COMPONENT_DIRS + "${CMAKE_CURRENT_LIST_DIR}/third_party/connectedhomeip/config/esp32/components" +) + +project(chip-ipv6only-app) +idf_build_set_property(CXX_COMPILE_OPTIONS "-std=c++17;-Os;-DLWIP_IPV6_SCOPES=0;-DCHIP_HAVE_CONFIG_H" APPEND) +idf_build_set_property(C_COMPILE_OPTIONS "-Os;-DLWIP_IPV6_SCOPES=0" APPEND) + +get_filename_component(CHIP_ROOT ./third_party/connectedhomeip REALPATH) +include(third_party/connectedhomeip/third_party/pigweed/repo/pw_build/pigweed.cmake) +pw_set_backend(pw_log pw_log_basic) +pw_set_backend(pw_assert pw_assert_log) +pw_set_backend(pw_sys_io pw_sys_io.esp32) + +add_subdirectory(third_party/connectedhomeip/third_party/pigweed/repo) +add_subdirectory(third_party/connectedhomeip/third_party/nanopb/repo) +add_subdirectory(third_party/connectedhomeip/examples/platform/esp32/pw_sys_io) diff --git a/examples/ipv6only-app/esp32/README.md b/examples/ipv6only-app/esp32/README.md new file mode 100644 index 00000000000000..583e386f6865cf --- /dev/null +++ b/examples/ipv6only-app/esp32/README.md @@ -0,0 +1,103 @@ +# CHIP ESP32 IPV6 Only Example Application + +This application implements ESP32 wifi control to support IPV6 tests. + +Once connected the application acts as a UDP echo server and will echo udp +messages it receives, this can be used to test for disconnect events. + +- [CHIP ESP32 IPV6 Only Example Application](#chip-esp32-ipv6-only-example-application) + - [Building the Example Application](#building-the-example-application) + - [To build the application, follow these steps:](#to-build-the-application-follow-these-steps) + - [Testing the Example Application](#testing-the-example-application) + +--- + +## Building the Example Application + +Building the example application requires the use of the Espressif ESP32 IoT +Development Framework and the xtensa-esp32-elf toolchain. + +The VSCode devcontainer has these components pre-installed, so you can skip this +step. To install these components manually, follow these steps: + +- Clone the Espressif ESP-IDF and checkout release/v4.1 branch + + $ mkdir ${HOME}/tools + $ cd ${HOME}/tools + $ git clone https://github.com/espressif/esp-idf.git + $ cd esp-idf + $ git checkout release/v4.3 + $ git submodule update --init + $ ./install.sh + +- Install ninja-build + + $ sudo apt-get install ninja-build + +### To build the application, follow these steps: + +Currently building in VSCode _and_ deploying from native is not supported, so +make sure the IDF_PATH has been exported(See the manual setup steps above). + +- Setting up the environment + + $ cd ${HOME}/tools/esp-idf + $ ./install.sh + $ . ./export.sh + $ cd {path-to-connectedhomeip} + + To download and install packages. + + $ source ./scripts/bootstrap.sh + $ source ./scripts/activate.sh + + If packages are already installed then simply activate them. + + $ source ./scripts/activate.sh + +- Configuration Options + + To choose from the different configuration options, run menuconfig + + $ idf.py menuconfig + + This example uses UART0 for serial communication. You can change this through + `PW RPC Example Configuration`. As a result, the console has been shifted to UART1 + You can change this through `Component config` -> `Common ESP-related` -> + `UART for console output` + +- Build the demo application. + + $ idf.py build + +- After building the application, to flash it outside of VSCode, connect your + device via USB. Then run the following command to flash the demo application + onto the device and then monitor its output. If necessary, replace + `/dev/tty.SLAB_USBtoUART`(MacOS) with the correct USB device name for your + system(like `/dev/ttyUSB0` on Linux). Note that sometimes you might have to + press and hold the `boot` button on the device while it's trying to connect + before flashing. For ESP32-DevKitC devices this is labeled in the + [functional description diagram](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-devkitc.html#functional-description). + + $ idf.py flash -p /dev/tty.SLAB_USBtoUART + + Note: Some users might have to install the + [VCP driver](https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers) + before the device shows up on `/dev/tty`. + +## Testing the Example Application + +Run the following command to start an interactive Python shell, where the Wifi +RPC commands can be invoked: + + python -m pw_hdlc.rpc_console --device /dev/ttyUSB0 -b 115200 ../common/gdm_wifi_base_rpc.proto + +An example flow of performing a scan, connecting, and getting the IPv6 address: + + scan = rpcs.chip.rpc.GDMWifiBase.StartScan(pw_rpc_timeout_s=5) + ap = next(filter(lambda a: b"SSID\000" in a.ssid, next(scan.responses()).aps)) + + connect = protos.chip.rpc.ConnectionData(ssid=ap.ssid,security_type=ap.security_type,secret=b"PASSWORD") + rpcs.chip.rpc.GDMWifiBase.Connect(connect, pw_rpc_timeout_s=10) + + rpcs.chip.rpc.GDMWifiBase.GetIP6Address() diff --git a/examples/ipv6only-app/esp32/include/gdm_wifi_service.h b/examples/ipv6only-app/esp32/include/gdm_wifi_service.h new file mode 100644 index 00000000000000..798ea1b556cac5 --- /dev/null +++ b/examples/ipv6only-app/esp32/include/gdm_wifi_service.h @@ -0,0 +1,163 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * 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. + */ + +#pragma once + +#include "esp_event.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "gdm_service/gdm_wifi_base_rpc.rpc.pb.h" +#include "pw_status/status.h" +#include "pw_status/try.h" + +namespace chip { +namespace rpc { + +class GDMWifiBase final : public generated::GDMWifiBase +{ +public: + // Singleton + static GDMWifiBase & Instance() { return instance_; } + + // Initalize the wifi station + pw::Status Init(); + + // Blocks the calling thread until wifi connection is completed succesfully. + // NOTE: Currently only supports blocking a single thread. + void BlockUntilWifiConnected() { xSemaphoreTake(wifi_connected_semaphore_, portMAX_DELAY); } + + // The following functions are the RPC handlers + + pw::Status GetChannel(ServerContext &, const chip_rpc_Empty & request, chip_rpc_Channel & response) + { + uint8_t channel = 0; + wifi_second_chan_t second; + PW_TRY(EspToPwStatus(esp_wifi_get_channel(&channel, &second))); + response.channel = channel; + return pw::OkStatus(); + } + + pw::Status GetSsid(ServerContext &, const chip_rpc_Empty & request, chip_rpc_Ssid & response) + { + wifi_config_t config; + PW_TRY(EspToPwStatus(esp_wifi_get_config(ESP_IF_WIFI_STA, &config))); + size_t size = std::min(sizeof(response.ssid.bytes), sizeof(config.sta.ssid)); + memcpy(response.ssid.bytes, config.sta.ssid, sizeof(response.ssid.bytes)); + response.ssid.size = size; + return pw::OkStatus(); + } + + pw::Status GetState(ServerContext &, const chip_rpc_Empty & request, chip_rpc_State & response) + { + wifi_ap_record_t ap_info; + esp_err_t err = esp_wifi_sta_get_ap_info(&ap_info); + PW_TRY(EspToPwStatus(err)); + response.connected = (err != ESP_ERR_WIFI_NOT_CONNECT); + return pw::OkStatus(); + } + + pw::Status GetMacAddress(ServerContext &, const chip_rpc_Empty & request, chip_rpc_MacAddress & response) + { + uint8_t mac[6]; + PW_TRY(EspToPwStatus(esp_wifi_get_mac(ESP_IF_WIFI_STA, mac))); + sprintf(response.mac_address, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return pw::OkStatus(); + } + + pw::Status GetWiFiInterface(ServerContext &, const chip_rpc_Empty & request, chip_rpc_WiFiInterface & response) + { + wifi_ap_record_t ap_info; + PW_TRY(EspToPwStatus(esp_wifi_sta_get_ap_info(&ap_info))); + sprintf(response.interface, "STA"); + return pw::OkStatus(); + } + + pw::Status GetIP4Address(ServerContext &, const chip_rpc_Empty & request, chip_rpc_IP4Address & response) + { + esp_netif_ip_info_t ip_info; + PW_TRY(EspToPwStatus(esp_netif_get_ip_info(esp_netif_, &ip_info))); + sprintf(response.address, IPSTR, IP2STR(&ip_info.ip)); + return pw::OkStatus(); + } + + pw::Status GetIP6Address(ServerContext &, const chip_rpc_Empty & request, chip_rpc_IP6Address & response) + { + esp_ip6_addr_t ip6{ 0 }; + PW_TRY(EspToPwStatus(esp_netif_get_ip6_linklocal(esp_netif_, &ip6))); + sprintf(response.address, IPV6STR, IPV62STR(ip6)); + return pw::OkStatus(); + } + + // NOTE: Currently this is blocking, it can be made non-blocking if needed + // but would require another worker thread to handle the scanning. + void StartScan(ServerContext &, const chip_rpc_ScanConfig & request, ServerWriter & writer); + + pw::Status StopScan(ServerContext &, const chip_rpc_Empty & request, chip_rpc_Empty & response) + { + esp_wifi_scan_stop(); + return pw::OkStatus(); + } + + pw::Status Connect(ServerContext &, const chip_rpc_ConnectionData & request, chip_rpc_ConnectionResult & response); + + pw::Status Disconnect(ServerContext &, const chip_rpc_Empty & request, chip_rpc_Empty & response) + { + PW_TRY(EspToPwStatus(esp_wifi_disconnect())); + return pw::OkStatus(); + } + +private: + static GDMWifiBase instance_; + esp_netif_t * esp_netif_ = nullptr; + SemaphoreHandle_t wifi_connected_semaphore_; + + static constexpr pw::Status EspToPwStatus(esp_err_t err) + { + switch (err) + { + case ESP_OK: + return pw::OkStatus(); + case ESP_ERR_WIFI_NOT_INIT: + return pw::Status::FailedPrecondition(); + case ESP_ERR_INVALID_ARG: + return pw::Status::InvalidArgument(); + case ESP_ERR_ESP_NETIF_INVALID_PARAMS: + return pw::Status::InvalidArgument(); + case ESP_ERR_WIFI_IF: + return pw::Status::NotFound(); + case ESP_ERR_WIFI_NOT_CONNECT: + return pw::Status::FailedPrecondition(); + case ESP_ERR_WIFI_NOT_STARTED: + return pw::Status::FailedPrecondition(); + case ESP_ERR_WIFI_CONN: + return pw::Status::Internal(); + case ESP_FAIL: + return pw::Status::Internal(); + default: + return pw::Status::Unknown(); + } + } + + static void WifiEventHandler(void * arg, esp_event_base_t event_base, int32_t event_id, void * event_data); +}; + +} // namespace rpc +} // namespace chip diff --git a/examples/ipv6only-app/esp32/main/CMakeLists.txt b/examples/ipv6only-app/esp32/main/CMakeLists.txt new file mode 100644 index 00000000000000..b416c08c54689e --- /dev/null +++ b/examples/ipv6only-app/esp32/main/CMakeLists.txt @@ -0,0 +1,88 @@ +# +# Copyright (c) 2021 Project CHIP Authors +# All rights reserved. +# +# 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. + +idf_component_register(INCLUDE_DIRS + "${CMAKE_CURRENT_LIST_DIR}" + "${CMAKE_CURRENT_LIST_DIR}/../include" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_sys_io/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_assert/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_assert_log/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_assert_log/public_overrides" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_bytes/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_checksum/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_containers/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_hdlc/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_log/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_log_basic/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_log_basic/public_overrides" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_span/public_overrides" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_span/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_polyfill/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_polyfill/standard_library_public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_polyfill/public_overrides" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_rpc/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_rpc/nanopb/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_rpc/raw/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_protobuf/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_status/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_stream/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_result/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_varint/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_preprocessor/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/pigweed/repo/pw_rpc/system_server/public" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/third_party/nanopb/repo" + "${CMAKE_SOURCE_DIR}/../../platform/esp32/pw_sys_io/public" + "${CMAKE_SOURCE_DIR}/../../platform/esp32" + "${CMAKE_SOURCE_DIR}/../../common/pigweed" + "${CMAKE_SOURCE_DIR}/../../common/pigweed/esp32" + "${CMAKE_SOURCE_DIR}/../../../src/lib/support" + "${IDF_PATH}/components/freertos/include/freertos" + + SRC_DIRS + "${CMAKE_CURRENT_LIST_DIR}" + "${CMAKE_SOURCE_DIR}/../../platform/esp32" + "${CMAKE_SOURCE_DIR}/../../common/pigweed" + "${CMAKE_SOURCE_DIR}/../../common/pigweed/esp32" + PRIV_REQUIRES bt chip) + +idf_component_get_property(chip_lib chip COMPONENT_LIB) + +set(WRAP_FUNCTIONS esp_log_write) +target_link_libraries(${chip_lib} INTERFACE "-Wl,--wrap=${WRAP_FUNCTIONS}") + +set_property(TARGET ${chip_lib} APPEND PROPERTY LINK_LIBRARIES ${COMPONENT_LIB}) + +get_filename_component(CHIP_ROOT ../third_party/connectedhomeip REALPATH) +get_filename_component(IPV6_EXAMPLE_ROOT ../.. REALPATH) +set(PIGWEED_ROOT "${CHIP_ROOT}/third_party/pigweed/repo") +include(${PIGWEED_ROOT}/pw_build/pigweed.cmake) +include(${PIGWEED_ROOT}/pw_protobuf_compiler/proto.cmake) +set(dir_pw_third_party_nanopb "${CHIP_ROOT}/third_party/nanopb/repo" CACHE STRING "" FORCE) + +pw_proto_library(gdm_service + SOURCES + ${IPV6_EXAMPLE_ROOT}/common/gdm_wifi_base_rpc.proto + INPUTS + ${IPV6_EXAMPLE_ROOT}/common/gdm_wifi_base_rpc.options + PREFIX + gdm_service + STRIP_PREFIX + ${IPV6_EXAMPLE_ROOT}/common +) + +target_link_libraries(${COMPONENT_LIB} PUBLIC + gdm_service.nanopb_rpc +) diff --git a/examples/ipv6only-app/esp32/main/Kconfig.projbuild b/examples/ipv6only-app/esp32/main/Kconfig.projbuild new file mode 100644 index 00000000000000..a1c84b60f9c865 --- /dev/null +++ b/examples/ipv6only-app/esp32/main/Kconfig.projbuild @@ -0,0 +1,35 @@ +menu "PW RPC Example Configuration" + + config EXAMPLE_UART_PORT_NUM + int "UART port number" + range 0 2 if IDF_TARGET_ESP32 + default 0 if IDF_TARGET_ESP32 + help + UART communication port number for the example. + See UART documentation for available port numbers. + + config EXAMPLE_UART_BAUD_RATE + int "UART communication speed" + range 1200 115200 + default 115200 + help + UART communication speed for Modbus example. + + config EXAMPLE_UART_RXD + int "UART RXD pin number" + range 0 34 if IDF_TARGET_ESP32 + default 3 + help + GPIO number for UART RX pin. See UART documentation for more information + about available pin numbers for UART. + + config EXAMPLE_UART_TXD + int "UART TXD pin number" + range 0 34 if IDF_TARGET_ESP32 + default 1 + help + GPIO number for UART TX pin. See UART documentation for more information + about available pin numbers for UART. + + +endmenu diff --git a/examples/ipv6only-app/esp32/main/gdm_wifi_service.cpp b/examples/ipv6only-app/esp32/main/gdm_wifi_service.cpp new file mode 100644 index 00000000000000..147d273bb36228 --- /dev/null +++ b/examples/ipv6only-app/esp32/main/gdm_wifi_service.cpp @@ -0,0 +1,341 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * 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 "gdm_wifi_service.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "pw_containers/flat_map.h" + +namespace chip { +namespace rpc { + +namespace { + +const char * TAG = "ipv6only"; + +constexpr pw::containers::FlatMap kChannelToFreqMap({ { + { 1, 2412 }, { 2, 2417 }, { 3, 2422 }, { 4, 2427 }, { 5, 2432 }, { 6, 2437 }, { 7, 2442 }, { 8, 2447 }, + { 9, 2452 }, { 10, 2457 }, { 11, 2462 }, { 12, 2467 }, { 13, 2472 }, { 14, 2484 }, { 32, 5160 }, { 34, 5170 }, + { 36, 5180 }, { 38, 5190 }, { 40, 5200 }, { 42, 5210 }, { 44, 5220 }, { 46, 5230 }, { 48, 5240 }, { 50, 5250 }, + { 52, 5260 }, { 54, 5270 }, { 56, 5280 }, { 58, 5290 }, { 60, 5300 }, { 62, 5310 }, { 64, 5320 }, { 68, 5340 }, + { 96, 5480 }, { 100, 5500 }, { 102, 5510 }, { 104, 5520 }, { 106, 5530 }, { 108, 5540 }, { 110, 5550 }, { 112, 5560 }, + { 114, 5570 }, { 116, 5580 }, { 118, 5590 }, { 120, 5600 }, { 122, 5610 }, { 124, 5620 }, { 126, 5630 }, { 128, 5640 }, + { 132, 5660 }, { 134, 5670 }, { 136, 5680 }, { 138, 5690 }, { 140, 5700 }, { 142, 5710 }, { 144, 5720 }, { 149, 5745 }, + { 151, 5755 }, { 153, 5765 }, { 155, 5775 }, { 157, 5785 }, { 159, 5795 }, { 161, 5805 }, { 165, 5825 }, { 169, 5845 }, + { 173, 5865 }, { 183, 4915 }, { 184, 4920 }, { 185, 4925 }, { 187, 4935 }, { 188, 4940 }, { 189, 4945 }, { 192, 4960 }, + { 196, 4980 }, +} }); + +// Class handles the event handlers needed for station startup. +// Creating the object will register all handlers needed, destroying will +// unregister. The object is only needed during initalization, after the station +// is up it is safe to destroy this object. +class WifiInitStationEventHandler +{ +public: + WifiInitStationEventHandler() + { + handler_context_.event_group = xEventGroupCreate(); + esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &EventHandler, &handler_context_); + } + + ~WifiInitStationEventHandler() + { + vEventGroupDelete(handler_context_.event_group); + esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &EventHandler); + } + + pw::Status WaitForStationUp() + { + EventBits_t bits = xEventGroupWaitBits(handler_context_.event_group, kWifiStationUpBit, pdFALSE, pdFALSE, portMAX_DELAY); + if (!(bits & kWifiStationUpBit)) + { + return pw::Status::Unknown(); + } + return pw::OkStatus(); + } + +private: + static constexpr uint8_t kWifiStationUpBit = BIT0; + struct HandlerContext + { + size_t retry_count = 0; + uint8_t error_code = 0; + EventGroupHandle_t event_group; + } handler_context_; + + static void EventHandler(void * arg, esp_event_base_t event_base, int32_t event_id, void * event_data) + { + HandlerContext * context = static_cast(arg); + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) + { + ESP_LOGI(TAG, "EVENT: WIFI_EVENT_STATION_START"); + xEventGroupSetBits(context->event_group, kWifiStationUpBit); + } + } +}; + +// Class handles the event handlers needed for wifi connection. +// Creating the object will register all handlers needed, destroying will +// unregister. The object is only needed during connection, once connected +// is up it is safe to destroy this object. +class WifiConnectionEventHandler +{ +public: + WifiConnectionEventHandler(esp_netif_t * esp_netif) + { + handler_context_.esp_netif = esp_netif; + handler_context_.event_group = xEventGroupCreate(); + esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &EventHandler, &handler_context_); + esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &EventHandler, &handler_context_); + esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &EventHandler, &handler_context_); + } + + ~WifiConnectionEventHandler() + { + esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &EventHandler); + esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &EventHandler); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &EventHandler); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_GOT_IP6, &EventHandler); + vEventGroupDelete(handler_context_.event_group); + } + + // Waits for the events to determine if connected succesfully. + pw::Status WaitForConnection(chip_rpc_ConnectionResult * result) + { + EventBits_t bits = xEventGroupWaitBits(handler_context_.event_group, kWifiFailBit | kWifiIpv6ConnectedBit, pdFALSE, pdFALSE, + portMAX_DELAY); + if (bits & kWifiIpv6ConnectedBit) + { + result->error = chip_rpc_CONNECTION_ERROR_OK; + } + else if (bits & kWifiFailBit) + { + result->error = handler_context_.error_code; + return pw::Status::Unavailable(); + } + else + { + ESP_LOGE(TAG, "UNEXPECTED EVENT"); + return pw::Status::Unknown(); + } + return pw::OkStatus(); + } + +private: + static constexpr size_t kWiFiConnectRetryMax = 5; + static constexpr uint8_t kWifiIpv6ConnectedBit = BIT0; + static constexpr uint8_t kWifiIpv4ConnectedBit = BIT1; + static constexpr uint8_t kWifiFailBit = BIT2; + + struct HandlerContext + { + size_t retry_count = 0; + enum _chip_rpc_CONNECTION_ERROR error_code; + EventGroupHandle_t event_group; + esp_netif_t * esp_netif; + } handler_context_; + + static void EventHandler(void * arg, esp_event_base_t event_base, int32_t event_id, void * event_data) + { + HandlerContext * context = static_cast(arg); + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) + { + ESP_LOGI(TAG, "EVENT: WIFI_EVENT_STA_DISCONNECTED, reason: %d", + (static_cast(event_data))->reason); + if (context->retry_count < kWiFiConnectRetryMax) + { + esp_wifi_connect(); + context->retry_count++; + ESP_LOGI(TAG, "retry to connect to the AP"); + } + else + { + context->error_code = + static_cast<_chip_rpc_CONNECTION_ERROR>((static_cast(event_data))->reason); + xEventGroupSetBits(context->event_group, kWifiFailBit); + } + } + else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) + { + ESP_ERROR_CHECK(esp_netif_create_ip6_linklocal(context->esp_netif)); + ESP_LOGI(TAG, "Connected, link local address created"); + } + else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) + { + auto * event = static_cast(event_data); + ESP_LOGI(TAG, "got ip4: " IPSTR, IP2STR(&event->ip_info.ip)); + xEventGroupSetBits(context->event_group, kWifiIpv4ConnectedBit); + } + else if (event_base == IP_EVENT && event_id == IP_EVENT_GOT_IP6) + { + auto * event = static_cast(event_data); + ESP_LOGI(TAG, "got ip6: " IPV6STR, IPV62STR(event->ip6_info.ip)); + xEventGroupSetBits(context->event_group, kWifiIpv6ConnectedBit); + } + } +}; + +// These are potentially large objects for the scan results. +constexpr size_t kScanRecordsMax = sizeof(chip_rpc_ScanResults().aps) / sizeof(chip_rpc_ScanResult); +chip_rpc_ScanResults out_scan_records; +wifi_ap_record_t scan_records[kScanRecordsMax] = { 0 }; + +} // namespace + +GDMWifiBase GDMWifiBase::instance_; + +pw::Status GDMWifiBase::Init() +{ + wifi_connected_semaphore_ = xSemaphoreCreateBinary(); + PW_TRY(EspToPwStatus(esp_netif_init())); + PW_TRY(EspToPwStatus(esp_event_loop_create_default())); + esp_netif_ = esp_netif_create_default_wifi_sta(); + PW_TRY(EspToPwStatus(esp_netif_dhcpc_stop(esp_netif_))); + + WifiInitStationEventHandler event_handler; + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + PW_TRY(EspToPwStatus(esp_wifi_init(&cfg))); + PW_TRY(EspToPwStatus(esp_wifi_set_mode(WIFI_MODE_STA))); + PW_TRY(EspToPwStatus(esp_wifi_start())); + + PW_TRY(event_handler.WaitForStationUp()); + return pw::OkStatus(); +} + +pw::Status GDMWifiBase::Connect(ServerContext &, const chip_rpc_ConnectionData & request, chip_rpc_ConnectionResult & response) +{ + wifi_config_t wifi_config { + .sta = { + /* Setting a password implies station will connect to all security modes including WEP/WPA. + * However these modes are deprecated and not advisable to be used. Incase your Access point + * doesn't support WPA2, these mode can be enabled by commenting below line */ + .threshold = { + .authmode = static_cast(request.security_type), + }, + + .pmf_cfg = { + .capable = true, + .required = false + }, + }, + }; + memcpy(wifi_config.sta.ssid, request.ssid.bytes, + std::min(sizeof(wifi_config.sta.ssid), static_cast(request.ssid.size))); + memcpy(wifi_config.sta.password, request.secret.bytes, + std::min(sizeof(wifi_config.sta.password), static_cast(request.secret.size))); + + WifiConnectionEventHandler event_handler(esp_netif_); + PW_TRY(EspToPwStatus(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config))); + esp_err_t err = esp_wifi_connect(); + + if (ESP_ERR_WIFI_SSID == err) + { + ESP_LOGI(TAG, "AP not found SSID:%s", wifi_config.sta.ssid); + response.error = chip_rpc_CONNECTION_ERROR_NO_AP_FOUND; + return pw::Status::NotFound(); + } + if (pw::Status status = event_handler.WaitForConnection(&response); status.ok()) + { + ESP_LOGI(TAG, "connected to ap SSID:%s password:%s", wifi_config.sta.ssid, wifi_config.sta.password); + xSemaphoreGive(wifi_connected_semaphore_); + } + else + { + ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s", wifi_config.sta.ssid, wifi_config.sta.password); + return status; + } + return pw::OkStatus(); +} + +void GDMWifiBase::StartScan(ServerContext &, const chip_rpc_ScanConfig & request, ServerWriter & writer) +{ + wifi_scan_config_t scan_config{ 0 }; + if (request.ssid_count != 0) + { + scan_config.ssid = const_cast(reinterpret_cast(request.ssid[0].bytes)); + } + if (request.bssid_count != 0) + { + scan_config.bssid = const_cast(reinterpret_cast(request.bssid[0].bytes)); + } + scan_config.channel = request.channel; + scan_config.show_hidden = request.show_hidden; + scan_config.scan_type = static_cast(request.active_scan); + if (request.active_scan) + { + scan_config.scan_time.active.min = request.scan_time_min_ms; + scan_config.scan_time.active.max = request.scan_time_max_ms; + } + else + { + scan_config.scan_time.passive = request.scan_time_min_ms; + } + + auto err = esp_wifi_scan_start(&scan_config, true /* block */); + if (ESP_OK != err) + { + ESP_LOGI(TAG, "Error starting scan: %d", err); + return; + } + + // Output scan results + uint16_t num_scan_records = kScanRecordsMax; + err = esp_wifi_scan_get_ap_records(&num_scan_records, scan_records); + if (ESP_OK != err) + { + ESP_LOGI(TAG, "Error getting scanned APs: %d", err); + num_scan_records = 0; + } + ESP_LOGI(TAG, "%d", num_scan_records); + out_scan_records.aps_count = num_scan_records; + + for (size_t i = 0; i < num_scan_records; ++i) + { + memcpy(out_scan_records.aps[i].ssid.bytes, scan_records[i].ssid, sizeof(out_scan_records.aps[i].ssid.bytes)); + out_scan_records.aps[i].ssid.size = sizeof(out_scan_records.aps[i].ssid.bytes); + memcpy(out_scan_records.aps[i].bssid.bytes, scan_records[i].bssid, sizeof(out_scan_records.aps[i].bssid.bytes)); + out_scan_records.aps[i].bssid.size = sizeof(out_scan_records.aps[i].bssid.bytes); + out_scan_records.aps[i].security_type = static_cast(scan_records[i].authmode); + out_scan_records.aps[i].channel = scan_records[i].primary; + auto found_channel = kChannelToFreqMap.find(scan_records[i].primary); + out_scan_records.aps[i].frequency = (found_channel ? found_channel->second : 0); + out_scan_records.aps[i].signal = scan_records[i].rssi; + } + writer.Write(out_scan_records); + writer.Finish(); +} + +void GDMWifiBase::WifiEventHandler(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_DISCONNECTED) + { + ESP_LOGI(TAG, "******** DISCONNECTED FROM AP *********"); + esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &WifiEventHandler); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_GOT_IP6, &WifiEventHandler); + } + else if (event_base == IP_EVENT && event_id == IP_EVENT_GOT_IP6) + { + // This is in case not only link-local address is provided + auto * event = static_cast(event_data); + ESP_LOGI(TAG, "got ip6 :" IPV6STR, IPV62STR(event->ip6_info.ip)); + } +} + +} // namespace rpc +} // namespace chip diff --git a/examples/ipv6only-app/esp32/main/main.cpp b/examples/ipv6only-app/esp32/main/main.cpp new file mode 100644 index 00000000000000..4531241c754d8b --- /dev/null +++ b/examples/ipv6only-app/esp32/main/main.cpp @@ -0,0 +1,152 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * 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 "PigweedLoggerMutex.h" +#include "RpcService.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "gdm_wifi_service.h" +#include "nvs_flash.h" +#include "pw_rpc/server.h" + +#include "lwip/err.h" +#include "lwip/sockets.h" +#include "lwip/sys.h" + +namespace { + +const char * TAG = "ipv6only"; + +constexpr size_t kUdpBufferSize = 512; + +constexpr size_t kRpcStackSizeBytes = (6 * 1024); +constexpr uint8_t kRpcTaskPriority = 5; +constexpr size_t kTestStackSizeBytes = (8 * 1024); +constexpr uint8_t kTestTaskPriority = 5; + +TaskHandle_t rpcTaskHandle; +TaskHandle_t testTaskHandle; + +void UdpReceiver(void * pvParameters) +{ + int portno; // port to listen on + struct sockaddr_in6 serveraddr; // server's addr + char buf[kUdpBufferSize]; // rx message buf + char * hostaddrp; // dotted decimal host addr string + int optval; // flag value for setsockopt + int n; // message byte size + int sockfd = 0; + unsigned int clientlen; // byte size of client's address + struct sockaddr_in6 clientaddr; // client addr + + while (1) + { + // Start the udp server after the wifi is connectd. + chip::rpc::GDMWifiBase::Instance().BlockUntilWifiConnected(); + ESP_LOGI(TAG, "UDP server starting"); + + portno = 8765; + // socket: create the parent socket + sockfd = socket(AF_INET6, SOCK_DGRAM, 0); + if (sockfd < 0) + { + ESP_LOGE(TAG, "ERROR opening socket"); + assert(0); + return; + } + + // setsockopt: Handy debugging trick that lets + // us rerun the server immediately after we kill it; + // otherwise we have to wait about 20 secs. + // Eliminates "ERROR on binding: Address already in use" error. + optval = 1; + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, static_cast(&optval), sizeof(int)); + + // build the server's Internet address + memset(&serveraddr, 0, sizeof(serveraddr)); + serveraddr.sin6_len = sizeof(serveraddr); + serveraddr.sin6_family = AF_INET6; + serveraddr.sin6_addr = in6addr_any; + serveraddr.sin6_port = htons((unsigned short) portno); + + // bind: associate the parent socket with a port + if (bind(sockfd, reinterpret_cast(&serveraddr), sizeof(serveraddr)) < 0) + { + ESP_LOGE(TAG, "ERROR on binding"); + assert(0); + vTaskDelete(NULL); + } + + ESP_LOGI(TAG, "UDP server bound to port %d", portno); + + // main loop: wait for a datagram, then respond + clientlen = sizeof(clientaddr); + wifi_ap_record_t ap_info; + fd_set readset; + while (ESP_OK == esp_wifi_sta_get_ap_info(&ap_info)) + { + // recvfrom: receive a UDP datagram from a client + memset(buf, 0, sizeof(buf)); + + FD_ZERO(&readset); + FD_SET(sockfd, &readset); + + int select_err = select(sockfd + 1, &readset, nullptr, nullptr, nullptr); + if (select_err < 0) + continue; + + n = recvfrom(sockfd, buf, kUdpBufferSize, 0, reinterpret_cast(&clientaddr), &clientlen); + if (n < 0) + continue; + // Echo back + n = sendto(sockfd, buf, n, 0, reinterpret_cast(&clientaddr), clientlen); + } + } + // Never returns +} + +void RegisterServices(pw::rpc::Server & server) +{ + server.RegisterService(chip::rpc::GDMWifiBase::Instance()); +} + +void RunRpcService(void *) +{ + ::chip::rpc::Start(RegisterServices, &::chip::rpc::logger_mutex); +} + +} // namespace + +extern "C" void app_main() +{ + PigweedLogger::init(); + + // 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, "Wifi Init: %s", pw_StatusString(chip::rpc::GDMWifiBase::Instance().Init())); + ESP_LOGI(TAG, "----------- chip-esp32-ipv6-example starting -----------"); + + xTaskCreate(RunRpcService, "RPC", kRpcStackSizeBytes / sizeof(StackType_t), nullptr, kRpcTaskPriority, &rpcTaskHandle); + xTaskCreate(UdpReceiver, "TestTask", kTestStackSizeBytes / sizeof(StackType_t), nullptr, kTestTaskPriority, &testTaskHandle); +} diff --git a/examples/ipv6only-app/esp32/partitions.csv b/examples/ipv6only-app/esp32/partitions.csv new file mode 100644 index 00000000000000..b338ff11a11589 --- /dev/null +++ b/examples/ipv6only-app/esp32/partitions.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, , 0x6000, +phy_init, data, phy, , 0x1000, +# Factory partition size about 1.9MB +factory, app, factory, , 1945K, diff --git a/examples/ipv6only-app/esp32/sdkconfig.defaults b/examples/ipv6only-app/esp32/sdkconfig.defaults new file mode 100644 index 00000000000000..e4ce5edbb3c0fd --- /dev/null +++ b/examples/ipv6only-app/esp32/sdkconfig.defaults @@ -0,0 +1,43 @@ +# +# Copyright (c) 2021 Project CHIP Authors +# Copyright (c) 2018 Nest Labs, Inc. +# All rights reserved. +# +# 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. +# +# Description: +# Some useful defaults for the demo app configuration. +# + + +# Default to 921600 baud when flashing and monitoring device +CONFIG_ESPTOOLPY_BAUD_921600B=y +CONFIG_ESPTOOLPY_BAUD=921600 +CONFIG_ESPTOOLPY_COMPRESSED=y +CONFIG_ESPTOOLPY_MONITOR_BAUD_115200B=y +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 + +# UART +CONFIG_ESP_CONSOLE_UART_CUSTOM=y +CONFIG_ESP_CONSOLE_UART_CUSTOM_NUM_1=y +CONFIG_ESP_CONSOLE_UART_NUM=1 + +#enable lwip ipv6 autoconfig +CONFIG_LWIP_IPV6_AUTOCONFIG=y + +# Use a custom partition table +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" + +# Enable Pigweed RPC Library in CHIP +CONFIG_ENABLE_PW_RPC=y diff --git a/examples/ipv6only-app/esp32/third_party/connectedhomeip b/examples/ipv6only-app/esp32/third_party/connectedhomeip new file mode 120000 index 00000000000000..11a54ed360106c --- /dev/null +++ b/examples/ipv6only-app/esp32/third_party/connectedhomeip @@ -0,0 +1 @@ +../../../../ \ No newline at end of file diff --git a/examples/lock-app/esp32/main/Rpc.cpp b/examples/lock-app/esp32/main/Rpc.cpp index ad02bf6ac21878..16ffe0f249d5c9 100644 --- a/examples/lock-app/esp32/main/Rpc.cpp +++ b/examples/lock-app/esp32/main/Rpc.cpp @@ -39,25 +39,6 @@ using chip::DeviceLayer::ConfigurationMgr; static bool uartInitialised; -extern "C" void __wrap_esp_log_write(esp_log_level_t level, const char * tag, const char * format, ...) -{ - va_list v; - va_start(v, format); -#ifndef CONFIG_LOG_DEFAULT_LEVEL_NONE - if (uartInitialised) - { - char formattedMsg[CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE]; - size_t len = vsnprintf(formattedMsg, sizeof formattedMsg, format, v); - if (len >= sizeof formattedMsg) - { - len = sizeof formattedMsg - 1; - } - PigweedLogger::putString(formattedMsg, len); - } -#endif - va_end(v); -} - namespace chip { namespace rpc { diff --git a/examples/pigweed-app/esp32/main/main.cpp b/examples/pigweed-app/esp32/main/main.cpp index 9ef7d9f21e2ac0..896339fd9e5040 100644 --- a/examples/pigweed-app/esp32/main/main.cpp +++ b/examples/pigweed-app/esp32/main/main.cpp @@ -35,27 +35,6 @@ const char * TAG = "chip-pigweed-app"; -static bool uartInitialised; - -extern "C" void __wrap_esp_log_write(esp_log_level_t level, const char * tag, const char * format, ...) -{ - va_list v; - va_start(v, format); -#ifndef CONFIG_LOG_DEFAULT_LEVEL_NONE - if (uartInitialised) - { - char formattedMsg[CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE]; - size_t len = vsnprintf(formattedMsg, sizeof formattedMsg, format, v); - if (len >= sizeof formattedMsg) - { - len = sizeof formattedMsg - 1; - } - PigweedLogger::putString(formattedMsg, len); - } -#endif - va_end(v); -} - namespace { using std::byte; @@ -81,7 +60,6 @@ void RunRpcService(void *) extern "C" void app_main() { PigweedLogger::init(); - uartInitialised = true; ESP_LOGI(TAG, "----------- chip-esp32-pigweed-example starting -----------"); diff --git a/examples/platform/esp32/PigweedLogger.cpp b/examples/platform/esp32/PigweedLogger.cpp index 27a9ba903d6c8c..24f43a71c3ab91 100644 --- a/examples/platform/esp32/PigweedLogger.cpp +++ b/examples/platform/esp32/PigweedLogger.cpp @@ -16,8 +16,10 @@ */ #include "FreeRTOS.h" +#include "esp_log.h" #include "pw_sys_io_esp32/init.h" #include "semphr.h" +#include "support/logging/CHIPLogging.h" #include #include @@ -33,6 +35,8 @@ pw::stream::SysIoWriter sWriter; size_t sWriteBufferPos; char sWriteBuffer[kWriteBufferSize]; +bool uartInitialised; + void send() { pw::hdlc::WriteUIFrame(kLogHdlcAddress, std::as_bytes(std::span(sWriteBuffer, sWriteBufferPos)), sWriter); @@ -46,6 +50,7 @@ void init() esp_log_mutex = xSemaphoreCreateMutex(); assert(esp_log_mutex != NULL); pw_sys_io_Init(); + uartInitialised = true; } int putString(const char * buffer, size_t size) @@ -81,4 +86,23 @@ SemaphoreHandle_t * getSemaphore() return &esp_log_mutex; } +extern "C" void __wrap_esp_log_write(esp_log_level_t level, const char * tag, const char * format, ...) +{ + va_list v; + va_start(v, format); +#ifndef CONFIG_LOG_DEFAULT_LEVEL_NONE + if (uartInitialised) + { + char formattedMsg[CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE]; + size_t len = vsnprintf(formattedMsg, sizeof formattedMsg, format, v); + if (len >= sizeof formattedMsg) + { + len = sizeof formattedMsg - 1; + } + PigweedLogger::putString(formattedMsg, len); + } +#endif + va_end(v); +} + } // namespace PigweedLogger