Add CLI args for configurable values

main
Nick Thomas 2022-10-11 23:54:59 +01:00
parent d168b045aa
commit 672798197b
4 changed files with 70 additions and 25 deletions

16
Cargo.lock generated
View File

@ -460,6 +460,7 @@ dependencies = [
"serde",
"serde_json",
"ureq",
"xflags",
]
[[package]]
@ -681,3 +682,18 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "xflags"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf19f5031a1a812e96fede16f8161218883079946cea87619d3613db1efd268"
dependencies = [
"xflags-macros",
]
[[package]]
name = "xflags-macros"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2afbd7f2039bb6cad2dd45f0c5dff49c0d4e26118398768b7a605524d4251809"

View File

@ -10,6 +10,7 @@ paho-mqtt = "*"
serde = { version = "*", features = [ "derive" ] }
serde_json = "*"
ureq = { version = "*", features = [ "json" ] }
xflags = "*"
[profile.release]
lto = true

View File

@ -38,7 +38,7 @@ solax2mqtt/<adapter-serial>/SENSOR/inverter {
Most of the data for this project came from [`squishykid/solax`](https://github.com/squishykid/solax).
Configuration is currently done by editing the source code and recompiling.
## Alternatives
PocketWIFI is dreadfully insecure. Use Modbus-RTU or, at the very least, a
PocketLAN, if you can. I'll be switching as soon as I'm able to.
@ -48,9 +48,29 @@ This is effectively a custom PocketWIFI module built out of an ESP8266. It's
possible I could put one of these together and make it talk MQTT instead of
having this separate daemon running. One for later.
The PocketWIFI squirts MQTTS to their cloud servers, but they don't verify TLS
so it can be trivially intercepted. However, the messages are in a useless (to
me) binary format.
The PocketWIFI squirts MQTTS to their cloud servers and don't verify TLS, so it
can be trivially intercepted. However, the messages are in a useless (to me)
binary format.
## Usage
First, get TCP access to the PocketWIFI module. Over the AP, it's at `5.8.8.8`.
If you tell it to connect to your WIFI network, your DHCP server will assign an
IP. Next, run `solax2mqtt`:
```
solax2mqtt --mqtt-url tcp://<host>:<port> \
--solax-url http://<host>[:port] \
--solax-password <admin-password>
```
That's it. Errors will be outputted to the console; failure to connect to the
Solax is not fatal (it will keep retrying). Failure to connect to the MQTT
server is (the process will exit).
You can also solax2mqtt --help` to see a few other options.
## `mqtt2prometheus`
Once the metrics are in MQTT, they can be sent to prometheus using
[`mqtt2prometheus`](https://github.com/hikhvar/mqtt2prometheus):

View File

@ -2,11 +2,9 @@ extern crate paho_mqtt as mqtt;
extern crate serde;
extern crate serde_json;
extern crate ureq;
extern crate xflags;
use std::{
result::Result,
time::Duration,
};
use std::{result::Result, time::Duration};
use serde::{Deserialize, Serialize};
@ -42,13 +40,6 @@ impl From<std::io::Error> for Error {
}
}
// Change these for your situation
const DFLT_BROKER: &str = "tcp://10.0.0.1:1883";
const DFLT_CLIENT: &str = "solax2mqtt";
const QOS: i32 = 1;
const HTTP_ENDPOINT: &str = "http://5.8.8.8";
const HTTP_BODY: &str = "optType=ReadRealTimeData&pwd=SXxxxxxxxx";
#[derive(Deserialize)]
struct SolaxResponse {
sn: String,
@ -187,10 +178,10 @@ impl TryFrom<SolaxResponse> for MQTTData {
}
}
fn mqtt_connect() -> Result<mqtt::Client, mqtt::Error> {
fn mqtt_connect(uri: String, client_id: String) -> Result<mqtt::Client, mqtt::Error> {
let mqtt_opts = mqtt::CreateOptionsBuilder::new()
.server_uri(DFLT_BROKER.to_string())
.client_id(DFLT_CLIENT.to_string())
.server_uri(uri)
.client_id(client_id)
.finalize();
// Create a client.
@ -212,19 +203,20 @@ fn mqtt_connect() -> Result<mqtt::Client, mqtt::Error> {
return Ok(mqtt_client);
}
fn gather() -> Result<MQTTData, Error> {
let response = ureq::post(HTTP_ENDPOINT).send_string(HTTP_BODY)?;
fn gather(uri: &str, password: &str) -> Result<MQTTData, Error> {
let body = format!("optType=ReadRealTimeData&pwd={}", password);
let response = ureq::post(uri).send_string(&body)?;
let solax_response: SolaxResponse = response.into_json()?;
solax_response.try_into()
}
fn publish(mqtt_client: &mqtt::Client, data: MQTTData) -> Option<Error> {
fn publish(mqtt_client: &mqtt::Client, data: MQTTData, qos: i32) -> Option<Error> {
let content = serde_json::to_string(&data).ok()?;
let msg = mqtt::Message::new(
format!("solax2mqtt/{}/SENSOR/inverter", data.inverter.adapter_sn),
content,
QOS,
qos,
);
if let Err(e) = mqtt_client.publish(msg) {
@ -235,12 +227,28 @@ fn publish(mqtt_client: &mqtt::Client, data: MQTTData) -> Option<Error> {
}
fn main() {
let mqtt_client = mqtt_connect().unwrap();
let flags = xflags::parse_or_exit! {
/// URL of the MQTT server to publish to, e.g. tcp://10.0.0.1:1883
required --mqtt-url url: String
/// Client ID to use when connecting to the MQTT server. Defaults to solax2mqtt
optional --mqtt-client-id id: String
/// QOS class to publish the messages as. Defaults to 1
optional --mqtt-qos qos: i32
/// URL of the Solax PocketWIFI adapter. Defaults to http://5.8.8.8
optional --solax-url url: String
/// Admin password of the PocketWIFI adapter. Often its serial number
required --solax-password password: String
};
let solax_url = flags.solax_url.unwrap_or("http://5.8.8.8".to_string());
let mqtt_client_id = flags.mqtt_client_id.unwrap_or("solax2mqtt".to_string());
let mqtt_qos = flags.mqtt_qos.unwrap_or(1);
let mqtt_client = mqtt_connect(flags.mqtt_url, mqtt_client_id).unwrap();
loop {
match gather() {
match gather(&solax_url, &flags.solax_password) {
Ok(msg) => {
if let Some(e) = publish(&mqtt_client, msg) {
if let Some(e) = publish(&mqtt_client, msg, mqtt_qos) {
println!("Failed to publish data to MQTT, exiting: {:?}", e);
break;
}