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": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "ESP-IDF",
|
"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",
|
"compileCommands": "${config:idf.buildPath}/compile_commands.json",
|
||||||
"includePath": [
|
"includePath": [
|
||||||
"${config:idf.espIdfPath}/components/**",
|
"${config:idf.espIdfPath}/components/**",
|
||||||
"${config:idf.espIdfPathWin}/components/**",
|
"${config:idf.espIdfPathWin}/components/**",
|
||||||
"${workspaceFolder}/**"
|
"${workspaceFolder}/**",
|
||||||
|
"${workspaceFolder}/core/**"
|
||||||
],
|
],
|
||||||
"browse": {
|
"browse": {
|
||||||
"path": [
|
"path": [
|
||||||
"${config:idf.espIdfPath}/components",
|
"${config:idf.espIdfPath}/components",
|
||||||
"${config:idf.espIdfPathWin}/components",
|
"${config:idf.espIdfPathWin}/components",
|
||||||
"${workspaceFolder}"
|
"${workspaceFolder}",
|
||||||
|
"${workspaceFolder}/core"
|
||||||
],
|
],
|
||||||
"limitSymbolsToIncludedHeaders": true
|
"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",
|
"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": {
|
"idf.customExtraVars": {
|
||||||
"OPENOCD_SCRIPTS": "C:\\Tools\\ESP\\tools\\openocd-esp32\\v0.12.0-esp32-20240318/openocd-esp32/share/openocd/scripts",
|
"OPENOCD_SCRIPTS": "C:\\Tools\\ESP\\tools\\openocd-esp32\\v0.12.0-esp32-20240318/openocd-esp32/share/openocd/scripts",
|
||||||
"IDF_CCACHE_ENABLE": "1",
|
"IDF_CCACHE_ENABLE": "1",
|
||||||
@@ -9,9 +9,11 @@
|
|||||||
},
|
},
|
||||||
"idf.espIdfPathWin": "C:\\Tools\\ESP-IDF\\v5.3\\esp-idf",
|
"idf.espIdfPathWin": "C:\\Tools\\ESP-IDF\\v5.3\\esp-idf",
|
||||||
"idf.openOcdConfigs": [
|
"idf.openOcdConfigs": [
|
||||||
"board/esp32-wrover-kit-3.3v.cfg"
|
"board/esp32c6-builtin.cfg"
|
||||||
],
|
],
|
||||||
"idf.portWin": "COM4",
|
"idf.portWin": "COM9",
|
||||||
"idf.pythonBinPathWin": "C:\\Tools\\ESP\\python_env\\idf5.3_py3.11_env\\Scripts\\python.exe",
|
"idf.toolsPathWin": "C:\\Tools\\ESP",
|
||||||
"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)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
set(EXTRA_COMPONENT_DIRS
|
||||||
|
"core/components"
|
||||||
|
"core/managed_components"
|
||||||
|
)
|
||||||
|
|
||||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
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
|
# Ethernet 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).)
|
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||||
|
|
||||||
**Note:** This example uses some internal APIs (e.g. `esp_wifi_internal_tx`) which might get changed between minor versions of ESP-IDF.
|
|
||||||
|
|
||||||
## Overview
|
## 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
|
## 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.
|
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
|
#### Pin Assignment
|
||||||
|
|
||||||
See common pin assignments for Ethernet examples from [upper level](../README.md#common-pin-assignments).
|
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
|
### Configure the project
|
||||||
|
|
||||||
```
|
```
|
||||||
idf.py menuconfig
|
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:
|
See common configurations for Ethernet examples from [upper level](../README.md#common-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`.
|
|
||||||
|
|
||||||
### Build, Flash, and Run
|
### Build, Flash, and Run
|
||||||
|
|
||||||
@@ -53,62 +58,23 @@ See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/l
|
|||||||
|
|
||||||
## Example Output
|
## Example Output
|
||||||
|
|
||||||
### Step 1: Initialize Ethernet and Wi-Fi (AP mode)
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
I (508) example: Power On Ethernet PHY
|
I (394) eth_example: Ethernet Started
|
||||||
I (518) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
|
I (3934) eth_example: Ethernet Link Up
|
||||||
I (518) emac: emac reset done
|
I (3934) eth_example: Ethernet HW Addr 30:ae:a4:c6:87:5b
|
||||||
I (518) example: Ethernet Started
|
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 (538) wifi: wifi driver task: 3ffc7fbc, prio:23, stack:3584, core=0
|
I (5864) eth_example: ~~~~~~~~~~~
|
||||||
I (538) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
|
I (5864) eth_example: ETHIP:192.168.2.151
|
||||||
I (538) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
|
I (5874) eth_example: ETHMASK:255.255.255.0
|
||||||
I (568) wifi: wifi firmware version: ec61a20
|
I (5874) eth_example: ETHGW:192.168.2.2
|
||||||
I (568) wifi: config NVS flash: enabled
|
I (5884) eth_example: ~~~~~~~~~~~
|
||||||
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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 2: Ethernet Connects to Router/Switch/PC (with DHCP server enabled)
|
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).
|
||||||
|
|
||||||
```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.
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
See common troubleshooting for Ethernet examples from [upper level](../README.md#common-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.
|
(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.)
|
||||||
* 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
@@ -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 ".")
|
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