diff --git a/Cargo.lock b/Cargo.lock index 975d45f..61e2e6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -87,6 +87,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.11.0" @@ -172,6 +181,25 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +[[package]] +name = "cpufeatures" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc948ebb96241bb40ab73effeb80d9f93afaad49359d159a5e61be51619fe813" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "ctor" version = "0.1.23" @@ -188,12 +216,31 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "either" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + [[package]] name = "flume" version = "0.10.14" @@ -213,6 +260,16 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + [[package]] name = "futures" version = "0.3.23" @@ -302,6 +359,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.7" @@ -315,6 +382,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "h2" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -347,6 +433,29 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + [[package]] name = "humantime" version = "2.1.0" @@ -363,6 +472,54 @@ dependencies = [ "serde", ] +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +dependencies = [ + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "1.9.1" @@ -373,6 +530,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + [[package]] name = "itertools" version = "0.10.3" @@ -466,6 +629,12 @@ dependencies = [ "libc", ] +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + [[package]] name = "memchr" version = "2.5.0" @@ -481,6 +650,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + [[package]] name = "mio" version = "0.8.4" @@ -510,11 +685,14 @@ dependencies = [ name = "modbus-mqtt" version = "0.1.0" dependencies = [ + "async-trait", "bytes", "clap", + "futures-util", "humantime-serde", "itertools", "pretty_assertions", + "reqwest", "rumqttc", "rust_decimal", "serde", @@ -523,8 +701,10 @@ dependencies = [ "tokio", "tokio-modbus", "tokio-serial", + "tokio-tungstenite", "tracing", "tracing-subscriber", + "uuid", ] [[package]] @@ -606,6 +786,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + [[package]] name = "pin-project" version = "1.0.12" @@ -650,6 +836,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + [[package]] name = "pretty_assertions" version = "1.2.1" @@ -704,6 +896,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + [[package]] name = "regex" version = "1.6.0" @@ -721,6 +943,45 @@ version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +[[package]] +name = "reqwest" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-native-certs", + "rustls-pemfile 1.0.1", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "ring" version = "0.16.20" @@ -894,6 +1155,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serialport" version = "4.2.0" @@ -912,6 +1185,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -1022,6 +1306,21 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + [[package]] name = "tokio" version = "1.20.1" @@ -1093,6 +1392,22 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki", +] + [[package]] name = "tokio-util" version = "0.7.3" @@ -1107,6 +1422,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.36" @@ -1165,18 +1486,94 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "rustls", + "sha-1", + "thiserror", + "url", + "utf-8", + "webpki", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + [[package]] name = "unicode-ident" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" +[[package]] +name = "unicode-normalization" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +dependencies = [ + "tinyvec", +] + [[package]] name = "untrusted" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +dependencies = [ + "getrandom", + "serde", +] + [[package]] name = "valuable" version = "0.1.0" @@ -1189,6 +1586,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1220,6 +1627,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.82" @@ -1342,3 +1761,12 @@ name = "windows_x86_64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] diff --git a/Cargo.toml b/Cargo.toml index fc00870..8794dac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,10 +6,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +async-trait = "0.1.57" bytes = "1.1.0" clap = { version = "3.2.12", features = ["derive", "env"] } +futures-util = "0.3.23" humantime-serde = "1.1.1" itertools = "0.10.3" +reqwest = { version = "0.11.11", features = ["rustls-tls-native-roots", "json"], default-features = false } rumqttc = "0.15.0" rust_decimal = { version = "1.26.1", features = ["serde-arbitrary-precision", "serde-float", "serde_json", "maths"] } serde = { version = "1.0.139", features = ["serde_derive"] } @@ -18,8 +21,10 @@ serialport = { version = "4.2.0", features = ["serde"] } tokio = { version = "1.20.0", features = ["rt", "rt-multi-thread", "time"] } tokio-modbus = "0.5.3" tokio-serial = "5.4.3" +tokio-tungstenite = { version = "0.17.2", features = ["rustls-tls-native-roots"] } tracing = "0.1.36" tracing-subscriber = "0.3.15" +uuid = { version = "1.1.2", features = ["v4", "serde"] } [dev-dependencies] pretty_assertions = "1.2.1" diff --git a/src/main.rs b/src/main.rs index e0199e7..5c1a610 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use rumqttc::{self, AsyncClient, Event, Incoming, LastWill, MqttOptions, Publish use serde::Serialize; use serde_json::json; use std::{collections::HashMap, time::Duration}; -use tokio::{sync::mpsc, sync::oneshot, time::MissedTickBehavior}; +use tokio::{select, sync::mpsc, sync::oneshot, time::MissedTickBehavior}; use tokio_modbus::prelude::*; use tracing::{debug, error, info}; @@ -38,7 +38,7 @@ enum MainStatus { Stopped, } -#[tokio::main(worker_threads = 1)] +#[tokio::main(worker_threads = 3)] async fn main() { tracing_subscriber::fmt::init(); @@ -243,6 +243,11 @@ async fn handle_connect( let unit = connect.unit; let mut modbus = match connect.settings { + ModbusProto::SungrowWiNetS { ref host } => { + modbus::sungrow::winets::connect_slave(host, unit) + .await + .unwrap() + } ModbusProto::Tcp { ref host, port } => { let socket_addr = format!("{}:{}", host, port).parse().unwrap(); tcp::connect_slave(socket_addr, unit).await.unwrap() @@ -392,7 +397,16 @@ async fn watch_registers( .await .unwrap(); - let words = rx.await.unwrap().unwrap(); + // FIXME: definitely getting errors here that need to be handled + // + // thread 'tokio-runtime-worker' panicked at 'called `Result::unwrap()` on an `Err` value: Error { kind: UnexpectedEof, message: "failed to fill whole buffer" }' + // thread 'tokio-runtime-worker' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: InvalidData, error: "Invalid data length: 0" }' + // thread 'tokio-runtime-worker' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 36, kind: Uncategorized, message: "Operation now in progress" }' + // thread 'tokio-runtime-worker' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 35, kind: WouldBlock, message: "Resource temporarily unavailable" } + // + // Splitting out the two awaits so I can see if all of the above panics come from the same await or some from one vs the other: + let response = rx.await.unwrap(); // await may have errorer on receiving + let words = response.unwrap(); // received message is also a result which may be a (presumably Modbus?) error let swapped_words = r.apply_swaps(&words); diff --git a/src/modbus/config.rs b/src/modbus/config.rs index 762270e..febaa5e 100644 --- a/src/modbus/config.rs +++ b/src/modbus/config.rs @@ -7,7 +7,7 @@ use pretty_assertions::{assert_eq, assert_ne}; use serde_json::json; #[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(untagged)] +#[serde(tag = "proto", rename_all = "lowercase")] pub enum ModbusProto { Tcp { host: String, @@ -33,6 +33,8 @@ pub enum ModbusProto { #[serde(default = "default_modbus_parity")] parity: tokio_serial::Parity, }, + #[serde(rename = "winet-s")] + SungrowWiNetS { host: String }, } fn default_modbus_port() -> u16 { diff --git a/src/modbus/mod.rs b/src/modbus/mod.rs index 131b568..5c2c28a 100644 --- a/src/modbus/mod.rs +++ b/src/modbus/mod.rs @@ -4,6 +4,7 @@ use serde::Serialize; use self::config::{Register, RegisterValueType}; pub mod config; +pub mod sungrow; #[derive(Serialize)] #[serde(rename_all = "lowercase")] diff --git a/src/modbus/sungrow.rs b/src/modbus/sungrow.rs new file mode 100644 index 0000000..f3b66a5 --- /dev/null +++ b/src/modbus/sungrow.rs @@ -0,0 +1,137 @@ +pub mod winets { + use async_trait::async_trait; + use std::io::Error; + use tokio::time::MissedTickBehavior; + use tokio_modbus::client::Client; + use tokio_modbus::client::Context as ModbusContext; + use tokio_modbus::prelude::{Request, Response}; + use tokio_modbus::slave::{Slave, SlaveContext}; + + use tracing::{debug, error, info}; + + pub async fn connect(host: H) -> Result + where + H: Into, + { + connect_slave(host, Slave(1)).await + } + + pub async fn connect_slave(host: H, slave: Slave) -> Result + where + H: Into, + { + let (tx, mut rx) = tokio::sync::watch::channel(None); + + tokio::spawn(async move { + debug!("Starting WiNet-S websocket"); + use futures_util::SinkExt; + // use futures_util::{future, pin_mut, StreamExt}; + use futures_util::StreamExt; + use std::time::Duration; + // use tokio::io::{AsyncReadExt, AsyncWriteExt}; + use serde_json::Value as JSON; + use tokio::select; + use tokio_tungstenite::{connect_async, tungstenite::protocol::Message}; + + let ws_url = format!("ws://{}:8082/ws/home/overview", "10.10.10.219"); + let (mut ws_stream, _) = connect_async(ws_url).await.expect("Failed to connect"); + // let (write, read) = ws_stream.split(); + ws_stream + .send(Message::Text( + serde_json::json!({"lang":"en_us","token":"","service":"connect"}).to_string(), + )) + .await + .expect("whoops"); + + // WiNet-S interface sends following message every now and then: + // {"lang":"zh_cn","service":"ping","token":"","id":"84c2265b-5f7f-4915-82e9-57250064316f"} + // UUID is always random, token always seems blank. + // Unclear if this is a real `Ping` message or just a regular `Text` message with "ping" content. + // update: it is just a text message 🙄 + // Response is just: + // { "result_code": 1, "result_msg": "success" } + let mut ping = tokio::time::interval(Duration::from_secs(5)); + ping.set_missed_tick_behavior(MissedTickBehavior::Delay); + + loop { + select! { + Some(resp) = ws_stream.next() => { + match resp { + Ok(msg) => { + debug!(%msg, "WS ->"); + + if let Message::Text(msg) = msg { + let value: JSON = serde_json::from_str(&msg).expect("expected json"); + if let JSON::String(ref token) = value["result_data"]["token"] { + // FIXME: this should fails when all receivers have been dropped but I'm pretty + // sure rx is not dropped because it's moved into Context struct :/ + tx.send(Some(token.clone())).unwrap(); + } + } + }, + Err(err) => error!(?err, "WS ->") + } + }, + _ = ping.tick() => { + let msg = serde_json::json!({ + "lang":"en_us", // WiNet-S always sends zh_cn, but this works + "service":"ping", + // WiNet-S includes `"token": ""`, but it works without it + "id": uuid::Uuid::new_v4() + }).to_string(); + debug!(%msg, "WS <-"); + ws_stream + .send(Message::Text(msg)) + .await + .expect("whoops"); + } + } + } + }); + + // wait for a token before returning the client, so that it is ready + rx.changed().await; + + let box_: Box = Box::new(Context { + unit: Some(slave), + token: rx, + }); + Ok(ModbusContext::from(box_)) + } + + /// Equivalent to tokio_modbus::service::tcp::Context + #[derive(Debug)] + pub struct Context { + unit: Option, + token: tokio::sync::watch::Receiver>, + // TODO: websocket + keep TCP connection for HTTP? + } + + #[async_trait] + impl Client for Context { + #[tracing::instrument(level = "debug")] + async fn call(&mut self, request: Request) -> Result { + match request { + Request::ReadCoils(_, _) => todo!(), + Request::ReadDiscreteInputs(_, _) => todo!(), + Request::WriteSingleCoil(_, _) => todo!(), + Request::WriteMultipleCoils(_, _) => todo!(), + Request::ReadInputRegisters(_, _) => { + Result::Ok(Response::ReadInputRegisters(vec![0xaa])) + } + Request::ReadHoldingRegisters(_, _) => todo!(), + Request::WriteSingleRegister(_, _) => todo!(), + Request::WriteMultipleRegisters(_, _) => todo!(), + Request::ReadWriteMultipleRegisters(_, _, _, _) => todo!(), + Request::Custom(_, _) => todo!(), + Request::Disconnect => todo!(), + } + } + } + + impl SlaveContext for Context { + fn set_slave(&mut self, slave: tokio_modbus::slave::Slave) { + self.unit = Some(slave); + } + } +}