diff --git a/README.md b/README.md index 231af50..ce28232 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Firmware for Pinetime based on [Embassy](https://embassy.dev). The goal is to pr * Automatically synchronizes time with using BLE standard Current Time Service. * Rollback to previous firmware if reset or crashing before new firmware is validated in watch UI. * Compatible with existing InfiniTime bootloader. -* DFU: Implements Nordic DFU protocol so you can update from a phone app such as nRF Connect to perform firmware updates. +* Implements Nordic and InfiniTime DFU protocols so you can update from a phone app such as nRF Connect to perform firmware updates. ## Getting started diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock index df70592..c7ee6a7 100644 --- a/firmware/Cargo.lock +++ b/firmware/Cargo.lock @@ -1553,7 +1553,7 @@ dependencies = [ [[package]] name = "watchful" -version = "0.2.6" +version = "0.3.1" dependencies = [ "bt-hci 0.1.2", "byte-slice-cast", diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml index 2a37bff..153b6b0 100644 --- a/firmware/Cargo.toml +++ b/firmware/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "watchful" -version = "0.3.0" +version = "0.3.1" license = "MIT OR Apache-2.0" build = "build.rs" resolver = "2" diff --git a/firmware/src/ble.rs b/firmware/src/ble.rs index 8c3c5b7..2a604aa 100644 --- a/firmware/src/ble.rs +++ b/firmware/src/ble.rs @@ -10,6 +10,7 @@ use trouble_host::attribute::Characteristic; use trouble_host::gatt::GattEvent; use trouble_host::prelude::*; +use crate::device::Battery; use crate::DfuConfig; pub const ATT_MTU: usize = L2CAP_MTU - 4 - 3; @@ -39,6 +40,16 @@ impl NrfUartService { } */ +// Battery service +#[gatt_service(uuid = service::BATTERY)] +struct BatteryService { + /// Battery Level + #[descriptor(uuid = descriptors::VALID_RANGE, read, value = [0, 100])] + #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, read, value = "Battery Level")] + #[characteristic(uuid = characteristic::BATTERY_LEVEL, read, notify)] + level: u8, +} + #[gatt_service(uuid = "FE59")] pub struct NrfDfuService { #[characteristic(uuid = "8EC90001-F315-4F60-9FB8-838830DAEA50", write, notify)] @@ -51,25 +62,27 @@ pub struct NrfDfuService { packet: Vec, } -#[gatt_service(uuid = "23D1BCEA-5F78-2315-DEEF-121230150000")] -pub struct InfinitimeDfuService { - #[characteristic(uuid = "23D1BCEA-5F78-2315-DEEF-121231150000", write, notify)] - control: Vec, - - #[characteristic(uuid = "23D1BCEA-5F78-2315-DEEF-121231150000", read)] //, value = "8")] - revision: u16, - - /// The maximum size of each packet is derived from the Att MTU size of the connection. - /// The maximum Att MTU size of the DFU Service is 256 bytes (saved in NRF_SDH_BLE_GATT_MAX_MTU_SIZE), - /// making the maximum size of the DFU Packet characteristic 253 bytes. (3 bytes are used for opcode and handle ID upon writing.) - #[characteristic(uuid = "23D1BCEA-5F78-2315-DEEF-121232150000", write_without_response)] - packet: Vec, -} +// NOTE: Disabled as it doesn't properly deal with init/start specific to infinitime protocol +//#[gatt_service(uuid = "23D1BCEA-5F78-2315-DEEF-121230150000")] +//pub struct InfinitimeDfuService { +// #[characteristic(uuid = "23D1BCEA-5F78-2315-DEEF-121231150000", write, notify)] +// control: Vec, +// +// #[characteristic(uuid = "23D1BCEA-5F78-2315-DEEF-121234150000", read, value = 8)] +// revision: u16, +// +// /// The maximum size of each packet is derived from the Att MTU size of the connection. +// /// The maximum Att MTU size of the DFU Service is 256 bytes (saved in NRF_SDH_BLE_GATT_MAX_MTU_SIZE), +// /// making the maximum size of the DFU Packet characteristic 253 bytes. (3 bytes are used for opcode and handle ID upon writing.) +// #[characteristic(uuid = "23D1BCEA-5F78-2315-DEEF-121232150000", write_without_response)] +// packet: Vec, +//} #[gatt_server] pub struct PineTimeServer { nrfdfu: NrfDfuService, - infdfu: InfinitimeDfuService, + battery: BatteryService, + // infdfu: InfinitimeDfuService, // uart: NrfUartService, } @@ -135,17 +148,17 @@ impl PineTimeServer<'_> { if handle == self.nrfdfu.control.handle { self.handle_dfu_control(target, dfu, connection, &self.nrfdfu.control) .await - } else if handle == self.infdfu.control.handle { - self.handle_dfu_control(target, dfu, connection, &self.infdfu.control) - .await + // } else if handle == self.infdfu.control.handle { + // self.handle_dfu_control(target, dfu, connection, &self.infdfu.control) + // .await } else if handle == self.nrfdfu.packet.handle { self.handle_dfu_packet(target, dfu, connection, &self.nrfdfu.control, &self.nrfdfu.packet) .await - } else if handle == self.infdfu.packet.handle { - self.handle_dfu_packet(target, dfu, connection, &self.infdfu.control, &self.infdfu.packet) - .await + // } else if handle == self.infdfu.packet.handle { + // self.handle_dfu_packet(target, dfu, connection, &self.infdfu.control, &self.infdfu.packet) + // .await } else { - warn!("unknown handle {}!", handle); + // Ignore, no need to handle None } } @@ -171,7 +184,12 @@ fn ble_addr() -> Address { const NAME: &str = "Watchful"; -pub fn start(spawner: Spawner, controller: NrfController, dfu_config: DfuConfig<'static>) { +pub fn start( + spawner: Spawner, + controller: NrfController, + dfu_config: DfuConfig<'static>, + battery: &'static Battery<'static>, +) { let resources = RESOURCES.init(BleResources::new()); let stack = STACK.init(trouble_host::new(controller, resources).set_random_address(ble_addr())); @@ -187,7 +205,7 @@ pub fn start(spawner: Spawner, controller: NrfController, dfu_config: DfuConfig< let server = SERVER.init(gatt); spawner.must_spawn(ble_task(runner)); - spawner.must_spawn(advertise_task(stack, peripheral, server, dfu_config)); + spawner.must_spawn(advertise_task(stack, peripheral, server, dfu_config, battery)); } #[embassy_executor::task] @@ -201,12 +219,15 @@ async fn advertise_task( mut peripheral: Peripheral<'static, NrfController>, server: &'static PineTimeServer<'static>, mut dfu_config: DfuConfig<'static>, + battery: &'static Battery<'static>, ) { + const BAS: [u8; 2] = [0x0F, 0x18]; + const DFU: [u8; 2] = [0x59, 0xFE]; let mut advertiser_data = [0; 31]; unwrap!(AdStructure::encode_slice( &[ AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), - AdStructure::ServiceUuids16(&[Uuid::Uuid16([0x59, 0xFE])]), + AdStructure::ServiceUuids16(&[Uuid::Uuid16(BAS), Uuid::Uuid16(DFU)]), AdStructure::CompleteLocalName(NAME.as_bytes()), ], &mut advertiser_data[..], @@ -225,7 +246,7 @@ async fn advertise_task( .await ); match advertiser.accept().await { - Ok(conn) => process(stack, conn, server, &mut dfu_config).await, + Ok(conn) => process(stack, conn, server, &mut dfu_config, battery).await, Err(e) => { warn!("Error advertising: {:?}", e); } @@ -238,6 +259,7 @@ async fn process( connection: Connection<'static>, server: &'static PineTimeServer<'_>, dfu_config: &mut DfuConfig<'static>, + battery: &'static Battery<'static>, ) { let ficr = embassy_nrf::pac::FICR; let part = ficr.info().part().read().part().to_bits(); @@ -273,6 +295,17 @@ async fn process( break; } ConnectionEvent::Gatt { data } => match data.process(server).await { + Ok(Some(GattEvent::Read(event))) => { + let handle = event.handle(); + if handle == server.battery.level.handle { + let value = battery.measure().await.max(100) as u8; + if let Err(_) = server.battery.level.set(server, &value) { + warn!("error updating battery level"); + } + } + let reply = unwrap!(event.accept()); + reply.send().await; + } Ok(Some(GattEvent::Write(event))) => { let handle = event.handle(); let reply = unwrap!(event.accept()); diff --git a/firmware/src/device.rs b/firmware/src/device.rs index 9540905..24b5032 100644 --- a/firmware/src/device.rs +++ b/firmware/src/device.rs @@ -7,6 +7,7 @@ use embassy_nrf::peripherals::{TWISPI0, TWISPI1}; use embassy_nrf::spim::Spim; use embassy_nrf::{saadc, twim}; use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::mutex::Mutex; use embassy_time::{Duration, Timer}; use mipidsi::models::ST7789; @@ -25,11 +26,11 @@ pub struct Device<'a> { pub clock: &'a Clock, pub screen: Screen<'static>, pub button: Button, - pub battery: Battery<'static>, + pub battery: &'a Battery<'static>, pub touchpad: Touchpad<'static>, pub hrs: Hrs<'static>, pub firmware_validator: FirmwareValidator<'static>, - pub vibrator: Vibrator<'static>, + pub vibrator: Vibrator<'static>, } impl<'a> Device<'a> {} @@ -59,22 +60,27 @@ impl Button { pub struct Battery<'a> { charging: Input<'a>, - adc: saadc::Saadc<'a, 1>, + adc: Mutex>, } impl<'a> Battery<'a> { pub fn new(adc: saadc::Saadc<'a, 1>, charging: Input<'a>) -> Self { - Self { adc, charging } + Self { + adc: Mutex::new(adc), + charging, + } } - pub async fn measure(&mut self) -> u32 { + + pub async fn measure(&self) -> u32 { let mut buf = [0i16; 1]; - self.adc.sample(&mut buf).await; + let mut adc = self.adc.lock().await; + adc.sample(&mut buf).await; let voltage = buf[0] as u32 * (8 * 600) / 1024; //let voltage = buf[0] as u32 * 2000 / 1241; approximate_charge(voltage) } - pub fn is_charging(&mut self) -> bool { + pub fn is_charging(&self) -> bool { self.charging.is_low() } } diff --git a/firmware/src/main.rs b/firmware/src/main.rs index db41b14..6090b70 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -54,6 +54,7 @@ bind_interrupts!(struct Irqs { }); static CLOCK: clock::Clock = clock::Clock::new(); +static BATTERY: StaticCell> = StaticCell::new(); type ExternalFlash = XtFlash, Output<'static>>>; @@ -149,6 +150,7 @@ async fn main(s: Spawner) { adc_config.resolution = saadc::Resolution::_10BIT; let saadc = saadc::Saadc::new(p.SAADC, Irqs, adc_config, [bat_config]); let battery = Battery::new(saadc, Input::new(p.P0_12.degrade(), Pull::Up)); + let battery = BATTERY.init(battery); // Touch peripheral let mut twim_config = twim::Config::default(); @@ -196,12 +198,12 @@ async fn main(s: Spawner) { let firmware_validator = FirmwareValidator::new(internal_flash); // BLE - ble::start(s, sdc, dfu_config); - + ble::start(s, sdc, dfu_config, battery); + // Vibration let motor = Output::new(p.P0_16, Level::High, OutputDrive::Standard0Disconnect1); let vibrator = Vibrator::new(motor); - + // Display let backlight = Backlight::new(p.P0_14.degrade(), p.P0_22.degrade(), p.P0_23.degrade()); let rst = Output::new(p.P0_26, Level::Low, OutputDrive::Standard); diff --git a/firmware/src/state.rs b/firmware/src/state.rs index 08844f2..74dcd61 100644 --- a/firmware/src/state.rs +++ b/firmware/src/state.rs @@ -221,7 +221,7 @@ impl MenuState { MenuAction::FirmwareSettings => { let validated = device.firmware_validator.is_valid(); WatchState::Menu(MenuState::new(MenuView::firmware_settings( - firmware_details(&mut device.battery, validated).await, + firmware_details(device.battery, validated).await, ))) } MenuAction::ValidateFirmware => { @@ -272,7 +272,7 @@ impl WorkoutState { } } -async fn firmware_details(battery: &mut crate::device::Battery<'_>, validated: bool) -> FirmwareDetails { +async fn firmware_details(battery: &crate::device::Battery<'_>, validated: bool) -> FirmwareDetails { const CARGO_NAME: &str = env!("CARGO_PKG_NAME"); const CARGO_VERSION: &str = env!("CARGO_PKG_VERSION"); const COMMIT: &str = env!("VERGEN_GIT_SHA");