Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
bdd65e740d | ||
|
0afbc1c508 | ||
|
3ecafd4534 | ||
|
1afa8fbe81 | ||
|
b4a4cdd1ac | ||
|
5930417ae4 | ||
|
4b6afda278 | ||
|
256fdeee47 | ||
|
c39f944b5a | ||
|
76fed4a4de | ||
|
3951b4e41c | ||
|
e4a1788698 |
14
README.md
14
README.md
@ -25,3 +25,17 @@ stored in folder **/client**
|
||||
go to **/client**
|
||||
* cargo build
|
||||
* cargo run
|
||||
|
||||
### Deamon
|
||||
Requires ''systemd''
|
||||
|
||||
Install by creating a link to this project
|
||||
```
|
||||
/etc/systemd/system# ln -s /home/c3ma/led-board/client/ledBoard.service ledBoard.service
|
||||
systemctl daemon-reload
|
||||
systemctl enable ledBoard.service
|
||||
```
|
||||
Start deamon with
|
||||
```
|
||||
systemctl start ledBoard.service
|
||||
```
|
||||
|
@ -22,3 +22,4 @@ serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
# end of web stuff
|
||||
ping = "0.4.1"
|
||||
paho-mqtt = "0.12.3"
|
||||
|
@ -1,10 +1,13 @@
|
||||
use std::{time::Duration, fmt::format};
|
||||
use std::sync::{RwLock, Mutex, Arc};
|
||||
use openssl::string;
|
||||
use paho_mqtt;
|
||||
use str;
|
||||
use bit::BitIndex;
|
||||
use chrono_tz::Europe::Berlin;
|
||||
use chrono::{DateTime, NaiveDateTime, Utc, Timelike};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use openweathermap::forecast::Weather;
|
||||
use openweathermap::{forecast::Weather, Receiver};
|
||||
use substring::Substring;
|
||||
use tinybmp::Bmp;
|
||||
use core::time;
|
||||
@ -20,7 +23,6 @@ use std::net::UdpSocket;
|
||||
use std::{env, thread};
|
||||
use std::io;
|
||||
use std::process::ExitCode;
|
||||
|
||||
use openweathermap::forecast::Forecast;
|
||||
use straba::NextDeparture;
|
||||
// This declaration will look for a file named `straba.rs` and will
|
||||
@ -243,6 +245,13 @@ fn render_clock(display: &mut UdpDisplay){
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn render_mqtt_message(display: &mut UdpDisplay, mqtt_message: String){
|
||||
let text_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
|
||||
Text::new(&mqtt_message, Point::new((1) as i32, 37), text_style)
|
||||
.draw(display)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn render_strab_partial(display: &mut UdpDisplay, station: &String, diff: i64, height: i32) {
|
||||
let text_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
|
||||
let mut diff_str = format!("{}min", (diff / 60));
|
||||
@ -276,13 +285,43 @@ fn render_strab_partial(display: &mut UdpDisplay, station: &String, diff: i64, h
|
||||
}
|
||||
|
||||
fn render_strab(display: &mut UdpDisplay, straba_res: &NextDeparture) {
|
||||
render_strab_partial(display, &straba_res.outbound_station, straba_res.outbound_diff, 15);
|
||||
render_strab_partial(display, &straba_res.inbound_station, straba_res.inbound_diff, 25);
|
||||
render_strab_partial(display, &straba_res.outbound_station, straba_res.outbound_diff, 17);
|
||||
render_strab_partial(display, &straba_res.inbound_station, straba_res.inbound_diff, 27);
|
||||
}
|
||||
|
||||
// The type we'll use to keep our dynamic list of topics inside the
|
||||
// MQTT client. Since we want to update it after creating the client,
|
||||
// we need to wrap the data in a lock, like a Mutex or RwLock.
|
||||
type UserTopics = RwLock<Vec<String>>;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Callback for a successful connection to the broker.
|
||||
// We subscribe to the topic(s) we want here.
|
||||
fn mqtt_on_connect_success(cli: &paho_mqtt::AsyncClient, _msgid: u16) {
|
||||
println!("MQTT | Connection succeeded");
|
||||
|
||||
// Subscribe to the desired topic(s).
|
||||
cli.subscribe("room/ledboard", paho_mqtt::QOS_0);
|
||||
}
|
||||
|
||||
// Callback for a failed attempt to connect to the server.
|
||||
// We simply sleep and then try again.
|
||||
//
|
||||
// Note that normally we don't want to do a blocking operation or sleep
|
||||
// from within a callback. But in this case, we know that the client is
|
||||
// *not* conected, and thus not doing anything important. So we don't worry
|
||||
// too much about stopping its callback thread.
|
||||
fn mqtt_on_connect_failure(cli: &paho_mqtt::AsyncClient, _msgid: u16, rc: i32) {
|
||||
println!("MQTT | Connection attempt failed with error code {}.\n", rc);
|
||||
thread::sleep(Duration::from_millis(2500));
|
||||
cli.reconnect_with_callbacks(mqtt_on_connect_success, mqtt_on_connect_failure);
|
||||
}
|
||||
|
||||
fn send_package(ipaddress: String,
|
||||
data: &Option<Result<Forecast, String>>,
|
||||
straba_res: &NextDeparture) {
|
||||
straba_res: &NextDeparture,
|
||||
mqtt_message: Option<String>) {
|
||||
let mut package: [u8; PACKAGE_LENGTH] = [0; PACKAGE_LENGTH];
|
||||
|
||||
// Brightness
|
||||
@ -300,6 +339,10 @@ fn send_package(ipaddress: String,
|
||||
render_strab(&mut display, straba_res);
|
||||
}
|
||||
|
||||
if mqtt_message.is_some() {
|
||||
render_mqtt_message(&mut display, mqtt_message.unwrap());
|
||||
}
|
||||
|
||||
render_clock(&mut display);
|
||||
|
||||
|
||||
@ -318,6 +361,8 @@ LEDboardClient <ip address>"
|
||||
);
|
||||
println!("one argument necessary!");
|
||||
println!("<ip address>");
|
||||
println!("second argument is optional:");
|
||||
println!("<ip of mqtt server>");
|
||||
}
|
||||
|
||||
fn check_connection(ipaddress: String) -> bool {
|
||||
@ -348,25 +393,81 @@ fn check_connection(ipaddress: String) -> bool {
|
||||
return device_online;
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
match args.len() {
|
||||
// no arguments passed
|
||||
1 => {
|
||||
// show a help message
|
||||
help();
|
||||
return ExitCode::SUCCESS;
|
||||
}
|
||||
// one argument passed
|
||||
2 => {
|
||||
let ip = &args[1];
|
||||
struct Message {
|
||||
string: Option<String>
|
||||
}
|
||||
|
||||
|
||||
let mut device_online = check_connection(ip.to_string());
|
||||
fn main_function(ipaddress: String, mqtt: Option<String>) -> ExitCode {
|
||||
let mut device_online = check_connection(ipaddress.clone());
|
||||
if !device_online {
|
||||
println!("{} not online", ip);
|
||||
println!("{:} not online", &ipaddress);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
let mut mqtt_client: Option<paho_mqtt::AsyncClient> = None;
|
||||
let mut mqtt_message: Arc<Mutex<Message>> = Arc::new(Mutex::new(Message{ string: None }));
|
||||
if mqtt.is_some() {
|
||||
let mqtt_ip: String = mqtt.clone().unwrap();
|
||||
// Define the set of options for the create.
|
||||
// Use an ID for a persistent session.
|
||||
let create_opts = paho_mqtt::CreateOptionsBuilder::new()
|
||||
.server_uri(mqtt_ip.clone())
|
||||
.client_id("ledboard")
|
||||
.finalize();
|
||||
// Create a client.
|
||||
let local_mqtt = paho_mqtt::AsyncClient::new(create_opts).unwrap();
|
||||
|
||||
println!("MQTT | Connecting to {:} MQTT server...", mqtt_ip);
|
||||
|
||||
// Define the set of options for the connection.
|
||||
let conn_opts = paho_mqtt::ConnectOptionsBuilder::new()
|
||||
.keep_alive_interval(Duration::from_secs(20))
|
||||
.clean_session(true)
|
||||
.finalize();
|
||||
|
||||
// Set a closure to be called whenever the client connection is established.
|
||||
local_mqtt.set_connected_callback(|_cli: &paho_mqtt::AsyncClient| {
|
||||
println!("Connected.");
|
||||
});
|
||||
|
||||
// Set a closure to be called whenever the client loses the connection.
|
||||
// It will attempt to reconnect, and set up function callbacks to keep
|
||||
// retrying until the connection is re-established.
|
||||
local_mqtt.set_connection_lost_callback(|cli: &paho_mqtt::AsyncClient| {
|
||||
println!("Connection lost. Attempting reconnect.");
|
||||
thread::sleep(Duration::from_millis(2500));
|
||||
cli.reconnect_with_callbacks(mqtt_on_connect_success, mqtt_on_connect_failure);
|
||||
});
|
||||
|
||||
// Attach a closure to the client to receive callback
|
||||
// on incoming messages.
|
||||
let mqtt_message_for_callback = mqtt_message.clone();
|
||||
local_mqtt.set_message_callback(move |cli, msg| {
|
||||
if let Some(msg) = msg {
|
||||
let topic = msg.topic();
|
||||
let payload_str = msg.payload_str();
|
||||
|
||||
//println!("MQTT | {} - {}", topic, payload_str);
|
||||
let mut lock = mqtt_message_for_callback.lock().unwrap();
|
||||
lock.string = Some(payload_str.to_string())
|
||||
}
|
||||
});
|
||||
|
||||
// Define the set of options for the connection
|
||||
let lwt = paho_mqtt::Message::new("room/ledboard/lwt", "lost connection", 1);
|
||||
|
||||
// The connect options. Defaults to an MQTT v3.x connection.
|
||||
let conn_opts = paho_mqtt::ConnectOptionsBuilder::new()
|
||||
.keep_alive_interval(Duration::from_secs(20))
|
||||
.will_message(lwt)
|
||||
.finalize();
|
||||
|
||||
// Make the connection to the broker
|
||||
println!("MQTT | Connecting to the MQTT server...");
|
||||
local_mqtt.connect_with_callbacks(conn_opts, mqtt_on_connect_success, mqtt_on_connect_failure);
|
||||
|
||||
// move local instance to global scope
|
||||
mqtt_client = Some(local_mqtt);
|
||||
}
|
||||
|
||||
let receiver = openweathermap::init_forecast("Mannheim",
|
||||
"metric",
|
||||
@ -382,7 +483,8 @@ fn main() -> ExitCode {
|
||||
println!("{:?} {:?}s", straba_res.inbound_station , straba_res.inbound_diff);
|
||||
|
||||
// Render start
|
||||
send_package(ip.to_string(), &last_data, &straba_res);
|
||||
send_package(ipaddress.clone(), &last_data, &straba_res, Some("MQTT: room/ledboard".to_string()));
|
||||
|
||||
loop {
|
||||
let st_now = SystemTime::now();
|
||||
let seconds = st_now.duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||
@ -402,7 +504,7 @@ fn main() -> ExitCode {
|
||||
}
|
||||
|
||||
if (straba_res.request_time + 50) < seconds as i64 {
|
||||
device_online = check_connection(ip.to_string());
|
||||
device_online = check_connection(ipaddress.clone());
|
||||
// request once a minute new data
|
||||
if device_online == true {
|
||||
straba_res = straba::fetch_data(None);
|
||||
@ -411,11 +513,42 @@ fn main() -> ExitCode {
|
||||
}
|
||||
}
|
||||
|
||||
let lock = mqtt_message.lock().unwrap();
|
||||
let mqtt_message: Option<String>;
|
||||
if lock.string.is_some() {
|
||||
mqtt_message = lock.string.clone();
|
||||
} else {
|
||||
mqtt_message = None;
|
||||
}
|
||||
|
||||
if device_online == true {
|
||||
// Render new image
|
||||
send_package(ip.to_string(), &last_data, &straba_res);
|
||||
send_package(ipaddress.clone(), &last_data, &straba_res, mqtt_message);
|
||||
}
|
||||
// Handle MQTT messages
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
match args.len() {
|
||||
// no arguments passed
|
||||
1 => {
|
||||
// show a help message
|
||||
help();
|
||||
return ExitCode::SUCCESS;
|
||||
}
|
||||
// one argument passed
|
||||
2 => {
|
||||
let ip = &args[1];
|
||||
return main_function(ip.to_string(), None);
|
||||
}
|
||||
// two argument passed
|
||||
3 => {
|
||||
let ip = &args[1];
|
||||
let mqtt = &args[2];
|
||||
return main_function(ip.to_string(), Some(mqtt.to_string()));
|
||||
}
|
||||
// all the other cases
|
||||
_ => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use chrono::DateTime;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use chrono::Local;
|
||||
use serde::Deserialize;
|
||||
|
||||
const STATION_URL:&str = "https://www.rnv-online.de/rest/departure/2494";
|
||||
@ -79,10 +79,11 @@ pub struct NextDeparture {
|
||||
}
|
||||
|
||||
pub fn fetch_data(debug_print : Option<bool>) -> NextDeparture {
|
||||
|
||||
let date = Local::now();
|
||||
let st_now = SystemTime::now();
|
||||
let seconds = st_now.duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||
let url = &format!("{}?datetime={}", STATION_URL, seconds);
|
||||
let timeString = date.format("%Y-%m-%d %H:%M:%S");
|
||||
let url = &format!("{}?datetime={}", STATION_URL, timeString);
|
||||
let result = reqwest::blocking::get(url);
|
||||
|
||||
let mut return_value = NextDeparture {
|
||||
@ -118,10 +119,24 @@ pub fn fetch_data(debug_print : Option<bool>) -> NextDeparture {
|
||||
return return_value;
|
||||
}
|
||||
|
||||
if debug_print.is_some() && debug_print.unwrap() == true {
|
||||
println!("----------- Seconds {:} {:} requesting ... -----------", seconds, timeString);
|
||||
}
|
||||
// parse JSON result.. search of both directions
|
||||
let json = body.unwrap();
|
||||
for el in json.graph_ql.response.journeys.elements {
|
||||
|
||||
if debug_print.is_some() && debug_print.unwrap() == true {
|
||||
println!("Requesting {:}", json.graph_ql.response.name);
|
||||
println!("Elements {:}", json.graph_ql.response.journeys.elements.len() );
|
||||
//println!("------------------------- %< ----------------------------");
|
||||
//println!("{}", &raw_text);
|
||||
//println!("------------------------- %< ----------------------------");
|
||||
}
|
||||
|
||||
for el in json.graph_ql.response.journeys.elements {
|
||||
|
||||
if debug_print.is_some() && debug_print.unwrap() == true {
|
||||
println!("Requesting {:}", json.graph_ql.response.name);
|
||||
println!("Line {:}", el.line.line_group.label);
|
||||
}
|
||||
for stop in el.stops {
|
||||
@ -140,6 +155,8 @@ pub fn fetch_data(debug_print : Option<bool>) -> NextDeparture {
|
||||
if diff < return_value.outbound_diff {
|
||||
return_value.outbound_station = stop.destination_label;
|
||||
return_value.outbound_diff = diff;
|
||||
} else if debug_print.is_some() && debug_print.unwrap() == true {
|
||||
println!("Unkown diff Stop {:} {:} (in {:} seconds)", stop.destination_label, txt_departure, diff );
|
||||
}
|
||||
} else if stop.destination_label.contains("Hochschule") ||
|
||||
stop.destination_label.contains("Hauptbahnhof") ||
|
||||
@ -147,13 +164,19 @@ pub fn fetch_data(debug_print : Option<bool>) -> NextDeparture {
|
||||
if diff < return_value.inbound_diff {
|
||||
return_value.inbound_station = stop.destination_label;
|
||||
return_value.inbound_diff = diff;
|
||||
} else if debug_print.is_some() && debug_print.unwrap() == true {
|
||||
println!("Unkown diff Stop {:} {:} (in {:} seconds)", stop.destination_label, txt_departure, diff );
|
||||
}
|
||||
} else if debug_print.is_some() && debug_print.unwrap() == true {
|
||||
println!("Unkown Stop {:} {:} (in {:} seconds)", stop.destination_label, txt_departure, diff );
|
||||
}
|
||||
} else {
|
||||
println!("Planned {:} {:?}", stop.destination_label, stop.planned_departure.iso_string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if debug_print.is_some() && debug_print.unwrap() == true {
|
||||
println!("----------- end of straba.rs -----------");
|
||||
}
|
||||
return_value
|
||||
}
|
||||
|
17
client/ledBoard.service
Normal file
17
client/ledBoard.service
Normal file
@ -0,0 +1,17 @@
|
||||
[Unit]
|
||||
Description=Log uptime in scoreboard
|
||||
DefaultDependencies=no
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
User=c3ma
|
||||
|
||||
# Specify users home as working directory
|
||||
WorkingDirectory=/home/c3ma/
|
||||
# Define wrapper to update and start project
|
||||
ExecStart=/usr/bin/bash <project home>/client/ledboard.sh
|
||||
TimeoutStartSec=0
|
||||
|
||||
[Install]
|
||||
WantedBy=network.target
|
12
client/ledboard.sh
Executable file
12
client/ledboard.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# Wrapper script to update project and build project
|
||||
#
|
||||
#Set target IP address
|
||||
IP=
|
||||
# Path to this project
|
||||
HOSTCLIENT=
|
||||
cd $HOSTCLIENT
|
||||
/usr/bin/pkill LEDboardClient
|
||||
git pull
|
||||
cargo build
|
||||
$HOSTCLIENT/target/debug/LEDboardClient $IP
|
Loading…
x
Reference in New Issue
Block a user