Add CLI args for configurable values
This commit is contained in:
16
Cargo.lock
generated
16
Cargo.lock
generated
@@ -460,6 +460,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"ureq",
|
"ureq",
|
||||||
|
"xflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -681,3 +682,18 @@ name = "winapi-x86_64-pc-windows-gnu"
|
|||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
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"
|
||||||
|
@@ -10,6 +10,7 @@ paho-mqtt = "*"
|
|||||||
serde = { version = "*", features = [ "derive" ] }
|
serde = { version = "*", features = [ "derive" ] }
|
||||||
serde_json = "*"
|
serde_json = "*"
|
||||||
ureq = { version = "*", features = [ "json" ] }
|
ureq = { version = "*", features = [ "json" ] }
|
||||||
|
xflags = "*"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
28
README.md
28
README.md
@@ -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).
|
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
|
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.
|
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
|
possible I could put one of these together and make it talk MQTT instead of
|
||||||
having this separate daemon running. One for later.
|
having this separate daemon running. One for later.
|
||||||
|
|
||||||
The PocketWIFI squirts MQTTS to their cloud servers, but they don't verify TLS
|
The PocketWIFI squirts MQTTS to their cloud servers and don't verify TLS, so it
|
||||||
so it can be trivially intercepted. However, the messages are in a useless (to
|
can be trivially intercepted. However, the messages are in a useless (to me)
|
||||||
me) binary format.
|
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
|
Once the metrics are in MQTT, they can be sent to prometheus using
|
||||||
[`mqtt2prometheus`](https://github.com/hikhvar/mqtt2prometheus):
|
[`mqtt2prometheus`](https://github.com/hikhvar/mqtt2prometheus):
|
||||||
|
50
src/main.rs
50
src/main.rs
@@ -2,11 +2,9 @@ extern crate paho_mqtt as mqtt;
|
|||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
extern crate ureq;
|
extern crate ureq;
|
||||||
|
extern crate xflags;
|
||||||
|
|
||||||
use std::{
|
use std::{result::Result, time::Duration};
|
||||||
result::Result,
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
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)]
|
#[derive(Deserialize)]
|
||||||
struct SolaxResponse {
|
struct SolaxResponse {
|
||||||
sn: String,
|
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()
|
let mqtt_opts = mqtt::CreateOptionsBuilder::new()
|
||||||
.server_uri(DFLT_BROKER.to_string())
|
.server_uri(uri)
|
||||||
.client_id(DFLT_CLIENT.to_string())
|
.client_id(client_id)
|
||||||
.finalize();
|
.finalize();
|
||||||
|
|
||||||
// Create a client.
|
// Create a client.
|
||||||
@@ -212,19 +203,20 @@ fn mqtt_connect() -> Result<mqtt::Client, mqtt::Error> {
|
|||||||
return Ok(mqtt_client);
|
return Ok(mqtt_client);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gather() -> Result<MQTTData, Error> {
|
fn gather(uri: &str, password: &str) -> Result<MQTTData, Error> {
|
||||||
let response = ureq::post(HTTP_ENDPOINT).send_string(HTTP_BODY)?;
|
let body = format!("optType=ReadRealTimeData&pwd={}", password);
|
||||||
|
let response = ureq::post(uri).send_string(&body)?;
|
||||||
let solax_response: SolaxResponse = response.into_json()?;
|
let solax_response: SolaxResponse = response.into_json()?;
|
||||||
|
|
||||||
solax_response.try_into()
|
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 content = serde_json::to_string(&data).ok()?;
|
||||||
let msg = mqtt::Message::new(
|
let msg = mqtt::Message::new(
|
||||||
format!("solax2mqtt/{}/SENSOR/inverter", data.inverter.adapter_sn),
|
format!("solax2mqtt/{}/SENSOR/inverter", data.inverter.adapter_sn),
|
||||||
content,
|
content,
|
||||||
QOS,
|
qos,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Err(e) = mqtt_client.publish(msg) {
|
if let Err(e) = mqtt_client.publish(msg) {
|
||||||
@@ -235,12 +227,28 @@ fn publish(mqtt_client: &mqtt::Client, data: MQTTData) -> Option<Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
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 {
|
loop {
|
||||||
match gather() {
|
match gather(&solax_url, &flags.solax_password) {
|
||||||
Ok(msg) => {
|
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);
|
println!("Failed to publish data to MQTT, exiting: {:?}", e);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user