Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
clonejo
mqttnc
Commits
c164254a
Commit
c164254a
authored
Jun 28, 2016
by
clonejo
Browse files
v0.2: highlight outdated topics in red using _interval subtopic
parent
af707723
Changes
3
Hide whitespace changes
Inline
Side-by-side
CHANGELOG
View file @
c164254a
v0.2
====
* mqttnc recognizes a topics publishing interval in the subtopic `_interval`
(in seconds). If that interval has passed for a topic, it is highlighted in
red.
* It is recommended to publish `_interval` messages with the retain flag set.
* Highlighting also works for topics which are not retained (as long as a
valid `_interval` message has been received).
* Leading and trailing whitespace is removed from payloads.
v0.1
====
(initial release)
* Ncurses+Rust based MQTT viewer. Shows topics in a tree.
* Grays out stale (retained) values.
* Update screen on new messages (erratic age counters).
\ No newline at end of file
* Update screen on new messages (erratic age counters).
Cargo.toml
View file @
c164254a
[package]
name
=
"mqttnc"
version
=
"0.
1
.0"
version
=
"0.
2
.0"
authors
=
[
"clonejo <clonejo@shakik.de>"
]
[dependencies]
...
...
src/main.rs
View file @
c164254a
...
...
@@ -9,7 +9,7 @@ extern crate netopt;
use
std
::
collections
::
BTreeMap
;
use
chrono
::{
DateTime
,
Local
};
use
chrono
::{
DateTime
,
Local
,
Duration
};
fn
main
()
{
use
clap
::{
App
,
Arg
};
...
...
@@ -44,32 +44,45 @@ fn main() {
}
enum
Age
{
Retained
,
Retained
(
DateTime
<
Local
>
)
,
Fresh
(
DateTime
<
Local
>
)
}
impl
Age
{
fn
is_retained
(
&
self
)
->
bool
{
match
*
self
{
Age
::
Retained
=>
true
,
Age
::
Retained
(
_
)
=>
true
,
Age
::
Fresh
(
_
)
=>
false
}
}
fn
get_minimum_age
(
&
self
)
->
DateTime
<
Local
>
{
match
*
self
{
Age
::
Retained
(
age
)
=>
age
,
Age
::
Fresh
(
age
)
=>
age
}
}
fn
format
(
&
self
,
now
:
DateTime
<
Local
>
)
->
String
{
match
*
self
{
Age
::
Fresh
(
t
)
=>
{
let
d
=
now
-
t
;
if
d
.num_minutes
()
==
0
{
format!
(
"{}s"
,
d
.num_seconds
())
}
else
if
d
.num_hours
()
==
0
{
let
seconds
=
d
.num_seconds
()
%
60
;
format!
(
"{}m{:0>2}s"
,
d
.num_minutes
(),
seconds
)
let
all_seconds
=
d
.num_seconds
();
let
seconds
=
all_seconds
%
60
;
let
all_minutes
=
all_seconds
/
60
;
let
minutes
=
all_minutes
%
60
;
let
hours
=
all_minutes
/
60
;
if
hours
>
0
{
format!
(
"{}h{:0>2}m"
,
hours
,
minutes
)
}
else
if
minutes
>
0
{
format!
(
"{}m{:0>2}s"
,
minutes
,
seconds
)
}
else
if
seconds
>=
0
{
format!
(
"{}s"
,
seconds
)
}
else
{
""
.to_owned
(
)
format!
(
"weird: {:?}"
,
d
)
}
},
Age
::
Retained
=>
{
Age
::
Retained
(
_
)
=>
{
"r"
.to_owned
()
}
}
...
...
@@ -79,14 +92,16 @@ impl Age {
struct
Topic
{
children
:
BTreeMap
<
String
,
Topic
>
,
last_msg
:
Option
<
(
String
,
Age
)
>
last_msg
:
Option
<
(
String
,
Age
)
>
,
interval
:
Option
<
Duration
>
}
impl
Topic
{
fn
new
()
->
Topic
{
Topic
{
children
:
BTreeMap
::
new
(),
last_msg
:
None
last_msg
:
None
,
interval
:
None
}
}
...
...
@@ -100,12 +115,23 @@ impl Topic {
}
else
{
None
};
let
c
=
self
.children
.entry
(
root_path
.to_owned
())
.or_insert_with
(||
Topic
::
new
());
c
.update
(
rest_path
,
payload
,
retained
);
if
rest_path
==
None
&&
root_path
==
"_interval"
{
match
payload
.parse
::
<
i64
>
()
{
Ok
(
interval
)
=>
{
self
.interval
=
Some
(
Duration
::
seconds
(
interval
));
},
Err
(
_
)
=>
{
self
.interval
=
None
;
}
}
}
else
{
let
c
=
self
.children
.entry
(
root_path
.to_owned
())
.or_insert_with
(||
Topic
::
new
());
c
.update
(
rest_path
,
payload
,
retained
);
}
},
None
=>
{
let
age
=
if
retained
{
Age
::
Retained
Age
::
Retained
(
Local
::
now
())
}
else
{
Age
::
Fresh
(
Local
::
now
())
};
...
...
@@ -114,35 +140,70 @@ impl Topic {
}
}
fn
print
(
&
self
,
now
:
DateTime
<
Local
>
)
{
fn
print
(
&
self
,
now
:
DateTime
<
Local
>
,
start_time
:
DateTime
<
Local
>
)
{
for
(
ref
topic
,
ref
child
)
in
self
.children
.iter
()
{
child
.print_
(
0
,
topic
,
now
);
child
.print_
(
0
,
topic
,
now
,
start_time
);
}
}
fn
print_
(
&
self
,
indent_level
:
usize
,
topic
:
&
str
,
now
:
DateTime
<
Local
>
)
{
fn
print_
(
&
self
,
indent_level
:
usize
,
topic
:
&
str
,
now
:
DateTime
<
Local
>
,
start_time
:
DateTime
<
Local
>
)
{
let
custom_text
;
if
self
.last_msg
.is_none
()
&&
self
.children
.len
()
==
1
{
let
(
ref
ctopic
,
ref
child
)
=
self
.children
.iter
()
.next
()
.unwrap
();
let
passed_topic
=
format!
(
"{}/{}"
,
topic
,
ctopic
);
child
.print_
(
indent_level
,
&
passed_topic
,
now
);
child
.print_
(
indent_level
,
&
passed_topic
,
now
,
start_time
);
}
else
{
ncurses
::
printw
(
&*
format!
(
"{0:1$}{2:<20}{0:3$}"
,
""
,
2
*
indent_level
,
topic
,
2
*
(
8
-
indent_level
)));
match
self
.last_msg
{
let
(
mut
color
,
age_str
,
payload
)
=
match
self
.last_msg
{
Some
((
ref
p
,
ref
t
))
=>
{
if
t
.is_retained
()
{
ncurses
::
attron
(
ncurses
::
COLOR_PAIR
(
1
));
let
color
=
if
t
.is_retained
()
{
Some
(
ncurses
::
COLOR_PAIR
(
1
))
}
else
{
None
};
let
age
=
Some
(
t
.format
(
now
));
(
color
,
age
,
Some
(
p
))
},
None
=>
{
if
self
.interval
.is_some
()
{
custom_text
=
"[interval known]"
.to_owned
();
(
Some
(
ncurses
::
COLOR_PAIR
(
1
)),
None
,
Some
(
&
custom_text
))
}
else
{
(
None
,
None
,
None
)
}
ncurses
::
printw
(
&*
format!
(
" {:>10} {}
\n
"
,
t
.format
(
now
),
p
));
if
t
.is_retained
()
{
ncurses
::
attroff
(
ncurses
::
COLOR_PAIR
(
1
));
}
};
// red text if we are sure it's stale
match
(
&
self
.interval
,
&
self
.last_msg
)
{
(
&
Some
(
interval
),
&
Some
((
_
,
ref
age
)))
=>
{
let
received_at
=
age
.get_minimum_age
();
if
received_at
+
interval
<
now
{
color
=
Some
(
ncurses
::
COLOR_PAIR
(
2
));
}
},
None
=>
{
ncurses
::
printw
(
"
\n
"
);
(
&
Some
(
interval
),
&
None
)
=>
{
if
start_time
+
interval
<
now
{
color
=
Some
(
ncurses
::
COLOR_PAIR
(
2
));
}
}
_
=>
{}
}
color
.map
(|
cp
|
{
ncurses
::
attron
(
cp
);
});
let
age_str_
=
match
age_str
{
Some
(
s
)
=>
s
,
None
=>
""
.to_owned
()
};
ncurses
::
printw
(
&*
format!
(
" {:>10} {}
\n
"
,
age_str_
,
payload
.unwrap_or
(
&
""
.to_owned
())));
color
.map
(|
cp
|
{
ncurses
::
attroff
(
cp
);
});
for
(
ref
topic
,
ref
child
)
in
self
.children
.iter
()
{
child
.print_
(
indent_level
+
1
,
topic
,
now
);
child
.print_
(
indent_level
+
1
,
topic
,
now
,
start_time
);
}
}
}
...
...
@@ -165,18 +226,20 @@ fn main_(server_str: String) -> Result<(), (i32, &'static str)> {
};
client
.subscribe
(
"#"
)
.unwrap
();
println!
(
"connected to mqtt server"
);
let
start_time
=
Local
::
now
();
let
mut
root_topic
=
Topic
::
new
();
ncurses
::
initscr
();
ncurses
::
start_color
();
ncurses
::
init_pair
(
1
,
8
,
ncurses
::
COLOR_BLACK
);
ncurses
::
init_pair
(
2
,
ncurses
::
COLOR_RED
,
ncurses
::
COLOR_BLACK
);
//ncurses::nonl();
loop
{
match
client
.await
()
.unwrap
()
{
Some
(
msg
)
=>
{
let
payload
=
str
::
from_utf8
(
&**
msg
.payload
)
.unwrap
()
.to_owned
();
let
payload
=
str
::
from_utf8
(
&**
msg
.payload
)
.unwrap
()
.
trim
()
.
to_owned
();
root_topic
.update
(
Some
(
&
msg
.topic.path
),
payload
,
msg
.retain
);
ncurses
::
mv
(
0
,
0
);
root_topic
.print
(
Local
::
now
());
root_topic
.print
(
Local
::
now
()
,
start_time
);
ncurses
::
clrtoeol
();
ncurses
::
refresh
();
},
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment