From 90958a4046ac3f266fdb82cbc7d928f22357a73a Mon Sep 17 00:00:00 2001 From: Keenan Tims Date: Tue, 22 Nov 2022 12:12:09 -0800 Subject: [PATCH] Add chrony SOCK refclock and initial UCCM source --- Cargo.lock | 463 ++++++++++++++++++++--------------------- Cargo.toml | 12 +- config.toml.dist | 5 + src/chrony.rs | 20 +- src/chrony_refclock.rs | 76 +++++++ src/hwmon.rs | 52 +++-- src/lib.rs | 84 +++++++- src/main.rs | 57 ++++- src/uccm.rs | 328 +++++++++++++++++++++++++++++ 9 files changed, 808 insertions(+), 289 deletions(-) create mode 100644 src/chrony_refclock.rs create mode 100644 src/uccm.rs diff --git a/Cargo.lock b/Cargo.lock index fbc9a30..a0fcd69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,10 +3,31 @@ version = 3 [[package]] -name = "aho-corasick" -version = "0.7.19" +name = "CoreFoundation-sys" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "d0e9889e6db118d49d88d84728d0e964d973a5680befb5f85f55141beea5c20b" +dependencies = [ + "libc", + "mach 0.1.2", +] + +[[package]] +name = "IOKit-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99696c398cbaf669d2368076bdb3d627fb0ce51a26899d7c61228c5c0af3bf4a" +dependencies = [ + "CoreFoundation-sys", + "libc", + "mach 0.1.2", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] @@ -121,16 +142,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] -name = "bytes" -version = "1.2.1" +name = "byteorder" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "cc" -version = "1.0.74" +version = "1.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" [[package]] name = "cfg-if" @@ -143,6 +170,10 @@ name = "chimemon" version = "0.1.0" dependencies = [ "async-trait", + "bitflags", + "byteorder", + "bytes", + "chrono", "chrony-candm", "clap", "env_logger", @@ -150,18 +181,20 @@ dependencies = [ "futures", "gethostname", "influxdb2", + "libc", "log", "serde", "serde_derive", "tokio", + "tokio-serial", "tokio-stream", ] [[package]] name = "chrono" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ "iana-time-zone", "js-sys", @@ -203,9 +236,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.18" +version = "4.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335867764ed2de42325fafe6d18b8af74ba97ee0c590fa016f157535b42ab04b" +checksum = "2148adefda54e14492fb9bddcc600b4344c5d1a3123bd666dcb939c6f0e0e57e" dependencies = [ "atty", "bitflags", @@ -218,9 +251,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.0.18" +version = "4.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a1b0f6422af32d5da0c58e2703320f379216ee70198241c84173a8c5ac28f3" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" dependencies = [ "heck", "proc-macro-error", @@ -248,16 +281,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -288,9 +311,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.80" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" +checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" dependencies = [ "cc", "cxxbridge-flags", @@ -300,9 +323,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.80" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" +checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" dependencies = [ "cc", "codespan-reporting", @@ -315,15 +338,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.80" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" +checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" [[package]] name = "cxxbridge-macro" -version = "1.0.80" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" +checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" dependencies = [ "proc-macro2", "quote", @@ -369,9 +392,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", "humantime", @@ -414,21 +437,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.1.0" @@ -648,9 +656,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.22" +version = "0.14.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfba89e19b959ca163c7752ba59d737c1ceea53a5d31a149c805446fc958064" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" dependencies = [ "bytes", "futures-channel", @@ -670,19 +678,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "iana-time-zone" version = "0.1.53" @@ -719,9 +714,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -794,9 +789,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" [[package]] name = "itertools" @@ -881,6 +876,24 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "mach" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd13ee2dd61cc82833ba05ade5a30bb3d63f7ced605ef827063c63078302de9" +dependencies = [ + "libc", +] + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "matchers" version = "0.0.1" @@ -896,6 +909,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.16" @@ -911,25 +933,45 @@ dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] -name = "native-tls" -version = "0.2.10" +name = "mio-serial" +version = "5.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +checksum = "dff736ea7e2501db099ae3f82aabc14db416a01958c8354b3f353dd961a18773" dependencies = [ - "lazy_static", - "libc", "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "mio", + "nix 0.25.0", + "serialport", + "winapi", +] + +[[package]] +name = "nix" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "nix" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", + "memoffset", + "pin-utils", ] [[package]] @@ -966,9 +1008,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ "hermit-abi", "libc", @@ -1001,51 +1043,6 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" -[[package]] -name = "openssl" -version = "0.10.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "opentelemetry" version = "0.13.0" @@ -1068,18 +1065,18 @@ dependencies = [ [[package]] name = "ordered-float" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f74e330193f90ec45e2b257fa3ef6df087784157ac1ad2c1e71c62837b03aa7" +checksum = "d84eb1409416d254e4a9c8fa56cc24701755025b458f0fcd8e59e1f5f40c23bf" dependencies = [ "num-traits", ] [[package]] name = "os_str_bytes" -version = "6.3.1" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] name = "parking_lot" @@ -1144,17 +1141,11 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" - [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-crate" @@ -1256,9 +1247,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", @@ -1276,9 +1267,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -1291,9 +1282,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.12" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" +checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" dependencies = [ "base64", "bytes", @@ -1304,20 +1295,18 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-tls", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "rustls", "serde", "serde_json", "serde_urlencoded", "tokio", - "tokio-native-tls", "tokio-util", "tower-service", "url", @@ -1327,22 +1316,39 @@ dependencies = [ "winreg", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustls" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + [[package]] name = "ryu" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" -[[package]] -name = "schannel" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" -dependencies = [ - "lazy_static", - "windows-sys 0.36.1", -] - [[package]] name = "scopeguard" version = "1.1.0" @@ -1356,26 +1362,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" [[package]] -name = "security-framework" -version = "2.7.0" +name = "sct" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" -dependencies = [ - "core-foundation-sys", - "libc", + "ring", + "untrusted", ] [[package]] @@ -1400,9 +1393,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" dependencies = [ "itoa 1.0.4", "ryu", @@ -1432,6 +1425,22 @@ dependencies = [ "serde", ] +[[package]] +name = "serialport" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aab92efb5cf60ad310548bc3f16fa6b0d950019cb7ed8ff41968c3d03721cf12" +dependencies = [ + "CoreFoundation-sys", + "IOKit-sys", + "bitflags", + "cfg-if", + "mach 0.3.2", + "nix 0.24.2", + "regex", + "winapi", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -1493,6 +1502,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "static_assertions" version = "1.1.0" @@ -1602,9 +1617,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.21.2" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" dependencies = [ "autocfg", "bytes", @@ -1629,12 +1644,15 @@ dependencies = [ ] [[package]] -name = "tokio-native-tls" -version = "0.3.0" +name = "tokio-serial" +version = "5.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "aa6e2e4cf0520a99c5f87d5abb24172b5bd220de57c3181baaaa5440540c64aa" dependencies = [ - "native-tls", + "cfg-if", + "futures", + "log", + "mio-serial", "tokio", ] @@ -1786,6 +1804,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.3.1" @@ -1803,12 +1827,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" @@ -1913,6 +1931,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1944,19 +1972,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - [[package]] name = "windows-sys" version = "0.42.0" @@ -1964,12 +1979,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows_x86_64_msvc", ] [[package]] @@ -1978,48 +1993,24 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - [[package]] name = "windows_aarch64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - [[package]] name = "windows_i686_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - [[package]] name = "windows_i686_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - [[package]] name = "windows_x86_64_gnu" version = "0.42.0" @@ -2032,12 +2023,6 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - [[package]] name = "windows_x86_64_msvc" version = "0.42.0" diff --git a/Cargo.toml b/Cargo.toml index cc025a5..85771ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,10 @@ edition = "2021" [dependencies] serde = "1.0" serde_derive = "1.0" -influxdb2 = "0.3.3" -tokio = { version = "1", features = ["rt"] } +influxdb2 = { version = "0.3.3", features = [ + "rustls", +], default-features = false } +tokio = { version = "1", features = ["rt", "io-util"] } clap = { version = "4.0", features = ["derive"] } log = "0.4" figment = { version = "0.10", features = ["toml"] } @@ -16,6 +18,12 @@ env_logger = "0.9.1" futures = "0.3.24" async-trait = "0.1.58" tokio-stream = { version = "0.1.11", features = ["sync"] } +bitflags = "1.3.2" +byteorder = "1.4.3" +tokio-serial = "5.4.4" +bytes = "1.2.1" +chrono = "0.4.23" +libc = "0.2.137" [dependencies.chrony-candm] git = "https://github.com/aws/chrony-candm" diff --git a/config.toml.dist b/config.toml.dist index 56730ac..675b16a 100644 --- a/config.toml.dist +++ b/config.toml.dist @@ -18,6 +18,11 @@ name = "hwmon0" sensor = "temp1_input" +[targets] + [targets.chrony] + enabled = true + sock = "/tmp/uccm.sock" + [influxdb] url = "http://localhost:8086" org = "default" diff --git a/src/chrony.rs b/src/chrony.rs index 9948cc3..51e4491 100644 --- a/src/chrony.rs +++ b/src/chrony.rs @@ -3,7 +3,7 @@ use chimemon::{ChimemonSource, ChimemonSourceChannel, Config}; use chrony_candm::reply::{self, ReplyBody, SourceMode}; use chrony_candm::request::{self, RequestBody}; use influxdb2::models::DataPoint; -use log::{info, warn}; +use log::{debug, info, warn}; use std::net::{SocketAddr, ToSocketAddrs}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tokio::runtime::Handle; @@ -77,8 +77,8 @@ pub fn datapoint_from_sourcedata( reply::SourceState::NonSelectable => String::from("unusable"), reply::SourceState::Falseticker => String::from("falseticker"), reply::SourceState::Jittery => String::from("jittery"), - reply::SourceState::Unselected => String::from("unused"), - reply::SourceState::Selectable => String::from("combined"), + reply::SourceState::Unselected => String::from("combined"), + reply::SourceState::Selectable => String::from("unused"), }, ) .field("poll", d.poll as i64) @@ -208,9 +208,9 @@ impl ChronyClient { let tracking_data = datapoint_from_tracking(&tracking, &self.config)?; - info!("Writing tracking data: {:?}", tracking_data); + info!("Sending tracking data"); - chan.send(tracking_data) + chan.send(tracking_data.into()) .expect("Unable to send tracking data to targets"); Ok(()) @@ -221,19 +221,21 @@ impl ChronyClient { chan: &ChimemonSourceChannel, ) -> Result<(), Box> { let sources = self.get_sources().await?; + let mut dps = Vec::with_capacity(sources.len()); for ds in sources { let source_data = datapoint_from_sourcedata(&ds, &self.config)?; - info!("Writing source data: {:?}", source_data); - chan.send(source_data) - .expect("Unable to send source data to targets"); + dps.push(source_data); } + info!("Sending source data"); + chan.send(dps.into()) + .expect("Unable to send source data to targets"); Ok(()) } } #[async_trait] impl ChimemonSource for ChronyClient { - async fn run(self, chan: tokio::sync::broadcast::Sender) { + async fn run(self, chan: ChimemonSourceChannel) { info!("Chrony task started"); let mut t_interval = tokio::time::interval(Duration::from_secs( diff --git a/src/chrony_refclock.rs b/src/chrony_refclock.rs new file mode 100644 index 0000000..c1be31f --- /dev/null +++ b/src/chrony_refclock.rs @@ -0,0 +1,76 @@ +use async_trait::async_trait; +use chimemon::{ChimemonMessage, ChimemonTarget, ChimemonTargetChannel, ChronySockConfig}; +use libc::{c_double, c_int, timeval}; +use log::debug; +use std::io::prelude::*; +use std::mem; +use std::os::unix::net::UnixDatagram; +use std::path::Path; + +const CHRONY_MAGIC: c_int = 0x534f434b; + +pub struct ChronySockServer { + sock: UnixDatagram, +} + +#[repr(C)] +#[derive(Debug)] +pub struct ChronyTimeReport { + tv: timeval, + offset: c_double, + pulse: c_int, + leap: c_int, + _pad: c_int, + magic: c_int, +} + +impl ChronySockServer { + pub fn new(config: ChronySockConfig) -> Self { + debug!( + "Size of chrony refclock report: {}", + mem::size_of::() + ); + let sock = UnixDatagram::unbound().unwrap(); + // TODO: Don't connect to the socket or we break when chrony restarts + // use sock.send_to instead and fail gracefully + sock.connect(&config.sock).expect("Unable to open socket"); + ChronySockServer { sock } + } +} + +#[async_trait] +impl ChimemonTarget for ChronySockServer { + async fn run(mut self, mut chan: ChimemonTargetChannel) { + loop { + let msg = chan.recv().await.unwrap(); + match msg { + ChimemonMessage::TimeReport(tr) => { + if tr.valid { + { + let frame = ChronyTimeReport { + tv: timeval { + tv_sec: tr.system_time.timestamp().try_into().unwrap_or_default(), + tv_usec: tr.system_time.timestamp_subsec_micros().try_into().unwrap_or_default(), + }, + offset: tr.offset.num_nanoseconds().unwrap() as f64 / 1e9, + leap: if tr.leap_flag { 1 } else { 0 }, + pulse: 0, + _pad: 0, + magic: CHRONY_MAGIC, + }; + unsafe { + let bs = std::slice::from_raw_parts( + (&frame as *const ChronyTimeReport) as *const u8, + mem::size_of::(), + ); + debug!("Sending to chrony sock {:#?}", frame); + self.sock.send(bs).unwrap(); + }; + } + } + } + _ => continue, + } + } + } +} diff --git a/src/hwmon.rs b/src/hwmon.rs index 4448bb9..7bcb0e8 100644 --- a/src/hwmon.rs +++ b/src/hwmon.rs @@ -4,7 +4,7 @@ use futures::{stream, StreamExt}; use influxdb2::models::DataPoint; use log::{debug, info}; use std::{ - path::{Path, PathBuf}, + path::PathBuf, time::{Duration, SystemTime, UNIX_EPOCH}, }; @@ -47,37 +47,35 @@ impl HwmonSource { #[async_trait] impl ChimemonSource for HwmonSource { - async fn run(self, chan: tokio::sync::broadcast::Sender) { + async fn run(self, chan: ChimemonSourceChannel) { info!("hwmon task started"); let mut interval = tokio::time::interval(Duration::from_secs(self.config.sources.hwmon.interval)); loop { interval.tick().await; - stream::iter(&self.sensors) - .for_each_concurrent(None, |s| async { - let sensor_val = HwmonSource::get_raw_value(s) - .await - .expect("Unable to read sensor"); - debug!( - "hwmon {} raw value {}", - s.path.to_string_lossy(), - sensor_val - ); - let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - let mut builder = - DataPoint::builder(&s.device).timestamp(now.as_nanos().try_into().unwrap()); - for (key, value) in &self.config.influxdb.tags { - builder = builder.tag(key, value) - } - builder = builder - .tag("sensor", &s.name) - .field("value", sensor_val.trim().parse::().unwrap()); - let dp = builder.build().unwrap(); - - info!("Writing hwmon data: {:?}", dp); - chan.send(dp).unwrap(); - }) - .await; + let s = stream::iter(&self.sensors).then(|s| async { + let sensor_val = HwmonSource::get_raw_value(s) + .await + .expect("Unable to read sensor"); + debug!( + "hwmon {} raw value {}", + s.path.to_string_lossy(), + sensor_val + ); + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let mut builder = + DataPoint::builder(&s.device).timestamp(now.as_nanos().try_into().unwrap()); + for (key, value) in &self.config.influxdb.tags { + builder = builder.tag(key, value) + } + builder = builder + .tag("sensor", &s.name) + .field("value", sensor_val.trim().parse::().unwrap()); + builder.build().unwrap() + }); + info!("Writing hwmon data"); + chan.send(s.collect::>().await.into()) + .unwrap(); } } } diff --git a/src/lib.rs b/src/lib.rs index e748eb9..05ac1e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; +use chrono::NaiveDateTime; use figment::{ - providers::{Format, Serialized, Toml}, + providers::{Data, Format, Serialized, Toml}, util::map, value::Map, Figment, @@ -8,7 +9,7 @@ use figment::{ use gethostname::gethostname; use influxdb2::models::DataPoint; use serde_derive::{Deserialize, Serialize}; -use std::path::Path; +use std::{path::Path, time::Duration}; use tokio::sync::broadcast::*; #[derive(Serialize, Deserialize, Clone)] @@ -60,6 +61,21 @@ impl Default for ChronyConfig { } } +#[derive(Serialize, Deserialize, Clone)] +pub struct ChronySockConfig { + pub enabled: bool, + pub sock: String, +} + +impl Default for ChronySockConfig { + fn default() -> Self { + ChronySockConfig { + enabled: false, + sock: "".into(), + } + } +} + #[derive(Serialize, Deserialize, Clone)] pub struct HwmonSensorConfig { pub name: String, @@ -84,25 +100,85 @@ impl Default for HwmonConfig { } } } +#[derive(Clone, Debug)] +pub struct TimeReport { + pub system_time: NaiveDateTime, + pub offset: chrono::Duration, + pub leaps: isize, + pub leap_flag: bool, + pub valid: bool, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct UCCMConfig { + pub enabled: bool, + pub port: String, + pub baud: u32, + pub status_interval: std::time::Duration, + pub timeout: std::time::Duration, +} + +impl Default for UCCMConfig { + fn default() -> Self { + UCCMConfig { + enabled: false, + port: "/dev/ttyS0".into(), + baud: 57600, + status_interval: std::time::Duration::from_secs(10), + timeout: std::time::Duration::from_secs(1), + } + } +} #[derive(Serialize, Deserialize, Clone, Default)] pub struct SourcesConfig { pub chrony: ChronyConfig, pub hwmon: HwmonConfig, + pub uccm: UCCMConfig, +} + +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct TargetsConfig { + pub chrony: ChronySockConfig, } #[derive(Serialize, Deserialize, Clone, Default)] pub struct Config { pub influxdb: InfluxConfig, pub sources: SourcesConfig, + pub targets: TargetsConfig, } pub fn load_config(filename: &Path) -> Figment { Figment::from(Serialized::defaults(Config::default())).merge(Toml::file(filename)) } -pub type ChimemonSourceChannel = Sender; -pub type ChimemonTargetChannel = Receiver; +#[derive(Clone, Debug)] +pub enum ChimemonMessage { + DataPoint(DataPoint), + DataPoints(Vec), + TimeReport(TimeReport), +} + +impl From for ChimemonMessage { + fn from(dp: DataPoint) -> Self { + ChimemonMessage::DataPoint(dp) + } +} +impl From> for ChimemonMessage { + fn from(dps: Vec) -> Self { + ChimemonMessage::DataPoints(dps) + } +} + +impl From for ChimemonMessage { + fn from(tr: TimeReport) -> Self { + ChimemonMessage::TimeReport(tr) + } +} + +pub type ChimemonSourceChannel = Sender; +pub type ChimemonTargetChannel = Receiver; #[async_trait] pub trait ChimemonSource { diff --git a/src/main.rs b/src/main.rs index 78dc8f6..8000737 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,16 @@ mod chrony; +mod chrony_refclock; mod hwmon; +mod uccm; use clap::{Parser, ValueEnum}; use env_logger::{self, Env}; use futures::{future::join_all, prelude::*}; -use log::{error, info, warn}; +use log::{debug, error, info, warn}; use std::path::Path; use tokio::sync::broadcast; -use crate::{chrony::*, hwmon::HwmonSource}; +use crate::{chrony::*, chrony_refclock::ChronySockServer, hwmon::HwmonSource, uccm::UCCMMonitor}; use chimemon::*; const PROGRAM_NAME: &str = "chimemon"; @@ -79,20 +81,59 @@ async fn main() -> Result<(), Box> { None }; match hwmon { - Some(hwmon) => { + Some(hwmon) => { tasks.push(tokio::spawn(hwmon.run(sourcechan.clone()))); } None => (), }; + let uccm = if config.sources.uccm.enabled { + Some(UCCMMonitor::new(config.to_owned())) + } else { + None + }; + match uccm { + Some(uccm) => { + tasks.push(tokio::spawn(uccm.run(sourcechan.clone()))); + } + None => (), + }; + + let chrony_refclock = if config.targets.chrony.enabled { + Some(ChronySockServer::new(config.targets.chrony.to_owned())) + } else { + None + }; + match chrony_refclock { + Some(chrony_refclock) => { + tasks.push(tokio::spawn(chrony_refclock.run(sourcechan.subscribe()))); + } + None => (), + }; + let mut influxrx = sourcechan.subscribe(); tasks.push(tokio::spawn(async move { loop { - let dp = influxrx.recv().await.unwrap(); - influx - .write(&config.influxdb.bucket, stream::iter([dp])) - .await - .expect("Error writing to influxdb"); + let msg = influxrx.recv().await.unwrap(); + match msg { + ChimemonMessage::DataPoint(dp) => { + debug!("Writing datapoint to influx: {:?}", dp); + influx + .write(&config.influxdb.bucket, stream::iter([dp])) + .await + .expect("Error writing to influxdb"); + } + ChimemonMessage::DataPoints(dps) => { + debug!("Writing datapoints to influx: {:?}", dps); + influx + .write(&config.influxdb.bucket, stream::iter(dps)) + .await + .expect("Error writing to influxdb"); + } + ChimemonMessage::TimeReport(tr) => { + debug!("GPS TOD: {:?}", tr); + } + } } })); diff --git a/src/uccm.rs b/src/uccm.rs new file mode 100644 index 0000000..b398ebe --- /dev/null +++ b/src/uccm.rs @@ -0,0 +1,328 @@ +use async_trait::async_trait; +use bitflags::bitflags; +use byteorder::{BigEndian, ReadBytesExt}; +use bytes::{Buf, BytesMut}; +use chimemon::{ + ChimemonMessage, ChimemonSource, ChimemonSourceChannel, Config, TimeReport, UCCMConfig, +}; +use chrono::{Duration, NaiveDateTime, Utc}; +use log::{debug, info, warn}; +use std::io::Cursor; +use std::str; +use std::sync::Arc; +use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, ReadHalf, WriteHalf}; +use tokio::join; +use tokio::sync::Mutex; +use tokio::time::{interval, sleep, Interval}; +use tokio_serial::{SerialPort, SerialStream}; + +pub const GPS_EPOCH: i64 = 315964800; // Doesn't seem possible to have a const DateTime object +pub type UccmEndian = BigEndian; + +pub enum UCCMMonitorParseState { + Idle, + ReadStatus, + ReadLoopDiag, + ReadTOD, +} + +pub struct UCCMMonitor { + // pub port: SerialStream, + rx: ReadHalf, + tx: WriteHalf, + pub info: Option, + config: UCCMConfig, +} + +#[derive(Debug)] +pub struct UCCMTODReport { + pub time: NaiveDateTime, // TAI timestamp + pub leaps: i8, + pub flags: UCCMFlags, +} + +bitflags! { + pub struct UCCMFlags: u32 { + const OSC_LOCK = (1<<29); + const LEAP_FLAG = (1<<25); + const INIT_UNLOCK = (1<<24); + + const INIT_NO_SATS = (1<<19); + const HAVE_GPS_TIME = (1<<18); + const POWER_FAIL = (1<<17); + + const MODEL_SYMMETRICOM = (1<<15); + const MODEL_TRIMBLE = (1<<14); + const NO_GPS_SYNC = (1<<11); + const NO_GPS_SYNC2 = (1<<9); + + const MODEL_SYMMETRICOM2 = (1<<7); + const MODEL_TRIMBLE2 = (1<<6); + const NO_ANT = (1<<5); + const GPS_LOS = (1<<4); + } +} + +/* FLAGS: + MSB 31 x + 30 ALWAYS 1 + 29 oscillator locked + 28..26 x + 25 leap flag + 24 initial unlock + + 23..20 x + 19 initialized no sats (trimble) + 18 have gps time flag + 17 power fail + 16 x + + 15..14 model/protocol ( 01 = trimble 10 = symmetricom ) + 13..12 x + 11 no gps sync + 10 always 1 + 9 no gps sync + 8 always 1 + + 7..6 model/protocol ( 01 = trimble 10 = symmetricom ) + 5 no ant + 4 gps los + LSB 3..0 x +*/ + +#[derive(Debug)] +pub struct GPSSVInfo { + pub tracked: bool, + pub in_view: bool, + pub prn: u8, + pub elevation: usize, + pub azimuth: usize, + pub cn0: usize, +} + +#[derive(Debug)] +pub struct UCCMStatusReport { + pub tfom: u8, + pub ffom: u8, + pub gpsPhase: f32, + pub gpsPPSValid: bool, + pub gpsSVs: [GPSSVInfo; 32], + pub gpsTime: NaiveDateTime, + pub antVoltage: f32, + pub antCurrent: f32, + pub temp: f32, + pub efcDac: u32, + pub freqError: f32, +} + +pub struct UCCMInfo { + pub vendor: String, + pub model: String, + pub serial: String, + pub version: String, +} + +impl TryFrom<&[u8]> for UCCMTODReport { + type Error = String; + fn try_from(strbuf: &[u8]) -> Result { + debug!("TOD buffer: `{:#?}`", String::from_utf8(strbuf.to_vec())); + let resp: Vec = strbuf + .split(|c| *c == ' ' as u8) + .map(|x| u8::from_str_radix(str::from_utf8(x).unwrap(), 16).unwrap()) + .collect(); + let mut rdr = Cursor::new(resp); + + // Sync flag + let c = rdr.read_u16::().unwrap(); + if c != 0xc500 { + return Err(format!("Missing start delimter (got: `{}`)", c)); + } + + // Consume 25 unknown bytes + rdr.advance(25); + let time = rdr.read_u32::().unwrap(); + rdr.advance(1); // consume padding + let leaps = rdr.read_i8().unwrap(); + let flags = rdr.read_u32::().unwrap(); + debug!("Flags: {}", flags); + rdr.advance(6); // Consume padding and checksum, don't check it + let c = rdr.read_u8().unwrap(); + if c != 0xca { + return Err(format!("Missing end delimiter (got: `{}`)", c)); + } + debug!("TOD time: {} leaps: {} flags: {}", time, leaps, flags); + + Ok(UCCMTODReport { + time: NaiveDateTime::from_timestamp_opt(GPS_EPOCH + time as i64, 0).unwrap(), + leaps, + flags: UCCMFlags::from_bits_truncate(flags), + }) + } +} + +impl UCCMMonitor { + pub fn new(config: Config) -> Self { + let builder = tokio_serial::new(&config.sources.uccm.port, config.sources.uccm.baud) + .timeout(config.sources.uccm.timeout) + .data_bits(tokio_serial::DataBits::Eight) + .parity(tokio_serial::Parity::None) + .stop_bits(tokio_serial::StopBits::One) + .flow_control(tokio_serial::FlowControl::None); + let mut port = SerialStream::open(&builder).expect("Must be able to open serial port"); + port.set_exclusive(true).expect("Can't lock serial port"); + info!( + "Opened serial port {}@{}", + port.name().unwrap(), + port.baud_rate().unwrap() + ); + let (rx, tx) = tokio::io::split(port); + UCCMMonitor { + // port, + rx, + tx, + info: None, + config: config.sources.uccm, + } + } + + pub async fn send_cmd(&mut self, cmd: &[u8]) -> Result { + debug!("cmd: `{:?}`", String::from_utf8_lossy(cmd)); + self.tx.write_all(cmd).await; + self.tx.write(&[b'\n']).await; + let mut reader = BufReader::new(&mut self.rx); + let mut resp = String::new(); + while !resp.contains("UCCM>") { + let mut buf = Vec::new(); + reader.read_until(b'>', &mut buf).await; + resp.push_str(&String::from_utf8_lossy(&buf)); + } + + // Remove the command we sent from the response + resp.replace_range(0..resp.find('\n').unwrap_or(0) + 1, ""); + resp = resp.replace("\r", ""); + debug!("cmd response: `{:?}`", resp); + Ok(resp) + } + + pub async fn get_info(&mut self) -> Result<(), std::io::Error> { + self.send_cmd(b"SCPI").await.unwrap_or_default(); + self.send_cmd(b"TOD DI").await.unwrap_or_default(); + sleep(tokio::time::Duration::from_secs(1)).await; + let resp = self.send_cmd(b"*IDN?").await.unwrap_or_default(); + let info: Vec<&str> = resp.lines().next().unwrap().split(',').collect(); + debug!("Response length: {}", info.len()); + if info.len() != 4 { + return Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "Unexpected *IDN? response", + )); + } + self.info = Some(UCCMInfo { + vendor: info[0].into(), + model: info[1].into(), + serial: info[2].into(), + version: info[3].into(), + }); + info!( + "Found {} {} s/n: {} version: {}", + self.info.as_ref().unwrap().vendor, + self.info.as_ref().unwrap().model, + self.info.as_ref().unwrap().serial, + self.info.as_ref().unwrap().version + ); + Ok(()) + } +} + +async fn rx_loop( + mut rx: ReadHalf, + chan: ChimemonSourceChannel, + state: Arc>, +) { + let mut rdbuf = BytesMut::with_capacity(1024); + loop { + match tokio::io::AsyncReadExt::read_buf(&mut rx, &mut rdbuf).await { + Ok(n) => { + if n == 0 { + continue; + } + } + Err(_) => continue, + } + match *state.lock().await { + UCCMMonitorParseState::Idle => { + while !rdbuf.starts_with(b"c5 00") && rdbuf.remaining() > 0 { + rdbuf.advance(1); + } + if rdbuf.len() < (44 * 2 + 43) { + // TOD frame is 44 bytes, plus 43 spaces + continue; + }; + let frame = rdbuf.split_to(44 * 2 + 43); + match UCCMTODReport::try_from(&frame[..]) { + Ok(tod) => { + let sysnow = Utc::now().naive_utc(); + let offset = tod.time - Duration::seconds(tod.leaps as i64) - sysnow; + debug!("System time: {:#?} GPS time: {:#?} Leaps: {:#?}", sysnow, tod.time, tod.leaps); + debug!("TOD offset: {}ms", offset.num_milliseconds()); + info!("{:#?}", tod); + let valid = tod.leaps > 0 + && tod + .flags + .contains(UCCMFlags::OSC_LOCK | UCCMFlags::HAVE_GPS_TIME); + chan.send(ChimemonMessage::TimeReport(TimeReport { + system_time: sysnow, + offset, + leaps: tod.leaps as isize, + leap_flag: if tod.flags.contains(UCCMFlags::LEAP_FLAG) { + true + } else { + false + }, + valid, + })) + .expect("Unable to send to channel"); + } + Err(e) => { + warn!("Unable to parse TOD frame: {}", e); + rdbuf.clear(); + } + } + } + UCCMMonitorParseState::ReadStatus => todo!(), + UCCMMonitorParseState::ReadLoopDiag => todo!(), + UCCMMonitorParseState::ReadTOD => todo!(), + } + } +} + +#[async_trait] +impl ChimemonSource for UCCMMonitor { + async fn run(mut self, chan: ChimemonSourceChannel) { + info!("UCCM task starting"); + if self.get_info().await.is_err() { + warn!("Error starting UCCM"); + return; + } + self.send_cmd(b"SCPI").await.unwrap(); + self.send_cmd(b"SYSTem:PRESet").await.unwrap(); + self.send_cmd(b"OUTPut:TP:SELection PP1S").await.unwrap(); + self.send_cmd(b"TESTMODE EN").await.unwrap(); + self.send_cmd(b"TOD EN").await.unwrap(); + let state = Arc::new(Mutex::::new( + UCCMMonitorParseState::Idle, + )); + let rx_handle = tokio::spawn(rx_loop(self.rx, chan.clone(), state.clone())); + // let tx_handle = tokio::spawn(async move { + // let mut interval = interval(self.config.status_interval); + // loop { + // interval.tick().await; + // let wfut = self.tx.write_all(b"SYST:STAT?\n"); + // *state.lock().await = UCCMMonitorParseState::ReadStatus; + // wfut.await; + // } + // }); + + join!(rx_handle); + } +}