arora_websocket/
bridge.rs1use std::collections::HashMap;
17use std::sync::{Arc, Mutex};
18
19use arora_bridge::{
20 Bridge, BridgeCommand, BridgeOp, BridgeResult, CommandStream, DataRequestedStream, DeviceInfo,
21 DeviceInfoStream,
22};
23use arora_types::data::{Key, StateChange};
24use arora_types::value::Value;
25use async_trait::async_trait;
26use futures::channel::{mpsc, oneshot};
27
28use crate::messages::Outgoing;
29use crate::server::AroraWSServer;
30
31pub struct WsBridge {
38 server: Arc<AroraWSServer>,
39 commands: Mutex<Option<mpsc::UnboundedReceiver<BridgeCommand>>>,
41}
42
43impl WsBridge {
44 pub async fn new(server: Arc<AroraWSServer>) -> Self {
47 let (cmd_tx, cmd_rx) = mpsc::unbounded::<BridgeCommand>();
48
49 let tx = cmd_tx.clone();
52 server
53 .set_set_slot_values_handler(move |values: HashMap<String, Value>| {
54 let mut change = StateChange::new();
55 for (path, value) in values {
56 change.set.insert(Key::from(path), Some(value));
57 }
58 let (reply_tx, _reply_rx) = oneshot::channel();
59 tx.unbounded_send(BridgeCommand::new(BridgeOp::Update(change), reply_tx))
60 .map_err(|_| "bridge command channel closed".to_string())
61 })
62 .await;
63
64 let tx = cmd_tx.clone();
66 server
67 .set_get_slot_values_handler(Arc::new(move |slots: Vec<String>| {
68 let tx = tx.clone();
69 Box::pin(async move {
70 let keys: Vec<Key> = slots.iter().cloned().map(Key::from).collect();
71 let (reply_tx, reply_rx) = oneshot::channel();
72 if tx
73 .unbounded_send(BridgeCommand::new(BridgeOp::Get(keys), reply_tx))
74 .is_err()
75 {
76 return HashMap::new();
77 }
78 match reply_rx.await {
79 Ok(Ok(result)) => values_from_get(&slots, result.ret),
80 _ => HashMap::new(),
81 }
82 }) as _
83 }))
84 .await;
85
86 Self {
87 server,
88 commands: Mutex::new(Some(cmd_rx)),
89 }
90 }
91}
92
93fn values_from_get(slots: &[String], ret: Value) -> HashMap<String, Value> {
96 let mut out = HashMap::new();
97 if let Value::ArrayValue(items) = ret {
98 for (slot, item) in slots.iter().zip(items) {
99 if let Value::Option(Some(value)) = item {
100 out.insert(slot.clone(), *value);
101 }
102 }
103 }
104 out
105}
106
107#[async_trait]
108impl Bridge for WsBridge {
109 async fn get_device_info(&self) -> BridgeResult<Option<DeviceInfo>> {
110 Ok(None)
112 }
113
114 async fn device_info_updated(&self) -> BridgeResult<DeviceInfoStream> {
115 Ok(Box::pin(futures::stream::empty()))
116 }
117
118 async fn update_device_info(
119 &self,
120 info: Option<DeviceInfo>,
121 ) -> BridgeResult<Option<DeviceInfo>> {
122 Ok(info)
123 }
124
125 async fn data_requested(&self) -> DataRequestedStream {
126 Box::pin(futures::stream::once(async { true }))
128 }
129
130 async fn send_data(&self, data: StateChange) -> BridgeResult<()> {
131 let mut values = HashMap::new();
133 for (key, value) in data.set {
134 if let Some(value) = value {
135 values.insert(key.path, value);
136 }
137 }
138 if !values.is_empty() {
139 self.server.push(Outgoing::SlotValuesChanged { values });
140 }
141 Ok(())
142 }
143
144 async fn commands(&self) -> CommandStream {
149 match self.commands.lock().unwrap().take() {
150 Some(rx) => Box::pin(rx),
151 None => {
152 log::warn!(
153 "WsBridge::commands() called more than once; the command \
154 stream was already taken, returning an empty stream"
155 );
156 Box::pin(futures::stream::empty())
157 }
158 }
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use crate::server::ServerConfig;
166
167 #[tokio::test]
168 async fn send_data_pushes_slot_values_changed() {
169 let server = Arc::new(AroraWSServer::new(ServerConfig::default()));
170 let bridge = WsBridge::new(server.clone()).await;
171 let mut rx = server.subscribe();
172
173 bridge
174 .send_data(StateChange::set("face/mouth", Value::F64(0.5)))
175 .await
176 .expect("send_data");
177
178 match rx.recv().await.expect("a push") {
179 Outgoing::SlotValuesChanged { values } => {
180 assert_eq!(values.get("face/mouth"), Some(&Value::F64(0.5)));
181 }
182 other => panic!("expected SlotValuesChanged, got {other:?}"),
183 }
184 }
185}