Add relays websocket
This commit is contained in:
parent
27739e2b71
commit
ab7090f2c5
6 changed files with 216 additions and 14 deletions
|
@ -1,12 +1,15 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use actix::{Actor, Context, Handler, Message, Recipient};
|
||||
use actix::{Actor, Addr, Context, Handler, Message, Recipient};
|
||||
use emgauwa_lib::db::DbController;
|
||||
use emgauwa_lib::errors::EmgauwaError;
|
||||
use emgauwa_lib::models::Controller;
|
||||
use emgauwa_lib::models::{convert_db_list, Controller, Relay};
|
||||
use emgauwa_lib::types::{ControllerUid, ControllerWsAction, RelayStates};
|
||||
use futures::executor::block_on;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
use crate::handlers::v1::ws::relays::{RelaysWs, SendRelays};
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<(), EmgauwaError>")]
|
||||
pub struct DisconnectController {
|
||||
|
@ -27,6 +30,10 @@ pub struct UpdateRelayStates {
|
|||
pub relay_states: RelayStates,
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<Vec<Relay>, EmgauwaError>")]
|
||||
pub struct GetRelays {}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<(), EmgauwaError>")]
|
||||
pub struct Action {
|
||||
|
@ -34,9 +41,16 @@ pub struct Action {
|
|||
pub action: ControllerWsAction,
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "()")]
|
||||
pub struct ConnectRelayClient {
|
||||
pub addr: Addr<RelaysWs>,
|
||||
}
|
||||
|
||||
pub struct AppState {
|
||||
pub pool: Pool<Sqlite>,
|
||||
pub connected_controllers: HashMap<ControllerUid, (Controller, Recipient<ControllerWsAction>)>,
|
||||
pub connected_relay_clients: Vec<Addr<RelaysWs>>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
|
@ -44,8 +58,51 @@ impl AppState {
|
|||
AppState {
|
||||
pool,
|
||||
connected_controllers: HashMap::new(),
|
||||
connected_relay_clients: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_relays(&self) -> Result<Vec<Relay>, EmgauwaError> {
|
||||
let mut pool_conn = block_on(self.pool.acquire())?;
|
||||
let db_controllers = block_on(DbController::get_all(&mut pool_conn))?;
|
||||
let mut controllers: Vec<Controller> = convert_db_list(&mut pool_conn, db_controllers)?;
|
||||
|
||||
self.connected_controllers
|
||||
.iter()
|
||||
.for_each(|(uid, (connected_controller, _))| {
|
||||
if let Some(c) = controllers.iter_mut().find(|c| c.c.uid == *uid) {
|
||||
c.apply_relay_states(&connected_controller.get_relay_states());
|
||||
}
|
||||
});
|
||||
|
||||
let mut relays: Vec<Relay> = Vec::new();
|
||||
controllers.iter().for_each(|c| {
|
||||
relays.extend(c.relays.clone());
|
||||
});
|
||||
|
||||
Ok(relays)
|
||||
}
|
||||
|
||||
fn notify_relay_clients(&mut self) {
|
||||
self.connected_relay_clients.retain(|addr| addr.connected());
|
||||
|
||||
match self.get_relays() {
|
||||
Ok(relays) => match serde_json::to_string(&relays) {
|
||||
Ok(json) => {
|
||||
self.connected_relay_clients.iter_mut().for_each(|addr| {
|
||||
let relays_json = json.clone();
|
||||
addr.do_send(SendRelays { relays_json });
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to serialize relays: {:?}", err);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
log::error!("Failed to get relays: {:?}", err);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Actor for AppState {
|
||||
|
@ -94,6 +151,15 @@ impl Handler<UpdateRelayStates> for AppState {
|
|||
if let Some((controller, _)) = self.connected_controllers.get_mut(&msg.controller_uid) {
|
||||
controller.apply_relay_states(&msg.relay_states);
|
||||
}
|
||||
self.notify_relay_clients();
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<GetRelays> for AppState {
|
||||
type Result = Result<Vec<Relay>, EmgauwaError>;
|
||||
|
||||
fn handle(&mut self, _msg: GetRelays, _ctx: &mut Self::Context) -> Self::Result {
|
||||
self.get_relays()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,3 +178,11 @@ impl Handler<Action> for AppState {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<ConnectRelayClient> for AppState {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: ConnectRelayClient, _ctx: &mut Self::Context) -> Self::Result {
|
||||
self.connected_relay_clients.push(msg.addr);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,13 +9,13 @@ use sqlx::pool::PoolConnection;
|
|||
use sqlx::Sqlite;
|
||||
|
||||
use crate::app_state::{ConnectController, UpdateRelayStates};
|
||||
use crate::handlers::v1::ws::controllers::ControllerWs;
|
||||
use crate::handlers::v1::ws::controllers::ControllersWs;
|
||||
|
||||
impl ControllerWs {
|
||||
impl ControllersWs {
|
||||
pub fn handle_register(
|
||||
&mut self,
|
||||
conn: &mut PoolConnection<Sqlite>,
|
||||
ctx: &mut <ControllerWs as Actor>::Context,
|
||||
ctx: &mut <ControllersWs as Actor>::Context,
|
||||
controller: Controller,
|
||||
) -> Result<(), EmgauwaError> {
|
||||
log::info!(
|
||||
|
|
|
@ -16,14 +16,14 @@ use ws::Message;
|
|||
use crate::app_state::{AppState, DisconnectController};
|
||||
use crate::utils::flatten_result;
|
||||
|
||||
pub struct ControllerWs {
|
||||
pub struct ControllersWs {
|
||||
pub pool: Pool<Sqlite>,
|
||||
pub controller_uid: Option<ControllerUid>,
|
||||
pub app_state: Addr<AppState>,
|
||||
pub hb: Instant,
|
||||
}
|
||||
|
||||
impl Actor for ControllerWs {
|
||||
impl Actor for ControllersWs {
|
||||
type Context = ws::WebsocketContext<Self>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Self::Context) {
|
||||
|
@ -45,11 +45,11 @@ impl Actor for ControllerWs {
|
|||
}
|
||||
}
|
||||
|
||||
impl ControllerWs {
|
||||
impl ControllersWs {
|
||||
pub fn handle_action(
|
||||
&mut self,
|
||||
conn: &mut PoolConnection<Sqlite>,
|
||||
ctx: &mut <ControllerWs as Actor>::Context,
|
||||
ctx: &mut <ControllersWs as Actor>::Context,
|
||||
action: ControllerWsAction,
|
||||
) {
|
||||
let action_res = match action {
|
||||
|
@ -83,7 +83,7 @@ impl ControllerWs {
|
|||
}
|
||||
}
|
||||
|
||||
impl Handler<ControllerWsAction> for ControllerWs {
|
||||
impl Handler<ControllerWsAction> for ControllersWs {
|
||||
type Result = Result<(), EmgauwaError>;
|
||||
|
||||
fn handle(&mut self, action: ControllerWsAction, ctx: &mut Self::Context) -> Self::Result {
|
||||
|
@ -101,7 +101,7 @@ impl Handler<ControllerWsAction> for ControllerWs {
|
|||
}
|
||||
}
|
||||
|
||||
impl StreamHandler<Result<Message, ProtocolError>> for ControllerWs {
|
||||
impl StreamHandler<Result<Message, ProtocolError>> for ControllersWs {
|
||||
fn handle(&mut self, msg: Result<Message, ProtocolError>, ctx: &mut Self::Context) {
|
||||
let mut pool_conn = match block_on(self.pool.acquire()) {
|
||||
Ok(conn) => conn,
|
||||
|
|
|
@ -7,9 +7,11 @@ use emgauwa_lib::errors::EmgauwaError;
|
|||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
use crate::app_state::AppState;
|
||||
use crate::handlers::v1::ws::controllers::ControllerWs;
|
||||
use crate::handlers::v1::ws::controllers::ControllersWs;
|
||||
use crate::handlers::v1::ws::relays::RelaysWs;
|
||||
|
||||
pub mod controllers;
|
||||
pub mod relays;
|
||||
|
||||
#[get("/ws/controllers")]
|
||||
pub async fn ws_controllers(
|
||||
|
@ -19,7 +21,7 @@ pub async fn ws_controllers(
|
|||
stream: web::Payload,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
let resp = ws::start(
|
||||
ControllerWs {
|
||||
ControllersWs {
|
||||
pool: pool.get_ref().clone(),
|
||||
controller_uid: None,
|
||||
app_state: app_state.get_ref().clone(),
|
||||
|
@ -31,3 +33,21 @@ pub async fn ws_controllers(
|
|||
.map_err(|_| EmgauwaError::Internal(String::from("error starting websocket")));
|
||||
resp
|
||||
}
|
||||
|
||||
#[get("/ws/relays")]
|
||||
pub async fn ws_relays(
|
||||
app_state: web::Data<Addr<AppState>>,
|
||||
req: HttpRequest,
|
||||
stream: web::Payload,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
let resp = ws::start(
|
||||
RelaysWs {
|
||||
app_state: app_state.get_ref().clone(),
|
||||
hb: Instant::now(),
|
||||
},
|
||||
&req,
|
||||
stream,
|
||||
)
|
||||
.map_err(|_| EmgauwaError::Internal(String::from("error starting websocket")));
|
||||
resp
|
||||
}
|
||||
|
|
107
emgauwa-core/src/handlers/v1/ws/relays/mod.rs
Normal file
107
emgauwa-core/src/handlers/v1/ws/relays/mod.rs
Normal file
|
@ -0,0 +1,107 @@
|
|||
use std::time::Instant;
|
||||
|
||||
use actix::{Actor, ActorContext, Addr, AsyncContext, Handler, Message, StreamHandler};
|
||||
use actix_web_actors::ws;
|
||||
use actix_web_actors::ws::ProtocolError;
|
||||
use emgauwa_lib::constants::{HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT};
|
||||
use emgauwa_lib::errors::EmgauwaError;
|
||||
use futures::executor::block_on;
|
||||
|
||||
use crate::app_state::{AppState, ConnectRelayClient};
|
||||
|
||||
pub struct RelaysWs {
|
||||
pub app_state: Addr<AppState>,
|
||||
pub hb: Instant,
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "()")]
|
||||
pub struct SendRelays {
|
||||
pub relays_json: String,
|
||||
}
|
||||
|
||||
impl Actor for RelaysWs {
|
||||
type Context = ws::WebsocketContext<Self>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Self::Context) {
|
||||
// get unique id for ctx
|
||||
match self.get_relays_json() {
|
||||
Ok(relays_json) => {
|
||||
ctx.text(relays_json);
|
||||
self.hb(ctx);
|
||||
|
||||
block_on(self.app_state.send(ConnectRelayClient {
|
||||
addr: ctx.address(),
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Error getting relays: {:?}", err);
|
||||
ctx.stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RelaysWs {
|
||||
fn get_relays_json(&self) -> Result<String, EmgauwaError> {
|
||||
let relays = block_on(self.app_state.send(crate::app_state::GetRelays {}))??;
|
||||
serde_json::to_string(&relays).map_err(EmgauwaError::from)
|
||||
}
|
||||
|
||||
// helper method that sends ping to client every 5 seconds (HEARTBEAT_INTERVAL).
|
||||
fn hb(&self, ctx: &mut ws::WebsocketContext<Self>) {
|
||||
ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
|
||||
// check client heartbeats
|
||||
if Instant::now().duration_since(act.hb) > HEARTBEAT_TIMEOUT {
|
||||
log::debug!("Websocket Relay heartbeat failed, disconnecting!");
|
||||
ctx.stop();
|
||||
// don't try to send a ping
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.ping(&[]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamHandler<Result<ws::Message, ProtocolError>> for RelaysWs {
|
||||
fn handle(&mut self, msg: Result<ws::Message, ProtocolError>, ctx: &mut Self::Context) {
|
||||
let msg = match msg {
|
||||
Err(_) => {
|
||||
ctx.stop();
|
||||
return;
|
||||
}
|
||||
Ok(msg) => msg,
|
||||
};
|
||||
|
||||
match msg {
|
||||
ws::Message::Ping(msg) => {
|
||||
self.hb = Instant::now();
|
||||
ctx.pong(&msg)
|
||||
}
|
||||
ws::Message::Pong(_) => {
|
||||
self.hb = Instant::now();
|
||||
}
|
||||
ws::Message::Text(_) => log::debug!("Received unexpected text in relays ws"),
|
||||
ws::Message::Binary(_) => log::debug!("Received unexpected binary in relays ws"),
|
||||
ws::Message::Close(reason) => {
|
||||
ctx.close(reason);
|
||||
ctx.stop();
|
||||
}
|
||||
ws::Message::Continuation(_) => {
|
||||
ctx.stop();
|
||||
}
|
||||
ws::Message::Nop => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<SendRelays> for RelaysWs {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: SendRelays, ctx: &mut Self::Context) -> Self::Result {
|
||||
ctx.text(msg.relays_json);
|
||||
}
|
||||
}
|
|
@ -107,7 +107,8 @@ async fn main() -> Result<(), std::io::Error> {
|
|||
.service(handlers::v1::tags::show)
|
||||
.service(handlers::v1::tags::delete)
|
||||
.service(handlers::v1::tags::add)
|
||||
.service(handlers::v1::ws::ws_controllers),
|
||||
.service(handlers::v1::ws::ws_controllers)
|
||||
.service(handlers::v1::ws::ws_relays),
|
||||
)
|
||||
})
|
||||
.listen(listener)?
|
||||
|
|
Loading…
Reference in a new issue