Skip to content

Commit 34c9bf1

Browse files
committed
feat: support battery level service
1 parent 9940fa1 commit 34c9bf1

File tree

7 files changed

+82
-41
lines changed

7 files changed

+82
-41
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Firmware for Pinetime based on [Embassy](https://embassy.dev). The goal is to pr
1616
* Automatically synchronizes time with using BLE standard Current Time Service.
1717
* Rollback to previous firmware if reset or crashing before new firmware is validated in watch UI.
1818
* Compatible with existing InfiniTime bootloader.
19-
* DFU: Implements Nordic DFU protocol so you can update from a phone app such as nRF Connect to perform firmware updates.
19+
* Implements Nordic and InfiniTime DFU protocols so you can update from a phone app such as nRF Connect to perform firmware updates.
2020

2121
## Getting started
2222

firmware/Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

firmware/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
edition = "2021"
33
name = "watchful"
4-
version = "0.3.0"
4+
version = "0.3.1"
55
license = "MIT OR Apache-2.0"
66
build = "build.rs"
77
resolver = "2"

firmware/src/ble.rs

+59-26
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use trouble_host::attribute::Characteristic;
1010
use trouble_host::gatt::GattEvent;
1111
use trouble_host::prelude::*;
1212

13+
use crate::device::Battery;
1314
use crate::DfuConfig;
1415

1516
pub const ATT_MTU: usize = L2CAP_MTU - 4 - 3;
@@ -39,6 +40,16 @@ impl NrfUartService {
3940
}
4041
*/
4142

43+
// Battery service
44+
#[gatt_service(uuid = service::BATTERY)]
45+
struct BatteryService {
46+
/// Battery Level
47+
#[descriptor(uuid = descriptors::VALID_RANGE, read, value = [0, 100])]
48+
#[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, read, value = "Battery Level")]
49+
#[characteristic(uuid = characteristic::BATTERY_LEVEL, read, notify)]
50+
level: u8,
51+
}
52+
4253
#[gatt_service(uuid = "FE59")]
4354
pub struct NrfDfuService {
4455
#[characteristic(uuid = "8EC90001-F315-4F60-9FB8-838830DAEA50", write, notify)]
@@ -51,25 +62,27 @@ pub struct NrfDfuService {
5162
packet: Vec<u8, ATT_MTU>,
5263
}
5364

54-
#[gatt_service(uuid = "23D1BCEA-5F78-2315-DEEF-121230150000")]
55-
pub struct InfinitimeDfuService {
56-
#[characteristic(uuid = "23D1BCEA-5F78-2315-DEEF-121231150000", write, notify)]
57-
control: Vec<u8, ATT_MTU>,
58-
59-
#[characteristic(uuid = "23D1BCEA-5F78-2315-DEEF-121231150000", read)] //, value = "8")]
60-
revision: u16,
61-
62-
/// The maximum size of each packet is derived from the Att MTU size of the connection.
63-
/// The maximum Att MTU size of the DFU Service is 256 bytes (saved in NRF_SDH_BLE_GATT_MAX_MTU_SIZE),
64-
/// making the maximum size of the DFU Packet characteristic 253 bytes. (3 bytes are used for opcode and handle ID upon writing.)
65-
#[characteristic(uuid = "23D1BCEA-5F78-2315-DEEF-121232150000", write_without_response)]
66-
packet: Vec<u8, ATT_MTU>,
67-
}
65+
// NOTE: Disabled as it doesn't properly deal with init/start specific to infinitime protocol
66+
//#[gatt_service(uuid = "23D1BCEA-5F78-2315-DEEF-121230150000")]
67+
//pub struct InfinitimeDfuService {
68+
// #[characteristic(uuid = "23D1BCEA-5F78-2315-DEEF-121231150000", write, notify)]
69+
// control: Vec<u8, ATT_MTU>,
70+
//
71+
// #[characteristic(uuid = "23D1BCEA-5F78-2315-DEEF-121234150000", read, value = 8)]
72+
// revision: u16,
73+
//
74+
// /// The maximum size of each packet is derived from the Att MTU size of the connection.
75+
// /// The maximum Att MTU size of the DFU Service is 256 bytes (saved in NRF_SDH_BLE_GATT_MAX_MTU_SIZE),
76+
// /// making the maximum size of the DFU Packet characteristic 253 bytes. (3 bytes are used for opcode and handle ID upon writing.)
77+
// #[characteristic(uuid = "23D1BCEA-5F78-2315-DEEF-121232150000", write_without_response)]
78+
// packet: Vec<u8, ATT_MTU>,
79+
//}
6880

6981
#[gatt_server]
7082
pub struct PineTimeServer {
7183
nrfdfu: NrfDfuService,
72-
infdfu: InfinitimeDfuService,
84+
battery: BatteryService,
85+
// infdfu: InfinitimeDfuService,
7386
// uart: NrfUartService,
7487
}
7588

@@ -135,17 +148,17 @@ impl PineTimeServer<'_> {
135148
if handle == self.nrfdfu.control.handle {
136149
self.handle_dfu_control(target, dfu, connection, &self.nrfdfu.control)
137150
.await
138-
} else if handle == self.infdfu.control.handle {
139-
self.handle_dfu_control(target, dfu, connection, &self.infdfu.control)
140-
.await
151+
// } else if handle == self.infdfu.control.handle {
152+
// self.handle_dfu_control(target, dfu, connection, &self.infdfu.control)
153+
// .await
141154
} else if handle == self.nrfdfu.packet.handle {
142155
self.handle_dfu_packet(target, dfu, connection, &self.nrfdfu.control, &self.nrfdfu.packet)
143156
.await
144-
} else if handle == self.infdfu.packet.handle {
145-
self.handle_dfu_packet(target, dfu, connection, &self.infdfu.control, &self.infdfu.packet)
146-
.await
157+
// } else if handle == self.infdfu.packet.handle {
158+
// self.handle_dfu_packet(target, dfu, connection, &self.infdfu.control, &self.infdfu.packet)
159+
// .await
147160
} else {
148-
warn!("unknown handle {}!", handle);
161+
// Ignore, no need to handle
149162
None
150163
}
151164
}
@@ -171,7 +184,12 @@ fn ble_addr() -> Address {
171184

172185
const NAME: &str = "Watchful";
173186

174-
pub fn start(spawner: Spawner, controller: NrfController, dfu_config: DfuConfig<'static>) {
187+
pub fn start(
188+
spawner: Spawner,
189+
controller: NrfController,
190+
dfu_config: DfuConfig<'static>,
191+
battery: &'static Battery<'static>,
192+
) {
175193
let resources = RESOURCES.init(BleResources::new());
176194
let stack = STACK.init(trouble_host::new(controller, resources).set_random_address(ble_addr()));
177195

@@ -187,7 +205,7 @@ pub fn start(spawner: Spawner, controller: NrfController, dfu_config: DfuConfig<
187205
let server = SERVER.init(gatt);
188206

189207
spawner.must_spawn(ble_task(runner));
190-
spawner.must_spawn(advertise_task(stack, peripheral, server, dfu_config));
208+
spawner.must_spawn(advertise_task(stack, peripheral, server, dfu_config, battery));
191209
}
192210

193211
#[embassy_executor::task]
@@ -201,12 +219,15 @@ async fn advertise_task(
201219
mut peripheral: Peripheral<'static, NrfController>,
202220
server: &'static PineTimeServer<'static>,
203221
mut dfu_config: DfuConfig<'static>,
222+
battery: &'static Battery<'static>,
204223
) {
224+
const BAS: [u8; 2] = [0x0F, 0x18];
225+
const DFU: [u8; 2] = [0x59, 0xFE];
205226
let mut advertiser_data = [0; 31];
206227
unwrap!(AdStructure::encode_slice(
207228
&[
208229
AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
209-
AdStructure::ServiceUuids16(&[Uuid::Uuid16([0x59, 0xFE])]),
230+
AdStructure::ServiceUuids16(&[Uuid::Uuid16(BAS), Uuid::Uuid16(DFU)]),
210231
AdStructure::CompleteLocalName(NAME.as_bytes()),
211232
],
212233
&mut advertiser_data[..],
@@ -225,7 +246,7 @@ async fn advertise_task(
225246
.await
226247
);
227248
match advertiser.accept().await {
228-
Ok(conn) => process(stack, conn, server, &mut dfu_config).await,
249+
Ok(conn) => process(stack, conn, server, &mut dfu_config, battery).await,
229250
Err(e) => {
230251
warn!("Error advertising: {:?}", e);
231252
}
@@ -238,6 +259,7 @@ async fn process(
238259
connection: Connection<'static>,
239260
server: &'static PineTimeServer<'_>,
240261
dfu_config: &mut DfuConfig<'static>,
262+
battery: &'static Battery<'static>,
241263
) {
242264
let ficr = embassy_nrf::pac::FICR;
243265
let part = ficr.info().part().read().part().to_bits();
@@ -273,6 +295,17 @@ async fn process(
273295
break;
274296
}
275297
ConnectionEvent::Gatt { data } => match data.process(server).await {
298+
Ok(Some(GattEvent::Read(event))) => {
299+
let handle = event.handle();
300+
if handle == server.battery.level.handle {
301+
let value = battery.measure().await.max(100) as u8;
302+
if let Err(_) = server.battery.level.set(server, &value) {
303+
warn!("error updating battery level");
304+
}
305+
}
306+
let reply = unwrap!(event.accept());
307+
reply.send().await;
308+
}
276309
Ok(Some(GattEvent::Write(event))) => {
277310
let handle = event.handle();
278311
let reply = unwrap!(event.accept());

firmware/src/device.rs

+13-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use embassy_nrf::peripherals::{TWISPI0, TWISPI1};
77
use embassy_nrf::spim::Spim;
88
use embassy_nrf::{saadc, twim};
99
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
10+
use embassy_sync::mutex::Mutex;
1011
use embassy_time::{Duration, Timer};
1112
use mipidsi::models::ST7789;
1213

@@ -25,11 +26,11 @@ pub struct Device<'a> {
2526
pub clock: &'a Clock,
2627
pub screen: Screen<'static>,
2728
pub button: Button,
28-
pub battery: Battery<'static>,
29+
pub battery: &'a Battery<'static>,
2930
pub touchpad: Touchpad<'static>,
3031
pub hrs: Hrs<'static>,
3132
pub firmware_validator: FirmwareValidator<'static>,
32-
pub vibrator: Vibrator<'static>,
33+
pub vibrator: Vibrator<'static>,
3334
}
3435

3536
impl<'a> Device<'a> {}
@@ -59,22 +60,27 @@ impl Button {
5960

6061
pub struct Battery<'a> {
6162
charging: Input<'a>,
62-
adc: saadc::Saadc<'a, 1>,
63+
adc: Mutex<NoopRawMutex, saadc::Saadc<'a, 1>>,
6364
}
6465

6566
impl<'a> Battery<'a> {
6667
pub fn new(adc: saadc::Saadc<'a, 1>, charging: Input<'a>) -> Self {
67-
Self { adc, charging }
68+
Self {
69+
adc: Mutex::new(adc),
70+
charging,
71+
}
6872
}
69-
pub async fn measure(&mut self) -> u32 {
73+
74+
pub async fn measure(&self) -> u32 {
7075
let mut buf = [0i16; 1];
71-
self.adc.sample(&mut buf).await;
76+
let mut adc = self.adc.lock().await;
77+
adc.sample(&mut buf).await;
7278
let voltage = buf[0] as u32 * (8 * 600) / 1024;
7379
//let voltage = buf[0] as u32 * 2000 / 1241;
7480
approximate_charge(voltage)
7581
}
7682

77-
pub fn is_charging(&mut self) -> bool {
83+
pub fn is_charging(&self) -> bool {
7884
self.charging.is_low()
7985
}
8086
}

firmware/src/main.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ bind_interrupts!(struct Irqs {
5454
});
5555

5656
static CLOCK: clock::Clock = clock::Clock::new();
57+
static BATTERY: StaticCell<device::Battery<'static>> = StaticCell::new();
5758

5859
type ExternalFlash = XtFlash<SpiDevice<'static, NoopRawMutex, Spim<'static, TWISPI0>, Output<'static>>>;
5960

@@ -149,6 +150,7 @@ async fn main(s: Spawner) {
149150
adc_config.resolution = saadc::Resolution::_10BIT;
150151
let saadc = saadc::Saadc::new(p.SAADC, Irqs, adc_config, [bat_config]);
151152
let battery = Battery::new(saadc, Input::new(p.P0_12.degrade(), Pull::Up));
153+
let battery = BATTERY.init(battery);
152154

153155
// Touch peripheral
154156
let mut twim_config = twim::Config::default();
@@ -196,12 +198,12 @@ async fn main(s: Spawner) {
196198
let firmware_validator = FirmwareValidator::new(internal_flash);
197199

198200
// BLE
199-
ble::start(s, sdc, dfu_config);
200-
201+
ble::start(s, sdc, dfu_config, battery);
202+
201203
// Vibration
202204
let motor = Output::new(p.P0_16, Level::High, OutputDrive::Standard0Disconnect1);
203205
let vibrator = Vibrator::new(motor);
204-
206+
205207
// Display
206208
let backlight = Backlight::new(p.P0_14.degrade(), p.P0_22.degrade(), p.P0_23.degrade());
207209
let rst = Output::new(p.P0_26, Level::Low, OutputDrive::Standard);

firmware/src/state.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ impl MenuState {
221221
MenuAction::FirmwareSettings => {
222222
let validated = device.firmware_validator.is_valid();
223223
WatchState::Menu(MenuState::new(MenuView::firmware_settings(
224-
firmware_details(&mut device.battery, validated).await,
224+
firmware_details(device.battery, validated).await,
225225
)))
226226
}
227227
MenuAction::ValidateFirmware => {
@@ -272,7 +272,7 @@ impl WorkoutState {
272272
}
273273
}
274274

275-
async fn firmware_details(battery: &mut crate::device::Battery<'_>, validated: bool) -> FirmwareDetails {
275+
async fn firmware_details(battery: &crate::device::Battery<'_>, validated: bool) -> FirmwareDetails {
276276
const CARGO_NAME: &str = env!("CARGO_PKG_NAME");
277277
const CARGO_VERSION: &str = env!("CARGO_PKG_VERSION");
278278
const COMMIT: &str = env!("VERGEN_GIT_SHA");

0 commit comments

Comments
 (0)