Skip to main content

lego_powered_up/
hubs.rs

1//! # Specific implementations for each of the supported hubs.
2//1 Models a hub with hub-related properties and commands, as well as
3//! accessing connected devices (internal and external).
4//!
5//! Accessing devices through the hub has changed; instead of a fixed port map,
6//! the map connected_io is populated with attached devices and their available
7//! options and we can select a device from there.
8//! The io_from_.. methods wrap some useful calls on connected_io(), for example;
9//! io_from_kind(IoTypeId::HubLed)
10//! accesses the LED on any hub type though hardware addresses differ,
11//! io_multiple_from_kind(IoTypeId::Motor)
12//! accesses all motors indifferent to where they are connected.
13//!
14//! This also reduces the need for specific implementations, all three
15//! types I have available are supported by generic_hub.  
16
17// This Source Code Form is subject to the terms of the Mozilla Public
18// License, v. 2.0. If a copy of the MPL was not distributed with this
19// file, You can obtain one at https://mozilla.org/MPL/2.0/.
20
21use btleplug::api::{Characteristic, Peripheral as _, WriteType};
22use btleplug::platform::Peripheral;
23use std::collections::BTreeMap;
24use std::fmt::Debug;
25use std::sync::Arc;
26use tokio_util::sync::CancellationToken;
27
28use crate::consts::{HubPropertyOperation, HubPropertyRef, HubType};
29use crate::error::{Error, OptionContext, Result};
30use crate::notifications::{
31    AlertOperation, AlertPayload, AlertType, ErrorMessageFormat, HubAction,
32    HubActionRequest, HubAlert, HubProperty, HubPropertyValue,
33    InformationRequest, InformationType, InputSetupSingle,
34    ModeInformationRequest, ModeInformationType, NetworkCommand,
35    NotificationMessage, PortOutputCommandFeedbackFormat,
36    PortValueCombinedFormat, PortValueSingleFormat,
37};
38use crate::{IoDevice, IoTypeId};
39pub type Tokens = Arc<(Peripheral, Characteristic)>;
40
41pub mod generic_hub;
42pub mod io_event;
43
44/// Trait describing a generic hub.
45#[async_trait::async_trait]
46pub trait Hub: Debug + Send + Sync {
47    async fn name(&self) -> Result<String>;
48    async fn disconnect(&self) -> Result<()>;
49    async fn shutdown(&self) -> Result<()>;
50    async fn is_connected(&self) -> Result<bool>;
51    // The init function cannot be a trait method until we have GAT :(
52    //fn init(peripheral: P);
53    fn properties(&self) -> &HubProperties;
54    fn kind(&self) -> HubType;
55    fn connected_io(&self) -> &BTreeMap<u8, IoDevice>;
56    fn connected_io_mut(&mut self) -> &mut BTreeMap<u8, IoDevice>;
57    fn channels(&mut self) -> &mut crate::hubs::Channels;
58    // fn detach_io(&mut self, ) -> Result<()>;
59    async fn subscribe(&self, char: Characteristic) -> Result<()>;
60    fn io_from_port(&self, port_id: u8) -> Result<IoDevice>;
61    fn io_from_kind(&self, kind: IoTypeId) -> Result<IoDevice>;
62    fn io_multi_from_kind(&self, kind: IoTypeId) -> Result<Vec<IoDevice>>;
63
64    fn tokens(&self) -> Tokens;
65    fn attach_io(&mut self, io_type_id: IoTypeId, port_id: u8) -> Result<()>;
66    fn peripheral(&self) -> Arc<Peripheral>;
67    fn characteristic(&self) -> Arc<Characteristic>;
68    fn device_cache(&self, d: IoDevice) -> IoDevice;
69    fn cancel_token(&self) -> CancellationToken;
70
71    // Port information
72    async fn request_port_info(
73        &self,
74        port_id: u8,
75        infotype: InformationType,
76    ) -> Result<()> {
77        let msg =
78            NotificationMessage::PortInformationRequest(InformationRequest {
79                port_id,
80                information_type: infotype,
81            });
82        self.send(msg).await
83    }
84    async fn req_mode_info(
85        &self,
86        port_id: u8,
87        mode: u8,
88        infotype: ModeInformationType,
89    ) -> Result<()> {
90        let msg = NotificationMessage::PortModeInformationRequest(
91            ModeInformationRequest {
92                port_id,
93                mode,
94                information_type: infotype,
95            },
96        );
97        self.send(msg).await
98    }
99
100    async fn set_port_mode(
101        &self,
102        port_id: u8,
103        mode: u8,
104        delta: u32,
105        notification_enabled: bool,
106    ) -> Result<()> {
107        let msg =
108            NotificationMessage::PortInputFormatSetupSingle(InputSetupSingle {
109                port_id,
110                mode,
111                delta,
112                notification_enabled,
113            });
114        self.send(msg).await
115    }
116
117    /// Hub properties: Single request, enable/disable notifications, reset
118    async fn hub_props(
119        &self,
120        reference: HubPropertyRef,
121        operation: HubPropertyOperation,
122    ) -> Result<()> {
123        let msg = NotificationMessage::HubProperties(HubProperty {
124            reference,
125            operation,
126            property: HubPropertyValue::SecondaryMacAddress, // Not used in request
127        });
128        self.send(msg).await
129    }
130
131    /// Perform Hub actions
132    async fn hub_action(&self, action_type: HubAction) -> Result<()> {
133        let msg =
134            NotificationMessage::HubActions(HubActionRequest { action_type });
135        self.send(msg).await
136    }
137
138    /// Hub alerts: Single request, enable/disable notifications
139    async fn hub_alerts(
140        &self,
141        alert_type: AlertType,
142        operation: AlertOperation,
143    ) -> Result<()> {
144        let msg = NotificationMessage::HubAlerts(HubAlert {
145            alert_type,
146            operation,
147            payload: AlertPayload::StatusOk,
148        });
149        self.send(msg).await
150    }
151
152    async fn send(&self, msg: NotificationMessage) -> Result<()> {
153        let buf = msg.serialise();
154        let tokens = self.tokens();
155        tokens
156            .0
157            .write(&tokens.1, &buf, WriteType::WithoutResponse)
158            .await?;
159        Ok(())
160    }
161
162    // Cannot provide a default implementation without access to the Peripheral trait from here
163    async fn send_raw(&self, msg: &[u8]) -> Result<()>;
164}
165
166pub type VersionNumber = u8;
167/// Propeties of a hub
168#[derive(Debug, Default)]
169pub struct HubProperties {
170    /// Friendly name, set via the PoweredUp or Control+ apps
171    pub name: String,
172    /// Firmware revision
173    pub fw_version: String,
174    /// Hardware revision
175    pub hw_version: String,
176    /// BLE MAC address
177    pub mac_address: String,
178    /// Battery level
179    pub battery_level: usize,
180    /// BLE signal strength
181    pub rssi: i16,
182}
183
184/// Devices can use this with cached tokens and not need to mutex-lock hub
185pub async fn send(tokens: Tokens, msg: NotificationMessage) -> Result<()> {
186    let buf = msg.serialise();
187    tokens
188        .0
189        .write(&tokens.1, &buf, WriteType::WithoutResponse)
190        .await?;
191    Ok(())
192}
193
194#[derive(Debug, Default, Clone)]
195pub struct Channels {
196    pub singlevalue_sender:
197        Option<tokio::sync::broadcast::Sender<PortValueSingleFormat>>,
198    pub combinedvalue_sender:
199        Option<tokio::sync::broadcast::Sender<PortValueCombinedFormat>>,
200    pub networkcmd_sender:
201        Option<tokio::sync::broadcast::Sender<NetworkCommand>>,
202    pub hubnotification_sender:
203        Option<tokio::sync::broadcast::Sender<HubNotification>>,
204    pub commandfeedback_sender:
205        Option<tokio::sync::broadcast::Sender<PortOutputCommandFeedbackFormat>>,
206}
207
208#[derive(Debug, Default, Clone)]
209pub struct HubNotification {
210    pub hub_property: Option<HubProperty>,
211    pub hub_action: Option<HubActionRequest>,
212    pub hub_alert: Option<HubAlert>,
213    pub hub_error: Option<ErrorMessageFormat>,
214}