Compare commits

..

No commits in common. "ollo-dev" and "master" have entirely different histories.

6 changed files with 66 additions and 266 deletions

View File

@ -25,17 +25,3 @@ stored in folder **/client**
go to **/client** go to **/client**
* cargo build * cargo build
* cargo run * 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
```

View File

@ -22,4 +22,3 @@ serde_derive = "1.0"
serde_json = "1.0" serde_json = "1.0"
# end of web stuff # end of web stuff
ping = "0.4.1" ping = "0.4.1"
paho-mqtt = "0.12.3"

View File

@ -1,13 +1,10 @@
use std::{time::Duration, fmt::format}; use std::{time::Duration, fmt::format};
use std::sync::{RwLock, Mutex, Arc};
use openssl::string;
use paho_mqtt;
use str; use str;
use bit::BitIndex; use bit::BitIndex;
use chrono_tz::Europe::Berlin; use chrono_tz::Europe::Berlin;
use chrono::{DateTime, NaiveDateTime, Utc, Timelike}; use chrono::{DateTime, NaiveDateTime, Utc, Timelike};
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use openweathermap::{forecast::Weather, Receiver}; use openweathermap::forecast::Weather;
use substring::Substring; use substring::Substring;
use tinybmp::Bmp; use tinybmp::Bmp;
use core::time; use core::time;
@ -23,6 +20,7 @@ use std::net::UdpSocket;
use std::{env, thread}; use std::{env, thread};
use std::io; use std::io;
use std::process::ExitCode; use std::process::ExitCode;
use openweathermap::forecast::Forecast; use openweathermap::forecast::Forecast;
use straba::NextDeparture; use straba::NextDeparture;
// This declaration will look for a file named `straba.rs` and will // This declaration will look for a file named `straba.rs` and will
@ -245,13 +243,6 @@ fn render_clock(display: &mut UdpDisplay){
.unwrap(); .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) { fn render_strab_partial(display: &mut UdpDisplay, station: &String, diff: i64, height: i32) {
let text_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On); let text_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
let mut diff_str = format!("{}min", (diff / 60)); let mut diff_str = format!("{}min", (diff / 60));
@ -285,43 +276,13 @@ fn render_strab_partial(display: &mut UdpDisplay, station: &String, diff: i64, h
} }
fn render_strab(display: &mut UdpDisplay, straba_res: &NextDeparture) { fn render_strab(display: &mut UdpDisplay, straba_res: &NextDeparture) {
render_strab_partial(display, &straba_res.outbound_station, straba_res.outbound_diff, 17); 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, 27); render_strab_partial(display, &straba_res.inbound_station, straba_res.inbound_diff, 25);
}
// 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, fn send_package(ipaddress: String,
data: &Option<Result<Forecast, 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]; let mut package: [u8; PACKAGE_LENGTH] = [0; PACKAGE_LENGTH];
// Brightness // Brightness
@ -339,10 +300,6 @@ fn send_package(ipaddress: String,
render_strab(&mut display, straba_res); render_strab(&mut display, straba_res);
} }
if mqtt_message.is_some() {
render_mqtt_message(&mut display, mqtt_message.unwrap());
}
render_clock(&mut display); render_clock(&mut display);
@ -361,8 +318,6 @@ LEDboardClient <ip address>"
); );
println!("one argument necessary!"); println!("one argument necessary!");
println!("<ip address>"); println!("<ip address>");
println!("second argument is optional:");
println!("<ip of mqtt server>");
} }
fn check_connection(ipaddress: String) -> bool { fn check_connection(ipaddress: String) -> bool {
@ -393,81 +348,25 @@ fn check_connection(ipaddress: String) -> bool {
return device_online; return device_online;
} }
struct Message { fn main() -> ExitCode {
string: Option<String> 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];
fn main_function(ipaddress: String, mqtt: Option<String>) -> ExitCode {
let mut device_online = check_connection(ipaddress.clone()); let mut device_online = check_connection(ip.to_string());
if !device_online { if !device_online {
println!("{:} not online", &ipaddress); println!("{} not online", ip);
return ExitCode::FAILURE; 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", let receiver = openweathermap::init_forecast("Mannheim",
"metric", "metric",
@ -483,8 +382,7 @@ fn main_function(ipaddress: String, mqtt: Option<String>) -> ExitCode {
println!("{:?} {:?}s", straba_res.inbound_station , straba_res.inbound_diff); println!("{:?} {:?}s", straba_res.inbound_station , straba_res.inbound_diff);
// Render start // Render start
send_package(ipaddress.clone(), &last_data, &straba_res, Some("MQTT: room/ledboard".to_string())); send_package(ip.to_string(), &last_data, &straba_res);
loop { loop {
let st_now = SystemTime::now(); let st_now = SystemTime::now();
let seconds = st_now.duration_since(UNIX_EPOCH).unwrap().as_secs(); let seconds = st_now.duration_since(UNIX_EPOCH).unwrap().as_secs();
@ -504,7 +402,7 @@ fn main_function(ipaddress: String, mqtt: Option<String>) -> ExitCode {
} }
if (straba_res.request_time + 50) < seconds as i64 { if (straba_res.request_time + 50) < seconds as i64 {
device_online = check_connection(ipaddress.clone()); device_online = check_connection(ip.to_string());
// request once a minute new data // request once a minute new data
if device_online == true { if device_online == true {
straba_res = straba::fetch_data(None); straba_res = straba::fetch_data(None);
@ -513,42 +411,11 @@ fn main_function(ipaddress: String, mqtt: Option<String>) -> 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 { if device_online == true {
// Render new image // Render new image
send_package(ipaddress.clone(), &last_data, &straba_res, mqtt_message); send_package(ip.to_string(), &last_data, &straba_res);
}
// 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 // all the other cases
_ => { _ => {

View File

@ -1,6 +1,6 @@
use chrono::DateTime; use chrono::DateTime;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use chrono::Local;
use serde::Deserialize; use serde::Deserialize;
const STATION_URL:&str = "https://www.rnv-online.de/rest/departure/2494"; const STATION_URL:&str = "https://www.rnv-online.de/rest/departure/2494";
@ -79,11 +79,10 @@ pub struct NextDeparture {
} }
pub fn fetch_data(debug_print : Option<bool>) -> NextDeparture { pub fn fetch_data(debug_print : Option<bool>) -> NextDeparture {
let date = Local::now();
let st_now = SystemTime::now(); let st_now = SystemTime::now();
let seconds = st_now.duration_since(UNIX_EPOCH).unwrap().as_secs(); let seconds = st_now.duration_since(UNIX_EPOCH).unwrap().as_secs();
let timeString = date.format("%Y-%m-%d %H:%M:%S"); let url = &format!("{}?datetime={}", STATION_URL, seconds);
let url = &format!("{}?datetime={}", STATION_URL, timeString);
let result = reqwest::blocking::get(url); let result = reqwest::blocking::get(url);
let mut return_value = NextDeparture { let mut return_value = NextDeparture {
@ -119,24 +118,10 @@ pub fn fetch_data(debug_print : Option<bool>) -> NextDeparture {
return return_value; return return_value;
} }
if debug_print.is_some() && debug_print.unwrap() == true {
println!("----------- Seconds {:} {:} requesting ... -----------", seconds, timeString);
}
// parse JSON result.. search of both directions // parse JSON result.. search of both directions
let json = body.unwrap(); let json = body.unwrap();
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 { for el in json.graph_ql.response.journeys.elements {
if debug_print.is_some() && debug_print.unwrap() == true { if debug_print.is_some() && debug_print.unwrap() == true {
println!("Requesting {:}", json.graph_ql.response.name);
println!("Line {:}", el.line.line_group.label); println!("Line {:}", el.line.line_group.label);
} }
for stop in el.stops { for stop in el.stops {
@ -155,8 +140,6 @@ pub fn fetch_data(debug_print : Option<bool>) -> NextDeparture {
if diff < return_value.outbound_diff { if diff < return_value.outbound_diff {
return_value.outbound_station = stop.destination_label; return_value.outbound_station = stop.destination_label;
return_value.outbound_diff = diff; 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") || } else if stop.destination_label.contains("Hochschule") ||
stop.destination_label.contains("Hauptbahnhof") || stop.destination_label.contains("Hauptbahnhof") ||
@ -164,19 +147,13 @@ pub fn fetch_data(debug_print : Option<bool>) -> NextDeparture {
if diff < return_value.inbound_diff { if diff < return_value.inbound_diff {
return_value.inbound_station = stop.destination_label; return_value.inbound_station = stop.destination_label;
return_value.inbound_diff = diff; 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 { } else {
println!("Planned {:} {:?}", stop.destination_label, stop.planned_departure.iso_string) 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 return_value
} }

View File

@ -1,17 +0,0 @@
[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

View File

@ -1,12 +0,0 @@
#!/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