mirror of
https://github.com/MrUnknownDE/OpenIris-ESPIDF.git
synced 2026-05-05 21:36:05 +02:00
Merge pull request #1 from EyeTrackVR/feature/add-support-for-other-boards
Feature/add support for other boards
This commit is contained in:
@@ -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
|
||||||
+2
-1
@@ -90,7 +90,7 @@ sdkconfig
|
|||||||
|
|
||||||
# Local History for Visual Studio Code
|
# Local History for Visual Studio Code
|
||||||
.history/
|
.history/
|
||||||
|
tests/.env
|
||||||
# Built Visual Studio Code Extensions
|
# Built Visual Studio Code Extensions
|
||||||
*.vsix
|
*.vsix
|
||||||
|
|
||||||
@@ -98,3 +98,4 @@ sdkconfig
|
|||||||
# Ignore all local history of files
|
# Ignore all local history of files
|
||||||
.history
|
.history
|
||||||
.ionide
|
.ionide
|
||||||
|
*\__pycache__
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.13
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
# The following five lines of boilerplate have to be in your project's
|
|
||||||
# CMakeLists in this exact order for cmake to work correctly
|
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
set(EXTRA_COMPONENT_DIRS ${CMAKE_CURRENT_LIST_DIR}/components)
|
set(EXTRA_COMPONENT_DIRS ${CMAKE_CURRENT_LIST_DIR}/components)
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ Firmware and tools for OpenIris — Wi‑Fi, UVC streaming, and a Python setup C
|
|||||||
- Python tools for setup over USB serial:
|
- Python tools for setup over USB serial:
|
||||||
- `tools/switchBoardType.py` — choose a board profile (builds the right sdkconfig)
|
- `tools/switchBoardType.py` — choose a board profile (builds the right sdkconfig)
|
||||||
- `tools/setup_openiris.py` — interactive CLI for Wi‑Fi, MDNS/Name, Mode, LED PWM, Logs, and a Settings Summary
|
- `tools/setup_openiris.py` — interactive CLI for Wi‑Fi, MDNS/Name, Mode, LED PWM, Logs, and a Settings Summary
|
||||||
- Composite USB (UVC + CDC) when UVC mode is enabled (`GENERAL_INCLUDE_UVC_MODE`) for simultaneous video streaming and command channel
|
- Composite USB (UVC + CDC) when UVC mode is enabled (`GENERAL_INCLUDE_UVC_MODE`) for simultaneous video streaming and command channel
|
||||||
- LED current monitoring (if enabled via `MONITORING_LED_CURRENT`) with filtered mA readings
|
- LED current monitoring (if enabled via `MONITORING_LED_CURRENT`) with filtered mA readings
|
||||||
- Configurable debug LED + external IR LED control with optional error mirroring (`LED_DEBUG_ENABLE`, `LED_EXTERNAL_AS_DEBUG`)
|
- Configurable debug LED + external IR LED control with optional error mirroring (`LED_DEBUG_ENABLE`, `LED_EXTERNAL_AS_DEBUG`)
|
||||||
- Auto‑discovered per‑board configuration overlays under `boards/`
|
- Auto‑discovered per‑board configuration overlays under `boards/`
|
||||||
- Command framework (JSON over serial / CDC / REST) for mode switching, Wi‑Fi config, OTA credentials, LED brightness, info & monitoring
|
- Command framework (JSON over serial / CDC / REST) for mode switching, Wi‑Fi config, OTA credentials, LED brightness, info & monitoring
|
||||||
- Single source advertised name (`CONFIG_GENERAL_ADVERTISED_NAME`) used for both UVC device name and mDNS hostname (unless overridden at runtime)
|
- Single source advertised name (`CONFIG_GENERAL_ADVERTISED_NAME`) used for both UVC device name and mDNS hostname (unless overridden at runtime)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -63,23 +63,32 @@ After this, you’re ready for the Quick start below.
|
|||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
|
### 1) Grab UV.
|
||||||
|
|
||||||
|
We're using UV to manage our tools, grab and install it from [here](https://docs.astral.sh/uv/getting-started/installation/).
|
||||||
|
|
||||||
|
Once installed, you'll be able to just run the commands below and UV will take care of setting up everything.
|
||||||
|
|
||||||
### 1) Pick your board (loads the default configuration)
|
### 1) Pick your board (loads the default configuration)
|
||||||
|
|
||||||
Boards are auto‑discovered from the `boards/` directory. First list them, then pick one:
|
Boards are auto‑discovered from the `boards/` directory. First list them, then pick one:
|
||||||
|
|
||||||
Windows (cmd):
|
Windows (cmd):
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
python .\tools\switchBoardType.py --list
|
uv run .\tools\switchBoardType.py --list
|
||||||
python .\tools\switchBoardType.py --board seed_studio_xiao_esp32s3 --diff
|
uv run .\tools\switchBoardType.py --board seed_studio_xiao_esp32s3 --diff
|
||||||
```
|
```
|
||||||
|
|
||||||
macOS/Linux (bash):
|
macOS/Linux (bash):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 ./tools/switchBoardType.py --list
|
uv run ./tools/switchBoardType.py --list
|
||||||
python3 ./tools/switchBoardType.py --board seed_studio_xiao_esp32s3 --diff
|
uv run ./tools/switchBoardType.py --board seed_studio_xiao_esp32s3 --diff
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- Use `--list` to see all detected board keys.
|
- Use `--list` to see all detected board keys.
|
||||||
- Board key = relative path under `boards/` with `/` replaced by `_` (and duplicate tail segments collapsed, e.g. `project_babble/project_babble` -> `project_babble`).
|
- Board key = relative path under `boards/` with `/` replaced by `_` (and duplicate tail segments collapsed, e.g. `project_babble/project_babble` -> `project_babble`).
|
||||||
- `--diff` shows what will change vs the current `sdkconfig`.
|
- `--diff` shows what will change vs the current `sdkconfig`.
|
||||||
@@ -89,8 +98,8 @@ Notes:
|
|||||||
|
|
||||||
- Set the target (e.g., ESP32‑S3).
|
- Set the target (e.g., ESP32‑S3).
|
||||||
- Build, flash, and open the serial monitor.
|
- Build, flash, and open the serial monitor.
|
||||||
- (Optional) For UVC mode ensure `GENERAL_INCLUDE_UVC_MODE=y`. If you want device to boot directly into UVC: also set `START_IN_UVC_MODE=y`.
|
- (Optional) For UVC mode ensure `GENERAL_INCLUDE_UVC_MODE=y`. If you want device to boot directly into UVC: also set `START_IN_UVC_MODE=y`.
|
||||||
- Disable Wi‑Fi services for pure wired builds: `GENERAL_ENABLE_WIRELESS=n`.
|
- Disable Wi‑Fi services for pure wired builds: `GENERAL_ENABLE_WIRELESS=n`.
|
||||||
|
|
||||||
### 3) Use the Python setup CLI (recommended)
|
### 3) Use the Python setup CLI (recommended)
|
||||||
|
|
||||||
@@ -99,19 +108,18 @@ Configure the device over USB serial.
|
|||||||
Before you run it:
|
Before you run it:
|
||||||
|
|
||||||
- If you still have the serial monitor open, close it (the port must be free).
|
- If you still have the serial monitor open, close it (the port must be free).
|
||||||
- In VS Code, open the sidebar “ESP‑IDF: Explorer” and click “Open ESP‑IDF Terminal”. We’ll run the CLI there so Python and packages are in the right environment.
|
|
||||||
|
|
||||||
Then run:
|
Then run:
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
python .\tools\setup_openiris.py --port COMxx
|
uv run .\tools\setup_openiris.py --port COMxx
|
||||||
```
|
```
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- Windows: `python .\tools\setup_openiris.py --port COM69`, …
|
- Windows: `uv run .\tools\setup_openiris.py --port COM69`, …
|
||||||
- macOS: idk
|
- macOS: `uv run .\tools\setup_openiris.py --port \dev\tty<port>`
|
||||||
- Linux: idk
|
- Linux: `uv run .\tools\setup_openiris.py --port \dev\tty<port>`
|
||||||
|
|
||||||
What the CLI can do:
|
What the CLI can do:
|
||||||
|
|
||||||
@@ -131,7 +139,9 @@ What the CLI can do:
|
|||||||
- The UVC device name is based on the MDNS hostname.
|
- The UVC device name is based on the MDNS hostname.
|
||||||
|
|
||||||
## Advertised Name (UVC + mDNS)
|
## Advertised Name (UVC + mDNS)
|
||||||
|
|
||||||
`CONFIG_GENERAL_ADVERTISED_NAME` (Kconfig) defines the base name announced over:
|
`CONFIG_GENERAL_ADVERTISED_NAME` (Kconfig) defines the base name announced over:
|
||||||
|
|
||||||
- USB UVC descriptor (appears in OS camera list)
|
- USB UVC descriptor (appears in OS camera list)
|
||||||
- mDNS hostname / service name
|
- mDNS hostname / service name
|
||||||
|
|
||||||
@@ -144,9 +154,9 @@ Runtime override: If the setup CLI (or a JSON command) provides a new device nam
|
|||||||
- Fast Wi‑Fi setup: in the CLI, go to “Wi‑Fi settings” → “Automatic setup”, then check “status”.
|
- Fast Wi‑Fi setup: in the CLI, go to “Wi‑Fi settings” → “Automatic setup”, then check “status”.
|
||||||
- Change name/MDNS: set the device name in the CLI, then replug USB — UVC will show the new name.
|
- Change name/MDNS: set the device name in the CLI, then replug USB — UVC will show the new name.
|
||||||
- Adjust brightness/LED: set LED PWM in the CLI.
|
- Adjust brightness/LED: set LED PWM in the CLI.
|
||||||
- Switch to UVC mode over commands (CDC/serial):
|
- Switch to UVC mode over commands (CDC/serial):
|
||||||
`{"commands":[{"command":"switch_mode","data":{"mode":"uvc"}}]}` then reboot.
|
`{"commands":[{"command":"switch_mode","data":{"mode":"uvc"}}]}` then reboot.
|
||||||
- Read filtered LED current (if enabled):
|
- Read filtered LED current (if enabled):
|
||||||
`{"commands":[{"command":"get_led_current"}]}`
|
`{"commands":[{"command":"get_led_current"}]}`
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -156,19 +166,62 @@ Runtime override: If the setup CLI (or a JSON command) provides a new device nam
|
|||||||
- `main/` — entry point
|
- `main/` — entry point
|
||||||
- `components/` — modules (Camera, WiFi, UVC, CommandManager, …)
|
- `components/` — modules (Camera, WiFi, UVC, CommandManager, …)
|
||||||
- `tools/` — Python helper tools (board switch, setup CLI, scanner)
|
- `tools/` — Python helper tools (board switch, setup CLI, scanner)
|
||||||
|
- `tests/` - Hardware in the loop tests, with support for different boards and automatic skips if a board can't perform a given test
|
||||||
If you want to dig deeper: commands are mapped via the `CommandManager` under `components/CommandManager/...`.
|
If you want to dig deeper: commands are mapped via the `CommandManager` under `components/CommandManager/...`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Running Hardware In The Loop Tests
|
||||||
|
|
||||||
|
In order to run the tests, you'll need to setup a couple of things:
|
||||||
|
|
||||||
|
- copy the `.env.example` file in `/tests/` and rename it to `.env`. Then, open it and fill out the network details - SSID and Password.
|
||||||
|
- plug in your board to your pc and wait for it to boot.
|
||||||
|
- open the terminal (`ctrl/cmd + j` in VSC) and head over to `/tests/` directory.
|
||||||
|
|
||||||
|
Once there, you can invoke the tests with `UV` like so:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
uv run pytest --board=<your board name> --connection=COM<the number your board connected to>
|
||||||
|
```
|
||||||
|
|
||||||
|
This will auto select every test we have, connect to your board automatically and have pytest skip tests that don't fit your board.
|
||||||
|
For example, tests involving switching modes to UVC and testing commands over there are disabled for esp32 based boards as only esp32s3 can do it. Same goes for WiFi for FaceFocus Boards.
|
||||||
|
|
||||||
|
If you see any fails, you can try rerunning them one by one with:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
uv run pytest --board=<your board name> --connection=COM<the number your board connected to> -k name_of_the_test
|
||||||
|
```
|
||||||
|
|
||||||
|
Or rerun every single failed test like so:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
uv run pytest --board=<your board name> --connection=COM<the number your board connected to> --lf
|
||||||
|
```
|
||||||
|
|
||||||
|
Sometimes tests will fail due to timeouts, this is normal.
|
||||||
|
|
||||||
|
You should see the tests starting to go off, with any luck - all of them passing, your board should also start reacting to the changes - reboots, blinking lights etc are **expected** as we're performing hardware in the loop tests.
|
||||||
|
|
||||||
|
### Warning:
|
||||||
|
|
||||||
|
After the testing session ends **WE WILL RESET THE BOARD**, any changes you've made yourself to it will be lost. This is done to ensure that the test we perform are unaffected by any changes done by said tests.
|
||||||
|
If we skipped that, tests involving adding fake networks would break some that rely on the board connecting to real ones in a timely manner, for example.
|
||||||
|
|
||||||
|
There is currently no way to skip that behavior.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### USB Composite (UVC + CDC)
|
### USB Composite (UVC + CDC)
|
||||||
|
|
||||||
When UVC support is compiled in, the device enumerates as a composite USB device:
|
When UVC support is compiled in, the device enumerates as a composite USB device:
|
||||||
|
|
||||||
- UVC interface: video streaming (JPEG frames)
|
- UVC interface: video streaming (JPEG frames)
|
||||||
- CDC (virtual COM): command channel accepting newline‑terminated JSON objects
|
- CDC (virtual COM): command channel accepting newline‑terminated JSON objects
|
||||||
|
|
||||||
Example newline‑terminated JSON commands over CDC (one per line):
|
Example newline‑terminated JSON commands over CDC (one per line):
|
||||||
|
|
||||||
```
|
```
|
||||||
{"commands":[{"command":"ping"}]}
|
{"commands":[{"command":"ping"}]}
|
||||||
{"commands":[{"command":"get_who_am_i"}]}
|
{"commands":[{"command":"get_who_am_i"}]}
|
||||||
@@ -176,52 +229,61 @@ Example newline‑terminated JSON commands over CDC (one per line):
|
|||||||
```
|
```
|
||||||
|
|
||||||
Chained commands in a single request (processed in order):
|
Chained commands in a single request (processed in order):
|
||||||
|
|
||||||
```
|
```
|
||||||
{"commands":[
|
{"commands":[
|
||||||
{"command":"set_mdns","data":{"hostname":"tracker"}},
|
{"command":"set_mdns","data":{"hostname":"tracker"}},
|
||||||
{"command":"set_wifi","data":{"name":"main","ssid":"your_network","password":"password","channel":0,"power":0}}
|
{"command":"set_wifi","data":{"name":"main","ssid":"your_network","password":"password","channel":0,"power":0}}
|
||||||
]}
|
]}
|
||||||
```
|
```
|
||||||
|
|
||||||
Responses are JSON blobs flushed immediately.
|
Responses are JSON blobs flushed immediately.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Monitoring (LED Current)
|
### Monitoring (LED Current)
|
||||||
|
|
||||||
Enabled with `MONITORING_LED_CURRENT=y` plus shunt/gain settings. The task samples every `CONFIG_MONITORING_LED_INTERVAL_MS` ms and maintains a filtered moving average over `CONFIG_MONITORING_LED_SAMPLES` samples. Use `get_led_current` command to query.
|
Enabled with `MONITORING_LED_CURRENT=y` plus shunt/gain settings. The task samples every `CONFIG_MONITORING_LED_INTERVAL_MS` ms and maintains a filtered moving average over `CONFIG_MONITORING_LED_SAMPLES` samples. Use `get_led_current` command to query.
|
||||||
|
|
||||||
### Debug & External LED Configuration
|
### Debug & External LED Configuration
|
||||||
|
|
||||||
| Kconfig | Effect |
|
| Kconfig | Effect |
|
||||||
|---------|--------|
|
| --------------------------- | --------------------------------------------------------------------------------------------------- |
|
||||||
| LED_DEBUG_ENABLE | Enables/disables discrete status LED GPIO init & drive |
|
| LED_DEBUG_ENABLE | Enables/disables discrete status LED GPIO init & drive |
|
||||||
| LED_EXTERNAL_CONTROL | Enables PWM control for IR / external LED |
|
| LED_EXTERNAL_CONTROL | Enables PWM control for IR / external LED |
|
||||||
| LED_EXTERNAL_PWM_DUTY_CYCLE | Default duty % applied at boot (0–100) |
|
| LED_EXTERNAL_PWM_DUTY_CYCLE | Default duty % applied at boot (0–100) |
|
||||||
| LED_EXTERNAL_AS_DEBUG | Mirrors only error patterns onto external LED (0%/50%) when debug LED absent or also for redundancy |
|
| LED_EXTERNAL_AS_DEBUG | Mirrors only error patterns onto external LED (0%/50%) when debug LED absent or also for redundancy |
|
||||||
|
|
||||||
### Board Profiles
|
### Board Profiles
|
||||||
|
|
||||||
Each file under `boards/` overlays `sdkconfig.base_defaults`. The merge order: base → board file → (optional) dynamic Wi‑Fi overrides via `switchBoardType.py` flags. Duplicate trailing segment directories collapse to unique keys.
|
Each file under `boards/` overlays `sdkconfig.base_defaults`. The merge order: base → board file → (optional) dynamic Wi‑Fi overrides via `switchBoardType.py` flags. Duplicate trailing segment directories collapse to unique keys.
|
||||||
|
|
||||||
- UVC doesn’t appear on the host?
|
- UVC doesn’t appear on the host?
|
||||||
- Switch mode to UVC via CLI tool, replug USB and wait 20s.
|
- Switch mode to UVC via CLI tool, replug USB and wait 20s.
|
||||||
|
|
||||||
### Adding a new board configuration
|
### Adding a new board configuration
|
||||||
|
|
||||||
1. Create a new config file under `boards/` (you can nest folders): for example `boards/my_family/my_variant`.
|
1. Create a new config file under `boards/` (you can nest folders): for example `boards/my_family/my_variant`.
|
||||||
2. Populate it with only the `CONFIG_...` lines that differ from the shared defaults. Shared baseline lives in `boards/sdkconfig.base_defaults` and is always merged first.
|
2. Populate it with only the `CONFIG_...` lines that differ from the shared defaults. Shared baseline lives in `boards/sdkconfig.base_defaults` and is always merged first.
|
||||||
3. The board key the script accepts will be the relative path with `/` turned into `_` (example: `boards/my_family/my_variant` -> `my_family_my_variant`).
|
3. The board key the script accepts will be the relative path with `/` turned into `_` (example: `boards/my_family/my_variant` -> `my_family_my_variant`).
|
||||||
4. Run `python tools/switchBoardType.py --list` to verify it’s detected, then switch using `-b my_family_my_variant`.
|
4. Run `uv run tools/switchBoardType.py --list` to verify it’s detected, then switch using `-b my_family_my_variant`.
|
||||||
5. If you accidentally create two files that collapse to the same key the last one found wins—rename to keep keys unique.
|
5. If you accidentally create two files that collapse to the same key the last one found wins—rename to keep keys unique.
|
||||||
|
|
||||||
Tips:
|
Tips:
|
||||||
|
|
||||||
- Use `--diff` after adding a board to sanity‑check only the intended keys change.
|
- Use `--diff` after adding a board to sanity‑check only the intended keys change.
|
||||||
- For Wi‑Fi overrides on first flash: add none—pass `--ssid` / `--password` when switching if needed.
|
- For Wi‑Fi overrides on first flash: add none—pass `--ssid` / `--password` when switching if needed.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### LED Status / Error Patterns
|
### LED Status / Error Patterns
|
||||||
|
|
||||||
The firmware uses a small set of LED patterns to indicate status and blocking errors. When `LED_DEBUG_ENABLE` is disabled and `LED_EXTERNAL_AS_DEBUG` is enabled the external IR LED mirrors ONLY error patterns (0%/50% duty). Non‑error patterns are not mirrored.
|
The firmware uses a small set of LED patterns to indicate status and blocking errors. When `LED_DEBUG_ENABLE` is disabled and `LED_EXTERNAL_AS_DEBUG` is enabled the external IR LED mirrors ONLY error patterns (0%/50% duty). Non‑error patterns are not mirrored.
|
||||||
|
|
||||||
| State | Visual | Category | Timing Pattern (ms) | Meaning |
|
| State | Visual | Category | Timing Pattern (ms) | Meaning |
|
||||||
|-------|--------|----------|---------------------|---------|
|
| ------------------------ | --------------------------------------------------------- | ------------ | ---------------------- | --------------------------------------------------------- |
|
||||||
| LedStateNone |  | idle | (off) | No activity / heartbeat window waiting |
|
| LedStateNone |  | idle | (off) | No activity / heartbeat window waiting |
|
||||||
| LedStateStreaming |  | active | steady on | Streaming running (UVC or Wi‑Fi) |
|
| LedStateStreaming |  | active | steady on | Streaming running (UVC or Wi‑Fi) |
|
||||||
| LedStateStoppedStreaming |  | inactive | steady off | Streaming intentionally stopped |
|
| LedStateStoppedStreaming |  | inactive | steady off | Streaming intentionally stopped |
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
CONFIG_IDF_TARGET="esp32"
|
||||||
|
# CONFIG_IDF_TARGET_ESP32S3 is not set
|
||||||
|
# CONFIG_WIRED_MODE is not set
|
||||||
|
CONFIG_LED_DEBUG_GPIO=33
|
||||||
|
# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240 is not set
|
||||||
|
# CONFIG_ESP32S3_SPIRAM_SUPPORT is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set
|
||||||
|
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set
|
||||||
|
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
|
||||||
|
# Camera sensor pinout configuration
|
||||||
|
CONFIG_CAMERA_MODULE_NAME="ESP32AITHINKER"
|
||||||
|
CONFIG_PWDN_GPIO_NUM=32
|
||||||
|
CONFIG_RESET_GPIO_NUM=-1
|
||||||
|
CONFIG_XCLK_GPIO_NUM=0
|
||||||
|
CONFIG_SIOD_GPIO_NUM=26
|
||||||
|
CONFIG_SIOC_GPIO_NUM=27
|
||||||
|
CONFIG_Y9_GPIO_NUM=35
|
||||||
|
CONFIG_Y8_GPIO_NUM=34
|
||||||
|
CONFIG_Y7_GPIO_NUM=39
|
||||||
|
CONFIG_Y6_GPIO_NUM=36
|
||||||
|
CONFIG_Y5_GPIO_NUM=21
|
||||||
|
CONFIG_Y4_GPIO_NUM=19
|
||||||
|
CONFIG_Y3_GPIO_NUM=18
|
||||||
|
CONFIG_Y2_GPIO_NUM=5
|
||||||
|
CONFIG_VSYNC_GPIO_NUM=25
|
||||||
|
CONFIG_HREF_GPIO_NUM=23
|
||||||
|
CONFIG_PCLK_GPIO_NUM=22
|
||||||
|
# end of Camera sensor pinout configuration
|
||||||
|
# CONFIG_FLASHMODE_QIO is not set
|
||||||
|
# CONFIG_FLASHMODE_QOUT is not set
|
||||||
|
# CONFIG_FLASHMODE_DIO is not set
|
||||||
|
CONFIG_SPIRAM_MODE_QUAD=y
|
||||||
|
# CONFIG_SPIRAM_MODE_OCT is not set
|
||||||
|
CONFIG_SPIRAM_TYPE_AUTO=y
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set
|
||||||
|
CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
|
||||||
|
CONFIG_SPIRAM_CLK_IO=30
|
||||||
|
CONFIG_SPIRAM_CS_IO=26
|
||||||
|
# CONFIG_SPIRAM_XIP_FROM_PSRAM is not set
|
||||||
|
# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set
|
||||||
|
# CONFIG_SPIRAM_RODATA is not set
|
||||||
|
# CONFIG_SPIRAM_SPEED_120M is not set
|
||||||
|
CONFIG_SPIRAM_SPEED_80M=y
|
||||||
|
# CONFIG_SUPPORTS_EXTERNAL_LED_CONTROL is not set
|
||||||
|
|
||||||
|
#
|
||||||
|
# OpenIris: Serial Communication Settings
|
||||||
|
#
|
||||||
|
CONFIG_UART_PORT_NUMBER=0
|
||||||
|
CONFIG_UART_RX_PIN=3
|
||||||
|
CONFIG_UART_TX_PIN=1
|
||||||
|
|
||||||
|
# end of OpenIris: Serial Communication Settings
|
||||||
|
|
||||||
|
# CONFIG_MONITORING_LED_CURRENT is not set
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
CONFIG_IDF_TARGET="esp32"
|
||||||
|
# CONFIG_IDF_TARGET_ESP32S3 is not set
|
||||||
|
# CONFIG_WIRED_MODE is not set
|
||||||
|
CONFIG_LED_DEBUG_GPIO=33
|
||||||
|
# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240 is not set
|
||||||
|
# CONFIG_ESP32S3_SPIRAM_SUPPORT is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set
|
||||||
|
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set
|
||||||
|
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
|
||||||
|
# Camera sensor pinout configuration
|
||||||
|
CONFIG_CAMERA_MODULE_NAME="ESP32CAM"
|
||||||
|
CONFIG_PWDN_GPIO_NUM=32
|
||||||
|
CONFIG_RESET_GPIO_NUM=33
|
||||||
|
CONFIG_XCLK_GPIO_NUM=4
|
||||||
|
CONFIG_SIOD_GPIO_NUM=18
|
||||||
|
CONFIG_SIOC_GPIO_NUM=23
|
||||||
|
CONFIG_Y9_GPIO_NUM=36
|
||||||
|
CONFIG_Y8_GPIO_NUM=19
|
||||||
|
CONFIG_Y7_GPIO_NUM=21
|
||||||
|
CONFIG_Y6_GPIO_NUM=39
|
||||||
|
CONFIG_Y5_GPIO_NUM=35
|
||||||
|
CONFIG_Y4_GPIO_NUM=14
|
||||||
|
CONFIG_Y3_GPIO_NUM=13
|
||||||
|
CONFIG_Y2_GPIO_NUM=34
|
||||||
|
CONFIG_VSYNC_GPIO_NUM=5
|
||||||
|
CONFIG_HREF_GPIO_NUM=27
|
||||||
|
CONFIG_PCLK_GPIO_NUM=25
|
||||||
|
# end of Camera sensor pinout configuration
|
||||||
|
# CONFIG_FLASHMODE_QIO is not set
|
||||||
|
# CONFIG_FLASHMODE_QOUT is not set
|
||||||
|
# CONFIG_FLASHMODE_DIO is not set
|
||||||
|
CONFIG_SPIRAM_MODE_QUAD=y
|
||||||
|
# CONFIG_SPIRAM_MODE_OCT is not set
|
||||||
|
CONFIG_SPIRAM_TYPE_AUTO=y
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set
|
||||||
|
CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
|
||||||
|
CONFIG_SPIRAM_CLK_IO=30
|
||||||
|
CONFIG_SPIRAM_CS_IO=26
|
||||||
|
# CONFIG_SPIRAM_XIP_FROM_PSRAM is not set
|
||||||
|
# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set
|
||||||
|
# CONFIG_SPIRAM_RODATA is not set
|
||||||
|
# CONFIG_SPIRAM_SPEED_120M is not set
|
||||||
|
CONFIG_SPIRAM_SPEED_80M=y
|
||||||
|
# CONFIG_SUPPORTS_EXTERNAL_LED_CONTROL is not set
|
||||||
|
|
||||||
|
#
|
||||||
|
# OpenIris: Serial Communication Settings
|
||||||
|
#
|
||||||
|
CONFIG_UART_PORT_NUMBER=0
|
||||||
|
CONFIG_UART_RX_PIN=3
|
||||||
|
CONFIG_UART_TX_PIN=1
|
||||||
|
|
||||||
|
# end of OpenIris: Serial Communication Settings
|
||||||
|
# CONFIG_MONITORING_LED_CURRENT is not set
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# TODO test out on real hardware
|
||||||
|
CONFIG_IDF_TARGET="esp32"
|
||||||
|
# CONFIG_IDF_TARGET_ESP32S3 is not set
|
||||||
|
# CONFIG_WIRED_MODE is not set
|
||||||
|
CONFIG_LED_DEBUG_GPIO=33
|
||||||
|
# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240 is not set
|
||||||
|
# CONFIG_ESP32S3_SPIRAM_SUPPORT is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set
|
||||||
|
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set
|
||||||
|
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
|
||||||
|
# Camera sensor pinout configuration
|
||||||
|
CONFIG_CAMERA_MODULE_NAME="ESP32M5STACK"
|
||||||
|
CONFIG_PWDN_GPIO_NUM=-1
|
||||||
|
CONFIG_RESET_GPIO_NUM=15
|
||||||
|
CONFIG_XCLK_GPIO_NUM=27
|
||||||
|
CONFIG_SIOD_GPIO_NUM=25
|
||||||
|
CONFIG_SIOC_GPIO_NUM=23
|
||||||
|
CONFIG_Y9_GPIO_NUM=19
|
||||||
|
CONFIG_Y8_GPIO_NUM=36
|
||||||
|
CONFIG_Y7_GPIO_NUM=18
|
||||||
|
CONFIG_Y6_GPIO_NUM=39
|
||||||
|
CONFIG_Y5_GPIO_NUM=5
|
||||||
|
CONFIG_Y4_GPIO_NUM=34
|
||||||
|
CONFIG_Y3_GPIO_NUM=35
|
||||||
|
CONFIG_Y2_GPIO_NUM=17
|
||||||
|
CONFIG_VSYNC_GPIO_NUM=22
|
||||||
|
CONFIG_HREF_GPIO_NUM=26
|
||||||
|
CONFIG_PCLK_GPIO_NUM=21
|
||||||
|
# end of Camera sensor pinout configuration
|
||||||
|
# CONFIG_FLASHMODE_QIO is not set
|
||||||
|
# CONFIG_FLASHMODE_QOUT is not set
|
||||||
|
# CONFIG_FLASHMODE_DIO is not set
|
||||||
|
CONFIG_SPIRAM_MODE_QUAD=y
|
||||||
|
# CONFIG_SPIRAM_MODE_OCT is not set
|
||||||
|
CONFIG_SPIRAM_TYPE_AUTO=y
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set
|
||||||
|
CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
|
||||||
|
CONFIG_SPIRAM_CLK_IO=30
|
||||||
|
CONFIG_SPIRAM_CS_IO=26
|
||||||
|
# CONFIG_SPIRAM_XIP_FROM_PSRAM is not set
|
||||||
|
# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set
|
||||||
|
# CONFIG_SPIRAM_RODATA is not set
|
||||||
|
# CONFIG_SPIRAM_SPEED_120M is not set
|
||||||
|
CONFIG_SPIRAM_SPEED_80M=y
|
||||||
|
# CONFIG_SUPPORTS_EXTERNAL_LED_CONTROL is not set
|
||||||
|
|
||||||
|
#
|
||||||
|
# OpenIris: Serial Communication Settings
|
||||||
|
#
|
||||||
|
CONFIG_UART_PORT_NUMBER=0
|
||||||
|
CONFIG_UART_RX_PIN=3
|
||||||
|
CONFIG_UART_TX_PIN=1
|
||||||
|
|
||||||
|
# end of OpenIris: Serial Communication Settings
|
||||||
|
|
||||||
|
# CONFIG_MONITORING_LED_CURRENT is not set
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
CONFIG_IDF_TARGET="esp32s3"
|
||||||
|
CONFIG_IDF_TARGET_ESP32S3=y
|
||||||
|
CONFIG_WIRED_MODE=y
|
||||||
|
CONFIG_BLINK_GPIO=38 # todo check this
|
||||||
|
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
|
||||||
|
CONFIG_ESP32S3_SPIRAM_SUPPORT=y
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set
|
||||||
|
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set
|
||||||
|
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
|
||||||
|
# Camera sensor pinout configuration
|
||||||
|
CONFIG_CAMERA_MODULE_NAME="ESP322ESP_EYE"
|
||||||
|
CONFIG_PWDN_GPIO_NUM=-1
|
||||||
|
CONFIG_RESET_GPIO_NUM=-1
|
||||||
|
CONFIG_XCLK_GPIO_NUM=4
|
||||||
|
CONFIG_SIOD_GPIO_NUM=18
|
||||||
|
CONFIG_SIOC_GPIO_NUM=23
|
||||||
|
CONFIG_Y9_GPIO_NUM=36
|
||||||
|
CONFIG_Y8_GPIO_NUM=37
|
||||||
|
CONFIG_Y7_GPIO_NUM=38
|
||||||
|
CONFIG_Y6_GPIO_NUM=39
|
||||||
|
CONFIG_Y5_GPIO_NUM=14
|
||||||
|
CONFIG_Y4_GPIO_NUM=19
|
||||||
|
CONFIG_Y3_GPIO_NUM=13
|
||||||
|
CONFIG_Y2_GPIO_NUM=34
|
||||||
|
CONFIG_VSYNC_GPIO_NUM=5
|
||||||
|
CONFIG_HREF_GPIO_NUM=27
|
||||||
|
CONFIG_PCLK_GPIO_NUM=25
|
||||||
|
# end of Camera sensor pinout configuration
|
||||||
|
# CONFIG_FLASHMODE_QIO is not set
|
||||||
|
# CONFIG_FLASHMODE_QOUT is not set
|
||||||
|
# CONFIG_FLASHMODE_DIO is not set
|
||||||
|
CONFIG_SPIRAM_MODE_QUAD=y
|
||||||
|
# CONFIG_SPIRAM_MODE_OCT is not set
|
||||||
|
CONFIG_SPIRAM_TYPE_AUTO=y
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set
|
||||||
|
CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
|
||||||
|
CONFIG_SPIRAM_CLK_IO=30
|
||||||
|
CONFIG_SPIRAM_CS_IO=26
|
||||||
|
# CONFIG_SPIRAM_XIP_FROM_PSRAM is not set
|
||||||
|
# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set
|
||||||
|
# CONFIG_SPIRAM_RODATA is not set
|
||||||
|
# CONFIG_SPIRAM_SPEED_120M is not set
|
||||||
|
CONFIG_SPIRAM_SPEED_80M=y
|
||||||
|
# CONFIG_SUPPORTS_EXTERNAL_LED_CONTROL is not set
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
CONFIG_IDF_TARGET="esp32s3"
|
||||||
|
CONFIG_IDF_TARGET_ESP32S3=y
|
||||||
CONFIG_LED_DEBUG_GPIO=38
|
CONFIG_LED_DEBUG_GPIO=38
|
||||||
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
|
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
|
||||||
CONFIG_ESP32S3_SPIRAM_SUPPORT=y
|
CONFIG_ESP32S3_SPIRAM_SUPPORT=y
|
||||||
|
|||||||
@@ -2228,7 +2228,6 @@ CONFIG_TUSB_PID=0x8000
|
|||||||
CONFIG_TUSB_MANUFACTURER="ETVR"
|
CONFIG_TUSB_MANUFACTURER="ETVR"
|
||||||
CONFIG_TUSB_PRODUCT="OpenIris Camera"
|
CONFIG_TUSB_PRODUCT="OpenIris Camera"
|
||||||
CONFIG_TUSB_SERIAL_NUM="12345678"
|
CONFIG_TUSB_SERIAL_NUM="12345678"
|
||||||
# CONFIG_UVC_SUPPORT_TWO_CAM is not set
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# USB Cam1 Config
|
# USB Cam1 Config
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
CONFIG_IDF_TARGET="esp32s3"
|
||||||
|
CONFIG_IDF_TARGET_ESP32S3=y
|
||||||
CONFIG_LED_DEBUG_GPIO=21
|
CONFIG_LED_DEBUG_GPIO=21
|
||||||
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
|
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
|
||||||
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
|
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
CONFIG_IDF_TARGET="esp32s3"
|
||||||
|
CONFIG_IDF_TARGET_ESP32S3=y
|
||||||
|
CONFIG_WIRED_MODE=y
|
||||||
|
CONFIG_BLINK_GPIO=38
|
||||||
|
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
|
||||||
|
CONFIG_ESP32S3_SPIRAM_SUPPORT=y
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set
|
||||||
|
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set
|
||||||
|
CONFIG_ESPTOOLPY_FLASHSIZE="8MB"
|
||||||
|
# Camera sensor pinout configuration
|
||||||
|
CONFIG_CAMERA_MODULE_NAME="WROOMS3"
|
||||||
|
CONFIG_PWDN_GPIO_NUM=-1
|
||||||
|
CONFIG_RESET_GPIO_NUM=-1
|
||||||
|
CONFIG_XCLK_GPIO_NUM=15
|
||||||
|
CONFIG_SIOD_GPIO_NUM=4
|
||||||
|
CONFIG_SIOC_GPIO_NUM=5
|
||||||
|
CONFIG_Y9_GPIO_NUM=16
|
||||||
|
CONFIG_Y8_GPIO_NUM=17
|
||||||
|
CONFIG_Y7_GPIO_NUM=18
|
||||||
|
CONFIG_Y6_GPIO_NUM=12
|
||||||
|
CONFIG_Y5_GPIO_NUM=10
|
||||||
|
CONFIG_Y4_GPIO_NUM=8
|
||||||
|
CONFIG_Y3_GPIO_NUM=9
|
||||||
|
CONFIG_Y2_GPIO_NUM=11
|
||||||
|
CONFIG_VSYNC_GPIO_NUM=6
|
||||||
|
CONFIG_HREF_GPIO_NUM=7
|
||||||
|
CONFIG_PCLK_GPIO_NUM=13
|
||||||
|
# end of Camera sensor pinout configuration
|
||||||
|
# CONFIG_FLASHMODE_QIO is not set
|
||||||
|
# CONFIG_FLASHMODE_QOUT is not set
|
||||||
|
# CONFIG_FLASHMODE_DIO is not set
|
||||||
|
CONFIG_SPIRAM_MODE_QUAD=y
|
||||||
|
# CONFIG_SPIRAM_MODE_OCT is not set
|
||||||
|
CONFIG_SPIRAM_TYPE_AUTO=y
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set
|
||||||
|
CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
|
||||||
|
CONFIG_SPIRAM_CLK_IO=30
|
||||||
|
CONFIG_SPIRAM_CS_IO=26
|
||||||
|
# CONFIG_SPIRAM_XIP_FROM_PSRAM is not set
|
||||||
|
# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set
|
||||||
|
# CONFIG_SPIRAM_RODATA is not set
|
||||||
|
# CONFIG_SPIRAM_SPEED_120M is not set
|
||||||
|
CONFIG_SPIRAM_SPEED_80M=y
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
CONFIG_IDF_TARGET="esp32s3"
|
||||||
|
CONFIG_IDF_TARGET_ESP32S3=y
|
||||||
|
CONFIG_WIRED_MODE=y
|
||||||
|
CONFIG_BLINK_GPIO=38
|
||||||
|
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
|
||||||
|
CONFIG_ESP32S3_SPIRAM_SUPPORT=y
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set
|
||||||
|
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set
|
||||||
|
CONFIG_ESPTOOLPY_FLASHSIZE="8MB"
|
||||||
|
# Camera sensor pinout configuration
|
||||||
|
CONFIG_CAMERA_MODULE_NAME="WROOMS3"
|
||||||
|
CONFIG_PWDN_GPIO_NUM=-1
|
||||||
|
CONFIG_RESET_GPIO_NUM=-1
|
||||||
|
CONFIG_XCLK_GPIO_NUM=15
|
||||||
|
CONFIG_SIOD_GPIO_NUM=4
|
||||||
|
CONFIG_SIOC_GPIO_NUM=5
|
||||||
|
CONFIG_Y9_GPIO_NUM=16
|
||||||
|
CONFIG_Y8_GPIO_NUM=17
|
||||||
|
CONFIG_Y7_GPIO_NUM=18
|
||||||
|
CONFIG_Y6_GPIO_NUM=12
|
||||||
|
CONFIG_Y5_GPIO_NUM=10
|
||||||
|
CONFIG_Y4_GPIO_NUM=8
|
||||||
|
CONFIG_Y3_GPIO_NUM=9
|
||||||
|
CONFIG_Y2_GPIO_NUM=11
|
||||||
|
CONFIG_VSYNC_GPIO_NUM=6
|
||||||
|
CONFIG_HREF_GPIO_NUM=7
|
||||||
|
CONFIG_PCLK_GPIO_NUM=13
|
||||||
|
# end of Camera sensor pinout configuration
|
||||||
|
CONFIG_FLASHMODE_QIO = y
|
||||||
|
# CONFIG_FLASHMODE_QOUT is not set
|
||||||
|
# CONFIG_FLASHMODE_DIO is not set
|
||||||
|
CONFIG_SPIRAM_MODE_QUAD=y
|
||||||
|
# CONFIG_SPIRAM_MODE_OCT is not set
|
||||||
|
CONFIG_SPIRAM_TYPE_AUTO=y
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set
|
||||||
|
CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
|
||||||
|
CONFIG_SPIRAM_CLK_IO=30
|
||||||
|
CONFIG_SPIRAM_CS_IO=26
|
||||||
|
# CONFIG_SPIRAM_XIP_FROM_PSRAM is not set
|
||||||
|
# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set
|
||||||
|
# CONFIG_SPIRAM_RODATA is not set
|
||||||
|
# CONFIG_SPIRAM_SPEED_120M is not set
|
||||||
|
CONFIG_SPIRAM_SPEED_80M=y
|
||||||
|
# CONFIG_SUPPORTS_EXTERNAL_LED_CONTROL is not set
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
CONFIG_IDF_TARGET="esp32s3"
|
||||||
|
CONFIG_IDF_TARGET_ESP32S3=y
|
||||||
|
CONFIG_WIRED_MODE=y
|
||||||
|
CONFIG_BLINK_GPIO=38
|
||||||
|
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
|
||||||
|
CONFIG_ESP32S3_SPIRAM_SUPPORT=y
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set
|
||||||
|
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set
|
||||||
|
# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set
|
||||||
|
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
|
||||||
|
# Camera sensor pinout configuration
|
||||||
|
CONFIG_CAMERA_MODULE_NAME="WROVER"
|
||||||
|
CONFIG_PWDN_GPIO_NUM=-1
|
||||||
|
CONFIG_RESET_GPIO_NUM=-1
|
||||||
|
CONFIG_XCLK_GPIO_NUM=21
|
||||||
|
CONFIG_SIOD_GPIO_NUM=26
|
||||||
|
CONFIG_SIOC_GPIO_NUM=27
|
||||||
|
CONFIG_Y9_GPIO_NUM=35
|
||||||
|
CONFIG_Y8_GPIO_NUM=34
|
||||||
|
CONFIG_Y7_GPIO_NUM=39
|
||||||
|
CONFIG_Y6_GPIO_NUM=36
|
||||||
|
CONFIG_Y5_GPIO_NUM=19
|
||||||
|
CONFIG_Y4_GPIO_NUM=18
|
||||||
|
CONFIG_Y3_GPIO_NUM=5
|
||||||
|
CONFIG_Y2_GPIO_NUM=4
|
||||||
|
CONFIG_VSYNC_GPIO_NUM=25
|
||||||
|
CONFIG_HREF_GPIO_NUM=23
|
||||||
|
CONFIG_PCLK_GPIO_NUM=22
|
||||||
|
# end of Camera sensor pinout configuration
|
||||||
|
CONFIG_FLASHMODE_QIO=y
|
||||||
|
# CONFIG_FLASHMODE_QOUT is not set
|
||||||
|
# CONFIG_FLASHMODE_DIO is not set
|
||||||
|
CONFIG_SPIRAM_MODE_QUAD=y
|
||||||
|
# CONFIG_SPIRAM_MODE_OCT is not set
|
||||||
|
CONFIG_SPIRAM_TYPE_AUTO=y
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set
|
||||||
|
# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set
|
||||||
|
CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
|
||||||
|
CONFIG_SPIRAM_CLK_IO=30
|
||||||
|
CONFIG_SPIRAM_CS_IO=26
|
||||||
|
# CONFIG_SPIRAM_XIP_FROM_PSRAM is not set
|
||||||
|
# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set
|
||||||
|
# CONFIG_SPIRAM_RODATA is not set
|
||||||
|
# CONFIG_SPIRAM_SPEED_120M is not set
|
||||||
|
CONFIG_SPIRAM_SPEED_80M=y
|
||||||
|
# CONFIG_SUPPORTS_EXTERNAL_LED_CONTROL is not set
|
||||||
@@ -75,7 +75,6 @@ void CameraManager::setupCameraPinout()
|
|||||||
.ledc_timer = LEDC_TIMER_0,
|
.ledc_timer = LEDC_TIMER_0,
|
||||||
.ledc_channel = LEDC_CHANNEL_0,
|
.ledc_channel = LEDC_CHANNEL_0,
|
||||||
|
|
||||||
// this causes problems
|
|
||||||
.pixel_format = PIXFORMAT_JPEG, // YUV422,GRAYSCALE,RGB565,JPEG
|
.pixel_format = PIXFORMAT_JPEG, // YUV422,GRAYSCALE,RGB565,JPEG
|
||||||
.frame_size = FRAMESIZE_240X240, // QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates.
|
.frame_size = FRAMESIZE_240X240, // QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates.
|
||||||
|
|
||||||
@@ -86,22 +85,6 @@ void CameraManager::setupCameraPinout()
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void CameraManager::setupBasicResolution()
|
|
||||||
{
|
|
||||||
|
|
||||||
if (!esp_psram_is_initialized())
|
|
||||||
{
|
|
||||||
ESP_LOGE(CAMERA_MANAGER_TAG, "PSRAM not initialized!");
|
|
||||||
ESP_LOGD(CAMERA_MANAGER_TAG, "Setting fb_location to CAMERA_FB_IN_DRAM with lower picture quality");
|
|
||||||
config.fb_location = CAMERA_FB_IN_DRAM;
|
|
||||||
config.jpeg_quality = 7;
|
|
||||||
config.fb_count = 2;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(CAMERA_MANAGER_TAG, "PSRAM size: %u", esp_psram_get_size());
|
|
||||||
}
|
|
||||||
|
|
||||||
void CameraManager::setupCameraSensor()
|
void CameraManager::setupCameraSensor()
|
||||||
{
|
{
|
||||||
ESP_LOGI(CAMERA_MANAGER_TAG, "Setting up camera sensor");
|
ESP_LOGI(CAMERA_MANAGER_TAG, "Setting up camera sensor");
|
||||||
@@ -138,7 +121,7 @@ void CameraManager::setupCameraSensor()
|
|||||||
// automatic gain control gain, controls by how much the resulting image
|
// automatic gain control gain, controls by how much the resulting image
|
||||||
// should be amplified
|
// should be amplified
|
||||||
camera_sensor->set_agc_gain(camera_sensor, 2); // 0 to 30
|
camera_sensor->set_agc_gain(camera_sensor, 2); // 0 to 30
|
||||||
camera_sensor->set_gainceiling(camera_sensor, (gainceiling_t)6); // 0 to 6
|
camera_sensor->set_gainceiling(camera_sensor, static_cast<gainceiling_t>(6)); // 0 to 6
|
||||||
|
|
||||||
// black and white pixel correction, averages the white and black spots
|
// black and white pixel correction, averages the white and black spots
|
||||||
camera_sensor->set_bpc(camera_sensor, 1); // 0 = disable , 1 = enable
|
camera_sensor->set_bpc(camera_sensor, 1); // 0 = disable , 1 = enable
|
||||||
@@ -170,9 +153,6 @@ bool CameraManager::setupCamera()
|
|||||||
{
|
{
|
||||||
ESP_LOGI(CAMERA_MANAGER_TAG, "Setting up camera pinout");
|
ESP_LOGI(CAMERA_MANAGER_TAG, "Setting up camera pinout");
|
||||||
this->setupCameraPinout();
|
this->setupCameraPinout();
|
||||||
ESP_LOGI(CAMERA_MANAGER_TAG, "Setting up camera with resolution");
|
|
||||||
// this->setupBasicResolution();
|
|
||||||
|
|
||||||
ESP_LOGI(CAMERA_MANAGER_TAG, "Initializing camera...");
|
ESP_LOGI(CAMERA_MANAGER_TAG, "Initializing camera...");
|
||||||
|
|
||||||
if (auto const hasCameraBeenInitialized = esp_camera_init(&config); hasCameraBeenInitialized == ESP_OK)
|
if (auto const hasCameraBeenInitialized = esp_camera_init(&config); hasCameraBeenInitialized == ESP_OK)
|
||||||
@@ -212,7 +192,6 @@ bool CameraManager::setupCamera()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
this->setupCameraSensor();
|
this->setupCameraSensor();
|
||||||
// this->loadConfigData(); // move this to update method once implemented
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ private:
|
|||||||
void loadConfigData();
|
void loadConfigData();
|
||||||
void setupCameraPinout();
|
void setupCameraPinout();
|
||||||
void setupCameraSensor();
|
void setupCameraSensor();
|
||||||
void setupBasicResolution();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CAMERAMANAGER_HPP
|
#endif // CAMERAMANAGER_HPP
|
||||||
@@ -161,5 +161,5 @@ CommandManagerResponse CommandManager::executeFromType(const CommandType type, c
|
|||||||
return CommandManagerResponse({{"command", type}, {"error", "Unknown command"}});
|
return CommandManagerResponse({{"command", type}, {"error", "Unknown command"}});
|
||||||
}
|
}
|
||||||
|
|
||||||
return CommandManagerResponse({"result", command()});
|
return CommandManagerResponse(nlohmann::json{{"result", command()}});
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,34 @@
|
|||||||
idf_component_register(SRCS
|
set(
|
||||||
"Monitoring/CurrentMonitor.cpp"
|
requires
|
||||||
"Monitoring/MonitoringManager.cpp"
|
Helpers
|
||||||
INCLUDE_DIRS "Monitoring"
|
)
|
||||||
REQUIRES driver esp_adc 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}
|
||||||
)
|
)
|
||||||
@@ -6,7 +6,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
class CurrentMonitor {
|
class CurrentMonitor
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
CurrentMonitor();
|
CurrentMonitor();
|
||||||
~CurrentMonitor() = default;
|
~CurrentMonitor() = default;
|
||||||
@@ -25,15 +26,15 @@ public:
|
|||||||
// Whether monitoring is enabled by Kconfig
|
// Whether monitoring is enabled by Kconfig
|
||||||
static constexpr bool isEnabled()
|
static constexpr bool isEnabled()
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||||
return true;
|
return true;
|
||||||
#else
|
#else
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#if CONFIG_MONITORING_LED_CURRENT
|
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||||
void init_adc();
|
void init_adc();
|
||||||
int read_mv_once();
|
int read_mv_once();
|
||||||
int gpio_to_adc_channel(int gpio);
|
int gpio_to_adc_channel(int gpio);
|
||||||
|
|||||||
@@ -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
|
||||||
+6
-6
@@ -2,7 +2,7 @@
|
|||||||
#include <esp_log.h>
|
#include <esp_log.h>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
#if CONFIG_MONITORING_LED_CURRENT
|
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||||
#include "esp_adc/adc_oneshot.h"
|
#include "esp_adc/adc_oneshot.h"
|
||||||
#include "esp_adc/adc_cali.h"
|
#include "esp_adc/adc_cali.h"
|
||||||
#include "esp_adc/adc_cali_scheme.h"
|
#include "esp_adc/adc_cali_scheme.h"
|
||||||
@@ -12,14 +12,14 @@ static const char *TAG_CM = "[CurrentMonitor]";
|
|||||||
|
|
||||||
CurrentMonitor::CurrentMonitor()
|
CurrentMonitor::CurrentMonitor()
|
||||||
{
|
{
|
||||||
#if CONFIG_MONITORING_LED_CURRENT
|
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||||
samples_.assign(CONFIG_MONITORING_LED_SAMPLES, 0);
|
samples_.assign(CONFIG_MONITORING_LED_SAMPLES, 0);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void CurrentMonitor::setup()
|
void CurrentMonitor::setup()
|
||||||
{
|
{
|
||||||
#if CONFIG_MONITORING_LED_CURRENT
|
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||||
init_adc();
|
init_adc();
|
||||||
#else
|
#else
|
||||||
ESP_LOGI(TAG_CM, "LED current monitoring disabled");
|
ESP_LOGI(TAG_CM, "LED current monitoring disabled");
|
||||||
@@ -28,7 +28,7 @@ void CurrentMonitor::setup()
|
|||||||
|
|
||||||
float CurrentMonitor::getCurrentMilliAmps() const
|
float CurrentMonitor::getCurrentMilliAmps() const
|
||||||
{
|
{
|
||||||
#if CONFIG_MONITORING_LED_CURRENT
|
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||||
const int shunt_milliohm = CONFIG_MONITORING_LED_SHUNT_MILLIOHM; // mΩ
|
const int shunt_milliohm = CONFIG_MONITORING_LED_SHUNT_MILLIOHM; // mΩ
|
||||||
if (shunt_milliohm <= 0)
|
if (shunt_milliohm <= 0)
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
@@ -48,7 +48,7 @@ float CurrentMonitor::pollAndGetMilliAmps()
|
|||||||
|
|
||||||
void CurrentMonitor::sampleOnce()
|
void CurrentMonitor::sampleOnce()
|
||||||
{
|
{
|
||||||
#if CONFIG_MONITORING_LED_CURRENT
|
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||||
int mv = read_mv_once();
|
int mv = read_mv_once();
|
||||||
// Divide by analog gain/divider factor to get shunt voltage
|
// Divide by analog gain/divider factor to get shunt voltage
|
||||||
if (CONFIG_MONITORING_LED_GAIN > 0)
|
if (CONFIG_MONITORING_LED_GAIN > 0)
|
||||||
@@ -76,7 +76,7 @@ void CurrentMonitor::sampleOnce()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if CONFIG_MONITORING_LED_CURRENT
|
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||||
|
|
||||||
static adc_oneshot_unit_handle_t s_adc_handle = nullptr;
|
static adc_oneshot_unit_handle_t s_adc_handle = nullptr;
|
||||||
static adc_cali_handle_t s_cali_handle = nullptr;
|
static adc_cali_handle_t s_cali_handle = nullptr;
|
||||||
@@ -4,9 +4,9 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include "CurrentMonitor.hpp"
|
#include "CurrentMonitor.hpp"
|
||||||
|
|
||||||
class MonitoringManager {
|
class MonitoringManager
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
void setup();
|
void setup();
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
void stop();
|
||||||
@@ -15,7 +15,7 @@ public:
|
|||||||
float getCurrentMilliAmps() const { return last_current_ma_.load(); }
|
float getCurrentMilliAmps() const { return last_current_ma_.load(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void taskEntry(void* arg);
|
static void taskEntry(void *arg);
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
TaskHandle_t task_{nullptr};
|
TaskHandle_t task_{nullptr};
|
||||||
|
|||||||
@@ -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()
|
||||||
|
{
|
||||||
|
}
|
||||||
+6
-6
@@ -2,11 +2,11 @@
|
|||||||
#include <esp_log.h>
|
#include <esp_log.h>
|
||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
static const char* TAG_MM = "[MonitoringManager]";
|
static const char *TAG_MM = "[MonitoringManager]";
|
||||||
|
|
||||||
void MonitoringManager::setup()
|
void MonitoringManager::setup()
|
||||||
{
|
{
|
||||||
#if CONFIG_MONITORING_LED_CURRENT
|
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||||
cm_.setup();
|
cm_.setup();
|
||||||
ESP_LOGI(TAG_MM, "Monitoring enabled. Interval=%dms, Samples=%d, Gain=%d, R=%dmΩ",
|
ESP_LOGI(TAG_MM, "Monitoring enabled. Interval=%dms, Samples=%d, Gain=%d, R=%dmΩ",
|
||||||
CONFIG_MONITORING_LED_INTERVAL_MS,
|
CONFIG_MONITORING_LED_INTERVAL_MS,
|
||||||
@@ -20,7 +20,7 @@ void MonitoringManager::setup()
|
|||||||
|
|
||||||
void MonitoringManager::start()
|
void MonitoringManager::start()
|
||||||
{
|
{
|
||||||
#if CONFIG_MONITORING_LED_CURRENT
|
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||||
if (task_ == nullptr)
|
if (task_ == nullptr)
|
||||||
{
|
{
|
||||||
xTaskCreate(&MonitoringManager::taskEntry, "MonitoringTask", 2048, this, 1, &task_);
|
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()
|
void MonitoringManager::run()
|
||||||
{
|
{
|
||||||
#if CONFIG_MONITORING_LED_CURRENT
|
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
float ma = cm_.pollAndGetMilliAmps();
|
float ma = cm_.pollAndGetMilliAmps();
|
||||||
@@ -104,13 +104,7 @@ struct MDNSConfig_t : BaseConfigModel
|
|||||||
void load()
|
void load()
|
||||||
{
|
{
|
||||||
// Default hostname comes from GENERAL_ADVERTISED_NAME (unified advertised name)
|
// Default hostname comes from GENERAL_ADVERTISED_NAME (unified advertised name)
|
||||||
std::string default_hostname =
|
std::string default_hostname = CONFIG_GENERAL_ADVERTISED_NAME;
|
||||||
#ifdef CONFIG_GENERAL_ADVERTISED_NAME
|
|
||||||
CONFIG_GENERAL_ADVERTISED_NAME;
|
|
||||||
#else
|
|
||||||
"openiristracker";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (default_hostname.empty())
|
if (default_hostname.empty())
|
||||||
{
|
{
|
||||||
default_hostname = "openiristracker";
|
default_hostname = "openiristracker";
|
||||||
@@ -146,7 +140,7 @@ struct CameraConfig_t : BaseConfigModel
|
|||||||
{
|
{
|
||||||
this->vflip = this->pref->getInt("vflip", 0);
|
this->vflip = this->pref->getInt("vflip", 0);
|
||||||
this->href = this->pref->getInt("href", 0);
|
this->href = this->pref->getInt("href", 0);
|
||||||
this->framesize = this->pref->getInt("framesize", 4);
|
this->framesize = this->pref->getInt("framesize", 5);
|
||||||
this->quality = this->pref->getInt("quality", 7);
|
this->quality = this->pref->getInt("quality", 7);
|
||||||
this->brightness = this->pref->getInt("brightness", 2);
|
this->brightness = this->pref->getInt("brightness", 2);
|
||||||
};
|
};
|
||||||
@@ -331,14 +325,11 @@ public:
|
|||||||
{
|
{
|
||||||
for (auto i = 0; i < this->networks.size() - 1; i++)
|
for (auto i = 0; i < this->networks.size() - 1; i++)
|
||||||
{
|
{
|
||||||
printf("we're at %d while networks size is %d ", i, this->networks.size() - 2);
|
|
||||||
WifiConfigRepresentation += Helpers::format_string("%s, ", this->networks[i].toRepresentation().c_str());
|
WifiConfigRepresentation += Helpers::format_string("%s, ", this->networks[i].toRepresentation().c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WifiConfigRepresentation += Helpers::format_string("%s", this->networks[networks.size() - 1].toRepresentation().c_str());
|
WifiConfigRepresentation += Helpers::format_string("%s", this->networks[networks.size() - 1].toRepresentation().c_str());
|
||||||
printf(WifiConfigRepresentation.c_str());
|
|
||||||
printf("\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Helpers::format_string(
|
return Helpers::format_string(
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#define PATCH_METHOD "PATCH"
|
||||||
#define POST_METHOD "POST"
|
#define POST_METHOD "POST"
|
||||||
|
#define GET_METHOD "GET"
|
||||||
|
#define DELETE_METHOD "DELETE"
|
||||||
|
|
||||||
bool getIsSuccess(const nlohmann::json &response)
|
bool getIsSuccess(const nlohmann::json &response)
|
||||||
{
|
{
|
||||||
@@ -19,27 +22,55 @@ bool getIsSuccess(const nlohmann::json &response)
|
|||||||
|
|
||||||
RestAPI::RestAPI(std::string url, std::shared_ptr<CommandManager> commandManager) : command_manager(commandManager)
|
RestAPI::RestAPI(std::string url, std::shared_ptr<CommandManager> commandManager) : command_manager(commandManager)
|
||||||
{
|
{
|
||||||
|
// until we stumble on a simpler way to handle the commands over the rest api
|
||||||
|
// the formula will be like this:
|
||||||
|
// each command gets its own endpoint
|
||||||
|
// each endpoint must include the action it performs in its path
|
||||||
|
// for example
|
||||||
|
// /get/ for getters
|
||||||
|
// /set/ for posts
|
||||||
|
// /delete/ for deletes
|
||||||
|
// /update/ for updates
|
||||||
|
// additional actions on the resource should be appended after the resource name
|
||||||
|
// like for example /api/set/config/save/
|
||||||
|
//
|
||||||
|
// one endpoint must not contain more than one action
|
||||||
|
|
||||||
this->url = std::move(url);
|
this->url = std::move(url);
|
||||||
// updates
|
// updates via PATCH
|
||||||
routes.emplace("/api/update/wifi/", &RestAPI::handle_update_wifi);
|
routes.emplace("/api/update/wifi/", RequestBaseData(PATCH_METHOD, CommandType::UPDATE_WIFI, 200, 400));
|
||||||
routes.emplace("/api/update/device/", &RestAPI::handle_update_device);
|
routes.emplace("/api/update/device/mode/", RequestBaseData(PATCH_METHOD, CommandType::SWITCH_MODE, 200, 400));
|
||||||
routes.emplace("/api/update/camera/", &RestAPI::handle_update_camera);
|
routes.emplace("/api/update/camera/", RequestBaseData(PATCH_METHOD, CommandType::UPDATE_CAMERA, 200, 400));
|
||||||
|
routes.emplace("/api/update/ota/credentials", RequestBaseData(PATCH_METHOD, CommandType::UPDATE_OTA_CREDENTIALS, 200, 400));
|
||||||
|
routes.emplace("/api/update/ap/", RequestBaseData(PATCH_METHOD, CommandType::UPDATE_AP_WIFI, 200, 400));
|
||||||
|
routes.emplace("/api/update/led_duty_cycle/", RequestBaseData(PATCH_METHOD, CommandType::SET_LED_DUTY_CYCLE, 200, 400));
|
||||||
|
|
||||||
// post will reset it
|
// POST will set the data
|
||||||
// resets
|
routes.emplace("/api/set/pause/", RequestBaseData(POST_METHOD, CommandType::PAUSE, 200, 400));
|
||||||
routes.emplace("/api/reset/config/", &RestAPI::handle_reset_config);
|
routes.emplace("/api/set/wifi/", RequestBaseData(POST_METHOD, CommandType::SET_WIFI, 200, 400));
|
||||||
// gets
|
routes.emplace("/api/set/mdns/", RequestBaseData(POST_METHOD, CommandType::SET_MDNS, 200, 400));
|
||||||
routes.emplace("/api/get/config/", &RestAPI::handle_get_config);
|
routes.emplace("/api/set/config/save/", RequestBaseData(POST_METHOD, CommandType::SAVE_CONFIG, 200, 400));
|
||||||
|
routes.emplace("/api/set/wifi/connect/", RequestBaseData(POST_METHOD, CommandType::CONNECT_WIFI, 200, 400));
|
||||||
|
|
||||||
// reboots
|
// resets via POST as well
|
||||||
routes.emplace("/api/reboot/device/", &RestAPI::handle_reboot);
|
routes.emplace("/api/reset/config/", RequestBaseData(POST_METHOD, CommandType::RESET_CONFIG, 200, 400));
|
||||||
routes.emplace("/api/reboot/camera/", &RestAPI::handle_camera_reboot);
|
|
||||||
|
|
||||||
// heartbeat
|
// gets via GET
|
||||||
routes.emplace("/api/ping/", &RestAPI::pong);
|
routes.emplace("/api/get/config/", RequestBaseData(GET_METHOD, CommandType::GET_CONFIG, 200, 400));
|
||||||
|
routes.emplace("/api/get/mdns/", RequestBaseData(GET_METHOD, CommandType::GET_MDNS_NAME, 200, 400));
|
||||||
|
routes.emplace("/api/get/led_duty_cycle/", RequestBaseData(GET_METHOD, CommandType::GET_LED_DUTY_CYCLE, 200, 400));
|
||||||
|
routes.emplace("/api/get/serial_number/", RequestBaseData(GET_METHOD, CommandType::GET_SERIAL, 200, 400));
|
||||||
|
routes.emplace("/api/get/led_current/", RequestBaseData(GET_METHOD, CommandType::GET_LED_CURRENT, 200, 400));
|
||||||
|
routes.emplace("/api/get/who_am_i/", RequestBaseData(GET_METHOD, CommandType::GET_WHO_AM_I, 200, 400));
|
||||||
|
|
||||||
// special
|
// deletes via DELETE
|
||||||
routes.emplace("/api/save/", &RestAPI::handle_save);
|
routes.emplace("/api/delete/wifi", RequestBaseData(DELETE_METHOD, CommandType::DELETE_NETWORK, 200, 400));
|
||||||
|
|
||||||
|
// reboots via POST
|
||||||
|
routes.emplace("/api/reboot/device/", RequestBaseData(GET_METHOD, CommandType::RESTART_DEVICE, 200, 500));
|
||||||
|
|
||||||
|
// heartbeat via GET
|
||||||
|
routes.emplace("/api/ping/", RequestBaseData(GET_METHOD, CommandType::PING, 200, 400));
|
||||||
}
|
}
|
||||||
|
|
||||||
void RestAPI::begin()
|
void RestAPI::begin()
|
||||||
@@ -58,19 +89,24 @@ void RestAPI::handle_request(struct mg_connection *connection, int event, void *
|
|||||||
auto const *message = static_cast<struct mg_http_message *>(event_data);
|
auto const *message = static_cast<struct mg_http_message *>(event_data);
|
||||||
auto const uri = std::string(message->uri.buf, message->uri.len);
|
auto const uri = std::string(message->uri.buf, message->uri.len);
|
||||||
|
|
||||||
if (auto const handler = this->routes[uri])
|
if (this->routes.find(uri) == this->routes.end())
|
||||||
{
|
{
|
||||||
|
mg_http_reply(connection, 404, "", "Wrong URL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const base_request_params = this->routes.at(uri);
|
||||||
|
|
||||||
auto *context = new RequestContext{
|
auto *context = new RequestContext{
|
||||||
.connection = connection,
|
.connection = connection,
|
||||||
.method = std::string(message->method.buf, message->method.len),
|
.method = std::string(message->method.buf, message->method.len),
|
||||||
.body = std::string(message->body.buf, message->body.len),
|
.body = std::string(message->body.buf, message->body.len),
|
||||||
};
|
};
|
||||||
(*this.*handler)(context);
|
this->handle_endpoint_command(context,
|
||||||
}
|
base_request_params.allowed_method,
|
||||||
else
|
base_request_params.command_type,
|
||||||
{
|
base_request_params.success_code,
|
||||||
mg_http_reply(connection, 404, "", "Wrong URL");
|
base_request_params.error_code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,97 +131,16 @@ void HandleRestAPIPollTask(void *pvParameter)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// COMMANDS
|
void RestAPI::handle_endpoint_command(RequestContext *context, std::string allowed_method, CommandType command_type, int success_code, int error_code)
|
||||||
// updates
|
|
||||||
void RestAPI::handle_update_wifi(RequestContext *context)
|
|
||||||
{
|
{
|
||||||
if (context->method != POST_METHOD)
|
|
||||||
|
if (context->method != allowed_method)
|
||||||
{
|
{
|
||||||
mg_http_reply(context->connection, 401, JSON_RESPONSE, "{%m:%m}", MG_ESC("error"), "Method not allowed");
|
mg_http_reply(context->connection, 401, JSON_RESPONSE, "{%m:%m}", MG_ESC("error"), "Method not allowed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nlohmann::json result = command_manager->executeFromType(CommandType::UPDATE_WIFI, context->body);
|
const nlohmann::json result = command_manager->executeFromType(command_type, context->body);
|
||||||
const auto code = getIsSuccess(result) ? 200 : 400;
|
const auto code = getIsSuccess(result) ? success_code : error_code;
|
||||||
mg_http_reply(context->connection, code, JSON_RESPONSE, result.dump().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void RestAPI::handle_update_device(RequestContext *context)
|
|
||||||
{
|
|
||||||
if (context->method != POST_METHOD)
|
|
||||||
{
|
|
||||||
mg_http_reply(context->connection, 401, JSON_RESPONSE, "{%m:%m}", MG_ESC("error"), "Method not allowed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nlohmann::json result = command_manager->executeFromType(CommandType::UPDATE_OTA_CREDENTIALS, context->body);
|
|
||||||
const auto code = getIsSuccess(result) ? 200 : 500;
|
|
||||||
mg_http_reply(context->connection, code, JSON_RESPONSE, result.dump().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void RestAPI::handle_update_camera(RequestContext *context)
|
|
||||||
{
|
|
||||||
if (context->method != POST_METHOD)
|
|
||||||
{
|
|
||||||
mg_http_reply(context->connection, 401, JSON_RESPONSE, "{%m:%m}", MG_ESC("error"), "Method not allowed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nlohmann::json result = command_manager->executeFromType(CommandType::UPDATE_CAMERA, context->body);
|
|
||||||
const auto code = getIsSuccess(result) ? 200 : 500;
|
|
||||||
mg_http_reply(context->connection, code, JSON_RESPONSE, result.dump().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// gets
|
|
||||||
|
|
||||||
void RestAPI::handle_get_config(RequestContext *context)
|
|
||||||
{
|
|
||||||
auto const result = this->command_manager->executeFromType(CommandType::GET_CONFIG, "");
|
|
||||||
const nlohmann::json jsonResult = result;
|
|
||||||
mg_http_reply(context->connection, 200, JSON_RESPONSE, "{%m:%m}", MG_ESC("result"), jsonResult.dump().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// resets
|
|
||||||
|
|
||||||
void RestAPI::handle_reset_config(RequestContext *context)
|
|
||||||
{
|
|
||||||
if (context->method != POST_METHOD)
|
|
||||||
{
|
|
||||||
mg_http_reply(context->connection, 401, JSON_RESPONSE, "{%m:%m}", MG_ESC("error"), "Method not allowed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nlohmann::json result = this->command_manager->executeFromType(CommandType::RESET_CONFIG, "{\"section\": \"all\"}");
|
|
||||||
const auto code = getIsSuccess(result) ? 200 : 500;
|
|
||||||
mg_http_reply(context->connection, code, JSON_RESPONSE, "{%m:%m}", MG_ESC("result"), result.dump().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// reboots
|
|
||||||
void RestAPI::handle_reboot(RequestContext *context)
|
|
||||||
{
|
|
||||||
const auto result = this->command_manager->executeFromType(CommandType::RESTART_DEVICE, "");
|
|
||||||
mg_http_reply(context->connection, 200, JSON_RESPONSE, "{%m:%m}", MG_ESC("result"), "Ok");
|
|
||||||
}
|
|
||||||
|
|
||||||
void RestAPI::handle_camera_reboot(RequestContext *context)
|
|
||||||
{
|
|
||||||
mg_http_reply(context->connection, 200, JSON_RESPONSE, "{%m:%m}", MG_ESC("result"), "Ok");
|
|
||||||
}
|
|
||||||
|
|
||||||
// heartbeat
|
|
||||||
|
|
||||||
void RestAPI::pong(RequestContext *context)
|
|
||||||
{
|
|
||||||
const nlohmann::json result = this->command_manager->executeFromType(CommandType::PING, "");
|
|
||||||
const auto code = getIsSuccess(result) ? 200 : 500;
|
|
||||||
mg_http_reply(context->connection, code, JSON_RESPONSE, result.dump().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// special
|
|
||||||
|
|
||||||
void RestAPI::handle_save(RequestContext *context)
|
|
||||||
{
|
|
||||||
const nlohmann::json result = this->command_manager->executeFromType(CommandType::SAVE_CONFIG, "");
|
|
||||||
const auto code = getIsSuccess(result) ? 200 : 500;
|
|
||||||
mg_http_reply(context->connection, code, JSON_RESPONSE, result.dump().c_str());
|
mg_http_reply(context->connection, code, JSON_RESPONSE, result.dump().c_str());
|
||||||
}
|
}
|
||||||
@@ -18,10 +18,18 @@ struct RequestContext
|
|||||||
std::string body;
|
std::string body;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct RequestBaseData
|
||||||
|
{
|
||||||
|
std::string allowed_method;
|
||||||
|
CommandType command_type;
|
||||||
|
int success_code;
|
||||||
|
int error_code;
|
||||||
|
RequestBaseData(std::string allowed_method, CommandType command_type, int success_code, int error_code) : allowed_method(allowed_method), command_type(command_type), success_code(success_code), error_code(error_code) {};
|
||||||
|
};
|
||||||
|
|
||||||
class RestAPI
|
class RestAPI
|
||||||
{
|
{
|
||||||
using route_handler = void (RestAPI::*)(RequestContext *);
|
typedef std::unordered_map<std::string, RequestBaseData> route_map;
|
||||||
typedef std::unordered_map<std::string, route_handler> route_map;
|
|
||||||
std::string url;
|
std::string url;
|
||||||
route_map routes;
|
route_map routes;
|
||||||
|
|
||||||
@@ -29,26 +37,7 @@ class RestAPI
|
|||||||
std::shared_ptr<CommandManager> command_manager;
|
std::shared_ptr<CommandManager> command_manager;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// updates
|
void handle_endpoint_command(RequestContext *context, std::string allowed_method, CommandType command_type, int success_code, int error_code);
|
||||||
void handle_update_wifi(RequestContext *context);
|
|
||||||
void handle_update_device(RequestContext *context);
|
|
||||||
void handle_update_camera(RequestContext *context);
|
|
||||||
|
|
||||||
// gets
|
|
||||||
void handle_get_config(RequestContext *context);
|
|
||||||
|
|
||||||
// resets
|
|
||||||
void handle_reset_config(RequestContext *context);
|
|
||||||
|
|
||||||
// reboots
|
|
||||||
void handle_reboot(RequestContext *context);
|
|
||||||
void handle_camera_reboot(RequestContext *context);
|
|
||||||
|
|
||||||
// heartbeat
|
|
||||||
void pong(RequestContext *context);
|
|
||||||
|
|
||||||
// special
|
|
||||||
void handle_save(RequestContext *context);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// this will also need command manager
|
// this will also need command manager
|
||||||
|
|||||||
@@ -1,4 +1,32 @@
|
|||||||
idf_component_register(SRCS "SerialManager/SerialManager.cpp"
|
set (
|
||||||
INCLUDE_DIRS "SerialManager"
|
requires
|
||||||
REQUIRES esp_driver_uart CommandManager ProjectConfig tinyusb
|
esp_driver_uart
|
||||||
|
CommandManager
|
||||||
|
ProjectConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
if ("$ENV{IDF_TARGET}" STREQUAL "esp32s3")
|
||||||
|
list(APPEND requires
|
||||||
|
tinyusb
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set (
|
||||||
|
source_files
|
||||||
|
"SerialManager/SerialManager.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
if ("$ENV{IDF_TARGET}" STREQUAL "esp32s3" )
|
||||||
|
list(APPEND source_files
|
||||||
|
"SerialManager/SerialManager_esp32s3.cpp"
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
list(APPEND source_files
|
||||||
|
"SerialManager/SerialManager_esp32.cpp"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${source_files}
|
||||||
|
INCLUDE_DIRS "SerialManager"
|
||||||
|
REQUIRES ${requires}
|
||||||
)
|
)
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
#include "SerialManager.hpp"
|
#include "SerialManager.hpp"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "main_globals.hpp"
|
#include "main_globals.hpp"
|
||||||
#include "tusb.h"
|
|
||||||
|
|
||||||
#define BUF_SIZE (1024)
|
|
||||||
|
|
||||||
SerialManager::SerialManager(std::shared_ptr<CommandManager> commandManager, esp_timer_handle_t *timerHandle)
|
SerialManager::SerialManager(std::shared_ptr<CommandManager> commandManager, esp_timer_handle_t *timerHandle)
|
||||||
: commandManager(commandManager), timerHandle(timerHandle)
|
: commandManager(commandManager), timerHandle(timerHandle)
|
||||||
@@ -12,62 +9,6 @@ SerialManager::SerialManager(std::shared_ptr<CommandManager> commandManager, esp
|
|||||||
this->temp_data = static_cast<uint8_t *>(malloc(256));
|
this->temp_data = static_cast<uint8_t *>(malloc(256));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SerialManager::setup()
|
|
||||||
{
|
|
||||||
usb_serial_jtag_driver_config_t usb_serial_jtag_config;
|
|
||||||
usb_serial_jtag_config.rx_buffer_size = BUF_SIZE;
|
|
||||||
usb_serial_jtag_config.tx_buffer_size = BUF_SIZE;
|
|
||||||
usb_serial_jtag_driver_install(&usb_serial_jtag_config);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SerialManager::try_receive()
|
|
||||||
{
|
|
||||||
static auto current_position = 0;
|
|
||||||
int len = usb_serial_jtag_read_bytes(this->temp_data, 256, 1000 / 20);
|
|
||||||
|
|
||||||
// If driver is uninstalled or an error occurs, abort read gracefully
|
|
||||||
if (len < 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len > 0)
|
|
||||||
{
|
|
||||||
// Notify main that a command was received during startup
|
|
||||||
notify_startup_command_received();
|
|
||||||
}
|
|
||||||
|
|
||||||
// since we've got something on the serial port
|
|
||||||
// we gotta keep reading until we've got the whole message
|
|
||||||
// we will submit the command once we get a newline, a return or the buffer is full
|
|
||||||
for (auto i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
this->data[current_position++] = this->temp_data[i];
|
|
||||||
// if we're at the end of the buffer, try to process the command anyway
|
|
||||||
// if we've got a new line, we've finished sending the commands, process them
|
|
||||||
if (current_position >= BUF_SIZE || this->data[current_position - 1] == '\n' || this->data[current_position - 1] == '\r')
|
|
||||||
{
|
|
||||||
data[current_position] = '\0';
|
|
||||||
current_position = 0;
|
|
||||||
|
|
||||||
const nlohmann::json result = this->commandManager->executeFromJson(std::string_view(reinterpret_cast<const char *>(this->data)));
|
|
||||||
const auto resultMessage = result.dump();
|
|
||||||
usb_serial_jtag_write_bytes_chunked(resultMessage.c_str(), resultMessage.length(), 1000 / 20);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SerialManager::usb_serial_jtag_write_bytes_chunked(const char *data, size_t len, size_t timeout)
|
|
||||||
{
|
|
||||||
while (len > 0)
|
|
||||||
{
|
|
||||||
auto to_write = len > BUF_SIZE ? BUF_SIZE : len;
|
|
||||||
auto written = usb_serial_jtag_write_bytes(data, to_write, timeout);
|
|
||||||
data += written;
|
|
||||||
len -= written;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to notify that a command was received during startup
|
// Function to notify that a command was received during startup
|
||||||
void SerialManager::notify_startup_command_received()
|
void SerialManager::notify_startup_command_received()
|
||||||
{
|
{
|
||||||
@@ -83,21 +24,6 @@ void SerialManager::notify_startup_command_received()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SerialManager::shutdown()
|
|
||||||
{
|
|
||||||
// Stop heartbeats; timer will be deleted by main if needed.
|
|
||||||
// Uninstall the USB Serial JTAG driver to free the internal USB for TinyUSB.
|
|
||||||
esp_err_t err = usb_serial_jtag_driver_uninstall();
|
|
||||||
if (err == ESP_OK)
|
|
||||||
{
|
|
||||||
ESP_LOGI("[SERIAL]", "usb_serial_jtag driver uninstalled");
|
|
||||||
}
|
|
||||||
else if (err != ESP_ERR_INVALID_STATE)
|
|
||||||
{
|
|
||||||
ESP_LOGW("[SERIAL]", "usb_serial_jtag_driver_uninstall returned %s", esp_err_to_name(err));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// we can cancel this task once we're in cdc
|
// we can cancel this task once we're in cdc
|
||||||
void HandleSerialManagerTask(void *pvParameters)
|
void HandleSerialManagerTask(void *pvParameters)
|
||||||
{
|
{
|
||||||
@@ -107,72 +33,3 @@ void HandleSerialManagerTask(void *pvParameters)
|
|||||||
serialManager->try_receive();
|
serialManager->try_receive();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HandleCDCSerialManagerTask(void *pvParameters)
|
|
||||||
{
|
|
||||||
auto const commandManager = static_cast<CommandManager *>(pvParameters);
|
|
||||||
static char buffer[BUF_SIZE];
|
|
||||||
auto idx = 0;
|
|
||||||
|
|
||||||
cdc_command_packet_t packet;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (xQueueReceive(cdcMessageQueue, &packet, portMAX_DELAY) == pdTRUE)
|
|
||||||
{
|
|
||||||
for (auto i = 0; i < packet.len; i++)
|
|
||||||
{
|
|
||||||
buffer[idx++] = packet.data[i];
|
|
||||||
// if we're at the end of the buffer, try to process the command anyway
|
|
||||||
// if we've got a new line, we've finished sending the commands, process them
|
|
||||||
if (idx >= BUF_SIZE || buffer[idx - 1] == '\n' || buffer[idx - 1] == '\r')
|
|
||||||
{
|
|
||||||
buffer[idx - 1] = '\0';
|
|
||||||
const nlohmann::json result = commandManager->executeFromJson(std::string_view(reinterpret_cast<const char *>(buffer)));
|
|
||||||
const auto resultMessage = result.dump();
|
|
||||||
tud_cdc_write(resultMessage.c_str(), resultMessage.length());
|
|
||||||
tud_cdc_write_flush();
|
|
||||||
idx = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tud_cdc_rx_cb is defined as TU_ATTR_WEAK so we can override it, we will be called back if we get some data
|
|
||||||
// but we don't want to do any processing here since we don't want to risk blocking
|
|
||||||
// grab the data and send it to a queue, a special task will process it and handle with the command manager
|
|
||||||
extern "C" void tud_cdc_rx_cb(uint8_t itf)
|
|
||||||
{
|
|
||||||
// we can void the interface number
|
|
||||||
(void)itf;
|
|
||||||
cdc_command_packet_t packet;
|
|
||||||
auto len = tud_cdc_available();
|
|
||||||
|
|
||||||
if (len > 0)
|
|
||||||
{
|
|
||||||
auto read = tud_cdc_read(packet.data, sizeof(packet.data));
|
|
||||||
if (read > 0)
|
|
||||||
{
|
|
||||||
// we should be safe here, given that the max buffer size is 64
|
|
||||||
packet.len = static_cast<uint8_t>(read);
|
|
||||||
xQueueSend(cdcMessageQueue, &packet, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts)
|
|
||||||
{
|
|
||||||
(void)itf;
|
|
||||||
(void)dtr;
|
|
||||||
(void)rts;
|
|
||||||
|
|
||||||
ESP_LOGI("[SERIAL]", "CDC line state changed: DTR=%d, RTS=%d", dtr, rts);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const *p_line_coding)
|
|
||||||
{
|
|
||||||
(void)itf;
|
|
||||||
ESP_LOGI("[SERIAL]", "CDC line coding: %" PRIu32 " bps, %d stop bits, %d parity, %d data bits",
|
|
||||||
p_line_coding->bit_rate, p_line_coding->stop_bits,
|
|
||||||
p_line_coding->parity, p_line_coding->data_bits);
|
|
||||||
}
|
|
||||||
@@ -10,14 +10,16 @@
|
|||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
#include "freertos/queue.h"
|
#include "freertos/queue.h"
|
||||||
#include "driver/uart.h"
|
#include "sdkconfig.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
#include "driver/usb_serial_jtag.h"
|
|
||||||
#include "esp_vfs_usb_serial_jtag.h"
|
|
||||||
#include "esp_vfs_dev.h"
|
#include "esp_vfs_dev.h"
|
||||||
#include "esp_mac.h"
|
#include "esp_mac.h"
|
||||||
|
|
||||||
|
#ifndef BUF_SIZE
|
||||||
|
#define BUF_SIZE (1024)
|
||||||
|
#endif
|
||||||
|
|
||||||
extern "C" void tud_cdc_rx_cb(uint8_t itf);
|
extern "C" void tud_cdc_rx_cb(uint8_t itf);
|
||||||
extern "C" void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts);
|
extern "C" void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts);
|
||||||
|
|
||||||
@@ -39,8 +41,6 @@ public:
|
|||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void usb_serial_jtag_write_bytes_chunked(const char *data, size_t len, size_t timeout);
|
|
||||||
|
|
||||||
std::shared_ptr<CommandManager> commandManager;
|
std::shared_ptr<CommandManager> commandManager;
|
||||||
esp_timer_handle_t *timerHandle;
|
esp_timer_handle_t *timerHandle;
|
||||||
uint8_t *data;
|
uint8_t *data;
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
#include "SerialManager.hpp"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "main_globals.hpp"
|
||||||
|
#include "driver/uart.h"
|
||||||
|
|
||||||
|
void SerialManager::setup()
|
||||||
|
{
|
||||||
|
uart_config_t uart_config = {
|
||||||
|
.baud_rate = 115200,
|
||||||
|
.data_bits = UART_DATA_8_BITS,
|
||||||
|
.parity = UART_PARITY_DISABLE,
|
||||||
|
.stop_bits = UART_STOP_BITS_1,
|
||||||
|
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto uart_num = static_cast<uart_port_t>(CONFIG_UART_PORT_NUMBER);
|
||||||
|
|
||||||
|
uart_driver_install(uart_num, BUF_SIZE, BUF_SIZE, 0, NULL, 0);
|
||||||
|
uart_param_config(uart_num, &uart_config);
|
||||||
|
|
||||||
|
uart_set_pin(uart_num,
|
||||||
|
CONFIG_UART_TX_PIN,
|
||||||
|
CONFIG_UART_RX_PIN,
|
||||||
|
UART_PIN_NO_CHANGE,
|
||||||
|
UART_PIN_NO_CHANGE);
|
||||||
|
|
||||||
|
gpio_set_pull_mode(static_cast<gpio_num_t>(CONFIG_UART_RX_PIN), GPIO_PULLDOWN_ONLY);
|
||||||
|
|
||||||
|
// ----- Startup Flush -----
|
||||||
|
uart_flush(uart_num);
|
||||||
|
|
||||||
|
uint8_t dump_buf[256];
|
||||||
|
// clean up initial onslaught of logs
|
||||||
|
while (uart_read_bytes(uart_num, dump_buf, sizeof(dump_buf), 10 / portTICK_PERIOD_MS) > 0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void uart_write_bytes_chunked(uart_port_t uart_num, const void *src, size_t size)
|
||||||
|
{
|
||||||
|
while (size > 0)
|
||||||
|
{
|
||||||
|
auto to_write = size > BUF_SIZE ? BUF_SIZE : size;
|
||||||
|
auto written = uart_write_bytes(uart_num, src, to_write);
|
||||||
|
src += written;
|
||||||
|
size -= written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialManager::try_receive()
|
||||||
|
{
|
||||||
|
static auto current_position = 0;
|
||||||
|
const auto uart_num = static_cast<uart_port_t>(CONFIG_UART_PORT_NUMBER);
|
||||||
|
int len = uart_read_bytes(uart_num, this->temp_data, BUF_SIZE, 1000 / 20);
|
||||||
|
|
||||||
|
// If driver is uninstalled or an error occurs, abort read gracefully
|
||||||
|
if (len <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len > 0)
|
||||||
|
{
|
||||||
|
notify_startup_command_received();
|
||||||
|
}
|
||||||
|
|
||||||
|
// since we've got something on the serial port
|
||||||
|
// we gotta keep reading until we've got the whole message
|
||||||
|
// we will submit the command once we get a newline, a return or the buffer is full
|
||||||
|
for (auto i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
this->data[current_position++] = this->temp_data[i];
|
||||||
|
// if we're at the end of the buffer, try to process the command anyway
|
||||||
|
// if we've got a new line, we've finished sending the commands, process them
|
||||||
|
if (current_position >= BUF_SIZE || this->data[current_position - 1] == '\n' || this->data[current_position - 1] == '\r')
|
||||||
|
{
|
||||||
|
data[current_position] = '\0';
|
||||||
|
current_position = 0;
|
||||||
|
|
||||||
|
const nlohmann::json result = this->commandManager->executeFromJson(std::string_view(reinterpret_cast<const char *>(this->data)));
|
||||||
|
const auto resultMessage = result.dump();
|
||||||
|
// todo check if this works
|
||||||
|
// uart_write_bytes_chunked(uart_num, resultMessage.c_str(), resultMessage.length())s
|
||||||
|
uart_write_bytes(uart_num, resultMessage.c_str(), resultMessage.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialManager::shutdown()
|
||||||
|
{
|
||||||
|
// Uninstall the UART driver to free the internal to keep compatibility with JTAG implementation.
|
||||||
|
const auto uart_num = static_cast<uart_port_t>(CONFIG_UART_PORT_NUMBER);
|
||||||
|
esp_err_t err = uart_driver_delete(uart_num);
|
||||||
|
if (err == ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGI("[SERIAL]", "usb_serial_jtag driver uninstalled");
|
||||||
|
}
|
||||||
|
else if (err != ESP_ERR_INVALID_STATE)
|
||||||
|
{
|
||||||
|
ESP_LOGW("[SERIAL]", "usb_serial_jtag_driver_uninstall returned %s", esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
#include "SerialManager.hpp"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "main_globals.hpp"
|
||||||
|
#include "driver/usb_serial_jtag.h"
|
||||||
|
#include "esp_vfs_usb_serial_jtag.h"
|
||||||
|
|
||||||
|
#include "tusb.h"
|
||||||
|
|
||||||
|
void SerialManager::setup()
|
||||||
|
{
|
||||||
|
#ifndef CONFIG_USE_UART_FOR_COMMUNICATION
|
||||||
|
usb_serial_jtag_driver_config_t usb_serial_jtag_config;
|
||||||
|
usb_serial_jtag_config.rx_buffer_size = BUF_SIZE;
|
||||||
|
usb_serial_jtag_config.tx_buffer_size = BUF_SIZE;
|
||||||
|
usb_serial_jtag_driver_install(&usb_serial_jtag_config);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
auto to_write = len > BUF_SIZE ? BUF_SIZE : len;
|
||||||
|
auto written = usb_serial_jtag_write_bytes(data, to_write, timeout);
|
||||||
|
data += written;
|
||||||
|
len -= written;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialManager::try_receive()
|
||||||
|
{
|
||||||
|
static auto current_position = 0;
|
||||||
|
int len = usb_serial_jtag_read_bytes(this->temp_data, 256, 1000 / 20);
|
||||||
|
|
||||||
|
// If driver is uninstalled or an error occurs, abort read gracefully
|
||||||
|
if (len < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len > 0)
|
||||||
|
{
|
||||||
|
// Notify main that a command was received during startup
|
||||||
|
notify_startup_command_received();
|
||||||
|
}
|
||||||
|
|
||||||
|
// since we've got something on the serial port
|
||||||
|
// we gotta keep reading until we've got the whole message
|
||||||
|
// we will submit the command once we get a newline, a return or the buffer is full
|
||||||
|
for (auto i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
this->data[current_position++] = this->temp_data[i];
|
||||||
|
// if we're at the end of the buffer, try to process the command anyway
|
||||||
|
// if we've got a new line, we've finished sending the commands, process them
|
||||||
|
if (current_position >= BUF_SIZE || this->data[current_position - 1] == '\n' || this->data[current_position - 1] == '\r')
|
||||||
|
{
|
||||||
|
data[current_position] = '\0';
|
||||||
|
current_position = 0;
|
||||||
|
|
||||||
|
const nlohmann::json result = this->commandManager->executeFromJson(std::string_view(reinterpret_cast<const char *>(this->data)));
|
||||||
|
const auto resultMessage = result.dump();
|
||||||
|
usb_serial_jtag_write_bytes_chunked(resultMessage.c_str(), resultMessage.length(), 1000 / 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialManager::shutdown()
|
||||||
|
{
|
||||||
|
// Uninstall the USB Serial JTAG driver to free the internal USB for TinyUSB.
|
||||||
|
esp_err_t err = usb_serial_jtag_driver_uninstall();
|
||||||
|
if (err == ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGI("[SERIAL]", "usb_serial_jtag driver uninstalled");
|
||||||
|
}
|
||||||
|
else if (err != ESP_ERR_INVALID_STATE)
|
||||||
|
{
|
||||||
|
ESP_LOGW("[SERIAL]", "usb_serial_jtag_driver_uninstall returned %s", esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleCDCSerialManagerTask(void *pvParameters)
|
||||||
|
{
|
||||||
|
#ifndef CONFIG_USE_UART_FOR_COMMUNICATION
|
||||||
|
auto const commandManager = static_cast<CommandManager *>(pvParameters);
|
||||||
|
static char buffer[BUF_SIZE];
|
||||||
|
auto idx = 0;
|
||||||
|
|
||||||
|
cdc_command_packet_t packet;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (xQueueReceive(cdcMessageQueue, &packet, portMAX_DELAY) == pdTRUE)
|
||||||
|
{
|
||||||
|
for (auto i = 0; i < packet.len; i++)
|
||||||
|
{
|
||||||
|
buffer[idx++] = packet.data[i];
|
||||||
|
// if we're at the end of the buffer, try to process the command anyway
|
||||||
|
// if we've got a new line, we've finished sending the commands, process them
|
||||||
|
if (idx >= BUF_SIZE || buffer[idx - 1] == '\n' || buffer[idx - 1] == '\r')
|
||||||
|
{
|
||||||
|
buffer[idx - 1] = '\0';
|
||||||
|
const nlohmann::json result = commandManager->executeFromJson(std::string_view(reinterpret_cast<const char *>(buffer)));
|
||||||
|
const auto resultMessage = result.dump();
|
||||||
|
tud_cdc_write(resultMessage.c_str(), resultMessage.length());
|
||||||
|
tud_cdc_write_flush();
|
||||||
|
idx = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// tud_cdc_rx_cb is defined as TU_ATTR_WEAK so we can override it, we will be called back if we get some data
|
||||||
|
// but we don't want to do any processing here since we don't want to risk blocking
|
||||||
|
// grab the data and send it to a queue, a special task will process it and handle with the command manager
|
||||||
|
extern "C" void tud_cdc_rx_cb(uint8_t itf)
|
||||||
|
{
|
||||||
|
// we can void the interface number
|
||||||
|
(void)itf;
|
||||||
|
cdc_command_packet_t packet;
|
||||||
|
auto len = tud_cdc_available();
|
||||||
|
|
||||||
|
if (len > 0)
|
||||||
|
{
|
||||||
|
auto read = tud_cdc_read(packet.data, sizeof(packet.data));
|
||||||
|
if (read > 0)
|
||||||
|
{
|
||||||
|
// we should be safe here, given that the max buffer size is 64
|
||||||
|
packet.len = static_cast<uint8_t>(read);
|
||||||
|
xQueueSend(cdcMessageQueue, &packet, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts)
|
||||||
|
{
|
||||||
|
(void)itf;
|
||||||
|
(void)dtr;
|
||||||
|
(void)rts;
|
||||||
|
|
||||||
|
ESP_LOGI("[SERIAL]", "CDC line state changed: DTR=%d, RTS=%d", dtr, rts);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const *p_line_coding)
|
||||||
|
{
|
||||||
|
(void)itf;
|
||||||
|
ESP_LOGI("[SERIAL]", "CDC line coding: %" PRIu32 " bps, %d stop bits, %d parity, %d data bits",
|
||||||
|
p_line_coding->bit_rate, p_line_coding->stop_bits,
|
||||||
|
p_line_coding->parity, p_line_coding->data_bits);
|
||||||
|
}
|
||||||
@@ -77,7 +77,8 @@ esp_err_t StreamHelpers::stream(httpd_req_t *req)
|
|||||||
|
|
||||||
// Only log every 100 frames to reduce overhead
|
// Only log every 100 frames to reduce overhead
|
||||||
static int frame_count = 0;
|
static int frame_count = 0;
|
||||||
if (++frame_count % 100 == 0) {
|
if (++frame_count % 100 == 0)
|
||||||
|
{
|
||||||
long request_end = Helpers::getTimeInMillis();
|
long request_end = Helpers::getTimeInMillis();
|
||||||
long latency = (request_end - last_request_time);
|
long latency = (request_end - last_request_time);
|
||||||
last_request_time = request_end;
|
last_request_time = request_end;
|
||||||
@@ -98,7 +99,6 @@ esp_err_t StreamServer::startStreamServer()
|
|||||||
{
|
{
|
||||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||||
config.stack_size = 20480;
|
config.stack_size = 20480;
|
||||||
// todo bring this back to 1 once we're done with logs over websockets
|
|
||||||
config.max_uri_handlers = 10;
|
config.max_uri_handlers = 10;
|
||||||
config.server_port = STREAM_SERVER_PORT;
|
config.server_port = STREAM_SERVER_PORT;
|
||||||
config.ctrl_port = STREAM_SERVER_PORT;
|
config.ctrl_port = STREAM_SERVER_PORT;
|
||||||
@@ -139,7 +139,6 @@ esp_err_t StreamServer::startStreamServer()
|
|||||||
httpd_register_uri_handler(camera_stream, &stream_page);
|
httpd_register_uri_handler(camera_stream, &stream_page);
|
||||||
|
|
||||||
ESP_LOGI(STREAM_SERVER_TAG, "Stream server started on port %d", STREAM_SERVER_PORT);
|
ESP_LOGI(STREAM_SERVER_TAG, "Stream server started on port %d", STREAM_SERVER_PORT);
|
||||||
// todo add printing IP addr here
|
|
||||||
|
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,19 @@
|
|||||||
|
set (
|
||||||
|
requires
|
||||||
|
esp_timer
|
||||||
|
esp32-camera
|
||||||
|
StateManager
|
||||||
|
CameraManager
|
||||||
|
Helpers
|
||||||
|
)
|
||||||
|
|
||||||
|
if ("$ENV{IDF_TARGET}" STREQUAL "esp32s3")
|
||||||
|
list(APPEND requires
|
||||||
|
usb_device_uvc
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
idf_component_register(SRCS "UVCStream/UVCStream.cpp"
|
idf_component_register(SRCS "UVCStream/UVCStream.cpp"
|
||||||
INCLUDE_DIRS "UVCStream"
|
INCLUDE_DIRS "UVCStream"
|
||||||
REQUIRES esp_timer esp32-camera StateManager usb_device_uvc CameraManager Helpers
|
REQUIRES ${requires}
|
||||||
)
|
)
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
#include "UVCStream.hpp"
|
#include "UVCStream.hpp"
|
||||||
|
|
||||||
|
#ifdef CONFIG_GENERAL_INCLUDE_UVC_MODE
|
||||||
#include <cstdio> // for snprintf
|
#include <cstdio> // for snprintf
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
// no deps on main globals here; handover is performed in main before calling setup when needed
|
|
||||||
|
|
||||||
static const char *UVC_STREAM_TAG = "[UVC DEVICE]";
|
static const char *UVC_STREAM_TAG = "[UVC DEVICE]";
|
||||||
|
|
||||||
@@ -167,12 +168,6 @@ static void UVCStreamHelpers::camera_fb_return_cb(uvc_fb_t *fb, void *cb_ctx)
|
|||||||
|
|
||||||
esp_err_t UVCStreamManager::setup()
|
esp_err_t UVCStreamManager::setup()
|
||||||
{
|
{
|
||||||
|
|
||||||
#ifndef CONFIG_GENERAL_INCLUDE_UVC_MODE
|
|
||||||
ESP_LOGE(UVC_STREAM_TAG, "The board does not support UVC, please, setup WiFi connection.");
|
|
||||||
return ESP_FAIL;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ESP_LOGI(UVC_STREAM_TAG, "Setting up UVC Stream");
|
ESP_LOGI(UVC_STREAM_TAG, "Setting up UVC Stream");
|
||||||
// Allocate a fixed-size transfer buffer (compile-time constant)
|
// Allocate a fixed-size transfer buffer (compile-time constant)
|
||||||
uvc_buffer_size = UVCStreamManager::UVC_MAX_FRAMESIZE_SIZE;
|
uvc_buffer_size = UVCStreamManager::UVC_MAX_FRAMESIZE_SIZE;
|
||||||
@@ -219,3 +214,5 @@ esp_err_t UVCStreamManager::start()
|
|||||||
// UVC device is already initialized in setup(), just log that we're starting
|
// UVC device is already initialized in setup(), just log that we're starting
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#ifndef UVCSTREAM_HPP
|
#ifndef UVCSTREAM_HPP
|
||||||
#define UVCSTREAM_HPP
|
#define UVCSTREAM_HPP
|
||||||
|
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#ifdef CONFIG_GENERAL_INCLUDE_UVC_MODE
|
||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
#include "esp_mac.h"
|
#include "esp_mac.h"
|
||||||
#include "esp_camera.h"
|
#include "esp_camera.h"
|
||||||
@@ -33,8 +37,6 @@ extern QueueHandle_t eventQueue;
|
|||||||
|
|
||||||
namespace UVCStreamHelpers
|
namespace UVCStreamHelpers
|
||||||
{
|
{
|
||||||
// TODO move the camera handling code to the camera manager and have the uvc manager initialize it in wired mode
|
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
camera_fb_t *cam_fb_p;
|
camera_fb_t *cam_fb_p;
|
||||||
@@ -64,3 +66,4 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
#endif // UVCSTREAM_HPP
|
#endif // UVCSTREAM_HPP
|
||||||
|
#endif
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_UVC_CAM1_MULTI_FRAMESIZE
|
#ifdef CONFIG_UVC_CAM1_MULTI_FRAMESIZE
|
||||||
//If enable, add VGA and HVGA to list
|
// If enable, add VGA and HVGA to list
|
||||||
#define UVC_CAM1_FRAME_MULTI 1
|
#define UVC_CAM1_FRAME_MULTI 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -24,25 +24,6 @@
|
|||||||
#define UVC_CAM1_BULK_MODE
|
#define UVC_CAM1_BULK_MODE
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
|
||||||
#ifdef CONFIG_FORMAT_MJPEG_CAM2
|
|
||||||
#define FORMAT_MJPEG_CAM2 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef CONFIG_UVC_CAM2_MULTI_FRAMESIZE
|
|
||||||
//If enable, add VGA and HVGA to list
|
|
||||||
#define UVC_CAM2_FRAME_MULTI 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define UVC_CAM2_FRAME_WIDTH CONFIG_UVC_CAM2_FRAMESIZE_WIDTH
|
|
||||||
#define UVC_CAM2_FRAME_HEIGHT CONFIG_UVC_CAM2_FRAMESIZE_HEIGT
|
|
||||||
#define UVC_CAM2_FRAME_RATE CONFIG_UVC_CAM2_FRAMERATE
|
|
||||||
|
|
||||||
#ifdef CONFIG_UVC_MODE_BULK_CAM2
|
|
||||||
#define UVC_CAM2_BULK_MODE
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef UVC_CAM2_FRAME_WIDTH
|
#ifndef UVC_CAM2_FRAME_WIDTH
|
||||||
#define UVC_CAM2_FRAME_WIDTH UVC_CAM1_FRAME_WIDTH
|
#define UVC_CAM2_FRAME_WIDTH UVC_CAM1_FRAME_WIDTH
|
||||||
#endif
|
#endif
|
||||||
@@ -55,7 +36,8 @@
|
|||||||
#define UVC_CAM2_FRAME_RATE UVC_CAM1_FRAME_RATE
|
#define UVC_CAM2_FRAME_RATE UVC_CAM1_FRAME_RATE
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static const struct {
|
static const struct
|
||||||
|
{
|
||||||
int width;
|
int width;
|
||||||
int height;
|
int height;
|
||||||
int rate;
|
int rate;
|
||||||
@@ -64,13 +46,13 @@ static const struct {
|
|||||||
{CONFIG_UVC_MULTI_FRAME_WIDTH_1, CONFIG_UVC_MULTI_FRAME_HEIGHT_1, CONFIG_UVC_MULTI_FRAME_FPS_1},
|
{CONFIG_UVC_MULTI_FRAME_WIDTH_1, CONFIG_UVC_MULTI_FRAME_HEIGHT_1, CONFIG_UVC_MULTI_FRAME_FPS_1},
|
||||||
{CONFIG_UVC_MULTI_FRAME_WIDTH_2, CONFIG_UVC_MULTI_FRAME_HEIGHT_2, CONFIG_UVC_MULTI_FRAME_FPS_2},
|
{CONFIG_UVC_MULTI_FRAME_WIDTH_2, CONFIG_UVC_MULTI_FRAME_HEIGHT_2, CONFIG_UVC_MULTI_FRAME_FPS_2},
|
||||||
{CONFIG_UVC_MULTI_FRAME_WIDTH_3, CONFIG_UVC_MULTI_FRAME_HEIGHT_3, CONFIG_UVC_MULTI_FRAME_FPS_3},
|
{CONFIG_UVC_MULTI_FRAME_WIDTH_3, CONFIG_UVC_MULTI_FRAME_HEIGHT_3, CONFIG_UVC_MULTI_FRAME_FPS_3},
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
{UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE},
|
{UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE},
|
||||||
{CONFIG_UVC_MULTI_FRAME_WIDTH_1, CONFIG_UVC_MULTI_FRAME_HEIGHT_1, CONFIG_UVC_MULTI_FRAME_FPS_1},
|
{CONFIG_UVC_MULTI_FRAME_WIDTH_1, CONFIG_UVC_MULTI_FRAME_HEIGHT_1, CONFIG_UVC_MULTI_FRAME_FPS_1},
|
||||||
{CONFIG_UVC_MULTI_FRAME_WIDTH_2, CONFIG_UVC_MULTI_FRAME_HEIGHT_2, CONFIG_UVC_MULTI_FRAME_FPS_2},
|
{CONFIG_UVC_MULTI_FRAME_WIDTH_2, CONFIG_UVC_MULTI_FRAME_HEIGHT_2, CONFIG_UVC_MULTI_FRAME_FPS_2},
|
||||||
{CONFIG_UVC_MULTI_FRAME_WIDTH_3, CONFIG_UVC_MULTI_FRAME_HEIGHT_3, CONFIG_UVC_MULTI_FRAME_FPS_3},
|
{CONFIG_UVC_MULTI_FRAME_WIDTH_3, CONFIG_UVC_MULTI_FRAME_HEIGHT_3, CONFIG_UVC_MULTI_FRAME_FPS_3},
|
||||||
}
|
}};
|
||||||
};
|
|
||||||
|
|
||||||
#define UVC_FRAME_NUM (sizeof(UVC_FRAMES_INFO[0]) / sizeof(UVC_FRAMES_INFO[0][0]))
|
#define UVC_FRAME_NUM (sizeof(UVC_FRAMES_INFO[0]) / sizeof(UVC_FRAMES_INFO[0][0]))
|
||||||
_Static_assert(UVC_FRAME_NUM == 4, "UVC_FRAME_NUM must be 4");
|
_Static_assert(UVC_FRAME_NUM == 4, "UVC_FRAME_NUM must be 4");
|
||||||
|
|||||||
@@ -20,11 +20,7 @@
|
|||||||
|
|
||||||
static const char *TAG = "usbd_uvc";
|
static const char *TAG = "usbd_uvc";
|
||||||
|
|
||||||
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
|
||||||
#define UVC_CAM_NUM 2
|
|
||||||
#else
|
|
||||||
#define UVC_CAM_NUM 1
|
#define UVC_CAM_NUM 1
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
@@ -85,12 +81,6 @@ void tud_suspend_cb(bool remote_wakeup_en)
|
|||||||
{
|
{
|
||||||
s_uvc_device.user_config[0].stop_cb(s_uvc_device.user_config[0].cb_ctx);
|
s_uvc_device.user_config[0].stop_cb(s_uvc_device.user_config[0].cb_ctx);
|
||||||
}
|
}
|
||||||
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
|
||||||
if (s_uvc_device.user_config[1].stop_cb)
|
|
||||||
{
|
|
||||||
s_uvc_device.user_config[1].stop_cb(s_uvc_device.user_config[1].cb_ctx);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
ESP_LOGI(TAG, "Suspend");
|
ESP_LOGI(TAG, "Suspend");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,82 +168,6 @@ static void video_task(void *arg)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
|
||||||
static void video_task2(void *arg)
|
|
||||||
{
|
|
||||||
uint32_t start_ms = 0;
|
|
||||||
uint32_t frame_num = 0;
|
|
||||||
uint32_t frame_len = 0;
|
|
||||||
uint32_t already_start = 0;
|
|
||||||
uint32_t tx_busy = 0;
|
|
||||||
uint8_t *uvc_buffer = s_uvc_device.user_config[1].uvc_buffer;
|
|
||||||
uint32_t uvc_buffer_size = s_uvc_device.user_config[1].uvc_buffer_size;
|
|
||||||
uvc_fb_t *pic = NULL;
|
|
||||||
|
|
||||||
while (1)
|
|
||||||
{
|
|
||||||
if (!tud_video_n_streaming(1, 0))
|
|
||||||
{
|
|
||||||
already_start = 0;
|
|
||||||
frame_num = 0;
|
|
||||||
tx_busy = 0;
|
|
||||||
vTaskDelay(1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!already_start)
|
|
||||||
{
|
|
||||||
already_start = 1;
|
|
||||||
start_ms = get_time_millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t cur = get_time_millis();
|
|
||||||
if (cur - start_ms < s_uvc_device.interval_ms[1])
|
|
||||||
{
|
|
||||||
vTaskDelay(1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tx_busy)
|
|
||||||
{
|
|
||||||
uint32_t xfer_done = ulTaskNotifyTake(pdTRUE, 1);
|
|
||||||
if (xfer_done == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
++frame_num;
|
|
||||||
tx_busy = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
start_ms += s_uvc_device.interval_ms[1];
|
|
||||||
ESP_LOGD(TAG, "frame %" PRIu32 " taking picture...", frame_num);
|
|
||||||
pic = s_uvc_device.user_config[1].fb_get_cb(s_uvc_device.user_config[1].cb_ctx);
|
|
||||||
if (pic)
|
|
||||||
{
|
|
||||||
ESP_LOGD(TAG, "Picture taken! Its size was: %zu bytes", pic->len);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ESP_LOGE(TAG, "Failed to capture picture");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pic->len > uvc_buffer_size)
|
|
||||||
{
|
|
||||||
ESP_LOGW(TAG, "frame size is too big, dropping frame");
|
|
||||||
s_uvc_device.user_config[1].fb_return_cb(pic, s_uvc_device.user_config[1].cb_ctx);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
frame_len = pic->len;
|
|
||||||
memcpy(uvc_buffer, pic->buf, frame_len);
|
|
||||||
s_uvc_device.user_config[1].fb_return_cb(pic, s_uvc_device.user_config[1].cb_ctx);
|
|
||||||
tx_busy = 1;
|
|
||||||
tud_video_n_frame_xfer(1, 0, (void *)uvc_buffer, frame_len);
|
|
||||||
ESP_LOGD(TAG, "frame %" PRIu32 " transfer start, size %" PRIu32, frame_num, frame_len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void tud_video_frame_xfer_complete_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx)
|
void tud_video_frame_xfer_complete_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx)
|
||||||
{
|
{
|
||||||
(void)ctl_idx;
|
(void)ctl_idx;
|
||||||
@@ -307,20 +221,11 @@ esp_err_t uvc_device_config(int index, uvc_device_config_t *config)
|
|||||||
esp_err_t uvc_device_init(void)
|
esp_err_t uvc_device_init(void)
|
||||||
{
|
{
|
||||||
ESP_RETURN_ON_FALSE(s_uvc_device.uvc_init[0], ESP_ERR_INVALID_STATE, TAG, "uvc device 0 not init");
|
ESP_RETURN_ON_FALSE(s_uvc_device.uvc_init[0], ESP_ERR_INVALID_STATE, TAG, "uvc device 0 not init");
|
||||||
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
|
||||||
ESP_RETURN_ON_FALSE(s_uvc_device.uvc_init[1], ESP_ERR_INVALID_STATE, TAG, "uvc device 1 not init, if not use, please disable CONFIG_UVC_SUPPORT_TWO_CAM");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef CONFIG_FORMAT_MJPEG_CAM1
|
#ifdef CONFIG_FORMAT_MJPEG_CAM1
|
||||||
s_uvc_device.format[0] = UVC_FORMAT_JPEG;
|
s_uvc_device.format[0] = UVC_FORMAT_JPEG;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
|
||||||
#ifdef CONFIG_FORMAT_MJPEG_CAM2
|
|
||||||
s_uvc_device.format[1] = UVC_FORMAT_JPEG;
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// init device stack on configured roothub port
|
// init device stack on configured roothub port
|
||||||
usb_phy_init();
|
usb_phy_init();
|
||||||
bool usb_init = tusb_init();
|
bool usb_init = tusb_init();
|
||||||
@@ -335,10 +240,6 @@ esp_err_t uvc_device_init(void)
|
|||||||
#if (CFG_TUD_VIDEO)
|
#if (CFG_TUD_VIDEO)
|
||||||
core_id = (CONFIG_UVC_CAM1_TASK_CORE < 0) ? tskNO_AFFINITY : CONFIG_UVC_CAM1_TASK_CORE;
|
core_id = (CONFIG_UVC_CAM1_TASK_CORE < 0) ? tskNO_AFFINITY : CONFIG_UVC_CAM1_TASK_CORE;
|
||||||
xTaskCreatePinnedToCore(video_task, "UVC", 4096, NULL, CONFIG_UVC_CAM1_TASK_PRIORITY, &s_uvc_device.uvc_task_hdl[0], core_id);
|
xTaskCreatePinnedToCore(video_task, "UVC", 4096, NULL, CONFIG_UVC_CAM1_TASK_PRIORITY, &s_uvc_device.uvc_task_hdl[0], core_id);
|
||||||
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
|
||||||
core_id = (CONFIG_UVC_CAM2_TASK_CORE < 0) ? tskNO_AFFINITY : CONFIG_UVC_CAM2_TASK_CORE;
|
|
||||||
xTaskCreatePinnedToCore(video_task2, "UVC2", 4096, NULL, CONFIG_UVC_CAM2_TASK_PRIORITY, &s_uvc_device.uvc_task_hdl[1], core_id);
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
ESP_LOGI(TAG, "UVC Device Start, Version: %d.%d.%d", USB_DEVICE_UVC_VER_MAJOR, USB_DEVICE_UVC_VER_MINOR, USB_DEVICE_UVC_VER_PATCH);
|
ESP_LOGI(TAG, "UVC Device Start, Version: %d.%d.%d", USB_DEVICE_UVC_VER_MAJOR, USB_DEVICE_UVC_VER_MINOR, USB_DEVICE_UVC_VER_PATCH);
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
|
|||||||
@@ -107,6 +107,28 @@ menu "OpenIris: WiFi Configuration"
|
|||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|
||||||
|
menu "OpenIris: Serial Communication Settings"
|
||||||
|
config UART_PORT_NUMBER
|
||||||
|
int "UART Port number"
|
||||||
|
default 0
|
||||||
|
range 0 4
|
||||||
|
help
|
||||||
|
The UART port to use for communication
|
||||||
|
|
||||||
|
config UART_RX_PIN
|
||||||
|
int "UART RX PIN number"
|
||||||
|
default -1
|
||||||
|
help
|
||||||
|
The UART RX pin to use for communication. If set to -1 we won't override it. It will be treated as NO_CHANGE
|
||||||
|
|
||||||
|
config UART_TX_PIN
|
||||||
|
int "UART TX PIN number"
|
||||||
|
default -1
|
||||||
|
help
|
||||||
|
The UART TX pin to use for communication. If set to -1 we won't override it. It will be treated as NO_CHANGE
|
||||||
|
|
||||||
|
endmenu
|
||||||
|
|
||||||
menu "OpenIris: LED Configuration"
|
menu "OpenIris: LED Configuration"
|
||||||
|
|
||||||
config LED_DEBUG_ENABLE
|
config LED_DEBUG_ENABLE
|
||||||
|
|||||||
+18
-6
@@ -22,7 +22,10 @@
|
|||||||
#include <SerialManager.hpp>
|
#include <SerialManager.hpp>
|
||||||
#include <RestAPI.hpp>
|
#include <RestAPI.hpp>
|
||||||
#include <main_globals.hpp>
|
#include <main_globals.hpp>
|
||||||
|
|
||||||
|
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||||
#include <MonitoringManager.hpp>
|
#include <MonitoringManager.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_GENERAL_INCLUDE_UVC_MODE
|
#ifdef CONFIG_GENERAL_INCLUDE_UVC_MODE
|
||||||
#include <UVCStream.hpp>
|
#include <UVCStream.hpp>
|
||||||
@@ -36,7 +39,7 @@
|
|||||||
#define BLINK_GPIO (gpio_num_t) CONFIG_LED_DEBUG_GPIO
|
#define BLINK_GPIO (gpio_num_t) CONFIG_LED_DEBUG_GPIO
|
||||||
#else
|
#else
|
||||||
// Use an invalid / unused GPIO when debug LED disabled to avoid accidental toggles
|
// Use an invalid / unused GPIO when debug LED disabled to avoid accidental toggles
|
||||||
#define BLINK_GPIO (gpio_num_t) - 1
|
#define BLINK_GPIO (gpio_num_t)(-1)
|
||||||
#endif
|
#endif
|
||||||
#define CONFIG_LED_C_PIN_GPIO (gpio_num_t) CONFIG_LED_EXTERNAL_GPIO
|
#define CONFIG_LED_C_PIN_GPIO (gpio_num_t) CONFIG_LED_EXTERNAL_GPIO
|
||||||
|
|
||||||
@@ -61,14 +64,18 @@ MDNSManager mdnsManager(deviceConfig, eventQueue);
|
|||||||
std::shared_ptr<CameraManager> cameraHandler = std::make_shared<CameraManager>(deviceConfig, eventQueue);
|
std::shared_ptr<CameraManager> cameraHandler = std::make_shared<CameraManager>(deviceConfig, eventQueue);
|
||||||
StreamServer streamServer(80, stateManager);
|
StreamServer streamServer(80, stateManager);
|
||||||
|
|
||||||
auto *restAPI = new RestAPI("http://0.0.0.0:81", commandManager);
|
std::shared_ptr<RestAPI> restAPI = std::make_shared<RestAPI>("http://0.0.0.0:81", commandManager);
|
||||||
|
|
||||||
#ifdef CONFIG_GENERAL_INCLUDE_UVC_MODE
|
#ifdef CONFIG_GENERAL_INCLUDE_UVC_MODE
|
||||||
UVCStreamManager uvcStream;
|
UVCStreamManager uvcStream;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto ledManager = std::make_shared<LEDManager>(BLINK_GPIO, CONFIG_LED_C_PIN_GPIO, ledStateQueue, deviceConfig);
|
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>();
|
std::shared_ptr<MonitoringManager> monitoringManager = std::make_shared<MonitoringManager>();
|
||||||
|
#endif
|
||||||
|
|
||||||
auto *serialManager = new SerialManager(commandManager, &timerHandle);
|
auto *serialManager = new SerialManager(commandManager, &timerHandle);
|
||||||
|
|
||||||
void startWiFiMode();
|
void startWiFiMode();
|
||||||
@@ -165,7 +172,6 @@ void startWiredMode(bool shouldCloseSerialManager)
|
|||||||
#ifndef CONFIG_GENERAL_INCLUDE_UVC_MODE
|
#ifndef CONFIG_GENERAL_INCLUDE_UVC_MODE
|
||||||
ESP_LOGE("[MAIN]", "UVC mode selected but the board likely does not support it.");
|
ESP_LOGE("[MAIN]", "UVC mode selected but the board likely does not support it.");
|
||||||
ESP_LOGI("[MAIN]", "Falling back to WiFi mode if credentials available");
|
ESP_LOGI("[MAIN]", "Falling back to WiFi mode if credentials available");
|
||||||
deviceMode = StreamingMode::WIFI;
|
|
||||||
startWiFiMode();
|
startWiFiMode();
|
||||||
#else
|
#else
|
||||||
ESP_LOGI("[MAIN]", "Starting UVC streaming mode.");
|
ESP_LOGI("[MAIN]", "Starting UVC streaming mode.");
|
||||||
@@ -216,6 +222,7 @@ void startWiFiMode()
|
|||||||
mdnsManager.start();
|
mdnsManager.start();
|
||||||
restAPI->begin();
|
restAPI->begin();
|
||||||
StreamingMode mode = deviceConfig->getDeviceMode();
|
StreamingMode mode = deviceConfig->getDeviceMode();
|
||||||
|
// don't enable in SETUP mode
|
||||||
if (mode == StreamingMode::WIFI)
|
if (mode == StreamingMode::WIFI)
|
||||||
{
|
{
|
||||||
streamServer.startStreamServer();
|
streamServer.startStreamServer();
|
||||||
@@ -223,8 +230,8 @@ void startWiFiMode()
|
|||||||
xTaskCreate(
|
xTaskCreate(
|
||||||
HandleRestAPIPollTask,
|
HandleRestAPIPollTask,
|
||||||
"HandleRestAPIPollTask",
|
"HandleRestAPIPollTask",
|
||||||
1024 * 2,
|
2024 * 2,
|
||||||
restAPI,
|
restAPI.get(),
|
||||||
1, // it's the rest API, we only serve commands over it so we don't really need a higher priority
|
1, // it's the rest API, we only serve commands over it so we don't really need a higher priority
|
||||||
nullptr);
|
nullptr);
|
||||||
#else
|
#else
|
||||||
@@ -265,18 +272,23 @@ extern "C" void app_main(void)
|
|||||||
dependencyRegistry->registerService<WiFiManager>(DependencyType::wifi_manager, wifiManager);
|
dependencyRegistry->registerService<WiFiManager>(DependencyType::wifi_manager, wifiManager);
|
||||||
#endif
|
#endif
|
||||||
dependencyRegistry->registerService<LEDManager>(DependencyType::led_manager, ledManager);
|
dependencyRegistry->registerService<LEDManager>(DependencyType::led_manager, ledManager);
|
||||||
|
|
||||||
|
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||||
dependencyRegistry->registerService<MonitoringManager>(DependencyType::monitoring_manager, monitoringManager);
|
dependencyRegistry->registerService<MonitoringManager>(DependencyType::monitoring_manager, monitoringManager);
|
||||||
|
#endif
|
||||||
|
|
||||||
// add endpoint to check firmware version
|
// add endpoint to check firmware version
|
||||||
// setup CI and building for other boards
|
|
||||||
|
|
||||||
// esp_log_set_vprintf(&websocket_logger);
|
// esp_log_set_vprintf(&websocket_logger);
|
||||||
Logo::printASCII();
|
Logo::printASCII();
|
||||||
initNVSStorage();
|
initNVSStorage();
|
||||||
deviceConfig->load();
|
deviceConfig->load();
|
||||||
ledManager->setup();
|
ledManager->setup();
|
||||||
|
|
||||||
|
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||||
monitoringManager->setup();
|
monitoringManager->setup();
|
||||||
monitoringManager->start();
|
monitoringManager->start();
|
||||||
|
#endif
|
||||||
|
|
||||||
xTaskCreate(
|
xTaskCreate(
|
||||||
HandleStateManagerTask,
|
HandleStateManagerTask,
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
[project]
|
||||||
|
name = "blink"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"pyserial>=3.5",
|
||||||
|
"pytest>=9.0.1",
|
||||||
|
"python-dotenv>=1.2.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"bumpver>=2025.1131",
|
||||||
|
]
|
||||||
|
|
||||||
|
[bumpver]
|
||||||
|
current_version = "0.1.0"
|
||||||
|
version_pattern = "MAJOR.MINOR.PATCH[PYTAGNUM]"
|
||||||
|
commit = true
|
||||||
|
tag = true
|
||||||
|
push = false
|
||||||
|
|
||||||
|
[bumpver.file_patterns]
|
||||||
|
"pyproject.toml" = [
|
||||||
|
'version = "{version}"',
|
||||||
|
]
|
||||||
|
"sdkconfig" = [
|
||||||
|
'CONFIG_GENERAL_VERSION="{version}"',
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.pytest]
|
||||||
|
testpaths = [
|
||||||
|
"tests"
|
||||||
|
]
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
|
||||||
# SPDX-License-Identifier: CC0-1.0
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from pytest_embedded_idf.dut import IdfDut
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.supported_targets
|
|
||||||
@pytest.mark.generic
|
|
||||||
def test_blink(dut: IdfDut) -> None:
|
|
||||||
# check and log bin size
|
|
||||||
binary_file = os.path.join(dut.app.binary_path, 'blink.bin')
|
|
||||||
bin_size = os.path.getsize(binary_file)
|
|
||||||
logging.info('blink_bin_size : {}KB'.format(bin_size // 1024))
|
|
||||||
@@ -595,6 +595,14 @@ CONFIG_WIFI_AP_SSID="EyeTrackVR"
|
|||||||
CONFIG_WIFI_AP_PASSWORD="12345678"
|
CONFIG_WIFI_AP_PASSWORD="12345678"
|
||||||
# end of OpenIris: WiFi Configuration
|
# end of OpenIris: WiFi Configuration
|
||||||
|
|
||||||
|
#
|
||||||
|
# OpenIris: Serial Communication Settings
|
||||||
|
#
|
||||||
|
CONFIG_UART_PORT_NUMBER=0
|
||||||
|
CONFIG_UART_RX_PIN=-1
|
||||||
|
CONFIG_UART_TX_PIN=-1
|
||||||
|
# end of OpenIris: Serial Communication Settings
|
||||||
|
|
||||||
#
|
#
|
||||||
# OpenIris: LED Configuration
|
# OpenIris: LED Configuration
|
||||||
#
|
#
|
||||||
|
|||||||
+2057
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
|||||||
|
WIFI_SSID=
|
||||||
|
WIFI_PASS=
|
||||||
|
SWITCH_MODE_REBOOT_TIME=5
|
||||||
|
WIFI_CONNECTION_TIMEOUT=5
|
||||||
|
INVALID_WIFI_CONNECTION_TIMEOUT=30
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
import dotenv
|
||||||
|
import pytest
|
||||||
|
import time
|
||||||
|
|
||||||
|
from tests.utils import (
|
||||||
|
OpenIrisDeviceManager,
|
||||||
|
has_command_failed,
|
||||||
|
get_current_ports,
|
||||||
|
get_new_port,
|
||||||
|
)
|
||||||
|
|
||||||
|
board_capabilities = {
|
||||||
|
"esp_eye": ["wired", "wireless"],
|
||||||
|
"esp32AIThinker": ["wireless"],
|
||||||
|
"esp32Cam": ["wireless"],
|
||||||
|
"esp32M5Stack": ["wireless"],
|
||||||
|
"facefocusvr_eye_L": ["wired", "measure_current"],
|
||||||
|
"facefocusvr_eye_R": ["wired", "measure_current"],
|
||||||
|
"facefocusvr_face": ["wired", "measure_current"],
|
||||||
|
"project_babble": ["wireless", "wired"],
|
||||||
|
"seed_studio": ["wireless", "wired"],
|
||||||
|
"wrooms3": ["wireless", "wired"],
|
||||||
|
"wrooms3QIO": ["wireless", "wired"],
|
||||||
|
"wrover": ["wireless", "wired"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
parser.addoption("--board", action="store")
|
||||||
|
parser.addoption(
|
||||||
|
"--connection",
|
||||||
|
action="store",
|
||||||
|
help="Defines how to connect to the given board, wireless by ip/mdns or wired by com/cdc",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_configure(config):
|
||||||
|
config.addinivalue_line(
|
||||||
|
"markers", "has_capability(caps): skip if the board does not have the capability"
|
||||||
|
)
|
||||||
|
config.addinivalue_line(
|
||||||
|
"markers", "lacks_capability(caps): skip if the board DOES have the capability"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def check_capability_marker(request, board_lacks_capability):
|
||||||
|
"""
|
||||||
|
Autorun on each test, checks if the board we started with, has the required capability
|
||||||
|
|
||||||
|
This lets us skip tests that are impossible to run on some boards - like for example:
|
||||||
|
|
||||||
|
It's impossible to run wired tests on a wireless board
|
||||||
|
It's impossible to run tests for measuring current on boards without this feature
|
||||||
|
"""
|
||||||
|
if marker := request.node.get_closest_marker("has_capability"):
|
||||||
|
if not len(marker.args):
|
||||||
|
raise ValueError(
|
||||||
|
"has_capability marker must be provided with a capability to check"
|
||||||
|
)
|
||||||
|
|
||||||
|
for capability in marker.args:
|
||||||
|
if board_lacks_capability(capability):
|
||||||
|
pytest.skip(f"Board does not have capability {capability}")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def check_lacks_capability_marker(request, board_lacks_capability):
|
||||||
|
if lacks_capability_marker := request.node.get_closest_marker("lacks_capability"):
|
||||||
|
if not len(lacks_capability_marker.args):
|
||||||
|
raise ValueError(
|
||||||
|
"lacks_capability marker must be provided with a capability to check"
|
||||||
|
)
|
||||||
|
|
||||||
|
for capability in lacks_capability_marker.args:
|
||||||
|
if not board_lacks_capability(capability):
|
||||||
|
pytest.skip(
|
||||||
|
"The board supports given capability: {required_capability}, skipping"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def board_name(request):
|
||||||
|
board_name = request.config.getoption("--board")
|
||||||
|
if not board_name:
|
||||||
|
raise ValueError("No board defined")
|
||||||
|
|
||||||
|
yield board_name
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def board_lacks_capability(board_name):
|
||||||
|
def func(capability: str):
|
||||||
|
if board_name:
|
||||||
|
if board_name not in board_capabilities:
|
||||||
|
raise ValueError(f"Unknown board {board_name}")
|
||||||
|
|
||||||
|
return capability not in board_capabilities[board_name]
|
||||||
|
return True
|
||||||
|
|
||||||
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def board_connection(request):
|
||||||
|
"""
|
||||||
|
Grabs the specified connection connection method, to be used ONLY for the initial connection. Everything after it HAS to be handled via Device Manager.
|
||||||
|
Ports WILL change throughout the tests, device manager can keep track of that and reconnect the board as needed.
|
||||||
|
"""
|
||||||
|
board_connection = request.config.getoption("--connection")
|
||||||
|
|
||||||
|
if not board_connection:
|
||||||
|
raise ValueError("No connection method defined")
|
||||||
|
|
||||||
|
yield board_connection
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TestConfig:
|
||||||
|
WIFI_SSID: str
|
||||||
|
WIFI_PASS: str
|
||||||
|
SWITCH_MODE_REBOOT_TIME: int
|
||||||
|
WIFI_CONNECTION_TIMEOUT: int
|
||||||
|
INVALID_WIFI_CONNECTION_TIMEOUT: int
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
WIFI_SSID: str,
|
||||||
|
WIFI_PASS: str,
|
||||||
|
SWITCH_MODE_REBOOT_TIME: int,
|
||||||
|
WIFI_CONNECTION_TIMEOUT: int,
|
||||||
|
INVALID_WIFI_CONNECTION_TIMEOUT: int,
|
||||||
|
):
|
||||||
|
self.WIFI_SSID = WIFI_SSID
|
||||||
|
self.WIFI_PASS = WIFI_PASS
|
||||||
|
self.SWITCH_MODE_REBOOT_TIME = int(SWITCH_MODE_REBOOT_TIME)
|
||||||
|
self.WIFI_CONNECTION_TIMEOUT = int(WIFI_CONNECTION_TIMEOUT)
|
||||||
|
self.INVALID_WIFI_CONNECTION_TIMEOUT = int(INVALID_WIFI_CONNECTION_TIMEOUT)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def config():
|
||||||
|
config = TestConfig(**dotenv.dotenv_values())
|
||||||
|
yield config
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def openiris_device_manager(board_connection, config):
|
||||||
|
manager = OpenIrisDeviceManager()
|
||||||
|
manager.get_device(board_connection, config)
|
||||||
|
yield manager
|
||||||
|
|
||||||
|
if manager._device:
|
||||||
|
manager._device.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def get_openiris_device(openiris_device_manager, config):
|
||||||
|
def func(port: str | None = None, _config: dict | None = None):
|
||||||
|
return openiris_device_manager.get_device(port, config or _config)
|
||||||
|
|
||||||
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def ensure_board_in_mode(openiris_device_manager, config):
|
||||||
|
"""
|
||||||
|
Given the OpenIrisDevice manager, grabs the current device and ensures it's in the required mode
|
||||||
|
|
||||||
|
if not, sends the command to switch and attempts reconnection if necessary, returning the device back
|
||||||
|
"""
|
||||||
|
supported_modes = ["wifi", "uvc"]
|
||||||
|
|
||||||
|
def func(mode, device):
|
||||||
|
if mode not in supported_modes:
|
||||||
|
raise ValueError(f"{mode} is not a supported mode")
|
||||||
|
|
||||||
|
command_result = device.send_command("get_device_mode")
|
||||||
|
if has_command_failed(command_result):
|
||||||
|
raise ValueError(f"Failed to get device mode, error: {command_result}")
|
||||||
|
|
||||||
|
current_mode = command_result["results"][0]["result"]["data"]["mode"].lower()
|
||||||
|
if mode == current_mode:
|
||||||
|
return device
|
||||||
|
|
||||||
|
old_ports = get_current_ports()
|
||||||
|
command_result = device.send_command("switch_mode", {"mode": mode})
|
||||||
|
if has_command_failed(command_result):
|
||||||
|
raise ValueError("Failed to switch mode, rerun the tests")
|
||||||
|
|
||||||
|
print("Rebooting the board after changing mode")
|
||||||
|
device.send_command("restart_device")
|
||||||
|
|
||||||
|
sleep_timeout = int(config.SWITCH_MODE_REBOOT_TIME)
|
||||||
|
print(
|
||||||
|
f"Sleeping for {sleep_timeout} seconds to allow the device to switch modes and boot up"
|
||||||
|
)
|
||||||
|
time.sleep(sleep_timeout)
|
||||||
|
new_ports = get_current_ports()
|
||||||
|
|
||||||
|
new_device = openiris_device_manager.get_device(
|
||||||
|
get_new_port(old_ports, new_ports), config
|
||||||
|
)
|
||||||
|
return new_device
|
||||||
|
|
||||||
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def after_session_cleanup(openiris_device_manager, config):
|
||||||
|
yield
|
||||||
|
|
||||||
|
print("Cleanup: Resetting the config and restarting device")
|
||||||
|
device = openiris_device_manager.get_device(config=config)
|
||||||
|
device.send_command("reset_config", {"section": "all"})
|
||||||
|
device.send_command("restart_device")
|
||||||
@@ -0,0 +1,543 @@
|
|||||||
|
import time
|
||||||
|
from tests.utils import has_command_failed, DetectPortChange
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_sending_invalid_command(get_openiris_device):
|
||||||
|
device = get_openiris_device()
|
||||||
|
command_result = device.send_command("some_invalid_command")
|
||||||
|
assert has_command_failed(command_result)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sending_invalid_command_with_payload(get_openiris_device):
|
||||||
|
device = get_openiris_device()
|
||||||
|
command_result = device.send_command("some_invalid_command", {"param": "invalid"})
|
||||||
|
assert has_command_failed(command_result)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ping_wired(get_openiris_device):
|
||||||
|
device = get_openiris_device()
|
||||||
|
|
||||||
|
command_result = device.send_command("ping")
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.has_capability("wired", "wireless")
|
||||||
|
def test_changing_mode_to_wired(get_openiris_device, ensure_board_in_mode, config):
|
||||||
|
device = get_openiris_device()
|
||||||
|
|
||||||
|
# let's make sure we're in the wireless mode first, if we're going to try changing it
|
||||||
|
device = ensure_board_in_mode("wifi", device)
|
||||||
|
with DetectPortChange() as port_selector:
|
||||||
|
command_result = device.send_command("switch_mode", {"mode": "uvc"})
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
device.send_command("restart_device")
|
||||||
|
time.sleep(config.SWITCH_MODE_REBOOT_TIME)
|
||||||
|
|
||||||
|
# and since we've changed the ports
|
||||||
|
device = get_openiris_device(port_selector.get_new_port())
|
||||||
|
# initial read to flush the logs first
|
||||||
|
device.send_command("get_device_mode")
|
||||||
|
result = device.send_command("get_device_mode")
|
||||||
|
assert not has_command_failed(result)
|
||||||
|
|
||||||
|
|
||||||
|
def test_changing_mode_same_mode(get_openiris_device):
|
||||||
|
device = get_openiris_device()
|
||||||
|
result = device.send_command("get_device_mode")
|
||||||
|
current_mode = result["results"][0]["result"]["data"]["mode"].lower()
|
||||||
|
command_result = device.send_command("switch_mode", {"mode": current_mode})
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
|
||||||
|
result = device.send_command("get_device_mode")
|
||||||
|
assert not has_command_failed(result)
|
||||||
|
assert result["results"][0]["result"]["data"]["mode"].lower() == current_mode
|
||||||
|
|
||||||
|
|
||||||
|
def test_changing_mode_invalid_mode(get_openiris_device):
|
||||||
|
device = get_openiris_device()
|
||||||
|
command_result = device.send_command("switch_mode", {"mode": "NOT SUPPORTED"})
|
||||||
|
assert has_command_failed(command_result)
|
||||||
|
|
||||||
|
|
||||||
|
def test_setting_mdns_name(get_openiris_device, ensure_board_in_mode, config):
|
||||||
|
def check_mdns_name(name: str):
|
||||||
|
command_result = device.send_command("get_mdns_name")
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
assert command_result["results"][0]["result"]["data"]["hostname"] == name
|
||||||
|
|
||||||
|
device = get_openiris_device()
|
||||||
|
device = ensure_board_in_mode("wifi", device)
|
||||||
|
first_name = "testname1"
|
||||||
|
second_name = "testname2"
|
||||||
|
# try setting the test mdns name first, just so we know the commands pass
|
||||||
|
command_result = device.send_command("set_mdns", {"hostname": first_name})
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
|
||||||
|
check_mdns_name(first_name)
|
||||||
|
|
||||||
|
command_result = device.send_command("set_mdns", {"hostname": second_name})
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
|
||||||
|
device.send_command("restart_device")
|
||||||
|
# let the board boot, wait till it connects
|
||||||
|
time.sleep(config.SWITCH_MODE_REBOOT_TIME)
|
||||||
|
check_mdns_name(second_name)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("payload", [{"name": "awd"}, {}])
|
||||||
|
def test_setting_mdns_name_invalid_payload(get_openiris_device, payload):
|
||||||
|
device = get_openiris_device()
|
||||||
|
command_result = device.send_command("set_mdns", payload)
|
||||||
|
assert has_command_failed(command_result)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.has_capability("wired", "wireless")
|
||||||
|
# make this to be has_capabilities instead
|
||||||
|
def test_reboot_command(get_openiris_device, ensure_board_in_mode, config):
|
||||||
|
device = ensure_board_in_mode("wifi", get_openiris_device())
|
||||||
|
|
||||||
|
command_result = device.send_command("switch_mode", {"mode": "uvc"})
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
|
||||||
|
# we're testing if rebooting actually triggers reboot
|
||||||
|
# so to be 100% sure of that, we're changing the mode to UVC and looking for new port
|
||||||
|
# which might be a little overkill kill and won't work on boards not supporting both modes
|
||||||
|
with DetectPortChange() as port_selector:
|
||||||
|
device.send_command("restart_device")
|
||||||
|
time.sleep(config.SWITCH_MODE_REBOOT_TIME)
|
||||||
|
|
||||||
|
assert port_selector.get_new_port()
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_serial(get_openiris_device):
|
||||||
|
device = get_openiris_device()
|
||||||
|
command_result = device.send_command("get_serial")
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
# test for response integrity as well to uphold the contract
|
||||||
|
assert "mac" in command_result["results"][0]["result"]["data"]
|
||||||
|
assert "serial" in command_result["results"][0]["result"]["data"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_who_am_i(get_openiris_device):
|
||||||
|
device = get_openiris_device()
|
||||||
|
command_result = device.send_command("get_who_am_i")
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
# test for response integrity as well to uphold the contract
|
||||||
|
assert "version" in command_result["results"][0]["result"]["data"]
|
||||||
|
assert "who_am_i" in command_result["results"][0]["result"]["data"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.has_capability("measure_current")
|
||||||
|
def test_get_led_current_supported(get_openiris_device):
|
||||||
|
device = get_openiris_device()
|
||||||
|
command_result = device.send_command("get_led_current")
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
assert "led_current_ma" in command_result["results"][0]["result"]["data"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.lacks_capability("measure_current")
|
||||||
|
def test_get_led_current_unsupported(get_openiris_device):
|
||||||
|
device = get_openiris_device()
|
||||||
|
command_result = device.send_command("get_led_current")
|
||||||
|
assert has_command_failed(command_result)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_led_duty_cycle(get_openiris_device):
|
||||||
|
device = get_openiris_device()
|
||||||
|
command_result = device.send_command("get_led_duty_cycle")
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
assert (
|
||||||
|
"led_external_pwm_duty_cycle" in command_result["results"][0]["result"]["data"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_led_duty_cycle(get_openiris_device):
|
||||||
|
device = get_openiris_device()
|
||||||
|
command_result = device.send_command("set_led_duty_cycle", {"dutyCycle": 0})
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
|
||||||
|
command_result = device.send_command("get_led_duty_cycle")
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
assert (
|
||||||
|
command_result["results"][0]["result"]["data"]["led_external_pwm_duty_cycle"]
|
||||||
|
== 0
|
||||||
|
)
|
||||||
|
|
||||||
|
command_result = device.send_command("set_led_duty_cycle", {"dutyCycle": 100})
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
|
||||||
|
command_result = device.send_command("get_led_duty_cycle")
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
assert (
|
||||||
|
command_result["results"][0]["result"]["data"]["led_external_pwm_duty_cycle"]
|
||||||
|
== 100
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"payload",
|
||||||
|
[
|
||||||
|
{},
|
||||||
|
{"dutyCycle": -1},
|
||||||
|
{"dutyCycle": 1.5},
|
||||||
|
{"dutyCycle": 150},
|
||||||
|
{"awd": 21},
|
||||||
|
{"dutyCycle": "21"},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_set_led_duty_cycle_invalid_payload(get_openiris_device, payload):
|
||||||
|
device = get_openiris_device()
|
||||||
|
command_result = device.send_command("set_led_duty_cycle", payload)
|
||||||
|
assert has_command_failed(command_result)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.has_capability("wireless")
|
||||||
|
def test_check_wifi_status(get_openiris_device, ensure_board_in_mode):
|
||||||
|
device = ensure_board_in_mode("wifi", get_openiris_device())
|
||||||
|
command_result = device.send_command("get_wifi_status")
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.has_capability("wireless")
|
||||||
|
def test_scan_networks(get_openiris_device, ensure_board_in_mode, config):
|
||||||
|
# this test might run after some tests that affect the network on the device
|
||||||
|
# which might prevent us from scanning and thus make the test fail, so we reset the config
|
||||||
|
device = get_openiris_device()
|
||||||
|
reset_command = device.send_command("reset_config", {"section": "all"})
|
||||||
|
assert not has_command_failed(reset_command)
|
||||||
|
|
||||||
|
with DetectPortChange() as port_selector:
|
||||||
|
device.send_command("restart_device")
|
||||||
|
time.sleep(config.SWITCH_MODE_REBOOT_TIME)
|
||||||
|
|
||||||
|
device = ensure_board_in_mode(
|
||||||
|
"wifi", get_openiris_device(port_selector.get_new_port())
|
||||||
|
)
|
||||||
|
command_result = device.send_command("scan_networks")
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
assert len(command_result["results"][0]["result"]["data"]["networks"]) != 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_config(get_openiris_device):
|
||||||
|
device = get_openiris_device()
|
||||||
|
command_result = device.send_command("get_config")
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
|
||||||
|
|
||||||
|
def test_reset_config_invalid_payload(get_openiris_device):
|
||||||
|
# to test the config, we can do two things. Set the mdns, get the config, reset it, get it again and compare
|
||||||
|
device = get_openiris_device()
|
||||||
|
reset_command = device.send_command("reset_config")
|
||||||
|
assert has_command_failed(reset_command)
|
||||||
|
|
||||||
|
|
||||||
|
def test_reset_config(get_openiris_device, config):
|
||||||
|
device = get_openiris_device()
|
||||||
|
command_result = device.send_command("set_mdns", {"hostname": "somedifferentname"})
|
||||||
|
assert not has_command_failed(command_result)
|
||||||
|
|
||||||
|
current_config = device.send_command("get_config")
|
||||||
|
assert not has_command_failed(current_config)
|
||||||
|
|
||||||
|
reset_command = device.send_command("reset_config", {"section": "all"})
|
||||||
|
assert not has_command_failed(reset_command)
|
||||||
|
|
||||||
|
# since the config was reset, but the data will still be held in memory, we need to reboot the device
|
||||||
|
with DetectPortChange():
|
||||||
|
device.send_command("restart_device")
|
||||||
|
time.sleep(config.SWITCH_MODE_REBOOT_TIME)
|
||||||
|
|
||||||
|
new_config = device.send_command("get_config")
|
||||||
|
assert not has_command_failed(new_config)
|
||||||
|
|
||||||
|
assert not new_config == current_config
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.has_capability("wireless")
|
||||||
|
def test_set_wifi(get_openiris_device, ensure_board_in_mode, config):
|
||||||
|
# since we want to test actual connection to the network, let's reset the device and reboot it
|
||||||
|
device = get_openiris_device()
|
||||||
|
reset_command = device.send_command("reset_config", {"section": "all"})
|
||||||
|
assert not has_command_failed(reset_command)
|
||||||
|
|
||||||
|
with DetectPortChange():
|
||||||
|
device.send_command("restart_device")
|
||||||
|
time.sleep(config.SWITCH_MODE_REBOOT_TIME)
|
||||||
|
|
||||||
|
# now that the config is clear, let's try setting the wifi
|
||||||
|
device = ensure_board_in_mode("wifi", device)
|
||||||
|
params = {
|
||||||
|
"name": "main",
|
||||||
|
"ssid": config.WIFI_SSID,
|
||||||
|
"password": config.WIFI_PASS,
|
||||||
|
"channel": 0,
|
||||||
|
"power": 0,
|
||||||
|
}
|
||||||
|
set_wifi_result = device.send_command("set_wifi", params)
|
||||||
|
assert not has_command_failed(set_wifi_result)
|
||||||
|
|
||||||
|
# now, let's force connection and check if it worked
|
||||||
|
connect_wifi_result = device.send_command("connect_wifi")
|
||||||
|
assert not -has_command_failed(connect_wifi_result)
|
||||||
|
time.sleep(config.WIFI_CONNECTION_TIMEOUT) # and let it try to for some time
|
||||||
|
|
||||||
|
wifi_status_command = device.send_command("get_wifi_status")
|
||||||
|
assert not has_command_failed(wifi_status_command)
|
||||||
|
assert wifi_status_command["results"][0]["result"]["data"]["status"] == "connected"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.has_capability("wireless")
|
||||||
|
def test_set_wifi_invalid_network(get_openiris_device, ensure_board_in_mode, config):
|
||||||
|
device = ensure_board_in_mode("wifi", get_openiris_device())
|
||||||
|
params = {
|
||||||
|
"name": "main",
|
||||||
|
"ssid": "PleaseDontBeARealNetwork",
|
||||||
|
"password": "AndThePasswordIsFake",
|
||||||
|
"channel": 0,
|
||||||
|
"power": 0,
|
||||||
|
}
|
||||||
|
set_wifi_result = device.send_command("set_wifi", params)
|
||||||
|
# even if the network is fake, we should not fail to set it
|
||||||
|
assert not has_command_failed(set_wifi_result)
|
||||||
|
|
||||||
|
device.send_command("connect_wifi")
|
||||||
|
|
||||||
|
time.sleep(
|
||||||
|
config.INVALID_WIFI_CONNECTION_TIMEOUT
|
||||||
|
) # and let it try to for some time
|
||||||
|
|
||||||
|
wifi_status_command = device.send_command("get_wifi_status")
|
||||||
|
# the command should not fail as well, but we should get an error result
|
||||||
|
assert not has_command_failed(wifi_status_command)
|
||||||
|
assert wifi_status_command["results"][0]["result"]["data"]["status"] == "error"
|
||||||
|
# and not to break other tests, clean up
|
||||||
|
device.send_command("reset_config", {"section": "all"})
|
||||||
|
device.send_command("restart_device")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.has_capability("wireless")
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"payload",
|
||||||
|
(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
"ssid": "PleaseDontBeARealNetwork",
|
||||||
|
"password": "AndThePasswordIsFake",
|
||||||
|
"channel": 0,
|
||||||
|
"power": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "IaintGotNoNameAndIMustConnect",
|
||||||
|
"password": "AndThePasswordIsFake",
|
||||||
|
"channel": 0,
|
||||||
|
"power": 0,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_set_wifi_invalid_payload(ensure_board_in_mode, get_openiris_device, payload):
|
||||||
|
device = ensure_board_in_mode("wifi", get_openiris_device())
|
||||||
|
set_wifi_result = device.send_command("set_wifi", payload)
|
||||||
|
# even if the network is fake, we should not fail to set it
|
||||||
|
assert has_command_failed(set_wifi_result)
|
||||||
|
# and not to break other tests, clean up
|
||||||
|
device.send_command("reset_config", {"section": "all"})
|
||||||
|
device.send_command("restart_device")
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_main_wifi_network(ensure_board_in_mode, get_openiris_device, config):
|
||||||
|
# now that the config is clear, let's try setting the wifi
|
||||||
|
device = ensure_board_in_mode("wifi", get_openiris_device())
|
||||||
|
params1 = {
|
||||||
|
"name": "main",
|
||||||
|
"ssid": "Nada",
|
||||||
|
"password": "Nuuh",
|
||||||
|
"channel": 0,
|
||||||
|
"power": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
params2 = {
|
||||||
|
**params1,
|
||||||
|
"ssid": config.WIFI_SSID,
|
||||||
|
"password": config.WIFI_PASS,
|
||||||
|
}
|
||||||
|
|
||||||
|
set_wifi_result = device.send_command("set_wifi", params1)
|
||||||
|
assert not has_command_failed(set_wifi_result)
|
||||||
|
|
||||||
|
set_wifi_result = device.send_command("set_wifi", params2)
|
||||||
|
assert not has_command_failed(set_wifi_result)
|
||||||
|
# and not to break other tests, clean up
|
||||||
|
device.send_command("reset_config", {"section": "all"})
|
||||||
|
device.send_command("restart_device")
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_wifi_add_another_network(ensure_board_in_mode, get_openiris_device):
|
||||||
|
device = ensure_board_in_mode("wifi", get_openiris_device())
|
||||||
|
params = {
|
||||||
|
"name": "anotherNetwork",
|
||||||
|
"ssid": "PleaseDontBeARealNetwork",
|
||||||
|
"password": "AndThePassowrdIsFake",
|
||||||
|
"channel": 0,
|
||||||
|
"power": 0,
|
||||||
|
}
|
||||||
|
set_wifi_result = device.send_command("set_wifi", params)
|
||||||
|
assert not has_command_failed(set_wifi_result)
|
||||||
|
# and not to break other tests, clean up
|
||||||
|
device.send_command("reset_config", {"section": "all"})
|
||||||
|
device.send_command("restart_device")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"payload",
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"ssid": "testAP",
|
||||||
|
"password": "12345678",
|
||||||
|
"channel": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ssid": "testAP",
|
||||||
|
"channel": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ssid": "testAP",
|
||||||
|
"password": "12345678",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ssid": "testAP",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"password": "12345678",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_update_ap_wifi(ensure_board_in_mode, get_openiris_device, payload):
|
||||||
|
device = ensure_board_in_mode("wifi", get_openiris_device())
|
||||||
|
result = device.send_command("update_ap_wifi", payload)
|
||||||
|
assert not has_command_failed(result)
|
||||||
|
# and not to break other tests, clean up
|
||||||
|
device.send_command("reset_config", {"section": "all"})
|
||||||
|
device.send_command("restart_device")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"payload",
|
||||||
|
(
|
||||||
|
{}, # completely empty payload
|
||||||
|
{
|
||||||
|
"channel": 2
|
||||||
|
}, # technically valid payload, but we're missing the network name,
|
||||||
|
{
|
||||||
|
"name": "IAMNOTTHERE",
|
||||||
|
"channel": 2,
|
||||||
|
}, # None-existent network
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@pytest.mark.has_capability("wireless")
|
||||||
|
def test_update_wifi_command_fail(ensure_board_in_mode, get_openiris_device, payload):
|
||||||
|
device = ensure_board_in_mode("wifi", get_openiris_device())
|
||||||
|
result = device.send_command("update_wifi", payload)
|
||||||
|
assert has_command_failed(result)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"payload",
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"name": "anotherNetwork",
|
||||||
|
"ssid": "WEUPDATEDTHESSID",
|
||||||
|
"password": "ACOMPLETELYDIFFERENTPASS",
|
||||||
|
"channel": 1,
|
||||||
|
"power": 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anotherNetwork",
|
||||||
|
"password": "ACOMPLETELYDIFFERENTPASS",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anotherNetwork",
|
||||||
|
"ssid": "WEUPDATEDTHESSID",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anotherNetwork",
|
||||||
|
"channel": 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anotherNetwork",
|
||||||
|
"power": 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@pytest.mark.has_capability("wireless")
|
||||||
|
def test_update_wifi_command(ensure_board_in_mode, get_openiris_device, payload):
|
||||||
|
device = ensure_board_in_mode("wifi", get_openiris_device())
|
||||||
|
params = {
|
||||||
|
"name": "anotherNetwork",
|
||||||
|
"ssid": "PleaseDontBeARealNetwork",
|
||||||
|
"password": "AndThePasswordIsFake",
|
||||||
|
"channel": 0,
|
||||||
|
"power": 0,
|
||||||
|
}
|
||||||
|
set_wifi_result = device.send_command("set_wifi", params)
|
||||||
|
assert not has_command_failed(set_wifi_result)
|
||||||
|
|
||||||
|
device = ensure_board_in_mode("wifi", get_openiris_device())
|
||||||
|
result = device.send_command("update_wifi", payload)
|
||||||
|
assert not has_command_failed(result)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"payload",
|
||||||
|
(
|
||||||
|
{},
|
||||||
|
{"name": ""},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@pytest.mark.has_capability("wireless")
|
||||||
|
def test_delete_network_fail(ensure_board_in_mode, get_openiris_device, payload):
|
||||||
|
device = ensure_board_in_mode("wifi", get_openiris_device())
|
||||||
|
result = device.send_command("delete_network", payload)
|
||||||
|
assert has_command_failed(result)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("payload", ({"name": "main"}, {"name": "NOTANETWORK"}))
|
||||||
|
@pytest.mark.has_capability("wireless")
|
||||||
|
def test_delete_network(ensure_board_in_mode, get_openiris_device, payload):
|
||||||
|
device = ensure_board_in_mode("wifi", get_openiris_device())
|
||||||
|
result = device.send_command("delete_network", payload)
|
||||||
|
assert not has_command_failed(result)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"payload",
|
||||||
|
(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
"vlip": 0,
|
||||||
|
"hflip": 0,
|
||||||
|
"framesize": 5,
|
||||||
|
"quality": 7,
|
||||||
|
"brightness": 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"vlip": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hflip": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"framesize": 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brightness": 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_update_camera(get_openiris_device, payload):
|
||||||
|
device = get_openiris_device()
|
||||||
|
result = device.send_command("update_camera", payload)
|
||||||
|
assert not has_command_failed(result)
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import time
|
||||||
|
import serial.tools.list_ports
|
||||||
|
from tools.openiris_device import OpenIrisDevice
|
||||||
|
|
||||||
|
OPENIRIS_DEVICE = None
|
||||||
|
|
||||||
|
|
||||||
|
class OpenIrisDeviceManager:
|
||||||
|
def __init__(self):
|
||||||
|
self._device: OpenIrisDevice | None = None
|
||||||
|
self.stored_ports = []
|
||||||
|
self._current_port: str | None = None
|
||||||
|
|
||||||
|
def get_device(self, port: str | None = None, config=None) -> OpenIrisDevice:
|
||||||
|
"""
|
||||||
|
Returns the current OpenIrisDevice connection helper
|
||||||
|
|
||||||
|
if the port changed from the one given previously, it will attempt to reconnect
|
||||||
|
if no device exists, we will create one and try to connect
|
||||||
|
|
||||||
|
This helper is designed to be used within a session long fixture
|
||||||
|
"""
|
||||||
|
if not port and not self._device:
|
||||||
|
raise ValueError("No device connected yet, provide a port first")
|
||||||
|
|
||||||
|
# I'm not sure if I like this approach
|
||||||
|
# maybe I need to rethink this fixture
|
||||||
|
current_ports = get_current_ports()
|
||||||
|
new_port = get_new_port(self.stored_ports, current_ports)
|
||||||
|
if new_port is not None:
|
||||||
|
self.stored_ports = current_ports
|
||||||
|
|
||||||
|
if not port:
|
||||||
|
port = new_port
|
||||||
|
|
||||||
|
if port and port != self._current_port:
|
||||||
|
print(f"Port changed from {self._current_port} to {port}, reconnecting...")
|
||||||
|
self._current_port = port
|
||||||
|
|
||||||
|
if self._device:
|
||||||
|
self._device.disconnect()
|
||||||
|
self._device = None
|
||||||
|
|
||||||
|
self._device = OpenIrisDevice(port, False, False)
|
||||||
|
self._device.connect()
|
||||||
|
time.sleep(config.SWITCH_MODE_REBOOT_TIME)
|
||||||
|
|
||||||
|
return self._device
|
||||||
|
|
||||||
|
|
||||||
|
def has_command_failed(result) -> bool:
|
||||||
|
return "error" in result or result["results"][0]["result"]["status"] != "success"
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_ports() -> list[str]:
|
||||||
|
return [port.name for port in serial.tools.list_ports.comports()]
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_port(old_ports, new_ports) -> str:
|
||||||
|
if ports_diff := list(set(new_ports) - set(old_ports)):
|
||||||
|
return ports_diff[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class DetectPortChange:
|
||||||
|
def __init__(self):
|
||||||
|
self.old_ports = []
|
||||||
|
self.new_ports = []
|
||||||
|
|
||||||
|
def __enter__(self, *args, **kwargs):
|
||||||
|
self.old_ports = get_current_ports()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *args, **kwargs):
|
||||||
|
self.new_ports = get_current_ports()
|
||||||
|
|
||||||
|
def get_new_port(self):
|
||||||
|
return get_new_port(self.old_ports, self.new_ports)
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
import time
|
||||||
|
import json
|
||||||
|
import serial
|
||||||
|
|
||||||
|
|
||||||
|
class OpenIrisDevice:
|
||||||
|
def __init__(self, port: str, debug: bool, debug_commands: bool):
|
||||||
|
self.port = port
|
||||||
|
self.debug = debug
|
||||||
|
self.debug_commands = debug_commands
|
||||||
|
self.connection: serial.Serial | None = None
|
||||||
|
self.connected = False
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.connected = self.__connect()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
self.__disconnect()
|
||||||
|
self.connected = False
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
self.connected = self.__connect()
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
self.__disconnect()
|
||||||
|
self.connected = False
|
||||||
|
|
||||||
|
def __connect(self) -> bool:
|
||||||
|
print(f"📡 Connecting directly to {self.port}...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.connection = serial.Serial(
|
||||||
|
port=self.port, baudrate=115200, timeout=1, write_timeout=1
|
||||||
|
)
|
||||||
|
self.connection.dtr = False
|
||||||
|
self.connection.rts = False
|
||||||
|
print(f"✅ Connected to the device on {self.port}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Failed to connect to {self.port}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __disconnect(self):
|
||||||
|
if self.connection and self.connection.is_open:
|
||||||
|
self.connection.close()
|
||||||
|
print(f"🔌 Disconnected from {self.port}")
|
||||||
|
|
||||||
|
def __check_if_response_is_complete(self, response) -> dict | None:
|
||||||
|
try:
|
||||||
|
if self.debug:
|
||||||
|
print(f"\nCHECKING: {response} \n")
|
||||||
|
return json.loads(response)
|
||||||
|
except ValueError:
|
||||||
|
if self.debug:
|
||||||
|
print("\nCHECK FAILED\n")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __read_response(self, timeout: int | None = None) -> dict | None:
|
||||||
|
# we can try and retrieve the response now.
|
||||||
|
# it should be more or less immediate, but some commands may take longer
|
||||||
|
# so we gotta timeout
|
||||||
|
timeout = timeout if timeout is not None else 15
|
||||||
|
start_time = time.time()
|
||||||
|
response_buffer = ""
|
||||||
|
while time.time() - start_time < timeout:
|
||||||
|
if self.connection.in_waiting:
|
||||||
|
packet = self.connection.read_all().decode("utf-8", errors="ignore")
|
||||||
|
if self.debug and packet.strip():
|
||||||
|
print(f"Received: {packet}")
|
||||||
|
print("-" * 10)
|
||||||
|
print(f"Current buffer: {response_buffer}")
|
||||||
|
print("-" * 10)
|
||||||
|
|
||||||
|
# we can't rely on new lines to detect if we're done
|
||||||
|
# nor can we assume that we're always gonna get valid json response
|
||||||
|
# but we can assume that if we're to get a valid response, it's gonna be json
|
||||||
|
# so we can start actually building the buffer only when
|
||||||
|
# some part of the packet starts with "{", and start building from there
|
||||||
|
# we can assume that no further data will be returned, so we can validate
|
||||||
|
# right after receiving the last packet
|
||||||
|
if (not response_buffer and "{" in packet) or response_buffer:
|
||||||
|
# assume we just started building the buffer and we've received the first packet
|
||||||
|
|
||||||
|
# alternative approach in case this doesn't work - we're always sending a valid json
|
||||||
|
# so we can start building the buffer from the first packet and keep trying to find the
|
||||||
|
# starting and ending brackets, extract that part and validate, if the message is complete, return
|
||||||
|
if not response_buffer:
|
||||||
|
starting_idx = packet.find("{")
|
||||||
|
response_buffer = packet[starting_idx:]
|
||||||
|
else:
|
||||||
|
response_buffer += packet
|
||||||
|
|
||||||
|
# and once we get something, we can validate if it's a valid json
|
||||||
|
if parsed_response := self.__check_if_response_is_complete(
|
||||||
|
response_buffer
|
||||||
|
):
|
||||||
|
return parsed_response
|
||||||
|
else:
|
||||||
|
# if it's not a valid response just yet,
|
||||||
|
# we might've stumbled a case where we got a valid response
|
||||||
|
# but to the end of it, we've got some leftovers attached
|
||||||
|
# so, we can try and find the ending
|
||||||
|
ending_idx = response_buffer[::-1].find("}")
|
||||||
|
if reparsed_response := self.__check_if_response_is_complete(
|
||||||
|
response_buffer[: len(response_buffer) - ending_idx]
|
||||||
|
):
|
||||||
|
return reparsed_response
|
||||||
|
else:
|
||||||
|
time.sleep(0.1)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def is_connected(self) -> bool:
|
||||||
|
return self.connected
|
||||||
|
|
||||||
|
def send_command(
|
||||||
|
self, command: str, params: dict | None = None, timeout: int | None = None
|
||||||
|
) -> dict:
|
||||||
|
if not self.connection or not self.connection.is_open:
|
||||||
|
return {"error": "Device Not Connected"}
|
||||||
|
|
||||||
|
cmd_obj = {"commands": [{"command": command}]}
|
||||||
|
if params:
|
||||||
|
cmd_obj["commands"][0]["data"] = params
|
||||||
|
|
||||||
|
# we're expecting the json string to end with a new line
|
||||||
|
# to signify we've finished sending the command
|
||||||
|
cmd_str = json.dumps(cmd_obj) + "\n"
|
||||||
|
try:
|
||||||
|
# clean it out first, just to be sure we're starting fresh
|
||||||
|
self.connection.reset_input_buffer()
|
||||||
|
if self.debug or self.debug_commands:
|
||||||
|
print(f"Sending command: {cmd_str}")
|
||||||
|
self.connection.write(cmd_str.encode())
|
||||||
|
response = self.__read_response(timeout)
|
||||||
|
|
||||||
|
if self.debug:
|
||||||
|
print(f"Received response: {response}")
|
||||||
|
|
||||||
|
return response or {"error": "Command timeout"}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": f"Communication error: {e}"}
|
||||||
+14
-123
@@ -5,14 +5,14 @@
|
|||||||
# ///
|
# ///
|
||||||
|
|
||||||
|
|
||||||
import json
|
|
||||||
import time
|
import time
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
import serial
|
|
||||||
import string
|
import string
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from openiris_device import OpenIrisDevice
|
||||||
|
|
||||||
|
|
||||||
def is_back(choice: str):
|
def is_back(choice: str):
|
||||||
return choice.lower() in ["back", "b", "exit"]
|
return choice.lower() in ["back", "b", "exit"]
|
||||||
@@ -79,122 +79,6 @@ class Menu(SubMenu):
|
|||||||
super().__init__(title, context, parent_menu)
|
super().__init__(title, context, parent_menu)
|
||||||
|
|
||||||
|
|
||||||
class OpenIrisDevice:
|
|
||||||
def __init__(self, port: str, debug: bool, debug_commands: bool):
|
|
||||||
self.port = port
|
|
||||||
self.debug = debug
|
|
||||||
self.debug_commands = debug_commands
|
|
||||||
self.connection: serial.Serial | None = None
|
|
||||||
self.connected = False
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self.connected = self.__connect()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, type, value, traceback):
|
|
||||||
self.__disconnect()
|
|
||||||
|
|
||||||
def __connect(self) -> bool:
|
|
||||||
print(f"📡 Connecting directly to {self.port}...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.connection = serial.Serial(
|
|
||||||
port=self.port, baudrate=115200, timeout=1, write_timeout=1
|
|
||||||
)
|
|
||||||
print(f"✅ Connected to the device on {self.port}")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Failed to connect to {self.port}: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __disconnect(self):
|
|
||||||
if self.connection and self.connection.is_open:
|
|
||||||
self.connection.close()
|
|
||||||
print(f"🔌 Disconnected from {self.port}")
|
|
||||||
|
|
||||||
def __check_if_response_is_complete(self, response) -> dict | None:
|
|
||||||
try:
|
|
||||||
return json.loads(response)
|
|
||||||
except ValueError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def __read_response(self, timeout: int | None = None) -> dict | None:
|
|
||||||
# we can try and retrieve the response now.
|
|
||||||
# it should be more or less immediate, but some commands may take longer
|
|
||||||
# so we gotta timeout
|
|
||||||
timeout = timeout if timeout is not None else 15
|
|
||||||
start_time = time.time()
|
|
||||||
response_buffer = ""
|
|
||||||
while time.time() - start_time < timeout:
|
|
||||||
if self.connection.in_waiting:
|
|
||||||
packet = self.connection.read_all().decode("utf-8", errors="ignore")
|
|
||||||
if self.debug and packet.strip():
|
|
||||||
print(f"Received: {packet}")
|
|
||||||
print("-" * 10)
|
|
||||||
print(f"Current buffer: {response_buffer}")
|
|
||||||
print("-" * 10)
|
|
||||||
|
|
||||||
# we can't rely on new lines to detect if we're done
|
|
||||||
# nor can we assume that we're always gonna get valid json response
|
|
||||||
# but we can assume that if we're to get a valid response, it's gonna be json
|
|
||||||
# so we can start actually building the buffer only when
|
|
||||||
# some part of the packet starts with "{", and start building from there
|
|
||||||
# we can assume that no further data will be returned, so we can validate
|
|
||||||
# right after receiving the last packet
|
|
||||||
if (not response_buffer and "{" in packet) or response_buffer:
|
|
||||||
# assume we just started building the buffer and we've received the first packet
|
|
||||||
|
|
||||||
# alternative approach in case this doesn't work - we're always sending a valid json
|
|
||||||
# so we can start building the buffer from the first packet and keep trying to find the
|
|
||||||
# starting and ending brackets, extract that part and validate, if the message is complete, return
|
|
||||||
if not response_buffer:
|
|
||||||
starting_idx = packet.find("{")
|
|
||||||
response_buffer = packet[starting_idx:]
|
|
||||||
else:
|
|
||||||
response_buffer += packet
|
|
||||||
|
|
||||||
# and once we get something, we can validate if it's a valid json
|
|
||||||
if parsed_response := self.__check_if_response_is_complete(
|
|
||||||
response_buffer
|
|
||||||
):
|
|
||||||
return parsed_response
|
|
||||||
else:
|
|
||||||
time.sleep(0.1)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def is_connected(self) -> bool:
|
|
||||||
return self.connected
|
|
||||||
|
|
||||||
def send_command(
|
|
||||||
self, command: str, params: dict | None = None, timeout: int | None = None
|
|
||||||
) -> dict:
|
|
||||||
if not self.connection or not self.connection.is_open:
|
|
||||||
return {"error": "Device Not Connected"}
|
|
||||||
|
|
||||||
cmd_obj = {"commands": [{"command": command}]}
|
|
||||||
if params:
|
|
||||||
cmd_obj["commands"][0]["data"] = params
|
|
||||||
|
|
||||||
# we're expecting the json string to end with a new line
|
|
||||||
# to signify we've finished sending the command
|
|
||||||
cmd_str = json.dumps(cmd_obj) + "\n"
|
|
||||||
try:
|
|
||||||
# clean it out first, just to be sure we're starting fresh
|
|
||||||
self.connection.reset_input_buffer()
|
|
||||||
if self.debug or self.debug_commands:
|
|
||||||
print(f"Sending command: {cmd_str}")
|
|
||||||
self.connection.write(cmd_str.encode())
|
|
||||||
response = self.__read_response(timeout)
|
|
||||||
|
|
||||||
if self.debug:
|
|
||||||
print(f"Received response: {response}")
|
|
||||||
|
|
||||||
return response or {"error": "Command timeout"}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return {"error": f"Communication error: {e}"}
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class WiFiNetwork:
|
class WiFiNetwork:
|
||||||
ssid: str
|
ssid: str
|
||||||
@@ -314,6 +198,7 @@ def get_serial_info(device: OpenIrisDevice) -> dict:
|
|||||||
"mac": response["results"][0]["result"]["data"]["mac"],
|
"mac": response["results"][0]["result"]["data"]["mac"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_device_info(device: OpenIrisDevice) -> dict:
|
def get_device_info(device: OpenIrisDevice) -> dict:
|
||||||
response = device.send_command("get_who_am_i")
|
response = device.send_command("get_who_am_i")
|
||||||
|
|
||||||
@@ -321,7 +206,10 @@ def get_device_info(device: OpenIrisDevice) -> dict:
|
|||||||
print(f"❌ Failed to get device info: {response['error']}")
|
print(f"❌ Failed to get device info: {response['error']}")
|
||||||
return {"who_am_i": None, "version": None}
|
return {"who_am_i": None, "version": None}
|
||||||
|
|
||||||
return {"who_am_i": response["results"][0]["result"]["data"]["who_am_i"], "version": response["results"][0]["result"]["data"]["version"]}
|
return {
|
||||||
|
"who_am_i": response["results"][0]["result"]["data"]["who_am_i"],
|
||||||
|
"version": response["results"][0]["result"]["data"]["version"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_wifi_status(device: OpenIrisDevice) -> dict:
|
def get_wifi_status(device: OpenIrisDevice) -> dict:
|
||||||
@@ -339,7 +227,9 @@ def get_led_current(device: OpenIrisDevice) -> dict:
|
|||||||
print(f"❌ Failed to get LED current: {response}")
|
print(f"❌ Failed to get LED current: {response}")
|
||||||
return {"led_current_ma": "unknown"}
|
return {"led_current_ma": "unknown"}
|
||||||
|
|
||||||
return {"led_current_ma": response["results"][0]["result"]["data"]["led_current_ma"]}
|
return {
|
||||||
|
"led_current_ma": response["results"][0]["result"]["data"]["led_current_ma"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def configure_device_name(device: OpenIrisDevice, *args, **kwargs):
|
def configure_device_name(device: OpenIrisDevice, *args, **kwargs):
|
||||||
@@ -479,7 +369,6 @@ def get_settings_summary(device: OpenIrisDevice, *args, **kwargs):
|
|||||||
if ver:
|
if ver:
|
||||||
print(f"🧭 Version: {ver}")
|
print(f"🧭 Version: {ver}")
|
||||||
|
|
||||||
|
|
||||||
wifi = summary.get("WiFi", {}).get("wifi_status", {})
|
wifi = summary.get("WiFi", {}).get("wifi_status", {})
|
||||||
if wifi:
|
if wifi:
|
||||||
status = wifi.get("status", "unknown")
|
status = wifi.get("status", "unknown")
|
||||||
@@ -487,6 +376,7 @@ def get_settings_summary(device: OpenIrisDevice, *args, **kwargs):
|
|||||||
configured = wifi.get("networks_configured", 0)
|
configured = wifi.get("networks_configured", 0)
|
||||||
print(f"📶 WiFi: {status} | IP: {ip} | Networks configured: {configured}")
|
print(f"📶 WiFi: {status} | IP: {ip} | Networks configured: {configured}")
|
||||||
|
|
||||||
|
|
||||||
def restart_device_command(device: OpenIrisDevice, *args, **kwargs):
|
def restart_device_command(device: OpenIrisDevice, *args, **kwargs):
|
||||||
print("🔄 Restarting device...")
|
print("🔄 Restarting device...")
|
||||||
response = device.send_command("restart_device")
|
response = device.send_command("restart_device")
|
||||||
@@ -497,6 +387,7 @@ def restart_device_command(device: OpenIrisDevice, *args, **kwargs):
|
|||||||
print("✅ Device restart command sent successfully")
|
print("✅ Device restart command sent successfully")
|
||||||
print("💡 Please wait a few seconds for the device to reboot")
|
print("💡 Please wait a few seconds for the device to reboot")
|
||||||
|
|
||||||
|
|
||||||
def scan_networks(wifi_scanner: WiFiScanner, *args, **kwargs):
|
def scan_networks(wifi_scanner: WiFiScanner, *args, **kwargs):
|
||||||
use_custom_timeout = (
|
use_custom_timeout = (
|
||||||
input("Should we use a custom scan timeout? (y/n)\n>> ").strip().lower() == "y"
|
input("Should we use a custom scan timeout? (y/n)\n>> ").strip().lower() == "y"
|
||||||
@@ -626,7 +517,7 @@ def automatic_wifi_configuration(
|
|||||||
ip = None
|
ip = None
|
||||||
last_status = None
|
last_status = None
|
||||||
while time.time() - start < timeout_s:
|
while time.time() - start < timeout_s:
|
||||||
status = get_wifi_status(device).get("wifi_status", {})
|
status = get_wifi_status(device).get("wifi_status") or {}
|
||||||
last_status = status
|
last_status = status
|
||||||
ip = status.get("ip_address")
|
ip = status.get("ip_address")
|
||||||
if ip and ip not in ("0.0.0.0", "", None):
|
if ip and ip not in ("0.0.0.0", "", None):
|
||||||
@@ -654,7 +545,7 @@ def attempt_wifi_connection(device: OpenIrisDevice, *args, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
def check_wifi_status(device: OpenIrisDevice, *args, **kwargs):
|
def check_wifi_status(device: OpenIrisDevice, *args, **kwargs):
|
||||||
status = get_wifi_status(device).get("wifi_status")
|
status = get_wifi_status(device).get("wifi_status") or {}
|
||||||
print(f"📶 WiFi Status: {status.get('status', 'Unknown')}")
|
print(f"📶 WiFi Status: {status.get('status', 'Unknown')}")
|
||||||
if ip_address := status.get("ip_address"):
|
if ip_address := status.get("ip_address"):
|
||||||
print(f"🌐 IP Address: {ip_address}")
|
print(f"🌐 IP Address: {ip_address}")
|
||||||
|
|||||||
+134
-24
@@ -1,17 +1,29 @@
|
|||||||
import os
|
import os
|
||||||
import difflib
|
import difflib
|
||||||
|
import shutil
|
||||||
import argparse
|
import argparse
|
||||||
from typing import Dict, Optional, List
|
from typing import Dict, Optional, List
|
||||||
|
|
||||||
HEADER_COLOR = "\033[95m"
|
HEADER_COLOR = "\033[95m"
|
||||||
OKGREEN = '\033[92m'
|
OKGREEN = "\033[92m"
|
||||||
WARNING = '\033[93m'
|
WARNING = "\033[93m"
|
||||||
OKBLUE = '\033[94m'
|
OKBLUE = "\033[94m"
|
||||||
ENDC = '\033[0m'
|
ENDC = "\033[0m"
|
||||||
|
|
||||||
BOARDS_DIR_NAME = "boards"
|
BOARDS_DIR_NAME = "boards"
|
||||||
SDKCONFIG_DEFAULTS_FILENAME = "sdkconfig.base_defaults"
|
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:
|
def get_root_path() -> str:
|
||||||
return os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]
|
return os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]
|
||||||
|
|
||||||
@@ -20,6 +32,11 @@ def get_boards_root() -> str:
|
|||||||
return os.path.join(get_root_path(), BOARDS_DIR_NAME)
|
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]:
|
def enumerate_board_configs() -> Dict[str, str]:
|
||||||
"""Walk the boards directory and build a mapping of board names to absolute file paths.
|
"""Walk the boards directory and build a mapping of board names to absolute file paths.
|
||||||
|
|
||||||
@@ -47,23 +64,36 @@ def enumerate_board_configs() -> Dict[str, str]:
|
|||||||
|
|
||||||
BOARD_CONFIGS = enumerate_board_configs()
|
BOARD_CONFIGS = enumerate_board_configs()
|
||||||
|
|
||||||
|
|
||||||
def build_arg_parser() -> argparse.ArgumentParser:
|
def build_arg_parser() -> argparse.ArgumentParser:
|
||||||
p = argparse.ArgumentParser()
|
p = argparse.ArgumentParser()
|
||||||
p.add_argument("-b", "--board", help="Board name (run with --list to see options). Flexible: accepts path-like or partial if unique.")
|
p.add_argument(
|
||||||
p.add_argument("--list", help="List discovered boards and exit", action="store_true")
|
"-b",
|
||||||
|
"--board",
|
||||||
|
help="Board name (run with --list to see options). Flexible: accepts path-like or partial if unique.",
|
||||||
|
)
|
||||||
|
p.add_argument(
|
||||||
|
"--list", help="List discovered boards and exit", action="store_true"
|
||||||
|
)
|
||||||
p.add_argument("--dry-run", help="Dry run, won't modify files", action="store_true")
|
p.add_argument("--dry-run", help="Dry run, won't modify files", action="store_true")
|
||||||
p.add_argument("--diff", help="Show the difference between base config and selected board", action="store_true")
|
p.add_argument(
|
||||||
|
"--diff",
|
||||||
|
help="Show the difference between base config and selected board",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
p.add_argument("--ssid", help="Set the WiFi SSID", type=str, default="")
|
p.add_argument("--ssid", help="Set the WiFi SSID", type=str, default="")
|
||||||
p.add_argument("--password", help="Set the WiFi password", type=str, default="")
|
p.add_argument("--password", help="Set the WiFi password", type=str, default="")
|
||||||
p.add_argument("--clear-wifi", help="Clear WiFi credentials", action="store_true")
|
p.add_argument("--clear-wifi", help="Clear WiFi credentials", action="store_true")
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
|
||||||
def list_boards():
|
def list_boards():
|
||||||
print("Discovered boards:")
|
print("Discovered boards:")
|
||||||
width = max((len(k) for k in BOARD_CONFIGS), default=0)
|
width = max((len(k) for k in BOARD_CONFIGS), default=0)
|
||||||
for name, path in sorted(BOARD_CONFIGS.items()):
|
for name, path in sorted(BOARD_CONFIGS.items()):
|
||||||
print(f" {name.ljust(width)} -> {os.path.relpath(path, get_root_path())}")
|
print(f" {name.ljust(width)} -> {os.path.relpath(path, get_root_path())}")
|
||||||
|
|
||||||
|
|
||||||
def _suggest_boards(partial: str) -> List[str]:
|
def _suggest_boards(partial: str) -> List[str]:
|
||||||
if not partial:
|
if not partial:
|
||||||
return []
|
return []
|
||||||
@@ -75,18 +105,23 @@ def _suggest_boards(partial: str) -> List[str]:
|
|||||||
choices = list(BOARD_CONFIGS.keys())
|
choices = list(BOARD_CONFIGS.keys())
|
||||||
return difflib.get_close_matches(partial, choices, n=5, cutoff=0.4)
|
return difflib.get_close_matches(partial, choices, n=5, cutoff=0.4)
|
||||||
|
|
||||||
|
|
||||||
def normalize_board_name(raw: Optional[str]) -> Optional[str]:
|
def normalize_board_name(raw: Optional[str]) -> Optional[str]:
|
||||||
if raw is None:
|
if raw is None:
|
||||||
return None
|
return None
|
||||||
candidate = raw.strip()
|
candidate = raw.strip()
|
||||||
if not candidate:
|
if not candidate:
|
||||||
return None
|
return None
|
||||||
candidate = candidate.replace('\\', '/').rstrip('/')
|
candidate = candidate.replace("\\", "/").rstrip("/")
|
||||||
# strip leading folders like tools/, boards/
|
# strip leading folders like tools/, boards/
|
||||||
parts = [p for p in candidate.split('/') if p not in ('.', '') and p not in ('tools', 'boards')]
|
parts = [
|
||||||
|
p
|
||||||
|
for p in candidate.split("/")
|
||||||
|
if p not in (".", "") and p not in ("tools", "boards")
|
||||||
|
]
|
||||||
if parts:
|
if parts:
|
||||||
candidate = parts[-1] if len(parts) == 1 else "_".join(parts)
|
candidate = parts[-1] if len(parts) == 1 else "_".join(parts)
|
||||||
candidate = candidate.replace('-', '_')
|
candidate = candidate.replace("-", "_")
|
||||||
# exact match
|
# exact match
|
||||||
if candidate in BOARD_CONFIGS:
|
if candidate in BOARD_CONFIGS:
|
||||||
return candidate
|
return candidate
|
||||||
@@ -131,8 +166,8 @@ def parse_config(config_file) -> dict:
|
|||||||
|
|
||||||
def handle_wifi_config(_new_config: dict, _main_config: dict, _args) -> dict:
|
def handle_wifi_config(_new_config: dict, _main_config: dict, _args) -> dict:
|
||||||
if _args.ssid:
|
if _args.ssid:
|
||||||
_new_config["CONFIG_WIFI_SSID"] = f"\"{_args.ssid}\""
|
_new_config["CONFIG_WIFI_SSID"] = f'"{_args.ssid}"'
|
||||||
_new_config["CONFIG_WIFI_PASSWORD"] = f"\"{_args.password}\""
|
_new_config["CONFIG_WIFI_PASSWORD"] = f'"{_args.password}"'
|
||||||
else:
|
else:
|
||||||
if "CONFIG_WIFI_SSID" in _main_config:
|
if "CONFIG_WIFI_SSID" in _main_config:
|
||||||
_new_config["CONFIG_WIFI_SSID"] = _main_config["CONFIG_WIFI_SSID"]
|
_new_config["CONFIG_WIFI_SSID"] = _main_config["CONFIG_WIFI_SSID"]
|
||||||
@@ -140,8 +175,8 @@ def handle_wifi_config(_new_config: dict, _main_config: dict, _args) -> dict:
|
|||||||
_new_config["CONFIG_WIFI_PASSWORD"] = _main_config["CONFIG_WIFI_PASSWORD"]
|
_new_config["CONFIG_WIFI_PASSWORD"] = _main_config["CONFIG_WIFI_PASSWORD"]
|
||||||
|
|
||||||
if _args.clear_wifi:
|
if _args.clear_wifi:
|
||||||
_new_config["CONFIG_WIFI_SSID"] = "\"\""
|
_new_config["CONFIG_WIFI_SSID"] = '""'
|
||||||
_new_config["CONFIG_WIFI_PASSWORD"] = "\"\""
|
_new_config["CONFIG_WIFI_PASSWORD"] = '""'
|
||||||
return _new_config
|
return _new_config
|
||||||
|
|
||||||
|
|
||||||
@@ -153,10 +188,64 @@ def compute_diff(_parsed_base_config: dict, _parsed_board_config: dict) -> dict:
|
|||||||
_diff[_key] = f"{OKGREEN}+{ENDC} {_parsed_board_config[_key]}"
|
_diff[_key] = f"{OKGREEN}+{ENDC} {_parsed_board_config[_key]}"
|
||||||
else:
|
else:
|
||||||
if _parsed_board_config[_key] != _parsed_base_config[_key]:
|
if _parsed_board_config[_key] != _parsed_base_config[_key]:
|
||||||
_diff[_key] = f"{OKGREEN}{_parsed_base_config[_key]}{ENDC} -> {OKBLUE}{_parsed_board_config[_key]}{ENDC}"
|
_diff[_key] = (
|
||||||
|
f"{OKGREEN}{_parsed_base_config[_key]}{ENDC} -> {OKBLUE}{_parsed_board_config[_key]}{ENDC}"
|
||||||
|
)
|
||||||
return _diff
|
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():
|
def main():
|
||||||
parser = build_arg_parser()
|
parser = build_arg_parser()
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@@ -180,14 +269,21 @@ def main():
|
|||||||
if not os.path.isfile(get_base_config_path()):
|
if not os.path.isfile(get_base_config_path()):
|
||||||
raise SystemExit(f"Base defaults file not found: {get_base_config_path()}")
|
raise SystemExit(f"Base defaults file not found: {get_base_config_path()}")
|
||||||
|
|
||||||
print(f"{OKGREEN}Switching configuration to board:{ENDC} {OKBLUE}{normalized}{ENDC}")
|
print(
|
||||||
|
f"{OKGREEN}Switching configuration to board:{ENDC} {OKBLUE}{normalized}{ENDC}"
|
||||||
|
)
|
||||||
print(f"{OKGREEN}Using defaults from :{ENDC} {get_base_config_path()}")
|
print(f"{OKGREEN}Using defaults from :{ENDC} {get_base_config_path()}")
|
||||||
print(f"{OKGREEN}Using board config from :{ENDC} {get_board_config_path(normalized)}")
|
print(
|
||||||
|
f"{OKGREEN}Using board config from :{ENDC} {get_board_config_path(normalized)}"
|
||||||
|
)
|
||||||
|
|
||||||
with open(get_main_config_path(), "r+") as main_config:
|
with open(get_main_config_path(), "r+") as main_config:
|
||||||
parsed_main_config = parse_config(main_config)
|
parsed_main_config = parse_config(main_config)
|
||||||
|
|
||||||
with open(get_base_config_path(), "r") as base_config, open(get_board_config_path(normalized), "r") as board_config:
|
with (
|
||||||
|
open(get_base_config_path(), "r") as base_config,
|
||||||
|
open(get_board_config_path(normalized), "r") as board_config,
|
||||||
|
):
|
||||||
parsed_base_config = parse_config(base_config)
|
parsed_base_config = parse_config(base_config)
|
||||||
parsed_board_config = parse_config(board_config)
|
parsed_board_config = parse_config(board_config)
|
||||||
|
|
||||||
@@ -195,15 +291,19 @@ def main():
|
|||||||
new_board_config = handle_wifi_config(new_board_config, parsed_main_config, args)
|
new_board_config = handle_wifi_config(new_board_config, parsed_main_config, args)
|
||||||
|
|
||||||
if args.diff:
|
if args.diff:
|
||||||
print("---"*5, f"{WARNING}DIFF{ENDC}", "---"*5)
|
print("---" * 5, f"{WARNING}DIFF{ENDC}", "---" * 5)
|
||||||
diff = compute_diff(parsed_main_config, new_board_config)
|
diff = compute_diff(parsed_main_config, new_board_config)
|
||||||
if not diff:
|
if not diff:
|
||||||
print(f"{HEADER_COLOR}[DIFF]{ENDC} No changes between existing main config and {OKBLUE}{normalized}{ENDC}")
|
print(
|
||||||
|
f"{HEADER_COLOR}[DIFF]{ENDC} No changes between existing main config and {OKBLUE}{normalized}{ENDC}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print(f"{HEADER_COLOR}[DIFF]{ENDC} Keys differing (main -> new {OKBLUE}{normalized}{ENDC}):")
|
print(
|
||||||
|
f"{HEADER_COLOR}[DIFF]{ENDC} Keys differing (main -> new {OKBLUE}{normalized}{ENDC}):"
|
||||||
|
)
|
||||||
for key in sorted(diff):
|
for key in sorted(diff):
|
||||||
print(f"{HEADER_COLOR}[DIFF]{ENDC} {key} : {diff[key]}")
|
print(f"{HEADER_COLOR}[DIFF]{ENDC} {key} : {diff[key]}")
|
||||||
print("---"*14)
|
print("---" * 14)
|
||||||
|
|
||||||
if not args.dry_run:
|
if not args.dry_run:
|
||||||
print(f"{WARNING}Writing changes to main config file{ENDC}")
|
print(f"{WARNING}Writing changes to main config file{ENDC}")
|
||||||
@@ -215,7 +315,17 @@ def main():
|
|||||||
main_config.write(f"{key}\n")
|
main_config.write(f"{key}\n")
|
||||||
else:
|
else:
|
||||||
print(f"{WARNING}[DRY-RUN]{ENDC} Skipping writing to files")
|
print(f"{WARNING}[DRY-RUN]{ENDC} Skipping writing to files")
|
||||||
print(f"{OKGREEN}Done. ESP-IDF is setup to build for:{ENDC} {OKBLUE}{normalized}{ENDC}")
|
|
||||||
|
|
||||||
if __name__ == "__main__": # pragma: no cover
|
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}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blink"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pyserial" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
{ name = "python-dotenv" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dev-dependencies]
|
||||||
|
dev = [
|
||||||
|
{ name = "bumpver" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [
|
||||||
|
{ name = "pyserial", specifier = ">=3.5" },
|
||||||
|
{ name = "pytest", specifier = ">=9.0.1" },
|
||||||
|
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata.requires-dev]
|
||||||
|
dev = [{ name = "bumpver", specifier = ">=2025.1131" }]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpver"
|
||||||
|
version = "2025.1131"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "click" },
|
||||||
|
{ name = "colorama" },
|
||||||
|
{ name = "lexid" },
|
||||||
|
{ name = "toml" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/8f/8a/cc13e816e9f0849dce423b904b06fd91b5444cba6df3200d512a702f2e95/bumpver-2025.1131.tar.gz", hash = "sha256:a35fd2d43a5f65f014035c094866bd3bd6c739606f29fd41246d6ec6e839d3f9", size = 115372, upload-time = "2025-07-02T20:36:11.982Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1d/5b/2d5ea6802495ee4506721977be522804314aa66ad629d9356e3c7e5af4a6/bumpver-2025.1131-py2.py3-none-any.whl", hash = "sha256:c02527f6ed7887afbc06c07630047b24a9f9d02d544a65639e99bf8b92aaa674", size = 65361, upload-time = "2025-07-02T20:36:10.103Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "click"
|
||||||
|
version = "8.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iniconfig"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lexid"
|
||||||
|
version = "2021.1006"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/60/0b/28a3f9abc75abbf1fa996eb2dd77e1e33a5d1aac62566e3f60a8ec8b8a22/lexid-2021.1006.tar.gz", hash = "sha256:509a3a4cc926d3dbf22b203b18a4c66c25e6473fb7c0e0d30374533ac28bafe5", size = 11525, upload-time = "2021-04-02T20:18:34.668Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/e3/35764404a4b7e2021be1f88f42264c2e92e0c4720273559a62461ce64a47/lexid-2021.1006-py2.py3-none-any.whl", hash = "sha256:5526bb5606fd74c7add23320da5f02805bddd7c77916f2dc1943e6bada8605ed", size = 7587, upload-time = "2021-04-02T20:18:33.129Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "25.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pluggy"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygments"
|
||||||
|
version = "2.19.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyserial"
|
||||||
|
version = "3.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/1e/7d/ae3f0a63f41e4d2f6cb66a5b57197850f919f59e558159a4dd3a818f5082/pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", size = 159125, upload-time = "2020-11-23T03:59:15.045Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585, upload-time = "2020-11-23T03:59:13.41Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "9.0.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
{ name = "iniconfig" },
|
||||||
|
{ name = "packaging" },
|
||||||
|
{ name = "pluggy" },
|
||||||
|
{ name = "pygments" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125, upload-time = "2025-11-12T13:05:09.333Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668, upload-time = "2025-11-12T13:05:07.379Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-dotenv"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" },
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user