From 710f233619f7725f2f9bace971a101fbb845143c Mon Sep 17 00:00:00 2001 From: bjackson312006 Date: Mon, 6 Jul 2026 16:48:31 -0700 Subject: [PATCH 1/9] qemu --- dt-rust.yaml | 2 + qemu.sh | 17 ++++ samples/zephyr-odp/CMakeLists.txt | 3 + .../mimxrt685_evk_mimxrt685s_cm33.overlay | 2 +- samples/zephyr-odp/boards/qemu_cortex_m3.conf | 21 +++++ .../zephyr-odp/boards/qemu_cortex_m3.overlay | 77 ++++++++++++++++ samples/zephyr-odp/drivers/pwm_emul.c | 89 +++++++++++++++++++ .../dts/bindings/zephyr,pwm-emul.yaml | 24 +++++ samples/zephyr-odp/sample.yaml | 1 + samples/zephyr-odp/src/thermal.rs | 2 +- 10 files changed, 236 insertions(+), 2 deletions(-) create mode 100755 qemu.sh create mode 100644 samples/zephyr-odp/boards/qemu_cortex_m3.conf create mode 100644 samples/zephyr-odp/boards/qemu_cortex_m3.overlay create mode 100644 samples/zephyr-odp/drivers/pwm_emul.c create mode 100644 samples/zephyr-odp/dts/bindings/zephyr,pwm-emul.yaml diff --git a/dt-rust.yaml b/dt-rust.yaml index ac652485..a0e727a1 100644 --- a/dt-rust.yaml +++ b/dt-rust.yaml @@ -94,6 +94,7 @@ names: - "ti,tmp11x_rs" - "ti,tmp11x" + - "fintek,f75303" level: 0 actions: - !Instance @@ -162,6 +163,7 @@ - !Compatible names: - "nxp,lpc-usart" # u_Note: Should probably pull in more eventually but this is what i have + - "zephyr,uart-emul" level: 0 actions: - !Instance diff --git a/qemu.sh b/qemu.sh new file mode 100755 index 00000000..e94b90da --- /dev/null +++ b/qemu.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Builds and runs the QEMU build. + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BUILD_DIR="${BUILD_DIR:-$(west topdir)/build-qemu}" + +if command -v rustup >/dev/null 2>&1; then + rustup target add thumbv7m-none-eabi +fi + +# Build for QEMU (pass -p always via WEST_BUILD_ARGS for a pristine build). +west build -b qemu_cortex_m3 "$SCRIPT_DIR/samples/zephyr-odp" -d "$BUILD_DIR" ${WEST_BUILD_ARGS} + +# Launch the image in QEMU. +west build -t run -d "$BUILD_DIR" diff --git a/samples/zephyr-odp/CMakeLists.txt b/samples/zephyr-odp/CMakeLists.txt index 2fd4f00f..4d3b77d7 100644 --- a/samples/zephyr-odp/CMakeLists.txt +++ b/samples/zephyr-odp/CMakeLists.txt @@ -12,3 +12,6 @@ rust_cargo_application() # Compile the fuel gauge emulator driver target_sources(app PRIVATE drivers/fuel_gauge_emul.c) + +# Compile the PWM emulator driver +target_sources(app PRIVATE drivers/pwm_emul.c) diff --git a/samples/zephyr-odp/boards/mimxrt685_evk_mimxrt685s_cm33.overlay b/samples/zephyr-odp/boards/mimxrt685_evk_mimxrt685s_cm33.overlay index a1bd062a..2e8dd1ca 100644 --- a/samples/zephyr-odp/boards/mimxrt685_evk_mimxrt685s_cm33.overlay +++ b/samples/zephyr-odp/boards/mimxrt685_evk_mimxrt685s_cm33.overlay @@ -6,7 +6,7 @@ */ &flexcomm2 { - ti_tmp11x: ti_tmp11x@48 { + temperature_sensor_0: ti_tmp11x@48 { compatible = "ti,tmp11x"; reg = <0x48>; #address-cells = <1>; diff --git a/samples/zephyr-odp/boards/qemu_cortex_m3.conf b/samples/zephyr-odp/boards/qemu_cortex_m3.conf new file mode 100644 index 00000000..1f00b400 --- /dev/null +++ b/samples/zephyr-odp/boards/qemu_cortex_m3.conf @@ -0,0 +1,21 @@ +# qemu_cortex_m3 configuration for the zephyr-odp sample. + +# Use QEMU UART instead of RTT +CONFIG_LOG_BACKEND_RTT=n +CONFIG_USE_SEGGER_RTT=n +CONFIG_RTT_CONSOLE=n +CONFIG_LOG_BACKEND_UART=y +CONFIG_UART_CONSOLE=y +CONFIG_STDOUT_CONSOLE=y + +# No DMA +CONFIG_DMA=n +CONFIG_DMA_MCUX_LPC=n # Disable NXP MCUX-specific stuff since QEMU + +# The emulators +CONFIG_EMUL=y +CONFIG_I2C_EMUL=y +CONFIG_GPIO_EMUL=y +CONFIG_UART_EMUL=y +CONFIG_F75303=y +CONFIG_EMUL_F75303=y diff --git a/samples/zephyr-odp/boards/qemu_cortex_m3.overlay b/samples/zephyr-odp/boards/qemu_cortex_m3.overlay new file mode 100644 index 00000000..4ab9d916 --- /dev/null +++ b/samples/zephyr-odp/boards/qemu_cortex_m3.overlay @@ -0,0 +1,77 @@ +#include +#include + +/ { + /* Emulated I2C bus hosting the temperature sensor and fuel gauge. */ + fake_i2c_bus: i2c@100 { + status = "okay"; + compatible = "zephyr,i2c-emul-controller"; + clock-frequency = ; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x100 4>; + + temperature_sensor_0: f75303@4c { + compatible = "fintek,f75303"; + reg = <0x4c>; + }; + + fuel_gauge: sbs-gauge@b { + compatible = "sbs,sbs-gauge-new-api"; + reg = <0x0b>; + status = "okay"; + }; + }; + + /* Emulated GPIO controller used for the tachometer input pin. */ + gpio_emul0: gpio-emul { + status = "okay"; + compatible = "zephyr,gpio-emul"; + rising-edge; + falling-edge; + high-level; + low-level; + gpio-controller; + #gpio-cells = <2>; + ngpios = <32>; + }; + tachometer: tachometer { + compatible = "zephyr,tach-gpio"; + gpios = <&gpio_emul0 15 (GPIO_ACTIVE_HIGH | GPIO_PULL_UP)>; + pulses-per-round = <2>; + }; + + /* Emulated PWM driver used by PWM Fan */ + pwm_emul0: pwm-emul { + status = "okay"; + compatible = "zephyr,pwm-emul"; + #pwm-cells = <3>; + frequency = <1000000>; + }; + fan0: my-fan { + compatible = "pwm-fan"; + pwms = <&pwm_emul0 7 40000 0>; + max-rpm = <5000>; + min-rpm = <0>; + min-start-rpm = <3000>; + }; + + /* Emulated RTC */ + rtc_emul: rtc_emul { + compatible = "zephyr,rtc-emul"; + status = "okay"; + }; + rs_rtc0: rs-rtc { + compatible = "rs,rtc"; + rtc = <&rtc_emul>; + resolution-hz = <1>; + }; + + /* Emulated UART */ + flexcomm0: uart-emul0 { + status = "okay"; + compatible = "zephyr,uart-emul"; + current-speed = <115200>; + loopback; + }; +}; diff --git a/samples/zephyr-odp/drivers/pwm_emul.c b/samples/zephyr-odp/drivers/pwm_emul.c new file mode 100644 index 00000000..87a87311 --- /dev/null +++ b/samples/zephyr-odp/drivers/pwm_emul.c @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2026 + * SPDX-License-Identifier: Apache-2.0 + * + * Emulated PWM controller for testing without hardware. + * + * This is a small functional stub (not an FFF mock like zephyr,fake-pwm). Also unlike zephyr,fake-pwm, it + * uses the standard 3-cell PWM specifier (channel, period, flags) instead of 2. + */ + +#define DT_DRV_COMPAT zephyr_pwm_emul + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(pwm_emul, CONFIG_PWM_LOG_LEVEL); + +#define PWM_EMUL_NUM_CHANNELS 8 + +struct pwm_emul_config { + uint64_t frequency_hz; +}; + +struct pwm_emul_channel { + uint32_t period_cycles; + uint32_t pulse_cycles; + pwm_flags_t flags; +}; + +struct pwm_emul_data { + struct pwm_emul_channel channels[PWM_EMUL_NUM_CHANNELS]; +}; + +static int pwm_emul_set_cycles(const struct device *dev, uint32_t channel, + uint32_t period_cycles, uint32_t pulse_cycles, + pwm_flags_t flags) +{ + struct pwm_emul_data *data = dev->data; + + if (channel >= PWM_EMUL_NUM_CHANNELS) { + return -EINVAL; + } + + data->channels[channel].period_cycles = period_cycles; + data->channels[channel].pulse_cycles = pulse_cycles; + data->channels[channel].flags = flags; + + LOG_DBG("channel %u: period=%u pulse=%u flags=0x%x", channel, + period_cycles, pulse_cycles, (unsigned int)flags); + + return 0; +} + +static int pwm_emul_get_cycles_per_sec(const struct device *dev, + uint32_t channel, uint64_t *cycles) +{ + const struct pwm_emul_config *config = dev->config; + + ARG_UNUSED(channel); + + *cycles = config->frequency_hz; + + return 0; +} + +static DEVICE_API(pwm, pwm_emul_api) = { + .set_cycles = pwm_emul_set_cycles, + .get_cycles_per_sec = pwm_emul_get_cycles_per_sec, +}; + +static int pwm_emul_init(const struct device *dev) +{ + ARG_UNUSED(dev); + return 0; +} + +#define PWM_EMUL_INIT(inst) \ + static struct pwm_emul_data pwm_emul_data_##inst; \ + static const struct pwm_emul_config pwm_emul_config_##inst = { \ + .frequency_hz = DT_INST_PROP(inst, frequency), \ + }; \ + DEVICE_DT_INST_DEFINE(inst, pwm_emul_init, NULL, \ + &pwm_emul_data_##inst, &pwm_emul_config_##inst, \ + POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, \ + &pwm_emul_api); + +DT_INST_FOREACH_STATUS_OKAY(PWM_EMUL_INIT) diff --git a/samples/zephyr-odp/dts/bindings/zephyr,pwm-emul.yaml b/samples/zephyr-odp/dts/bindings/zephyr,pwm-emul.yaml new file mode 100644 index 00000000..99448b0e --- /dev/null +++ b/samples/zephyr-odp/dts/bindings/zephyr,pwm-emul.yaml @@ -0,0 +1,24 @@ +# Copyright (c) 2026 +# SPDX-License-Identifier: Apache-2.0 + +description: | + Emulated PWM controller for testing without hardware. + +compatible: "zephyr,pwm-emul" + +include: [pwm-controller.yaml, base.yaml] + +properties: + "#pwm-cells": + const: 3 + + frequency: + type: int + default: 1000000 + description: | + Frequency of the emulated underlying timer, in Hz. + +pwm-cells: + - channel + - period + - flags diff --git a/samples/zephyr-odp/sample.yaml b/samples/zephyr-odp/sample.yaml index 7546705c..d8e517c6 100644 --- a/samples/zephyr-odp/sample.yaml +++ b/samples/zephyr-odp/sample.yaml @@ -8,6 +8,7 @@ common: platform_allow: - nrf52840dk/nrf52840' - 'mimxrt685_evk/mimxrt685s/cm33' + - 'qemu_cortex_m3' tests: sample.rust.helloworld: tags: ODP diff --git a/samples/zephyr-odp/src/thermal.rs b/samples/zephyr-odp/src/thermal.rs index 7f3f502c..282437d0 100644 --- a/samples/zephyr-odp/src/thermal.rs +++ b/samples/zephyr-odp/src/thermal.rs @@ -70,7 +70,7 @@ mod tmp11x { pub struct Sensor(zephyr::device::temperature_sensor::TemperatureSensor); impl Sensor { pub fn new() -> Self { - Self(zephyr::devicetree::labels::ti_tmp11x::get_instance().expect("Failed to call zephyr::devicetree::labels::ti_tmp11x::get_instance()")) + Self(zephyr::devicetree::labels::temperature_sensor_0::get_instance().expect("Failed to call zephyr::devicetree::labels::temperature_sensor_0::get_instance()")) } } impl thermal_service_interface::sensor::Driver for Sensor {} // Marker trait so Sensor can be used with thermal_service. From c30314a38288f73f354a61d92033b6d2216c8387 Mon Sep 17 00:00:00 2001 From: bjackson312006 Date: Mon, 6 Jul 2026 18:01:53 -0700 Subject: [PATCH 2/9] add native_sim/native/64 target for zephyr-odp (WIP) --- native-arm64.sh | 19 +++++++ .../boards/native_sim_native_64.conf | 18 ++++++ .../boards/native_sim_native_64.overlay | 57 +++++++++++++++++++ samples/zephyr-odp/sample.yaml | 1 + 4 files changed, 95 insertions(+) create mode 100755 native-arm64.sh create mode 100644 samples/zephyr-odp/boards/native_sim_native_64.conf create mode 100644 samples/zephyr-odp/boards/native_sim_native_64.overlay diff --git a/native-arm64.sh b/native-arm64.sh new file mode 100755 index 00000000..6c24679a --- /dev/null +++ b/native-arm64.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Build + run the zephyr-odp sample on native_sim (64-bit). + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BUILD_DIR="${BUILD_DIR:-$(west topdir)/build-native}" + +# native_sim/native/64 on a 64-bit host maps to the aarch64/x86_64 bare-metal +# Rust target +if command -v rustup >/dev/null 2>&1; then + rustup target add aarch64-unknown-none +fi + +# Build for native_sim (pass -p always via WEST_BUILD_ARGS for a pristine build). +west build -b native_sim/native/64 "$SCRIPT_DIR/samples/zephyr-odp" -d "$BUILD_DIR" ${WEST_BUILD_ARGS} + +# Launch the native binary. +west build -t run -d "$BUILD_DIR" diff --git a/samples/zephyr-odp/boards/native_sim_native_64.conf b/samples/zephyr-odp/boards/native_sim_native_64.conf new file mode 100644 index 00000000..8fd82cd5 --- /dev/null +++ b/samples/zephyr-odp/boards/native_sim_native_64.conf @@ -0,0 +1,18 @@ +# native_sim/native/64 configuration for the zephyr-odp sample. + +CONFIG_LOG_BACKEND_RTT=n +CONFIG_USE_SEGGER_RTT=n +CONFIG_RTT_CONSOLE=n +CONFIG_LOG_BACKEND_UART=y +CONFIG_UART_CONSOLE=y +CONFIG_STDOUT_CONSOLE=y + +CONFIG_DMA=n +CONFIG_DMA_MCUX_LPC=n + +CONFIG_EMUL=y +CONFIG_I2C_EMUL=y +CONFIG_GPIO_EMUL=y +CONFIG_UART_EMUL=y +CONFIG_F75303=y +CONFIG_EMUL_F75303=y diff --git a/samples/zephyr-odp/boards/native_sim_native_64.overlay b/samples/zephyr-odp/boards/native_sim_native_64.overlay new file mode 100644 index 00000000..44f34485 --- /dev/null +++ b/samples/zephyr-odp/boards/native_sim_native_64.overlay @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026 + * SPDX-License-Identifier: Apache-2.0 + * + * Devicetree overlay for the zephyr-odp sample on native_sim/native/64. + * + */ + +#include + +&i2c0 { + temperature_sensor_0: f75303@4c { + compatible = "fintek,f75303"; + reg = <0x4c>; + }; + + fuel_gauge: sbs-gauge@b { + compatible = "sbs,sbs-gauge-new-api"; + reg = <0x0b>; + status = "okay"; + }; +}; + +/ { + tachometer: tachometer { + compatible = "zephyr,tach-gpio"; + gpios = <&gpio0 15 (GPIO_ACTIVE_HIGH | GPIO_PULL_UP)>; + pulses-per-round = <2>; + }; + + pwm_emul0: pwm-emul { + status = "okay"; + compatible = "zephyr,pwm-emul"; + #pwm-cells = <3>; + frequency = <1000000>; + }; + fan0: my-fan { + compatible = "pwm-fan"; + pwms = <&pwm_emul0 7 40000 0>; + max-rpm = <5000>; + min-rpm = <0>; + min-start-rpm = <3000>; + }; + + rs_rtc0: rs-rtc { + compatible = "rs,rtc"; + rtc = <&rtc>; + resolution-hz = <1>; + }; + + flexcomm0: uart-emul0 { + status = "okay"; + compatible = "zephyr,uart-emul"; + current-speed = <115200>; + loopback; + }; +}; diff --git a/samples/zephyr-odp/sample.yaml b/samples/zephyr-odp/sample.yaml index d8e517c6..794f3d3e 100644 --- a/samples/zephyr-odp/sample.yaml +++ b/samples/zephyr-odp/sample.yaml @@ -9,6 +9,7 @@ common: - nrf52840dk/nrf52840' - 'mimxrt685_evk/mimxrt685s/cm33' - 'qemu_cortex_m3' + - 'native_sim/native/64' tests: sample.rust.helloworld: tags: ODP From ca8bb9bf3d9ba11c4678dc4cf47dc2db4d19e2b1 Mon Sep 17 00:00:00 2001 From: bjackson312006 Date: Tue, 7 Jul 2026 11:10:58 -0700 Subject: [PATCH 3/9] no more referring to temp sensor as ti_tmp11x --- samples/zephyr-odp/src/thermal.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/zephyr-odp/src/thermal.rs b/samples/zephyr-odp/src/thermal.rs index 282437d0..f4573b28 100644 --- a/samples/zephyr-odp/src/thermal.rs +++ b/samples/zephyr-odp/src/thermal.rs @@ -3,7 +3,7 @@ const FAN_EVENT_CHANNEL_SIZE: usize = 8; type SensorEventSender = embassy_sync::channel::Sender<'static, embedded_services::GlobalRawMutex, thermal_service_interface::sensor::Event, SENSOR_EVENT_CHANNEL_SIZE>; type FanEventSender = embassy_sync::channel::Sender<'static, embedded_services::GlobalRawMutex, thermal_service_interface::fan::Event, FAN_EVENT_CHANNEL_SIZE>; -type SensorService = thermal_service::sensor::Service<'static, tmp11x::Sensor, SensorEventSender, 16>; +type SensorService = thermal_service::sensor::Service<'static, temperature_sensor::Sensor, SensorEventSender, 16>; type FanService = thermal_service::fan::Service<'static, zephyr::device::pwm_fan::PwmFan, SensorService, FanEventSender, 16>; pub type ThermalService = thermal_service::Service<'static, SensorService, FanService>; @@ -14,7 +14,7 @@ pub async fn init(spawner: embassy_executor::Spawner) -> ThermalService { let sensor_service = crate::utils::spawn_service!(spawner, SensorService, |resources| thermal_service::sensor::Service::new( resources, thermal_service::sensor::InitParams { - driver: tmp11x::Sensor::new(), // The TMP11x temperature sensor driver + driver: temperature_sensor::Sensor::new(), // The temp sensor driver configured via the devicetree event_senders: &mut [], // List of event senders // Thermal service sensor config @@ -62,11 +62,11 @@ pub async fn init(spawner: embassy_executor::Spawner) -> ThermalService { thermal_service::Service::init(resources, thermal_service::InitParams { sensors, fans }) } -mod tmp11x { +mod temperature_sensor { pub use embedded_sensors_hal_async::temperature::DegreesCelsius; use log::info; - // Wrapper around the Zephyr TMP11x temperature sensor with implementations for the generic embedded_sensors_hal_async::temperature::TemperatureSensor trait. + // Wrapper around the Zephyr temperature sensor with implementations for the generic embedded_sensors_hal_async::temperature::TemperatureSensor trait. pub struct Sensor(zephyr::device::temperature_sensor::TemperatureSensor); impl Sensor { pub fn new() -> Self { From c7f42c61f238c07c210fb89dac06ee6578684567 Mon Sep 17 00:00:00 2001 From: bjackson312006 Date: Tue, 7 Jul 2026 17:29:42 -0700 Subject: [PATCH 4/9] nativesim builds --- samples/zephyr-odp/Cargo.toml | 24 ++-- samples/zephyr-odp/src/battery.rs | 218 ++++++++++++++++++++++++------ samples/zephyr-odp/src/lib.rs | 4 +- zephyr/Cargo.toml | 4 +- zephyr/src/device/fuel_gauge.rs | 175 +++++------------------- 5 files changed, 227 insertions(+), 198 deletions(-) diff --git a/samples/zephyr-odp/Cargo.toml b/samples/zephyr-odp/Cargo.toml index b5e9421b..28e63493 100644 --- a/samples/zephyr-odp/Cargo.toml +++ b/samples/zephyr-odp/Cargo.toml @@ -20,25 +20,25 @@ embassy-futures = "0.1.1" embassy-sync = "0.8" embassy-time = { version = "0.5", features = ["tick-hz-10_000"] } # u_Note: tick-hz-10_000 is needed to match with zephyr. If this isn't here, something like Timer::after_secs(1) would actually wait for 100 seconds instead of 1 seconds. There should probably be some kind of way to do this manually so the embassy APIs work no matter what the Zephyr tick rate is. embedded-sensors-hal-async = "0.3.0" -embedded-services = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } -odp-service-common = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } +embedded-services = { git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } +odp-service-common = { git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } static_cell = "2.1" -thermal-service = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } -thermal-service-interface = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } -thermal-service-relay = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } -uart-service = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } +thermal-service = { git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } +thermal-service-interface = { git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } +thermal-service-relay = { git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } +uart-service = { git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } zephyr = { version = "0.1.0", features = ["time-driver", "executor-zephyr"] } log = { version = "0.4.22" } # features = ["max_level_error", "release_max_level_error"] embedded-io-async = {version = "0.7.0"} embedded-batteries-async = {version = "0.3.4"} -battery-service = { package = "battery-service", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0", features = ["mock"] } -battery-service-interface = { package = "battery-service-interface", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } -battery-service-relay = { package = "battery-service-relay", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } +battery-service = { package = "battery-service", git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a", features = ["mock"] } +battery-service-interface = { package = "battery-service-interface", git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } +battery-service-relay = { package = "battery-service-relay", git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } -time-alarm-service = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0", features = ["mock"]} -time-alarm-service-interface = { package = "time-alarm-service-interface", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } -time-alarm-service-relay = { package = "time-alarm-service-relay", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } +time-alarm-service = { git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a", features = ["mock"]} +time-alarm-service-interface = { package = "time-alarm-service-interface", git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } +time-alarm-service-relay = { package = "time-alarm-service-relay", git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } [lints.clippy] perf = { level = "forbid" } diff --git a/samples/zephyr-odp/src/battery.rs b/samples/zephyr-odp/src/battery.rs index b2b5eab6..0549f981 100644 --- a/samples/zephyr-odp/src/battery.rs +++ b/samples/zephyr-odp/src/battery.rs @@ -1,43 +1,40 @@ use static_cell::StaticCell; +use embassy_sync::mutex::Mutex; +use embedded_services::GlobalRawMutex; +use battery_service::FuelGauge as _; -pub type BatteryService = battery_service::Service<'static, 1>; +/// Our `BatteryServiceWrapper` driver but wrapped in a mutex so we can provide it to battery-service +type WrappedFuelGauge = Mutex; -const BAT_ID: battery_service::device::DeviceId = battery_service::device::DeviceId(0); +/// Registration for our fuel gauge +type Reg = battery_service::ArrayRegistration<'static, WrappedFuelGauge, 1>; + +pub type BatteryService = battery_service::Service<'static, Reg>; pub async fn init(spawner: embassy_executor::Spawner) -> BatteryService { log::info!("Initializing battery service..."); - static BATTERY_DEVICE: StaticCell = StaticCell::new(); - let device = BATTERY_DEVICE.init(battery_service::device::Device::new(BAT_ID)); let driver = zephyr::devicetree::labels::fuel_gauge::get_instance().expect("Failed to call get_instance() for fuel_gauge."); - let battery = battery_service::wrapper::Wrapper::new(device, driver); - let service = crate::utils::spawn_service!(spawner, BatteryService, |resources| battery_service::Service::new( - resources, - battery_service::InitParams { - devices: [device], - config: battery_service::context::Config::default(), - } - )) - .expect("Failed to initialize battery service"); + static FUEL_GAUGE: StaticCell = StaticCell::new(); + let fuel_gauge: &'static WrappedFuelGauge = + FUEL_GAUGE.init(Mutex::new(BatteryServiceWrapper::from(driver))); + + let service = battery_service::Service::new(battery_service::ArrayRegistration { + fuel_gauges: [fuel_gauge], + }); - spawner.spawn(battery_device_controller_task(battery)).expect("Failed to spawn battery device controller task"); - spawner.spawn(update_data_task(service)).expect("Failed to spawn battery update data task"); + spawner + .spawn(update_data_task(fuel_gauge)) + .expect("Failed to spawn battery update data task"); log::info!("Initialized battery service!"); service } #[embassy_executor::task] -async fn battery_device_controller_task(battery: battery_service::wrapper::Wrapper<'static, zephyr::device::fuel_gauge::FuelGauge>) { - battery.process().await; -} - -#[embassy_executor::task] -pub async fn update_data_task(service: BatteryService) { - - // Initialize the state machine - if let Err(e) = battery_service::mock::init_state_machine(&service).await { +pub async fn update_data_task(fuel_gauge: &'static WrappedFuelGauge) { + if let Err(e) = battery_service::mock::init_state_machine(fuel_gauge).await { log::error!("FG: Failed to init state machine: {:?}. Terminating this task...", e); return; } @@ -46,37 +43,178 @@ pub async fn update_data_task(service: BatteryService) { let mut count: usize = 0; loop { embassy_time::Timer::after_secs(1).await; + if count.is_multiple_of(const { 60 * 60 }) - && let Err(e) = service - .execute_event(battery_service::context::BatteryEvent { - event: battery_service::context::BatteryEventInner::PollStaticData, - device_id: BAT_ID, - }) - .await + && let Err(e) = fuel_gauge.lock().await.update_static_data().await { failures += 1; - log::error!("FG: Static data error: {:#?}", e); + log::error!("FG: Static data error: {:?}", e); } - if let Err(e) = service - .execute_event(battery_service::context::BatteryEvent { - event: battery_service::context::BatteryEventInner::PollDynamicData, - device_id: BAT_ID, - }) - .await - { + + if let Err(e) = fuel_gauge.lock().await.update_dynamic_data().await { failures += 1; - log::error!("FG: Dynamic data error: {:#?}", e); + log::error!("FG: Dynamic data error: {:?}", e); } if failures > 10 { failures = 0; count = 0; log::error!("FG: Too many errors, timing out and starting recovery..."); - if battery_service::mock::recover_state_machine(&service).await.is_err() { + if battery_service::mock::recover_state_machine(fuel_gauge).await.is_err() { log::error!("FG: Failed to recover state machine!"); } } count = count.wrapping_add(1); } +} + +/// Wrapper around the Zephyr driver. +pub struct BatteryServiceWrapper { + pub driver: zephyr::device::fuel_gauge::FuelGauge, + pub state: battery_service::State, +} + +/// Map Zephyr error to wrapper error. +#[derive(Debug)] +pub enum BatteryServiceWrapperError { ZephyrError(zephyr::error::Error) } +impl From for BatteryServiceWrapperError { + fn from(err: zephyr::error::Error) -> Self { + BatteryServiceWrapperError::ZephyrError(err) + } +} + +/// Map a BatteryServiceWrapperError to a smart_battery::ErrorKind +impl embedded_batteries_async::smart_battery::Error for BatteryServiceWrapperError { + fn kind(&self) -> embedded_batteries_async::smart_battery::ErrorKind { + match self { + BatteryServiceWrapperError::ZephyrError(e) => { + embedded_batteries_async::smart_battery::Error::kind(e) + } + } + } +} + +/// Allow you to convert a normal `FuelGauge` into a `BatteryServiceWrapper` to be used with the battery service. +impl From for BatteryServiceWrapper { + fn from(value: zephyr::device::fuel_gauge::FuelGauge) -> Self { + Self { + driver: value, + state: battery_service::State::default(), + } + } +} + +/// Convert local errors into battery-service errors. +impl From for battery_service::FuelGaugeError { + fn from(_error: BatteryServiceWrapperError) -> Self { + battery_service::FuelGaugeError::BusError + } +} + +// Implement SmartBattery for Battery Service. +embedded_batteries_async::impl_smart_battery_for_wrapper_type!(BatteryServiceWrapper, driver, BatteryServiceWrapperError); + +impl battery_service::FuelGauge for BatteryServiceWrapper { + type FuelGaugeError = BatteryServiceWrapperError; + type StaticData = battery_service::StaticBatteryMsgs; + type DynamicData = battery_service::DynamicBatteryMsgs; + + async fn initialize(&mut self) -> Result<(), Self::FuelGaugeError> { + self.driver.set_capacity_mode(zephyr::device::fuel_gauge::CapacityMode::MilliAmp).await?; + self.state.on_initialized(); + Ok(()) + } + + async fn ping(&mut self) -> Result<(), Self::FuelGaugeError> { + use embedded_batteries_async::smart_battery::SmartBattery; + SmartBattery::charging_voltage(self).await.inspect_err(|e| log::error!("Failed to ping fuel gauge: Call to self.charging_voltage() failed with e: {:?}", e))?; + log::info!("Successfully pinged fuel gauge."); + self.state.on_recovered(); + Ok(()) + } + + async fn update_dynamic_data(&mut self) -> Result<(), Self::FuelGaugeError> { + use embedded_batteries_async::smart_battery::SmartBattery; + + let average_current = SmartBattery::average_current(self).await?; + let battery_status = SmartBattery::battery_status(self).await?.into_bits(); + let battery_temp = SmartBattery::temperature(self).await?; + let charging_current = SmartBattery::charging_current(self).await?; + let charging_voltage = SmartBattery::charging_voltage(self).await?; + let voltage = SmartBattery::voltage(self).await?; + let current = SmartBattery::current(self).await?; + let full_charge_capacity = SmartBattery::full_charge_capacity(self).await?; + let remaining_capacity = SmartBattery::remaining_capacity(self).await?; + let relative_soc = SmartBattery::relative_state_of_charge(self).await?; + let cycle_count = SmartBattery::cycle_count(self).await?; + let max_error = SmartBattery::max_error(self).await.unwrap_or(0); // u_TODO: Hardcoding to zero since the Zephyr API doesn't expose max error yet + + self.state.on_dynamic_data(|d| { + d.average_current = average_current; + d.battery_status = battery_status; + d.max_power_mw = 0; + d.battery_temp = battery_temp; + d.sus_power_mw = 0; + d.charging_current = charging_current; + d.charging_voltage = charging_voltage; + d.voltage = voltage; + d.current = current; + d.full_charge_capacity = full_charge_capacity; + d.remaining_capacity = remaining_capacity; + d.relative_soc = relative_soc; + d.cycle_count = cycle_count; + d.max_error = max_error; + d.bmd_status = battery_service_interface::BmdStatusFlags::default(); + d.turbo_vload = 0; + d.turbo_rhf_effective_mohm = 0; + }); + Ok(()) + } + + async fn update_static_data(&mut self) -> Result<(), Self::FuelGaugeError> { + use embedded_batteries_async::smart_battery::SmartBattery; + use embedded_batteries_async::smart_battery::CapacityModeValue; + use battery_service_interface::fuel_gauge::{DEVICE_CHEMISTRY_SIZE, DEVICE_NAME_SIZE, MANUFACTURER_NAME_SIZE}; + + let design_voltage = SmartBattery::design_voltage(self).await?; + let design_capacity = SmartBattery::design_capacity(self).await?; + let design_capacity_value: u16 = match design_capacity { + CapacityModeValue::CentiWattUnsigned(v) => v, + CapacityModeValue::MilliAmpUnsigned(v) => v, + }; + let battery_mode = SmartBattery::battery_mode(self).await?; + let measurement_accuracy: u32 = SmartBattery::max_error(self).await.unwrap_or(0).into(); // u_TODO: Hardcoding to zero since the Zephyr API doesn't expose max error yet + + let mut manufacturer_name = [0u8; MANUFACTURER_NAME_SIZE]; + SmartBattery::manufacturer_name(self, &mut manufacturer_name).await?; + let mut device_name = [0u8; DEVICE_NAME_SIZE]; + SmartBattery::device_name(self, &mut device_name).await?; + let mut device_chemistry = [0u8; DEVICE_CHEMISTRY_SIZE]; + SmartBattery::device_chemistry(self, &mut device_chemistry).await?; + + self.state.on_static_data(|s| { + s.manufacturer_name = manufacturer_name; + s.device_name = device_name; + s.device_chemistry = device_chemistry; + s.design_voltage = design_voltage; + s.design_capacity = design_capacity; + s.battery_mode = battery_mode; + s.design_cap_warning = CapacityModeValue::MilliAmpUnsigned(design_capacity_value / 4); + s.design_cap_low = CapacityModeValue::MilliAmpUnsigned(design_capacity_value / 10); + s.measurement_accuracy = measurement_accuracy; + s.power_threshold_support = battery_service_interface::PowerThresholdSupport::empty(); + s.bmc_flags = battery_service_interface::BmcControlFlags::empty(); + s.bmd_capability = battery_service_interface::BmdCapabilityFlags::empty(); + }); + Ok(()) + } + + fn state(&self) -> &battery_service::State { + &self.state + } + + fn state_mut(&mut self) -> &mut battery_service::State { + &mut self.state + } } \ No newline at end of file diff --git a/samples/zephyr-odp/src/lib.rs b/samples/zephyr-odp/src/lib.rs index 17df3134..8044ae2a 100644 --- a/samples/zephyr-odp/src/lib.rs +++ b/samples/zephyr-odp/src/lib.rs @@ -72,8 +72,8 @@ async fn init(spawner: Spawner) { // UART service. Spawns out the thermal, battery, and timer services. #[embassy_executor::task] async fn uart_service(relay: RelayHandler) { - static UART_SERVICE: StaticCell> = StaticCell::new(); - let uart_service = UART_SERVICE.init(uart_service::DefaultService::default_smbusespi(relay).unwrap()); + static UART_SERVICE: StaticCell> = StaticCell::new(); + let uart_service = UART_SERVICE.init(uart_service::MctpSerialService::default_mctp_serial(relay).unwrap()); let uart_driver: zephyr::device::uart::Uart = zephyr::devicetree::labels::flexcomm0::get_instance().unwrap(); info!("Starting uart_service::task::uart_service()..."); let Err(e) = uart_service::task::uart_service(uart_service, uart_driver).await; diff --git a/zephyr/Cargo.toml b/zephyr/Cargo.toml index 0d6bdf68..7a6a3711 100644 --- a/zephyr/Cargo.toml +++ b/zephyr/Cargo.toml @@ -28,12 +28,10 @@ arrayvec = { version = "0.7.6", default-features = false } # Imports for pwm_fan.rs: embedded-fans-async = {version = "0.2.0"} -thermal-service-interface = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } +thermal-service-interface = { git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } # Imports for fuel_gauge.rs: embedded-batteries-async = {version = "0.3.4"} -battery-service = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } -battery-service-interface = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } embassy-time = "0.5" # u_Note: Must match battery-service's version since Duration is in their public API # Imports for uart.rs: diff --git a/zephyr/src/device/fuel_gauge.rs b/zephyr/src/device/fuel_gauge.rs index e6a0699e..04ff2f6f 100644 --- a/zephyr/src/device/fuel_gauge.rs +++ b/zephyr/src/device/fuel_gauge.rs @@ -131,26 +131,26 @@ impl FuelGauge { /// Reads the gauge's `manufacturer_name` into the provided buffer. /// According to Zephyr, manufacturer name is 1 byte of string length + 20 bytes of data (21 bytes total). - pub fn manufacturer_name(&self, buffer: &mut [u8]) -> crate::error::Result<()> { + pub(crate) fn manufacturer_name(&self, buffer: &mut [u8]) -> crate::error::Result<()> { self.get_buffer_prop(FuelGaugeBufferProp::ManufacturerName, buffer) } /// Reads the gauge's `device_name` into the provided buffer. /// According to Zephyr, device name is 1 byte of string length + 20 bytes of data (21 bytes total). - pub fn device_name(&self, buffer: &mut [u8]) -> crate::error::Result<()> { + pub(crate) fn device_name(&self, buffer: &mut [u8]) -> crate::error::Result<()> { self.get_buffer_prop(FuelGaugeBufferProp::DeviceName, buffer) } /// Reads the gauge's `device_chemistry` into the provided buffer. /// According to Zephyr, device chemistry is 1 byte of string length + 4 bytes of data (5 bytes total). - pub fn device_chemistry(&self, buffer: &mut [u8]) -> crate::error::Result<()> { + pub(crate) fn device_chemistry(&self, buffer: &mut [u8]) -> crate::error::Result<()> { self.get_buffer_prop(FuelGaugeBufferProp::DeviceChemistry, buffer) } /// Returns the gauge's `avg_current` reading. /// /// Zephyr notes: Provide a 1 minute average of the current on the battery. Does not check for flags or whether those values are bad readings. See driver instance header for details on implementation and how the average is calculated. Units in uA negative=discharging - pub fn avg_current(&self) -> crate::error::Result { + pub(crate) fn avg_current(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::AvgCurrent).map(|val| unsafe { *val.avg_current.as_ref() }) } @@ -166,7 +166,7 @@ impl FuelGauge { /// Returns the gauge's `current` reading. /// /// Zephyr notes: Battery current (uA); negative=discharging. - pub fn current(&self) -> crate::error::Result { + pub(crate) fn current(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::Current).map(|val| unsafe { *val.current.as_ref() }) } @@ -174,7 +174,7 @@ impl FuelGauge { /// Returns the gauge's `cycle_count` reading. /// /// Zephyr notes: Cycle count in 1/100ths (number of charge/discharge cycles). - pub fn cycle_count(&self) -> crate::error::Result { + pub(crate) fn cycle_count(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::CycleCount).map(|val| unsafe { *val.cycle_count.as_ref() }) } @@ -198,7 +198,7 @@ impl FuelGauge { /// Returns the gauge's `full_charge_capacity` reading. /// /// Zephyr notes: Full Charge Capacity in uAh (might change in some implementations to determine wear). - pub fn full_charge_capacity(&self) -> crate::error::Result { + pub(crate) fn full_charge_capacity(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::FullChargeCapacity).map(|val| unsafe { *val.full_charge_capacity.as_ref() }) } @@ -214,7 +214,7 @@ impl FuelGauge { /// Returns the gauge's `remaining_capacity` reading. /// /// Zephyr notes: Remaining capacity in uAh. - pub fn remaining_capacity(&self) -> crate::error::Result { + pub(crate) fn remaining_capacity(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::RemainingCapacity).map(|val| unsafe { *val.remaining_capacity.as_ref() }) } @@ -222,7 +222,7 @@ impl FuelGauge { /// Returns the gauge's `runtime_to_empty` reading. /// /// Zephyr notes: Remaining battery life time in minutes. - pub fn runtime_to_empty(&self) -> crate::error::Result { + pub(crate) fn runtime_to_empty(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::RuntimeToEmpty).map(|val| unsafe { *val.runtime_to_empty.as_ref() }) } @@ -246,7 +246,7 @@ impl FuelGauge { /// Returns the gauge's `absolute_state_of_charge` reading. /// /// Zephyr notes: Absolute state of charge (percent, 0-100) - expressed as % of design capacity. - pub fn absolute_state_of_charge(&self) -> crate::error::Result { + pub(crate) fn absolute_state_of_charge(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::AbsoluteStateOfCharge).map(|val| unsafe { *val.absolute_state_of_charge.as_ref() }) } @@ -254,7 +254,7 @@ impl FuelGauge { /// Returns the gauge's `relative_state_of_charge` reading. /// /// Zephyr notes: Relative state of charge (percent, 0-100) - expressed as % of full charge capacity. - pub fn relative_state_of_charge(&self) -> crate::error::Result { + pub(crate) fn relative_state_of_charge(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::RelativeStateOfCharge).map(|val| unsafe { *val.relative_state_of_charge.as_ref() }) } @@ -262,7 +262,7 @@ impl FuelGauge { /// Returns the gauge's `temperature` reading. /// /// Zephyr notes: Temperature in 0.1 K. - pub fn temperature(&self) -> crate::error::Result { + pub(crate) fn temperature(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::Temperature).map(|val| unsafe { *val.temperature.as_ref() }) } @@ -270,7 +270,7 @@ impl FuelGauge { /// Returns the gauge's `voltage` reading. /// /// Zephyr notes: Battery voltage (uV). - pub fn voltage(&self) -> crate::error::Result { + pub(crate) fn voltage(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::Voltage).map(|val| unsafe { *val.voltage.as_ref() }) } @@ -278,7 +278,7 @@ impl FuelGauge { /// Returns the gauge's `sbs_mode` reading. /// /// Zephyr notes: Battery Mode (flags). - pub fn sbs_mode(&self) -> crate::error::Result { + pub(crate) fn sbs_mode(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::SbsMode).map(|val| unsafe { *val.sbs_mode.as_ref() }) } @@ -286,7 +286,7 @@ impl FuelGauge { /// Sets the gauge's `sbs_mode` setting. /// /// Zephyr notes: Battery Mode (flags). - pub fn set_sbs_mode(&self, value: u16) -> crate::error::Result<()> { + pub(crate) fn set_sbs_mode(&self, value: u16) -> crate::error::Result<()> { // SAFETY: All union fields are primitive types, so zeroed memory is valid. let mut val: crate::raw::fuel_gauge_prop_val = unsafe { core::mem::zeroed() }; @@ -299,7 +299,7 @@ impl FuelGauge { /// Returns the gauge's `chg_current` (charge current) reading. /// /// Zephyr notes: Battery desired Max Charging Current (uA). - pub fn chg_current(&self) -> crate::error::Result { + pub(crate) fn chg_current(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::ChgCurrent).map(|val| unsafe { *val.chg_current.as_ref() }) } @@ -307,7 +307,7 @@ impl FuelGauge { /// Returns the gauge's `chg_voltage` (charge voltage) reading. /// /// Zephyr notes: Battery desired Max Charging Voltage (uV). - pub fn chg_voltage(&self) -> crate::error::Result { + pub(crate) fn chg_voltage(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::ChgVoltage).map(|val| unsafe { *val.chg_voltage.as_ref() }) } @@ -315,7 +315,7 @@ impl FuelGauge { /// Returns the gauge's `fg_status` reading. /// /// Zephyr notes: Alarm, Status and Error codes (flags). - pub fn fg_status(&self) -> crate::error::Result { + pub(crate) fn fg_status(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::FgStatus).map(|val| unsafe { *val.fg_status.as_ref() }) } @@ -323,7 +323,7 @@ impl FuelGauge { /// Returns the gauge's `design_cap` (design capacity) reading. /// /// Zephyr notes: Design Capacity (mAh or 10mWh). - pub fn design_cap(&self) -> crate::error::Result { + pub(crate) fn design_cap(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::DesignCap).map(|val| unsafe { *val.design_cap.as_ref() }) } @@ -331,7 +331,7 @@ impl FuelGauge { /// Returns the gauge's `design_volt` (design voltage) reading. /// /// Zephyr notes: Design Voltage (mV). - pub fn design_volt(&self) -> crate::error::Result { + pub(crate) fn design_volt(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::DesignVolt).map(|val| unsafe { *val.design_volt.as_ref() }) } @@ -339,7 +339,7 @@ impl FuelGauge { /// Returns the gauge's `sbs_at_rate` reading. /// /// Zephyr notes: AtRate (mA or 10 mW). - pub fn sbs_at_rate(&self) -> crate::error::Result { + pub(crate) fn sbs_at_rate(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::SbsAtRate).map(|val| unsafe { *val.sbs_at_rate.as_ref() }) } @@ -347,7 +347,7 @@ impl FuelGauge { /// Sets the gauge's `at_rate` setting. /// /// Zephyr notes: AtRate (mA or 10 mW). - pub fn set_sbs_at_rate(&self, value: i16) -> crate::error::Result<()> { + pub(crate) fn set_sbs_at_rate(&self, value: i16) -> crate::error::Result<()> { // SAFETY: All union fields are primitive types, so zeroed memory is valid. let mut val: crate::raw::fuel_gauge_prop_val = unsafe { core::mem::zeroed() }; @@ -360,7 +360,7 @@ impl FuelGauge { /// Returns the gauge's `sbs_at_rate_time_to_full` reading. /// /// Zephyr notes: AtRateTimeToFull (minutes). - pub fn sbs_at_rate_time_to_full(&self) -> crate::error::Result { + pub(crate) fn sbs_at_rate_time_to_full(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::SbsAtRateTimeToFull).map(|val| unsafe { *val.sbs_at_rate_time_to_full.as_ref() }) } @@ -368,7 +368,7 @@ impl FuelGauge { /// Returns the gauge's `sbs_at_rate_time_to_empty` reading. /// /// Zephyr notes: AtRateTimeToEmpty (minutes). - pub fn sbs_at_rate_time_to_empty(&self) -> crate::error::Result { + pub(crate) fn sbs_at_rate_time_to_empty(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::SbsAtRateTimeToEmpty).map(|val| unsafe { *val.sbs_at_rate_time_to_empty.as_ref() }) } @@ -376,7 +376,7 @@ impl FuelGauge { /// Returns the gauge's `sbs_at_rate_ok` reading. /// /// Zephyr notes: AtRateOK (boolean). - pub fn sbs_at_rate_ok(&self) -> crate::error::Result { + pub(crate) fn sbs_at_rate_ok(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::SbsAtRateOk).map(|val| unsafe { *val.sbs_at_rate_ok.as_ref() }) } @@ -384,7 +384,7 @@ impl FuelGauge { /// Returns the gauge's `sbs_remaining_capacity_alarm` reading. /// /// Zephyr notes: Remaining Capacity Alarm (mAh or 10mWh). - pub fn sbs_remaining_capacity_alarm(&self) -> crate::error::Result { + pub(crate) fn sbs_remaining_capacity_alarm(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::SbsRemainingCapacityAlarm).map(|val| unsafe { *val.sbs_remaining_capacity_alarm.as_ref() }) } @@ -392,7 +392,7 @@ impl FuelGauge { /// Sets the gauge's `sbs_remaining_capacity_alarm` setting. /// /// Zephyr notes: Remaining Capacity Alarm (mAh or 10mWh). - pub fn set_sbs_remaining_capacity_alarm(&self, value: u16) -> crate::error::Result<()> { + pub(crate) fn set_sbs_remaining_capacity_alarm(&self, value: u16) -> crate::error::Result<()> { // SAFETY: All union fields are primitive types, so zeroed memory is valid. let mut val: crate::raw::fuel_gauge_prop_val = unsafe { core::mem::zeroed() }; @@ -405,7 +405,7 @@ impl FuelGauge { /// Returns the gauge's `sbs_remaining_time_alarm` reading. /// /// Zephyr notes: Remaining Time Alarm (minutes). - pub fn sbs_remaining_time_alarm(&self) -> crate::error::Result { + pub(crate) fn sbs_remaining_time_alarm(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::SbsRemainingTimeAlarm).map(|val| unsafe { *val.sbs_remaining_time_alarm.as_ref() }) } @@ -413,7 +413,7 @@ impl FuelGauge { /// Sets the gauge's `sbs_remaining_time_alarm` setting. /// /// Zephyr notes: Remaining Time Alarm (minutes). - pub fn set_sbs_remaining_time_alarm(&self, value: u16) -> crate::error::Result<()> { + pub(crate) fn set_sbs_remaining_time_alarm(&self, value: u16) -> crate::error::Result<()> { // SAFETY: All union fields are primitive types, so zeroed memory is valid. let mut val: crate::raw::fuel_gauge_prop_val = unsafe { core::mem::zeroed() }; @@ -929,119 +929,12 @@ impl embedded_batteries_async::smart_battery::ErrorType for FuelGauge { impl embedded_batteries_async::smart_battery::Error for crate::error::Error { // u_Note: eventually add better mapping to this fn kind(&self) -> embedded_batteries_async::smart_battery::ErrorKind { - embedded_batteries_async::smart_battery::ErrorKind::Other - } -} - -impl battery_service::controller::Controller for FuelGauge { - type ControllerError = crate::error::Error; - - async fn initialize(&mut self) -> Result<(), Self::ControllerError> { - self.set_capacity_mode(CapacityMode::MilliAmp).await - } - - async fn ping(&mut self) -> Result<(), Self::ControllerError> { - use embedded_batteries_async::smart_battery::SmartBattery; - SmartBattery::charging_voltage(self).await.inspect_err(|e| log::error!("Failed to ping fuel gauge: Call to self.charging_voltage() failed with e: {}", e))?; - log::info!("Successfully pinged fuel gauge."); - Ok(()) - } - - async fn get_dynamic_data(&mut self) -> Result { - use embedded_batteries_async::smart_battery::SmartBattery; - use embedded_batteries_async::smart_battery::CapacityModeValue; - - let voltage_mv: u32 = SmartBattery::voltage(self).await?.into(); - - let new_msgs = battery_service::device::DynamicBatteryMsgs { - average_current_ma: SmartBattery::average_current(self).await?, - battery_status: SmartBattery::battery_status(self).await?.into_bits(), - max_power_mw: 0, - battery_temp_dk: SmartBattery::temperature(self).await?, - sus_power_mw: 0, - charging_current_ma: SmartBattery::charging_current(self).await?, - charging_voltage_mv: SmartBattery::charging_voltage(self).await?, - voltage_mv: voltage_mv as u16, - current_ma: SmartBattery::current(self).await?, - full_charge_capacity_mwh: match SmartBattery::full_charge_capacity(self).await? { - CapacityModeValue::CentiWattUnsigned(cwh) => (cwh as u32) * 10, - CapacityModeValue::MilliAmpUnsigned(mah) => (mah as u32) * voltage_mv / 1000, - }, - remaining_capacity_mwh: match SmartBattery::remaining_capacity(self).await? { - CapacityModeValue::CentiWattUnsigned(cwh) => (cwh as u32) * 10, - CapacityModeValue::MilliAmpUnsigned(mah) => (mah as u32) * voltage_mv / 1000, - }, - relative_soc_pct: SmartBattery::relative_state_of_charge(self).await?.into(), - cycle_count: SmartBattery::cycle_count(self).await?, - max_error_pct: SmartBattery::max_error(self).await.unwrap_or(0).into(), // u_TODO: Hardcoding to zero since the Zephyr API doesn't expose max error yet - bmd_status: battery_service_interface::BmdStatusFlags::default(), - turbo_vload_mv: 0, - turbo_rhf_effective_mohm: 0, - }; - Ok(new_msgs) - } - - #[allow(clippy::indexing_slicing)] - async fn get_static_data(&mut self) -> Result { - use embedded_batteries_async::smart_battery::SmartBattery; - use embedded_batteries_async::smart_battery::CapacityModeValue; - - let design_voltage_mv: u32 = SmartBattery::design_voltage(self).await?.into(); - let design_capacity_mwh: u32 = match SmartBattery::design_capacity(self).await?.into() { - CapacityModeValue::CentiWattUnsigned(cwh) => (cwh as u32) * 10, - CapacityModeValue::MilliAmpUnsigned(mah) => (mah as u32) * design_voltage_mv / 1000, - }; - - let mut new_msgs = battery_service::device::StaticBatteryMsgs { - manufacturer_name: Default::default(), - device_name: Default::default(), - device_chemistry: Default::default(), - design_voltage_mv: design_voltage_mv as u16, - design_capacity_mwh: design_capacity_mwh, - device_chemistry_id: Default::default(), - serial_num: Default::default(), - battery_mode: SmartBattery::battery_mode(self).await?, - design_cap_warning: design_capacity_mwh / 4, - design_cap_low: design_capacity_mwh / 10, - measurement_accuracy: SmartBattery::max_error(self).await.unwrap_or(0).into(), // u_TODO: Hardcoding to zero since the Zephyr API doesn't expose max error yet - max_sample_time: Default::default(), - min_sample_time: Default::default(), - max_averaging_interval: Default::default(), - min_averaging_interval: Default::default(), - cap_granularity_1: Default::default(), - cap_granularity_2: Default::default(), - power_threshold_support: battery_service_interface::PowerThresholdSupport::empty(), - max_instant_pwr_threshold: Default::default(), - max_sus_pwr_threshold: Default::default(), - bmc_flags: battery_service_interface::BmcControlFlags::empty(), - bmd_capability: battery_service_interface::BmdCapabilityFlags::empty(), - bmd_recalibrate_count: Default::default(), - bmd_quick_recalibrate_time: Default::default(), - bmd_slow_recalibrate_time: Default::default(), - }; - let mut buf = [0u8; 21]; + use embedded_batteries_async::smart_battery::ErrorKind; - let buf_len = new_msgs.manufacturer_name.len(); - SmartBattery::manufacturer_name(self, &mut buf[..buf_len]).await?; - new_msgs.manufacturer_name.copy_from_slice(&buf[..buf_len]); - - let buf_len = new_msgs.device_name.len(); - SmartBattery::device_name(self, &mut buf[..buf_len]).await?; - new_msgs.device_name.copy_from_slice(&buf[..buf_len]); - - let buf_len = new_msgs.device_chemistry.len(); - SmartBattery::device_chemistry(self, &mut buf[..buf_len]).await?; - new_msgs.device_chemistry.copy_from_slice(&buf[..buf_len]); - - Ok(new_msgs) - } - - async fn get_device_event(&mut self) -> battery_service::controller::ControllerEvent { - // TODO: Loop forever till we figure out what we want to do here - loop { - embassy_time::Timer::after_secs(1000000).await; + let code: u32 = self.0; + match code { + crate::raw::EIO | crate::raw::ETIMEDOUT | crate::raw::ENXIO => ErrorKind::CommError, + _ => ErrorKind::Other, } } - - fn set_timeout(&mut self, _duration: embassy_time::Duration) {} } \ No newline at end of file From ee945ab29728d4e3bed151b15718aa631cd52639 Mon Sep 17 00:00:00 2001 From: bjackson312006 Date: Wed, 8 Jul 2026 12:16:14 -0700 Subject: [PATCH 5/9] native-sim + test-tui working --- clean.sh | 36 +++++++++ doc.sh | 74 ++++++++++++++----- dt-rust.yaml | 1 + native-arm64.sh | 3 + .../mimxrt685_evk_mimxrt685s_cm33.overlay | 4 + .../boards/native_sim_native_64.overlay | 9 +-- .../zephyr-odp/boards/qemu_cortex_m3.overlay | 2 +- samples/zephyr-odp/prj.conf | 6 ++ samples/zephyr-odp/src/lib.rs | 15 +++- samples/zephyr-odp/src/time_alarm.rs | 12 +-- 10 files changed, 131 insertions(+), 31 deletions(-) create mode 100755 clean.sh diff --git a/clean.sh b/clean.sh new file mode 100755 index 00000000..dd1f9055 --- /dev/null +++ b/clean.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# Remove every Zephyr/west build directory for this project so the next build is pristine. + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Find the west workspace top level. +if command -v west >/dev/null 2>&1; then + WS="$(west topdir)" +else + WS="$(cd "$SCRIPT_DIR/../../.." && pwd)" +fi + +BUILD_DIRS=( + "$SCRIPT_DIR"/build + "$SCRIPT_DIR"/docgen/build +) +for d in "$WS"/build "$WS"/build-*; do + [ -d "$d" ] && BUILD_DIRS+=("$d") +done + +removed_any=0 +for d in "${BUILD_DIRS[@]}"; do + if [ -d "$d" ]; then + echo "Removing $d" + rm -rf "$d" + removed_any=1 + fi +done + +if [ "$removed_any" -eq 0 ]; then + echo "Nothing to clean; no build directories found." +else + echo "Clean complete." +fi diff --git a/doc.sh b/doc.sh index 8f546268..d265a64a 100755 --- a/doc.sh +++ b/doc.sh @@ -1,10 +1,11 @@ #!/bin/bash -# Build and automatically open the Zephyr Rust documentation. -# On WSL it opens the docs in the Windows default browser; on native Linux +# Build and automatically open the Zephyr Rust documentation for every configured +# build directory found in the workspace (build, build-native, build-qemu, ...). +# Empty or not-yet-configured build directories are skipped. +# +# On WSL it opens the docs in the Windows default browser, on native Linux # it opens them with xdg-open. # -# Run from anywhere inside the west workspace; the build dir is resolved -# relative to the workspace top level (override with the BUILD_DIR env var). set -e @@ -17,25 +18,62 @@ else echo "Detected Native Linux." fi +# Open a generated doc file with the platform's default browser handler. +open_doc() { + local path="$1" + if [ "$IS_WSL" -eq 1 ]; then + # Convert the Linux path to a Windows path and launch the default browser. + cmd.exe /c start "" "$(wslpath -w "$path")" + else + # Open with the default Linux browser handler. + xdg-open "$path" + fi +} + WS="$(west topdir)" -BUILD_DIR="${BUILD_DIR:-$WS/build}" -# 1. Run the west build target -west build -d "$BUILD_DIR" -t rustdoc +# Collect the build directories to document. If BUILD_DIR is set, only use that one. +# Otherwise, document $WS/build plus any $WS/build-* variant (build-native, build-qemu, ...). +BUILD_DIRS=() +if [ -n "$BUILD_DIR" ]; then + BUILD_DIRS+=("$BUILD_DIR") +else + for d in "$WS"/build "$WS"/build-*; do + [ -d "$d" ] && BUILD_DIRS+=("$d") + done +fi -# 2. Find the generated index.html file dynamically -LINUX_PATH="$(find "$BUILD_DIR" -name "index.html" | grep "doc/rustapp/index.html" | head -n 1)" +if [ "${#BUILD_DIRS[@]}" -eq 0 ]; then + echo "No build directories found under $WS." + exit 0 +fi -if [ -n "$LINUX_PATH" ]; then - if [ "$IS_WSL" -eq 1 ]; then - # Convert the Linux path to a Windows path and launch the default browser - WIN_PATH="$(wslpath -w "$LINUX_PATH")" - cmd.exe /c start "" "$WIN_PATH" +generated_any=0 +for dir in "${BUILD_DIRS[@]}"; do + # Skip anything that isn't a configured Zephyr build (empty / not yet built). + if [ ! -f "$dir/build.ninja" ] && [ ! -f "$dir/CMakeCache.txt" ]; then + echo "Skipping $dir (empty or not a configured build directory)." + continue + fi + + echo "Generating Rust docs for $dir ..." + if ! west build -d "$dir" -t rustdoc; then + echo "Warning: rustdoc build failed for $dir; skipping." + continue + fi + + # Find the generated index.html for the rustapp crate in this build dir. + DOC_PATH="$(find "$dir" -path "*doc/rustapp/index.html" | head -n 1)" + if [ -n "$DOC_PATH" ]; then + echo "Opening $DOC_PATH" + open_doc "$DOC_PATH" + generated_any=1 else - # Open with the default Linux browser handler - xdg-open "$LINUX_PATH" + echo "Warning: could not locate the generated index.html in $dir." fi -else - echo "Error: Could not locate the generated index.html file." +done + +if [ "$generated_any" -eq 0 ]; then + echo "Error: no documentation was generated." exit 1 fi diff --git a/dt-rust.yaml b/dt-rust.yaml index a0e727a1..c1dad4a3 100644 --- a/dt-rust.yaml +++ b/dt-rust.yaml @@ -164,6 +164,7 @@ names: - "nxp,lpc-usart" # u_Note: Should probably pull in more eventually but this is what i have - "zephyr,uart-emul" + - "zephyr,native-pty-uart" level: 0 actions: - !Instance diff --git a/native-arm64.sh b/native-arm64.sh index 6c24679a..0c2ecddd 100755 --- a/native-arm64.sh +++ b/native-arm64.sh @@ -6,6 +6,9 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BUILD_DIR="${BUILD_DIR:-$(west topdir)/build-native}" +# Kill any native_sim instances left running from previous runs. +pkill -f "$BUILD_DIR/zephyr/zephyr.exe" || true + # native_sim/native/64 on a 64-bit host maps to the aarch64/x86_64 bare-metal # Rust target if command -v rustup >/dev/null 2>&1; then diff --git a/samples/zephyr-odp/boards/mimxrt685_evk_mimxrt685s_cm33.overlay b/samples/zephyr-odp/boards/mimxrt685_evk_mimxrt685s_cm33.overlay index 2e8dd1ca..d9025774 100644 --- a/samples/zephyr-odp/boards/mimxrt685_evk_mimxrt685s_cm33.overlay +++ b/samples/zephyr-odp/boards/mimxrt685_evk_mimxrt685s_cm33.overlay @@ -59,4 +59,8 @@ rtc = <&rtc_emul>; /* phandle to the real RTC */ resolution-hz = <1>; /* 1 Hz resolution */ }; +}; + +cool_uart: &flexcomm0 { + status = "okay"; }; \ No newline at end of file diff --git a/samples/zephyr-odp/boards/native_sim_native_64.overlay b/samples/zephyr-odp/boards/native_sim_native_64.overlay index 44f34485..1e672c58 100644 --- a/samples/zephyr-odp/boards/native_sim_native_64.overlay +++ b/samples/zephyr-odp/boards/native_sim_native_64.overlay @@ -47,11 +47,8 @@ rtc = <&rtc>; resolution-hz = <1>; }; +}; - flexcomm0: uart-emul0 { - status = "okay"; - compatible = "zephyr,uart-emul"; - current-speed = <115200>; - loopback; - }; +cool_uart: &uart1 { + status = "okay"; }; diff --git a/samples/zephyr-odp/boards/qemu_cortex_m3.overlay b/samples/zephyr-odp/boards/qemu_cortex_m3.overlay index 4ab9d916..dd966e60 100644 --- a/samples/zephyr-odp/boards/qemu_cortex_m3.overlay +++ b/samples/zephyr-odp/boards/qemu_cortex_m3.overlay @@ -68,7 +68,7 @@ }; /* Emulated UART */ - flexcomm0: uart-emul0 { + cool_uart: uart-emul0 { status = "okay"; compatible = "zephyr,uart-emul"; current-speed = <115200>; diff --git a/samples/zephyr-odp/prj.conf b/samples/zephyr-odp/prj.conf index 86f16cc0..7b550d1a 100644 --- a/samples/zephyr-odp/prj.conf +++ b/samples/zephyr-odp/prj.conf @@ -9,6 +9,12 @@ CONFIG_RUST=y CONFIG_RUST_ALLOC=y CONFIG_LOG=y +# The embassy-time driver is told our tick rate statically via the `tick-hz-10_000` +# Cargo feature (see Cargo.toml). But, this doesn't always match with Zephyr's tick +# rate (i'm pretty sure it can differ from board to board). So, I'm just going to set the +# Zephyr tick rate to 10,000 here across the board +CONFIG_SYS_CLOCK_TICKS_PER_SEC=10000 + # RTT logging CONFIG_LOG_BACKEND_RTT=y CONFIG_USE_SEGGER_RTT=y diff --git a/samples/zephyr-odp/src/lib.rs b/samples/zephyr-odp/src/lib.rs index 8044ae2a..03f87f8d 100644 --- a/samples/zephyr-odp/src/lib.rs +++ b/samples/zephyr-odp/src/lib.rs @@ -3,6 +3,9 @@ #![no_std] +#![allow(unused_variables)] +#![allow(dead_code)] + use core::ffi::c_int; use embassy_executor::Spawner; use log::info; @@ -67,6 +70,16 @@ async fn init(spawner: Spawner) { // Spawn all the different tasks. spawner.spawn(uart_service(relay)).expect("Failed to spawn uart_service()"); + spawner.spawn(heartbeat()).expect("Failed to spawn heartbeat()"); +} + +#[embassy_executor::task] +async fn heartbeat() { + use embassy_time::Timer; + loop { + log::info!("Heartbeat"); + Timer::after_secs(1).await; + } } // UART service. Spawns out the thermal, battery, and timer services. @@ -74,7 +87,7 @@ async fn init(spawner: Spawner) { async fn uart_service(relay: RelayHandler) { static UART_SERVICE: StaticCell> = StaticCell::new(); let uart_service = UART_SERVICE.init(uart_service::MctpSerialService::default_mctp_serial(relay).unwrap()); - let uart_driver: zephyr::device::uart::Uart = zephyr::devicetree::labels::flexcomm0::get_instance().unwrap(); + let uart_driver: zephyr::device::uart::Uart = zephyr::devicetree::labels::cool_uart::get_instance().unwrap(); info!("Starting uart_service::task::uart_service()..."); let Err(e) = uart_service::task::uart_service(uart_service, uart_driver).await; info!("After uart_service::task::uart_service()"); diff --git a/samples/zephyr-odp/src/time_alarm.rs b/samples/zephyr-odp/src/time_alarm.rs index 68c01dd5..61ad9104 100644 --- a/samples/zephyr-odp/src/time_alarm.rs +++ b/samples/zephyr-odp/src/time_alarm.rs @@ -5,20 +5,22 @@ pub type TimeAlarmService = time_alarm_service::Service<'static>; pub async fn init(spawner: embassy_executor::Spawner) -> TimeAlarmService { use static_cell::StaticCell; + const ERASED_NVRAM: u32 = u32::MAX; + static TZ_STORAGE: StaticCell> = StaticCell::new(); - let tz_storage = TZ_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(0)); + let tz_storage = TZ_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(ERASED_NVRAM)); static AC_EXP_STORAGE: StaticCell> = StaticCell::new(); - let ac_exp_storage = AC_EXP_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(0)); + let ac_exp_storage = AC_EXP_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(ERASED_NVRAM)); static AC_POL_STORAGE: StaticCell> = StaticCell::new(); - let ac_pol_storage = AC_POL_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(0)); + let ac_pol_storage = AC_POL_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(ERASED_NVRAM)); static DC_EXP_STORAGE: StaticCell> = StaticCell::new(); - let dc_exp_storage = DC_EXP_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(0)); + let dc_exp_storage = DC_EXP_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(ERASED_NVRAM)); static DC_POL_STORAGE: StaticCell> = StaticCell::new(); - let dc_pol_storage = DC_POL_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(0)); + let dc_pol_storage = DC_POL_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(ERASED_NVRAM)); static RTC_DRIVER: StaticCell = StaticCell::new(); let rtc_driver = RTC_DRIVER.init(zephyr::devicetree::rs_rtc::get_instance().expect("Failed to call zephyr::devicetree::rs_rtc::get_instance()")); From 46e6f7136fe7bd444f5d87457c8dbf60599e70b8 Mon Sep 17 00:00:00 2001 From: bjackson312006 Date: Wed, 8 Jul 2026 14:30:23 -0700 Subject: [PATCH 6/9] point to embedded-services main rather than my fork --- build.sh => mimxrt.sh | 0 samples/zephyr-odp/Cargo.toml | 24 ++++++++++++------------ zephyr/Cargo.toml | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) rename build.sh => mimxrt.sh (100%) diff --git a/build.sh b/mimxrt.sh similarity index 100% rename from build.sh rename to mimxrt.sh diff --git a/samples/zephyr-odp/Cargo.toml b/samples/zephyr-odp/Cargo.toml index 28e63493..fde817a7 100644 --- a/samples/zephyr-odp/Cargo.toml +++ b/samples/zephyr-odp/Cargo.toml @@ -20,25 +20,25 @@ embassy-futures = "0.1.1" embassy-sync = "0.8" embassy-time = { version = "0.5", features = ["tick-hz-10_000"] } # u_Note: tick-hz-10_000 is needed to match with zephyr. If this isn't here, something like Timer::after_secs(1) would actually wait for 100 seconds instead of 1 seconds. There should probably be some kind of way to do this manually so the embassy APIs work no matter what the Zephyr tick rate is. embedded-sensors-hal-async = "0.3.0" -embedded-services = { git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } -odp-service-common = { git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } +embedded-services = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } +odp-service-common = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } static_cell = "2.1" -thermal-service = { git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } -thermal-service-interface = { git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } -thermal-service-relay = { git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } -uart-service = { git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } +thermal-service = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } +thermal-service-interface = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } +thermal-service-relay = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } +uart-service = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } zephyr = { version = "0.1.0", features = ["time-driver", "executor-zephyr"] } log = { version = "0.4.22" } # features = ["max_level_error", "release_max_level_error"] embedded-io-async = {version = "0.7.0"} embedded-batteries-async = {version = "0.3.4"} -battery-service = { package = "battery-service", git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a", features = ["mock"] } -battery-service-interface = { package = "battery-service-interface", git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } -battery-service-relay = { package = "battery-service-relay", git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } +battery-service = { package = "battery-service", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d", features = ["mock"] } +battery-service-interface = { package = "battery-service-interface", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } +battery-service-relay = { package = "battery-service-relay", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } -time-alarm-service = { git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a", features = ["mock"]} -time-alarm-service-interface = { package = "time-alarm-service-interface", git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } -time-alarm-service-relay = { package = "time-alarm-service-relay", git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } +time-alarm-service = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d", features = ["mock"]} +time-alarm-service-interface = { package = "time-alarm-service-interface", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } +time-alarm-service-relay = { package = "time-alarm-service-relay", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } [lints.clippy] perf = { level = "forbid" } diff --git a/zephyr/Cargo.toml b/zephyr/Cargo.toml index 7a6a3711..f7b86dbf 100644 --- a/zephyr/Cargo.toml +++ b/zephyr/Cargo.toml @@ -28,7 +28,7 @@ arrayvec = { version = "0.7.6", default-features = false } # Imports for pwm_fan.rs: embedded-fans-async = {version = "0.2.0"} -thermal-service-interface = { git = "https://github.com/bjackson312006/embedded-services", rev = "6495326afa639ec661fbc6175f9059dcdd45510a" } +thermal-service-interface = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } # Imports for fuel_gauge.rs: embedded-batteries-async = {version = "0.3.4"} From 5e4acb9fabc4996b27dc4cd20ece33e9508c3199 Mon Sep 17 00:00:00 2001 From: bjackson312006 Date: Wed, 8 Jul 2026 14:40:33 -0700 Subject: [PATCH 7/9] renamed native-arm64.sh to native-sim.sh. Now supports x86_64 in adddition to just ARM64 --- native-arm64.sh => native-sim.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) rename native-arm64.sh => native-sim.sh (67%) diff --git a/native-arm64.sh b/native-sim.sh similarity index 67% rename from native-arm64.sh rename to native-sim.sh index 0c2ecddd..4a543e39 100755 --- a/native-arm64.sh +++ b/native-sim.sh @@ -10,9 +10,14 @@ BUILD_DIR="${BUILD_DIR:-$(west topdir)/build-native}" pkill -f "$BUILD_DIR/zephyr/zephyr.exe" || true # native_sim/native/64 on a 64-bit host maps to the aarch64/x86_64 bare-metal -# Rust target +# Rust target (see CMakeLists.txt). Pick the one matching the host CPU. +case "$(uname -m)" in + aarch64|arm64) RUST_TARGET="aarch64-unknown-none" ;; + x86_64|amd64) RUST_TARGET="x86_64-unknown-none" ;; + *) echo "Unsupported host architecture: $(uname -m)" >&2; exit 1 ;; +esac if command -v rustup >/dev/null 2>&1; then - rustup target add aarch64-unknown-none + rustup target add "$RUST_TARGET" fi # Build for native_sim (pass -p always via WEST_BUILD_ARGS for a pristine build). From d74a8750c402e1f571bdae521a4826b52a5b57df Mon Sep 17 00:00:00 2001 From: bjackson312006 Date: Wed, 8 Jul 2026 14:46:14 -0700 Subject: [PATCH 8/9] no more heartbeat task --- samples/zephyr-odp/src/lib.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/samples/zephyr-odp/src/lib.rs b/samples/zephyr-odp/src/lib.rs index 03f87f8d..69cbc438 100644 --- a/samples/zephyr-odp/src/lib.rs +++ b/samples/zephyr-odp/src/lib.rs @@ -70,16 +70,6 @@ async fn init(spawner: Spawner) { // Spawn all the different tasks. spawner.spawn(uart_service(relay)).expect("Failed to spawn uart_service()"); - spawner.spawn(heartbeat()).expect("Failed to spawn heartbeat()"); -} - -#[embassy_executor::task] -async fn heartbeat() { - use embassy_time::Timer; - loop { - log::info!("Heartbeat"); - Timer::after_secs(1).await; - } } // UART service. Spawns out the thermal, battery, and timer services. From 0a4c06c815809aa70eba3ad8fdeb232a4fa562de Mon Sep 17 00:00:00 2001 From: Blake Jackson Date: Wed, 8 Jul 2026 18:54:46 -0700 Subject: [PATCH 9/9] Revert "secure-ec-poc: Native Sim" --- mimxrt.sh => build.sh | 0 clean.sh | 36 --- doc.sh | 74 ++---- dt-rust.yaml | 1 - native-sim.sh | 27 --- samples/zephyr-odp/Cargo.toml | 24 +- .../mimxrt685_evk_mimxrt685s_cm33.overlay | 4 - .../boards/native_sim_native_64.conf | 18 -- .../boards/native_sim_native_64.overlay | 54 ----- .../zephyr-odp/boards/qemu_cortex_m3.overlay | 2 +- samples/zephyr-odp/prj.conf | 6 - samples/zephyr-odp/sample.yaml | 1 - samples/zephyr-odp/src/battery.rs | 218 ++++-------------- samples/zephyr-odp/src/lib.rs | 9 +- samples/zephyr-odp/src/time_alarm.rs | 12 +- zephyr/Cargo.toml | 4 +- zephyr/src/device/fuel_gauge.rs | 175 +++++++++++--- 17 files changed, 223 insertions(+), 442 deletions(-) rename mimxrt.sh => build.sh (100%) delete mode 100755 clean.sh delete mode 100755 native-sim.sh delete mode 100644 samples/zephyr-odp/boards/native_sim_native_64.conf delete mode 100644 samples/zephyr-odp/boards/native_sim_native_64.overlay diff --git a/mimxrt.sh b/build.sh similarity index 100% rename from mimxrt.sh rename to build.sh diff --git a/clean.sh b/clean.sh deleted file mode 100755 index dd1f9055..00000000 --- a/clean.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -# Remove every Zephyr/west build directory for this project so the next build is pristine. - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Find the west workspace top level. -if command -v west >/dev/null 2>&1; then - WS="$(west topdir)" -else - WS="$(cd "$SCRIPT_DIR/../../.." && pwd)" -fi - -BUILD_DIRS=( - "$SCRIPT_DIR"/build - "$SCRIPT_DIR"/docgen/build -) -for d in "$WS"/build "$WS"/build-*; do - [ -d "$d" ] && BUILD_DIRS+=("$d") -done - -removed_any=0 -for d in "${BUILD_DIRS[@]}"; do - if [ -d "$d" ]; then - echo "Removing $d" - rm -rf "$d" - removed_any=1 - fi -done - -if [ "$removed_any" -eq 0 ]; then - echo "Nothing to clean; no build directories found." -else - echo "Clean complete." -fi diff --git a/doc.sh b/doc.sh index d265a64a..8f546268 100755 --- a/doc.sh +++ b/doc.sh @@ -1,11 +1,10 @@ #!/bin/bash -# Build and automatically open the Zephyr Rust documentation for every configured -# build directory found in the workspace (build, build-native, build-qemu, ...). -# Empty or not-yet-configured build directories are skipped. -# -# On WSL it opens the docs in the Windows default browser, on native Linux +# Build and automatically open the Zephyr Rust documentation. +# On WSL it opens the docs in the Windows default browser; on native Linux # it opens them with xdg-open. # +# Run from anywhere inside the west workspace; the build dir is resolved +# relative to the workspace top level (override with the BUILD_DIR env var). set -e @@ -18,62 +17,25 @@ else echo "Detected Native Linux." fi -# Open a generated doc file with the platform's default browser handler. -open_doc() { - local path="$1" - if [ "$IS_WSL" -eq 1 ]; then - # Convert the Linux path to a Windows path and launch the default browser. - cmd.exe /c start "" "$(wslpath -w "$path")" - else - # Open with the default Linux browser handler. - xdg-open "$path" - fi -} - WS="$(west topdir)" +BUILD_DIR="${BUILD_DIR:-$WS/build}" -# Collect the build directories to document. If BUILD_DIR is set, only use that one. -# Otherwise, document $WS/build plus any $WS/build-* variant (build-native, build-qemu, ...). -BUILD_DIRS=() -if [ -n "$BUILD_DIR" ]; then - BUILD_DIRS+=("$BUILD_DIR") -else - for d in "$WS"/build "$WS"/build-*; do - [ -d "$d" ] && BUILD_DIRS+=("$d") - done -fi +# 1. Run the west build target +west build -d "$BUILD_DIR" -t rustdoc -if [ "${#BUILD_DIRS[@]}" -eq 0 ]; then - echo "No build directories found under $WS." - exit 0 -fi +# 2. Find the generated index.html file dynamically +LINUX_PATH="$(find "$BUILD_DIR" -name "index.html" | grep "doc/rustapp/index.html" | head -n 1)" -generated_any=0 -for dir in "${BUILD_DIRS[@]}"; do - # Skip anything that isn't a configured Zephyr build (empty / not yet built). - if [ ! -f "$dir/build.ninja" ] && [ ! -f "$dir/CMakeCache.txt" ]; then - echo "Skipping $dir (empty or not a configured build directory)." - continue - fi - - echo "Generating Rust docs for $dir ..." - if ! west build -d "$dir" -t rustdoc; then - echo "Warning: rustdoc build failed for $dir; skipping." - continue - fi - - # Find the generated index.html for the rustapp crate in this build dir. - DOC_PATH="$(find "$dir" -path "*doc/rustapp/index.html" | head -n 1)" - if [ -n "$DOC_PATH" ]; then - echo "Opening $DOC_PATH" - open_doc "$DOC_PATH" - generated_any=1 +if [ -n "$LINUX_PATH" ]; then + if [ "$IS_WSL" -eq 1 ]; then + # Convert the Linux path to a Windows path and launch the default browser + WIN_PATH="$(wslpath -w "$LINUX_PATH")" + cmd.exe /c start "" "$WIN_PATH" else - echo "Warning: could not locate the generated index.html in $dir." + # Open with the default Linux browser handler + xdg-open "$LINUX_PATH" fi -done - -if [ "$generated_any" -eq 0 ]; then - echo "Error: no documentation was generated." +else + echo "Error: Could not locate the generated index.html file." exit 1 fi diff --git a/dt-rust.yaml b/dt-rust.yaml index c1dad4a3..a0e727a1 100644 --- a/dt-rust.yaml +++ b/dt-rust.yaml @@ -164,7 +164,6 @@ names: - "nxp,lpc-usart" # u_Note: Should probably pull in more eventually but this is what i have - "zephyr,uart-emul" - - "zephyr,native-pty-uart" level: 0 actions: - !Instance diff --git a/native-sim.sh b/native-sim.sh deleted file mode 100755 index 4a543e39..00000000 --- a/native-sim.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -# Build + run the zephyr-odp sample on native_sim (64-bit). - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -BUILD_DIR="${BUILD_DIR:-$(west topdir)/build-native}" - -# Kill any native_sim instances left running from previous runs. -pkill -f "$BUILD_DIR/zephyr/zephyr.exe" || true - -# native_sim/native/64 on a 64-bit host maps to the aarch64/x86_64 bare-metal -# Rust target (see CMakeLists.txt). Pick the one matching the host CPU. -case "$(uname -m)" in - aarch64|arm64) RUST_TARGET="aarch64-unknown-none" ;; - x86_64|amd64) RUST_TARGET="x86_64-unknown-none" ;; - *) echo "Unsupported host architecture: $(uname -m)" >&2; exit 1 ;; -esac -if command -v rustup >/dev/null 2>&1; then - rustup target add "$RUST_TARGET" -fi - -# Build for native_sim (pass -p always via WEST_BUILD_ARGS for a pristine build). -west build -b native_sim/native/64 "$SCRIPT_DIR/samples/zephyr-odp" -d "$BUILD_DIR" ${WEST_BUILD_ARGS} - -# Launch the native binary. -west build -t run -d "$BUILD_DIR" diff --git a/samples/zephyr-odp/Cargo.toml b/samples/zephyr-odp/Cargo.toml index fde817a7..b5e9421b 100644 --- a/samples/zephyr-odp/Cargo.toml +++ b/samples/zephyr-odp/Cargo.toml @@ -20,25 +20,25 @@ embassy-futures = "0.1.1" embassy-sync = "0.8" embassy-time = { version = "0.5", features = ["tick-hz-10_000"] } # u_Note: tick-hz-10_000 is needed to match with zephyr. If this isn't here, something like Timer::after_secs(1) would actually wait for 100 seconds instead of 1 seconds. There should probably be some kind of way to do this manually so the embassy APIs work no matter what the Zephyr tick rate is. embedded-sensors-hal-async = "0.3.0" -embedded-services = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } -odp-service-common = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } +embedded-services = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } +odp-service-common = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } static_cell = "2.1" -thermal-service = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } -thermal-service-interface = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } -thermal-service-relay = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } -uart-service = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } +thermal-service = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } +thermal-service-interface = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } +thermal-service-relay = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } +uart-service = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } zephyr = { version = "0.1.0", features = ["time-driver", "executor-zephyr"] } log = { version = "0.4.22" } # features = ["max_level_error", "release_max_level_error"] embedded-io-async = {version = "0.7.0"} embedded-batteries-async = {version = "0.3.4"} -battery-service = { package = "battery-service", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d", features = ["mock"] } -battery-service-interface = { package = "battery-service-interface", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } -battery-service-relay = { package = "battery-service-relay", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } +battery-service = { package = "battery-service", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0", features = ["mock"] } +battery-service-interface = { package = "battery-service-interface", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } +battery-service-relay = { package = "battery-service-relay", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } -time-alarm-service = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d", features = ["mock"]} -time-alarm-service-interface = { package = "time-alarm-service-interface", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } -time-alarm-service-relay = { package = "time-alarm-service-relay", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } +time-alarm-service = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0", features = ["mock"]} +time-alarm-service-interface = { package = "time-alarm-service-interface", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } +time-alarm-service-relay = { package = "time-alarm-service-relay", git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } [lints.clippy] perf = { level = "forbid" } diff --git a/samples/zephyr-odp/boards/mimxrt685_evk_mimxrt685s_cm33.overlay b/samples/zephyr-odp/boards/mimxrt685_evk_mimxrt685s_cm33.overlay index d9025774..2e8dd1ca 100644 --- a/samples/zephyr-odp/boards/mimxrt685_evk_mimxrt685s_cm33.overlay +++ b/samples/zephyr-odp/boards/mimxrt685_evk_mimxrt685s_cm33.overlay @@ -59,8 +59,4 @@ rtc = <&rtc_emul>; /* phandle to the real RTC */ resolution-hz = <1>; /* 1 Hz resolution */ }; -}; - -cool_uart: &flexcomm0 { - status = "okay"; }; \ No newline at end of file diff --git a/samples/zephyr-odp/boards/native_sim_native_64.conf b/samples/zephyr-odp/boards/native_sim_native_64.conf deleted file mode 100644 index 8fd82cd5..00000000 --- a/samples/zephyr-odp/boards/native_sim_native_64.conf +++ /dev/null @@ -1,18 +0,0 @@ -# native_sim/native/64 configuration for the zephyr-odp sample. - -CONFIG_LOG_BACKEND_RTT=n -CONFIG_USE_SEGGER_RTT=n -CONFIG_RTT_CONSOLE=n -CONFIG_LOG_BACKEND_UART=y -CONFIG_UART_CONSOLE=y -CONFIG_STDOUT_CONSOLE=y - -CONFIG_DMA=n -CONFIG_DMA_MCUX_LPC=n - -CONFIG_EMUL=y -CONFIG_I2C_EMUL=y -CONFIG_GPIO_EMUL=y -CONFIG_UART_EMUL=y -CONFIG_F75303=y -CONFIG_EMUL_F75303=y diff --git a/samples/zephyr-odp/boards/native_sim_native_64.overlay b/samples/zephyr-odp/boards/native_sim_native_64.overlay deleted file mode 100644 index 1e672c58..00000000 --- a/samples/zephyr-odp/boards/native_sim_native_64.overlay +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2026 - * SPDX-License-Identifier: Apache-2.0 - * - * Devicetree overlay for the zephyr-odp sample on native_sim/native/64. - * - */ - -#include - -&i2c0 { - temperature_sensor_0: f75303@4c { - compatible = "fintek,f75303"; - reg = <0x4c>; - }; - - fuel_gauge: sbs-gauge@b { - compatible = "sbs,sbs-gauge-new-api"; - reg = <0x0b>; - status = "okay"; - }; -}; - -/ { - tachometer: tachometer { - compatible = "zephyr,tach-gpio"; - gpios = <&gpio0 15 (GPIO_ACTIVE_HIGH | GPIO_PULL_UP)>; - pulses-per-round = <2>; - }; - - pwm_emul0: pwm-emul { - status = "okay"; - compatible = "zephyr,pwm-emul"; - #pwm-cells = <3>; - frequency = <1000000>; - }; - fan0: my-fan { - compatible = "pwm-fan"; - pwms = <&pwm_emul0 7 40000 0>; - max-rpm = <5000>; - min-rpm = <0>; - min-start-rpm = <3000>; - }; - - rs_rtc0: rs-rtc { - compatible = "rs,rtc"; - rtc = <&rtc>; - resolution-hz = <1>; - }; -}; - -cool_uart: &uart1 { - status = "okay"; -}; diff --git a/samples/zephyr-odp/boards/qemu_cortex_m3.overlay b/samples/zephyr-odp/boards/qemu_cortex_m3.overlay index dd966e60..4ab9d916 100644 --- a/samples/zephyr-odp/boards/qemu_cortex_m3.overlay +++ b/samples/zephyr-odp/boards/qemu_cortex_m3.overlay @@ -68,7 +68,7 @@ }; /* Emulated UART */ - cool_uart: uart-emul0 { + flexcomm0: uart-emul0 { status = "okay"; compatible = "zephyr,uart-emul"; current-speed = <115200>; diff --git a/samples/zephyr-odp/prj.conf b/samples/zephyr-odp/prj.conf index 7b550d1a..86f16cc0 100644 --- a/samples/zephyr-odp/prj.conf +++ b/samples/zephyr-odp/prj.conf @@ -9,12 +9,6 @@ CONFIG_RUST=y CONFIG_RUST_ALLOC=y CONFIG_LOG=y -# The embassy-time driver is told our tick rate statically via the `tick-hz-10_000` -# Cargo feature (see Cargo.toml). But, this doesn't always match with Zephyr's tick -# rate (i'm pretty sure it can differ from board to board). So, I'm just going to set the -# Zephyr tick rate to 10,000 here across the board -CONFIG_SYS_CLOCK_TICKS_PER_SEC=10000 - # RTT logging CONFIG_LOG_BACKEND_RTT=y CONFIG_USE_SEGGER_RTT=y diff --git a/samples/zephyr-odp/sample.yaml b/samples/zephyr-odp/sample.yaml index 794f3d3e..d8e517c6 100644 --- a/samples/zephyr-odp/sample.yaml +++ b/samples/zephyr-odp/sample.yaml @@ -9,7 +9,6 @@ common: - nrf52840dk/nrf52840' - 'mimxrt685_evk/mimxrt685s/cm33' - 'qemu_cortex_m3' - - 'native_sim/native/64' tests: sample.rust.helloworld: tags: ODP diff --git a/samples/zephyr-odp/src/battery.rs b/samples/zephyr-odp/src/battery.rs index 0549f981..b2b5eab6 100644 --- a/samples/zephyr-odp/src/battery.rs +++ b/samples/zephyr-odp/src/battery.rs @@ -1,40 +1,43 @@ use static_cell::StaticCell; -use embassy_sync::mutex::Mutex; -use embedded_services::GlobalRawMutex; -use battery_service::FuelGauge as _; -/// Our `BatteryServiceWrapper` driver but wrapped in a mutex so we can provide it to battery-service -type WrappedFuelGauge = Mutex; +pub type BatteryService = battery_service::Service<'static, 1>; -/// Registration for our fuel gauge -type Reg = battery_service::ArrayRegistration<'static, WrappedFuelGauge, 1>; - -pub type BatteryService = battery_service::Service<'static, Reg>; +const BAT_ID: battery_service::device::DeviceId = battery_service::device::DeviceId(0); pub async fn init(spawner: embassy_executor::Spawner) -> BatteryService { log::info!("Initializing battery service..."); + static BATTERY_DEVICE: StaticCell = StaticCell::new(); + let device = BATTERY_DEVICE.init(battery_service::device::Device::new(BAT_ID)); let driver = zephyr::devicetree::labels::fuel_gauge::get_instance().expect("Failed to call get_instance() for fuel_gauge."); + let battery = battery_service::wrapper::Wrapper::new(device, driver); - static FUEL_GAUGE: StaticCell = StaticCell::new(); - let fuel_gauge: &'static WrappedFuelGauge = - FUEL_GAUGE.init(Mutex::new(BatteryServiceWrapper::from(driver))); - - let service = battery_service::Service::new(battery_service::ArrayRegistration { - fuel_gauges: [fuel_gauge], - }); + let service = crate::utils::spawn_service!(spawner, BatteryService, |resources| battery_service::Service::new( + resources, + battery_service::InitParams { + devices: [device], + config: battery_service::context::Config::default(), + } + )) + .expect("Failed to initialize battery service"); - spawner - .spawn(update_data_task(fuel_gauge)) - .expect("Failed to spawn battery update data task"); + spawner.spawn(battery_device_controller_task(battery)).expect("Failed to spawn battery device controller task"); + spawner.spawn(update_data_task(service)).expect("Failed to spawn battery update data task"); log::info!("Initialized battery service!"); service } #[embassy_executor::task] -pub async fn update_data_task(fuel_gauge: &'static WrappedFuelGauge) { - if let Err(e) = battery_service::mock::init_state_machine(fuel_gauge).await { +async fn battery_device_controller_task(battery: battery_service::wrapper::Wrapper<'static, zephyr::device::fuel_gauge::FuelGauge>) { + battery.process().await; +} + +#[embassy_executor::task] +pub async fn update_data_task(service: BatteryService) { + + // Initialize the state machine + if let Err(e) = battery_service::mock::init_state_machine(&service).await { log::error!("FG: Failed to init state machine: {:?}. Terminating this task...", e); return; } @@ -43,178 +46,37 @@ pub async fn update_data_task(fuel_gauge: &'static WrappedFuelGauge) { let mut count: usize = 0; loop { embassy_time::Timer::after_secs(1).await; - if count.is_multiple_of(const { 60 * 60 }) - && let Err(e) = fuel_gauge.lock().await.update_static_data().await + && let Err(e) = service + .execute_event(battery_service::context::BatteryEvent { + event: battery_service::context::BatteryEventInner::PollStaticData, + device_id: BAT_ID, + }) + .await { failures += 1; - log::error!("FG: Static data error: {:?}", e); + log::error!("FG: Static data error: {:#?}", e); } - - if let Err(e) = fuel_gauge.lock().await.update_dynamic_data().await { + if let Err(e) = service + .execute_event(battery_service::context::BatteryEvent { + event: battery_service::context::BatteryEventInner::PollDynamicData, + device_id: BAT_ID, + }) + .await + { failures += 1; - log::error!("FG: Dynamic data error: {:?}", e); + log::error!("FG: Dynamic data error: {:#?}", e); } if failures > 10 { failures = 0; count = 0; log::error!("FG: Too many errors, timing out and starting recovery..."); - if battery_service::mock::recover_state_machine(fuel_gauge).await.is_err() { + if battery_service::mock::recover_state_machine(&service).await.is_err() { log::error!("FG: Failed to recover state machine!"); } } count = count.wrapping_add(1); } -} - -/// Wrapper around the Zephyr driver. -pub struct BatteryServiceWrapper { - pub driver: zephyr::device::fuel_gauge::FuelGauge, - pub state: battery_service::State, -} - -/// Map Zephyr error to wrapper error. -#[derive(Debug)] -pub enum BatteryServiceWrapperError { ZephyrError(zephyr::error::Error) } -impl From for BatteryServiceWrapperError { - fn from(err: zephyr::error::Error) -> Self { - BatteryServiceWrapperError::ZephyrError(err) - } -} - -/// Map a BatteryServiceWrapperError to a smart_battery::ErrorKind -impl embedded_batteries_async::smart_battery::Error for BatteryServiceWrapperError { - fn kind(&self) -> embedded_batteries_async::smart_battery::ErrorKind { - match self { - BatteryServiceWrapperError::ZephyrError(e) => { - embedded_batteries_async::smart_battery::Error::kind(e) - } - } - } -} - -/// Allow you to convert a normal `FuelGauge` into a `BatteryServiceWrapper` to be used with the battery service. -impl From for BatteryServiceWrapper { - fn from(value: zephyr::device::fuel_gauge::FuelGauge) -> Self { - Self { - driver: value, - state: battery_service::State::default(), - } - } -} - -/// Convert local errors into battery-service errors. -impl From for battery_service::FuelGaugeError { - fn from(_error: BatteryServiceWrapperError) -> Self { - battery_service::FuelGaugeError::BusError - } -} - -// Implement SmartBattery for Battery Service. -embedded_batteries_async::impl_smart_battery_for_wrapper_type!(BatteryServiceWrapper, driver, BatteryServiceWrapperError); - -impl battery_service::FuelGauge for BatteryServiceWrapper { - type FuelGaugeError = BatteryServiceWrapperError; - type StaticData = battery_service::StaticBatteryMsgs; - type DynamicData = battery_service::DynamicBatteryMsgs; - - async fn initialize(&mut self) -> Result<(), Self::FuelGaugeError> { - self.driver.set_capacity_mode(zephyr::device::fuel_gauge::CapacityMode::MilliAmp).await?; - self.state.on_initialized(); - Ok(()) - } - - async fn ping(&mut self) -> Result<(), Self::FuelGaugeError> { - use embedded_batteries_async::smart_battery::SmartBattery; - SmartBattery::charging_voltage(self).await.inspect_err(|e| log::error!("Failed to ping fuel gauge: Call to self.charging_voltage() failed with e: {:?}", e))?; - log::info!("Successfully pinged fuel gauge."); - self.state.on_recovered(); - Ok(()) - } - - async fn update_dynamic_data(&mut self) -> Result<(), Self::FuelGaugeError> { - use embedded_batteries_async::smart_battery::SmartBattery; - - let average_current = SmartBattery::average_current(self).await?; - let battery_status = SmartBattery::battery_status(self).await?.into_bits(); - let battery_temp = SmartBattery::temperature(self).await?; - let charging_current = SmartBattery::charging_current(self).await?; - let charging_voltage = SmartBattery::charging_voltage(self).await?; - let voltage = SmartBattery::voltage(self).await?; - let current = SmartBattery::current(self).await?; - let full_charge_capacity = SmartBattery::full_charge_capacity(self).await?; - let remaining_capacity = SmartBattery::remaining_capacity(self).await?; - let relative_soc = SmartBattery::relative_state_of_charge(self).await?; - let cycle_count = SmartBattery::cycle_count(self).await?; - let max_error = SmartBattery::max_error(self).await.unwrap_or(0); // u_TODO: Hardcoding to zero since the Zephyr API doesn't expose max error yet - - self.state.on_dynamic_data(|d| { - d.average_current = average_current; - d.battery_status = battery_status; - d.max_power_mw = 0; - d.battery_temp = battery_temp; - d.sus_power_mw = 0; - d.charging_current = charging_current; - d.charging_voltage = charging_voltage; - d.voltage = voltage; - d.current = current; - d.full_charge_capacity = full_charge_capacity; - d.remaining_capacity = remaining_capacity; - d.relative_soc = relative_soc; - d.cycle_count = cycle_count; - d.max_error = max_error; - d.bmd_status = battery_service_interface::BmdStatusFlags::default(); - d.turbo_vload = 0; - d.turbo_rhf_effective_mohm = 0; - }); - Ok(()) - } - - async fn update_static_data(&mut self) -> Result<(), Self::FuelGaugeError> { - use embedded_batteries_async::smart_battery::SmartBattery; - use embedded_batteries_async::smart_battery::CapacityModeValue; - use battery_service_interface::fuel_gauge::{DEVICE_CHEMISTRY_SIZE, DEVICE_NAME_SIZE, MANUFACTURER_NAME_SIZE}; - - let design_voltage = SmartBattery::design_voltage(self).await?; - let design_capacity = SmartBattery::design_capacity(self).await?; - let design_capacity_value: u16 = match design_capacity { - CapacityModeValue::CentiWattUnsigned(v) => v, - CapacityModeValue::MilliAmpUnsigned(v) => v, - }; - let battery_mode = SmartBattery::battery_mode(self).await?; - let measurement_accuracy: u32 = SmartBattery::max_error(self).await.unwrap_or(0).into(); // u_TODO: Hardcoding to zero since the Zephyr API doesn't expose max error yet - - let mut manufacturer_name = [0u8; MANUFACTURER_NAME_SIZE]; - SmartBattery::manufacturer_name(self, &mut manufacturer_name).await?; - let mut device_name = [0u8; DEVICE_NAME_SIZE]; - SmartBattery::device_name(self, &mut device_name).await?; - let mut device_chemistry = [0u8; DEVICE_CHEMISTRY_SIZE]; - SmartBattery::device_chemistry(self, &mut device_chemistry).await?; - - self.state.on_static_data(|s| { - s.manufacturer_name = manufacturer_name; - s.device_name = device_name; - s.device_chemistry = device_chemistry; - s.design_voltage = design_voltage; - s.design_capacity = design_capacity; - s.battery_mode = battery_mode; - s.design_cap_warning = CapacityModeValue::MilliAmpUnsigned(design_capacity_value / 4); - s.design_cap_low = CapacityModeValue::MilliAmpUnsigned(design_capacity_value / 10); - s.measurement_accuracy = measurement_accuracy; - s.power_threshold_support = battery_service_interface::PowerThresholdSupport::empty(); - s.bmc_flags = battery_service_interface::BmcControlFlags::empty(); - s.bmd_capability = battery_service_interface::BmdCapabilityFlags::empty(); - }); - Ok(()) - } - - fn state(&self) -> &battery_service::State { - &self.state - } - - fn state_mut(&mut self) -> &mut battery_service::State { - &mut self.state - } } \ No newline at end of file diff --git a/samples/zephyr-odp/src/lib.rs b/samples/zephyr-odp/src/lib.rs index 69cbc438..17df3134 100644 --- a/samples/zephyr-odp/src/lib.rs +++ b/samples/zephyr-odp/src/lib.rs @@ -3,9 +3,6 @@ #![no_std] -#![allow(unused_variables)] -#![allow(dead_code)] - use core::ffi::c_int; use embassy_executor::Spawner; use log::info; @@ -75,9 +72,9 @@ async fn init(spawner: Spawner) { // UART service. Spawns out the thermal, battery, and timer services. #[embassy_executor::task] async fn uart_service(relay: RelayHandler) { - static UART_SERVICE: StaticCell> = StaticCell::new(); - let uart_service = UART_SERVICE.init(uart_service::MctpSerialService::default_mctp_serial(relay).unwrap()); - let uart_driver: zephyr::device::uart::Uart = zephyr::devicetree::labels::cool_uart::get_instance().unwrap(); + static UART_SERVICE: StaticCell> = StaticCell::new(); + let uart_service = UART_SERVICE.init(uart_service::DefaultService::default_smbusespi(relay).unwrap()); + let uart_driver: zephyr::device::uart::Uart = zephyr::devicetree::labels::flexcomm0::get_instance().unwrap(); info!("Starting uart_service::task::uart_service()..."); let Err(e) = uart_service::task::uart_service(uart_service, uart_driver).await; info!("After uart_service::task::uart_service()"); diff --git a/samples/zephyr-odp/src/time_alarm.rs b/samples/zephyr-odp/src/time_alarm.rs index 61ad9104..68c01dd5 100644 --- a/samples/zephyr-odp/src/time_alarm.rs +++ b/samples/zephyr-odp/src/time_alarm.rs @@ -5,22 +5,20 @@ pub type TimeAlarmService = time_alarm_service::Service<'static>; pub async fn init(spawner: embassy_executor::Spawner) -> TimeAlarmService { use static_cell::StaticCell; - const ERASED_NVRAM: u32 = u32::MAX; - static TZ_STORAGE: StaticCell> = StaticCell::new(); - let tz_storage = TZ_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(ERASED_NVRAM)); + let tz_storage = TZ_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(0)); static AC_EXP_STORAGE: StaticCell> = StaticCell::new(); - let ac_exp_storage = AC_EXP_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(ERASED_NVRAM)); + let ac_exp_storage = AC_EXP_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(0)); static AC_POL_STORAGE: StaticCell> = StaticCell::new(); - let ac_pol_storage = AC_POL_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(ERASED_NVRAM)); + let ac_pol_storage = AC_POL_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(0)); static DC_EXP_STORAGE: StaticCell> = StaticCell::new(); - let dc_exp_storage = DC_EXP_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(ERASED_NVRAM)); + let dc_exp_storage = DC_EXP_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(0)); static DC_POL_STORAGE: StaticCell> = StaticCell::new(); - let dc_pol_storage = DC_POL_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(ERASED_NVRAM)); + let dc_pol_storage = DC_POL_STORAGE.init(time_alarm_service::mock::MockNvramStorage::new(0)); static RTC_DRIVER: StaticCell = StaticCell::new(); let rtc_driver = RTC_DRIVER.init(zephyr::devicetree::rs_rtc::get_instance().expect("Failed to call zephyr::devicetree::rs_rtc::get_instance()")); diff --git a/zephyr/Cargo.toml b/zephyr/Cargo.toml index f7b86dbf..0d6bdf68 100644 --- a/zephyr/Cargo.toml +++ b/zephyr/Cargo.toml @@ -28,10 +28,12 @@ arrayvec = { version = "0.7.6", default-features = false } # Imports for pwm_fan.rs: embedded-fans-async = {version = "0.2.0"} -thermal-service-interface = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "918fdad60a25357d9ab8e693916e40bef31d486d" } +thermal-service-interface = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } # Imports for fuel_gauge.rs: embedded-batteries-async = {version = "0.3.4"} +battery-service = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } +battery-service-interface = { git = "https://github.com/OpenDevicePartnership/embedded-services", rev = "086c9efd7c58f06b6a6bc3f894832bbd2f5c8ec0" } embassy-time = "0.5" # u_Note: Must match battery-service's version since Duration is in their public API # Imports for uart.rs: diff --git a/zephyr/src/device/fuel_gauge.rs b/zephyr/src/device/fuel_gauge.rs index 04ff2f6f..e6a0699e 100644 --- a/zephyr/src/device/fuel_gauge.rs +++ b/zephyr/src/device/fuel_gauge.rs @@ -131,26 +131,26 @@ impl FuelGauge { /// Reads the gauge's `manufacturer_name` into the provided buffer. /// According to Zephyr, manufacturer name is 1 byte of string length + 20 bytes of data (21 bytes total). - pub(crate) fn manufacturer_name(&self, buffer: &mut [u8]) -> crate::error::Result<()> { + pub fn manufacturer_name(&self, buffer: &mut [u8]) -> crate::error::Result<()> { self.get_buffer_prop(FuelGaugeBufferProp::ManufacturerName, buffer) } /// Reads the gauge's `device_name` into the provided buffer. /// According to Zephyr, device name is 1 byte of string length + 20 bytes of data (21 bytes total). - pub(crate) fn device_name(&self, buffer: &mut [u8]) -> crate::error::Result<()> { + pub fn device_name(&self, buffer: &mut [u8]) -> crate::error::Result<()> { self.get_buffer_prop(FuelGaugeBufferProp::DeviceName, buffer) } /// Reads the gauge's `device_chemistry` into the provided buffer. /// According to Zephyr, device chemistry is 1 byte of string length + 4 bytes of data (5 bytes total). - pub(crate) fn device_chemistry(&self, buffer: &mut [u8]) -> crate::error::Result<()> { + pub fn device_chemistry(&self, buffer: &mut [u8]) -> crate::error::Result<()> { self.get_buffer_prop(FuelGaugeBufferProp::DeviceChemistry, buffer) } /// Returns the gauge's `avg_current` reading. /// /// Zephyr notes: Provide a 1 minute average of the current on the battery. Does not check for flags or whether those values are bad readings. See driver instance header for details on implementation and how the average is calculated. Units in uA negative=discharging - pub(crate) fn avg_current(&self) -> crate::error::Result { + pub fn avg_current(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::AvgCurrent).map(|val| unsafe { *val.avg_current.as_ref() }) } @@ -166,7 +166,7 @@ impl FuelGauge { /// Returns the gauge's `current` reading. /// /// Zephyr notes: Battery current (uA); negative=discharging. - pub(crate) fn current(&self) -> crate::error::Result { + pub fn current(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::Current).map(|val| unsafe { *val.current.as_ref() }) } @@ -174,7 +174,7 @@ impl FuelGauge { /// Returns the gauge's `cycle_count` reading. /// /// Zephyr notes: Cycle count in 1/100ths (number of charge/discharge cycles). - pub(crate) fn cycle_count(&self) -> crate::error::Result { + pub fn cycle_count(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::CycleCount).map(|val| unsafe { *val.cycle_count.as_ref() }) } @@ -198,7 +198,7 @@ impl FuelGauge { /// Returns the gauge's `full_charge_capacity` reading. /// /// Zephyr notes: Full Charge Capacity in uAh (might change in some implementations to determine wear). - pub(crate) fn full_charge_capacity(&self) -> crate::error::Result { + pub fn full_charge_capacity(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::FullChargeCapacity).map(|val| unsafe { *val.full_charge_capacity.as_ref() }) } @@ -214,7 +214,7 @@ impl FuelGauge { /// Returns the gauge's `remaining_capacity` reading. /// /// Zephyr notes: Remaining capacity in uAh. - pub(crate) fn remaining_capacity(&self) -> crate::error::Result { + pub fn remaining_capacity(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::RemainingCapacity).map(|val| unsafe { *val.remaining_capacity.as_ref() }) } @@ -222,7 +222,7 @@ impl FuelGauge { /// Returns the gauge's `runtime_to_empty` reading. /// /// Zephyr notes: Remaining battery life time in minutes. - pub(crate) fn runtime_to_empty(&self) -> crate::error::Result { + pub fn runtime_to_empty(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::RuntimeToEmpty).map(|val| unsafe { *val.runtime_to_empty.as_ref() }) } @@ -246,7 +246,7 @@ impl FuelGauge { /// Returns the gauge's `absolute_state_of_charge` reading. /// /// Zephyr notes: Absolute state of charge (percent, 0-100) - expressed as % of design capacity. - pub(crate) fn absolute_state_of_charge(&self) -> crate::error::Result { + pub fn absolute_state_of_charge(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::AbsoluteStateOfCharge).map(|val| unsafe { *val.absolute_state_of_charge.as_ref() }) } @@ -254,7 +254,7 @@ impl FuelGauge { /// Returns the gauge's `relative_state_of_charge` reading. /// /// Zephyr notes: Relative state of charge (percent, 0-100) - expressed as % of full charge capacity. - pub(crate) fn relative_state_of_charge(&self) -> crate::error::Result { + pub fn relative_state_of_charge(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::RelativeStateOfCharge).map(|val| unsafe { *val.relative_state_of_charge.as_ref() }) } @@ -262,7 +262,7 @@ impl FuelGauge { /// Returns the gauge's `temperature` reading. /// /// Zephyr notes: Temperature in 0.1 K. - pub(crate) fn temperature(&self) -> crate::error::Result { + pub fn temperature(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::Temperature).map(|val| unsafe { *val.temperature.as_ref() }) } @@ -270,7 +270,7 @@ impl FuelGauge { /// Returns the gauge's `voltage` reading. /// /// Zephyr notes: Battery voltage (uV). - pub(crate) fn voltage(&self) -> crate::error::Result { + pub fn voltage(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::Voltage).map(|val| unsafe { *val.voltage.as_ref() }) } @@ -278,7 +278,7 @@ impl FuelGauge { /// Returns the gauge's `sbs_mode` reading. /// /// Zephyr notes: Battery Mode (flags). - pub(crate) fn sbs_mode(&self) -> crate::error::Result { + pub fn sbs_mode(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::SbsMode).map(|val| unsafe { *val.sbs_mode.as_ref() }) } @@ -286,7 +286,7 @@ impl FuelGauge { /// Sets the gauge's `sbs_mode` setting. /// /// Zephyr notes: Battery Mode (flags). - pub(crate) fn set_sbs_mode(&self, value: u16) -> crate::error::Result<()> { + pub fn set_sbs_mode(&self, value: u16) -> crate::error::Result<()> { // SAFETY: All union fields are primitive types, so zeroed memory is valid. let mut val: crate::raw::fuel_gauge_prop_val = unsafe { core::mem::zeroed() }; @@ -299,7 +299,7 @@ impl FuelGauge { /// Returns the gauge's `chg_current` (charge current) reading. /// /// Zephyr notes: Battery desired Max Charging Current (uA). - pub(crate) fn chg_current(&self) -> crate::error::Result { + pub fn chg_current(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::ChgCurrent).map(|val| unsafe { *val.chg_current.as_ref() }) } @@ -307,7 +307,7 @@ impl FuelGauge { /// Returns the gauge's `chg_voltage` (charge voltage) reading. /// /// Zephyr notes: Battery desired Max Charging Voltage (uV). - pub(crate) fn chg_voltage(&self) -> crate::error::Result { + pub fn chg_voltage(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::ChgVoltage).map(|val| unsafe { *val.chg_voltage.as_ref() }) } @@ -315,7 +315,7 @@ impl FuelGauge { /// Returns the gauge's `fg_status` reading. /// /// Zephyr notes: Alarm, Status and Error codes (flags). - pub(crate) fn fg_status(&self) -> crate::error::Result { + pub fn fg_status(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::FgStatus).map(|val| unsafe { *val.fg_status.as_ref() }) } @@ -323,7 +323,7 @@ impl FuelGauge { /// Returns the gauge's `design_cap` (design capacity) reading. /// /// Zephyr notes: Design Capacity (mAh or 10mWh). - pub(crate) fn design_cap(&self) -> crate::error::Result { + pub fn design_cap(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::DesignCap).map(|val| unsafe { *val.design_cap.as_ref() }) } @@ -331,7 +331,7 @@ impl FuelGauge { /// Returns the gauge's `design_volt` (design voltage) reading. /// /// Zephyr notes: Design Voltage (mV). - pub(crate) fn design_volt(&self) -> crate::error::Result { + pub fn design_volt(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::DesignVolt).map(|val| unsafe { *val.design_volt.as_ref() }) } @@ -339,7 +339,7 @@ impl FuelGauge { /// Returns the gauge's `sbs_at_rate` reading. /// /// Zephyr notes: AtRate (mA or 10 mW). - pub(crate) fn sbs_at_rate(&self) -> crate::error::Result { + pub fn sbs_at_rate(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::SbsAtRate).map(|val| unsafe { *val.sbs_at_rate.as_ref() }) } @@ -347,7 +347,7 @@ impl FuelGauge { /// Sets the gauge's `at_rate` setting. /// /// Zephyr notes: AtRate (mA or 10 mW). - pub(crate) fn set_sbs_at_rate(&self, value: i16) -> crate::error::Result<()> { + pub fn set_sbs_at_rate(&self, value: i16) -> crate::error::Result<()> { // SAFETY: All union fields are primitive types, so zeroed memory is valid. let mut val: crate::raw::fuel_gauge_prop_val = unsafe { core::mem::zeroed() }; @@ -360,7 +360,7 @@ impl FuelGauge { /// Returns the gauge's `sbs_at_rate_time_to_full` reading. /// /// Zephyr notes: AtRateTimeToFull (minutes). - pub(crate) fn sbs_at_rate_time_to_full(&self) -> crate::error::Result { + pub fn sbs_at_rate_time_to_full(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::SbsAtRateTimeToFull).map(|val| unsafe { *val.sbs_at_rate_time_to_full.as_ref() }) } @@ -368,7 +368,7 @@ impl FuelGauge { /// Returns the gauge's `sbs_at_rate_time_to_empty` reading. /// /// Zephyr notes: AtRateTimeToEmpty (minutes). - pub(crate) fn sbs_at_rate_time_to_empty(&self) -> crate::error::Result { + pub fn sbs_at_rate_time_to_empty(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::SbsAtRateTimeToEmpty).map(|val| unsafe { *val.sbs_at_rate_time_to_empty.as_ref() }) } @@ -376,7 +376,7 @@ impl FuelGauge { /// Returns the gauge's `sbs_at_rate_ok` reading. /// /// Zephyr notes: AtRateOK (boolean). - pub(crate) fn sbs_at_rate_ok(&self) -> crate::error::Result { + pub fn sbs_at_rate_ok(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::SbsAtRateOk).map(|val| unsafe { *val.sbs_at_rate_ok.as_ref() }) } @@ -384,7 +384,7 @@ impl FuelGauge { /// Returns the gauge's `sbs_remaining_capacity_alarm` reading. /// /// Zephyr notes: Remaining Capacity Alarm (mAh or 10mWh). - pub(crate) fn sbs_remaining_capacity_alarm(&self) -> crate::error::Result { + pub fn sbs_remaining_capacity_alarm(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::SbsRemainingCapacityAlarm).map(|val| unsafe { *val.sbs_remaining_capacity_alarm.as_ref() }) } @@ -392,7 +392,7 @@ impl FuelGauge { /// Sets the gauge's `sbs_remaining_capacity_alarm` setting. /// /// Zephyr notes: Remaining Capacity Alarm (mAh or 10mWh). - pub(crate) fn set_sbs_remaining_capacity_alarm(&self, value: u16) -> crate::error::Result<()> { + pub fn set_sbs_remaining_capacity_alarm(&self, value: u16) -> crate::error::Result<()> { // SAFETY: All union fields are primitive types, so zeroed memory is valid. let mut val: crate::raw::fuel_gauge_prop_val = unsafe { core::mem::zeroed() }; @@ -405,7 +405,7 @@ impl FuelGauge { /// Returns the gauge's `sbs_remaining_time_alarm` reading. /// /// Zephyr notes: Remaining Time Alarm (minutes). - pub(crate) fn sbs_remaining_time_alarm(&self) -> crate::error::Result { + pub fn sbs_remaining_time_alarm(&self) -> crate::error::Result { // SAFETY: Per the Zephyr API contract, the driver will have populated the correct field of the union. self.get_prop(FuelGaugeProp::SbsRemainingTimeAlarm).map(|val| unsafe { *val.sbs_remaining_time_alarm.as_ref() }) } @@ -413,7 +413,7 @@ impl FuelGauge { /// Sets the gauge's `sbs_remaining_time_alarm` setting. /// /// Zephyr notes: Remaining Time Alarm (minutes). - pub(crate) fn set_sbs_remaining_time_alarm(&self, value: u16) -> crate::error::Result<()> { + pub fn set_sbs_remaining_time_alarm(&self, value: u16) -> crate::error::Result<()> { // SAFETY: All union fields are primitive types, so zeroed memory is valid. let mut val: crate::raw::fuel_gauge_prop_val = unsafe { core::mem::zeroed() }; @@ -929,12 +929,119 @@ impl embedded_batteries_async::smart_battery::ErrorType for FuelGauge { impl embedded_batteries_async::smart_battery::Error for crate::error::Error { // u_Note: eventually add better mapping to this fn kind(&self) -> embedded_batteries_async::smart_battery::ErrorKind { - use embedded_batteries_async::smart_battery::ErrorKind; + embedded_batteries_async::smart_battery::ErrorKind::Other + } +} + +impl battery_service::controller::Controller for FuelGauge { + type ControllerError = crate::error::Error; + + async fn initialize(&mut self) -> Result<(), Self::ControllerError> { + self.set_capacity_mode(CapacityMode::MilliAmp).await + } + + async fn ping(&mut self) -> Result<(), Self::ControllerError> { + use embedded_batteries_async::smart_battery::SmartBattery; + SmartBattery::charging_voltage(self).await.inspect_err(|e| log::error!("Failed to ping fuel gauge: Call to self.charging_voltage() failed with e: {}", e))?; + log::info!("Successfully pinged fuel gauge."); + Ok(()) + } + + async fn get_dynamic_data(&mut self) -> Result { + use embedded_batteries_async::smart_battery::SmartBattery; + use embedded_batteries_async::smart_battery::CapacityModeValue; + + let voltage_mv: u32 = SmartBattery::voltage(self).await?.into(); + + let new_msgs = battery_service::device::DynamicBatteryMsgs { + average_current_ma: SmartBattery::average_current(self).await?, + battery_status: SmartBattery::battery_status(self).await?.into_bits(), + max_power_mw: 0, + battery_temp_dk: SmartBattery::temperature(self).await?, + sus_power_mw: 0, + charging_current_ma: SmartBattery::charging_current(self).await?, + charging_voltage_mv: SmartBattery::charging_voltage(self).await?, + voltage_mv: voltage_mv as u16, + current_ma: SmartBattery::current(self).await?, + full_charge_capacity_mwh: match SmartBattery::full_charge_capacity(self).await? { + CapacityModeValue::CentiWattUnsigned(cwh) => (cwh as u32) * 10, + CapacityModeValue::MilliAmpUnsigned(mah) => (mah as u32) * voltage_mv / 1000, + }, + remaining_capacity_mwh: match SmartBattery::remaining_capacity(self).await? { + CapacityModeValue::CentiWattUnsigned(cwh) => (cwh as u32) * 10, + CapacityModeValue::MilliAmpUnsigned(mah) => (mah as u32) * voltage_mv / 1000, + }, + relative_soc_pct: SmartBattery::relative_state_of_charge(self).await?.into(), + cycle_count: SmartBattery::cycle_count(self).await?, + max_error_pct: SmartBattery::max_error(self).await.unwrap_or(0).into(), // u_TODO: Hardcoding to zero since the Zephyr API doesn't expose max error yet + bmd_status: battery_service_interface::BmdStatusFlags::default(), + turbo_vload_mv: 0, + turbo_rhf_effective_mohm: 0, + }; + Ok(new_msgs) + } + + #[allow(clippy::indexing_slicing)] + async fn get_static_data(&mut self) -> Result { + use embedded_batteries_async::smart_battery::SmartBattery; + use embedded_batteries_async::smart_battery::CapacityModeValue; + + let design_voltage_mv: u32 = SmartBattery::design_voltage(self).await?.into(); + let design_capacity_mwh: u32 = match SmartBattery::design_capacity(self).await?.into() { + CapacityModeValue::CentiWattUnsigned(cwh) => (cwh as u32) * 10, + CapacityModeValue::MilliAmpUnsigned(mah) => (mah as u32) * design_voltage_mv / 1000, + }; + + let mut new_msgs = battery_service::device::StaticBatteryMsgs { + manufacturer_name: Default::default(), + device_name: Default::default(), + device_chemistry: Default::default(), + design_voltage_mv: design_voltage_mv as u16, + design_capacity_mwh: design_capacity_mwh, + device_chemistry_id: Default::default(), + serial_num: Default::default(), + battery_mode: SmartBattery::battery_mode(self).await?, + design_cap_warning: design_capacity_mwh / 4, + design_cap_low: design_capacity_mwh / 10, + measurement_accuracy: SmartBattery::max_error(self).await.unwrap_or(0).into(), // u_TODO: Hardcoding to zero since the Zephyr API doesn't expose max error yet + max_sample_time: Default::default(), + min_sample_time: Default::default(), + max_averaging_interval: Default::default(), + min_averaging_interval: Default::default(), + cap_granularity_1: Default::default(), + cap_granularity_2: Default::default(), + power_threshold_support: battery_service_interface::PowerThresholdSupport::empty(), + max_instant_pwr_threshold: Default::default(), + max_sus_pwr_threshold: Default::default(), + bmc_flags: battery_service_interface::BmcControlFlags::empty(), + bmd_capability: battery_service_interface::BmdCapabilityFlags::empty(), + bmd_recalibrate_count: Default::default(), + bmd_quick_recalibrate_time: Default::default(), + bmd_slow_recalibrate_time: Default::default(), + }; + let mut buf = [0u8; 21]; - let code: u32 = self.0; - match code { - crate::raw::EIO | crate::raw::ETIMEDOUT | crate::raw::ENXIO => ErrorKind::CommError, - _ => ErrorKind::Other, + let buf_len = new_msgs.manufacturer_name.len(); + SmartBattery::manufacturer_name(self, &mut buf[..buf_len]).await?; + new_msgs.manufacturer_name.copy_from_slice(&buf[..buf_len]); + + let buf_len = new_msgs.device_name.len(); + SmartBattery::device_name(self, &mut buf[..buf_len]).await?; + new_msgs.device_name.copy_from_slice(&buf[..buf_len]); + + let buf_len = new_msgs.device_chemistry.len(); + SmartBattery::device_chemistry(self, &mut buf[..buf_len]).await?; + new_msgs.device_chemistry.copy_from_slice(&buf[..buf_len]); + + Ok(new_msgs) + } + + async fn get_device_event(&mut self) -> battery_service::controller::ControllerEvent { + // TODO: Loop forever till we figure out what we want to do here + loop { + embassy_time::Timer::after_secs(1000000).await; } } + + fn set_timeout(&mut self, _duration: embassy_time::Duration) {} } \ No newline at end of file