use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use arora_bridge::{
Bridge, BridgeCommand, BridgeOp, BridgeResult, CommandStream, DataRequestedStream, DeviceInfo,
DeviceInfoStream,
};
use arora_types::data::{Key, StateChange};
use arora_types::value::Value;
use async_trait::async_trait;
use futures::channel::{mpsc, oneshot};
use crate::messages::Outgoing;
use crate::server::AroraWSServer;
pub struct WsBridge {
server: Arc<AroraWSServer>,
commands: Mutex<Option<mpsc::UnboundedReceiver<BridgeCommand>>>,
}
impl WsBridge {
pub async fn new(server: Arc<AroraWSServer>) -> Self {
let (cmd_tx, cmd_rx) = mpsc::unbounded::<BridgeCommand>();
let tx = cmd_tx.clone();
server
.set_set_slot_values_handler(move |values: HashMap<String, Value>| {
let mut change = StateChange::new();
for (path, value) in values {
change.set.insert(Key::from(path), Some(value));
}
let (reply_tx, _reply_rx) = oneshot::channel();
tx.unbounded_send(BridgeCommand::new(BridgeOp::Update(change), reply_tx))
.map_err(|_| "bridge command channel closed".to_string())
})
.await;
let tx = cmd_tx.clone();
server
.set_get_slot_values_handler(Arc::new(move |slots: Vec<String>| {
let tx = tx.clone();
Box::pin(async move {
let keys: Vec<Key> = slots.iter().cloned().map(Key::from).collect();
let (reply_tx, reply_rx) = oneshot::channel();
if tx
.unbounded_send(BridgeCommand::new(BridgeOp::Get(keys), reply_tx))
.is_err()
{
return HashMap::new();
}
match reply_rx.await {
Ok(Ok(result)) => values_from_get(&slots, result.ret),
_ => HashMap::new(),
}
}) as _
}))
.await;
Self {
server,
commands: Mutex::new(Some(cmd_rx)),
}
}
}
fn values_from_get(slots: &[String], ret: Value) -> HashMap<String, Value> {
let mut out = HashMap::new();
if let Value::ArrayValue(items) = ret {
for (slot, item) in slots.iter().zip(items) {
if let Value::Option(Some(value)) = item {
out.insert(slot.clone(), *value);
}
}
}
out
}
#[async_trait]
impl Bridge for WsBridge {
async fn get_device_info(&self) -> BridgeResult<Option<DeviceInfo>> {
Ok(None)
}
async fn device_info_updated(&self) -> BridgeResult<DeviceInfoStream> {
Ok(Box::pin(futures::stream::empty()))
}
async fn update_device_info(
&self,
info: Option<DeviceInfo>,
) -> BridgeResult<Option<DeviceInfo>> {
Ok(info)
}
async fn data_requested(&self) -> DataRequestedStream {
Box::pin(futures::stream::once(async { true }))
}
async fn send_data(&self, data: StateChange) -> BridgeResult<()> {
let mut values = HashMap::new();
for (key, value) in data.set {
if let Some(value) = value {
values.insert(key.path, value);
}
}
if !values.is_empty() {
self.server.push(Outgoing::SlotValuesChanged { values });
}
Ok(())
}
async fn commands(&self) -> CommandStream {
match self.commands.lock().unwrap().take() {
Some(rx) => Box::pin(rx),
None => {
log::warn!(
"WsBridge::commands() called more than once; the command \
stream was already taken, returning an empty stream"
);
Box::pin(futures::stream::empty())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::server::ServerConfig;
#[tokio::test]
async fn send_data_pushes_slot_values_changed() {
let server = Arc::new(AroraWSServer::new(ServerConfig::default()));
let bridge = WsBridge::new(server.clone()).await;
let mut rx = server.subscribe();
bridge
.send_data(StateChange::set("face/mouth", Value::F64(0.5)))
.await
.expect("send_data");
match rx.recv().await.expect("a push") {
Outgoing::SlotValuesChanged { values } => {
assert_eq!(values.get("face/mouth"), Some(&Value::F64(0.5)));
}
other => panic!("expected SlotValuesChanged, got {other:?}"),
}
}
}