Add initial version of the Build and release action

This commit is contained in:
Lorow
2025-11-10 19:27:05 +01:00
parent 96016909c5
commit c3ea42c4d0
26 changed files with 339 additions and 32 deletions

119
.github/workflows/build-and-release.yml vendored Normal file
View File

@@ -0,0 +1,119 @@
name: Build nad Release the OpenIris bin files
on:
workflow_dispatch:
push:
tags:
- "*.*.*"
branches:
- "main"
- "beta"
pull_request:
types:
- opened
- reopened
- synchronize
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
deployments: write
jobs:
setup:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- id: set-matrix
run: echo "matrix={\"firmware_config\":[{\"board_name\":\"esp32AIThinker\", \"target\":\"esp32\"}, {\"board_name\":\"esp32M5Stack\", \"target\":\"esp32\"}, {\"board_name\":\"esp32cam\", \"target\":\"esp32\"}, {\"board_name\":\"esp_eye\", \"target\":\"esp32s3\"}, {\"board_name\":\"facefocusvr_eye_L\", \"target\":\"esp32s3\"}, {\"board_name\":\"facefocusvr_eye_R\", \"target\":\"esp32s3\"}, {\"board_name\":\"facefocusvr_face\", \"target\":\"esp32s3\"}, {\"board_name\":\"project_babble\", \"target\":\"esp32s3\"}, {\"board_name\":\"seed_studio_xiao_esp32s3\", \"target\":\"esp32s3\"}, {\"board_name\":\"wrooms3\", \"target\":\"esp32s3\"}, {\"board_name\":\"wrooms3QIO\", \"target\":\"esp32s3\"}, {\"board_name\":\"wrover\", \"target\":\"esp32s3\"}]}" >> $GITHUB_OUTPUT
build:
needs: setup
permissions:
contents: write
strategy:
fail-fast: false
matrix: ${{fromJson(needs.setup.outputs.matrix)}}
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v5
with:
submodules: "recursive"
- name: Setup UV
uses: astral-sh/setup-uv@v6
- name: Set up Python
run: uv python install
- name: Setup SDKConfig
run: uv run ./tools/switchBoardType.py --board ${{ matrix.firmware_config.board_name }} --diff
- name: Show SDKConfig
run: cat ./sdkconfig
- name: Build
uses: espressif/esp-idf-ci-action@v1
with:
esp_idf_version: v5.4.2
target: ${{ matrix.firmware_config.target }}
path: ./
- name: Merge bins
uses: espressif/esp-idf-ci-action@v1
with:
esp_idf_version: v5.4.2
target: ${{ matrix.firmware_config.target }}
path: ./
command: idf.py merge-bin -f raw
- name: Zip the resulting bin
run: zip -r ${{matrix.firmware_config.board_name}}.zip build/merged-binary.bin
- name: Archive Firmware binaries
uses: actions/upload-artifact@v4
with:
name: ${{matrix.firmware_config.board_name}}-firmware
path: ./${{matrix.firmware_config.board_name}}.zip
retention-days: 5
if-no-files-found: error
release:
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout repo
uses: actions/checkout@v5
- name: Prepare directory
run: mkdir -p build
- name: Download firmware builds
uses: actions/download-artifact@v4
with:
path: build/
- name: Make Release
uses: softprops/action-gh-release@v2
if: github.ref_type == 'tag'
with:
files: build/*.zip
prerelease: ${{contains(github.ref_type, 'rc')}}
cleanup:
needs: [setup, release]
strategy:
fail-fast: false
matrix: ${{fromJson(needs.setup.outputs.matrix)}}
name: Cleanup actions
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: "♻️ remove build artifacts"
uses: geekyeggo/delete-artifact@v5
with:
name: ${{matrix.firmware_config.board_name}}-firmware

View File

@@ -1,6 +1,34 @@
idf_component_register(SRCS
"Monitoring/CurrentMonitor.cpp"
"Monitoring/MonitoringManager.cpp"
INCLUDE_DIRS "Monitoring"
REQUIRES driver esp_adc Helpers
set(
requires
Helpers
)
if ("$ENV{IDF_TARGET}" STREQUAL "esp32s3")
list(APPEND requires
driver
esp_adc
)
endif()
set(
source_files
""
)
if ("$ENV{IDF_TARGET}" STREQUAL "esp32s3")
list(APPEND source_files
"Monitoring/CurrentMonitor_esp32s3.cpp"
"Monitoring/MonitoringManager_esp32s3.cpp"
)
else()
list(APPEND source_files
"Monitoring/CurrentMonitor_esp32.cpp"
"Monitoring/MonitoringManager_esp32.cpp"
)
endif()
idf_component_register(SRCS ${source_files}
INCLUDE_DIRS "Monitoring"
REQUIRES ${requires}
)

View File

@@ -6,7 +6,8 @@
#include <vector>
#include "sdkconfig.h"
class CurrentMonitor {
class CurrentMonitor
{
public:
CurrentMonitor();
~CurrentMonitor() = default;
@@ -25,15 +26,15 @@ public:
// Whether monitoring is enabled by Kconfig
static constexpr bool isEnabled()
{
#ifdef CONFIG_MONITORING_LED_CURRENT
#ifdef CONFIG_MONITORING_LED_CURRENT
return true;
#else
#else
return false;
#endif
#endif
}
private:
#if CONFIG_MONITORING_LED_CURRENT
#ifdef CONFIG_MONITORING_LED_CURRENT
void init_adc();
int read_mv_once();
int gpio_to_adc_channel(int gpio);

View File

@@ -0,0 +1,42 @@
#include "CurrentMonitor.hpp"
#include <esp_log.h>
static const char *TAG_CM = "[CurrentMonitor]";
CurrentMonitor::CurrentMonitor()
{
// empty as esp32 doesn't support this
// but without a separate implementation, the linker will complain :c
}
void CurrentMonitor::setup()
{
ESP_LOGI(TAG_CM, "LED current monitoring disabled");
}
float CurrentMonitor::getCurrentMilliAmps() const
{
return 0.0f;
}
float CurrentMonitor::pollAndGetMilliAmps()
{
sampleOnce();
return getCurrentMilliAmps();
}
void CurrentMonitor::sampleOnce()
{
(void)0;
}
#ifdef CONFIG_MONITORING_LED_CURRENT
void CurrentMonitor::init_adc()
{
}
int CurrentMonitor::read_mv_once()
{
return 0;
}
#endif

View File

@@ -2,7 +2,7 @@
#include <esp_log.h>
#include <cmath>
#if CONFIG_MONITORING_LED_CURRENT
#ifdef CONFIG_MONITORING_LED_CURRENT
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"
@@ -12,14 +12,14 @@ static const char *TAG_CM = "[CurrentMonitor]";
CurrentMonitor::CurrentMonitor()
{
#if CONFIG_MONITORING_LED_CURRENT
#ifdef CONFIG_MONITORING_LED_CURRENT
samples_.assign(CONFIG_MONITORING_LED_SAMPLES, 0);
#endif
}
void CurrentMonitor::setup()
{
#if CONFIG_MONITORING_LED_CURRENT
#ifdef CONFIG_MONITORING_LED_CURRENT
init_adc();
#else
ESP_LOGI(TAG_CM, "LED current monitoring disabled");
@@ -28,7 +28,7 @@ void CurrentMonitor::setup()
float CurrentMonitor::getCurrentMilliAmps() const
{
#if CONFIG_MONITORING_LED_CURRENT
#ifdef CONFIG_MONITORING_LED_CURRENT
const int shunt_milliohm = CONFIG_MONITORING_LED_SHUNT_MILLIOHM; // mΩ
if (shunt_milliohm <= 0)
return 0.0f;
@@ -48,7 +48,7 @@ float CurrentMonitor::pollAndGetMilliAmps()
void CurrentMonitor::sampleOnce()
{
#if CONFIG_MONITORING_LED_CURRENT
#ifdef CONFIG_MONITORING_LED_CURRENT
int mv = read_mv_once();
// Divide by analog gain/divider factor to get shunt voltage
if (CONFIG_MONITORING_LED_GAIN > 0)
@@ -76,7 +76,7 @@ void CurrentMonitor::sampleOnce()
#endif
}
#if CONFIG_MONITORING_LED_CURRENT
#ifdef CONFIG_MONITORING_LED_CURRENT
static adc_oneshot_unit_handle_t s_adc_handle = nullptr;
static adc_cali_handle_t s_cali_handle = nullptr;

View File

@@ -4,9 +4,9 @@
#include <atomic>
#include "CurrentMonitor.hpp"
class MonitoringManager {
class MonitoringManager
{
public:
void setup();
void start();
void stop();
@@ -15,7 +15,7 @@ public:
float getCurrentMilliAmps() const { return last_current_ma_.load(); }
private:
static void taskEntry(void* arg);
static void taskEntry(void *arg);
void run();
TaskHandle_t task_{nullptr};

View File

@@ -0,0 +1,25 @@
#include "MonitoringManager.hpp"
#include <esp_log.h>
static const char *TAG_MM = "[MonitoringManager]";
void MonitoringManager::setup()
{
ESP_LOGI(TAG_MM, "Monitoring disabled by Kconfig");
}
void MonitoringManager::start()
{
}
void MonitoringManager::stop()
{
}
void MonitoringManager::taskEntry(void *arg)
{
}
void MonitoringManager::run()
{
}

View File

@@ -2,11 +2,11 @@
#include <esp_log.h>
#include "sdkconfig.h"
static const char* TAG_MM = "[MonitoringManager]";
static const char *TAG_MM = "[MonitoringManager]";
void MonitoringManager::setup()
{
#if CONFIG_MONITORING_LED_CURRENT
#ifdef CONFIG_MONITORING_LED_CURRENT
cm_.setup();
ESP_LOGI(TAG_MM, "Monitoring enabled. Interval=%dms, Samples=%d, Gain=%d, R=%dmΩ",
CONFIG_MONITORING_LED_INTERVAL_MS,
@@ -20,7 +20,7 @@ void MonitoringManager::setup()
void MonitoringManager::start()
{
#if CONFIG_MONITORING_LED_CURRENT
#ifdef CONFIG_MONITORING_LED_CURRENT
if (task_ == nullptr)
{
xTaskCreate(&MonitoringManager::taskEntry, "MonitoringTask", 2048, this, 1, &task_);
@@ -38,14 +38,14 @@ void MonitoringManager::stop()
}
}
void MonitoringManager::taskEntry(void* arg)
void MonitoringManager::taskEntry(void *arg)
{
static_cast<MonitoringManager*>(arg)->run();
static_cast<MonitoringManager *>(arg)->run();
}
void MonitoringManager::run()
{
#if CONFIG_MONITORING_LED_CURRENT
#ifdef CONFIG_MONITORING_LED_CURRENT
while (true)
{
float ma = cm_.pollAndGetMilliAmps();

View File

@@ -16,7 +16,7 @@ void SerialManager::setup()
#endif
}
usb_serial_jtag_write_bytes_chunked(const char *data, size_t len, size_t timeout)
void usb_serial_jtag_write_bytes_chunked(const char *data, size_t len, size_t timeout)
{
#ifndef CONFIG_USE_UART_FOR_COMMUNICATION
while (len > 0)

View File

@@ -7,8 +7,8 @@ set (
Helpers
)
if ("$ENV{IDF_ETARGET}" STREQUAL "esp32s3")
list(APPEND required
if ("$ENV{IDF_TARGET}" STREQUAL "esp32s3")
list(APPEND requires
usb_device_uvc
)
endif()

View File

@@ -1,6 +1,6 @@
#ifdef CONFIG_GENERAL_INCLUDE_UVC_MODE
#include "UVCStream.hpp"
#ifdef CONFIG_GENERAL_INCLUDE_UVC_MODE
#include <cstdio> // for snprintf
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

View File

@@ -1,7 +1,10 @@
#ifdef CONFIG_GENERAL_INCLUDE_UVC_MODE
#pragma once
#ifndef UVCSTREAM_HPP
#define UVCSTREAM_HPP
#include "sdkconfig.h"
#ifdef CONFIG_GENERAL_INCLUDE_UVC_MODE
#include "esp_timer.h"
#include "esp_mac.h"
#include "esp_camera.h"

View File

@@ -22,7 +22,10 @@
#include <SerialManager.hpp>
#include <RestAPI.hpp>
#include <main_globals.hpp>
#ifdef CONFIG_MONITORING_LED_CURRENT
#include <MonitoringManager.hpp>
#endif
#ifdef CONFIG_GENERAL_INCLUDE_UVC_MODE
#include <UVCStream.hpp>
@@ -68,7 +71,11 @@ UVCStreamManager uvcStream;
#endif
auto ledManager = std::make_shared<LEDManager>(BLINK_GPIO, CONFIG_LED_C_PIN_GPIO, ledStateQueue, deviceConfig);
#ifdef CONFIG_MONITORING_LED_CURRENT
std::shared_ptr<MonitoringManager> monitoringManager = std::make_shared<MonitoringManager>();
#endif
auto *serialManager = new SerialManager(commandManager, &timerHandle);
void startWiFiMode();
@@ -265,18 +272,23 @@ extern "C" void app_main(void)
dependencyRegistry->registerService<WiFiManager>(DependencyType::wifi_manager, wifiManager);
#endif
dependencyRegistry->registerService<LEDManager>(DependencyType::led_manager, ledManager);
#ifdef CONFIG_MONITORING_LED_CURRENT
dependencyRegistry->registerService<MonitoringManager>(DependencyType::monitoring_manager, monitoringManager);
#endif
// add endpoint to check firmware version
// setup CI and building for other boards
// esp_log_set_vprintf(&websocket_logger);
Logo::printASCII();
initNVSStorage();
deviceConfig->load();
ledManager->setup();
#ifdef CONFIG_MONITORING_LED_CURRENT
monitoringManager->setup();
monitoringManager->start();
#endif
xTaskCreate(
HandleStateManagerTask,

View File

@@ -494,6 +494,7 @@ def get_settings_summary(device: OpenIrisDevice, *args, **kwargs):
configured = wifi.get("networks_configured", 0)
print(f"📶 WiFi: {status} | IP: {ip} | Networks configured: {configured}")
def restart_device_command(device: OpenIrisDevice, *args, **kwargs):
print("🔄 Restarting device...")
response = device.send_command("restart_device")
@@ -504,6 +505,7 @@ def restart_device_command(device: OpenIrisDevice, *args, **kwargs):
print("✅ Device restart command sent successfully")
print("💡 Please wait a few seconds for the device to reboot")
def scan_networks(wifi_scanner: WiFiScanner, *args, **kwargs):
use_custom_timeout = (
input("Should we use a custom scan timeout? (y/n)\n>> ").strip().lower() == "y"

View File

@@ -1,5 +1,6 @@
import os
import difflib
import shutil
import argparse
from typing import Dict, Optional, List
@@ -13,6 +14,16 @@ BOARDS_DIR_NAME = "boards"
SDKCONFIG_DEFAULTS_FILENAME = "sdkconfig.base_defaults"
# some components are super platform specific.
# to a point where building them with esp32 will fail every single time due to depndencies not supporting it
# with our own components, we can ship some shims to keep things clean
# but with those, unless we roll our own somehow, we're out of luck.
# So, to make things simpler, when selecting for which board to build, we're gonna reconfigure the components
# on the fly.
PLATFORM_SPECIFIC_COMPONENTS = {"esp32s3": ["usb_device_uvc"]}
PLATFORM_SPECIFIC_COMPONENTS_DIRS = {"esp32s3": "esp32s3"}
def get_root_path() -> str:
return os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]
@@ -21,6 +32,11 @@ def get_boards_root() -> str:
return os.path.join(get_root_path(), BOARDS_DIR_NAME)
def get_config_platform(_parsed_config: dict) -> str:
# 1:-1 to strip quotes
return _parsed_config["CONFIG_IDF_TARGET"][1:-1]
def enumerate_board_configs() -> Dict[str, str]:
"""Walk the boards directory and build a mapping of board names to absolute file paths.
@@ -178,6 +194,58 @@ def compute_diff(_parsed_base_config: dict, _parsed_board_config: dict) -> dict:
return _diff
def _move_directories(component: str, destination_path: str):
if os.path.exists(component):
shutil.move(component, destination_path)
def handle_extra_components(old_platform: str, new_platform: str, dry_run: bool):
print(
f"{OKGREEN}Switching components configuration from platform:{ENDC} {OKBLUE}{old_platform}{ENDC} {OKGREEN}to platform:{ENDC} {OKBLUE}{new_platform}{ENDC}"
)
if old_platform == new_platform:
print(f"{OKGREEN}The platform is the same. Nothing to do here.{ENDC}")
return
old_platform_components = PLATFORM_SPECIFIC_COMPONENTS.get(old_platform, [])
new_platform_components = PLATFORM_SPECIFIC_COMPONENTS.get(new_platform, [])
if dry_run:
print(f"{OKGREEN}Would remove: {ENDC}")
for component in old_platform_components:
print(f"{OKBLUE}- {component} {ENDC}")
print(f"{OKGREEN}Would add: {ENDC}")
for component in new_platform_components:
print(f"{OKBLUE}- {component} {ENDC}")
return
components_path = os.path.join(get_root_path(), "components")
if old_base_dir := PLATFORM_SPECIFIC_COMPONENTS_DIRS.get(old_platform):
old_extra_components_path = os.path.join(
os.path.join(get_root_path(), "extra_components"), old_base_dir
)
for component in old_platform_components:
component_path = os.path.join(components_path, component)
print(
f"{OKGREEN}Moving:{ENDC}{OKBLUE} {component}{ENDC} to {OKBLUE}{old_extra_components_path}{ENDC}"
)
_move_directories(component_path, old_extra_components_path)
if new_base_dir := PLATFORM_SPECIFIC_COMPONENTS_DIRS.get(new_platform):
new_extra_components_path = os.path.join(
os.path.join(get_root_path(), "extra_components"), new_base_dir
)
for component in new_platform_components:
component_path = os.path.join(new_extra_components_path, component)
print(
f"{OKGREEN}Moving:{ENDC}{OKBLUE} {component}{ENDC} to {OKBLUE}{components_path}{ENDC}"
)
_move_directories(component_path, components_path)
def main():
parser = build_arg_parser()
args = parser.parse_args()
@@ -247,6 +315,13 @@ def main():
main_config.write(f"{key}\n")
else:
print(f"{WARNING}[DRY-RUN]{ENDC} Skipping writing to files")
handle_extra_components(
get_config_platform(parsed_main_config),
get_config_platform(new_board_config),
args.dry_run,
)
print(
f"{OKGREEN}Done. ESP-IDF is setup to build for:{ENDC} {OKBLUE}{normalized}{ENDC}"
)