Compare commits

...

22 Commits

Author SHA1 Message Date
gustice a5805aceb2 proj: Migrate to core as git-submodule 2025-02-15 21:49:15 +01:00
gustice c7e1b4805b proj: added dependency to core project 2025-02-15 16:44:23 +01:00
gustice b667f9c255 proj: Simple logo created 2025-02-15 16:43:52 +01:00
gustice 199007c0c5 refactor: Rewiring MQTT constants to toplevel defined constants 2025-02-15 16:43:26 +01:00
gustice c872f1a9ae feature: Pictogram illumination queue 2025-01-18 22:46:00 +01:00
gustice 5fcc0dda1e feature: Including MQTT client 2025-01-16 22:44:55 +01:00
gustice 968d078c07 Adding NVS to project 2025-01-01 21:15:01 +01:00
gustice dd9f59e418 motor control changed to infinit pipeline with continuous buffer 2024-12-23 19:51:25 +01:00
gustice eb0eb436ed added utils from different project 2024-12-15 23:07:25 +01:00
gustice 6db9bd85a5 including light effects from different project 2024-12-15 22:46:44 +01:00
gustice e4621f8ad8 including ArtNet feature from different Project 2024-12-15 22:21:39 +01:00
gustice 1cf86768c0 including tftp based shell and sockets from different project 2024-12-15 12:09:32 +01:00
gustice bd923e4fdc proj: switching to ESP32C6 2024-12-15 00:47:32 +01:00
gustice 2dc06e8d79 including homing sensor 2024-11-23 22:01:01 +01:00
gustice de2c0c167c add gpio and finalisation of pinning 2024-11-16 21:23:26 +01:00
gustice 0181c72a9d PWM based motor driver added from other project 2024-11-16 20:04:38 +01:00
gustice f6608075ff I2C PWM expander added from different Project 2024-11-16 16:42:29 +01:00
gustice deccb3186c adding freeRtos API from different project 2024-11-16 15:50:59 +01:00
gustice 49b3b46ffe migrate to cpp app remove irrelevant parts 2024-11-16 15:30:33 +01:00
gustice 33c875c6bd removing wifi support and include net-IF 2024-11-16 12:59:57 +01:00
gustice 3f627f08f7 simple demo runs 2024-11-16 12:02:17 +01:00
gustice bc651eff27 Compiles but does not run 2024-11-16 11:53:28 +01:00
34 changed files with 5791 additions and 299 deletions
+1
View File
@@ -0,0 +1 @@
build*
+3
View File
@@ -0,0 +1,3 @@
[submodule "core"]
path = core
url = git@github.com:Gustice/EspIdfDevicePlatform.git
+5 -3
View File
@@ -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
}
+8 -6
View File
@@ -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"
}
+12
View File
@@ -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
View File
@@ -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)
+31 -65
View File
@@ -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
![eth2ap](eth2ap.png)
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.)
Submodule
+1
Submodule core added at 3041a17851
+17
View File
@@ -0,0 +1,17 @@
{
"version": "0.2",
"ignorePaths": [],
"dictionaryDefinitions": [],
"dictionaries": [],
"words": [
"Gustice",
"LOGD",
"LOGI",
"LOGW",
"LOGE",
"GPIO",
],
"ignoreWords": [],
"import": []
}
+15
View File
@@ -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.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

+1 -1
View File
@@ -1,2 +1,2 @@
idf_component_register(SRCS "ethernet_example_main.c"
idf_component_register(SRCS "watchMain.cpp"
INCLUDE_DIRS ".")
-222
View File
@@ -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(&eth_handles, &eth_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, &eth_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();
}
+119
View File
@@ -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(&eth_handles, &eth_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, &eth_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]));
}
}
+660
View File
@@ -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(&eth_handles, &eth_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");
}
}
+53
View File
@@ -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>
+2192
View File
File diff suppressed because it is too large Load Diff
+47
View File
@@ -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"
+16
View File
@@ -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)
+35
View File
@@ -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.
+9
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
idf_component_register(
SRCS "unitTest.cpp"
INCLUDE_DIRS "."
)
+37
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_ESP_TASK_WDT_EN=n
+1
View File
@@ -0,0 +1 @@
__pycache__/
+54
View File
@@ -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()
+33
View File
@@ -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
+180
View File
@@ -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()
+26
View File
@@ -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")
+28
View File
@@ -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
```
+43
View File
@@ -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()