Compare commits
22 Commits
master
...
a5805aceb2
| Author | SHA1 | Date | |
|---|---|---|---|
| a5805aceb2 | |||
| c7e1b4805b | |||
| b667f9c255 | |||
| 199007c0c5 | |||
| c872f1a9ae | |||
| 5fcc0dda1e | |||
| 968d078c07 | |||
| dd9f59e418 | |||
| eb0eb436ed | |||
| 6db9bd85a5 | |||
| e4621f8ad8 | |||
| 1cf86768c0 | |||
| bd923e4fdc | |||
| 2dc06e8d79 | |||
| de2c0c167c | |||
| 0181c72a9d | |||
| f6608075ff | |||
| deccb3186c | |||
| 49b3b46ffe | |||
| 33c875c6bd | |||
| 3f627f08f7 | |||
| bc651eff27 |
@@ -0,0 +1 @@
|
||||
build*
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "core"]
|
||||
path = core
|
||||
url = git@github.com:Gustice/EspIdfDevicePlatform.git
|
||||
Vendored
+5
-3
@@ -2,18 +2,20 @@
|
||||
"configurations": [
|
||||
{
|
||||
"name": "ESP-IDF",
|
||||
"compilerPath": "${config:idf.toolsPathWin}\\tools\\xtensa-esp-elf\\esp-13.2.0_20240530\\xtensa-esp-elf\\bin\\xtensa-esp32-elf-gcc.exe",
|
||||
"compilerPath": "${config:idf.toolsPathWin}\\tools\\riscv32-esp-elf\\esp-13.2.0_20240530\\riscv32-esp-elf\\bin\\riscv32-esp-elf-gcc.exe",
|
||||
"compileCommands": "${config:idf.buildPath}/compile_commands.json",
|
||||
"includePath": [
|
||||
"${config:idf.espIdfPath}/components/**",
|
||||
"${config:idf.espIdfPathWin}/components/**",
|
||||
"${workspaceFolder}/**"
|
||||
"${workspaceFolder}/**",
|
||||
"${workspaceFolder}/core/**"
|
||||
],
|
||||
"browse": {
|
||||
"path": [
|
||||
"${config:idf.espIdfPath}/components",
|
||||
"${config:idf.espIdfPathWin}/components",
|
||||
"${workspaceFolder}"
|
||||
"${workspaceFolder}",
|
||||
"${workspaceFolder}/core"
|
||||
],
|
||||
"limitSymbolsToIncludedHeaders": true
|
||||
}
|
||||
|
||||
Vendored
+8
-6
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"C_Cpp.clang_format_fallbackStyle": "{ BasedOnStyle: LLVM, UseTab: Never, IndentWidth: 4, TabWidth: 4, AllowShortIfStatementsOnASingleLine: false, IndentCaseLabels: false, ColumnLimit: 0, AccessModifierOffset: -2, NamespaceIndentation: All, FixNamespaceComments: false }",
|
||||
|
||||
"C_Cpp.intelliSenseEngine": "default",
|
||||
"idf.adapterTargetName": "esp32",
|
||||
"idf.customExtraPaths": "C:\\Tools\\ESP\\tools\\xtensa-esp-elf-gdb\\14.2_20240403\\xtensa-esp-elf-gdb\\bin;C:\\Tools\\ESP\\tools\\riscv32-esp-elf-gdb\\14.2_20240403\\riscv32-esp-elf-gdb\\bin;C:\\Tools\\ESP\\tools\\xtensa-esp-elf\\esp-13.2.0_20240530\\xtensa-esp-elf\\bin;C:\\Tools\\ESP\\tools\\riscv32-esp-elf\\esp-13.2.0_20240530\\riscv32-esp-elf\\bin;C:\\Tools\\ESP\\tools\\esp32ulp-elf\\2.38_20240113\\esp32ulp-elf\\bin;C:\\Tools\\ESP\\tools\\cmake\\3.24.0\\bin;C:\\Tools\\ESP\\tools\\openocd-esp32\\v0.12.0-esp32-20240318\\openocd-esp32\\bin;C:\\Tools\\ESP\\tools\\ninja\\1.11.1;C:\\Tools\\ESP\\tools\\idf-exe\\1.0.3;C:\\Tools\\ESP\\tools\\ccache\\4.8\\ccache-4.8-windows-x86_64;C:\\Tools\\ESP\\tools\\dfu-util\\0.11\\dfu-util-0.11-win64;C:\\Tools\\ESP\\tools\\esp-rom-elfs\\20240305",
|
||||
"idf.customExtraVars": {
|
||||
"OPENOCD_SCRIPTS": "C:\\Tools\\ESP\\tools\\openocd-esp32\\v0.12.0-esp32-20240318/openocd-esp32/share/openocd/scripts",
|
||||
"IDF_CCACHE_ENABLE": "1",
|
||||
@@ -9,9 +9,11 @@
|
||||
},
|
||||
"idf.espIdfPathWin": "C:\\Tools\\ESP-IDF\\v5.3\\esp-idf",
|
||||
"idf.openOcdConfigs": [
|
||||
"board/esp32-wrover-kit-3.3v.cfg"
|
||||
"board/esp32c6-builtin.cfg"
|
||||
],
|
||||
"idf.portWin": "COM4",
|
||||
"idf.pythonBinPathWin": "C:\\Tools\\ESP\\python_env\\idf5.3_py3.11_env\\Scripts\\python.exe",
|
||||
"idf.toolsPathWin": "C:\\Tools\\ESP"
|
||||
"idf.portWin": "COM9",
|
||||
"idf.toolsPathWin": "C:\\Tools\\ESP",
|
||||
"idf.flashType": "UART",
|
||||
"C_Cpp.autoAddFileAssociations": false,
|
||||
"project.deviceIp": "172.16.17.56"
|
||||
}
|
||||
|
||||
Vendored
+12
@@ -254,6 +254,18 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Start TeraTerm Telnet",
|
||||
"type": "shell",
|
||||
"command": "C:/Program Files (x86)/teraterm5/ttermpro.exe",
|
||||
"args": [
|
||||
"telnet://${config:project.deviceIp}:23",
|
||||
"/W=\"Device-Platform\"",
|
||||
"/T=1",
|
||||
"/F=\"${workspaceFolder}/core/.configs/TelnetTeraTerm.INI\""
|
||||
],
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
+6
-1
@@ -1,4 +1,9 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS
|
||||
"core/components"
|
||||
"core/managed_components"
|
||||
)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(X_WatchController-FW)
|
||||
project(X_WatchController_FW)
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- |
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
|
||||
|
||||
# eth2ap Example
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples. To try a more complex application about Ethernet to WiFi data forwarding, please go to [iot-solution](https://github.com/espressif/esp-iot-solution/tree/release/v1.0/examples/eth2wifi).)
|
||||
|
||||
**Note:** This example uses some internal APIs (e.g. `esp_wifi_internal_tx`) which might get changed between minor versions of ESP-IDF.
|
||||
# Ethernet Example
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
## Overview
|
||||

|
||||
|
||||
The similarities on MAC layer between Ethernet and Wi-Fi make it easy to forward packets from Ethernet to Wi-Fi and vice versa. This example illustrates how to implement a simple "router" which only supports forwarding packets between Ethernet port and Wi-Fi AP interface. In this case, the Ethernet should play the role of WAN (i.e. it can access outside network) so that a mobile device could get access to the Internet when it gets connected to ESP32 through Wi-Fi.
|
||||
This example demonstrates basic usage of `Ethernet driver` together with `esp_netif`. Initialization of the `Ethernet driver` is wrapped in separate [sub-component](./components/ethernet_init/ethernet_init.c) of this project to clearly distinguish between the driver's and `esp_netif` initializations. The work flow of the example could be as follows:
|
||||
|
||||
**Note:** In this example, ESP32 works like a *bridge* between Ethernet and Wi-Fi, and it won't perform any actions on Layer3 and higher layer, which means there's no need to initialize the TCP/IP stack.
|
||||
1. Install Ethernet driver
|
||||
2. Attach the driver to `esp_netif`
|
||||
3. Send DHCP requests and wait for a DHCP lease
|
||||
4. If get IP address successfully, then you will be able to ping the device
|
||||
|
||||
If you have a new Ethernet application to go (for example, connect to IoT cloud via Ethernet), try this as a basic template, then add your own code.
|
||||
|
||||
## How to use example
|
||||
|
||||
@@ -19,23 +21,26 @@ The similarities on MAC layer between Ethernet and Wi-Fi make it easy to forward
|
||||
|
||||
To run this example, it's recommended that you have an official ESP32 Ethernet development board - [ESP32-Ethernet-Kit](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/get-started-ethernet-kit.html). This example should also work for 3rd party ESP32 board as long as it's integrated with a supported Ethernet PHY chip. Up until now, ESP-IDF supports up to four Ethernet PHY: `LAN8720`, `IP101`, `DP83848` and `RTL8201`, additional PHY drivers should be implemented by users themselves.
|
||||
|
||||
Besides that, `esp_eth` component can drive third-party Ethernet module which integrates MAC and PHY and provides common communication interface (e.g. SPI, USB, etc). This example will take the **DM9051** as an example, illustrating how to install the Ethernet driver in the same manner.
|
||||
Besides that, `esp_eth` component can drive third-party Ethernet module which integrates MAC and PHY and provides common communication interface (e.g. SPI, USB, etc). This example will take the `DM9051`, `W5500` or `KSZ8851SNL` SPI modules as an example, illustrating how to install the Ethernet driver in the same manner.
|
||||
|
||||
The ESP-IDF supports the usage of multiple Ethernet interfaces at a time when external modules are utilized which is also demonstrated by this example. There are several options you can combine:
|
||||
* Internal EMAC and one SPI Ethernet module.
|
||||
* Two SPI Ethernet modules of the same type connected to single SPI interface and accessed by switching appropriate CS.
|
||||
* Internal EMAC and two SPI Ethernet modules of the same type.
|
||||
|
||||
#### Pin Assignment
|
||||
|
||||
See common pin assignments for Ethernet examples from [upper level](../README.md#common-pin-assignments).
|
||||
|
||||
When using two Ethernet SPI modules at a time, they are to be connected to single SPI interface. Both modules then share data (MOSI/MISO) and CLK signals. However, the CS, interrupt and reset pins need to be connected to separate GPIO for each Ethernet SPI module.
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
In addition to the common configurations for Ethernet examples from [upper level](../README.md#common-configurations), you might also need to update the default value of following configurations:
|
||||
|
||||
In the `Example Configuration` menu:
|
||||
* Set the SSID and password for Wi-Fi ap interface under `Wi-Fi SSID` and `Wi-Fi Password`.
|
||||
* Set the maximum connection number under `Maximum STA connections`.
|
||||
See common configurations for Ethernet examples from [upper level](../README.md#common-configurations).
|
||||
|
||||
### Build, Flash, and Run
|
||||
|
||||
@@ -53,62 +58,23 @@ See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/l
|
||||
|
||||
## Example Output
|
||||
|
||||
### Step 1: Initialize Ethernet and Wi-Fi (AP mode)
|
||||
|
||||
```bash
|
||||
I (508) example: Power On Ethernet PHY
|
||||
I (518) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
|
||||
I (518) emac: emac reset done
|
||||
I (518) example: Ethernet Started
|
||||
......
|
||||
I (538) wifi: wifi driver task: 3ffc7fbc, prio:23, stack:3584, core=0
|
||||
I (538) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
|
||||
I (538) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
|
||||
I (568) wifi: wifi firmware version: ec61a20
|
||||
I (568) wifi: config NVS flash: enabled
|
||||
I (568) wifi: config nano formating: disabled
|
||||
I (568) wifi: Init dynamic tx buffer num: 32
|
||||
I (568) wifi: Init data frame dynamic rx buffer num: 32
|
||||
I (578) wifi: Init management frame dynamic rx buffer num: 32
|
||||
I (588) wifi: Init management short buffer num: 32
|
||||
I (588) wifi: Init static rx buffer size: 1600
|
||||
I (588) wifi: Init static rx buffer num: 10
|
||||
I (598) wifi: Init dynamic rx buffer num: 32
|
||||
I (394) eth_example: Ethernet Started
|
||||
I (3934) eth_example: Ethernet Link Up
|
||||
I (3934) eth_example: Ethernet HW Addr 30:ae:a4:c6:87:5b
|
||||
I (5864) esp_netif_handlers: eth ip: 192.168.2.151, mask: 255.255.255.0, gw: 192.168.2.2
|
||||
I (5864) eth_example: Ethernet Got IP Address
|
||||
I (5864) eth_example: ~~~~~~~~~~~
|
||||
I (5864) eth_example: ETHIP:192.168.2.151
|
||||
I (5874) eth_example: ETHMASK:255.255.255.0
|
||||
I (5874) eth_example: ETHGW:192.168.2.2
|
||||
I (5884) eth_example: ~~~~~~~~~~~
|
||||
```
|
||||
|
||||
### Step 2: Ethernet Connects to Router/Switch/PC (with DHCP server enabled)
|
||||
|
||||
```bash
|
||||
I (4518) example: Ethernet Link Up
|
||||
```
|
||||
|
||||
### Step 3: Start Wi-Fi AP
|
||||
|
||||
```bash
|
||||
I (4618) phy: phy_version: 4100, 2a5dd04, Jan 23 2019, 21:00:07, 0, 0
|
||||
I (4618) wifi: mode : softAP (30:ae:a4:c6:87:5b)
|
||||
I (4628) wifi: Total power save buffer number: 16
|
||||
I (4628) wifi: Init max length of beacon: 752/752
|
||||
I (4628) wifi: Init max length of beacon: 752/752
|
||||
```
|
||||
|
||||
### Step 4: Wi-Fi station (e.g. mobile phone) connects to ESP32's Wi-Fi
|
||||
|
||||
```bash
|
||||
I (10168) wifi: new:<1,0>, old:<1,0>, ap:<1,1>, sta:<255,255>, prof:1
|
||||
I (10168) wifi: station: c4:0b:cb:ec:9a:84 join, AID=1, bgn, 20
|
||||
I (10258) example: AP got a station connected
|
||||
```
|
||||
|
||||
Now your mobile phone should get access to the Internet.
|
||||
Now you can ping your ESP32 in the terminal by entering `ping 192.168.2.151` (it depends on the actual IP address you get).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
See common troubleshooting for Ethernet examples from [upper level](../README.md#common-troubleshooting).
|
||||
|
||||
* If you got error message like `WiFi send packet failed` when running the example, you may need to enlarge the value of `FLOW_CONTROL_WIFI_SEND_DELAY_MS` in "ethernet_example_main.c", because Ethernet process packets faster than Wi-Fi on ESP32.
|
||||
* If you got error message like `send flow control message failed or timeout` when running the example, you may need to enlarge the value of `FLOW_CONTROL_QUEUE_LENGTH` in "ethernet_example_main".
|
||||
* Wi-Fi station doesn't receive any IP via DHCP?
|
||||
* All Layer 3 (TCP/IP functions) on the ESP32 are disabled, including the SoftAP DHCP server. This means that devices must be able to access another DHCP server (for example on a Wi-Fi router connected via ethernet) or should use statically assigned IP addresses.
|
||||
|
||||
(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.)
|
||||
(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.)
|
||||
Submodule
+1
Submodule core added at 3041a17851
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"version": "0.2",
|
||||
"ignorePaths": [],
|
||||
"dictionaryDefinitions": [],
|
||||
"dictionaries": [],
|
||||
"words": [
|
||||
"Gustice",
|
||||
"LOGD",
|
||||
"LOGI",
|
||||
"LOGW",
|
||||
"LOGE",
|
||||
"GPIO",
|
||||
|
||||
],
|
||||
"ignoreWords": [],
|
||||
"import": []
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
dependencies:
|
||||
ethernet_init:
|
||||
component_hash: null
|
||||
source:
|
||||
path: C:\Tools\ESP-IDF\v5.3\esp-idf\examples\ethernet\basic\components\ethernet_init
|
||||
type: local
|
||||
version: '*'
|
||||
idf:
|
||||
component_hash: null
|
||||
source:
|
||||
type: idf
|
||||
version: 5.3.0
|
||||
manifest_hash: 736e193f01d8a1ca4afaed91b473283f46d56db468270aee2f055b9425257e7f
|
||||
target: esp32c6
|
||||
version: 1.0.0
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 87 KiB |
+1
-1
@@ -1,2 +1,2 @@
|
||||
idf_component_register(SRCS "ethernet_example_main.c"
|
||||
idf_component_register(SRCS "watchMain.cpp"
|
||||
INCLUDE_DIRS ".")
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
/* eth2ap (Ethernet to Wi-Fi AP packet forwarding) 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 "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_eth_driver.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_private/wifi.h"
|
||||
#include "ethernet_init.h"
|
||||
|
||||
static const char *TAG = "eth2ap_example";
|
||||
static esp_eth_handle_t s_eth_handle = NULL;
|
||||
static QueueHandle_t flow_control_queue = NULL;
|
||||
static bool s_sta_is_connected = false;
|
||||
static bool s_ethernet_is_connected = false;
|
||||
static uint8_t s_eth_mac[6];
|
||||
|
||||
#define FLOW_CONTROL_QUEUE_TIMEOUT_MS (100)
|
||||
#define FLOW_CONTROL_QUEUE_LENGTH (40)
|
||||
#define FLOW_CONTROL_WIFI_SEND_TIMEOUT_MS (100)
|
||||
|
||||
typedef struct {
|
||||
void *packet;
|
||||
uint16_t length;
|
||||
} flow_control_msg_t;
|
||||
|
||||
// Forward packets from Wi-Fi to Ethernet
|
||||
static esp_err_t pkt_wifi2eth(void *buffer, uint16_t len, void *eb)
|
||||
{
|
||||
if (s_ethernet_is_connected) {
|
||||
if (esp_eth_transmit(s_eth_handle, buffer, len) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Ethernet send packet failed");
|
||||
}
|
||||
}
|
||||
esp_wifi_internal_free_rx_buffer(eb);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Forward packets from Ethernet to Wi-Fi
|
||||
// Note that, Ethernet works faster than Wi-Fi on ESP32,
|
||||
// so we need to add an extra queue to balance their speed difference.
|
||||
static esp_err_t pkt_eth2wifi(esp_eth_handle_t eth_handle, uint8_t *buffer, uint32_t len, void *priv)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
flow_control_msg_t msg = {
|
||||
.packet = buffer,
|
||||
.length = len
|
||||
};
|
||||
if (xQueueSend(flow_control_queue, &msg, pdMS_TO_TICKS(FLOW_CONTROL_QUEUE_TIMEOUT_MS)) != pdTRUE) {
|
||||
ESP_LOGE(TAG, "send flow control message failed or timeout");
|
||||
free(buffer);
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// This task will fetch the packet from the queue, and then send out through Wi-Fi.
|
||||
// Wi-Fi handles packets slower than Ethernet, we might add some delay between each transmitting.
|
||||
static void eth2wifi_flow_control_task(void *args)
|
||||
{
|
||||
flow_control_msg_t msg;
|
||||
int res = 0;
|
||||
uint32_t timeout = 0;
|
||||
while (1) {
|
||||
if (xQueueReceive(flow_control_queue, &msg, pdMS_TO_TICKS(FLOW_CONTROL_QUEUE_TIMEOUT_MS)) == pdTRUE) {
|
||||
timeout = 0;
|
||||
if (s_sta_is_connected && msg.length) {
|
||||
do {
|
||||
vTaskDelay(pdMS_TO_TICKS(timeout));
|
||||
timeout += 2;
|
||||
res = esp_wifi_internal_tx(WIFI_IF_AP, msg.packet, msg.length);
|
||||
} while (res && timeout < FLOW_CONTROL_WIFI_SEND_TIMEOUT_MS);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "WiFi send packet failed: %d", res);
|
||||
}
|
||||
}
|
||||
free(msg.packet);
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
// Event handler for Ethernet
|
||||
static void eth_event_handler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
switch (event_id) {
|
||||
case ETHERNET_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "Ethernet Link Up");
|
||||
s_ethernet_is_connected = true;
|
||||
esp_eth_ioctl(s_eth_handle, ETH_CMD_G_MAC_ADDR, s_eth_mac);
|
||||
esp_wifi_set_mac(WIFI_IF_AP, s_eth_mac);
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
break;
|
||||
case ETHERNET_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "Ethernet Link Down");
|
||||
s_ethernet_is_connected = false;
|
||||
ESP_ERROR_CHECK(esp_wifi_stop());
|
||||
break;
|
||||
case ETHERNET_EVENT_START:
|
||||
ESP_LOGI(TAG, "Ethernet Started");
|
||||
break;
|
||||
case ETHERNET_EVENT_STOP:
|
||||
ESP_LOGI(TAG, "Ethernet Stopped");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Event handler for Wi-Fi
|
||||
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
static uint8_t s_con_cnt = 0;
|
||||
switch (event_id) {
|
||||
case WIFI_EVENT_AP_STACONNECTED:
|
||||
ESP_LOGI(TAG, "Wi-Fi AP got a station connected");
|
||||
if (!s_con_cnt) {
|
||||
s_sta_is_connected = true;
|
||||
esp_wifi_internal_reg_rxcb(WIFI_IF_AP, pkt_wifi2eth);
|
||||
}
|
||||
s_con_cnt++;
|
||||
break;
|
||||
case WIFI_EVENT_AP_STADISCONNECTED:
|
||||
ESP_LOGI(TAG, "Wi-Fi AP got a station disconnected");
|
||||
s_con_cnt--;
|
||||
if (!s_con_cnt) {
|
||||
s_sta_is_connected = false;
|
||||
esp_wifi_internal_reg_rxcb(WIFI_IF_AP, NULL);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void initialize_ethernet(void)
|
||||
{
|
||||
uint8_t eth_port_cnt = 0;
|
||||
esp_eth_handle_t *eth_handles;
|
||||
ESP_ERROR_CHECK(example_eth_init(ð_handles, ð_port_cnt));
|
||||
if (eth_port_cnt > 1) {
|
||||
ESP_LOGW(TAG, "multiple Ethernet devices detected, the first initialized is to be used!");
|
||||
}
|
||||
s_eth_handle = eth_handles[0];
|
||||
free(eth_handles);
|
||||
ESP_ERROR_CHECK(esp_eth_update_input_path(s_eth_handle, pkt_eth2wifi, NULL));
|
||||
bool eth_promiscuous = true;
|
||||
ESP_ERROR_CHECK(esp_eth_ioctl(s_eth_handle, ETH_CMD_S_PROMISCUOUS, ð_promiscuous));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_eth_start(s_eth_handle));
|
||||
}
|
||||
|
||||
static void initialize_wifi(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler, NULL));
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
|
||||
wifi_config_t wifi_config = {
|
||||
.ap = {
|
||||
.ssid = CONFIG_EXAMPLE_WIFI_SSID,
|
||||
.ssid_len = strlen(CONFIG_EXAMPLE_WIFI_SSID),
|
||||
.password = CONFIG_EXAMPLE_WIFI_PASSWORD,
|
||||
.max_connection = CONFIG_EXAMPLE_MAX_STA_CONN,
|
||||
.authmode = WIFI_AUTH_WPA_WPA2_PSK,
|
||||
.channel = CONFIG_EXAMPLE_WIFI_CHANNEL // default: channel 1
|
||||
},
|
||||
};
|
||||
if (strlen(CONFIG_EXAMPLE_WIFI_PASSWORD) == 0) {
|
||||
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
|
||||
}
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
|
||||
}
|
||||
|
||||
static esp_err_t initialize_flow_control(void)
|
||||
{
|
||||
flow_control_queue = xQueueCreate(FLOW_CONTROL_QUEUE_LENGTH, sizeof(flow_control_msg_t));
|
||||
if (!flow_control_queue) {
|
||||
ESP_LOGE(TAG, "create flow control queue failed");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
BaseType_t ret = xTaskCreate(eth2wifi_flow_control_task, "flow_ctl", 2048, NULL, (tskIDLE_PRIORITY + 2), NULL);
|
||||
if (ret != pdTRUE) {
|
||||
ESP_LOGE(TAG, "create flow control task failed");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
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_event_loop_create_default());
|
||||
ESP_ERROR_CHECK(initialize_flow_control());
|
||||
initialize_wifi();
|
||||
initialize_ethernet();
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/* Ethernet Basic 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 <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_eth.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "ethernet_init.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
static const char *TAG = "eth_example";
|
||||
|
||||
/** Event handler for Ethernet events */
|
||||
static void eth_event_handler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
uint8_t mac_addr[6] = {0};
|
||||
/* we can get the ethernet driver handle from event data */
|
||||
esp_eth_handle_t eth_handle = *(esp_eth_handle_t *)event_data;
|
||||
|
||||
switch (event_id) {
|
||||
case ETHERNET_EVENT_CONNECTED:
|
||||
esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr);
|
||||
ESP_LOGI(TAG, "Ethernet Link Up");
|
||||
ESP_LOGI(TAG, "Ethernet HW Addr %02x:%02x:%02x:%02x:%02x:%02x",
|
||||
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
|
||||
break;
|
||||
case ETHERNET_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "Ethernet Link Down");
|
||||
break;
|
||||
case ETHERNET_EVENT_START:
|
||||
ESP_LOGI(TAG, "Ethernet Started");
|
||||
break;
|
||||
case ETHERNET_EVENT_STOP:
|
||||
ESP_LOGI(TAG, "Ethernet Stopped");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** Event handler for IP_EVENT_ETH_GOT_IP */
|
||||
static void got_ip_event_handler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
|
||||
const esp_netif_ip_info_t *ip_info = &event->ip_info;
|
||||
|
||||
ESP_LOGI(TAG, "Ethernet Got IP Address");
|
||||
ESP_LOGI(TAG, "~~~~~~~~~~~");
|
||||
ESP_LOGI(TAG, "ETHIP:" IPSTR, IP2STR(&ip_info->ip));
|
||||
ESP_LOGI(TAG, "ETHMASK:" IPSTR, IP2STR(&ip_info->netmask));
|
||||
ESP_LOGI(TAG, "ETHGW:" IPSTR, IP2STR(&ip_info->gw));
|
||||
ESP_LOGI(TAG, "~~~~~~~~~~~");
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// Initialize Ethernet driver
|
||||
uint8_t eth_port_cnt = 0;
|
||||
esp_eth_handle_t *eth_handles;
|
||||
ESP_ERROR_CHECK(example_eth_init(ð_handles, ð_port_cnt));
|
||||
|
||||
// Initialize TCP/IP network interface aka the esp-netif (should be called only once in application)
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
// Create default event loop that running in background
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
// Create instance(s) of esp-netif for Ethernet(s)
|
||||
if (eth_port_cnt == 1) {
|
||||
// Use ESP_NETIF_DEFAULT_ETH when just one Ethernet interface is used and you don't need to modify
|
||||
// default esp-netif configuration parameters.
|
||||
esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH();
|
||||
esp_netif_t *eth_netif = esp_netif_new(&cfg);
|
||||
// Attach Ethernet driver to TCP/IP stack
|
||||
ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handles[0])));
|
||||
} else {
|
||||
// Use ESP_NETIF_INHERENT_DEFAULT_ETH when multiple Ethernet interfaces are used and so you need to modify
|
||||
// esp-netif configuration parameters for each interface (name, priority, etc.).
|
||||
esp_netif_inherent_config_t esp_netif_config = ESP_NETIF_INHERENT_DEFAULT_ETH();
|
||||
esp_netif_config_t cfg_spi = {
|
||||
.base = &esp_netif_config,
|
||||
.stack = ESP_NETIF_NETSTACK_DEFAULT_ETH
|
||||
};
|
||||
char if_key_str[10];
|
||||
char if_desc_str[10];
|
||||
char num_str[3];
|
||||
for (int i = 0; i < eth_port_cnt; i++) {
|
||||
itoa(i, num_str, 10);
|
||||
strcat(strcpy(if_key_str, "ETH_"), num_str);
|
||||
strcat(strcpy(if_desc_str, "eth"), num_str);
|
||||
esp_netif_config.if_key = if_key_str;
|
||||
esp_netif_config.if_desc = if_desc_str;
|
||||
esp_netif_config.route_prio -= i*5;
|
||||
esp_netif_t *eth_netif = esp_netif_new(&cfg_spi);
|
||||
|
||||
// Attach Ethernet driver to TCP/IP stack
|
||||
ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handles[i])));
|
||||
}
|
||||
}
|
||||
|
||||
// Register user defined event handers
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, NULL));
|
||||
|
||||
// Start Ethernet driver state machine
|
||||
for (int i = 0; i < eth_port_cnt; i++) {
|
||||
ESP_ERROR_CHECK(esp_eth_start(eth_handles[i]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,660 @@
|
||||
/**
|
||||
* @file watchMain.cpp
|
||||
* @author Gustice
|
||||
* @brief ChaosWatch Application
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
// Conig-Key:
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
struct {
|
||||
const char *syslogDest = "syslogDst"; // example: "172.16.16.10"
|
||||
const char *mqttBroker = "mqttBroker"; // example: "172.16.17.62"
|
||||
const char *mqttPrefix = "mqttPrefix"; // example: "SomeTag"
|
||||
} ConfigKeys;
|
||||
|
||||
// MQTT Topics
|
||||
struct {
|
||||
// Subscriptions:
|
||||
const char *moveTo = /* <mqttPrefix>/ */ "watch/moveTo";
|
||||
const char *startShuffle = /* <mqttPrefix>/ */ "watch/startShuffle";
|
||||
// Publishes:
|
||||
const char *inPosition = /* <mqttPrefix>/ */ "watch/inPosition";
|
||||
const char *changingPosition = /* <mqttPrefix>/ */ "watch/changingPosition";
|
||||
} MqttTopics;
|
||||
|
||||
// ToDo:
|
||||
// - MQTT Integration
|
||||
// - OTA
|
||||
// - Syslog heartbeat
|
||||
// - Streamlined DMX
|
||||
// -
|
||||
|
||||
#include "watchMain.hpp"
|
||||
|
||||
extern "C" { // This switch allows the ROS C-implementation to find this main
|
||||
void app_main(void);
|
||||
}
|
||||
|
||||
struct DeviceState {
|
||||
uint8_t minJumps{6};
|
||||
uint8_t maxJumps{32};
|
||||
uint8_t profile{};
|
||||
uint8_t restTime{};
|
||||
float speed = 1.0; // Rad/sec
|
||||
float accel = 1.0; // Rad/sec^2
|
||||
Color color{0x80, 0x80, 0x80};
|
||||
uint8_t pictogram{}; // 0..5 active Pictogramm
|
||||
};
|
||||
|
||||
enum class MoveType {
|
||||
StepTo,
|
||||
Shuffle,
|
||||
Demo
|
||||
};
|
||||
struct MoveCommand {
|
||||
MoveType type;
|
||||
uint8_t pos;
|
||||
};
|
||||
struct PictoIllumCommand {
|
||||
uint8_t pos; // 0 for none / Valid indexes 1 .. 6
|
||||
};
|
||||
|
||||
static fos::Queue<MoveCommand> commandQueue(8);
|
||||
static fos::Queue<PictoIllumCommand> pictoIllumQue(2);
|
||||
|
||||
DeviceState activeState;
|
||||
static const char *TAG = "Watch";
|
||||
static bool EthernetIsConnected = false;
|
||||
|
||||
std::unique_ptr<Cli> pShell;
|
||||
|
||||
void onCommand(std::string name, Cli::CArgs args) {
|
||||
ESP_LOGI(TAG, "Invoking: %s", name.c_str());
|
||||
}
|
||||
|
||||
void startUpdate(Cli::CArgs args) {
|
||||
ESP_LOGI(TAG, "Invoking Update");
|
||||
if (args.size() != 1) {
|
||||
pShell->write("Need URL to application'\n");
|
||||
return;
|
||||
}
|
||||
// @ToDo: Start Update here
|
||||
}
|
||||
|
||||
void getConfig(Cli::CArgs args) {
|
||||
if (args.size() != 1) {
|
||||
pShell->write("Needs string key for query'\n");
|
||||
return;
|
||||
}
|
||||
auto key = args[0];
|
||||
auto &store = NvsApi::getInstance();
|
||||
auto val = store.getValue(key);
|
||||
pShell->write(key + ": " + val + "\n");
|
||||
}
|
||||
|
||||
void testPictoIllum(Cli::CArgs args) {
|
||||
if (args.size() != 1) {
|
||||
pShell->write("Needs position argument for illuminated position 0 for off, 1..6 for pictograms'\n");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
int p = std::stoi(args[0]);
|
||||
pictoIllumQue.enqueue(std::make_unique<PictoIllumCommand>(p));
|
||||
} catch (const std::exception &e) {
|
||||
ESP_LOGI(TAG, "Failed to parse %s and %s", args[0].c_str(), args[1].c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void testMovement(Cli::CArgs args) {
|
||||
ESP_LOGI(TAG, "on triggerMovement:");
|
||||
if (args.size() != 1) {
|
||||
pShell->write("need target position (1..6)'\n");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
int p = std::stoi(args[0]);
|
||||
activeState.profile = p;
|
||||
commandQueue.enqueue(std::make_unique<MoveCommand>(MoveType::StepTo, p));
|
||||
} catch (const std::exception &e) {
|
||||
ESP_LOGI(TAG, "Failed to parse %s and %s", args[0].c_str(), args[1].c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void setConfig(Cli::CArgs args) {
|
||||
if (args.size() != 2) {
|
||||
pShell->write("Needs string key and value'\n");
|
||||
return;
|
||||
}
|
||||
auto key = args[0];
|
||||
auto val = args[1];
|
||||
|
||||
auto &store = NvsApi::getInstance();
|
||||
pShell->write(std::string{"Storing value = '"} + val + "'\n");
|
||||
store.setValue(key, val);
|
||||
}
|
||||
|
||||
void setLogServerConfig(Cli::CArgs args) {
|
||||
ESP_LOGI(TAG, "on showLogServerConfig:");
|
||||
auto &store = NvsApi::getInstance();
|
||||
if (args.size() != 1) {
|
||||
auto logger = store.getValue("syslogDst");
|
||||
pShell->write(std::string{"syslog destination = '"} + logger + "'\n");
|
||||
pShell->write("provide argument to change syslog server'\n");
|
||||
return;
|
||||
}
|
||||
pShell->write(std::string{"setting syslog destination to = '"} + args[0] + "'\n");
|
||||
store.setValue("syslogDst", args[0]);
|
||||
}
|
||||
|
||||
void setDynamics(Cli::CArgs args) {
|
||||
ESP_LOGI(TAG, "on setDynamics:");
|
||||
if (args.size() != 2) {
|
||||
pShell->write("need float values for velocity and acceleration'\n");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
float v = std::stof(args[0]);
|
||||
float a = std::stof(args[1]);
|
||||
activeState.accel = a;
|
||||
activeState.speed = v;
|
||||
} catch (const std::exception &e) {
|
||||
ESP_LOGI(TAG, "Failed to parse %s and %s", args[0].c_str(), args[1].c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void startDemoMovement(Cli::CArgs args) {
|
||||
ESP_LOGI(TAG, "on startDemoMovement:");
|
||||
commandQueue.enqueue(std::make_unique<MoveCommand>(MoveType::Demo));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Telnet socket via TCP/IP
|
||||
*
|
||||
* @details The ini-file for TeraTerm only changes CRReceive and CRSend values both to "=CR"
|
||||
* @param arg ignored task argument
|
||||
*/
|
||||
static void telnetSocketTask(int addrFamily) {
|
||||
ESP_LOGI(TAG, "Starting Telnet task");
|
||||
try {
|
||||
TelnetServer telnet(addrFamily);
|
||||
telnet.run(
|
||||
[](TcpSession &session) {
|
||||
pShell = std::make_unique<Cli>(session, [](std::string n, Cli::CArgs a) { onCommand(n, a); });
|
||||
pShell->addBinding(
|
||||
{"update", "Start OTA-Update-Process",
|
||||
[](Cli::CArgs a) { startUpdate(a); }});
|
||||
pShell->addBinding(
|
||||
{"logserver_set", "Set log server config ip",
|
||||
[](Cli::CArgs a) { setLogServerConfig(a); }});
|
||||
|
||||
pShell->addBinding(
|
||||
{"getcfg", "Get configuration value for key",
|
||||
[](Cli::CArgs a) { getConfig(a); }});
|
||||
pShell->addBinding(
|
||||
{"setcfg", "Set configuration value by key and value",
|
||||
[](Cli::CArgs a) { setConfig(a); }});
|
||||
|
||||
pShell->addBinding(
|
||||
{"dyn_set", "set dynamics of movement (velocity,accelerations) as floats",
|
||||
[](Cli::CArgs a) { setDynamics(a); }});
|
||||
pShell->addBinding(
|
||||
{"tstMove", "start movement (profile-idx,steps) as integers ",
|
||||
[](Cli::CArgs a) { testMovement(a); }});
|
||||
|
||||
pShell->addBinding(
|
||||
{"tstIllum", "test illumination command 0=off /1..6",
|
||||
[](Cli::CArgs a) { testPictoIllum(a); }});
|
||||
},
|
||||
[](TcpSession &session) {
|
||||
pShell->process();
|
||||
Syslog::setupTask(session.getOther());
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
ESP_LOGE("TCP", "%s", e.what());
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
constexpr uint32_t DmxUniverse = 0;
|
||||
constexpr int DmxChannelCount = 9;
|
||||
|
||||
uint8_t scaledRgbChannel(uint8_t value, float scale) {
|
||||
return static_cast<uint8_t>(value * scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Callback for ArtNet DMX-socket. Does not send data only updates the image
|
||||
*
|
||||
* @param data Pointer to dmx-values
|
||||
* @param size length of dmx-values
|
||||
*/
|
||||
static void UniverseCallback(const uint8_t *data, const uint16_t size) {
|
||||
int len = std::min(static_cast<int>(size), DmxChannelCount);
|
||||
ESP_LOGD(TAG, "Got DMX");
|
||||
ESP_LOG_BUFFER_HEXDUMP("DMX", data, len, ESP_LOG_DEBUG);
|
||||
Syslog::logv(Syslog::Severity::Debug, "Got DMX: r%0x g%0x b%0x i%0x m%i M%i p%i a%i w%i r%i",
|
||||
/* RGB*/ data[0], data[1], data[2], /*Intens*/ data[3],
|
||||
/* MinMaxJumps */ data[4], data[5], /*profile*/ data[6], /*accel/speed*/ data[7], data[8],
|
||||
/*RestTime*/ data[9]);
|
||||
|
||||
float intens = (float)data[3] / UINT8_MAX;
|
||||
activeState.color = Color{
|
||||
scaledRgbChannel(data[0], intens),
|
||||
scaledRgbChannel(data[1], intens),
|
||||
scaledRgbChannel(data[2], intens),
|
||||
0};
|
||||
|
||||
activeState.minJumps = data[4];
|
||||
activeState.maxJumps = data[5];
|
||||
activeState.profile = data[6];
|
||||
|
||||
uint8_t accel = data[7];
|
||||
uint8_t velo = data[8];
|
||||
activeState.accel = (float)accel / 100 + 0.5; // min .5 / max 3
|
||||
activeState.speed = (float)velo / 100 + 0.5; // min .5 / max 3
|
||||
|
||||
activeState.restTime = data[9];
|
||||
}
|
||||
|
||||
void moveToSubs(std::string data) {
|
||||
ESP_LOGI(TAG, "MoveTo command '%s'", data.c_str());
|
||||
try {
|
||||
int p = std::stoi(data);
|
||||
Syslog::logv(Syslog::Severity::Informational, "got MoveTo-Cmd->%i", p);
|
||||
commandQueue.enqueue(std::make_unique<MoveCommand>(MoveType::StepTo, p));
|
||||
auto &client = MqttClient::getInstance();
|
||||
client.publish(MqttTopics.inPosition, data + "Picto");
|
||||
} catch (const std::exception &e) {
|
||||
ESP_LOGE(TAG, "Error to process data %s, with ex: %s", data.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void startShuffle(std::string data) {
|
||||
ESP_LOGI(TAG, "StartShuffle command %s", data.c_str());
|
||||
|
||||
try {
|
||||
Syslog::logv(Syslog::Severity::Informational, "got Shuffle-Command");
|
||||
commandQueue.enqueue(std::make_unique<MoveCommand>(MoveType::Shuffle, 0));
|
||||
auto &client = MqttClient::getInstance();
|
||||
client.publish(MqttTopics.changingPosition, "....");
|
||||
} catch (const std::exception &e) {
|
||||
ESP_LOGE(TAG, "Error to process data %s, with ex: %s", data.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief DMX-Socket for ArtNet TCP/IP
|
||||
*
|
||||
* @details Tested with [Q Light Controller+](https://www.qlcplus.org/)
|
||||
* @param args ignored task argument
|
||||
*/
|
||||
static void dmxSocketSenderTask(UdpSocket &socket) {
|
||||
try {
|
||||
ArtnetSender artnet(socket);
|
||||
std::array<uint8_t, 16> data;
|
||||
while (socket.isActive()) {
|
||||
data[0]++;
|
||||
artnet.send("", 0, data.data(), data.size());
|
||||
fos::Task::delayMs(500);
|
||||
}
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
ESP_LOGE("UDP", "%s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief DMX-Socket for ArtNet TCP/IP
|
||||
*
|
||||
* @details Tested with [Q Light Controller+](https://www.qlcplus.org/)
|
||||
* @param args ignored task argument
|
||||
*/
|
||||
static void dmxSocketTask(int addrFamily) {
|
||||
|
||||
ESP_LOGI(TAG, "Starting ArtNet task");
|
||||
constexpr uint16_t ArtNetPort = arx::artnet::DEFAULT_PORT;
|
||||
IpAdr address = addrFamily == AF_INET ? IpAdr(INADDR_ANY, ArtNetPort) : IpAdr(ArtNetPort);
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
UdpSocket socket(address);
|
||||
socket.bindTo();
|
||||
|
||||
ArtnetReceiver artnet(socket);
|
||||
artnet.subscribe(DmxUniverse, UniverseCallback);
|
||||
artnet.parse();
|
||||
// parse artnet as long socket is active, then restart socket
|
||||
while (socket.isActive()) {
|
||||
ESP_LOGD(TAG, "Waiting for data");
|
||||
artnet.parse();
|
||||
}
|
||||
}
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
ESP_LOGE("UDP", "%s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
fos::Semaphore etherActive;
|
||||
|
||||
/** Event handler for IP_EVENT_ETH_GOT_IP */
|
||||
static void got_ip_event_handler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data) {
|
||||
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
|
||||
const esp_netif_ip_info_t *ip_info = &event->ip_info;
|
||||
|
||||
ESP_LOGI(TAG, "Ethernet Got IP Address");
|
||||
ESP_LOGI(TAG, "~~~~~~~~~~~");
|
||||
ESP_LOGI(TAG, "ETHIP:" IPSTR, IP2STR(&ip_info->ip));
|
||||
ESP_LOGI(TAG, "ETHMASK:" IPSTR, IP2STR(&ip_info->netmask));
|
||||
ESP_LOGI(TAG, "ETHGW:" IPSTR, IP2STR(&ip_info->gw));
|
||||
ESP_LOGI(TAG, "~~~~~~~~~~~");
|
||||
etherActive.give();
|
||||
}
|
||||
|
||||
// Event handler for Ethernet
|
||||
static void eth_event_handler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data) {
|
||||
uint8_t mac_addr[6] = {0};
|
||||
switch (event_id) {
|
||||
case ETHERNET_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "Ethernet Link Up");
|
||||
EthernetIsConnected = true;
|
||||
ESP_LOGI(TAG, "Ethernet Link Up");
|
||||
ESP_LOGI(TAG, "Ethernet HW Addr %02x:%02x:%02x:%02x:%02x:%02x",
|
||||
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
|
||||
break;
|
||||
case ETHERNET_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "Ethernet Link Down");
|
||||
EthernetIsConnected = false;
|
||||
break;
|
||||
case ETHERNET_EVENT_START:
|
||||
ESP_LOGI(TAG, "Ethernet Started");
|
||||
break;
|
||||
case ETHERNET_EVENT_STOP:
|
||||
ESP_LOGI(TAG, "Ethernet Stopped");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void initialize_ethernet(void) {
|
||||
uint8_t eth_port_cnt = 0;
|
||||
esp_eth_handle_t *eth_handles;
|
||||
ESP_ERROR_CHECK(example_eth_init(ð_handles, ð_port_cnt));
|
||||
if (eth_port_cnt > 1) {
|
||||
ESP_LOGW(TAG, "multiple Ethernet devices detected, the first initialized is to be used!");
|
||||
}
|
||||
|
||||
static esp_eth_handle_t s_eth_handle = NULL;
|
||||
s_eth_handle = eth_handles[0];
|
||||
free(eth_handles);
|
||||
|
||||
// Initialize TCP/IP network interface aka the esp-netif (should be called only once in application)
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
// Create default event loop that running in background
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
// Use ESP_NETIF_DEFAULT_ETH when just one Ethernet interface is used and you don't need to modify
|
||||
// default esp-netif configuration parameters.
|
||||
esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH();
|
||||
esp_netif_t *eth_netif = esp_netif_new(&cfg);
|
||||
// Attach Ethernet driver to TCP/IP stack
|
||||
ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(s_eth_handle)));
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_eth_start(s_eth_handle));
|
||||
}
|
||||
|
||||
namespace Effect {
|
||||
extern const WaveForm_t OffsetPulseWave{gau8_offsetPulse, cu16_TemplateLength};
|
||||
extern const WaveForm_t InitSlopeWave{gau8_initSlope, cu16_TemplateLength};
|
||||
extern const WaveForm_t OffsetSlopeWave{gau8_offsetSlope, cu16_TemplateLength};
|
||||
extern const WaveForm_t FullSlopeWave{gau8_fullSlope, cu16_TemplateLength};
|
||||
extern const WaveForm_t FullPulseWave{gau8_fullPulse, cu16_TemplateLength};
|
||||
|
||||
const Macro macDemoStart = {
|
||||
{64, 1, &InitSlopeWave, StateMachine::UpdateWave, 0xFF},
|
||||
{32, 1, StateMachine::UpdateIdle, 0xFF},
|
||||
};
|
||||
|
||||
const Macro macDemoPulse = {
|
||||
{64, 1, &InitSlopeWave, StateMachine::UpdateWave, 0xFF},
|
||||
{32, 2, StateMachine::UpdateIdle, 0xFF},
|
||||
{64, 1, &OffsetPulseWave, StateMachine::UpdateWave, 0xFF},
|
||||
};
|
||||
|
||||
const Macro macDemoFlicker = {
|
||||
{64, 1, &InitSlopeWave, StateMachine::UpdateWave, 0xFF},
|
||||
{32, 1, StateMachine::UpdateFlicker, 0xFF},
|
||||
};
|
||||
}
|
||||
|
||||
void LightingTask() {
|
||||
ESP_LOGI(TAG, "Start Lighting");
|
||||
|
||||
static I2cMaster master(I2C_NUM_0,
|
||||
/*sda=*/GPIO_NUM_10,
|
||||
/*scl=*/GPIO_NUM_11);
|
||||
// static PmwExpanderPca9685 pca9685(master);
|
||||
static PmwExpanderPca9685 ledDriver1(master, /*iverted=*/false);
|
||||
static PmwExpanderPca9685 ledDriver2(master, /*iverted=*/false, 0x41);
|
||||
PmwExpanderPca9685::GrayValues values[2]{};
|
||||
|
||||
OutputPort pwmEnable(GPIO_NUM_1, GpioPort::Logic::Inverted);
|
||||
pwmEnable.write(true);
|
||||
|
||||
auto const &colorSource = activeState.color;
|
||||
std::array<Effect::EffectProcessor, 6> processors{
|
||||
Effect::EffectProcessor(8),
|
||||
Effect::EffectProcessor(8),
|
||||
Effect::EffectProcessor(8),
|
||||
Effect::EffectProcessor(8),
|
||||
Effect::EffectProcessor(8),
|
||||
Effect::EffectProcessor(8)};
|
||||
processors[0].setEffect(Effect::macDark, &colorSource, 0);
|
||||
processors[1].setEffect(Effect::macDark, &colorSource, 0);
|
||||
processors[2].setEffect(Effect::macDark, &colorSource, 0);
|
||||
processors[3].setEffect(Effect::macDark, &colorSource, 0);
|
||||
processors[4].setEffect(Effect::macDark, &colorSource, 0);
|
||||
processors[5].setEffect(Effect::macDark, &colorSource, 0);
|
||||
|
||||
const std::array<Effect::Macro, 6> PictoEffects{
|
||||
Effect::macDemoPulse,
|
||||
Effect::macDemoPulse,
|
||||
Effect::macDemoPulse,
|
||||
Effect::macDemoPulse,
|
||||
Effect::macDemoPulse,
|
||||
Effect::macStartFlicker};
|
||||
|
||||
uint8_t lastPicto = 0;
|
||||
|
||||
while (true) {
|
||||
auto onPos = pictoIllumQue.dequeue(0);
|
||||
if (onPos) {
|
||||
ESP_LOGI(TAG, "Changing picto-Illum to pos = %i", onPos->pos);
|
||||
ESP_LOGI(TAG, "Current color set r%i g%i b%i ",
|
||||
activeState.color.getRed(), activeState.color.getGreen(), activeState.color.getBlue());
|
||||
auto pos = onPos->pos;
|
||||
if (pos == 0) {
|
||||
Syslog::logv(Syslog::Severity::Informational,
|
||||
"Dimming picto-illum pos:%i ", lastPicto);
|
||||
processors[lastPicto - 1].setEffect(Effect::macDark, &colorSource, 0);
|
||||
} else if ((pos >= 1) && (pos <= 6)) {
|
||||
auto idx = pos - 1;
|
||||
Syslog::logv(Syslog::Severity::Informational,
|
||||
"Firing picto-illum pos:%i ", pos);
|
||||
processors[idx].setEffect(PictoEffects[idx], &colorSource, 0);
|
||||
lastPicto = pos;
|
||||
} else {
|
||||
Syslog::logv(Syslog::Severity::Error,
|
||||
"Got invalid index for illuminated pictogram =%i ", pos);
|
||||
}
|
||||
}
|
||||
|
||||
std::array<uint16_t *, 6> startPoints{
|
||||
&(values[0][4]),
|
||||
&(values[0][8]),
|
||||
&(values[0][12]),
|
||||
&(values[1][4]),
|
||||
&(values[1][8]),
|
||||
&(values[1][12]),
|
||||
};
|
||||
for (size_t i = 0; i < processors.size(); i++) {
|
||||
auto col = processors[i].tick();
|
||||
auto sp = startPoints[i];
|
||||
sp[0] = (uint16_t)(col.getWhite()) << 7;
|
||||
sp[1] = (uint16_t)(col.getBlue()) << 7;
|
||||
sp[2] = (uint16_t)(col.getGreen()) << 7;
|
||||
sp[3] = (uint16_t)(col.getRed()) << 7;
|
||||
}
|
||||
|
||||
ledDriver1.sendImage(values[0]);
|
||||
ledDriver2.sendImage(values[1]);
|
||||
fos::Task::delayMs(40);
|
||||
}
|
||||
}
|
||||
|
||||
void MovementTask() {
|
||||
ESP_LOGI(TAG, "Start Stepper");
|
||||
constexpr int MicroSteps = 32;
|
||||
StepperDriverDrv8833::Pins stepperPins{
|
||||
LEDC_TIMER_0,
|
||||
{LEDC_CHANNEL_0, LEDC_CHANNEL_1, LEDC_CHANNEL_2, LEDC_CHANNEL_3},
|
||||
{GPIO_NUM_23, GPIO_NUM_22, GPIO_NUM_21, GPIO_NUM_20},
|
||||
};
|
||||
StepperDriverDrv8833 stepperDriver(stepperPins, MicroSteps, 0.25F);
|
||||
stepperDriver.setPos(0);
|
||||
|
||||
StepperConfig stepperCfg{1.8F, MicroSteps};
|
||||
InputPort initSens(GPIO_NUM_18);
|
||||
|
||||
InputPort motorFault(GPIO_NUM_8);
|
||||
motorFault.setLogic(GpioPort::Logic::Inverted);
|
||||
|
||||
OutputPort motorSleep(GPIO_NUM_15, GpioPort::Logic::Inverted);
|
||||
motorSleep.write(false);
|
||||
|
||||
StepperMotor stepper(stepperCfg, stepperDriver, initSens);
|
||||
|
||||
ESP_LOGI("Stepper", "Find Home");
|
||||
stepper.findRef();
|
||||
// Illum 1
|
||||
pictoIllumQue.enqueue(std::make_unique<PictoIllumCommand>(1));
|
||||
|
||||
ESP_LOGI(TAG, "Fist Position is %f.2", stepper.getScaledPos() * 6);
|
||||
fos::Task::delayMs(1000);
|
||||
|
||||
activeState.pictogram = 0;
|
||||
|
||||
while (true) {
|
||||
ESP_LOGD(TAG, "Waiting for command");
|
||||
auto cmd = commandQueue.dequeue();
|
||||
if (cmd->type == MoveType::StepTo) {
|
||||
if (stepper.isIdle()) {
|
||||
pictoIllumQue.enqueue(std::make_unique<PictoIllumCommand>(0));
|
||||
int newPos = cmd->pos;
|
||||
if ((newPos >= 1) && (newPos <= 6)) {
|
||||
auto idx = newPos - 1;
|
||||
Syslog::logv(Syslog::Severity::Informational,
|
||||
"Triggered movement to picto pos:%i ", newPos);
|
||||
} else {
|
||||
Syslog::logv(Syslog::Severity::Error,
|
||||
"Got invalid index for illuminated pictogram =%i ", newPos);
|
||||
continue;
|
||||
}
|
||||
int delta = newPos - activeState.pictogram;
|
||||
if (delta <= 0) {
|
||||
delta += 6;
|
||||
}
|
||||
delta = delta + (activeState.minJumps % 6) * 6;
|
||||
if (delta < activeState.minJumps) {
|
||||
delta += 6;
|
||||
}
|
||||
Syslog::logv(Syslog::Severity::Notice,
|
||||
"Running Stepper by %i steps with prof=%i a=%.2f w=%.2f",
|
||||
delta, activeState.profile, activeState.accel, activeState.speed);
|
||||
pictoIllumQue.enqueue(std::make_unique<PictoIllumCommand>(0));
|
||||
stepper.moveBy(delta * std::numbers::pi / 3,
|
||||
activeState.profile, activeState.accel, activeState.speed);
|
||||
pictoIllumQue.enqueue(std::make_unique<PictoIllumCommand>(newPos));
|
||||
activeState.pictogram = newPos;
|
||||
}
|
||||
} else if (cmd->type == MoveType::Demo) {
|
||||
int i = 0;
|
||||
while (i++ < 80) {
|
||||
stepperDriver.step(1);
|
||||
stepperDriver.getSubPos();
|
||||
fos::Task::delayMs(100);
|
||||
}
|
||||
ESP_LOGI("Stepper", "Find Home");
|
||||
stepper.findRef();
|
||||
activeState.pictogram = -1;
|
||||
stepper.findRef();
|
||||
fos::Task::delayMs(1000);
|
||||
for (size_t i = 0; i < 6; i++) {
|
||||
stepper.moveBy(std::numbers::pi / 3);
|
||||
while (!stepper.isIdle()) {
|
||||
fos::Task::delayMs(100);
|
||||
}
|
||||
ESP_LOGI(TAG, "Position is %f.2", stepper.getScaledPos() * 6);
|
||||
}
|
||||
} else {
|
||||
fos::Task::delayMs(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
initialize_ethernet();
|
||||
NvsApi::init();
|
||||
static fos::Task lightingTask(LightingTask, "Lights", 0x4000uL);
|
||||
static fos::Task movementTask(MovementTask, "Motor", 0x4000uL);
|
||||
|
||||
etherActive.take();
|
||||
ESP_LOGI(TAG, "Start net services");
|
||||
|
||||
static int ipVersion = AF_INET;
|
||||
static fos::TaskT<int> telnetSocket(telnetSocketTask, ipVersion, "tcpServer", 4096);
|
||||
static fos::TaskT<int> dmxNode(dmxSocketTask, ipVersion, "dmxNode", 4096);
|
||||
// static Task otaUpdate(SystemFunctions::OtaTaskEntry, "otaTask", 8192);
|
||||
|
||||
auto &store = NvsApi::getInstance();
|
||||
auto logger = store.getValue(ConfigKeys.syslogDest);
|
||||
if (logger != "") {
|
||||
Syslog::setupTask(IpAdr{logger, 0});
|
||||
}
|
||||
|
||||
auto broker = store.getValue(ConfigKeys.mqttBroker);
|
||||
if (broker != "") {
|
||||
ESP_LOGI(TAG, "Initializing MQTT");
|
||||
auto prefix = store.getValue(ConfigKeys.mqttPrefix);
|
||||
auto &client = MqttClient::init(broker, prefix);
|
||||
if (!client.isRunning()) {
|
||||
client.addSubscription(MqttTopics.moveTo, [](std::string d) { moveToSubs(d); });
|
||||
client.addSubscription(MqttTopics.startShuffle, [](std::string d) { startShuffle(d); });
|
||||
client.start();
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
fos::Task::delayMs(5000);
|
||||
// ToDo send
|
||||
Syslog::logv(Syslog::Severity::Notice, "Health state");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @file watchMain.hpp
|
||||
* @author Gustice
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_eth_driver.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_private/wifi.h"
|
||||
#include "ethernet_init.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_eth.h"
|
||||
|
||||
// Component header
|
||||
#include "Task.hpp"
|
||||
#include "PwmExpanderPca9685.hpp"
|
||||
#include "StepperDrv8833.hpp"
|
||||
#include "stepperMotor.hpp"
|
||||
#include "cliWrapper.hpp"
|
||||
#include "udpSockets.hpp"
|
||||
#include "tcpSockets.hpp"
|
||||
#include "Mutex.hpp"
|
||||
#include "Queue.hpp"
|
||||
#include "Semaphore.hpp"
|
||||
#include "Task.hpp"
|
||||
#include "Artnet.h"
|
||||
#include "Processor.hpp"
|
||||
#include "nvsFunctions.hpp"
|
||||
#include "mqttClient.hpp"
|
||||
|
||||
// #include "esp_chip_info.h"
|
||||
// #include "esp_flash.h"
|
||||
// #include "esp_ota_ops.h"
|
||||
// #include "esp_partition.h"
|
||||
// #include "esp_random.h"
|
||||
// #include "esp_system.h"
|
||||
// #include "fileSystem.hpp"
|
||||
// #include "lwip/apps/netbiosns.h"
|
||||
// #include "mdns.h"
|
||||
// #include "soc/soc_caps.h"
|
||||
// #include "systemFunctions.hpp"
|
||||
// #include <inttypes.h>
|
||||
// #include <sys/param.h>
|
||||
// #include <unistd.h>
|
||||
@@ -0,0 +1,47 @@
|
||||
#
|
||||
# Compiler options
|
||||
#
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||
CONFIG_LWIP_LOCAL_HOSTNAME="ChaosWatch"
|
||||
|
||||
#
|
||||
# Example Ethernet Configuration
|
||||
#
|
||||
CONFIG_EXAMPLE_USE_SPI_ETHERNET=y
|
||||
CONFIG_EXAMPLE_SPI_ETHERNETS_NUM=1
|
||||
# CONFIG_EXAMPLE_USE_DM9051 is not set
|
||||
# CONFIG_EXAMPLE_USE_KSZ8851SNL is not set
|
||||
CONFIG_EXAMPLE_USE_W5500=y
|
||||
CONFIG_EXAMPLE_ETH_SPI_HOST=1
|
||||
CONFIG_EXAMPLE_ETH_SPI_SCLK_GPIO=6
|
||||
CONFIG_EXAMPLE_ETH_SPI_MOSI_GPIO=7
|
||||
CONFIG_EXAMPLE_ETH_SPI_MISO_GPIO=2
|
||||
CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ=16
|
||||
CONFIG_EXAMPLE_ETH_SPI_CS0_GPIO=3
|
||||
CONFIG_EXAMPLE_ETH_SPI_INT0_GPIO=4
|
||||
CONFIG_EXAMPLE_ETH_SPI_POLLING0_MS=0
|
||||
CONFIG_EXAMPLE_ETH_SPI_PHY_RST0_GPIO=-1
|
||||
CONFIG_EXAMPLE_ETH_SPI_PHY_ADDR0=1
|
||||
# end of Example Ethernet Configuration
|
||||
|
||||
#
|
||||
# Ethernet
|
||||
#
|
||||
CONFIG_ETH_SPI_ETHERNET_W5500=y
|
||||
|
||||
#
|
||||
# Wi-Fi
|
||||
#
|
||||
# CONFIG_ESP_WIFI_GMAC_SUPPORT is not set
|
||||
|
||||
#
|
||||
# LWIP
|
||||
#
|
||||
CONFIG_LWIP_LOCAL_HOSTNAME="ChaosWatch"
|
||||
|
||||
#
|
||||
# Serial flasher config
|
||||
#
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE="8MB"
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# This is the project CMakeLists.txt file for the test subproject
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# Include the components directory of the main application:
|
||||
#
|
||||
set(EXTRA_COMPONENT_DIRS "../components ../managed_components")
|
||||
|
||||
# Set the components to include the tests for.
|
||||
# This can be overriden from CMake cache:
|
||||
# - when invoking CMake directly: cmake -D TEST_COMPONENTS="xxxxx" ..
|
||||
# - when using idf.py: idf.py -T xxxxx build
|
||||
#
|
||||
set(TEST_COMPONENTS "testable freeRtosApi hwHal" CACHE STRING "List of components to test")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(unit_test_test)
|
||||
@@ -0,0 +1,35 @@
|
||||
# Unit Testing
|
||||
|
||||
Unit Test based on unity-library [Unity](https://github.com/ThrowTheSwitch/Unity).
|
||||
|
||||
* _Assertions_ (`TEST_ASSERT` and similar) are used when writing the test cases. See [the reference](https://github.com/ThrowTheSwitch/Unity/blob/master/docs/UnityAssertionsReference.md) for more information about assertions.
|
||||
|
||||
* `UNITY_BEGIN()` and `UNITY_END()` macros allow Unity to count the number of tests which have passed or failed, and display the summary.
|
||||
|
||||
In addition to features of Unity, _test registration_ feature of ESP-IDF is used. This feature works when unit test functions are declared using `TEST_CASE` macro. Such functions from all the object files in the program are automatically collected into a list, and run-time facilities are provided to run functions from this list.
|
||||
|
||||
Note testable components have a *test* subdirectory which is included in the *test project*
|
||||
When the main application project is compiled, tests are not included.
|
||||
Test project includes the tests by setting `TEST_COMPONENTS` variable in the project makefile.
|
||||
|
||||
## Build and flash production code
|
||||
|
||||
* Start an ESP-IDF-Terminal
|
||||
* Run `idf.py -p PORT flash monitor` in the current directory (`unit_test`).
|
||||
* Observe the output: a list of random numbers and their mean value.
|
||||
To exit the serial monitor, type ``Ctrl-]``.
|
||||
|
||||
## Build and flash test code
|
||||
|
||||
* Start an ESP-IDF-Terminal
|
||||
* Enter `test` directory (`unit_test/test`), and run `idf.py -p PORT flash monitor`.
|
||||
To exit the serial monitor, type ``Ctrl-]``.
|
||||
* Observe the output: results of test case execution.
|
||||
|
||||
This is the test menu, which is launched by `unity_run_menu` function in the test project. It allows running the tests in a few ways:
|
||||
* Run specific test by its index: type the number and press ENTER.
|
||||
* Run a group of tests with a certain tag: type the tag, including square brackets, and press ENTER.
|
||||
* nRu all tests except the ones with a certain tag: press `!`, then type the tag, including the square brackets, then press ENTER.
|
||||
* Run test with a specific name: type the name of the test, including quotes, and press ENTER.
|
||||
* Run all the tests: press `*` and then ENTER.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
dependencies:
|
||||
idf:
|
||||
component_hash: null
|
||||
source:
|
||||
type: idf
|
||||
version: 5.3.0
|
||||
manifest_hash: c4f4ec05313d427eb56c633f78ce213e553379725bf2af3c511665f930571beb
|
||||
target: esp32c6
|
||||
version: 1.0.0
|
||||
@@ -0,0 +1,4 @@
|
||||
idf_component_register(
|
||||
SRCS "unitTest.cpp"
|
||||
INCLUDE_DIRS "."
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @file unitTest.cpp
|
||||
* @author Gustice
|
||||
* @brief Unit/Integration-Test frame for hardware bound components
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
extern "C" { // This switch allows the ROS C-implementation to find this main
|
||||
void app_main(void);
|
||||
}
|
||||
|
||||
void app_main(void) {
|
||||
printf("\n#### Running all the registered tests #####\n\n");
|
||||
UNITY_BEGIN();
|
||||
unity_run_all_tests();
|
||||
// Alternatively:
|
||||
// "Executing one test by its name"
|
||||
// unity_run_test_by_name("Mean of an empty array is zero");
|
||||
// Running tests with [mean] tag"
|
||||
// unity_run_tests_by_tag("[mean]", false);
|
||||
// print_banner("Running tests without [fails] tag");
|
||||
// unity_run_tests_by_tag("[fails]", true);
|
||||
UNITY_END();
|
||||
|
||||
printf("\n#### Starting interactive test menu: ####\n");
|
||||
printf("# - run explicit #\n");
|
||||
printf("- run all with *, \n");
|
||||
printf("# - run tagged with [tag], \n");
|
||||
printf("# - run all other than ![tag])\n");
|
||||
printf("# Exit with 'Ctrl-]'\n");
|
||||
unity_run_menu();
|
||||
}
|
||||
+2150
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||
|
||||
CONFIG_ESP_TASK_WDT_EN=n
|
||||
@@ -0,0 +1 @@
|
||||
__pycache__/
|
||||
@@ -0,0 +1,54 @@
|
||||
import math
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from utils.defines import *
|
||||
from utils.generator import *
|
||||
from matplotlib.ticker import (MultipleLocator, AutoMinorLocator)
|
||||
|
||||
def main():
|
||||
st = Stepper(1.8, 32)
|
||||
print(f"Step = {st.Step}; Steps = {st.Steps}")
|
||||
|
||||
wv = QuadWave(64)
|
||||
# plt.plot(wv.Sine, label="sin")
|
||||
# plt.plot(wv.Cosine, label="cos")
|
||||
# plt.legend()
|
||||
# plt.show()
|
||||
|
||||
gen = Generator(st, 0.001, 1, 1)
|
||||
|
||||
curves = []
|
||||
curves.append(gen.createSmoothStep(math.pi)) # 180 deg
|
||||
curves.append(gen.createSmoothStep(math.pi*1/6)) # 60 deg
|
||||
curves.append(gen.createBuildUpStep(math.pi)) # 180 deg
|
||||
curves.append(gen.createRapidStartSwingInEnd(math.pi)) # 180 deg
|
||||
curves.append(gen.createJumpInTarget(math.pi)) # 180 deg
|
||||
|
||||
fig, axes = plt.subplots(len(curves), 1, layout='constrained')
|
||||
|
||||
for (i,ax) in enumerate(axes):
|
||||
crv = curves[i]
|
||||
ax.plot(crv["p"], label="ϕ")
|
||||
ax.plot(crv["w"], label="ω", linestyle="--")
|
||||
|
||||
dax = ax.twinx() # instantiate a second Axes that shares the same x-axis
|
||||
dax.step(range(len(crv["o"])), crv["o"], label="o", linewidth=.5)
|
||||
print(f"EndPos = {crv["pE"]}; (Delta = {math.pi - crv["pE"]}) in {crv["T"] * 1000} steps")
|
||||
|
||||
tMax = max(map(lambda x : x["T"], curves))
|
||||
tEnd = math.ceil(tMax)*1000
|
||||
|
||||
for ax in axes:
|
||||
ax.yaxis.set_ticks([0, math.pi/3, math.pi*2/3, math.pi],["Picto0", "Picto1", "Picto2", "Picto3"])
|
||||
ax.xaxis.set_ticks(range(0,tEnd+1000,1000), range(0,math.ceil(tMax)+1,1))
|
||||
ax.xaxis.set_ticks(range(0,tEnd+1000,100), minor = True)
|
||||
ax.yaxis.grid(linestyle='dotted')
|
||||
ax.set_xlabel('time (s)')
|
||||
ax. set_ylabel('angle')
|
||||
# ax.legend()
|
||||
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,33 @@
|
||||
if __name__ == "__main__": # If the Helper.py file is run directly
|
||||
print("This is a modul and should be imported")
|
||||
|
||||
import math
|
||||
|
||||
class Stepper:
|
||||
# step as degrees
|
||||
Step : float
|
||||
# step as radiant
|
||||
StepRad : float
|
||||
# steps for full resolution
|
||||
Steps : int
|
||||
# microsteps per step
|
||||
Resolution : int
|
||||
|
||||
def __init__(self, step, res):
|
||||
self.Step = step
|
||||
self.Steps = 360 / step
|
||||
self.StepRad = math.pi * step/180
|
||||
self.Resolution = res
|
||||
|
||||
class QuadWave:
|
||||
Sine : []
|
||||
Cosine : []
|
||||
def __init__(self, resolution):
|
||||
sine = []
|
||||
cosine = []
|
||||
s = 2 * math.pi / resolution
|
||||
for i in range(resolution):
|
||||
sine.append(math.sin(s * i))
|
||||
cosine.append(math.cos(s * i))
|
||||
self.Sine = sine
|
||||
self.Cosine = cosine
|
||||
@@ -0,0 +1,180 @@
|
||||
if __name__ == "__main__": # If the Helper.py file is run directly
|
||||
print("This is a modul and should be imported")
|
||||
|
||||
import math
|
||||
|
||||
class Generator:
|
||||
dT = 0
|
||||
aMax = 0
|
||||
wMax = 0 # v = a * t
|
||||
# s = a * t^2 / 2
|
||||
# s = v * t
|
||||
|
||||
def __init__(self, stpr, dt, a, v):
|
||||
self.stepper = stpr
|
||||
self.dT = dt
|
||||
self.aMax = a
|
||||
self.wMax = v
|
||||
|
||||
def __initVars(self):
|
||||
self.__pArr = [0]
|
||||
self.__wArr = [0]
|
||||
self.__tArr = [0]
|
||||
self.__t = 0
|
||||
|
||||
def __appendPoint(self, p, w, t):
|
||||
self.__wArr.append(w)
|
||||
self.__pArr.append(p)
|
||||
self.__tArr.append(t)
|
||||
|
||||
def __accelUntil(self, a, wInit, check):
|
||||
t = 0
|
||||
pX = self.__pArr[-1]
|
||||
while True:
|
||||
w = wInit + a * t
|
||||
p = pX + wInit * t + a * t**2 / 2
|
||||
if check(w, p):
|
||||
break
|
||||
self.__appendPoint(p, w, self.__t + t)
|
||||
t = t + self.dT
|
||||
self.__t = self.__t + t
|
||||
|
||||
def __getStopDistance(self, a, w):
|
||||
tDec = w / a
|
||||
pDec = a * tDec**2 / 2
|
||||
return (pDec, tDec)
|
||||
|
||||
def __moveUntil(self, wM, pEnd):
|
||||
t = 0
|
||||
pX = self.__pArr[-1]
|
||||
while True:
|
||||
p = pX + wM * t
|
||||
if p > pEnd:
|
||||
break
|
||||
self.__appendPoint(p, wM, self.__t + t)
|
||||
t = t + self.dT
|
||||
self.__t = self.__t + t
|
||||
|
||||
def __buildUpSwing(self, a, vM, swings):
|
||||
t = 0
|
||||
pX = self.__pArr[-1]
|
||||
|
||||
TwMax = vM / a
|
||||
tau = 2*math.pi
|
||||
sTau = swings*tau
|
||||
|
||||
while True:
|
||||
t = t + self.dT
|
||||
env = a * t
|
||||
p = math.sin(sTau*t/TwMax) * env * TwMax/sTau
|
||||
w = (p-self.__pArr[-1])/self.dT
|
||||
if w > self.wMax:
|
||||
break
|
||||
self.__appendPoint(p, w, self.__t + t)
|
||||
|
||||
self.__t = self.__t + t
|
||||
|
||||
def __transformToStepper(self) :
|
||||
stp = self.stepper.StepRad
|
||||
res = stp / self.stepper.Resolution
|
||||
oArr = map(lambda x : int((x % stp) / res), self.__pArr)
|
||||
oArr = list(oArr)
|
||||
return {"p": self.__pArr, "w": self.__wArr, "t": self.__tArr, "pE": self.__pArr[-1], "T": self.__t, "o": oArr}
|
||||
|
||||
def __smoothSubStep(self, delta, aM, wM):
|
||||
(pDec,_) = self.__getStopDistance(aM, wM)
|
||||
|
||||
if (pDec < (delta - self.__pArr[-1])/2):
|
||||
# t0..t1: acceleration to max velocity
|
||||
self.__accelUntil(aM, 0, lambda w,p : w > wM)
|
||||
|
||||
# t1..t2: movement to until almost destination
|
||||
self.__moveUntil(wM, delta - pDec)
|
||||
|
||||
# t2..r3: deceleration until destination
|
||||
self.__accelUntil(-aM, wM, lambda w,p : w < 0)
|
||||
else:
|
||||
# t0..t1: acceleration until center is reached
|
||||
pCen = ((delta - self.__pArr[-1]) /2 + self.__pArr[-1])
|
||||
self.__accelUntil(aM, 0, lambda w,p : p > pCen)
|
||||
|
||||
# t2..r3: deceleration until destination
|
||||
self.__accelUntil(-aM, self.__wArr[-1], lambda w,p : w < 0)
|
||||
|
||||
def createSmoothStep(self, delta):
|
||||
self.__initVars()
|
||||
|
||||
(pDec,_) = self.__getStopDistance(self.aMax, self.wMax)
|
||||
|
||||
if (pDec < delta/2):
|
||||
# t0..t1: acceleration to max velocity
|
||||
self.__accelUntil(self.aMax, 0, lambda w,p : w > self.wMax)
|
||||
|
||||
# t1..t2: movement to until almost destination
|
||||
self.__moveUntil(self.wMax, delta - pDec)
|
||||
|
||||
# t2..r3: deceleration until destination
|
||||
self.__accelUntil(-self.aMax, self.wMax, lambda w,p : w < 0)
|
||||
else:
|
||||
# t0..t1: acceleration until center is reached
|
||||
pCen = (self.__pArr[-1]) + delta /2
|
||||
self.__accelUntil(self.aMax, 0, lambda w,p : p > pCen)
|
||||
|
||||
# t2..r3: deceleration until destination
|
||||
self.__accelUntil(-self.aMax, self.__wArr[-1] , lambda w,p : w < 0)
|
||||
|
||||
return self.__transformToStepper()
|
||||
|
||||
def createBuildUpStep(self, delta):
|
||||
self.__initVars()
|
||||
|
||||
# t0..t1: increasing swing
|
||||
self.__buildUpSwing(self.aMax/4, self.wMax, 3)
|
||||
|
||||
(pDec,_) = self.__getStopDistance(self.aMax, self.wMax)
|
||||
|
||||
# t1..t2: movement to until almost destination
|
||||
self.__moveUntil(self.wMax, delta - pDec)
|
||||
|
||||
# t2..r3: deceleration until destination
|
||||
self.__accelUntil(-self.aMax, self.wMax, lambda w,p : w < 0)
|
||||
|
||||
return self.__transformToStepper()
|
||||
|
||||
def createRapidStartSwingInEnd(self, delta):
|
||||
self.__initVars()
|
||||
|
||||
(pDec,_) = self.__getStopDistance(self.aMax, self.wMax)
|
||||
|
||||
# t0..t1: acceleration to max velocity
|
||||
self.__accelUntil(self.aMax*2, 0, lambda w,p : w > self.wMax*2)
|
||||
|
||||
# t1..r2: deceleration until moving speed
|
||||
self.__accelUntil(-self.aMax, self.wMax*2, lambda w,p : w < self.wMax )
|
||||
|
||||
# t2..t3: movement to until destination (override)
|
||||
self.__moveUntil(self.wMax, delta)
|
||||
|
||||
# t3..t4: deceleration
|
||||
self.__accelUntil(-self.aMax, self.wMax, lambda w,p : w < 0)
|
||||
|
||||
pCen = delta + (self.__pArr[-1] - delta) /2
|
||||
self.__accelUntil(-self.aMax, 0, lambda w,p : p < pCen )
|
||||
self.__accelUntil(self.aMax, self.__wArr[-1], lambda w,p : w > 0)
|
||||
|
||||
return self.__transformToStepper()
|
||||
|
||||
def createJumpInTarget(self, delta):
|
||||
self.__initVars()
|
||||
|
||||
jumpInRange = math.pi/3
|
||||
deltaPre = delta - jumpInRange
|
||||
|
||||
self.__smoothSubStep(deltaPre, self.aMax, self.wMax)
|
||||
|
||||
jumpInSteps = [jumpInRange/2, jumpInRange/3, jumpInRange/6]
|
||||
nextPoint = deltaPre
|
||||
for step in jumpInSteps:
|
||||
nextPoint = nextPoint + step
|
||||
self.__smoothSubStep(nextPoint, self.aMax*10, self.wMax*2)
|
||||
return self.__transformToStepper()
|
||||
@@ -0,0 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
|
||||
set(DEF_UTESTDIR "uTest")
|
||||
set(DEF_SRCDIR "src")
|
||||
set(DEF_INCLUDEDIR "include")
|
||||
|
||||
# use just host compilers for this one
|
||||
set(CMAKE_C_COMPILER "gcc")
|
||||
set(CMAKE_CXX_COMPILER "g++")
|
||||
set(CMAKE_GENERATOR "Ninja")
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
|
||||
set(CMAKE_VERBOSE_MAKEFILE TRUE)
|
||||
|
||||
include(test_core.cmake)
|
||||
enable_testing()
|
||||
|
||||
project(UnitTests)
|
||||
|
||||
find_package(doctest REQUIRED PATHS "/mingw64/lib/cmake")
|
||||
# target_link_libraries(${PROJECT_NAME} PRIVATE doctest::doctest)
|
||||
|
||||
addSubDirs_ofRoot("${CMAKE_CURRENT_LIST_DIR}/../components")
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Unit Testing - Software
|
||||
|
||||
In contrast to *test*-directory this module demonstrates how unit tests can also be run on host.
|
||||
These unit tests aim to test freestanding software modules with more convenient methods and tools
|
||||
|
||||
## Setup
|
||||
|
||||
make sure you have following packages installed on your host syste
|
||||
- gnu compiler
|
||||
- cmake
|
||||
- generator
|
||||
- doctest-dev
|
||||
|
||||
## Build and test code
|
||||
|
||||
* Start some terminal in *uTest*-directory that has access to all needed tools (i.e. on windows a mingw environment or WSL)
|
||||
|
||||
Run following commands
|
||||
```sh
|
||||
# run cmake configuration
|
||||
cmake -S . -B build
|
||||
# run cmake build
|
||||
cmake --build build
|
||||
# run tests
|
||||
./build/<testprog>
|
||||
# run all tests with
|
||||
cd build && ctest
|
||||
```
|
||||
@@ -0,0 +1,43 @@
|
||||
function(addSubDirs_ofRoot anchorPath)
|
||||
message("scanning: ${anchorPath}")
|
||||
file(GLOB targetModules RELATIVE "${anchorPath}" "${anchorPath}/*")
|
||||
foreach(module ${targetModules})
|
||||
message("checking: ${module}")
|
||||
get_filename_component(moduleName ${module} NAME)
|
||||
set(modulePath "${anchorPath}/${moduleName}")
|
||||
if (NOT IS_DIRECTORY "${modulePath}")
|
||||
# skip files
|
||||
continue()
|
||||
endif()
|
||||
if (${moduleName} MATCHES "^_.*")
|
||||
# skip ignored component
|
||||
continue()
|
||||
endif()
|
||||
|
||||
if(EXISTS "${modulePath}/${DEF_UTESTDIR}")
|
||||
message("Found unit-test: ${moduleName}")
|
||||
add_subdirectory("${modulePath}/${DEF_UTESTDIR}" ${moduleName})
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
macro(unitTestsFrame)
|
||||
if (CMAKE_TESTING_ENABLED)
|
||||
# get_filename_component(moduleName "${CMAKE_CURRENT_LIST_DIR}/.." NAME)
|
||||
file(GLOB_RECURSE targetSources "../${DEF_SRCDIR}/*.cpp" "../${DEF_SRCDIR}/*.c")
|
||||
add_library(${moduleName} ${targetSources})
|
||||
target_include_directories(${moduleName} PUBLIC "../${DEF_INCLUDEDIR}")
|
||||
|
||||
set(moduleTestName "${moduleName}Test")
|
||||
file(GLOB_RECURSE testSource "*.cpp")
|
||||
add_executable(${moduleTestName} ${testSource})
|
||||
target_link_libraries(${moduleTestName} PRIVATE ${moduleName} doctest::doctest)
|
||||
target_include_directories(${moduleTestName} PRIVATE "../${DEF_SRCDIR}")
|
||||
|
||||
add_test (TEST-${moduleName} ${moduleTestName})
|
||||
|
||||
# gtest_discover_tests(${moduleTestName}
|
||||
# PROPERTIES TIMEOUT 30 LABELS ${moduleName} LABELS "all"
|
||||
# )
|
||||
endif()
|
||||
endmacro()
|
||||
Reference in New Issue
Block a user