diff --git a/.gitmodules b/.gitmodules index a9a9dac..80837aa 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "third_party/opendroneid-core-c"] path = third_party/opendroneid-core-c url = https://github.com/opendroneid/opendroneid-core-c.git +[submodule "third_party/mavlink_c_library_v2"] + path = third_party/mavlink_c_library_v2 + url = https://github.com/mavlink/c_library_v2.git diff --git a/README.md b/README.md index 542b921..4aeed82 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ and navigate to **ESP Remote ID**. The sections below explain what each option m | **UAS ID** | Unique identifier for the aircraft, broadcast in the Basic ID message. Max 20 characters. | | **Operator registration ID** | Your national pilot/operator registration number (e.g. an EASA number like `FIN87ASTRDGE12K8`), broadcast in the Operator ID message. Max 20 characters. | -Set both to your actual registration values before any flight. The placeholder defaults (`CHANGE_ME_*`) are not valid for operation. +Set both to your actual registration values before any flight. The placeholder defaults (`CHANGE_ME_*`) are not valid for operation and intentionally block all broadcasts until replaced or overridden by valid MAVLink OpenDroneID input. ### UAS ID type @@ -106,15 +106,102 @@ BLE is enabled by default. Wi-Fi Beacon and Wi-Fi NAN are available but disabled | Option | Default | Description | |--------|---------|-------------| | `Enable Bluetooth LE legacy advertisements` | Enabled | Broadcasts Android-compatible BLE service-data advertisements on UUID `0xFFFA`. | +| `BLE advertisement rotation interval` | `250 ms` | BLE payload rotation cadence. Default keeps Location refreshed around 1 Hz while also rotating Basic ID, System, and Operator ID. | | `Enable Wi-Fi Beacon advertisements` | Disabled | Broadcasts OpenDroneID Message Pack payloads in Wi-Fi Beacon vendor IEs. | | `Enable Wi-Fi NAN advertisements` | Disabled | Broadcasts OpenDroneID Message Pack payloads in Wi-Fi NAN sync/action frames. | | `Wi-Fi Remote ID channel` | `6` | 2.4 GHz channel used by the ESP32-S3 Wi-Fi transport. | -| `Wi-Fi Remote ID transmit interval` | `1000 ms` | Remote ID Wi-Fi cadence. | | `Wi-Fi TX power` | `20 dBm` | Converted internally to ESP-IDF quarter-dBm units. | | `Wi-Fi Beacon SSID` | `OpenDroneID` | SSID embedded only in Beacon frames. Receivers should parse the vendor IE, not rely on this SSID. | -| `TX status LED GPIO` | Disabled (`-1`) | Optional simple GPIO LED that pulses after each successful BLE, Wi-Fi Beacon, or Wi-Fi NAN transmission. | -| `TX status LED is active high` | Enabled | Disable for active-low LEDs. | -| `TX status LED pulse duration` | `40 ms` | Duration of each transmission pulse. | +| `Wi-Fi Beacon TX interval` | `1000 ms` | Wi-Fi Beacon transmission cadence. | +| `Wi-Fi NAN TX interval` | `1000 ms` | Wi-Fi NAN sync/action transmission cadence. | +| `Enable onboard RGB status indicator` | Disabled | Drives a board-mounted addressable RGB LED as a local status indicator. | +| `Onboard RGB LED data GPIO` | `48` | Physical ESP32-S3 GPIO connected to the onboard WS2812/SK6812-style RGB LED data input. The UICPAL ESP32-S3-N16R8 board variant used by this firmware uses GPIO48; other variants may use GPIO33 or another pin. | +| `Onboard RGB LED brightness` | `16%` | Scales the local indicator LED brightness. | +| `Onboard RGB operational flash pattern` | `Drone beacon 1 Hz short flash` | Pattern used after transports have started and valid Remote ID identity is available. | +| `Enable external GPIO lighting outputs` | Disabled | Enables up to five simple GPIO outputs for external light trigger circuits. | +| `Startup delay before transmissions` | `10000 ms` | Development delay before starting BLE/Wi-Fi so there is time to attach the serial monitor. | + +### Onboard RGB Indicator + +The onboard RGB indicator is intended for local module status, not for driving external aviation lights. It uses the ESP32-S3 RMT peripheral to drive one addressable RGB LED, typically a WS2812/SK6812 device on the devboard. The UICPAL ESP32-S3-N16R8 board variant used by this firmware uses GPIO48 for the onboard RGB LED. + +Status behavior: + +| Firmware state | Onboard RGB behavior | +|----------------|----------------------| +| Waiting for Remote ID readiness or transport startup | Amber slow blink | +| Operational | Configured green flash pattern | +| Error state | Red fast blink | + +Operational means the firmware has started its enabled transports and the state store has a valid Basic ID/UAS ID plus Operator ID. This prevents the indicator from showing the operational pattern while placeholder identity values are still blocking broadcasts. + +The RGB data pin is not a suitable transistor trigger for external lights because addressable LEDs use a high-speed encoded data waveform. Use the external GPIO lighting outputs for that purpose. + +### External GPIO Lighting + +The external GPIO lighting module provides five independently configurable GPIO outputs. These outputs are logic-level triggers intended for transistor, MOSFET, relay-driver, or opto-isolator inputs. Do not power aircraft lights directly from ESP32 GPIO pins. + +Each output has its own configuration: + +| Setting | Description | +|---------|-------------| +| `Enable output N` | Enables this lighting output. | +| `Output N GPIO` | Physical ESP32-S3 GPIO used as the trigger signal. | +| `Output N active high` | Controls whether logical ON drives the pin high or low. | +| `Output N open drain` | Uses open-drain output mode for compatible external circuits. | +| `Output N operational pattern` | Pattern used when Remote ID transports are started and identity is ready. | +| `Output N pattern phase offset` | Staggers matching patterns across multiple outputs. | + +Outputs remain off until valid Basic ID/UAS ID and Operator ID are available and enabled transports have started. + +Available operational patterns: + +- `Solid on` +- `Beacon 1 Hz short flash` +- `Beacon 1 Hz 50% duty` +- `Single strobe` +- `Double strobe` +- `Triple strobe` +- `Fast strobe` + +Example setup: + +```text +Output 0: GPIO 4, Double strobe, phase 0 ms +Output 1: GPIO 5, Double strobe, phase 500 ms +Output 2: GPIO 6, Beacon 1 Hz short flash +Output 3: disabled +Output 4: disabled +``` + +### MAVLink OpenDroneID Input + +MAVLink input is available but disabled by default. When enabled, the firmware listens on a configured UART and accepts these MAVLink OpenDroneID messages: + +- `OPEN_DRONE_ID_BASIC_ID` +- `OPEN_DRONE_ID_OPERATOR_ID` +- `OPEN_DRONE_ID_LOCATION` + +Incoming Basic ID and Operator ID updates feed the same readiness gate as Kconfig values. This allows a flight controller or companion computer to provide identity at runtime while keeping the placeholder Kconfig values in flash. Broadcasts still do not start until a valid Basic ID/UAS ID and Operator ID are present. + +| Option | Default | Description | +|--------|---------|-------------| +| `Enable MAVLink OpenDroneID UART input` | Disabled | Enables the UART MAVLink parser and state producer. | +| `MAVLink UART number` | `1` | ESP32-S3 UART peripheral used for input. This does not imply physical pins. | +| `MAVLink UART baud rate` | `57600` | UART baud rate. Common values are `57600`, `115200`, and `921600`. | +| `MAVLink UART RX GPIO` | `-1` | Physical ESP32-S3 pin routed to the selected UART RX signal. Connect this to the flight controller MAVLink TX pin. Must be configured when MAVLink input is enabled. | +| `MAVLink UART TX GPIO` | `-1` | Optional physical ESP32-S3 pin routed to the selected UART TX signal. Connect this to the flight controller MAVLink RX pin only for bidirectional wiring. Leave as `-1` for receive-only. | +| `Accepted MAVLink target system` | `0` | `0` accepts all systems; non-zero accepts that system or broadcast target `0`. | +| `Accepted MAVLink target component` | `0` | `0` accepts all components; non-zero accepts that component or broadcast target `0`. | + +On ESP32-S3, the UART number selects the hardware UART block (`UART0`, `UART1`, or `UART2`). It does not uniquely select board pins. UART signals are routed through the ESP32 GPIO matrix, and many dev boards label only the programming/debug serial pins as `RX`/`TX`. Choose GPIOs that are exposed on your board and not already used by USB serial, flash/PSRAM, buttons, LEDs, or strapping functions. + +Receive-only wiring is enough for the current MAVLink producer: + +```text +Flight controller TX -> ESP32-S3 MAVLink UART RX GPIO +Flight controller GND -> ESP32-S3 GND +``` For Android app testing, use one transport at a time because many receiver apps do not display whether a detected aircraft came from BLE or Wi-Fi. @@ -130,6 +217,8 @@ Before flashing for any actual operation: - [ ] Set **EU operation category** to **Open** (or your authorised category) - [ ] Optionally enable **Broadcast a known takeoff position** and enter your takeoff coordinates +Note: BLE and Wi-Fi transports wait for the store readiness gate before transmitting. The gate requires a usable Basic ID/UAS ID and Operator ID, either from Kconfig or from runtime producers such as MAVLink. + ## Prerequisites - ESP32-S3 board @@ -164,21 +253,6 @@ The default ESP serial device inside the container is `/dev/ttyESP32`. Override make flash ESPPORT=/dev/ttyACM0 ``` -## Architecture - -The ESP-IDF component is split so transports can share the same Remote ID state and OpenDroneID encoding: - -- `src/remoteid/main.c`: application entrypoint and top-level startup. -- `src/remoteid/remoteid/config.h`: compile-time configuration constants from Kconfig. -- `src/remoteid/remoteid/types.h`: internal Remote ID state and message bundle types. -- `src/remoteid/remoteid/model.c`: builds the current aircraft/operator/location state from configuration. -- `src/remoteid/remoteid/encode.c`: converts internal state into official OpenDroneID messages. -- `src/remoteid/remoteid/led.c`: optional TX status LED pulse handling. -- `src/remoteid/remoteid/ble.c`: NimBLE lifecycle, TX power, and BLE legacy advertisement rotation. -- `src/remoteid/remoteid/wifi.c`: ESP-IDF Wi-Fi setup and OpenDroneID Wi-Fi Beacon/NAN transmission. - -Both transports use the same `model` and `encode` modules instead of rebuilding message payloads independently. - ## Transmit Power OpenDroneID BLE reception range is not guaranteed by a fixed distance in the EU documents; practical range depends on transmitter power, receiver hardware, antenna design, enclosure, orientation, and RF environment. This firmware therefore makes BLE advertising TX power explicit and configurable. @@ -191,9 +265,9 @@ CONFIG_REMOTEID_BLE_TX_POWER_P9=y Change it with `make menuconfig` under `ESP Remote ID -> BLE advertising TX power`, or by editing `sdkconfig.defaults` before regenerating `sdkconfig`. Keep the configured level within the limits of your board, antenna, enclosure, and local RF rules. -## macOS Serial Bridge +## (macOS) Serial Bridge -Docker Desktop on macOS does not expose `/dev/cu.*` serial devices directly to Linux containers. Use `socat` to bridge the ESP32-S3 USB serial device from the host into the devcontainer. +Docker on macOS does not expose `/dev/cu.*` serial devices directly to Linux containers. Use `socat` to bridge the ESP32-S3 USB serial device from the host into the devcontainer. On the macOS host: @@ -244,7 +318,7 @@ Use `make menuconfig` under **ESP Remote ID** within the `Component config` menu For Wi-Fi-only testing, flash the firmware and use an Android receiver that supports the specific Wi-Fi transport being tested. If the app does not show a source transport, enable only one Wi-Fi transport at a time so any detection has a known source. -The screenshot below shows the OpenDroneID Android app receiving live advertisements from the module. The placeholder identifiers (`CHANGE_ME_UAS_ID`, `CHANGE_ME_OP_ID`) are visible, replace these with your registration details before any flight. +The screenshot below shows the OpenDroneID Android app receiving live advertisements from the module. Current firmware blocks placeholder identifiers (`CHANGE_ME_UAS_ID`, `CHANGE_ME_OP_ID`), so replace these with your registration details or provide valid MAVLink OpenDroneID identity before testing reception.

OpenDroneID Android app showing one detected drone with Basic ID, Location (no position), System, and Operator ID messages diff --git a/src/remoteid/CMakeLists.txt b/src/remoteid/CMakeLists.txt index b130de8..3281ddc 100644 --- a/src/remoteid/CMakeLists.txt +++ b/src/remoteid/CMakeLists.txt @@ -3,8 +3,12 @@ idf_component_register( "main.c" "remoteid/ble.c" "remoteid/encode.c" - "remoteid/led.c" + "remoteid/indicator.c" + "remoteid/lighting.c" + "remoteid/mavlink.c" "remoteid/model.c" + "remoteid/pattern.c" + "remoteid/store.c" "remoteid/wifi.c" INCLUDE_DIRS "." @@ -12,6 +16,8 @@ idf_component_register( REQUIRES bt esp_driver_gpio + esp_driver_rmt + esp_driver_uart esp_event esp_netif esp_timer @@ -19,3 +25,7 @@ idf_component_register( nvs_flash opendroneid_core ) + +target_include_directories(${COMPONENT_LIB} PRIVATE + "${CMAKE_CURRENT_LIST_DIR}/../../third_party/mavlink_c_library_v2" +) diff --git a/src/remoteid/Kconfig b/src/remoteid/Kconfig index e34fab7..72f3586 100644 --- a/src/remoteid/Kconfig +++ b/src/remoteid/Kconfig @@ -43,6 +43,89 @@ config REMOTEID_OPERATOR_ID is broadcast in the Operator ID message. Must be 20 characters or fewer. +config REMOTEID_STARTUP_DELAY_MS + int "Startup delay before transmissions (ms)" + default 10000 + range 0 60000 + help + Delay after boot before BLE or Wi-Fi Remote ID transmissions start. + This is useful during development because it leaves time to attach the + serial monitor after flashing or reset. + +config REMOTEID_MAVLINK_INPUT + bool "Enable MAVLink OpenDroneID UART input" + default n + help + Parse incoming MAVLink v2 OpenDroneID messages from a flight controller + or companion computer and feed them into the Remote ID state store. + The first supported messages are OPEN_DRONE_ID_BASIC_ID, + OPEN_DRONE_ID_OPERATOR_ID, and OPEN_DRONE_ID_LOCATION. + +if REMOTEID_MAVLINK_INPUT + +config REMOTEID_MAVLINK_UART_NUM + int "MAVLink UART number" + default 1 + range 0 2 + help + ESP32-S3 UART peripheral used for MAVLink input. This selects the + hardware UART block, not the physical pins. The ESP32-S3 GPIO matrix + lets UART signals be routed to many different pins, so configure the RX + and optional TX GPIOs below to match your board wiring. + +config REMOTEID_MAVLINK_BAUD + int "MAVLink UART baud rate" + default 57600 + range 1200 921600 + help + Baud rate for the MAVLink input UART. Common flight-controller values + are 57600, 115200, and 921600. + +config REMOTEID_MAVLINK_RX_GPIO + int "MAVLink UART RX GPIO" + default -1 + range -1 48 + help + Physical ESP32-S3 GPIO used as the selected UART's RX signal. Connect + this pin to the flight controller or companion computer MAVLink TX pin. + This must be set to a valid GPIO when MAVLink input is enabled. Do not + assume the UART number automatically selects a usable board pin. + +config REMOTEID_MAVLINK_TX_GPIO + int "MAVLink UART TX GPIO" + default -1 + range -1 48 + help + Optional physical ESP32-S3 GPIO used as the selected UART's TX signal. + Connect this pin to the flight controller or companion computer MAVLink + RX pin only if bidirectional UART wiring is needed. Current firmware + only requires receive-only MAVLink input, so leave this at -1 unless + you intentionally want the TX signal routed. + +config REMOTEID_MAVLINK_TASK_STACK + int "MAVLink input task stack size" + default 4096 + range 3072 8192 + +config REMOTEID_MAVLINK_TARGET_SYSTEM + int "Accepted MAVLink target system" + default 0 + range 0 255 + help + If non-zero, only OpenDroneID messages addressed to this target system + or broadcast target 0 are accepted. Leave as 0 to accept all systems. + +config REMOTEID_MAVLINK_TARGET_COMPONENT + int "Accepted MAVLink target component" + default 0 + range 0 255 + help + If non-zero, only OpenDroneID messages addressed to this target + component or broadcast target 0 are accepted. Leave as 0 to accept all + components. + +endif # REMOTEID_MAVLINK_INPUT + choice REMOTEID_ID_TYPE prompt "UAS ID type" default REMOTEID_ID_TYPE_CAA_REG @@ -217,31 +300,82 @@ config REMOTEID_BLE_TX_POWER_P9 endchoice -config REMOTEID_TX_LED_GPIO - int "TX status LED GPIO" - default -1 - range -1 48 +config REMOTEID_BLE_TX_INTERVAL_MS + int "BLE advertisement rotation interval (ms)" + default 250 + range 100 5000 + depends on REMOTEID_TRANSPORT_BLE help - GPIO for a simple status LED that pulses on each successful BLE or - Wi-Fi Remote ID transmission. Set to -1 to disable. For boards with - an addressable RGB LED, this option only works if the LED can be driven - as a simple GPIO. + Interval between BLE advertisement payload rotations. The default + rotates through the message schedule fast enough to keep Location at + approximately 1 Hz while still periodically advertising Basic ID, + System, and Operator ID. -config REMOTEID_TX_LED_ACTIVE_HIGH - bool "TX status LED is active high" - default y - depends on REMOTEID_TX_LED_GPIO >= 0 +config REMOTEID_INDICATOR_RGB_ENABLE + bool "Enable onboard RGB status indicator" + default n help - Disable this if the configured LED turns on when the GPIO is driven - low. + Drive a board-mounted addressable RGB LED, typically WS2812/SK6812, as + a local firmware status indicator. Before Remote ID is operational it + shows status/error indications. After transports have started and valid + identity is present, it switches to the configured operational flash + pattern. This is separate from the TX status LED pulse output. + +if REMOTEID_INDICATOR_RGB_ENABLE -config REMOTEID_TX_LED_PULSE_MS - int "TX status LED pulse duration (ms)" - default 40 - range 1 1000 - depends on REMOTEID_TX_LED_GPIO >= 0 +config REMOTEID_INDICATOR_RGB_GPIO + int "Onboard RGB LED data GPIO" + default 48 + range -1 48 + help + Physical ESP32-S3 GPIO connected to the onboard addressable RGB LED data + input. The UICPAL ESP32-S3-N16R8 board variant used by this firmware + uses GPIO48 for its WS2812 RGB LED. Other ESP32-S3 dev boards may use + GPIO33 or another pin, so verify your board schematic before enabling + this option on non-UICPAL hardware. Set to -1 to leave the indicator + disabled even when this menu section is enabled. + +config REMOTEID_INDICATOR_RGB_BRIGHTNESS_PERCENT + int "Onboard RGB LED brightness (%)" + default 16 + range 1 100 + help + Scales the local indicator LED brightness. This only affects the + onboard RGB status LED, not any future external light trigger GPIOs. + +choice REMOTEID_INDICATOR_OPERATIONAL_PATTERN + prompt "Onboard RGB operational flash pattern" + default REMOTEID_INDICATOR_PATTERN_DRONE_BEACON_1HZ_SHORT help - Duration of the LED pulse after each successful transmission. + Pattern used by the onboard RGB LED after Remote ID is operational. + External aviation lighting outputs will be configured separately later. + +config REMOTEID_INDICATOR_PATTERN_SOLID + bool "Solid on" + +config REMOTEID_INDICATOR_PATTERN_DRONE_BEACON_1HZ_SHORT + bool "Drone beacon 1 Hz short flash" + +config REMOTEID_INDICATOR_PATTERN_DRONE_BEACON_1HZ_50 + bool "Drone beacon 1 Hz 50% duty" + +config REMOTEID_INDICATOR_PATTERN_STROBE_SINGLE + bool "Single strobe" + +config REMOTEID_INDICATOR_PATTERN_STROBE_DOUBLE + bool "Double strobe" + +config REMOTEID_INDICATOR_PATTERN_STROBE_TRIPLE + bool "Triple strobe" + +config REMOTEID_INDICATOR_PATTERN_FAST_STROBE + bool "Fast strobe" + +endchoice + +endif # REMOTEID_INDICATOR_RGB_ENABLE + +rsource "Kconfig.lighting" if REMOTEID_TRANSPORT_WIFI_BEACON || REMOTEID_TRANSPORT_WIFI_NAN @@ -254,14 +388,6 @@ config REMOTEID_WIFI_CHANNEL 6 is the standard/default channel for ASTM F3411 Wi-Fi Beacon/NAN on 2.4 GHz. Select a different channel only when permitted and required. -config REMOTEID_WIFI_INTERVAL_MS - int "Wi-Fi Remote ID transmit interval (ms)" - default 1000 - range 100 5000 - help - Interval between OpenDroneID Wi-Fi transmissions. Use 1000 ms for a - 1 Hz Remote ID cadence. - config REMOTEID_WIFI_TX_POWER_DBM int "Wi-Fi TX power (dBm)" default 20 @@ -283,6 +409,26 @@ config REMOTEID_WIFI_BEACON_SSID bytes or fewer. Remote ID receivers should parse the vendor IE, not rely on this SSID. +config REMOTEID_WIFI_BEACON_TX_INTERVAL_MS + int "Wi-Fi Beacon TX interval (ms)" + default 1000 + range 100 5000 + help + Interval between OpenDroneID Wi-Fi Beacon transmissions. The default + 1000 ms gives a 1 Hz Remote ID Beacon cadence. + endif # REMOTEID_TRANSPORT_WIFI_BEACON +if REMOTEID_TRANSPORT_WIFI_NAN + +config REMOTEID_WIFI_NAN_TX_INTERVAL_MS + int "Wi-Fi NAN TX interval (ms)" + default 1000 + range 100 5000 + help + Interval between OpenDroneID Wi-Fi NAN sync/action transmissions. The + default 1000 ms gives a 1 Hz Remote ID NAN cadence. + +endif # REMOTEID_TRANSPORT_WIFI_NAN + endmenu diff --git a/src/remoteid/Kconfig.lighting b/src/remoteid/Kconfig.lighting new file mode 100644 index 0000000..3b5ef18 --- /dev/null +++ b/src/remoteid/Kconfig.lighting @@ -0,0 +1,306 @@ +menu "External GPIO lighting outputs" + +config REMOTEID_LIGHTING_ENABLE + bool "Enable external GPIO lighting outputs" + default n + help + Enables up to five simple GPIO outputs for external lighting triggers. + These outputs are intended to drive transistor, MOSFET, relay driver, + or opto-isolator inputs. Do not drive aircraft lights directly from an + ESP32 GPIO pin. + +if REMOTEID_LIGHTING_ENABLE + +menu "Lighting output 0" + +config REMOTEID_LIGHTING_OUTPUT0_ENABLE + bool "Enable output 0" + default n + +config REMOTEID_LIGHTING_OUTPUT0_GPIO + int "Output 0 GPIO" + default -1 + range -1 48 + depends on REMOTEID_LIGHTING_OUTPUT0_ENABLE + +config REMOTEID_LIGHTING_OUTPUT0_ACTIVE_HIGH + bool "Output 0 active high" + default y + depends on REMOTEID_LIGHTING_OUTPUT0_ENABLE + +config REMOTEID_LIGHTING_OUTPUT0_OPEN_DRAIN + bool "Output 0 open drain" + default n + depends on REMOTEID_LIGHTING_OUTPUT0_ENABLE + +choice REMOTEID_LIGHTING_OUTPUT0_PATTERN + prompt "Output 0 operational pattern" + default REMOTEID_LIGHTING_OUTPUT0_PATTERN_BEACON_1HZ_SHORT + depends on REMOTEID_LIGHTING_OUTPUT0_ENABLE + +config REMOTEID_LIGHTING_OUTPUT0_PATTERN_SOLID + bool "Solid on" + +config REMOTEID_LIGHTING_OUTPUT0_PATTERN_BEACON_1HZ_SHORT + bool "Beacon 1 Hz short flash" + +config REMOTEID_LIGHTING_OUTPUT0_PATTERN_BEACON_1HZ_50 + bool "Beacon 1 Hz 50% duty" + +config REMOTEID_LIGHTING_OUTPUT0_PATTERN_STROBE_SINGLE + bool "Single strobe" + +config REMOTEID_LIGHTING_OUTPUT0_PATTERN_STROBE_DOUBLE + bool "Double strobe" + +config REMOTEID_LIGHTING_OUTPUT0_PATTERN_STROBE_TRIPLE + bool "Triple strobe" + +config REMOTEID_LIGHTING_OUTPUT0_PATTERN_FAST_STROBE + bool "Fast strobe" + +endchoice + +config REMOTEID_LIGHTING_OUTPUT0_PHASE_MS + int "Output 0 pattern phase offset (ms)" + default 0 + range 0 60000 + depends on REMOTEID_LIGHTING_OUTPUT0_ENABLE + +endmenu + +menu "Lighting output 1" + +config REMOTEID_LIGHTING_OUTPUT1_ENABLE + bool "Enable output 1" + default n + +config REMOTEID_LIGHTING_OUTPUT1_GPIO + int "Output 1 GPIO" + default -1 + range -1 48 + depends on REMOTEID_LIGHTING_OUTPUT1_ENABLE + +config REMOTEID_LIGHTING_OUTPUT1_ACTIVE_HIGH + bool "Output 1 active high" + default y + depends on REMOTEID_LIGHTING_OUTPUT1_ENABLE + +config REMOTEID_LIGHTING_OUTPUT1_OPEN_DRAIN + bool "Output 1 open drain" + default n + depends on REMOTEID_LIGHTING_OUTPUT1_ENABLE + +choice REMOTEID_LIGHTING_OUTPUT1_PATTERN + prompt "Output 1 operational pattern" + default REMOTEID_LIGHTING_OUTPUT1_PATTERN_BEACON_1HZ_SHORT + depends on REMOTEID_LIGHTING_OUTPUT1_ENABLE + +config REMOTEID_LIGHTING_OUTPUT1_PATTERN_SOLID + bool "Solid on" + +config REMOTEID_LIGHTING_OUTPUT1_PATTERN_BEACON_1HZ_SHORT + bool "Beacon 1 Hz short flash" + +config REMOTEID_LIGHTING_OUTPUT1_PATTERN_BEACON_1HZ_50 + bool "Beacon 1 Hz 50% duty" + +config REMOTEID_LIGHTING_OUTPUT1_PATTERN_STROBE_SINGLE + bool "Single strobe" + +config REMOTEID_LIGHTING_OUTPUT1_PATTERN_STROBE_DOUBLE + bool "Double strobe" + +config REMOTEID_LIGHTING_OUTPUT1_PATTERN_STROBE_TRIPLE + bool "Triple strobe" + +config REMOTEID_LIGHTING_OUTPUT1_PATTERN_FAST_STROBE + bool "Fast strobe" + +endchoice + +config REMOTEID_LIGHTING_OUTPUT1_PHASE_MS + int "Output 1 pattern phase offset (ms)" + default 0 + range 0 60000 + depends on REMOTEID_LIGHTING_OUTPUT1_ENABLE + +endmenu + +menu "Lighting output 2" + +config REMOTEID_LIGHTING_OUTPUT2_ENABLE + bool "Enable output 2" + default n + +config REMOTEID_LIGHTING_OUTPUT2_GPIO + int "Output 2 GPIO" + default -1 + range -1 48 + depends on REMOTEID_LIGHTING_OUTPUT2_ENABLE + +config REMOTEID_LIGHTING_OUTPUT2_ACTIVE_HIGH + bool "Output 2 active high" + default y + depends on REMOTEID_LIGHTING_OUTPUT2_ENABLE + +config REMOTEID_LIGHTING_OUTPUT2_OPEN_DRAIN + bool "Output 2 open drain" + default n + depends on REMOTEID_LIGHTING_OUTPUT2_ENABLE + +choice REMOTEID_LIGHTING_OUTPUT2_PATTERN + prompt "Output 2 operational pattern" + default REMOTEID_LIGHTING_OUTPUT2_PATTERN_BEACON_1HZ_SHORT + depends on REMOTEID_LIGHTING_OUTPUT2_ENABLE + +config REMOTEID_LIGHTING_OUTPUT2_PATTERN_SOLID + bool "Solid on" + +config REMOTEID_LIGHTING_OUTPUT2_PATTERN_BEACON_1HZ_SHORT + bool "Beacon 1 Hz short flash" + +config REMOTEID_LIGHTING_OUTPUT2_PATTERN_BEACON_1HZ_50 + bool "Beacon 1 Hz 50% duty" + +config REMOTEID_LIGHTING_OUTPUT2_PATTERN_STROBE_SINGLE + bool "Single strobe" + +config REMOTEID_LIGHTING_OUTPUT2_PATTERN_STROBE_DOUBLE + bool "Double strobe" + +config REMOTEID_LIGHTING_OUTPUT2_PATTERN_STROBE_TRIPLE + bool "Triple strobe" + +config REMOTEID_LIGHTING_OUTPUT2_PATTERN_FAST_STROBE + bool "Fast strobe" + +endchoice + +config REMOTEID_LIGHTING_OUTPUT2_PHASE_MS + int "Output 2 pattern phase offset (ms)" + default 0 + range 0 60000 + depends on REMOTEID_LIGHTING_OUTPUT2_ENABLE + +endmenu + +menu "Lighting output 3" + +config REMOTEID_LIGHTING_OUTPUT3_ENABLE + bool "Enable output 3" + default n + +config REMOTEID_LIGHTING_OUTPUT3_GPIO + int "Output 3 GPIO" + default -1 + range -1 48 + depends on REMOTEID_LIGHTING_OUTPUT3_ENABLE + +config REMOTEID_LIGHTING_OUTPUT3_ACTIVE_HIGH + bool "Output 3 active high" + default y + depends on REMOTEID_LIGHTING_OUTPUT3_ENABLE + +config REMOTEID_LIGHTING_OUTPUT3_OPEN_DRAIN + bool "Output 3 open drain" + default n + depends on REMOTEID_LIGHTING_OUTPUT3_ENABLE + +choice REMOTEID_LIGHTING_OUTPUT3_PATTERN + prompt "Output 3 operational pattern" + default REMOTEID_LIGHTING_OUTPUT3_PATTERN_BEACON_1HZ_SHORT + depends on REMOTEID_LIGHTING_OUTPUT3_ENABLE + +config REMOTEID_LIGHTING_OUTPUT3_PATTERN_SOLID + bool "Solid on" + +config REMOTEID_LIGHTING_OUTPUT3_PATTERN_BEACON_1HZ_SHORT + bool "Beacon 1 Hz short flash" + +config REMOTEID_LIGHTING_OUTPUT3_PATTERN_BEACON_1HZ_50 + bool "Beacon 1 Hz 50% duty" + +config REMOTEID_LIGHTING_OUTPUT3_PATTERN_STROBE_SINGLE + bool "Single strobe" + +config REMOTEID_LIGHTING_OUTPUT3_PATTERN_STROBE_DOUBLE + bool "Double strobe" + +config REMOTEID_LIGHTING_OUTPUT3_PATTERN_STROBE_TRIPLE + bool "Triple strobe" + +config REMOTEID_LIGHTING_OUTPUT3_PATTERN_FAST_STROBE + bool "Fast strobe" + +endchoice + +config REMOTEID_LIGHTING_OUTPUT3_PHASE_MS + int "Output 3 pattern phase offset (ms)" + default 0 + range 0 60000 + depends on REMOTEID_LIGHTING_OUTPUT3_ENABLE + +endmenu + +menu "Lighting output 4" + +config REMOTEID_LIGHTING_OUTPUT4_ENABLE + bool "Enable output 4" + default n + +config REMOTEID_LIGHTING_OUTPUT4_GPIO + int "Output 4 GPIO" + default -1 + range -1 48 + depends on REMOTEID_LIGHTING_OUTPUT4_ENABLE + +config REMOTEID_LIGHTING_OUTPUT4_ACTIVE_HIGH + bool "Output 4 active high" + default y + depends on REMOTEID_LIGHTING_OUTPUT4_ENABLE + +config REMOTEID_LIGHTING_OUTPUT4_OPEN_DRAIN + bool "Output 4 open drain" + default n + depends on REMOTEID_LIGHTING_OUTPUT4_ENABLE + +choice REMOTEID_LIGHTING_OUTPUT4_PATTERN + prompt "Output 4 operational pattern" + default REMOTEID_LIGHTING_OUTPUT4_PATTERN_BEACON_1HZ_SHORT + depends on REMOTEID_LIGHTING_OUTPUT4_ENABLE + +config REMOTEID_LIGHTING_OUTPUT4_PATTERN_SOLID + bool "Solid on" + +config REMOTEID_LIGHTING_OUTPUT4_PATTERN_BEACON_1HZ_SHORT + bool "Beacon 1 Hz short flash" + +config REMOTEID_LIGHTING_OUTPUT4_PATTERN_BEACON_1HZ_50 + bool "Beacon 1 Hz 50% duty" + +config REMOTEID_LIGHTING_OUTPUT4_PATTERN_STROBE_SINGLE + bool "Single strobe" + +config REMOTEID_LIGHTING_OUTPUT4_PATTERN_STROBE_DOUBLE + bool "Double strobe" + +config REMOTEID_LIGHTING_OUTPUT4_PATTERN_STROBE_TRIPLE + bool "Triple strobe" + +config REMOTEID_LIGHTING_OUTPUT4_PATTERN_FAST_STROBE + bool "Fast strobe" + +endchoice + +config REMOTEID_LIGHTING_OUTPUT4_PHASE_MS + int "Output 4 pattern phase offset (ms)" + default 0 + range 0 60000 + depends on REMOTEID_LIGHTING_OUTPUT4_ENABLE + +endmenu + +endif # REMOTEID_LIGHTING_ENABLE + +endmenu diff --git a/src/remoteid/main.c b/src/remoteid/main.c index 752dc6e..df24725 100644 --- a/src/remoteid/main.c +++ b/src/remoteid/main.c @@ -1,29 +1,52 @@ #include "esp_bt.h" #include "esp_err.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" #include "nvs_flash.h" #include "remoteid/ble.h" -#include "remoteid/led.h" +#include "remoteid/indicator.h" +#include "remoteid/lighting.h" +#include "remoteid/mavlink.h" #include "remoteid/model.h" +#include "remoteid/store.h" #include "remoteid/wifi.h" #include "sdkconfig.h" +static const char *TAG = "remoteid_main"; + +#ifndef CONFIG_REMOTEID_STARTUP_DELAY_MS +#define CONFIG_REMOTEID_STARTUP_DELAY_MS 10000 +#endif + void app_main(void) { static remoteid_state_t state; - // TODO DO NOT FORGET When remoteid_state_t is no longer static (e.g., when fed through MAVLlink or other channels) - // ensure access is thread-safe of check how freertos handles it. - + ESP_ERROR_CHECK(remoteid_indicator_init()); ESP_ERROR_CHECK(nvs_flash_init()); ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); ESP_ERROR_CHECK(remoteid_model_init(&state)); - ESP_ERROR_CHECK(remoteid_led_init()); + ESP_ERROR_CHECK(remoteid_store_start(&state)); + ESP_ERROR_CHECK(remoteid_lighting_init()); + +#if CONFIG_REMOTEID_MAVLINK_INPUT + ESP_ERROR_CHECK(remoteid_mavlink_start()); +#endif + + if (CONFIG_REMOTEID_STARTUP_DELAY_MS > 0) { + ESP_LOGI(TAG, "delaying Remote ID transport startup for %d ms", CONFIG_REMOTEID_STARTUP_DELAY_MS); + vTaskDelay(pdMS_TO_TICKS(CONFIG_REMOTEID_STARTUP_DELAY_MS)); + } #if CONFIG_REMOTEID_TRANSPORT_BLE - ESP_ERROR_CHECK(remoteid_ble_start(&state)); + ESP_ERROR_CHECK(remoteid_ble_start()); #endif #if CONFIG_REMOTEID_TRANSPORT_WIFI_BEACON || CONFIG_REMOTEID_TRANSPORT_WIFI_NAN - ESP_ERROR_CHECK(remoteid_wifi_start(&state)); + ESP_ERROR_CHECK(remoteid_wifi_start()); #endif + + remoteid_indicator_mark_transports_started(); + remoteid_lighting_mark_transports_started(); } diff --git a/src/remoteid/remoteid/ble.c b/src/remoteid/remoteid/ble.c index f404a93..1cc9efe 100644 --- a/src/remoteid/remoteid/ble.c +++ b/src/remoteid/remoteid/ble.c @@ -11,16 +11,19 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "host/ble_hs.h" -#include "led.h" #include "nimble/nimble_port.h" #include "nimble/nimble_port_freertos.h" #include "opendroneid.h" #include "sdkconfig.h" +#include "store.h" #define REMOTEID_SERVICE_DATA_LEN (2 + ODID_MESSAGE_SIZE) #define REMOTEID_LEGACY_ADV_DATA_LEN (1 + 1 + 2 + REMOTEID_SERVICE_DATA_LEN) #define REMOTEID_ADV_INTERVAL_100_MS 0x00a0 -#define REMOTEID_ADV_ROTATION_MS 250 + +#ifndef CONFIG_REMOTEID_BLE_TX_INTERVAL_MS +#define CONFIG_REMOTEID_BLE_TX_INTERVAL_MS 250 +#endif static const char *TAG = "remoteid_ble"; @@ -35,7 +38,6 @@ static const remoteid_message_index_t s_message_schedule[] = { REMOTEID_MESSAGE_LOCATION, }; -static const remoteid_state_t *s_state; static remoteid_message_bundle_t s_bundle; static uint8_t s_message_counters[ODID_MSG_COUNTER_AMOUNT]; static size_t s_schedule_index; @@ -109,17 +111,35 @@ static esp_err_t advertise_message(const uint8_t message[ODID_MESSAGE_SIZE]) } s_message_counters[counter_index]++; - remoteid_led_pulse(); return ESP_OK; } static void remoteid_ble_task(void *arg) { (void)arg; + remoteid_state_t snapshot; + + ESP_LOGI(TAG, "BLE transport waiting for Remote ID Basic ID and Operator ID"); + ESP_ERROR_CHECK(remoteid_store_wait_ready(portMAX_DELAY)); + ESP_LOGI(TAG, "BLE transport identity ready, starting advertisements"); while (true) { + if (!remoteid_store_is_ready()) { + ble_gap_adv_stop(); + ESP_LOGW(TAG, "BLE transport paused until Remote ID identity is ready again"); + ESP_ERROR_CHECK(remoteid_store_wait_ready(portMAX_DELAY)); + } + + ESP_ERROR_CHECK(remoteid_store_get_snapshot(&snapshot)); + esp_err_t static_rc = remoteid_encode_static_messages(&snapshot, &s_bundle); + if (static_rc != ESP_OK) { + ESP_LOGE(TAG, "failed to encode static messages"); + vTaskDelay(pdMS_TO_TICKS(CONFIG_REMOTEID_BLE_TX_INTERVAL_MS)); + continue; + } + remoteid_message_index_t message_index = s_message_schedule[s_schedule_index]; - esp_err_t rc = remoteid_encode_dynamic_message(s_state, &s_bundle, message_index); + esp_err_t rc = remoteid_encode_dynamic_message(&snapshot, &s_bundle, message_index); if (rc != ESP_OK) { ESP_LOGE(TAG, "failed to encode dynamic message %u, broadcasting stale message", message_index); } @@ -130,7 +150,7 @@ static void remoteid_ble_task(void *arg) } s_schedule_index = (s_schedule_index + 1) % (sizeof(s_message_schedule) / sizeof(s_message_schedule[0])); - vTaskDelay(pdMS_TO_TICKS(REMOTEID_ADV_ROTATION_MS)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_REMOTEID_BLE_TX_INTERVAL_MS)); } } @@ -155,11 +175,8 @@ static void nimble_host_task(void *param) nimble_port_freertos_deinit(); } -esp_err_t remoteid_ble_start(const remoteid_state_t *state) +esp_err_t remoteid_ble_start(void) { - s_state = state; - - ESP_RETURN_ON_ERROR(remoteid_encode_static_messages(s_state, &s_bundle), TAG, "build static ODID messages"); ESP_RETURN_ON_ERROR(nimble_port_init(), TAG, "initialize NimBLE"); ESP_RETURN_ON_ERROR(configure_ble_tx_power(), TAG, "configure BLE TX power"); diff --git a/src/remoteid/remoteid/ble.h b/src/remoteid/remoteid/ble.h index e068879..128ba62 100644 --- a/src/remoteid/remoteid/ble.h +++ b/src/remoteid/remoteid/ble.h @@ -1,6 +1,4 @@ #pragma once #include "esp_err.h" -#include "types.h" - -esp_err_t remoteid_ble_start(const remoteid_state_t *state); +esp_err_t remoteid_ble_start(void); diff --git a/src/remoteid/remoteid/encode.c b/src/remoteid/remoteid/encode.c index 29d8b5f..b086655 100644 --- a/src/remoteid/remoteid/encode.c +++ b/src/remoteid/remoteid/encode.c @@ -33,57 +33,42 @@ static void fill_basic_id_data(const remoteid_state_t *state, ODID_BasicID_data static void fill_location_data(const remoteid_state_t *state, ODID_Location_data *data) { - if (state->has_position) { - data->Status = ODID_STATUS_AIRBORNE; - data->Direction = 0.0f; - data->SpeedHorizontal = 0.0f; - data->SpeedVertical = 0.0f; - data->Latitude = state->latitude; - data->Longitude = state->longitude; - data->AltitudeBaro = state->altitude_m; - data->AltitudeGeo = state->altitude_m; - data->HeightType = ODID_HEIGHT_REF_OVER_TAKEOFF; - data->Height = 0.0f; - data->HorizAccuracy = ODID_HOR_ACC_UNKNOWN; - data->VertAccuracy = ODID_VER_ACC_UNKNOWN; - data->BaroAccuracy = ODID_VER_ACC_UNKNOWN; - data->SpeedAccuracy = ODID_SPEED_ACC_UNKNOWN; - data->TSAccuracy = ODID_TIME_ACC_UNKNOWN; - data->TimeStamp = (float)(uptime_seconds() % MAX_TIMESTAMP); - } else { - data->Status = ODID_STATUS_UNDECLARED; - data->Direction = (float)INV_DIR; - data->SpeedHorizontal = (float)INV_SPEED_H; - data->SpeedVertical = (float)INV_SPEED_V; - data->Latitude = 0.0; - data->Longitude = 0.0; - data->AltitudeBaro = (float)INV_ALT; - data->AltitudeGeo = (float)INV_ALT; - data->HeightType = ODID_HEIGHT_REF_OVER_TAKEOFF; - data->Height = (float)INV_ALT; - data->HorizAccuracy = ODID_HOR_ACC_UNKNOWN; - data->VertAccuracy = ODID_VER_ACC_UNKNOWN; - data->BaroAccuracy = ODID_VER_ACC_UNKNOWN; - data->SpeedAccuracy = ODID_SPEED_ACC_UNKNOWN; - data->TSAccuracy = ODID_TIME_ACC_UNKNOWN; - data->TimeStamp = (float)INV_TIMESTAMP; - } + data->Status = state->status; + data->Direction = state->direction; + data->SpeedHorizontal = state->speed_horizontal; + data->SpeedVertical = state->speed_vertical; + data->Latitude = state->latitude; + data->Longitude = state->longitude; + data->AltitudeBaro = state->altitude_baro_m; + data->AltitudeGeo = state->altitude_geo_m; + data->HeightType = state->height_type; + data->Height = state->height; + data->HorizAccuracy = state->horiz_acc; + data->VertAccuracy = state->vert_acc; + data->BaroAccuracy = state->baro_acc; + data->SpeedAccuracy = state->speed_acc; + data->TSAccuracy = state->ts_acc; + data->TimeStamp = state->timestamp >= 0.0f + ? state->timestamp + : (float)(uptime_seconds() % MAX_TIMESTAMP); } static void fill_system_data(const remoteid_state_t *state, ODID_System_data *data) { - data->OperatorLocationType = ODID_OPERATOR_LOCATION_TYPE_TAKEOFF; - data->ClassificationType = ODID_CLASSIFICATION_TYPE_EU; - data->OperatorLatitude = state->has_position ? state->latitude : 0.0; - data->OperatorLongitude = state->has_position ? state->longitude : 0.0; - data->AreaCount = 1; - data->AreaRadius = 0; - data->AreaCeiling = (float)INV_ALT; - data->AreaFloor = (float)INV_ALT; - data->CategoryEU = state->eu_category; - data->ClassEU = state->eu_class; - data->OperatorAltitudeGeo = state->has_position ? state->altitude_m : (float)INV_ALT; - data->Timestamp = uptime_seconds(); + data->OperatorLocationType = state->operator_location_type; + data->ClassificationType = state->classification_type; + data->OperatorLatitude = state->has_operator_position ? state->operator_latitude : 0.0; + data->OperatorLongitude = state->has_operator_position ? state->operator_longitude : 0.0; + data->AreaCount = state->area_count; + data->AreaRadius = state->area_radius; + data->AreaCeiling = state->area_ceiling_m; + data->AreaFloor = state->area_floor_m; + data->CategoryEU = state->eu_category; + data->ClassEU = state->eu_class; + data->OperatorAltitudeGeo = state->has_operator_position + ? state->operator_altitude_geo_m + : (float)INV_ALT; + data->Timestamp = uptime_seconds(); } static void fill_operator_id_data(const remoteid_state_t *state, ODID_OperatorID_data *data) diff --git a/src/remoteid/remoteid/indicator.c b/src/remoteid/remoteid/indicator.c new file mode 100644 index 0000000..91f04b2 --- /dev/null +++ b/src/remoteid/remoteid/indicator.c @@ -0,0 +1,234 @@ +#include "indicator.h" + +#include "sdkconfig.h" + +#ifndef CONFIG_REMOTEID_INDICATOR_RGB_ENABLE +#define CONFIG_REMOTEID_INDICATOR_RGB_ENABLE 0 +#endif + +#if CONFIG_REMOTEID_INDICATOR_RGB_ENABLE + +#include +#include +#include + +#include "driver/rmt_encoder.h" +#include "driver/rmt_tx.h" +#include "esp_check.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "pattern.h" +#include "store.h" + +#define REMOTEID_INDICATOR_RMT_RESOLUTION_HZ 10000000 +#define REMOTEID_INDICATOR_TASK_STACK 3072 +#define REMOTEID_INDICATOR_TASK_PRIORITY 4 +#define REMOTEID_INDICATOR_LED_BYTES 3 + +typedef struct remoteid_indicator_color { + uint8_t red; + uint8_t green; + uint8_t blue; +} remoteid_indicator_color_t; + +typedef enum remoteid_indicator_mode { + REMOTEID_INDICATOR_MODE_WAITING, + REMOTEID_INDICATOR_MODE_OPERATIONAL, + REMOTEID_INDICATOR_MODE_ERROR, +} remoteid_indicator_mode_t; + +static const char *TAG = "remoteid_indicator"; + +static rmt_channel_handle_t s_rmt_channel; +static rmt_encoder_handle_t s_rmt_encoder; +static _Atomic bool s_transports_started; +static _Atomic bool s_error; + +static const remoteid_pattern_step_t s_status_waiting[] = { + { true, 250 }, + { false, 1750 }, +}; + +static const remoteid_pattern_step_t s_status_error[] = { + { true, 100 }, + { false, 100 }, +}; + +static uint8_t scale_channel(uint8_t value) +{ + return (uint8_t)(((uint16_t)value * CONFIG_REMOTEID_INDICATOR_RGB_BRIGHTNESS_PERCENT) / 100U); +} + +static esp_err_t set_rgb(remoteid_indicator_color_t color) +{ + uint8_t payload[REMOTEID_INDICATOR_LED_BYTES] = { + scale_channel(color.green), + scale_channel(color.red), + scale_channel(color.blue), + }; + const rmt_transmit_config_t tx_config = { + .loop_count = 0, + .flags.eot_level = 0, + }; + + ESP_RETURN_ON_ERROR(rmt_transmit(s_rmt_channel, s_rmt_encoder, payload, sizeof(payload), &tx_config), + TAG, "transmit onboard RGB LED data"); + ESP_RETURN_ON_ERROR(rmt_tx_wait_all_done(s_rmt_channel, 100), TAG, "wait for onboard RGB LED update"); + return ESP_OK; +} + +static void set_rgb_logged(remoteid_indicator_color_t color) +{ + esp_err_t rc = set_rgb(color); + if (rc != ESP_OK) { + ESP_LOGW(TAG, "failed to update onboard RGB LED: %s", esp_err_to_name(rc)); + } +} + +static const remoteid_pattern_step_t *operational_pattern(size_t *count) +{ +#if CONFIG_REMOTEID_INDICATOR_PATTERN_SOLID + return remoteid_pattern_steps(REMOTEID_PATTERN_SOLID, count); +#elif CONFIG_REMOTEID_INDICATOR_PATTERN_DRONE_BEACON_1HZ_50 + return remoteid_pattern_steps(REMOTEID_PATTERN_BEACON_1HZ_50, count); +#elif CONFIG_REMOTEID_INDICATOR_PATTERN_STROBE_SINGLE + return remoteid_pattern_steps(REMOTEID_PATTERN_STROBE_SINGLE, count); +#elif CONFIG_REMOTEID_INDICATOR_PATTERN_STROBE_DOUBLE + return remoteid_pattern_steps(REMOTEID_PATTERN_STROBE_DOUBLE, count); +#elif CONFIG_REMOTEID_INDICATOR_PATTERN_STROBE_TRIPLE + return remoteid_pattern_steps(REMOTEID_PATTERN_STROBE_TRIPLE, count); +#elif CONFIG_REMOTEID_INDICATOR_PATTERN_FAST_STROBE + return remoteid_pattern_steps(REMOTEID_PATTERN_FAST_STROBE, count); +#else + return remoteid_pattern_steps(REMOTEID_PATTERN_BEACON_1HZ_SHORT, count); +#endif +} + +static remoteid_indicator_mode_t current_mode(void) +{ + if (atomic_load_explicit(&s_error, memory_order_acquire)) { + return REMOTEID_INDICATOR_MODE_ERROR; + } + if (atomic_load_explicit(&s_transports_started, memory_order_acquire) && remoteid_store_is_ready()) { + return REMOTEID_INDICATOR_MODE_OPERATIONAL; + } + return REMOTEID_INDICATOR_MODE_WAITING; +} + +static const remoteid_pattern_step_t *pattern_for_mode(remoteid_indicator_mode_t mode, + size_t *count, + remoteid_indicator_color_t *color) +{ + if (mode == REMOTEID_INDICATOR_MODE_ERROR) { + *count = sizeof(s_status_error) / sizeof(s_status_error[0]); + *color = (remoteid_indicator_color_t){ .red = 255, .green = 0, .blue = 0 }; + return s_status_error; + } + + if (mode == REMOTEID_INDICATOR_MODE_OPERATIONAL) { + *color = (remoteid_indicator_color_t){ .red = 0, .green = 255, .blue = 0 }; + return operational_pattern(count); + } + + *count = sizeof(s_status_waiting) / sizeof(s_status_waiting[0]); + *color = (remoteid_indicator_color_t){ .red = 255, .green = 128, .blue = 0 }; + return s_status_waiting; +} + +static void remoteid_indicator_task(void *arg) +{ + (void)arg; + set_rgb_logged((remoteid_indicator_color_t){ 0 }); + + while (true) { + size_t step_count = 0; + remoteid_indicator_color_t color = { 0 }; + remoteid_indicator_mode_t mode = current_mode(); + const remoteid_pattern_step_t *pattern = pattern_for_mode(mode, &step_count, &color); + + for (size_t i = 0; i < step_count; i++) { + set_rgb_logged(pattern[i].on ? color : (remoteid_indicator_color_t){ 0 }); + vTaskDelay(pdMS_TO_TICKS(pattern[i].duration_ms)); + + if (current_mode() != mode) { + break; + } + } + } +} + +esp_err_t remoteid_indicator_init(void) +{ + if (CONFIG_REMOTEID_INDICATOR_RGB_GPIO < 0) { + ESP_LOGW(TAG, "onboard RGB indicator enabled but no GPIO configured; indicator disabled"); + return ESP_OK; + } + + const rmt_tx_channel_config_t channel_config = { + .gpio_num = CONFIG_REMOTEID_INDICATOR_RGB_GPIO, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = REMOTEID_INDICATOR_RMT_RESOLUTION_HZ, + .mem_block_symbols = 64, + .trans_queue_depth = 1, + .flags.init_level = 0, + }; + ESP_RETURN_ON_ERROR(rmt_new_tx_channel(&channel_config, &s_rmt_channel), TAG, + "create onboard RGB LED RMT channel"); + + const rmt_bytes_encoder_config_t encoder_config = { + .bit0 = { + .level0 = 1, + .duration0 = 3, + .level1 = 0, + .duration1 = 9, + }, + .bit1 = { + .level0 = 1, + .duration0 = 9, + .level1 = 0, + .duration1 = 3, + }, + .flags.msb_first = 1, + }; + ESP_RETURN_ON_ERROR(rmt_new_bytes_encoder(&encoder_config, &s_rmt_encoder), TAG, + "create onboard RGB LED RMT encoder"); + ESP_RETURN_ON_ERROR(rmt_enable(s_rmt_channel), TAG, "enable onboard RGB LED RMT channel"); + + if (xTaskCreate(remoteid_indicator_task, "remoteid_indicator", REMOTEID_INDICATOR_TASK_STACK, + NULL, REMOTEID_INDICATOR_TASK_PRIORITY, NULL) != pdPASS) { + ESP_LOGE(TAG, "failed to create onboard RGB indicator task"); + return ESP_ERR_NO_MEM; + } + + ESP_LOGI(TAG, "onboard RGB indicator enabled on GPIO %d, brightness %d%%", + CONFIG_REMOTEID_INDICATOR_RGB_GPIO, CONFIG_REMOTEID_INDICATOR_RGB_BRIGHTNESS_PERCENT); + return ESP_OK; +} + +void remoteid_indicator_mark_transports_started(void) +{ + atomic_store_explicit(&s_transports_started, true, memory_order_release); +} + +void remoteid_indicator_set_error(void) +{ + atomic_store_explicit(&s_error, true, memory_order_release); +} + +#else + +esp_err_t remoteid_indicator_init(void) +{ + return ESP_OK; +} + +void remoteid_indicator_mark_transports_started(void) +{ +} + +void remoteid_indicator_set_error(void) +{ +} + +#endif diff --git a/src/remoteid/remoteid/indicator.h b/src/remoteid/remoteid/indicator.h new file mode 100644 index 0000000..1271ed3 --- /dev/null +++ b/src/remoteid/remoteid/indicator.h @@ -0,0 +1,7 @@ +#pragma once + +#include "esp_err.h" + +esp_err_t remoteid_indicator_init(void); +void remoteid_indicator_mark_transports_started(void); +void remoteid_indicator_set_error(void); diff --git a/src/remoteid/remoteid/led.c b/src/remoteid/remoteid/led.c deleted file mode 100644 index a4dc088..0000000 --- a/src/remoteid/remoteid/led.c +++ /dev/null @@ -1,87 +0,0 @@ -#include "led.h" - -#include "driver/gpio.h" -#include "esp_check.h" -#include "esp_log.h" -#include "esp_timer.h" -#include "sdkconfig.h" - -#ifndef CONFIG_REMOTEID_TX_LED_GPIO -#define CONFIG_REMOTEID_TX_LED_GPIO -1 -#endif - -#ifndef CONFIG_REMOTEID_TX_LED_ACTIVE_HIGH -#define CONFIG_REMOTEID_TX_LED_ACTIVE_HIGH 1 -#endif - -#ifndef CONFIG_REMOTEID_TX_LED_PULSE_MS -#define CONFIG_REMOTEID_TX_LED_PULSE_MS 40 -#endif - -static const char *TAG = "remoteid_led"; - -#if CONFIG_REMOTEID_TX_LED_GPIO >= 0 -static esp_timer_handle_t s_led_timer; - -static int led_on_level(void) -{ - return CONFIG_REMOTEID_TX_LED_ACTIVE_HIGH ? 1 : 0; -} - -static int led_off_level(void) -{ - return CONFIG_REMOTEID_TX_LED_ACTIVE_HIGH ? 0 : 1; -} - -static void turn_led_off(void *arg) -{ - (void)arg; - gpio_set_level(CONFIG_REMOTEID_TX_LED_GPIO, led_off_level()); -} - -esp_err_t remoteid_led_init(void) -{ - gpio_config_t config = { - .pin_bit_mask = 1ULL << CONFIG_REMOTEID_TX_LED_GPIO, - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - ESP_RETURN_ON_ERROR(gpio_config(&config), TAG, "configure TX status LED GPIO"); - ESP_RETURN_ON_ERROR(gpio_set_level(CONFIG_REMOTEID_TX_LED_GPIO, led_off_level()), TAG, "turn TX status LED off"); - - const esp_timer_create_args_t timer_args = { - .callback = turn_led_off, - .name = "remoteid_tx_led", - }; - ESP_RETURN_ON_ERROR(esp_timer_create(&timer_args, &s_led_timer), TAG, "create TX status LED timer"); - - ESP_LOGI(TAG, "TX status LED enabled on GPIO %d, active %s, pulse %d ms", CONFIG_REMOTEID_TX_LED_GPIO, - CONFIG_REMOTEID_TX_LED_ACTIVE_HIGH ? "high" : "low", CONFIG_REMOTEID_TX_LED_PULSE_MS); - - return ESP_OK; -} - -void remoteid_led_pulse(void) -{ - gpio_set_level(CONFIG_REMOTEID_TX_LED_GPIO, led_on_level()); - esp_timer_stop(s_led_timer); - esp_err_t rc = esp_timer_start_once(s_led_timer, CONFIG_REMOTEID_TX_LED_PULSE_MS * 1000ULL); - if (rc != ESP_OK) { - ESP_LOGW(TAG, "failed to start TX status LED pulse timer: %s", esp_err_to_name(rc)); - gpio_set_level(CONFIG_REMOTEID_TX_LED_GPIO, led_off_level()); - } -} -#else -esp_err_t remoteid_led_init(void) -{ - ESP_LOGI(TAG, "TX status LED disabled; set ESP Remote ID -> TX status LED GPIO to pulse an LED"); - return ESP_OK; -} - -void remoteid_led_pulse(void) -{ - // TODO Implement -} -#endif diff --git a/src/remoteid/remoteid/led.h b/src/remoteid/remoteid/led.h deleted file mode 100644 index 27582cf..0000000 --- a/src/remoteid/remoteid/led.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include "esp_err.h" - -esp_err_t remoteid_led_init(void); -void remoteid_led_pulse(void); diff --git a/src/remoteid/remoteid/lighting.c b/src/remoteid/remoteid/lighting.c new file mode 100644 index 0000000..11f4a5a --- /dev/null +++ b/src/remoteid/remoteid/lighting.c @@ -0,0 +1,365 @@ +#include "lighting.h" + +#include +#include +#include + +#include "pattern.h" + +#include "driver/gpio.h" +#include "esp_check.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" +#include "store.h" + +#ifndef CONFIG_REMOTEID_LIGHTING_ENABLE +#define CONFIG_REMOTEID_LIGHTING_ENABLE 0 +#endif + +#define REMOTEID_LIGHTING_OUTPUT_COUNT 5 +#define REMOTEID_LIGHTING_TASK_STACK 3072 +#define REMOTEID_LIGHTING_TASK_PRIORITY 4 +#define REMOTEID_LIGHTING_TICK_MS 20 + +#if CONFIG_REMOTEID_LIGHTING_ENABLE + +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT0_ENABLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT0_ENABLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT0_GPIO +#define CONFIG_REMOTEID_LIGHTING_OUTPUT0_GPIO -1 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT0_ACTIVE_HIGH +#define CONFIG_REMOTEID_LIGHTING_OUTPUT0_ACTIVE_HIGH 1 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT0_OPEN_DRAIN +#define CONFIG_REMOTEID_LIGHTING_OUTPUT0_OPEN_DRAIN 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT0_PATTERN_SOLID +#define CONFIG_REMOTEID_LIGHTING_OUTPUT0_PATTERN_SOLID 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT0_PATTERN_BEACON_1HZ_50 +#define CONFIG_REMOTEID_LIGHTING_OUTPUT0_PATTERN_BEACON_1HZ_50 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT0_PATTERN_STROBE_SINGLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT0_PATTERN_STROBE_SINGLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT0_PATTERN_STROBE_DOUBLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT0_PATTERN_STROBE_DOUBLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT0_PATTERN_STROBE_TRIPLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT0_PATTERN_STROBE_TRIPLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT0_PATTERN_FAST_STROBE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT0_PATTERN_FAST_STROBE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT0_PHASE_MS +#define CONFIG_REMOTEID_LIGHTING_OUTPUT0_PHASE_MS 0 +#endif + +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT1_ENABLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT1_ENABLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT1_GPIO +#define CONFIG_REMOTEID_LIGHTING_OUTPUT1_GPIO -1 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT1_ACTIVE_HIGH +#define CONFIG_REMOTEID_LIGHTING_OUTPUT1_ACTIVE_HIGH 1 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT1_OPEN_DRAIN +#define CONFIG_REMOTEID_LIGHTING_OUTPUT1_OPEN_DRAIN 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT1_PATTERN_SOLID +#define CONFIG_REMOTEID_LIGHTING_OUTPUT1_PATTERN_SOLID 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT1_PATTERN_BEACON_1HZ_50 +#define CONFIG_REMOTEID_LIGHTING_OUTPUT1_PATTERN_BEACON_1HZ_50 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT1_PATTERN_STROBE_SINGLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT1_PATTERN_STROBE_SINGLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT1_PATTERN_STROBE_DOUBLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT1_PATTERN_STROBE_DOUBLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT1_PATTERN_STROBE_TRIPLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT1_PATTERN_STROBE_TRIPLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT1_PATTERN_FAST_STROBE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT1_PATTERN_FAST_STROBE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT1_PHASE_MS +#define CONFIG_REMOTEID_LIGHTING_OUTPUT1_PHASE_MS 0 +#endif + +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT2_ENABLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT2_ENABLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT2_GPIO +#define CONFIG_REMOTEID_LIGHTING_OUTPUT2_GPIO -1 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT2_ACTIVE_HIGH +#define CONFIG_REMOTEID_LIGHTING_OUTPUT2_ACTIVE_HIGH 1 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT2_OPEN_DRAIN +#define CONFIG_REMOTEID_LIGHTING_OUTPUT2_OPEN_DRAIN 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT2_PATTERN_SOLID +#define CONFIG_REMOTEID_LIGHTING_OUTPUT2_PATTERN_SOLID 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT2_PATTERN_BEACON_1HZ_50 +#define CONFIG_REMOTEID_LIGHTING_OUTPUT2_PATTERN_BEACON_1HZ_50 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT2_PATTERN_STROBE_SINGLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT2_PATTERN_STROBE_SINGLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT2_PATTERN_STROBE_DOUBLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT2_PATTERN_STROBE_DOUBLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT2_PATTERN_STROBE_TRIPLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT2_PATTERN_STROBE_TRIPLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT2_PATTERN_FAST_STROBE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT2_PATTERN_FAST_STROBE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT2_PHASE_MS +#define CONFIG_REMOTEID_LIGHTING_OUTPUT2_PHASE_MS 0 +#endif + +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT3_ENABLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT3_ENABLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT3_GPIO +#define CONFIG_REMOTEID_LIGHTING_OUTPUT3_GPIO -1 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT3_ACTIVE_HIGH +#define CONFIG_REMOTEID_LIGHTING_OUTPUT3_ACTIVE_HIGH 1 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT3_OPEN_DRAIN +#define CONFIG_REMOTEID_LIGHTING_OUTPUT3_OPEN_DRAIN 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT3_PATTERN_SOLID +#define CONFIG_REMOTEID_LIGHTING_OUTPUT3_PATTERN_SOLID 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT3_PATTERN_BEACON_1HZ_50 +#define CONFIG_REMOTEID_LIGHTING_OUTPUT3_PATTERN_BEACON_1HZ_50 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT3_PATTERN_STROBE_SINGLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT3_PATTERN_STROBE_SINGLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT3_PATTERN_STROBE_DOUBLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT3_PATTERN_STROBE_DOUBLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT3_PATTERN_STROBE_TRIPLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT3_PATTERN_STROBE_TRIPLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT3_PATTERN_FAST_STROBE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT3_PATTERN_FAST_STROBE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT3_PHASE_MS +#define CONFIG_REMOTEID_LIGHTING_OUTPUT3_PHASE_MS 0 +#endif + +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT4_ENABLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT4_ENABLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT4_GPIO +#define CONFIG_REMOTEID_LIGHTING_OUTPUT4_GPIO -1 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT4_ACTIVE_HIGH +#define CONFIG_REMOTEID_LIGHTING_OUTPUT4_ACTIVE_HIGH 1 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT4_OPEN_DRAIN +#define CONFIG_REMOTEID_LIGHTING_OUTPUT4_OPEN_DRAIN 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT4_PATTERN_SOLID +#define CONFIG_REMOTEID_LIGHTING_OUTPUT4_PATTERN_SOLID 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT4_PATTERN_BEACON_1HZ_50 +#define CONFIG_REMOTEID_LIGHTING_OUTPUT4_PATTERN_BEACON_1HZ_50 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT4_PATTERN_STROBE_SINGLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT4_PATTERN_STROBE_SINGLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT4_PATTERN_STROBE_DOUBLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT4_PATTERN_STROBE_DOUBLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT4_PATTERN_STROBE_TRIPLE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT4_PATTERN_STROBE_TRIPLE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT4_PATTERN_FAST_STROBE +#define CONFIG_REMOTEID_LIGHTING_OUTPUT4_PATTERN_FAST_STROBE 0 +#endif +#ifndef CONFIG_REMOTEID_LIGHTING_OUTPUT4_PHASE_MS +#define CONFIG_REMOTEID_LIGHTING_OUTPUT4_PHASE_MS 0 +#endif + +typedef struct remoteid_lighting_output_config { + bool enabled; + int gpio; + bool active_high; + bool open_drain; + remoteid_pattern_t pattern; + uint32_t phase_ms; +} remoteid_lighting_output_config_t; + +typedef struct remoteid_lighting_output_state { + bool configured; +} remoteid_lighting_output_state_t; + +static const char *TAG = "remoteid_lighting"; + +static _Atomic bool s_transports_started; +static int64_t s_start_us; +static remoteid_lighting_output_state_t s_outputs[REMOTEID_LIGHTING_OUTPUT_COUNT]; + +#define OUTPUT_PATTERN(idx) \ + (CONFIG_REMOTEID_LIGHTING_OUTPUT##idx##_PATTERN_SOLID ? REMOTEID_PATTERN_SOLID : \ + CONFIG_REMOTEID_LIGHTING_OUTPUT##idx##_PATTERN_BEACON_1HZ_50 ? REMOTEID_PATTERN_BEACON_1HZ_50 : \ + CONFIG_REMOTEID_LIGHTING_OUTPUT##idx##_PATTERN_STROBE_SINGLE ? REMOTEID_PATTERN_STROBE_SINGLE : \ + CONFIG_REMOTEID_LIGHTING_OUTPUT##idx##_PATTERN_STROBE_DOUBLE ? REMOTEID_PATTERN_STROBE_DOUBLE : \ + CONFIG_REMOTEID_LIGHTING_OUTPUT##idx##_PATTERN_STROBE_TRIPLE ? REMOTEID_PATTERN_STROBE_TRIPLE : \ + CONFIG_REMOTEID_LIGHTING_OUTPUT##idx##_PATTERN_FAST_STROBE ? REMOTEID_PATTERN_FAST_STROBE : \ + REMOTEID_PATTERN_BEACON_1HZ_SHORT) + +#define OUTPUT_CONFIG(idx) \ + { \ + .enabled = CONFIG_REMOTEID_LIGHTING_OUTPUT##idx##_ENABLE, \ + .gpio = CONFIG_REMOTEID_LIGHTING_OUTPUT##idx##_GPIO, \ + .active_high = CONFIG_REMOTEID_LIGHTING_OUTPUT##idx##_ACTIVE_HIGH, \ + .open_drain = CONFIG_REMOTEID_LIGHTING_OUTPUT##idx##_OPEN_DRAIN, \ + .pattern = OUTPUT_PATTERN(idx), \ + .phase_ms = CONFIG_REMOTEID_LIGHTING_OUTPUT##idx##_PHASE_MS, \ + } + +static const remoteid_lighting_output_config_t s_config[REMOTEID_LIGHTING_OUTPUT_COUNT] = { + OUTPUT_CONFIG(0), + OUTPUT_CONFIG(1), + OUTPUT_CONFIG(2), + OUTPUT_CONFIG(3), + OUTPUT_CONFIG(4), +}; + +static int output_level(const remoteid_lighting_output_config_t *config, bool on) +{ + bool active = config->active_high ? on : !on; + return active ? 1 : 0; +} + +static void set_output(size_t index, bool on) +{ + if (!s_outputs[index].configured) { + return; + } + + gpio_set_level(s_config[index].gpio, output_level(&s_config[index], on)); +} + + +static bool output_should_be_on(size_t index, int64_t now_us) +{ + const remoteid_lighting_output_config_t *config = &s_config[index]; + + if (!s_outputs[index].configured) { + return false; + } + + if (!atomic_load_explicit(&s_transports_started, memory_order_acquire) || !remoteid_store_is_ready()) { + return false; + } + + uint32_t elapsed_ms = (uint32_t)((now_us - s_start_us) / 1000LL); + return remoteid_pattern_is_on(config->pattern, elapsed_ms, config->phase_ms); +} + +static void lighting_task(void *arg) +{ + (void)arg; + + while (true) { + int64_t now_us = esp_timer_get_time(); + for (size_t i = 0; i < REMOTEID_LIGHTING_OUTPUT_COUNT; i++) { + set_output(i, output_should_be_on(i, now_us)); + } + vTaskDelay(pdMS_TO_TICKS(REMOTEID_LIGHTING_TICK_MS)); + } +} + +static bool gpio_already_used(size_t current_index) +{ + for (size_t i = 0; i < current_index; i++) { + if (s_outputs[i].configured && s_config[i].gpio == s_config[current_index].gpio) { + return true; + } + } + return false; +} + +static esp_err_t configure_output(size_t index) +{ + const remoteid_lighting_output_config_t *config = &s_config[index]; + + if (!config->enabled) { + return ESP_OK; + } + if (config->gpio < 0) { + ESP_LOGW(TAG, "lighting output %u enabled without GPIO; output disabled", index); + return ESP_OK; + } + if (gpio_already_used(index)) { + ESP_LOGW(TAG, "lighting output %u duplicates GPIO %d; output disabled", index, config->gpio); + return ESP_OK; + } + + gpio_config_t pin_config = { + .pin_bit_mask = 1ULL << config->gpio, + .mode = config->open_drain ? GPIO_MODE_OUTPUT_OD : GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + ESP_RETURN_ON_ERROR(gpio_config(&pin_config), TAG, "configure lighting output GPIO"); + + s_outputs[index].configured = true; + set_output(index, false); + ESP_LOGI(TAG, "lighting output %u enabled on GPIO %d (pattern=%d phase=%lu ms)", + index, config->gpio, config->pattern, config->phase_ms); + return ESP_OK; +} + +esp_err_t remoteid_lighting_init(void) +{ + s_start_us = esp_timer_get_time(); + + for (size_t i = 0; i < REMOTEID_LIGHTING_OUTPUT_COUNT; i++) { + ESP_RETURN_ON_ERROR(configure_output(i), TAG, "configure lighting output"); + } + + if (xTaskCreate(lighting_task, "remoteid_lighting", REMOTEID_LIGHTING_TASK_STACK, + NULL, REMOTEID_LIGHTING_TASK_PRIORITY, NULL) != pdPASS) { + ESP_LOGE(TAG, "failed to create lighting task"); + return ESP_ERR_NO_MEM; + } + + return ESP_OK; +} + +void remoteid_lighting_mark_transports_started(void) +{ + atomic_store_explicit(&s_transports_started, true, memory_order_release); +} + +#else + +esp_err_t remoteid_lighting_init(void) +{ + return ESP_OK; +} + +void remoteid_lighting_mark_transports_started(void) +{ +} + +#endif diff --git a/src/remoteid/remoteid/lighting.h b/src/remoteid/remoteid/lighting.h new file mode 100644 index 0000000..0802ede --- /dev/null +++ b/src/remoteid/remoteid/lighting.h @@ -0,0 +1,6 @@ +#pragma once + +#include "esp_err.h" + +esp_err_t remoteid_lighting_init(void); +void remoteid_lighting_mark_transports_started(void); diff --git a/src/remoteid/remoteid/mavlink.c b/src/remoteid/remoteid/mavlink.c new file mode 100644 index 0000000..b6b604e --- /dev/null +++ b/src/remoteid/remoteid/mavlink.c @@ -0,0 +1,274 @@ +#include "mavlink.h" + +#include "sdkconfig.h" + +#if CONFIG_REMOTEID_MAVLINK_INPUT + +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Waddress-of-packed-member" +#include "common/mavlink.h" +#pragma GCC diagnostic pop +#include "driver/uart.h" +#include "esp_check.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "store.h" + +#define REMOTEID_MAVLINK_BUF_SIZE 256 +#define REMOTEID_MAVLINK_QUEUE_SIZE 0 +#define REMOTEID_MAVLINK_TASK_PRIORITY 5 + +static const char *TAG = "remoteid_mavlink"; + +static bool target_matches(uint8_t target_system, uint8_t target_component) +{ + if (CONFIG_REMOTEID_MAVLINK_TARGET_SYSTEM != 0 && + target_system != 0 && + target_system != CONFIG_REMOTEID_MAVLINK_TARGET_SYSTEM) { + return false; + } + + if (CONFIG_REMOTEID_MAVLINK_TARGET_COMPONENT != 0 && + target_component != 0 && + target_component != CONFIG_REMOTEID_MAVLINK_TARGET_COMPONENT) { + return false; + } + + return true; +} + +static void copy_mavlink_text(char *dst, size_t dst_len, const void *src, size_t src_len) +{ + size_t len = 0; + const char *text = (const char *)src; + + while (len < src_len && text[len] != '\0') { + len++; + } + + if (len >= dst_len) { + len = dst_len - 1; + } + + memcpy(dst, text, len); + dst[len] = '\0'; +} + +static void submit_basic_id(const mavlink_message_t *message) +{ + mavlink_open_drone_id_basic_id_t msg = { 0 }; + mavlink_msg_open_drone_id_basic_id_decode(message, &msg); + + if (!target_matches(msg.target_system, msg.target_component)) { + ESP_LOGD(TAG, "ignored Basic ID for target system=%u component=%u", msg.target_system, msg.target_component); + return; + } + + remoteid_store_update_t update = { + .type = REMOTEID_STORE_UPDATE_BASIC_ID, + .data.basic_id.id_type = (ODID_idtype_t)msg.id_type, + .data.basic_id.ua_type = (ODID_uatype_t)msg.ua_type, + }; + copy_mavlink_text(update.data.basic_id.uas_id, sizeof(update.data.basic_id.uas_id), + msg.uas_id, sizeof(msg.uas_id)); + + if (remoteid_store_submit(&update, pdMS_TO_TICKS(100)) == ESP_OK) { + ESP_LOGI(TAG, "accepted MAVLink Basic ID"); + } else { + ESP_LOGW(TAG, "dropped MAVLink Basic ID update: state queue full"); + } +} + +static void submit_operator_id(const mavlink_message_t *message) +{ + mavlink_open_drone_id_operator_id_t msg = { 0 }; + mavlink_msg_open_drone_id_operator_id_decode(message, &msg); + + if (!target_matches(msg.target_system, msg.target_component)) { + ESP_LOGD(TAG, "ignored Operator ID for target system=%u component=%u", msg.target_system, msg.target_component); + return; + } + + remoteid_store_update_t update = { + .type = REMOTEID_STORE_UPDATE_OPERATOR_ID, + }; + copy_mavlink_text(update.data.operator_id.operator_id, sizeof(update.data.operator_id.operator_id), + msg.operator_id, sizeof(msg.operator_id)); + + if (remoteid_store_submit(&update, pdMS_TO_TICKS(100)) == ESP_OK) { + ESP_LOGI(TAG, "accepted MAVLink Operator ID"); + } else { + ESP_LOGW(TAG, "dropped MAVLink Operator ID update: state queue full"); + } +} + +static void submit_location(const mavlink_message_t *message) +{ + mavlink_open_drone_id_location_t msg = { 0 }; + mavlink_msg_open_drone_id_location_decode(message, &msg); + + if (!target_matches(msg.target_system, msg.target_component)) { + ESP_LOGD(TAG, "ignored Location for target system=%u component=%u", msg.target_system, msg.target_component); + return; + } + + bool has_position = msg.latitude != 0 || msg.longitude != 0; + + remoteid_store_update_t update = { + .type = REMOTEID_STORE_UPDATE_LOCATION, + .data.location.has_position = has_position, + .data.location.latitude = (double)msg.latitude / 10000000.0, + .data.location.longitude = (double)msg.longitude / 10000000.0, + .data.location.altitude_geo_m = msg.altitude_geodetic <= (float)INV_ALT ? (float)INV_ALT : msg.altitude_geodetic, + .data.location.altitude_baro_m = msg.altitude_barometric <= (float)INV_ALT ? (float)INV_ALT : msg.altitude_barometric, + .data.location.status = (ODID_status_t)msg.status, + .data.location.speed_horizontal = msg.speed_horizontal >= (float)INV_SPEED_H ? (float)INV_SPEED_H : msg.speed_horizontal, + .data.location.speed_vertical = msg.speed_vertical >= (float)INV_SPEED_V ? (float)INV_SPEED_V : msg.speed_vertical, + .data.location.direction = msg.direction >= 36100 + ? (float)INV_DIR + : msg.direction / 100.0f, + .data.location.height = msg.height <= (float)INV_ALT ? (float)INV_ALT : msg.height, + .data.location.height_type = (ODID_Height_reference_t)msg.height_reference, + .data.location.horiz_acc = (ODID_Horizontal_accuracy_t)msg.horizontal_accuracy, + .data.location.vert_acc = (ODID_Vertical_accuracy_t)msg.vertical_accuracy, + .data.location.baro_acc = (ODID_Vertical_accuracy_t)msg.barometer_accuracy, + .data.location.speed_acc = (ODID_Speed_accuracy_t)msg.speed_accuracy, + .data.location.ts_acc = (ODID_Timestamp_accuracy_t)msg.timestamp_accuracy, + .data.location.timestamp = msg.timestamp >= (float)MAX_TIMESTAMP ? -1.0f : msg.timestamp, + }; + + if (remoteid_store_submit(&update, pdMS_TO_TICKS(100)) == ESP_OK) { + ESP_LOGD(TAG, "accepted MAVLink Location (has_position=%d status=%u)", has_position, msg.status); + } else { + ESP_LOGW(TAG, "dropped MAVLink Location update: state queue full"); + } +} + +static void submit_system(const mavlink_message_t *message) +{ + mavlink_open_drone_id_system_t msg = { 0 }; + mavlink_msg_open_drone_id_system_decode(message, &msg); + + if (!target_matches(msg.target_system, msg.target_component)) { + ESP_LOGD(TAG, "ignored System for target system=%u component=%u", msg.target_system, msg.target_component); + return; + } + + bool has_operator_position = msg.operator_latitude != 0 || msg.operator_longitude != 0; + + remoteid_store_update_t update = { + .type = REMOTEID_STORE_UPDATE_SYSTEM, + .data.system.has_operator_position = has_operator_position, + .data.system.operator_latitude = (double)msg.operator_latitude / 10000000.0, + .data.system.operator_longitude = (double)msg.operator_longitude / 10000000.0, + .data.system.operator_altitude_geo_m = msg.operator_altitude_geo <= (float)INV_ALT ? (float)INV_ALT : msg.operator_altitude_geo, + .data.system.operator_location_type = (ODID_operator_location_type_t)msg.operator_location_type, + .data.system.area_count = msg.area_count, + .data.system.area_radius = msg.area_radius, + .data.system.area_ceiling_m = msg.area_ceiling, + .data.system.area_floor_m = msg.area_floor, + .data.system.classification_type = (ODID_classification_type_t)msg.classification_type, + .data.system.eu_category = (ODID_category_EU_t)msg.category_eu, + .data.system.eu_class = (ODID_class_EU_t)msg.class_eu, + }; + + if (remoteid_store_submit(&update, pdMS_TO_TICKS(100)) == ESP_OK) { + ESP_LOGD(TAG, "accepted MAVLink System (has_operator_position=%d)", has_operator_position); + } else { + ESP_LOGW(TAG, "dropped MAVLink System update: state queue full"); + } +} + +static void handle_message(const mavlink_message_t *message) +{ + switch (message->msgid) { + case MAVLINK_MSG_ID_OPEN_DRONE_ID_BASIC_ID: + submit_basic_id(message); + break; + case MAVLINK_MSG_ID_OPEN_DRONE_ID_OPERATOR_ID: + submit_operator_id(message); + break; + case MAVLINK_MSG_ID_OPEN_DRONE_ID_LOCATION: + submit_location(message); + break; + case MAVLINK_MSG_ID_OPEN_DRONE_ID_SYSTEM: + submit_system(message); + break; + default: + break; + } +} + +static void remoteid_mavlink_task(void *arg) +{ + (void)arg; + uint8_t buf[REMOTEID_MAVLINK_BUF_SIZE]; + mavlink_message_t message; + mavlink_status_t status; + + memset(&message, 0, sizeof(message)); + memset(&status, 0, sizeof(status)); + + while (true) { + int len = uart_read_bytes(CONFIG_REMOTEID_MAVLINK_UART_NUM, buf, sizeof(buf), pdMS_TO_TICKS(1000)); + for (int i = 0; i < len; i++) { + if (mavlink_parse_char(MAVLINK_COMM_0, buf[i], &message, &status)) { + handle_message(&message); + } + } + } +} + +esp_err_t remoteid_mavlink_start(void) +{ + ESP_RETURN_ON_FALSE(CONFIG_REMOTEID_MAVLINK_RX_GPIO >= 0, ESP_ERR_INVALID_ARG, TAG, + "MAVLink RX GPIO must be configured when MAVLink input is enabled"); + + uart_config_t uart_config = { + .baud_rate = CONFIG_REMOTEID_MAVLINK_BAUD, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .source_clk = UART_SCLK_DEFAULT, + }; + + int tx_pin = CONFIG_REMOTEID_MAVLINK_TX_GPIO >= 0 ? CONFIG_REMOTEID_MAVLINK_TX_GPIO : UART_PIN_NO_CHANGE; + ESP_RETURN_ON_ERROR(uart_driver_install(CONFIG_REMOTEID_MAVLINK_UART_NUM, + REMOTEID_MAVLINK_BUF_SIZE * 2, + 0, + REMOTEID_MAVLINK_QUEUE_SIZE, + NULL, + 0), + TAG, "install MAVLink UART driver"); + ESP_RETURN_ON_ERROR(uart_param_config(CONFIG_REMOTEID_MAVLINK_UART_NUM, &uart_config), + TAG, "configure MAVLink UART"); + ESP_RETURN_ON_ERROR(uart_set_pin(CONFIG_REMOTEID_MAVLINK_UART_NUM, + tx_pin, + CONFIG_REMOTEID_MAVLINK_RX_GPIO, + UART_PIN_NO_CHANGE, + UART_PIN_NO_CHANGE), + TAG, "configure MAVLink UART pins"); + + if (xTaskCreate(remoteid_mavlink_task, "remoteid_mavlink", CONFIG_REMOTEID_MAVLINK_TASK_STACK, + NULL, REMOTEID_MAVLINK_TASK_PRIORITY, NULL) != pdPASS) { + ESP_LOGE(TAG, "failed to create MAVLink input task"); + return ESP_ERR_NO_MEM; + } + + ESP_LOGI(TAG, "MAVLink OpenDroneID input started on UART%d at %d baud", + CONFIG_REMOTEID_MAVLINK_UART_NUM, CONFIG_REMOTEID_MAVLINK_BAUD); + return ESP_OK; +} + +#else + +esp_err_t remoteid_mavlink_start(void) +{ + return ESP_OK; +} + +#endif diff --git a/src/remoteid/remoteid/mavlink.h b/src/remoteid/remoteid/mavlink.h new file mode 100644 index 0000000..cf0b83f --- /dev/null +++ b/src/remoteid/remoteid/mavlink.h @@ -0,0 +1,5 @@ +#pragma once + +#include "esp_err.h" + +esp_err_t remoteid_mavlink_start(void); diff --git a/src/remoteid/remoteid/model.c b/src/remoteid/remoteid/model.c index 7919afe..3a1d909 100644 --- a/src/remoteid/remoteid/model.c +++ b/src/remoteid/remoteid/model.c @@ -68,19 +68,50 @@ esp_err_t remoteid_model_init(remoteid_state_t *state) strncpy(state->operator_id, REMOTEID_OPERATOR_ID, sizeof(state->operator_id) - 1); state->id_type = configured_id_type(); state->ua_type = configured_ua_type(); - state->eu_category = configured_eu_category(); - state->eu_class = configured_eu_class(); + + state->speed_horizontal = (float)INV_SPEED_H; + state->speed_vertical = (float)INV_SPEED_V; + state->direction = (float)INV_DIR; + state->height = (float)INV_ALT; + state->height_type = ODID_HEIGHT_REF_OVER_TAKEOFF; + state->horiz_acc = ODID_HOR_ACC_UNKNOWN; + state->vert_acc = ODID_VER_ACC_UNKNOWN; + state->baro_acc = ODID_VER_ACC_UNKNOWN; + state->speed_acc = ODID_SPEED_ACC_UNKNOWN; + state->ts_acc = ODID_TIME_ACC_UNKNOWN; + state->timestamp = -1.0f; + + state->operator_location_type = ODID_OPERATOR_LOCATION_TYPE_TAKEOFF; + state->area_count = 1; + state->area_radius = 0; + state->area_ceiling_m = (float)INV_ALT; + state->area_floor_m = (float)INV_ALT; + state->classification_type = ODID_CLASSIFICATION_TYPE_EU; + state->eu_category = configured_eu_category(); + state->eu_class = configured_eu_class(); #if CONFIG_REMOTEID_HAS_POSITION - state->has_position = true; - state->latitude = REMOTEID_TAKEOFF_LATITUDE; - state->longitude = REMOTEID_TAKEOFF_LONGITUDE; - state->altitude_m = REMOTEID_TAKEOFF_ALTITUDE_M; + state->has_position = true; + state->latitude = REMOTEID_TAKEOFF_LATITUDE; + state->longitude = REMOTEID_TAKEOFF_LONGITUDE; + state->altitude_geo_m = REMOTEID_TAKEOFF_ALTITUDE_M; + state->altitude_baro_m = REMOTEID_TAKEOFF_ALTITUDE_M; + state->status = ODID_STATUS_AIRBORNE; + state->has_operator_position = true; + state->operator_latitude = REMOTEID_TAKEOFF_LATITUDE; + state->operator_longitude = REMOTEID_TAKEOFF_LONGITUDE; + state->operator_altitude_geo_m = REMOTEID_TAKEOFF_ALTITUDE_M; #else - state->has_position = false; - state->latitude = 0.0; - state->longitude = 0.0; - state->altitude_m = (float)INV_ALT; + state->has_position = false; + state->latitude = 0.0; + state->longitude = 0.0; + state->altitude_geo_m = (float)INV_ALT; + state->altitude_baro_m = (float)INV_ALT; + state->status = ODID_STATUS_UNDECLARED; + state->has_operator_position = false; + state->operator_latitude = 0.0; + state->operator_longitude = 0.0; + state->operator_altitude_geo_m = (float)INV_ALT; #endif return ESP_OK; diff --git a/src/remoteid/remoteid/pattern.c b/src/remoteid/remoteid/pattern.c new file mode 100644 index 0000000..90d634c --- /dev/null +++ b/src/remoteid/remoteid/pattern.c @@ -0,0 +1,107 @@ +#include "pattern.h" + +static const remoteid_pattern_step_t s_solid[] = { + { true, 1000 }, +}; + +static const remoteid_pattern_step_t s_beacon_1hz_short[] = { + { true, 100 }, + { false, 900 }, +}; + +static const remoteid_pattern_step_t s_beacon_1hz_50[] = { + { true, 500 }, + { false, 500 }, +}; + +static const remoteid_pattern_step_t s_strobe_single[] = { + { true, 60 }, + { false, 1940 }, +}; + +static const remoteid_pattern_step_t s_strobe_double[] = { + { true, 60 }, + { false, 80 }, + { true, 60 }, + { false, 1800 }, +}; + +static const remoteid_pattern_step_t s_strobe_triple[] = { + { true, 60 }, + { false, 80 }, + { true, 60 }, + { false, 80 }, + { true, 60 }, + { false, 1660 }, +}; + +static const remoteid_pattern_step_t s_fast_strobe[] = { + { true, 60 }, + { false, 440 }, +}; + +const remoteid_pattern_step_t *remoteid_pattern_steps(remoteid_pattern_t pattern, size_t *count) +{ + switch (pattern) { + case REMOTEID_PATTERN_SOLID: + *count = sizeof(s_solid) / sizeof(s_solid[0]); + return s_solid; + case REMOTEID_PATTERN_BEACON_1HZ_50: + *count = sizeof(s_beacon_1hz_50) / sizeof(s_beacon_1hz_50[0]); + return s_beacon_1hz_50; + case REMOTEID_PATTERN_STROBE_SINGLE: + *count = sizeof(s_strobe_single) / sizeof(s_strobe_single[0]); + return s_strobe_single; + case REMOTEID_PATTERN_STROBE_DOUBLE: + *count = sizeof(s_strobe_double) / sizeof(s_strobe_double[0]); + return s_strobe_double; + case REMOTEID_PATTERN_STROBE_TRIPLE: + *count = sizeof(s_strobe_triple) / sizeof(s_strobe_triple[0]); + return s_strobe_triple; + case REMOTEID_PATTERN_FAST_STROBE: + *count = sizeof(s_fast_strobe) / sizeof(s_fast_strobe[0]); + return s_fast_strobe; + case REMOTEID_PATTERN_BEACON_1HZ_SHORT: + default: + *count = sizeof(s_beacon_1hz_short) / sizeof(s_beacon_1hz_short[0]); + return s_beacon_1hz_short; + } +} + +uint32_t remoteid_pattern_period_ms(remoteid_pattern_t pattern) +{ + size_t count = 0; + const remoteid_pattern_step_t *steps = remoteid_pattern_steps(pattern, &count); + uint32_t period_ms = 0; + + for (size_t i = 0; i < count; i++) { + period_ms += steps[i].duration_ms; + } + + return period_ms; +} + +bool remoteid_pattern_is_on(remoteid_pattern_t pattern, uint32_t elapsed_ms, uint32_t phase_ms) +{ + size_t count = 0; + const remoteid_pattern_step_t *steps = remoteid_pattern_steps(pattern, &count); + uint32_t period_ms = 0; + + for (size_t i = 0; i < count; i++) { + period_ms += steps[i].duration_ms; + } + + if (period_ms == 0) { + return false; + } + + uint32_t position_ms = (elapsed_ms % period_ms + phase_ms % period_ms) % period_ms; + for (size_t i = 0; i < count; i++) { + if (position_ms < steps[i].duration_ms) { + return steps[i].on; + } + position_ms -= steps[i].duration_ms; + } + + return false; +} diff --git a/src/remoteid/remoteid/pattern.h b/src/remoteid/remoteid/pattern.h new file mode 100644 index 0000000..f9c1aff --- /dev/null +++ b/src/remoteid/remoteid/pattern.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include + +typedef enum remoteid_pattern { + REMOTEID_PATTERN_SOLID, + REMOTEID_PATTERN_BEACON_1HZ_SHORT, + REMOTEID_PATTERN_BEACON_1HZ_50, + REMOTEID_PATTERN_STROBE_SINGLE, + REMOTEID_PATTERN_STROBE_DOUBLE, + REMOTEID_PATTERN_STROBE_TRIPLE, + REMOTEID_PATTERN_FAST_STROBE, +} remoteid_pattern_t; + +typedef struct remoteid_pattern_step { + bool on; + uint16_t duration_ms; +} remoteid_pattern_step_t; + +const remoteid_pattern_step_t *remoteid_pattern_steps(remoteid_pattern_t pattern, size_t *count); +uint32_t remoteid_pattern_period_ms(remoteid_pattern_t pattern); +bool remoteid_pattern_is_on(remoteid_pattern_t pattern, uint32_t elapsed_ms, uint32_t phase_ms); diff --git a/src/remoteid/remoteid/store.c b/src/remoteid/remoteid/store.c new file mode 100644 index 0000000..78e1e65 --- /dev/null +++ b/src/remoteid/remoteid/store.c @@ -0,0 +1,207 @@ +#include "store.h" + +#include + +#include "esp_check.h" +#include "esp_log.h" +#include "freertos/event_groups.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "freertos/task.h" + +#define REMOTEID_STORE_READY_BIT BIT0 +#define REMOTEID_STORE_QUEUE_LEN 8 + +static const char *TAG = "remoteid_store"; + +static QueueHandle_t s_update_queue; +static SemaphoreHandle_t s_state_mutex; +static EventGroupHandle_t s_events; +static remoteid_state_t s_state; +static bool s_basic_id_valid; +static bool s_operator_id_valid; + +static bool usable_text_field(const char *value, const char *placeholder) +{ + return value[0] != '\0' && strncmp(value, placeholder, ODID_ID_SIZE) != 0; +} + +static bool basic_id_is_usable(const remoteid_state_t *state) +{ + return usable_text_field(state->uas_id, "CHANGE_ME_UAS_ID"); +} + +static bool operator_id_is_usable(const remoteid_state_t *state) +{ + return usable_text_field(state->operator_id, "CHANGE_ME_OP_ID"); +} + +static void update_ready_state_locked(void) +{ + bool ready = s_basic_id_valid && s_operator_id_valid; + + if (ready) { + xEventGroupSetBits(s_events, REMOTEID_STORE_READY_BIT); + } else { + xEventGroupClearBits(s_events, REMOTEID_STORE_READY_BIT); + } +} + +static void log_missing_identity_locked(void) +{ + if (!s_basic_id_valid) { + ESP_LOGW(TAG, "Remote ID broadcast waiting for valid Basic ID/UAS ID"); + } + if (!s_operator_id_valid) { + ESP_LOGW(TAG, "Remote ID broadcast waiting for valid Operator ID"); + } +} + +static void apply_update_locked(const remoteid_store_update_t *update) +{ + switch (update->type) { + case REMOTEID_STORE_UPDATE_BASIC_ID: + strlcpy(s_state.uas_id, update->data.basic_id.uas_id, sizeof(s_state.uas_id)); + s_state.id_type = update->data.basic_id.id_type; + s_state.ua_type = update->data.basic_id.ua_type; + s_basic_id_valid = basic_id_is_usable(&s_state); + ESP_LOGI(TAG, "updated Basic ID from input source (valid=%d)", s_basic_id_valid); + break; + case REMOTEID_STORE_UPDATE_OPERATOR_ID: + strlcpy(s_state.operator_id, update->data.operator_id.operator_id, sizeof(s_state.operator_id)); + s_operator_id_valid = operator_id_is_usable(&s_state); + ESP_LOGI(TAG, "updated Operator ID from input source (valid=%d)", s_operator_id_valid); + break; + case REMOTEID_STORE_UPDATE_LOCATION: + s_state.has_position = update->data.location.has_position; + s_state.latitude = update->data.location.latitude; + s_state.longitude = update->data.location.longitude; + s_state.altitude_geo_m = update->data.location.altitude_geo_m; + s_state.altitude_baro_m = update->data.location.altitude_baro_m; + s_state.status = update->data.location.status; + s_state.speed_horizontal = update->data.location.speed_horizontal; + s_state.speed_vertical = update->data.location.speed_vertical; + s_state.direction = update->data.location.direction; + s_state.height = update->data.location.height; + s_state.height_type = update->data.location.height_type; + s_state.horiz_acc = update->data.location.horiz_acc; + s_state.vert_acc = update->data.location.vert_acc; + s_state.baro_acc = update->data.location.baro_acc; + s_state.speed_acc = update->data.location.speed_acc; + s_state.ts_acc = update->data.location.ts_acc; + s_state.timestamp = update->data.location.timestamp; + ESP_LOGD(TAG, "updated Location from input source (has_position=%d status=%d)", + s_state.has_position, s_state.status); + break; + case REMOTEID_STORE_UPDATE_SYSTEM: + s_state.has_operator_position = update->data.system.has_operator_position; + s_state.operator_latitude = update->data.system.operator_latitude; + s_state.operator_longitude = update->data.system.operator_longitude; + s_state.operator_altitude_geo_m = update->data.system.operator_altitude_geo_m; + s_state.operator_location_type = update->data.system.operator_location_type; + s_state.area_count = update->data.system.area_count; + s_state.area_radius = update->data.system.area_radius; + s_state.area_ceiling_m = update->data.system.area_ceiling_m; + s_state.area_floor_m = update->data.system.area_floor_m; + s_state.classification_type = update->data.system.classification_type; + s_state.eu_category = update->data.system.eu_category; + s_state.eu_class = update->data.system.eu_class; + ESP_LOGD(TAG, "updated System from input source"); + break; + } + + bool was_ready = (xEventGroupGetBits(s_events) & REMOTEID_STORE_READY_BIT) != 0; + update_ready_state_locked(); + bool is_ready = s_basic_id_valid && s_operator_id_valid; + if (!was_ready && is_ready) { + ESP_LOGI(TAG, "Remote ID identity complete, broadcasts may start"); + } +} + +static void remoteid_store_task(void *arg) +{ + (void)arg; + remoteid_store_update_t update; + + while (true) { + if (xQueueReceive(s_update_queue, &update, portMAX_DELAY) != pdTRUE) { + continue; + } + + xSemaphoreTake(s_state_mutex, portMAX_DELAY); + apply_update_locked(&update); + xSemaphoreGive(s_state_mutex); + } +} + +esp_err_t remoteid_store_start(const remoteid_state_t *initial_state) +{ + ESP_RETURN_ON_FALSE(initial_state, ESP_ERR_INVALID_ARG, TAG, "initial state is required"); + + s_update_queue = xQueueCreate(REMOTEID_STORE_QUEUE_LEN, sizeof(remoteid_store_update_t)); + ESP_RETURN_ON_FALSE(s_update_queue, ESP_ERR_NO_MEM, TAG, "create state update queue"); + + s_state_mutex = xSemaphoreCreateMutex(); + ESP_RETURN_ON_FALSE(s_state_mutex, ESP_ERR_NO_MEM, TAG, "create state mutex"); + + s_events = xEventGroupCreate(); + ESP_RETURN_ON_FALSE(s_events, ESP_ERR_NO_MEM, TAG, "create state event group"); + + xSemaphoreTake(s_state_mutex, portMAX_DELAY); + s_state = *initial_state; + s_basic_id_valid = basic_id_is_usable(&s_state); + s_operator_id_valid = operator_id_is_usable(&s_state); + update_ready_state_locked(); + if (s_basic_id_valid && s_operator_id_valid) { + ESP_LOGI(TAG, "Remote ID identity loaded from Kconfig, broadcasts may start"); + } else { + log_missing_identity_locked(); + } + xSemaphoreGive(s_state_mutex); + + if (xTaskCreate(remoteid_store_task, "remoteid_store", 4096, NULL, 6, NULL) != pdPASS) { + ESP_LOGE(TAG, "failed to create remoteid_store task"); + return ESP_ERR_NO_MEM; + } + + return ESP_OK; +} + +esp_err_t remoteid_store_submit(const remoteid_store_update_t *update, TickType_t timeout) +{ + ESP_RETURN_ON_FALSE(update, ESP_ERR_INVALID_ARG, TAG, "update is required"); + ESP_RETURN_ON_FALSE(s_update_queue, ESP_ERR_INVALID_STATE, TAG, "state store is not started"); + + if (xQueueSend(s_update_queue, update, timeout) != pdTRUE) { + return ESP_ERR_TIMEOUT; + } + return ESP_OK; +} + +esp_err_t remoteid_store_get_snapshot(remoteid_state_t *out) +{ + ESP_RETURN_ON_FALSE(out, ESP_ERR_INVALID_ARG, TAG, "snapshot destination is required"); + ESP_RETURN_ON_FALSE(s_state_mutex, ESP_ERR_INVALID_STATE, TAG, "state store is not started"); + + xSemaphoreTake(s_state_mutex, portMAX_DELAY); + *out = s_state; + xSemaphoreGive(s_state_mutex); + + return ESP_OK; +} + +esp_err_t remoteid_store_wait_ready(TickType_t timeout) +{ + ESP_RETURN_ON_FALSE(s_events, ESP_ERR_INVALID_STATE, TAG, "state store is not started"); + + EventBits_t bits = xEventGroupWaitBits(s_events, REMOTEID_STORE_READY_BIT, pdFALSE, pdTRUE, timeout); + return (bits & REMOTEID_STORE_READY_BIT) ? ESP_OK : ESP_ERR_TIMEOUT; +} + +bool remoteid_store_is_ready(void) +{ + if (!s_events) { + return false; + } + return (xEventGroupGetBits(s_events) & REMOTEID_STORE_READY_BIT) != 0; +} diff --git a/src/remoteid/remoteid/store.h b/src/remoteid/remoteid/store.h new file mode 100644 index 0000000..7dfb220 --- /dev/null +++ b/src/remoteid/remoteid/store.h @@ -0,0 +1,67 @@ +#pragma once + +#include + +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "types.h" + +typedef enum remoteid_store_update_type { + REMOTEID_STORE_UPDATE_BASIC_ID, + REMOTEID_STORE_UPDATE_OPERATOR_ID, + REMOTEID_STORE_UPDATE_LOCATION, + REMOTEID_STORE_UPDATE_SYSTEM, +} remoteid_store_update_type_t; + +typedef struct remoteid_store_update { + remoteid_store_update_type_t type; + union { + struct { + char uas_id[ODID_ID_SIZE + 1]; + ODID_idtype_t id_type; + ODID_uatype_t ua_type; + } basic_id; + struct { + char operator_id[ODID_ID_SIZE + 1]; + } operator_id; + struct { + bool has_position; + double latitude; + double longitude; + float altitude_geo_m; + float altitude_baro_m; + ODID_status_t status; + float speed_horizontal; + float speed_vertical; + float direction; + float height; + ODID_Height_reference_t height_type; + ODID_Horizontal_accuracy_t horiz_acc; + ODID_Vertical_accuracy_t vert_acc; + ODID_Vertical_accuracy_t baro_acc; + ODID_Speed_accuracy_t speed_acc; + ODID_Timestamp_accuracy_t ts_acc; + float timestamp; + } location; + struct { + bool has_operator_position; + double operator_latitude; + double operator_longitude; + float operator_altitude_geo_m; + ODID_operator_location_type_t operator_location_type; + uint16_t area_count; + uint16_t area_radius; + float area_ceiling_m; + float area_floor_m; + ODID_classification_type_t classification_type; + ODID_category_EU_t eu_category; + ODID_class_EU_t eu_class; + } system; + } data; +} remoteid_store_update_t; + +esp_err_t remoteid_store_start(const remoteid_state_t *initial_state); +esp_err_t remoteid_store_submit(const remoteid_store_update_t *update, TickType_t timeout); +esp_err_t remoteid_store_get_snapshot(remoteid_state_t *out); +esp_err_t remoteid_store_wait_ready(TickType_t timeout); +bool remoteid_store_is_ready(void); diff --git a/src/remoteid/remoteid/types.h b/src/remoteid/remoteid/types.h index 53beaaf..81f1145 100644 --- a/src/remoteid/remoteid/types.h +++ b/src/remoteid/remoteid/types.h @@ -14,16 +14,44 @@ typedef enum remoteid_message_index { } remoteid_message_index_t; typedef struct remoteid_state { + // Basic ID char uas_id[ODID_ID_SIZE + 1]; char operator_id[ODID_ID_SIZE + 1]; ODID_idtype_t id_type; ODID_uatype_t ua_type; - ODID_category_EU_t eu_category; - ODID_class_EU_t eu_class; + + // Location bool has_position; double latitude; double longitude; - float altitude_m; + float altitude_geo_m; + float altitude_baro_m; + ODID_status_t status; + float speed_horizontal; + float speed_vertical; + float direction; + float height; + ODID_Height_reference_t height_type; + ODID_Horizontal_accuracy_t horiz_acc; + ODID_Vertical_accuracy_t vert_acc; + ODID_Vertical_accuracy_t baro_acc; + ODID_Speed_accuracy_t speed_acc; + ODID_Timestamp_accuracy_t ts_acc; + float timestamp; // seconds after the UTC hour, or < 0 if unknown + + // System / operator + bool has_operator_position; + double operator_latitude; + double operator_longitude; + float operator_altitude_geo_m; + ODID_operator_location_type_t operator_location_type; + uint16_t area_count; + uint16_t area_radius; + float area_ceiling_m; + float area_floor_m; + ODID_classification_type_t classification_type; + ODID_category_EU_t eu_category; + ODID_class_EU_t eu_class; } remoteid_state_t; typedef struct remoteid_message_bundle { diff --git a/src/remoteid/remoteid/wifi.c b/src/remoteid/remoteid/wifi.c index 1a8a071..b8c1dca 100644 --- a/src/remoteid/remoteid/wifi.c +++ b/src/remoteid/remoteid/wifi.c @@ -11,12 +11,11 @@ #include "esp_wifi.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "led.h" #include "opendroneid.h" #include "sdkconfig.h" +#include "store.h" #define REMOTEID_WIFI_FRAME_BUF_SIZE 512 -#define REMOTEID_WIFI_TX_INTERVAL_MS 1000 #ifndef CONFIG_REMOTEID_WIFI_BEACON_SSID #define CONFIG_REMOTEID_WIFI_BEACON_SSID "OpenDroneID" @@ -34,6 +33,14 @@ #define CONFIG_REMOTEID_WIFI_TX_POWER_DBM 20 #endif +#ifndef CONFIG_REMOTEID_WIFI_BEACON_TX_INTERVAL_MS +#define CONFIG_REMOTEID_WIFI_BEACON_TX_INTERVAL_MS 1000 +#endif + +#ifndef CONFIG_REMOTEID_WIFI_NAN_TX_INTERVAL_MS +#define CONFIG_REMOTEID_WIFI_NAN_TX_INTERVAL_MS 1000 +#endif + #ifndef CONFIG_REMOTEID_TRANSPORT_WIFI_BEACON #define CONFIG_REMOTEID_TRANSPORT_WIFI_BEACON 0 #endif @@ -44,13 +51,17 @@ static const char *TAG = "remoteid_wifi"; -static const remoteid_state_t *s_state; -static uint8_t s_send_counter; +#if CONFIG_REMOTEID_TRANSPORT_WIFI_BEACON +static uint8_t s_beacon_send_counter; +#endif +#if CONFIG_REMOTEID_TRANSPORT_WIFI_NAN +static uint8_t s_nan_send_counter; +#endif #if CONFIG_REMOTEID_TRANSPORT_WIFI_BEACON static uint16_t beacon_interval_tu(void) { - uint32_t interval_us = REMOTEID_WIFI_TX_INTERVAL_MS * 1000U; + uint32_t interval_us = CONFIG_REMOTEID_WIFI_BEACON_TX_INTERVAL_MS * 1000U; uint32_t interval_tu = interval_us / 1024U; return (uint16_t)(interval_tu > 0 ? interval_tu : 1); @@ -124,7 +135,7 @@ static esp_err_t init_wifi_driver(void) ap_config.ap.channel = CONFIG_REMOTEID_WIFI_CHANNEL; ap_config.ap.authmode = WIFI_AUTH_OPEN; ap_config.ap.max_connection = 0; - ap_config.ap.beacon_interval = REMOTEID_WIFI_TX_INTERVAL_MS; + ap_config.ap.beacon_interval = CONFIG_REMOTEID_WIFI_BEACON_TX_INTERVAL_MS; ESP_RETURN_ON_ERROR(esp_wifi_set_config(WIFI_IF_AP, &ap_config), TAG, "configure Wi-Fi AP"); #endif @@ -135,34 +146,37 @@ static esp_err_t init_wifi_driver(void) #endif ESP_RETURN_ON_ERROR(esp_wifi_set_max_tx_power(CONFIG_REMOTEID_WIFI_TX_POWER_DBM * 4), TAG, "set Wi-Fi TX power"); - ESP_LOGI(TAG, "Wi-Fi Remote ID enabled on channel %d at %d dBm, interval %d ms (beacon=%d, nan=%d)", - CONFIG_REMOTEID_WIFI_CHANNEL, CONFIG_REMOTEID_WIFI_TX_POWER_DBM, REMOTEID_WIFI_TX_INTERVAL_MS, - CONFIG_REMOTEID_TRANSPORT_WIFI_BEACON, CONFIG_REMOTEID_TRANSPORT_WIFI_NAN); + ESP_LOGI(TAG, + "Wi-Fi Remote ID enabled on channel %d at %d dBm (beacon=%d interval=%d ms, nan=%d interval=%d ms)", + CONFIG_REMOTEID_WIFI_CHANNEL, CONFIG_REMOTEID_WIFI_TX_POWER_DBM, CONFIG_REMOTEID_TRANSPORT_WIFI_BEACON, + CONFIG_REMOTEID_WIFI_BEACON_TX_INTERVAL_MS, CONFIG_REMOTEID_TRANSPORT_WIFI_NAN, + CONFIG_REMOTEID_WIFI_NAN_TX_INTERVAL_MS); return ESP_OK; } #if CONFIG_REMOTEID_TRANSPORT_WIFI_BEACON static esp_err_t send_remoteid_beacon(void) { + remoteid_state_t snapshot; ODID_UAS_Data uas_data; uint8_t frame[REMOTEID_WIFI_FRAME_BUF_SIZE]; uint8_t mac[6]; - ESP_RETURN_ON_ERROR(remoteid_encode_uas_data(s_state, &uas_data), TAG, "build OpenDroneID UAS data"); + ESP_RETURN_ON_ERROR(remoteid_store_get_snapshot(&snapshot), TAG, "get Remote ID snapshot"); + ESP_RETURN_ON_ERROR(remoteid_encode_uas_data(&snapshot, &uas_data), TAG, "build OpenDroneID UAS data"); ESP_RETURN_ON_ERROR(esp_wifi_get_mac(WIFI_IF_AP, mac), TAG, "get Wi-Fi AP MAC"); int len = odid_wifi_build_message_pack_beacon_frame( &uas_data, (const char *)mac, CONFIG_REMOTEID_WIFI_BEACON_SSID, strlen(CONFIG_REMOTEID_WIFI_BEACON_SSID), - beacon_interval_tu(), s_send_counter, frame, sizeof(frame)); + beacon_interval_tu(), s_beacon_send_counter, frame, sizeof(frame)); if (len < 0) { ESP_LOGE(TAG, "failed to build OpenDroneID Wi-Fi Beacon frame: %d", len); return ESP_FAIL; } ESP_RETURN_ON_ERROR(esp_wifi_80211_tx(WIFI_IF_AP, frame, len, true), TAG, "transmit Wi-Fi Beacon frame"); - s_send_counter++; + s_beacon_send_counter++; log_transmitted_message_types("Wi-Fi Beacon", &uas_data); - remoteid_led_pulse(); return ESP_OK; } @@ -171,12 +185,14 @@ static esp_err_t send_remoteid_beacon(void) #if CONFIG_REMOTEID_TRANSPORT_WIFI_NAN static esp_err_t send_remoteid_nan(void) { + remoteid_state_t snapshot; ODID_UAS_Data uas_data; uint8_t frame[REMOTEID_WIFI_FRAME_BUF_SIZE]; uint8_t mac[6]; int len; - ESP_RETURN_ON_ERROR(remoteid_encode_uas_data(s_state, &uas_data), TAG, "build OpenDroneID UAS data"); + ESP_RETURN_ON_ERROR(remoteid_store_get_snapshot(&snapshot), TAG, "get Remote ID snapshot"); + ESP_RETURN_ON_ERROR(remoteid_encode_uas_data(&snapshot, &uas_data), TAG, "build OpenDroneID UAS data"); ESP_RETURN_ON_ERROR(esp_wifi_get_mac(WIFI_IF_STA, mac), TAG, "get Wi-Fi STA MAC"); len = odid_wifi_build_nan_sync_beacon_frame((const char *)mac, frame, sizeof(frame)); @@ -186,49 +202,83 @@ static esp_err_t send_remoteid_nan(void) } ESP_RETURN_ON_ERROR(esp_wifi_80211_tx(WIFI_IF_STA, frame, len, true), TAG, "transmit Wi-Fi NAN sync frame"); - len = odid_wifi_build_message_pack_nan_action_frame(&uas_data, (const char *)mac, s_send_counter, frame, + len = odid_wifi_build_message_pack_nan_action_frame(&uas_data, (const char *)mac, s_nan_send_counter, frame, sizeof(frame)); if (len < 0) { ESP_LOGE(TAG, "failed to build OpenDroneID Wi-Fi NAN action frame: %d", len); return ESP_FAIL; } ESP_RETURN_ON_ERROR(esp_wifi_80211_tx(WIFI_IF_STA, frame, len, true), TAG, "transmit Wi-Fi NAN action frame"); - s_send_counter++; + s_nan_send_counter++; log_transmitted_message_types("Wi-Fi NAN", &uas_data); - remoteid_led_pulse(); return ESP_OK; } #endif -static void remoteid_wifi_task(void *arg) +#if CONFIG_REMOTEID_TRANSPORT_WIFI_BEACON +static void remoteid_wifi_beacon_task(void *arg) { (void)arg; + ESP_LOGI(TAG, "Wi-Fi Beacon transport waiting for Remote ID Basic ID and Operator ID"); + ESP_ERROR_CHECK(remoteid_store_wait_ready(portMAX_DELAY)); + ESP_LOGI(TAG, "Wi-Fi Beacon transport identity ready, starting transmissions"); + while (true) { -#if CONFIG_REMOTEID_TRANSPORT_WIFI_BEACON + if (!remoteid_store_is_ready()) { + ESP_LOGW(TAG, "Wi-Fi Beacon transport paused until Remote ID identity is ready again"); + ESP_ERROR_CHECK(remoteid_store_wait_ready(portMAX_DELAY)); + } + if (send_remoteid_beacon() == ESP_OK) { ESP_LOGD(TAG, "transmitted OpenDroneID Wi-Fi Beacon"); } + vTaskDelay(pdMS_TO_TICKS(CONFIG_REMOTEID_WIFI_BEACON_TX_INTERVAL_MS)); + } +} #endif + #if CONFIG_REMOTEID_TRANSPORT_WIFI_NAN +static void remoteid_wifi_nan_task(void *arg) +{ + (void)arg; + + ESP_LOGI(TAG, "Wi-Fi NAN transport waiting for Remote ID Basic ID and Operator ID"); + ESP_ERROR_CHECK(remoteid_store_wait_ready(portMAX_DELAY)); + ESP_LOGI(TAG, "Wi-Fi NAN transport identity ready, starting transmissions"); + + while (true) { + if (!remoteid_store_is_ready()) { + ESP_LOGW(TAG, "Wi-Fi NAN transport paused until Remote ID identity is ready again"); + ESP_ERROR_CHECK(remoteid_store_wait_ready(portMAX_DELAY)); + } + if (send_remoteid_nan() == ESP_OK) { ESP_LOGD(TAG, "transmitted OpenDroneID Wi-Fi NAN"); } -#endif - vTaskDelay(pdMS_TO_TICKS(REMOTEID_WIFI_TX_INTERVAL_MS)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_REMOTEID_WIFI_NAN_TX_INTERVAL_MS)); } } +#endif -esp_err_t remoteid_wifi_start(const remoteid_state_t *state) +esp_err_t remoteid_wifi_start(void) { - s_state = state; + ESP_RETURN_ON_ERROR(init_wifi_driver(), TAG, "initialize Wi-Fi Remote ID transport"); + +#if CONFIG_REMOTEID_TRANSPORT_WIFI_BEACON + if (xTaskCreate(remoteid_wifi_beacon_task, "remoteid_wifi_bcn", 4096, NULL, 5, NULL) != pdPASS) { + ESP_LOGE(TAG, "failed to create remoteid_wifi_bcn task"); + return ESP_ERR_NO_MEM; + } +#endif - ESP_RETURN_ON_ERROR(init_wifi_driver(), TAG, "initialize Wi-Fi Beacon transport"); - if (xTaskCreate(remoteid_wifi_task, "remoteid_wifi", 4096, NULL, 5, NULL) != pdPASS) { - ESP_LOGE(TAG, "failed to create remoteid_wifi task"); +#if CONFIG_REMOTEID_TRANSPORT_WIFI_NAN + if (xTaskCreate(remoteid_wifi_nan_task, "remoteid_wifi_nan", 4096, NULL, 5, NULL) != pdPASS) { + ESP_LOGE(TAG, "failed to create remoteid_wifi_nan task"); return ESP_ERR_NO_MEM; } +#endif return ESP_OK; } diff --git a/src/remoteid/remoteid/wifi.h b/src/remoteid/remoteid/wifi.h index b04caf6..6edde56 100644 --- a/src/remoteid/remoteid/wifi.h +++ b/src/remoteid/remoteid/wifi.h @@ -1,6 +1,4 @@ #pragma once #include "esp_err.h" -#include "types.h" - -esp_err_t remoteid_wifi_start(const remoteid_state_t *state); +esp_err_t remoteid_wifi_start(void); diff --git a/third_party/mavlink_c_library_v2 b/third_party/mavlink_c_library_v2 new file mode 160000 index 0000000..59ae0eb --- /dev/null +++ b/third_party/mavlink_c_library_v2 @@ -0,0 +1 @@ +Subproject commit 59ae0ebc759d10e8beb6bd55cadaa45c0915db5b