Skip to content

Commit 5431b53

Browse files
authored
Add a log tab to the http server (#31)
1 parent 4971aa5 commit 5431b53

File tree

7 files changed

+144
-28
lines changed

7 files changed

+144
-28
lines changed

.idea/misc.xml

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## Next
22
- Add HTTP server running on port 3000. You can toggle stuff on it!
3+
- Crash when the message queue is full, in the hopes that the supervisor restarts us. rumqttc seems to have issues reconnecting sometimes.
4+
- Broadcast discovery info every time we connect to the mqtt server.
35

46
## 0.1.5
57
- Fix STRING types in the JSON mqtt set endpoint as well.

src/controller.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::error::Error;
44

55
use crate::utils::Numberish;
66
use regex::Regex;
7-
use serde::{Deserialize, Deserializer, Serialize, Serializer};
7+
use serde::{Serialize, Serializer};
88
use simple_error::{bail, simple_error};
99
use slog::debug;
1010
use slog_scope;
@@ -179,9 +179,9 @@ pub trait DeviceController: Send + Sync {
179179
pub struct AprontestController {
180180
runner: Box<
181181
dyn for<'a> Fn(
182-
&'a [&str],
183-
)
184-
-> Pin<Box<dyn Future<Output = Result<String, Box<dyn Error>>> + 'a + Send>>
182+
&'a [&str],
183+
)
184+
-> Pin<Box<dyn Future<Output = Result<String, Box<dyn Error>>> + 'a + Send>>
185185
+ Send
186186
+ Sync,
187187
>,

src/http.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::config::Config;
22
use crate::controller::{AttributeId, DeviceController, DeviceId};
3+
use crate::syncer::DeviceSyncer;
34
use crate::utils::{Numberish, ResultExtensions};
45
use hyper::service::{make_service_fn, service_fn};
56
use hyper::{Body, Method, Request, Response, Server};
@@ -19,6 +20,7 @@ pub struct HttpServer {
1920
config: Config,
2021
controller: Arc<dyn DeviceController>,
2122
shutdown_signal: Sender<()>,
23+
syncer: Option<Arc<DeviceSyncer>>,
2224
}
2325

2426
#[derive(RustEmbed)]
@@ -31,12 +33,17 @@ lazy_static! {
3133
}
3234

3335
impl HttpServer {
34-
pub fn new(config: &Config, controller: Arc<dyn DeviceController>) -> Arc<HttpServer> {
36+
pub fn new(
37+
config: &Config,
38+
controller: Arc<dyn DeviceController>,
39+
syncer: Option<Arc<DeviceSyncer>>,
40+
) -> Arc<HttpServer> {
3541
let (tx, rx) = tokio::sync::oneshot::channel::<()>();
3642

3743
let this = Arc::new(HttpServer {
3844
config: config.clone(),
3945
controller,
46+
syncer,
4047
shutdown_signal: tx,
4148
});
4249

@@ -120,6 +127,10 @@ impl HttpServer {
120127
error!(slog_scope::logger(), "device_list_failed"; "error" => ?e);
121128
Ok(Self::json_error_response(&e))
122129
}),
130+
(&Method::GET, "/api/events") => self.last_messages().await.or_else(|e| {
131+
error!(slog_scope::logger(), "last_messages_failed"; "error" => ?e);
132+
Ok(Self::json_error_response(&e))
133+
}),
123134
(&Method::POST, path) if SET_DEVICE_ATTRIBUTE_REGEX.is_match(path) => {
124135
return self.set_attribute(request).await.or_else(|e| {
125136
error!(slog_scope::logger(), "set_attribute_failed"; "error" => ?e);
@@ -145,6 +156,23 @@ impl HttpServer {
145156
}
146157
}
147158

159+
async fn last_messages(self: Arc<Self>) -> Result<Response<Body>, Box<dyn Error>> {
160+
let result: Vec<_> = {
161+
let lock = self
162+
.syncer
163+
.as_ref()
164+
.ok_or_else(|| simple_error!("No MQTT syncer!"))?
165+
.last_n_messages
166+
.lock()
167+
.await;
168+
(*lock).iter().cloned().collect()
169+
};
170+
Ok(Self::json_response(
171+
200,
172+
serde_json::json!({ "events": result }),
173+
))
174+
}
175+
148176
async fn run_command_output(
149177
self: Arc<Self>,
150178
mut command: Command,

src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,13 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
184184
let controller = controller::FakeController::new();
185185
let controller = Arc::new(controller);
186186

187-
let _syncer = if config.has_mqtt() {
187+
let syncer = if config.has_mqtt() {
188188
Some(syncer::DeviceSyncer::new(&config, controller.clone()))
189189
} else {
190190
None
191191
};
192192
let _http = if http_port.is_some() {
193-
Some(HttpServer::new(&config, controller.clone()))
193+
Some(HttpServer::new(&config, controller.clone(), syncer))
194194
} else {
195195
None
196196
};

src/syncer.rs

Lines changed: 91 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,61 @@ use crate::converter::device_to_discovery_payload;
44
use crate::utils::ResultExtensions;
55
use async_channel::{bounded, Receiver, Sender};
66
use rumqttc::{Event, EventLoop, Incoming, Publish, Request, Subscribe};
7+
use serde::{Serialize, Serializer};
78
use serde_json::value::Value::Object;
89
use simple_error::{bail, simple_error};
9-
use slog::{debug, error, info, trace, warn};
10+
use slog::{crit, debug, error, info, trace, warn};
1011
use slog_scope;
11-
use std::collections::HashMap;
12+
use std::collections::{HashMap, VecDeque};
1213
use std::error::Error;
14+
use std::ops::Deref;
1315
use std::sync::Arc;
16+
use tokio::sync::Mutex;
1417
use tokio::time::Duration;
1518

19+
#[derive(Clone, Debug, Eq, PartialEq)]
20+
pub struct MaybeJsonString {
21+
pub byte_contents: Vec<u8>,
22+
}
23+
24+
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
25+
pub enum LoggedMessage {
26+
OutgoingMessage(String, MaybeJsonString),
27+
IncomingMessage(String, MaybeJsonString),
28+
Connected,
29+
Disconnected,
30+
}
31+
32+
impl MaybeJsonString {
33+
pub fn new<P: Clone + Into<Vec<u8>>>(bytes: &P) -> MaybeJsonString {
34+
MaybeJsonString {
35+
byte_contents: bytes.clone().into(),
36+
}
37+
}
38+
}
39+
40+
impl Serialize for MaybeJsonString {
41+
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
42+
where
43+
S: Serializer,
44+
{
45+
let str = match std::str::from_utf8(&self.byte_contents) {
46+
Ok(v) => v,
47+
Err(_) => return serializer.serialize_bytes(&self.byte_contents),
48+
};
49+
match serde_json::from_str(str) {
50+
Ok(Object(m)) => m.serialize(serializer),
51+
_ => serializer.serialize_str(str),
52+
}
53+
}
54+
}
55+
1656
pub struct DeviceSyncer {
1757
config: Config,
1858
controller: Arc<dyn DeviceController>,
1959
sender: Sender<Request>,
2060
repoll: Sender<DeviceId>,
61+
pub last_n_messages: Mutex<VecDeque<LoggedMessage>>,
2162
}
2263

2364
impl<'a> DeviceSyncer {
@@ -32,6 +73,7 @@ impl<'a> DeviceSyncer {
3273
controller,
3374
sender: ev.handle(),
3475
repoll: repoll_sender,
76+
last_n_messages: Mutex::new(VecDeque::with_capacity(10)),
3577
};
3678
let this = Arc::new(syncer);
3779
trace!(slog_scope::logger(), "start_thread");
@@ -48,14 +90,16 @@ impl<'a> DeviceSyncer {
4890
.await
4991
}
5092
});
93+
this
94+
}
5195

52-
if this.config.discovery_topic_prefix.is_some() {
96+
async fn start_broadcast_discovery_broadcast(self: Arc<Self>) {
97+
if self.config.discovery_topic_prefix.is_some() {
5398
tokio::task::spawn({
54-
let this = this.clone();
99+
let this = self.clone();
55100
async move { this.broadcast_discovery().await }
56101
});
57102
}
58-
this
59103
}
60104

61105
async fn do_subscribe(&self) -> Result<(), Box<dyn Error>> {
@@ -212,6 +256,14 @@ impl<'a> DeviceSyncer {
212256
Ok(())
213257
}
214258

259+
async fn log_message(self: Arc<Self>, message: LoggedMessage) {
260+
let mut msgs = self.last_n_messages.lock().await;
261+
if msgs.len() == 10 {
262+
msgs.pop_front();
263+
};
264+
msgs.push_back(message)
265+
}
266+
215267
async fn loop_once(self: Arc<Self>, ev: &mut EventLoop) -> Result<(), Box<dyn Error>> {
216268
let message = match ev.poll().await? {
217269
Event::Incoming(i) => i,
@@ -223,10 +275,18 @@ impl<'a> DeviceSyncer {
223275
return match message {
224276
Incoming::Connect(_) => Ok(()),
225277
Incoming::ConnAck(_) => {
226-
self.do_subscribe().await?;
278+
self.clone().log_message(LoggedMessage::Connected).await;
279+
self.clone().do_subscribe().await?;
280+
self.start_broadcast_discovery_broadcast().await;
227281
Ok(())
228282
}
229283
Incoming::Publish(message) => {
284+
self.clone()
285+
.log_message(LoggedMessage::IncomingMessage(
286+
message.topic.clone(),
287+
MaybeJsonString::new(&message.payload.deref()),
288+
))
289+
.await;
230290
let this = self.clone();
231291
tokio::task::spawn(async move {
232292
this.process_one(message)
@@ -249,7 +309,10 @@ impl<'a> DeviceSyncer {
249309
Incoming::UnsubAck(_) => bail!("Unexpected unsuback!"),
250310
Incoming::PingReq => Ok(()),
251311
Incoming::PingResp => Ok(()),
252-
Incoming::Disconnect => Ok(()),
312+
Incoming::Disconnect => {
313+
self.clone().log_message(LoggedMessage::Disconnected).await;
314+
Ok(())
315+
}
253316
};
254317
}
255318

@@ -271,7 +334,7 @@ impl<'a> DeviceSyncer {
271334
}
272335
}
273336

274-
async fn poll_device_(&self, device_id: DeviceId) -> Result<(), Box<dyn Error>> {
337+
async fn poll_device_(self: Arc<Self>, device_id: DeviceId) -> Result<(), Box<dyn Error>> {
275338
let device_info = { self.controller.describe(device_id).await? };
276339
let attributes = device_info
277340
.attributes
@@ -287,17 +350,24 @@ impl<'a> DeviceSyncer {
287350
let payload = serde_json::Value::Object(attributes).to_string();
288351
trace!(slog_scope::logger(), "poll_device_status"; "device_id" => device_id, "payload" => &payload);
289352

290-
let mut publish = Publish::new(
291-
self.config
292-
.to_topic_string(&TopicType::StatusTopic(device_id))
293-
.unwrap(),
294-
rumqttc::QoS::AtLeastOnce,
295-
payload,
296-
);
353+
let topic = self
354+
.config
355+
.to_topic_string(&TopicType::StatusTopic(device_id))
356+
.unwrap();
357+
let logged_message =
358+
LoggedMessage::OutgoingMessage(topic.clone(), MaybeJsonString::new(&payload));
359+
let mut publish = Publish::new(topic, rumqttc::QoS::AtLeastOnce, payload);
297360
publish.retain = true;
298-
self.sender.try_send(Request::Publish(publish))?;
299-
300-
Ok(())
361+
match self.sender.try_send(Request::Publish(publish)) {
362+
Ok(_) => {
363+
self.log_message(logged_message).await;
364+
Ok(())
365+
}
366+
Err(e) => {
367+
crit!(slog_scope::logger(), "sending_failed_crashing_to_maybe_reconnect"; "error" => ?e);
368+
panic!(e)
369+
}
370+
}
301371
}
302372

303373
async fn poll_device(self: Arc<Self>, device_id: DeviceId) -> () {
@@ -362,13 +432,16 @@ impl<'a> DeviceSyncer {
362432
let config = v.discovery_info.to_string();
363433
info!(slog_scope::logger(), "discovered_device"; "id" => id, "name" => &device.name);
364434
debug!(slog_scope::logger(), "broadcast_discovery_result"; "id" => id, "topic" => &topic, "config" => &config);
435+
let log_message =
436+
LoggedMessage::OutgoingMessage(topic.clone(), MaybeJsonString::new(&config));
365437
self.sender
366438
.send(Request::Publish(Publish::new(
367439
topic,
368440
rumqttc::QoS::AtLeastOnce,
369441
config,
370442
)))
371443
.await?;
444+
self.log_message(log_message).await;
372445
Ok(())
373446
}
374447
None => {

src/web/index.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const Nav = (props) => {
6060
<ul className="navbar-nav">
6161
<NavLink id="home" name="Home" {...props} />
6262
<NavLink id="add" name="Add Device" {...props} />
63+
<NavLink id="mqtt" name="MQTT Log" {...props} />
6364
<NavLink id="aprontest" name="aprontest output" {...props} />
6465
</ul>
6566
</div>
@@ -216,6 +217,20 @@ const DeviceDetails = ({device, changeName, setAttribute}) => {
216217
</div>
217218
}
218219

220+
const MqttLog = () => {
221+
const [events, setEvents] = React.useState(["Loading"]);
222+
223+
React.useEffect(() => {
224+
api('/api/events').then(l => setEvents(l.events.reverse()));
225+
}, []);
226+
227+
return <div>
228+
{events.map((e) => {
229+
return <reactJsonView.default name="event" sortKeys={true} src={e} />
230+
})}
231+
</div>;
232+
}
233+
219234
const HomePage = ({device, setDevice}) => {
220235
const [deviceRefresh, setDeviceRefresh] = React.useState(0);
221236
const [devicesList, setDevicesList] = React.useState(null);
@@ -338,6 +353,7 @@ const Root = () => {
338353
<div className="p-4">
339354
{active === 'home' ? <HomePage device={device} setDevice={setDevice} /> : null}
340355
{active === 'add' ? <AddDevice /> : null}
356+
{active === 'mqtt' ? <MqttLog /> : null}
341357
{active === 'aprontest' ? <RawApronTest /> : null}
342358
</div>
343359
<ErrorToast message={error} onDismiss={() => setError(null)} />

0 commit comments

Comments
 (0)