Merge pull request #121 from cjkas/feat/esp32p4-build

build: add the ESP32-P4 (#29)
This commit is contained in:
cjkas 2026-10-29 22:04:00 +01:00 • committed by GitHub
commit bb7438d37d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 318 additions and 40 deletions

View file

@ -72,7 +72,7 @@ body:
> After a crash, include the whole panic: the `Guru Meditation Error` line, the `Backtrace:` line and the `ELF file SHA256:` line. > After a crash, include the whole panic: the `Guru Meditation Error` line, the `Backtrace:` line and the `ELF file SHA256:` line.
> Each release has a `SomfyController.ino.<chip>.elf.zip` that decodes the backtrace: > Each release has a `SomfyController.ino.<chip>.elf.zip` that decodes the backtrace:
> `xtensa-esp32s3-elf-addr2line -pfiaC -e SomfyController.ino.esp32s3.elf <backtrace addresses>` > `xtensa-esp32s3-elf-addr2line -pfiaC -e SomfyController.ino.esp32s3.elf <backtrace addresses>`
> (`riscv32-esp-elf-addr2line` on the C3, C5 and C6). > (`riscv32-esp-elf-addr2line` on the C3, C5, C6 and P4).
render: bash render: bash
validations: validations:
required: false required: false

View file

@ -61,7 +61,7 @@ jobs:
# elfname: the symbols of that binary, to decode a crash's backtrace # elfname: the symbols of that binary, to decode a crash's backtrace
# obname: onboard image (bootloader + partitions + firmware + littlefs merged) # obname: onboard image (bootloader + partitions + firmware + littlefs merged)
# for flashing a new chip via USB/serial # for flashing a new chip via USB/serial
# addr_bootloader: chip-dependent (ESP32: 0x1000, S3/C3/C6: 0x0, C5: 0x2000) # addr_bootloader: chip-dependent (ESP32: 0x1000, S3/C3/C6: 0x0, C5/P4: 0x2000)
# addr_fs: must match the spiffs offset in the env's partition table # addr_fs: must match the spiffs offset in the env's partition table
# (0x310000 in both esp32_3MB.csv and esp32_huge_app.csv) # (0x310000 in both esp32_3MB.csv and esp32_huge_app.csv)
include: include:
@ -110,6 +110,24 @@ jobs:
elfname: SomfyController.ino.esp32c6.elf elfname: SomfyController.ino.esp32c6.elf
fsname: SomfyController.littlefs.esp32c6.bin fsname: SomfyController.littlefs.esp32c6.bin
obname: SomfyController.onboard.esp32c6.bin obname: SomfyController.onboard.esp32c6.bin
- env: esp32p4
name: ESP32-P4
chip: ESP32-P4
addr_bootloader: "0x2000"
addr_fs: "0x310000"
fwname: SomfyController.ino.esp32p4.bin
elfname: SomfyController.ino.esp32p4.elf
fsname: SomfyController.littlefs.esp32p4.bin
obname: SomfyController.onboard.esp32p4.bin
- env: esp32p4es
name: ESP32-P4 (before v2.0)
chip: ESP32-P4
addr_bootloader: "0x2000"
addr_fs: "0x310000"
fwname: SomfyController.ino.esp32p4es.bin
elfname: SomfyController.ino.esp32p4es.elf
fsname: SomfyController.littlefs.esp32p4es.bin
obname: SomfyController.onboard.esp32p4es.bin
steps: steps:
- name: Get Release - name: Get Release

View file

@ -1578,23 +1578,22 @@ class Wifi {
{ val: 4, label: 'T-Internet POE - LILYGO', clk: 3, ct: 0, addr: 0, pwr: 16, mdc: 23, mdio: 18 }, { val: 4, label: 'T-Internet POE - LILYGO', clk: 3, ct: 0, addr: 0, pwr: 16, mdc: 23, mdio: 18 },
{ val: 5, label: 'wESP32 v7+ - Silicognition', clk: 0, ct: 2, addr: 0, pwr: -1, mdc: 16, mdio: 17 }, { val: 5, label: 'wESP32 v7+ - Silicognition', clk: 0, ct: 2, addr: 0, pwr: -1, mdc: 16, mdio: 17 },
{ val: 6, label: 'wESP32 < v7 - Silicognition', clk: 0, ct: 0, addr: 0, pwr: -1, mdc: 16, mdio: 17 }, { val: 6, label: 'wESP32 < v7 - Silicognition', clk: 0, ct: 0, addr: 0, pwr: -1, mdc: 16, mdio: 17 },
{ val: 1, label: 'WT32-ETH01 - Wireless Tag', clk: 0, ct: 0, addr: 1, pwr: 16, mdc: 23, mdio: 18 } { val: 1, label: 'WT32-ETH01 - Wireless Tag', clk: 0, ct: 0, addr: 1, pwr: 16, mdc: 23, mdio: 18 },
{ val: 8, label: 'ESP32-P4-WIFI6-POE-ETH - Waveshare', clk: 0, ct: 1, addr: 1, pwr: 51, mdc: 31, mdio: 52, p4: true }
]; ];
ethClockModes = [{ val: 0, label: 'GPIO0 IN' }, { val: 1, label: 'GPIO0 OUT' }, { val: 2, label: 'GPIO16 OUT' }, { val: 3, label: 'GPIO17 OUT' }]; ethClockModes = [{ val: 0, label: 'GPIO0 IN' }, { val: 1, label: 'GPIO0 OUT' }, { val: 2, label: 'GPIO16 OUT' }, { val: 3, label: 'GPIO17 OUT' }];
// The P4 takes the clock in on GPIO 50 only (eth_clock_id_t in EthPhy.h).
ethClockModesP4 = [{ val: 0, label: 'GPIO50 IN' }];
// The ids the firmware stores (eth_phy_id_t in EthPhy.h). 4 is the DM9051, an SPI chip the ESP32's MAC can't drive. // The ids the firmware stores (eth_phy_id_t in EthPhy.h). 4 is the DM9051, an SPI chip the ESP32's MAC can't drive.
ethPhyTypes = [{ val: 0, label: 'LAN8720' }, { val: 1, label: 'TLK110' }, { val: 2, label: 'RTL8201' }, { val: 3, label: 'DP83848' }, { val: 5, label: 'KSZ8041' }, { val: 6, label: 'KSZ8081' }]; ethPhyTypes = [{ val: 0, label: 'LAN8720' }, { val: 1, label: 'TLK110' }, { val: 2, label: 'RTL8201' }, { val: 3, label: 'DP83848' }, { val: 5, label: 'KSZ8041' }, { val: 6, label: 'KSZ8081' }];
init() { init() {
document.getElementById("divNetworkStrength").innerHTML = this.displaySignal(-100); document.getElementById("divNetworkStrength").innerHTML = this.displaySignal(-100);
if (this.initialized) return; if (this.initialized) return;
let addr = []; let addr = [];
this.loadETHDropdown(document.getElementById('selETHClkMode'), this.ethClockModes);
this.loadETHDropdown(document.getElementById('selETHPhyType'), this.ethPhyTypes); this.loadETHDropdown(document.getElementById('selETHPhyType'), this.ethPhyTypes);
this.loadETHDropdown(document.getElementById('selETHBoardType'), this.ethBoardTypes);
for (let i = 0; i < 32; i++) addr.push({ val: i, label: `PHY ${i}` }); for (let i = 0; i < 32; i++) addr.push({ val: i, label: `PHY ${i}` });
this.loadETHDropdown(document.getElementById('selETHAddress'), addr); this.loadETHDropdown(document.getElementById('selETHAddress'), addr);
this.loadETHPins(document.getElementById('selETHPWRPin'), 'power'); this.loadETHOptions();
this.loadETHPins(document.getElementById('selETHMDCPin'), 'mdc', 23);
this.loadETHPins(document.getElementById('selETHMDIOPin'), 'mdio', 18);
ui.toElement(document.getElementById('divNetAdapter'), { ui.toElement(document.getElementById('divNetAdapter'), {
wifi: {ssid:'', passphrase:''}, wifi: {ssid:'', passphrase:''},
ethernet: { boardType: 1, wirelessFallback: false, dhcp: true, dns1: '', dns2: '', ip: '', gateway: '' } ethernet: { boardType: 1, wirelessFallback: false, dhcp: true, dns1: '', dns2: '', ip: '', gateway: '' }
@ -1602,14 +1601,27 @@ class Wifi {
this.onETHBoardTypeChanged(document.getElementById('selETHBoardType')); this.onETHBoardTypeChanged(document.getElementById('selETHBoardType'));
this.initialized = true; this.initialized = true;
} }
loadETHPins(sel, type, selected) { isP4() { return (document.getElementById('divContainer').getAttribute('data-chipmodel') || '').startsWith('p4'); }
clockModes() { return this.isP4() ? this.ethClockModesP4 : this.ethClockModes; }
// The board presets, clock modes and pins of the chip: the ESP32's or the
// P4's. The chip comes with the sync, after init().
loadETHOptions() {
let p4 = this.isP4();
this.loadETHDropdown(document.getElementById('selETHClkMode'), this.clockModes());
this.loadETHDropdown(document.getElementById('selETHBoardType'), this.ethBoardTypes.filter(b => b.val === 0 || (b.p4 === true) === p4));
let maxPin = p4 ? 54 : 35;
this.loadETHPins(document.getElementById('selETHPWRPin'), 'power', undefined, maxPin);
this.loadETHPins(document.getElementById('selETHMDCPin'), 'mdc', p4 ? 31 : 23, maxPin);
this.loadETHPins(document.getElementById('selETHMDIOPin'), 'mdio', p4 ? 52 : 18, maxPin);
}
loadETHPins(sel, type, selected, maxPin) {
let arr = []; let arr = [];
switch (type) { switch (type) {
case 'power': case 'power':
arr.push({ val: -1, label: 'None' }); arr.push({ val: -1, label: 'None' });
break; break;
} }
for (let i = 0; i < 36; i++) { for (let i = 0; i <= maxPin; i++) {
arr.push({ val: i, label: `GPIO ${i}` }); arr.push({ val: i, label: `GPIO ${i}` });
} }
this.loadETHDropdown(sel, arr, selected); this.loadETHDropdown(sel, arr, selected);
@ -1652,6 +1664,7 @@ class Wifi {
else { else {
let settings = ProtoViews.network(proto.schema, reply); let settings = ProtoViews.network(proto.schema, reply);
console.log(settings); console.log(settings);
this.loadETHOptions();
let hasEthernet = settings.hasEthernet; let hasEthernet = settings.hasEthernet;
this.hasEthernet = hasEthernet; this.hasEthernet = hasEthernet;
document.getElementById('divHardwired').style.display = hasEthernet ? '' : 'none'; document.getElementById('divHardwired').style.display = hasEthernet ? '' : 'none';
@ -1815,7 +1828,7 @@ class Wifi {
if (obj.connType >= 2) { if (obj.connType >= 2) {
let boardType = this.ethBoardTypes.find(elem => obj.ethernet.boardType === elem.val); let boardType = this.ethBoardTypes.find(elem => obj.ethernet.boardType === elem.val);
let phyType = this.ethPhyTypes.find(elem => obj.ethernet.phyType === elem.val); let phyType = this.ethPhyTypes.find(elem => obj.ethernet.phyType === elem.val);
let clkMode = this.ethClockModes.find(elem => obj.ethernet.CLKMode === elem.val); let clkMode = this.clockModes().find(elem => obj.ethernet.CLKMode === elem.val);
let div = document.createElement('div'); let div = document.createElement('div');
let html = `<div id="divLanSettings" class="inst-overlay">`; let html = `<div id="divLanSettings" class="inst-overlay">`;
html += '<div style="width:100%;color:red;text-align:center;font-weight:bold;"><span style="padding:10px;display:inline-block;width:100%;border-radius:5px;border-top-right-radius:17px;border-top-left-radius:17px;background:white;">BEWARE ... WARNING ... DANGER<span></div>'; html += '<div style="width:100%;color:red;text-align:center;font-weight:bold;"><span style="padding:10px;display:inline-block;width:100%;border-radius:5px;border-top-right-radius:17px;border-top-left-radius:17px;background:white;">BEWARE ... WARNING ... DANGER<span></div>';
@ -2720,12 +2733,18 @@ class Somfy {
{ name: 's3', maxPins: 48, inputs: [19, 20, 22, 23, 24, 25, 27, 28, 29, 30, 31, 32], outputs: [19, 20, 22, 23, 24, 25, 27, 28, 29, 30, 31, 32] }, { name: 's3', maxPins: 48, inputs: [19, 20, 22, 23, 24, 25, 27, 28, 29, 30, 31, 32], outputs: [19, 20, 22, 23, 24, 25, 27, 28, 29, 30, 31, 32] },
{ name: 'c3', maxPins: 21, inputs: [11, 12, 13, 14, 15, 16, 17, 18, 19, 20], outputs: [11, 12, 13, 14, 15, 16, 17, 21] }, { name: 'c3', maxPins: 21, inputs: [11, 12, 13, 14, 15, 16, 17, 18, 19, 20], outputs: [11, 12, 13, 14, 15, 16, 17, 21] },
{ name: 'c5', maxPins: 28, inputs: [13, 14, 15, 16, 17, 18, 19, 20, 21, 22], outputs: [13, 14, 15, 16, 17, 18, 19, 20, 21, 22] }, { name: 'c5', maxPins: 28, inputs: [13, 14, 15, 16, 17, 18, 19, 20, 21, 22], outputs: [13, 14, 15, 16, 17, 18, 19, 20, 21, 22] },
{ name: 'c6', maxPins: 23, inputs: [], outputs: [] } { name: 'c6', maxPins: 23, inputs: [], outputs: [] },
// Without the C6's SDIO link (14-19, 54), USB Serial/JTAG (24, 25),
// Ethernet (28-31, 34, 35, 49-52), UART0 (37, 38) and the strapping
// pins (34-38).
{ name: 'p4', maxPins: 54, inputs: [14, 15, 16, 17, 18, 19, 24, 25, 28, 29, 30, 31, 34, 35, 36, 37, 38, 49, 50, 51, 52, 54], outputs: [14, 15, 16, 17, 18, 19, 24, 25, 28, 29, 30, 31, 34, 35, 36, 37, 38, 49, 50, 51, 52, 54] }
]; ];
loadPins(type, sel, opt) { loadPins(type, sel, opt) {
while (sel.firstChild) sel.removeChild(sel.firstChild); while (sel.firstChild) sel.removeChild(sel.firstChild);
let cm = document.getElementById('divContainer').getAttribute('data-chipmodel'); let cm = document.getElementById('divContainer').getAttribute('data-chipmodel');
// The pre-v3 P4 build has the same pins.
if (cm === 'p4es') cm = 'p4';
let pm = this.pinMaps.find(x => x.name === cm) || { name: '', maxPins: 39, inputs: [], outputs: [] }; let pm = this.pinMaps.find(x => x.name === cm) || { name: '', maxPins: 39, inputs: [], outputs: [] };
//console.log({ cm: cm, pm: pm }); //console.log({ cm: cm, pm: pm });
for (let i = 0; i <= pm.maxPins; i++) { for (let i = 0; i <= pm.maxPins; i++) {

View file

@ -15,7 +15,8 @@ We moved every firmware env from `espressif32 @ 6.13.0` (Arduino 2.0.17 on ESP-I
> **Amended by [ADR 0006](0006-two-security-modes-eleven-http-routes.md) (4.0.0).** The C3 builds again. Without the old API (ports 8080 and 8081), its image is 1,498,512 bytes at level 2 and leaves 72.6 KB of the OTA slot. It runs on chip revision v0.3 or later only: the framework's C3 libraries are built with `CONFIG_ESP32C3_REV_MIN_3`. Its default radio pins moved off GPIO 12-17, which hold the SPI flash. > **Amended by [ADR 0006](0006-two-security-modes-eleven-http-routes.md) (4.0.0).** The C3 builds again. Without the old API (ports 8080 and 8081), its image is 1,498,512 bytes at level 2 and leaves 72.6 KB of the OTA slot. It runs on chip revision v0.3 or later only: the framework's C3 libraries are built with `CONFIG_ESP32C3_REV_MIN_3`. Its default radio pins moved off GPIO 12-17, which hold the SPI flash.
- **The C5 is updated over USB.** New in 4.0.0. At level 2 its image left 3.4 KB of the OTA slot, under CI's 8 KB, so like the C6 it is released as a debug build with one 3 MB app slot (`esp32_huge_app.csv`), which no OTA can write. The framework's C5 libraries run on chip revision v1.0 or later only (`CONFIG_ESP32C5_REV_MIN_FULL=100`). Its WiFi has 5 GHz too, so the passive scans for the configured network (`src/ESPNetwork.cpp`, 300 ms a channel) cover about 38 channels instead of 13: about 11 s instead of 4 s, depending on the country's channels. - **The C5 is updated over USB.** New in 4.0.0. At level 2 its image left 3.4 KB of the OTA slot, under CI's 8 KB, so like the C6 it is released as a debug build with one 3 MB app slot (`esp32_huge_app.csv`), which no OTA can write. The framework's C5 libraries run on chip revision v1.0 or later only (`CONFIG_ESP32C5_REV_MIN_FULL=100`). Its WiFi has 5 GHz too, so the passive scans for the configured network (`src/ESPNetwork.cpp`, 300 ms a channel) cover about 38 channels instead of 13: about 11 s instead of 4 s, depending on the country's channels.
- **Ethernet PHY ids are ours.** Arduino 3.x renumbered `eth_phy_type_t` (`ETH_PHY_GENERIC` is 0). NVS, backups and the web UI keep the Arduino 2.x numbers as `eth_phy_id_t` (`src/EthPhy.h`), translated by name when Ethernet starts. Never cast a stored id to the framework enum. - **The P4 has two builds.** New in 4.0.0, for boards such as Waveshare's ESP32-P4-WIFI6-POE-ETH. The framework's P4 libraries run on chip revision v3.01 or later only (`CONFIG_ESP32P4_REV_MIN_FULL=301`), its `esp32p4_es` libraries on revisions before v2.0, so `esp32p4` (chip model `p4`) and `esp32p4es` (`p4es`) each get their own release files. No build runs on v2.x or v3.00. Both keep the OTA slot: at level 2 their images leave about 128 KB. WiFi goes through the board's ESP32-C6 on SDIO (esp-hosted, the framework's pins 14-19 and reset 54), so the C6 has to run a matching esp-hosted slave firmware, which Waveshare's boards ship with.
- **Ethernet only on the original ESP32.** Arduino 3.x has the RMII MAC API only where `CONFIG_ETH_USE_ESP32_EMAC` is set. S3, C3, C5 and C6 build without Ethernet. - **Ethernet PHY ids are ours.** Arduino 3.x renumbered `eth_phy_type_t` (`ETH_PHY_GENERIC` is 0). NVS, backups and the web UI keep the Arduino 2.x numbers as `eth_phy_id_t` (`src/EthPhy.h`), translated by name when Ethernet starts. Never cast a stored id to the framework enum. The RMII clock mode is kept the same way, as `eth_clock_id_t` in the ESP32's numbering: the P4's `eth_clock_mode_t` is the IDF's `emac_rmii_clock_mode_t`, whose 0 is a deprecated default. On the P4 only clock in (GPIO 50) translates, since `ETH.begin()` keeps the clock on that pin, and a save refuses the others (`ETH_CLOCK_ID_MAX`).
- **Ethernet only on the original ESP32 and the P4.** Arduino 3.x has the RMII MAC API only where `CONFIG_ETH_USE_ESP32_EMAC` is set. S3, C3, C5 and C6 build without Ethernet.
- **Updates roll back until proven.** We override the core's `verifyRollbackLater()` hook to return true, and `loop()` marks the new firmware valid once it has run 60 s and the network is up. A reset before then boots the previous firmware. The LittleFS image an update wrote stays, so the old firmware runs with the new web UI. - **Updates roll back until proven.** We override the core's `verifyRollbackLater()` hook to return true, and `loop()` marks the new firmware valid once it has run 60 s and the network is up. A reset before then boots the previous firmware. The LittleFS image an update wrote stays, so the old firmware runs with the new web UI.
- **Tooling.** pioarduino needs its own Core fork (`pip install pioarduino`, the pioarduino IDE extension in VS Code), which it installs into `~/.platformio/penv` in place of PlatformIO Core. - **Tooling.** pioarduino needs its own Core fork (`pip install pioarduino`, the pioarduino IDE extension in VS Code), which it installs into `~/.platformio/penv` in place of PlatformIO Core.

View file

@ -126,6 +126,27 @@ build_flags =
${esp32.build_flags} ${esp32.build_flags}
-DCORE_DEBUG_LEVEL=3 -DCORE_DEBUG_LEVEL=3
; ESP32-P4 boards such as Waveshare's ESP32-P4-WIFI6-POE-ETH: WiFi through an
; ESP32-C6 on SDIO (esp-hosted), Ethernet on the P4's own RMII MAC. The
; framework's P4 libraries run on chip revision v3.01 or later only, and its
; esp32p4_es libraries on revisions before v2.0, so each gets its own build.
; No build runs on v2.x or v3.00.
[env:esp32p4]
extends = esp32
board = esp32-p4_r3
board_build.partitions = esp32_3MB.csv
build_flags =
${dualcore.build_flags}
-DCORE_DEBUG_LEVEL=2
[env:esp32p4es]
extends = esp32
board = esp32-p4
board_build.partitions = esp32_3MB.csv
build_flags =
${dualcore.build_flags}
-DCORE_DEBUG_LEVEL=2
; Host-side unit tests: pio test -e native (needs gcc/g++ on PATH). ; Host-side unit tests: pio test -e native (needs gcc/g++ on PATH).
; Only the sources the tests cover are built; test/stubs stands in for Arduino.h. ; Only the sources the tests cover are built; test/stubs stands in for Arduino.h.
[env:native] [env:native]

View file

@ -149,7 +149,7 @@ static net_record_t netRecord() {
#if HAS_ETHERNET #if HAS_ETHERNET
rec.ethBoardType = settings.Ethernet.boardType; rec.ethBoardType = settings.Ethernet.boardType;
rec.ethPhyType = settings.Ethernet.phyType; rec.ethPhyType = settings.Ethernet.phyType;
rec.ethClkMode = static_cast<uint8_t>(settings.Ethernet.CLKMode); rec.ethClkMode = settings.Ethernet.CLKMode;
rec.ethPhyAddress = settings.Ethernet.phyAddress; rec.ethPhyAddress = settings.Ethernet.phyAddress;
rec.ethPWRPin = settings.Ethernet.PWRPin; rec.ethPWRPin = settings.Ethernet.PWRPin;
rec.ethMDCPin = settings.Ethernet.MDCPin; rec.ethMDCPin = settings.Ethernet.MDCPin;
@ -196,7 +196,7 @@ static void applyNetRecord(const net_record_t &rec, const restore_options_t &opt
ESP_LOGI(TAG, "Restoring Ethernet adapter settings"); ESP_LOGI(TAG, "Restoring Ethernet adapter settings");
settings.Ethernet.boardType = rec.ethBoardType; settings.Ethernet.boardType = rec.ethBoardType;
settings.Ethernet.phyType = rec.ethPhyType; settings.Ethernet.phyType = rec.ethPhyType;
settings.Ethernet.CLKMode = static_cast<eth_clock_mode_t>(rec.ethClkMode); settings.Ethernet.CLKMode = rec.ethClkMode;
settings.Ethernet.phyAddress = rec.ethPhyAddress; settings.Ethernet.phyAddress = rec.ethPhyAddress;
settings.Ethernet.PWRPin = rec.ethPWRPin; settings.Ethernet.PWRPin = rec.ethPWRPin;
settings.Ethernet.MDCPin = rec.ethMDCPin; settings.Ethernet.MDCPin = rec.ethMDCPin;

View file

@ -145,6 +145,17 @@ bool ConfigSettings::begin() {
strcpy(this->chipModel, "c6"); strcpy(this->chipModel, "c6");
break; break;
#endif #endif
#ifdef CONFIG_IDF_TARGET_ESP32P4
// Chips before v2.0 run their own build (esp32p4_es libraries), so they
// get their own release files. v2.x and v3.00 run neither.
case esp_chip_model_t::CHIP_ESP32P4:
#if CONFIG_ESP32P4_SELECTS_REV_LESS_V3
strcpy(this->chipModel, "p4es");
#else
strcpy(this->chipModel, "p4");
#endif
break;
#endif
default: default:
sprintf(this->chipModel, "UNK%d", static_cast<int>(ci.model)); sprintf(this->chipModel, "UNK%d", static_cast<int>(ci.model));
break; break;
@ -499,7 +510,7 @@ bool EthernetSettings::begin() {
bool EthernetSettings::fromJSON(JsonObject &obj) { bool EthernetSettings::fromJSON(JsonObject &obj) {
if(!obj["boardType"].isNull()) this->boardType = obj["boardType"]; if(!obj["boardType"].isNull()) this->boardType = obj["boardType"];
if(!obj["phyAddress"].isNull()) this->phyAddress = obj["phyAddress"]; if(!obj["phyAddress"].isNull()) this->phyAddress = obj["phyAddress"];
if(!obj["CLKMode"].isNull()) this->CLKMode = static_cast<eth_clock_mode_t>(obj["CLKMode"]); if(!obj["CLKMode"].isNull()) this->CLKMode = obj["CLKMode"];
if(!obj["phyType"].isNull()) this->phyType = obj["phyType"]; if(!obj["phyType"].isNull()) this->phyType = obj["phyType"];
if(!obj["PWRPin"].isNull()) this->PWRPin = obj["PWRPin"]; if(!obj["PWRPin"].isNull()) this->PWRPin = obj["PWRPin"];
if(!obj["MDCPin"].isNull()) this->MDCPin = obj["MDCPin"]; if(!obj["MDCPin"].isNull()) this->MDCPin = obj["MDCPin"];
@ -507,9 +518,7 @@ bool EthernetSettings::fromJSON(JsonObject &obj) {
return true; return true;
} }
bool EthernetSettings::usesPin(uint8_t pin) { bool EthernetSettings::usesPin(uint8_t pin) {
if((this->CLKMode == 0 || this->CLKMode == 1) && pin == 0) return true; if(ethClockPin(this->CLKMode) == pin) return true;
else if(this->CLKMode == 2 && pin == 16) return true;
else if(this->CLKMode == 3 && pin == 17) return true;
else if(this->PWRPin == pin) return true; else if(this->PWRPin == pin) return true;
else if(this->MDCPin == pin) return true; else if(this->MDCPin == pin) return true;
else if(this->MDIOPin == pin) return true; else if(this->MDIOPin == pin) return true;
@ -522,7 +531,7 @@ bool EthernetSettings::save() {
pref.putChar("boardType", this->boardType); pref.putChar("boardType", this->boardType);
pref.putChar("phyAddress", this->phyAddress); pref.putChar("phyAddress", this->phyAddress);
pref.putChar("phyType", this->phyType); pref.putChar("phyType", this->phyType);
pref.putChar("CLKMode", static_cast<uint8_t>(this->CLKMode)); pref.putChar("CLKMode", this->CLKMode);
pref.putChar("PWRPin", this->PWRPin); pref.putChar("PWRPin", this->PWRPin);
pref.putChar("MDCPin", this->MDCPin); pref.putChar("MDCPin", this->MDCPin);
pref.putChar("MDIOPin", this->MDIOPin); pref.putChar("MDIOPin", this->MDIOPin);
@ -534,7 +543,7 @@ bool EthernetSettings::load() {
pref.begin("ETH"); pref.begin("ETH");
this->boardType = pref.getChar("boardType", this->boardType); this->boardType = pref.getChar("boardType", this->boardType);
this->phyType = pref.getChar("phyType", this->phyType); this->phyType = pref.getChar("phyType", this->phyType);
this->CLKMode = static_cast<eth_clock_mode_t>(pref.getChar("CLKMode", ETH_CLOCK_GPIO0_IN)); this->CLKMode = pref.getChar("CLKMode", this->CLKMode);
this->phyAddress = pref.getChar("phyAddress", this->phyAddress); this->phyAddress = pref.getChar("phyAddress", this->phyAddress);
this->PWRPin = pref.getChar("PWRPin", this->PWRPin); this->PWRPin = pref.getChar("PWRPin", this->PWRPin);
this->MDCPin = pref.getChar("MDCPin", this->MDCPin); this->MDCPin = pref.getChar("MDCPin", this->MDCPin);

View file

@ -88,14 +88,26 @@ class WifiSettings: BaseSettings {
class EthernetSettings: BaseSettings { class EthernetSettings: BaseSettings {
public: public:
EthernetSettings(); EthernetSettings();
uint8_t CLKMode = static_cast<uint8_t>(eth_clock_id_t::GPIO0_IN); // An eth_clock_id_t, see EthPhy.h.
#if defined(CONFIG_IDF_TARGET_ESP32P4)
// The IP101 wiring of Espressif's P4 boards, which Waveshare's ESP32-P4-ETH
// boards copy, and the web UI's board type for it. The ESP32's defaults
// would put MDIO on an SDIO pin.
uint8_t boardType = 8;
uint8_t phyType = static_cast<uint8_t>(eth_phy_id_t::TLK110);
int8_t phyAddress = 1;
int8_t PWRPin = 51;
int8_t MDCPin = 31;
int8_t MDIOPin = 52;
#else
uint8_t boardType = 0; // These board types are enumerated in the ui and used to set the chip settings. uint8_t boardType = 0; // These board types are enumerated in the ui and used to set the chip settings.
uint8_t phyType = static_cast<uint8_t>(eth_phy_id_t::LAN8720); // An eth_phy_id_t, see EthPhy.h. uint8_t phyType = static_cast<uint8_t>(eth_phy_id_t::LAN8720); // An eth_phy_id_t, see EthPhy.h.
eth_clock_mode_t CLKMode = ETH_CLOCK_GPIO0_IN;
// The LAN8720 wiring Arduino 2.x defaulted to. 3.x has no defaults. // The LAN8720 wiring Arduino 2.x defaulted to. 3.x has no defaults.
int8_t phyAddress = 0; int8_t phyAddress = 0;
int8_t PWRPin = -1; int8_t PWRPin = -1;
int8_t MDCPin = 23; int8_t MDCPin = 23;
int8_t MDIOPin = 18; int8_t MDIOPin = 18;
#endif
bool begin(); bool begin();
bool fromJSON(JsonObject &obj); bool fromJSON(JsonObject &obj);

View file

@ -62,7 +62,7 @@ static void writeSettings(ProtoWriter &w, settings_section_t section) {
w.add(proto_field_t::hasEthernet, true); w.add(proto_field_t::hasEthernet, true);
w.add(proto_field_t::boardType, settings.Ethernet.boardType); w.add(proto_field_t::boardType, settings.Ethernet.boardType);
w.add(proto_field_t::phyType, settings.Ethernet.phyType); w.add(proto_field_t::phyType, settings.Ethernet.phyType);
w.add(proto_field_t::CLKMode, static_cast<uint8_t>(settings.Ethernet.CLKMode)); w.add(proto_field_t::CLKMode, settings.Ethernet.CLKMode);
w.add(proto_field_t::phyAddress, settings.Ethernet.phyAddress); w.add(proto_field_t::phyAddress, settings.Ethernet.phyAddress);
w.add(proto_field_t::PWRPin, settings.Ethernet.PWRPin); w.add(proto_field_t::PWRPin, settings.Ethernet.PWRPin);
w.add(proto_field_t::MDCPin, settings.Ethernet.MDCPin); w.add(proto_field_t::MDCPin, settings.Ethernet.MDCPin);

View file

@ -356,11 +356,14 @@ bool ESPNetwork::connectWired() {
ETH.setHostname("ESPSomfy-RTS"); ETH.setHostname("ESPSomfy-RTS");
ESP_LOGD(TAG, "Set hostname to: %s", ETH.getHostname()); ESP_LOGD(TAG, "Set hostname to: %s", ETH.getHostname());
eth_phy_type_t phyType; eth_phy_type_t phyType;
eth_clock_mode_t clkMode;
bool begun = false; bool begun = false;
if(!ethPhyType(settings.Ethernet.phyType, phyType)) if(!ethPhyType(settings.Ethernet.phyType, phyType))
ESP_LOGE(TAG, "Ethernet PHY type %u is not supported", (unsigned int)settings.Ethernet.phyType); ESP_LOGE(TAG, "Ethernet PHY type %u is not supported", (unsigned int)settings.Ethernet.phyType);
else if(!ethClockMode(settings.Ethernet.CLKMode, clkMode))
ESP_LOGE(TAG, "Ethernet clock mode %u is not supported", (unsigned int)settings.Ethernet.CLKMode);
else else
begun = ETH.begin(phyType, settings.Ethernet.phyAddress, settings.Ethernet.MDCPin, settings.Ethernet.MDIOPin, settings.Ethernet.PWRPin, settings.Ethernet.CLKMode); begun = ETH.begin(phyType, settings.Ethernet.phyAddress, settings.Ethernet.MDCPin, settings.Ethernet.MDIOPin, settings.Ethernet.PWRPin, clkMode);
if(!begun) { if(!begun) {
ESP_LOGE(TAG, "Ethernet Begin failed"); ESP_LOGE(TAG, "Ethernet Begin failed");
this->ethStarted = false; this->ethStarted = false;

View file

@ -5,8 +5,8 @@
#include <sdkconfig.h> #include <sdkconfig.h>
#endif #endif
// Ethernet needs the RMII MAC, which only the original ESP32 has. The C3, C5, // Ethernet needs the RMII MAC, which only the original ESP32 and the P4 have.
// S3 and C6 build without it. // The C3, C5, S3 and C6 build without it.
#if defined(CONFIG_ETH_USE_ESP32_EMAC) && CONFIG_ETH_USE_ESP32_EMAC #if defined(CONFIG_ETH_USE_ESP32_EMAC) && CONFIG_ETH_USE_ESP32_EMAC
#define HAS_ETHERNET 1 #define HAS_ETHERNET 1
#else #else
@ -26,6 +26,39 @@ enum class eth_phy_id_t : uint8_t {
KSZ8081 = 6 KSZ8081 = 6
}; };
// RMII clock ids as stored in NVS and backups and sent to the web UI: the
// original ESP32's eth_clock_mode_t values. The P4's eth_clock_mode_t is the
// IDF's emac_rmii_clock_mode_t, a different enum, so an id is translated.
enum class eth_clock_id_t : uint8_t {
GPIO0_IN = 0, // The clock comes in. On the P4, on GPIO 50.
GPIO0_OUT = 1,
GPIO16_OUT = 2,
GPIO17_OUT = 3
};
// The highest clock id the chip takes: the P4's ETH.begin() takes the clock
// in on GPIO 50 only.
#if defined(CONFIG_IDF_TARGET_ESP32P4)
constexpr uint8_t ETH_CLOCK_ID_MAX = 0;
#else
constexpr uint8_t ETH_CLOCK_ID_MAX = 3;
#endif
// The GPIO a stored clock id puts the RMII clock on, -1 for an id the chip
// can't use.
inline int8_t ethClockPin(uint8_t id) {
#if defined(CONFIG_IDF_TARGET_ESP32P4)
return static_cast<eth_clock_id_t>(id) == eth_clock_id_t::GPIO0_IN ? 50 : -1;
#else
switch(static_cast<eth_clock_id_t>(id)) {
case eth_clock_id_t::GPIO0_IN:
case eth_clock_id_t::GPIO0_OUT: return 0;
case eth_clock_id_t::GPIO16_OUT: return 16;
case eth_clock_id_t::GPIO17_OUT: return 17;
default: return -1;
}
#endif
}
#if HAS_ETHERNET #if HAS_ETHERNET
#include <ETH.h> #include <ETH.h>
// Sets type to the framework's PHY for a stored id. False for an id the RMII // Sets type to the framework's PHY for a stored id. False for an id the RMII
@ -41,5 +74,20 @@ inline bool ethPhyType(uint8_t id, eth_phy_type_t &type) {
default: return false; default: return false;
} }
} }
// Sets mode to the framework's clock mode for a stored id. False for an id
// the chip can't use.
inline bool ethClockMode(uint8_t id, eth_clock_mode_t &mode) {
switch(static_cast<eth_clock_id_t>(id)) {
#if defined(CONFIG_IDF_TARGET_ESP32P4)
case eth_clock_id_t::GPIO0_IN: mode = EMAC_CLK_EXT_IN; return true;
#else
case eth_clock_id_t::GPIO0_IN: mode = ETH_CLOCK_GPIO0_IN; return true;
case eth_clock_id_t::GPIO0_OUT: mode = ETH_CLOCK_GPIO0_OUT; return true;
case eth_clock_id_t::GPIO16_OUT: mode = ETH_CLOCK_GPIO16_OUT; return true;
case eth_clock_id_t::GPIO17_OUT: mode = ETH_CLOCK_GPIO17_OUT; return true;
#endif
default: return false;
}
}
#endif #endif
#endif #endif

View file

@ -3,6 +3,7 @@
#include <string.h> #include <string.h>
#include "ProtoSettings.h" #include "ProtoSettings.h"
#include "ProtoParse.h" #include "ProtoParse.h"
#include "EthPhy.h"
static const char *const SECTIONS[] = {"general", "network", "mqtt", "security", "radio"}; static const char *const SECTIONS[] = {"general", "network", "mqtt", "security", "radio"};
const char *settingsSectionName(settings_section_t section) { return SECTIONS[static_cast<uint8_t>(section)]; } const char *settingsSectionName(settings_section_t section) { return SECTIONS[static_cast<uint8_t>(section)]; }
@ -45,11 +46,12 @@ static const settings_field_t FIELDS[] = {
{settings_part_t::wifi, proto_field_t::hidden, nullptr, 0, 0}, {settings_part_t::wifi, proto_field_t::hidden, nullptr, 0, 0},
{settings_part_t::ethernet, proto_field_t::boardType, nullptr, 0, 255}, {settings_part_t::ethernet, proto_field_t::boardType, nullptr, 0, 255},
{settings_part_t::ethernet, proto_field_t::phyType, nullptr, 0, 255}, {settings_part_t::ethernet, proto_field_t::phyType, nullptr, 0, 255},
{settings_part_t::ethernet, proto_field_t::CLKMode, nullptr, 0, 3}, {settings_part_t::ethernet, proto_field_t::CLKMode, nullptr, 0, ETH_CLOCK_ID_MAX},
{settings_part_t::ethernet, proto_field_t::phyAddress, nullptr, -1, 31}, {settings_part_t::ethernet, proto_field_t::phyAddress, nullptr, -1, 31},
{settings_part_t::ethernet, proto_field_t::PWRPin, nullptr, -1, 48}, // Pins run to GPIO 54, the P4's last.
{settings_part_t::ethernet, proto_field_t::MDCPin, nullptr, 0, 48}, {settings_part_t::ethernet, proto_field_t::PWRPin, nullptr, -1, 54},
{settings_part_t::ethernet, proto_field_t::MDIOPin, nullptr, 0, 48}, {settings_part_t::ethernet, proto_field_t::MDCPin, nullptr, 0, 54},
{settings_part_t::ethernet, proto_field_t::MDIOPin, nullptr, 0, 54},
{settings_part_t::ip, proto_field_t::dhcp, nullptr, 0, 0}, {settings_part_t::ip, proto_field_t::dhcp, nullptr, 0, 0},
{settings_part_t::ip, proto_field_t::ip, nullptr, 0, 15}, {settings_part_t::ip, proto_field_t::ip, nullptr, 0, 15},
{settings_part_t::ip, proto_field_t::gateway, nullptr, 0, 15}, {settings_part_t::ip, proto_field_t::gateway, nullptr, 0, 15},
@ -75,12 +77,12 @@ static const settings_field_t FIELDS[] = {
// RTS, RTW, RTV, GP Relay or GP Remote (radio_proto), checked on its own. // RTS, RTW, RTV, GP Relay or GP Remote (radio_proto), checked on its own.
{settings_part_t::radio, proto_field_t::proto, nullptr, 0, 9}, {settings_part_t::radio, proto_field_t::proto, nullptr, 0, 9},
{settings_part_t::radio, proto_field_t::enabled, nullptr, 0, 0}, {settings_part_t::radio, proto_field_t::enabled, nullptr, 0, 0},
{settings_part_t::radio, proto_field_t::TXPin, nullptr, 0, 48}, {settings_part_t::radio, proto_field_t::TXPin, nullptr, 0, 54},
{settings_part_t::radio, proto_field_t::RXPin, nullptr, 0, 48}, {settings_part_t::radio, proto_field_t::RXPin, nullptr, 0, 54},
{settings_part_t::radio, proto_field_t::SCKPin, nullptr, 0, 48}, {settings_part_t::radio, proto_field_t::SCKPin, nullptr, 0, 54},
{settings_part_t::radio, proto_field_t::MOSIPin, nullptr, 0, 48}, {settings_part_t::radio, proto_field_t::MOSIPin, nullptr, 0, 54},
{settings_part_t::radio, proto_field_t::MISOPin, nullptr, 0, 48}, {settings_part_t::radio, proto_field_t::MISOPin, nullptr, 0, 54},
{settings_part_t::radio, proto_field_t::CSNPin, nullptr, 0, 48}, {settings_part_t::radio, proto_field_t::CSNPin, nullptr, 0, 54},
// What the CC1101 can be set to, in MHz and kHz. // What the CC1101 can be set to, in MHz and kHz.
{settings_part_t::radio, proto_field_t::frequency, nullptr, 30000, 92800}, {settings_part_t::radio, proto_field_t::frequency, nullptr, 30000, 92800},
{settings_part_t::radio, proto_field_t::rxBandwidth, nullptr, 5803, 81250}, {settings_part_t::radio, proto_field_t::rxBandwidth, nullptr, 5803, 81250},
@ -137,7 +139,8 @@ static const char *checkValue(const settings_field_t &s, const proto_field_info_
return why; return why;
} }
if(inRange) return nullptr; if(inRange) return nullptr;
snprintf(why, sizeof(why), "%s must be %ld-%ld", f.name, (long)s.lo, (long)s.hi); if(s.lo == s.hi) snprintf(why, sizeof(why), "%s must be %ld", f.name, (long)s.lo);
else snprintf(why, sizeof(why), "%s must be %ld-%ld", f.name, (long)s.lo, (long)s.hi);
return why; return why;
} }
if(f.kind == 'n') { if(f.kind == 'n') {

View file

@ -506,6 +506,21 @@ void transceiver_config_t::load() {
this->SCKPin = 6; this->SCKPin = 6;
this->CSNPin = 0; this->CSNPin = 0;
break; break;
#endif
#ifdef CONFIG_IDF_TARGET_ESP32P4
case esp_chip_model_t::CHIP_ESP32P4:
// The SPI pins of the framework's variant for Waveshare's ESP32-P4-ETH
// (variants/waveshare_p4_poe_eth); TX and RX take 2 and 3, which that
// board leaves free. The C6's SDIO link holds 14-19 and 54, Ethernet
// 28-31, 34, 35 and 49-52, and 34-38 are strapping pins. 46 is on LDO
// channel 4, which the core turns on when a pin of it is used.
this->TXPin = 2;
this->RXPin = 3;
this->MOSIPin = 32;
this->MISOPin = 33;
this->SCKPin = 27;
this->CSNPin = 46;
break;
#endif #endif
default: default:
this->TXPin = 13; this->TXPin = 13;

View file

@ -85,7 +85,7 @@ const char *wifiAuthModeName(uint8_t authMode);
bool otaProven(bool networkUp, unsigned long uptimeMs, unsigned long minUptimeMs); bool otaProven(bool networkUp, unsigned long uptimeMs, unsigned long minUptimeMs);
// A release asset's name: "SomfyController.<kind>.esp32<chipModel>.bin", kind // A release asset's name: "SomfyController.<kind>.esp32<chipModel>.bin", kind
// "ino" for the firmware or "littlefs" for the web UI image, chipModel as in // "ino" for the firmware or "littlefs" for the web UI image, chipModel as in
// ConfigSettings ("" for the ESP32, "s3", "c3", "c5", "c6"). // ConfigSettings ("" for the ESP32, "s3", "c3", "c5", "c6", "p4", "p4es").
void releaseAssetName(char *buf, size_t size, const char *kind, const char *chipModel); void releaseAssetName(char *buf, size_t size, const char *kind, const char *chipModel);
// A coredump backtrace as " 0x%08x" per frame. bt holds maxFrames entries and // A coredump backtrace as " 0x%08x" per frame. bt holds maxFrames entries and
// depth is how many the dump claims are valid. // depth is how many the dump claims are valid.

View file

@ -13,4 +13,20 @@ typedef enum {
ETH_PHY_KSZ8081, ETH_PHY_KSZ8081,
ETH_PHY_MAX ETH_PHY_MAX
} eth_phy_type_t; } eth_phy_type_t;
#if defined(CONFIG_IDF_TARGET_ESP32P4)
// The P4's ETH.h takes the IDF's enum, where 0 is a deprecated default.
typedef enum {
EMAC_CLK_DEFAULT,
EMAC_CLK_EXT_IN,
EMAC_CLK_OUT
} emac_rmii_clock_mode_t;
typedef emac_rmii_clock_mode_t eth_clock_mode_t;
#else
typedef enum {
ETH_CLOCK_GPIO0_IN,
ETH_CLOCK_GPIO0_OUT,
ETH_CLOCK_GPIO16_OUT,
ETH_CLOCK_GPIO17_OUT
} eth_clock_mode_t;
#endif
#endif #endif

View file

@ -52,6 +52,45 @@ static void test_rejects_unknown_ids() {
TEST_ASSERT_FALSE(ethPhyType(255, type)); TEST_ASSERT_FALSE(ethPhyType(255, type));
} }
/*********************************************************************
* Clock modes
********************************************************************/
static void test_clock_ids_match_stored_values() {
TEST_ASSERT_EQUAL_UINT8(0, static_cast<uint8_t>(eth_clock_id_t::GPIO0_IN));
TEST_ASSERT_EQUAL_UINT8(1, static_cast<uint8_t>(eth_clock_id_t::GPIO0_OUT));
TEST_ASSERT_EQUAL_UINT8(2, static_cast<uint8_t>(eth_clock_id_t::GPIO16_OUT));
TEST_ASSERT_EQUAL_UINT8(3, static_cast<uint8_t>(eth_clock_id_t::GPIO17_OUT));
}
static void test_translates_each_clock_mode() {
eth_clock_mode_t mode;
TEST_ASSERT_TRUE(ethClockMode(0, mode));
TEST_ASSERT_EQUAL(ETH_CLOCK_GPIO0_IN, mode);
TEST_ASSERT_TRUE(ethClockMode(1, mode));
TEST_ASSERT_EQUAL(ETH_CLOCK_GPIO0_OUT, mode);
TEST_ASSERT_TRUE(ethClockMode(2, mode));
TEST_ASSERT_EQUAL(ETH_CLOCK_GPIO16_OUT, mode);
TEST_ASSERT_TRUE(ethClockMode(3, mode));
TEST_ASSERT_EQUAL(ETH_CLOCK_GPIO17_OUT, mode);
}
static void test_rejects_unknown_clock_ids() {
eth_clock_mode_t mode;
TEST_ASSERT_FALSE(ethClockMode(4, mode));
TEST_ASSERT_FALSE(ethClockMode(255, mode));
}
static void test_a_save_takes_each_clock_id_that_translates() {
eth_clock_mode_t mode;
TEST_ASSERT_EQUAL_UINT8(3, ETH_CLOCK_ID_MAX);
TEST_ASSERT_TRUE(ethClockMode(ETH_CLOCK_ID_MAX, mode));
TEST_ASSERT_FALSE(ethClockMode(ETH_CLOCK_ID_MAX + 1, mode));
}
static void test_clock_pins() {
TEST_ASSERT_EQUAL_INT8(0, ethClockPin(0));
TEST_ASSERT_EQUAL_INT8(0, ethClockPin(1));
TEST_ASSERT_EQUAL_INT8(16, ethClockPin(2));
TEST_ASSERT_EQUAL_INT8(17, ethClockPin(3));
TEST_ASSERT_EQUAL_INT8(-1, ethClockPin(4));
}
int main(int, char **) { int main(int, char **) {
UNITY_BEGIN(); UNITY_BEGIN();
RUN_TEST(test_ids_match_stored_values); RUN_TEST(test_ids_match_stored_values);
@ -59,5 +98,10 @@ int main(int, char **) {
RUN_TEST(test_lan8720_is_not_the_framework_zero); RUN_TEST(test_lan8720_is_not_the_framework_zero);
RUN_TEST(test_rejects_dm9051); RUN_TEST(test_rejects_dm9051);
RUN_TEST(test_rejects_unknown_ids); RUN_TEST(test_rejects_unknown_ids);
RUN_TEST(test_clock_ids_match_stored_values);
RUN_TEST(test_translates_each_clock_mode);
RUN_TEST(test_rejects_unknown_clock_ids);
RUN_TEST(test_a_save_takes_each_clock_id_that_translates);
RUN_TEST(test_clock_pins);
return UNITY_END(); return UNITY_END();
} }

View file

@ -0,0 +1,58 @@
#include <unity.h>
// As on the ESP32-P4, which has the RMII MAC too but takes the IDF's clock
// enum.
#define CONFIG_ETH_USE_ESP32_EMAC 1
#define CONFIG_IDF_TARGET_ESP32P4 1
#include "EthPhy.h"
void setUp() {}
void tearDown() {}
static void test_builds_with_ethernet() {
TEST_ASSERT_EQUAL(1, HAS_ETHERNET);
}
static void test_ip101_is_the_tlk110_id() {
// Waveshare's ESP32-P4-ETH boards have an IP101, which Arduino drives as
// the TLK110; the web UI's preset stores 1.
eth_phy_type_t type = ETH_PHY_MAX;
TEST_ASSERT_TRUE(ethPhyType(1, type));
TEST_ASSERT_EQUAL(ETH_PHY_TLK110, type);
}
static void test_clock_in_is_ext_in() {
// A cast would give the IDF's deprecated EMAC_CLK_DEFAULT.
eth_clock_mode_t mode = EMAC_CLK_DEFAULT;
TEST_ASSERT_TRUE(ethClockMode(static_cast<uint8_t>(eth_clock_id_t::GPIO0_IN), mode));
TEST_ASSERT_EQUAL(EMAC_CLK_EXT_IN, mode);
}
static void test_rejects_clock_out() {
// ETH.begin() keeps the clock on GPIO 50, which only takes a clock in.
eth_clock_mode_t mode;
TEST_ASSERT_FALSE(ethClockMode(1, mode));
TEST_ASSERT_FALSE(ethClockMode(2, mode));
TEST_ASSERT_FALSE(ethClockMode(3, mode));
TEST_ASSERT_FALSE(ethClockMode(4, mode));
}
static void test_a_save_takes_clock_in_only() {
// ProtoSettings refuses a CLKMode over this, so a save can't store a mode
// that stops Ethernet at the next start.
eth_clock_mode_t mode;
TEST_ASSERT_EQUAL_UINT8(0, ETH_CLOCK_ID_MAX);
TEST_ASSERT_TRUE(ethClockMode(ETH_CLOCK_ID_MAX, mode));
TEST_ASSERT_FALSE(ethClockMode(ETH_CLOCK_ID_MAX + 1, mode));
}
static void test_clock_pin_is_50() {
TEST_ASSERT_EQUAL_INT8(50, ethClockPin(0));
TEST_ASSERT_EQUAL_INT8(-1, ethClockPin(1));
TEST_ASSERT_EQUAL_INT8(-1, ethClockPin(3));
}
int main(int, char **) {
UNITY_BEGIN();
RUN_TEST(test_builds_with_ethernet);
RUN_TEST(test_ip101_is_the_tlk110_id);
RUN_TEST(test_clock_in_is_ext_in);
RUN_TEST(test_rejects_clock_out);
RUN_TEST(test_a_save_takes_clock_in_only);
RUN_TEST(test_clock_pin_is_50);
return UNITY_END();
}

View file

@ -93,11 +93,18 @@ static void test_whole_numbers_are_checked_against_what_the_setting_holds() {
TEST_ASSERT_EQUAL_STRING("connMode must be 1-3", expand(R"({"se":"network","cn":0})")); TEST_ASSERT_EQUAL_STRING("connMode must be 1-3", expand(R"({"se":"network","cn":0})"));
TEST_ASSERT_EQUAL_STRING("port must be 1-65535", expand(R"({"se":"mqtt","po":65536})")); TEST_ASSERT_EQUAL_STRING("port must be 1-65535", expand(R"({"se":"mqtt","po":65536})"));
TEST_ASSERT_EQUAL_STRING("txPower must be -30-12", expand(R"({"se":"radio","tp":13})")); TEST_ASSERT_EQUAL_STRING("txPower must be -30-12", expand(R"({"se":"radio","tp":13})"));
TEST_ASSERT_EQUAL_STRING("TXPin must be 0-48", expand(R"({"se":"radio","tx":-1})")); TEST_ASSERT_EQUAL_STRING("TXPin must be 0-54", expand(R"({"se":"radio","tx":-1})"));
TEST_ASSERT_EQUAL_STRING("securityType must be 0-2", expand(R"({"se":"security","sy":3})")); TEST_ASSERT_EQUAL_STRING("securityType must be 0-2", expand(R"({"se":"security","sy":3})"));
TEST_ASSERT_EQUAL_STRING("port must be 1-65535", expand(R"({"se":"mqtt","po":18446744073709551615})")); TEST_ASSERT_EQUAL_STRING("port must be 1-65535", expand(R"({"se":"mqtt","po":18446744073709551615})"));
// A pin of -1 is none. // A pin of -1 is none.
TEST_ASSERT_GOOD(expand(R"({"se":"network","pw":-1})")); TEST_ASSERT_GOOD(expand(R"({"se":"network","pw":-1})"));
// Pins run to GPIO 54, the P4's last.
TEST_ASSERT_GOOD(expand(R"({"se":"radio","tx":54})"));
TEST_ASSERT_EQUAL_STRING("TXPin must be 0-54", expand(R"({"se":"radio","tx":55})"));
TEST_ASSERT_GOOD(expand(R"({"se":"network","mi":54})"));
// The ESP32's four clock modes; a P4 build takes 0 only (ETH_CLOCK_ID_MAX).
TEST_ASSERT_GOOD(expand(R"({"se":"network","ck":3})"));
TEST_ASSERT_EQUAL_STRING("CLKMode must be 0-3", expand(R"({"se":"network","ck":4})"));
TEST_ASSERT_GOOD(expand(R"({"se":"radio","tp":-30})")); TEST_ASSERT_GOOD(expand(R"({"se":"radio","tp":-30})"));
} }
static void test_the_bit_length_is_56_or_80() { static void test_the_bit_length_is_56_or_80() {

View file

@ -284,6 +284,10 @@ static void test_releaseAssetName_matches_the_release_workflow() {
TEST_ASSERT_EQUAL_STRING("SomfyController.ino.esp32c5.bin", buf); TEST_ASSERT_EQUAL_STRING("SomfyController.ino.esp32c5.bin", buf);
releaseAssetName(buf, sizeof(buf), "littlefs", "c6"); releaseAssetName(buf, sizeof(buf), "littlefs", "c6");
TEST_ASSERT_EQUAL_STRING("SomfyController.littlefs.esp32c6.bin", buf); TEST_ASSERT_EQUAL_STRING("SomfyController.littlefs.esp32c6.bin", buf);
releaseAssetName(buf, sizeof(buf), "ino", "p4");
TEST_ASSERT_EQUAL_STRING("SomfyController.ino.esp32p4.bin", buf);
releaseAssetName(buf, sizeof(buf), "littlefs", "p4es");
TEST_ASSERT_EQUAL_STRING("SomfyController.littlefs.esp32p4es.bin", buf);
} }
/********************************************************************* /*********************************************************************