use btleplug::api::{Central, Peripheral as _, WriteType};
use btleplug::platform::{Adapter, Peripheral};
use uuid::{Uuid, uuid};
const FIRMWARE_VERSION_UUID: Uuid = uuid!("00002a26-0000-1000-8000-00805f9b34fb");
const BATTERY_LEVEL_UUID: Uuid = uuid!("00002a19-0000-1000-8000-00805f9b34fb");
const HEART_RATE_UUID: Uuid = uuid!("00002a37-0000-1000-8000-00805f9b34fb");
const STEP_COUNT_UUID: Uuid = uuid!("00030001-78fc-48fe-8e23-433b3a1942d0");
const NOTIFICATION_UUID: Uuid = uuid!("00002a46-0000-1000-8000-00805f9b34fb");
const WEATHER_SERVICE_UUID: Uuid = uuid!("00050001-78fc-48fe-8e23-433b3a1942d0");
const CURRENT_TIME_UUID: Uuid = uuid!("00002a2b-0000-1000-8000-00805f9b34fb");
const NAVIGATION_FLAG_UUID: Uuid = uuid!("00010001-78fc-48fe-8e23-433b3a1942d0");
const NAVIGATION_NARRATIVE_UUID: Uuid = uuid!("00010002-78fc-48fe-8e23-433b3a1942d0");
const NAVIGATION_MAN_DIST_UUID: Uuid = uuid!("00010003-78fc-48fe-8e23-433b3a1942d0");
const NAVIGATION_PROGRESS_UUID: Uuid = uuid!("00010004-78fc-48fe-8e23-433b3a1942d0");
pub enum NotificationCategory {
SimpleAlert = 0,
Email,
News,
CallNotification,
MissedCall,
SMS,
Voicemail,
Schedule,
HighPrioritizedAlert,
InstantMessage,
}
pub struct Notification {
category: NotificationCategory,
title: String,
body: String,
}
impl Notification {
pub fn new(category: NotificationCategory, title: String, body: String) -> Notification {
return Self {
category: category,
title: title,
body: body,
};
}
}
pub async fn find_infinitime(adapter: &Adapter) -> Option<Peripheral> {
for p in adapter.peripherals().await.unwrap() {
if p.properties()
.await
.unwrap()
.unwrap()
.local_name
.iter()
.any(|name| name.to_lowercase().contains("infinitime"))
{
return Some(p);
}
}
return None;
}
async fn get_property_value(infinitime: &Peripheral, property: Uuid) -> Vec<u8> {
let chars = infinitime.characteristics();
let cmd_char = chars.iter().find(|c| c.uuid == property).unwrap();
let result = infinitime.read(cmd_char).await.unwrap();
return result;
}
pub async fn get_firmware_version(infinitime: &Peripheral) -> String {
let data = get_property_value(infinitime, FIRMWARE_VERSION_UUID).await;
return String::from_utf8(data).expect("Found invalid UTF-8");
}
pub async fn get_battery_level(infinitime: &Peripheral) -> String {
let data = get_property_value(infinitime, BATTERY_LEVEL_UUID).await;
let percent = data[0];
return percent.to_string();
}
pub async fn get_heart_rate(infinitime: &Peripheral) -> String {
let data = get_property_value(infinitime, HEART_RATE_UUID).await;
let rate = data[1];
return rate.to_string();
}
pub async fn get_step_count(infinitime: &Peripheral) -> String {
let data = get_property_value(infinitime, STEP_COUNT_UUID).await;
let count = data[0];
return count.to_string();
}
pub async fn send_notification(infinitime: &Peripheral, notification: Notification) {
let chars = infinitime.characteristics();
let cmd_char = chars.iter().find(|c| c.uuid == NOTIFICATION_UUID).unwrap();
infinitime
.write(
cmd_char,
&[
&[0, notification.category as u8],
notification.title.as_bytes(),
notification.body.as_bytes(),
]
.join(&0),
WriteType::WithoutResponse,
)
.await
.unwrap();
}
pub async fn send_current_weather(
infinitime: &Peripheral,
timestamp: u64,
temp: i16,
min_temp: i16,
max_temp: i16,
location: &str,
icon: u8,
sunrise: i16,
sunset: i16,
) -> Result<(), btleplug::Error> {
let chars = infinitime.characteristics();
let cmd_char = chars
.iter()
.find(|c| c.uuid == WEATHER_SERVICE_UUID)
.unwrap();
let mut data = Vec::new();
// Message header
data.push(0); // Message type: Current weather
data.push(1); // Message version: 1 (with sunrise/sunset)
data.extend(timestamp.to_ne_bytes());
// Temperatures (°C * 100)
data.extend((temp * 100).to_ne_bytes());
data.extend((min_temp * 100).to_ne_bytes());
data.extend((max_temp * 100).to_ne_bytes());
// Location ,padded with 0 bytes
let location_bytes = location.as_bytes();
for byte in location_bytes {
data.push(*byte);
}
while data.len() < 48 {
data.push(0);
}
data.push(icon);
data.extend(sunrise.to_ne_bytes());
data.extend(sunset.to_ne_bytes());
infinitime
.write(cmd_char, &data, WriteType::WithoutResponse)
.await?;
Ok(())
}
pub async fn send_forecast(
infinitime: &Peripheral,
timestamp: u64,
num_days: u8,
days: &[(i16, i16, u8)],
) -> Result<(), btleplug::Error> {
let chars = infinitime.characteristics();
let cmd_char = chars
.iter()
.find(|c| c.uuid == WEATHER_SERVICE_UUID)
.unwrap();
let mut data = Vec::new();
// Message header
data.push(1); // Message type: Forecast
data.push(0); // Message version: 0
data.extend(timestamp.to_ne_bytes());
data.push(num_days);
for (min_temp, max_temp, icon) in days {
data.extend((min_temp * 100).to_ne_bytes());
data.extend((max_temp * 100).to_ne_bytes());
data.push(*icon);
}
infinitime
.write(cmd_char, &data, WriteType::WithoutResponse)
.await?;
Ok(())
}
pub async fn send_current_time(
infinitime: &Peripheral,
year: u16,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
weekday: u8,
microsecond: u32,
) -> Result<(), btleplug::Error> {
let chars = infinitime.characteristics();
let cmd_char = chars.iter().find(|c| c.uuid == CURRENT_TIME_UUID).unwrap();
let mut data = Vec::new();
data.extend(year.to_le_bytes());
data.push(month);
data.push(day);
data.push(hour);
data.push(minute);
data.push(second);
data.push(weekday);
data.push((microsecond / (1_000_000 * 256)) as u8);
data.push(0x01);
infinitime
.write(cmd_char, &data, WriteType::WithoutResponse)
.await?;
Ok(())
}
pub struct NavigationInstruction {
flag: String,
narrative: String,
man_dist: String,
progress: u8,
}
impl NavigationInstruction {
pub fn new(flag: String, narrative: String, man_dist: String, progress: u8) -> Self {
Self {
flag,
narrative,
man_dist,
progress,
}
}
}
pub async fn send_navigation(
infinitime: &Peripheral,
navigation: &NavigationInstruction,
) -> Result<(), btleplug::Error> {
let chars = infinitime.characteristics();
// Icon name
let cmd_char = chars
.iter()
.find(|c| c.uuid == NAVIGATION_FLAG_UUID)
.unwrap();
infinitime
.write(
cmd_char,
navigation.flag.as_bytes(),
WriteType::WithoutResponse,
)
.await?;
// Text description
let cmd_char = chars
.iter()
.find(|c| c.uuid == NAVIGATION_NARRATIVE_UUID)
.unwrap();
infinitime
.write(
cmd_char,
navigation.narrative.as_bytes(),
WriteType::WithoutResponse,
)
.await?;
// Man dist
let cmd_char = chars
.iter()
.find(|c| c.uuid == NAVIGATION_MAN_DIST_UUID)
.unwrap();
infinitime
.write(
cmd_char,
navigation.man_dist.as_bytes(),
WriteType::WithoutResponse,
)
.await?;
// Progress (0-100)
let cmd_char = chars
.iter()
.find(|c| c.uuid == NAVIGATION_PROGRESS_UUID)
.unwrap();
infinitime
.write(cmd_char, &[navigation.progress], WriteType::WithoutResponse)
.await?;
Ok(())
}