Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2110f42268 | ||
|
|
5a50c3b3b9 | ||
|
|
d67863cedb | ||
|
|
e3adeae9ac | ||
|
|
625750f620 | ||
|
|
1dc782fe45 | ||
|
|
127c55c1dc | ||
|
|
61f417735e | ||
|
|
eef9d63de2 | ||
|
|
d9d9b9d344 | ||
|
|
2ff3d8df56 | ||
|
|
462672bb6a | ||
|
|
84d539a4fa | ||
|
|
850b2d411d | ||
|
|
008efe6ba1 | ||
|
|
3bd248b9fe | ||
|
|
d73cfe7a02 | ||
|
|
6d2279d44b | ||
|
|
728dd6eb26 | ||
|
|
742fd35d5f | ||
|
|
5e1126e896 | ||
|
|
e91719f902 | ||
|
|
0efa9bcb07 | ||
|
|
37d897d240 | ||
|
|
7488e1d97a | ||
|
|
0a87e45901 | ||
|
|
6205189b14 | ||
|
|
9d7da4f0af | ||
|
|
241b24be43 | ||
|
|
aa77d12879 | ||
|
|
78f183a8e5 | ||
|
|
9f6fc8184e | ||
|
|
ec3bdcb1df | ||
|
|
30292650bc | ||
|
|
1e9bcf9b19 | ||
|
|
1e88979ced | ||
|
|
1112fbaae2 | ||
|
|
1abe0bbbdb | ||
|
|
3644d16cb5 | ||
|
|
cbd82ee7c3 | ||
|
|
06ea89d500 | ||
|
|
0868a90513 | ||
|
|
39fb295b02 | ||
|
|
85dbae7458 | ||
|
|
6ce2174e25 | ||
|
|
3ccaa1cd26 | ||
|
|
72fec6dc9e | ||
|
|
547e4ac2bd | ||
|
|
5fbdf7b9ab | ||
|
|
85bd4b0d96 | ||
|
|
dfce760521 | ||
|
|
30c97bf9aa | ||
|
|
2c8373adcf | ||
|
|
d08972254c | ||
|
|
a76d39e0cd | ||
|
|
a74d0dbb80 | ||
|
|
17ab24739d | ||
|
|
09f0ea3454 | ||
|
|
0dd2494ea0 | ||
|
|
502c6d010e | ||
|
|
57c1cda229 | ||
|
|
8c2b20b148 | ||
|
|
12e7f245ce | ||
|
|
671e36fa0f | ||
|
|
1ffd7eade6 | ||
|
|
703bbfc9e6 | ||
|
|
057afb0b2e | ||
|
|
94bd29c734 | ||
|
|
d078b7b143 | ||
|
|
acbeb98f4f | ||
|
|
cab8bf062d | ||
|
|
9e04a7fa33 | ||
|
|
c92d4847ca | ||
|
|
aceeaa08c2 | ||
|
|
35946d0c88 | ||
|
|
e864a91caa | ||
|
|
a5b9a0b1d5 | ||
|
|
412d625138 | ||
|
|
2d2415a801 | ||
|
|
b4cce1dba4 | ||
|
|
f80cdb05d5 | ||
|
|
f24f31259b | ||
|
|
6411f9ff80 | ||
|
|
8b462d5a1d | ||
|
|
d8a0039db3 | ||
|
|
dec8ea481c | ||
|
|
0c8b8388d0 | ||
|
|
d3a56710d6 | ||
|
|
f9f67d3309 | ||
|
|
66122ad957 | ||
|
|
95feaf46bf | ||
|
|
88f14e0329 | ||
|
|
5bf19af844 | ||
|
|
19e2a337cf | ||
|
|
0fd47cfcb1 | ||
|
|
e0548d9a65 | ||
|
|
259ed30dd5 | ||
|
|
2e028f0c0f | ||
|
|
1328c6ce03 | ||
|
|
adc06623cd | ||
|
|
172ad0ab28 | ||
|
|
bfabb5aa13 | ||
|
|
94d5a844cf | ||
|
|
75c528a430 | ||
|
|
36045455db | ||
|
|
bb12f96723 | ||
|
|
8151c99944 | ||
|
|
6408010aff | ||
|
|
c96c96217a | ||
|
|
93d179a81d | ||
|
|
0f6f2240fc | ||
|
|
74505c6c22 | ||
|
|
3dfb879101 | ||
|
|
44d4c650cc | ||
|
|
3121679205 | ||
|
|
9fe5e582b0 | ||
|
|
3e6b838757 | ||
|
|
3d7bcfecca | ||
|
|
63a472a1ba | ||
|
|
df6b9e72c0 | ||
|
|
6ad4b9c8bb | ||
|
|
4fcc8dfe12 | ||
|
|
1da64c9cea | ||
|
|
9b6912be8d | ||
|
|
3beed9478c | ||
|
|
3715728fc6 | ||
|
|
5beee78cad | ||
|
|
32fe774e62 | ||
|
|
dead77b49c | ||
|
|
d7aa976eb1 | ||
|
|
6f9e89d616 | ||
|
|
bb3f6c0d5a | ||
|
|
620974ac59 | ||
|
|
6c78ff95e4 | ||
|
|
a814236bfa | ||
|
|
2bee030410 | ||
|
|
285ad59b08 | ||
|
|
f8b861cb39 | ||
|
|
f6a3aa6458 | ||
|
|
dd5f3e8e73 | ||
|
|
f50e32af02 | ||
|
|
345e083edd | ||
|
|
5660ddd05f | ||
|
|
c4360850af | ||
|
|
ce2123c88c | ||
|
|
520f33bf29 | ||
|
|
c620f1db7f | ||
|
|
d352e88d6f | ||
|
|
cc009b31f3 | ||
|
|
b5c189ab49 | ||
|
|
64f57277f2 | ||
|
|
af8ee60fe8 | ||
|
|
beb2e3cc2d | ||
|
|
0e2bcfd7bc | ||
|
|
417ffb23c2 | ||
|
|
a57d4cdf36 |
No files matched your search
@@ -1,3 +1,15 @@
|
||||
# `2.14.1`
|
||||
_26.01.2026_
|
||||
|
||||
## Laser
|
||||
-[#1079](https://github.com/qitechgmbh/control/pull/1079) Fixed Overflowing of buffer for Graphs causing crashes
|
||||
|
||||
# `2.14.0`
|
||||
_22.01.2026_
|
||||
|
||||
## General
|
||||
- [#1040](https://github.com/qitechgmbh/control/pull/1040) Added API for reading Machine Data
|
||||
|
||||
# `2.13.0`
|
||||
_07.01.2026_
|
||||
|
||||
@@ -5,7 +17,6 @@ _07.01.2026_
|
||||
- [#925](https://github.com/qitechgmbh/control/pull/925) Switch to Setup when selected machine is disconnected
|
||||
- [#937](https://github.com/qitechgmbh/control/pull/937) Added minimal Support for WAGO
|
||||
- [#945](https://github.com/qitechgmbh/control/pull/945) Improvements for On-Screen Keyboard usage in Ethercat Setup Page
|
||||
- []
|
||||
|
||||
## Extruder V3
|
||||
- [#930](https://github.com/qitechgmbh/control/pull/930) Added Extruder V3
|
||||
|
||||
@@ -127,16 +127,16 @@ dependencies = [
|
||||
"futures-lite",
|
||||
"parking",
|
||||
"polling",
|
||||
"rustix 1.1.2",
|
||||
"rustix 1.1.3",
|
||||
"slab",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "3.4.1"
|
||||
version = "3.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc"
|
||||
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
@@ -169,7 +169,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
"rustix 1.1.2",
|
||||
"rustix 1.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -184,7 +184,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustix 1.1.2",
|
||||
"rustix 1.1.3",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"windows-sys 0.61.2",
|
||||
@@ -204,7 +204,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -221,7 +221,7 @@ checksum = "99e1aca718ea7b89985790c94aad72d77533063fe00bc497bb79a7c2dae6a661"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -232,9 +232,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.8.7"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425"
|
||||
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
|
||||
dependencies = [
|
||||
"axum-core",
|
||||
"axum-macros",
|
||||
@@ -266,9 +266,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.5.5"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22"
|
||||
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@@ -291,7 +291,7 @@ checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -372,9 +372,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
version = "3.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
@@ -405,9 +405,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.46"
|
||||
version = "1.2.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36"
|
||||
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
@@ -427,9 +427,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.42"
|
||||
version = "0.4.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
|
||||
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
@@ -465,18 +465,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.51"
|
||||
version = "4.5.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
|
||||
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.51"
|
||||
version = "4.5.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
|
||||
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
@@ -484,9 +484,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.6"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
@@ -539,7 +539,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -580,9 +580,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.3.0"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675"
|
||||
checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
|
||||
dependencies = [
|
||||
"crc-catalog",
|
||||
]
|
||||
@@ -668,9 +668,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.9.0"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
||||
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
|
||||
|
||||
[[package]]
|
||||
name = "deluxe"
|
||||
@@ -682,7 +682,7 @@ dependencies = [
|
||||
"deluxe-macros",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -695,7 +695,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -705,12 +705,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f87546d9c837f0b7557e47b8bd6eae52c3c223141b76aa233c345c9ab41d9117"
|
||||
dependencies = [
|
||||
"deluxe-core",
|
||||
"heck",
|
||||
"heck 0.4.1",
|
||||
"if_chain",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -721,7 +721,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -962,7 +962,7 @@ dependencies = [
|
||||
"deluxe",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1014,14 +1014,14 @@ dependencies = [
|
||||
"criterion",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "euclid"
|
||||
version = "0.22.11"
|
||||
version = "0.22.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48"
|
||||
checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
@@ -1055,9 +1055,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.5"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
|
||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
@@ -1170,9 +1170,9 @@ checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.12"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
|
||||
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
@@ -1223,9 +1223,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "heapless"
|
||||
@@ -1243,6 +1243,12 @@ version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.2"
|
||||
@@ -1251,12 +1257,11 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.3.1"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
|
||||
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
@@ -1339,9 +1344,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.18"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56"
|
||||
checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
@@ -1352,7 +1357,7 @@ dependencies = [
|
||||
"hyper",
|
||||
"libc",
|
||||
"pin-project-lite",
|
||||
"socket2 0.6.1",
|
||||
"socket2 0.6.2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -1390,9 +1395,9 @@ checksum = "cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.12.0"
|
||||
version = "2.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
|
||||
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
@@ -1466,15 +1471,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.82"
|
||||
version = "0.3.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65"
|
||||
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
@@ -1488,9 +1493,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.177"
|
||||
version = "0.2.180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@@ -1551,9 +1556,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.28"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "mach2"
|
||||
@@ -1577,11 +1582,14 @@ dependencies = [
|
||||
"ethercrab",
|
||||
"euclid",
|
||||
"lazy_static",
|
||||
"modbus",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serialport",
|
||||
"smol",
|
||||
"socketioxide",
|
||||
"strum",
|
||||
"strum_macros 0.26.4",
|
||||
"textplots",
|
||||
"tonic",
|
||||
"tracing",
|
||||
@@ -1660,15 +1668,25 @@ checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.1.0"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
|
||||
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "modbus"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/just-some-entity/modbus-rs.git#f1fff0ebd09334d6de29b2faa00336d031741dbb"
|
||||
dependencies = [
|
||||
"crc",
|
||||
"heapless",
|
||||
"strum_macros 0.27.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nb"
|
||||
version = "0.1.3"
|
||||
@@ -1821,17 +1839,11 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pcap"
|
||||
version = "2.3.0"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83cdabc34a80d9ec3563694cc31423fba6bb9bab4f31a9a5d5b85f29bd6d660a"
|
||||
checksum = "bbd2eecc2ddc671ec563b5b39f846556aade68a65d1afb14d8fe6b30b0457d75"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"errno 0.2.8",
|
||||
@@ -1850,9 +1862,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.8.3"
|
||||
version = "2.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4"
|
||||
checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"ucd-trie",
|
||||
@@ -1860,9 +1872,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.8.3"
|
||||
version = "2.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de"
|
||||
checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
@@ -1870,22 +1882,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.8.3"
|
||||
version = "2.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843"
|
||||
checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.8.3"
|
||||
version = "2.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a"
|
||||
checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"sha2",
|
||||
@@ -1908,7 +1920,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1982,7 +1994,7 @@ dependencies = [
|
||||
"concurrent-queue",
|
||||
"hermit-abi",
|
||||
"pin-project-lite",
|
||||
"rustix 1.1.2",
|
||||
"rustix 1.1.3",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
@@ -2007,9 +2019,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -2072,9 +2084,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
@@ -2128,31 +2140,28 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rmp"
|
||||
version = "0.8.14"
|
||||
version = "0.8.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4"
|
||||
checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"num-traits",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp-serde"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db"
|
||||
checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"rmp",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.26"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
@@ -2175,9 +2184,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
|
||||
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"errno 0.3.14",
|
||||
@@ -2194,9 +2203,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
@@ -2221,7 +2230,7 @@ checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2251,20 +2260,20 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.145"
|
||||
version = "1.0.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2381,10 +2390,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.6"
|
||||
version = "1.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
|
||||
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||
dependencies = [
|
||||
"errno 0.3.14",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -2453,9 +2463,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.1"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
|
||||
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
@@ -2560,6 +2570,37 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
@@ -2573,9 +2614,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.111"
|
||||
version = "2.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2606,22 +2647,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.17"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.17"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2660,15 +2701,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.48.0"
|
||||
version = "1.49.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
||||
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"socket2 0.6.1",
|
||||
"socket2 0.6.2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
@@ -2681,14 +2722,14 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.17"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
|
||||
checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
@@ -2709,9 +2750,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.17"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
|
||||
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@@ -2768,9 +2809,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
|
||||
checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
@@ -2787,9 +2828,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.6.6"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
||||
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bytes",
|
||||
@@ -2843,7 +2884,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2858,9 +2899,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-journald"
|
||||
version = "0.3.1"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0b4143302cf1022dac868d521e36e8b27691f72c84b3311750d5188ebba657"
|
||||
checksum = "2d3a81ed245bfb62592b1e2bc153e77656d94ee6a0497683a65a12ccaf2438d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"tracing-core",
|
||||
@@ -2880,9 +2921,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.20"
|
||||
version = "0.3.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
|
||||
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"matchers",
|
||||
@@ -2940,18 +2981,18 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
||||
|
||||
[[package]]
|
||||
name = "unescaper"
|
||||
version = "0.1.6"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c01d12e3a56a4432a8b436f293c25f4808bdf9e9f9f98f9260bba1f1bc5a1f26"
|
||||
checksum = "4064ed685c487dbc25bd3f0e9548f2e34bab9d18cefc700f9ec2dba74ba1138e"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.8.1"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
|
||||
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
@@ -3035,18 +3076,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.105"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60"
|
||||
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -3057,9 +3098,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.105"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2"
|
||||
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -3067,22 +3108,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.105"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc"
|
||||
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.105"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76"
|
||||
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -3139,7 +3180,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3150,7 +3191,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3396,9 +3437,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.46.0"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
@@ -3411,20 +3452,26 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.27"
|
||||
version = "0.8.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
|
||||
checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.27"
|
||||
version = "0.8.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
||||
checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439"
|
||||
@@ -17,22 +17,22 @@ QiTech Control combines the modularity and reliability of standard EtherCAT term
|
||||
|
||||
# Repo Structure
|
||||
|
||||
Frontend
|
||||
**Frontend**
|
||||
|
||||
- `/electron`: Frontend code for the control software built with React and Electron.
|
||||
|
||||
Backend
|
||||
**Backend**
|
||||
|
||||
- `/server`: Glue between Beckhoff and Electron. Implements machine logic.
|
||||
- `/ethercat-hal`: Hardware abstraction layer for Beckhoff (and possibly other EtherCat) devices and protocols.
|
||||
- `/ethercat-hal-derive`: Macros for `ethercat-hal`
|
||||
- `/control-core`: Core control logic for the server.
|
||||
|
||||
Operating System
|
||||
**Operating System**
|
||||
|
||||
- `/nixos`: Custom Linux with realtime kernel & preconfigured for touchscreens.
|
||||
|
||||
Other
|
||||
**Other**
|
||||
|
||||
- `/docs`: Documentation for the project.
|
||||
|
||||
@@ -54,115 +54,57 @@ We could combine the code of the frontend and backend using [Doxius](https://dio
|
||||
|
||||
[Developer Documentation](./docs/developer-docs/)
|
||||
|
||||
## Backend
|
||||
[API Documentation](https://github.com/qitechgmbh/control/blob/master/docs/rest-api.md)
|
||||
|
||||
-> Backend
|
||||
- Rust stable 1.86^ toolchain (install via [rustup](https://rustup.rs/))
|
||||
- `rust-analyzer` extension for VSCode
|
||||
- Set your interface in `server/src/ethercat/init.rs` like `en10`
|
||||
- Connect a Beckhoff EK1100 to your interface
|
||||
- run `cd server && cargo run` to start the server (localhost:3001)
|
||||
|
||||
## Frontend
|
||||
|
||||
-> Frontend
|
||||
- nodejs and npm installed
|
||||
- run `cd electron && npm i && npm run start` to start the frontend
|
||||
|
||||
# Machines
|
||||
|
||||
| Machine Type | Version | Release Date | Description | Change to Previous Version | Vendor ID | Machine ID | Implemented | Docs |
|
||||
| ------------ | ------- | ------------ | --------------------------- | ------------------------------------------------------ | -------------------------- | ---------- | ----------- | ------------------------------- |
|
||||
| Winder | V1 | ??? | Winding Filaments & Similar | - | 1 (Qitech Industries GmbH) | 1 | Reserved | - |
|
||||
| Winder | V2 | 2025 | Winding Filaments & Similar | Reengineered Traverse | 1 (Qitech Industries GmbH) | 2 | Yes | [](./docs/machines/winder-1.md) |
|
||||
| Extruder | V1 | ??? | Single Screw Extruder | - | 1 (Qitech Industries GmbH) | 3 | Reserved | - |
|
||||
| Extruder | V2 | 2025 | Single Screw Extruder | PT100 Thermometers, Optional Additional Heating Zone 4 | 1 (Qitech Industries GmbH) | 4 | Yes | |
|
||||
| Waterway | V1 | 2025 | Filament Water Cooling | - | 1 (Qitech Industries GmbH) | 5 | In Progress | |
|
||||
| Laser | V1 | ??? | Diameter Measuring Laser | - | 1 (Qitech Industries GmbH) | 6 | Yes | |
|
||||
| Mock | - | ??? | Mock Machine for Testing | - | 1 (Qitech Industries GmbH) | 7 | Yes | - |
|
||||
|
||||
# Minimal Hardware Examples
|
||||
## Minimal Hardware Examples
|
||||
|
||||
For complete step-by-step tutorials on setting up your first hardware, including wiring diagrams and software configuration, see the [Getting Started Guide](./docs/developer-docs/getting-started.md#minimal-hardware-examples).
|
||||
|
||||
Available examples:
|
||||
- **LED Control with EL2004** - Digital output control (simplest setup)
|
||||
- **Analog Input with EL3021** - Reading analog current measurements
|
||||
| Example | Vendor | Hardware | Description | How-To Video | Docs |
|
||||
| :----------------- | :------- | :------------ | :-------------------------------------- | :----------- | :---------------------------- |
|
||||
| **Digital Input** | WAGO | 750-402 | 4-Channel Digital Input | [ ] | - |
|
||||
| **Digital Output** | WAGO | 750-753 | 8-Channel Digital Output | [ ] | - |
|
||||
| **Analog Input** | WAGO | 750-455 | 4-Channel Analog Input | [ ] | - |
|
||||
| **Analog Output** | WAGO | 750-553 | 4-Channel Analog Output | [ ] | - |
|
||||
| **Serial Comms** | WAGO | 750-652 | RS-485 Modbus Module | [ ] | - |
|
||||
| **Stepper Drive** | WAGO | 750-67x | Stepper Controller | [ ] | - |
|
||||
| **Power Supply** | WAGO | 2787-214 | Power Supply Unit | [ ] | - |
|
||||
| **LED Control** | Beckhoff | EL2004 | Digital output control (simplest setup) | [ ] | [](./docs/hardware/el2004.md) |
|
||||
| **Analog Input** | Beckhoff | EL3021 | Reading analog current measurements | [ ] | [](./docs/hardware/el3021.md) |
|
||||
|
||||
# QiTech Machines
|
||||
|
||||
# More Docs
|
||||
| Machine Name | Description | Machine ID | Implemented | Video | Docs |
|
||||
| :---------------- | :-------------------------- | :--------- | :---------- | :-------------------------------------------------------------- | :------------------------------ |
|
||||
| Winder V1 | Winding Filaments & Similar | 1 | Legacy | [Video](https://youtu.be/4aE4cFhioKA?si=Xdj_LVnFrAYWnLm6) | - |
|
||||
| Winder V2 | Winding Filaments & Similar | 2 | Yes | [Video](https://youtu.be/f2kzh6kpQWE?si=HEYcLOaC9gWSp2Wo) | [](./docs/machines/winder-1.md) |
|
||||
| XL Winder V1 | Large Scale Winder | 3 | Yes | [Video](https://youtu.be/ynI6ioWIQQY?si=4EDVKXbgIqrvGGtY) | - |
|
||||
| Buffer V1 | Filament buffering system | 4 | In Progress | [Video](https://youtu.be/VR5mEdZDPA0?si=eip7dQXU4zaXK7Ev) | - |
|
||||
| Extruder V1 | Single Screw Extruder | 5 | Legacy | [Video](https://youtu.be/gchoG-yGczI?si=bEM4daf5eOVaU5_2) | - |
|
||||
| Extruder V2 | Single Screw Extruder | 6 | Yes | [Video](https://youtu.be/mexRYGDNWa4?si=h7JBz_XKMwKLPenG) | |
|
||||
| Waterway V1 | Filament Water Cooling | 7 | In Progress | [Video](https://youtu.be/_T5z1J8bl_k?si=uOC1hIQ1EAP0cHIF) | |
|
||||
| Laser V1 | Diameter Measuring Laser | 8 | Yes | [Video](https://youtu.be/WDM34lj4afM?si=MZzUKkHCrzH4P0aA) | |
|
||||
| 2-Axis-Laser V1 | Dual Axis Laser | 9 | Yes | [Video](https://youtu.be/WDM34lj4afM?si=MZzUKkHCrzH4P0aA) | - |
|
||||
| Mock | Mock Machine for Testing | 10 | Yes | | - |
|
||||
| Extruder V3 | Single Screw Extruder | 11 | Yes | [Video](https://youtu.be/ipHHuPzCvn4?si=hkZ1b93rVuwDxhFD) | |
|
||||
| Mini Schredder V1 | mini plastic crusher | 12 | Legacy | [Video](https://youtu.be/m8NplNqdu2Q?si=x6zjDktJtpbSMSmu) | - |
|
||||
| Pro Schredder V1 | large plastic crusher | 13 | Legacy | [Video](https://youtu.be/pSaVMqp06pU?si=y61enutRpxKscBm4) | - |
|
||||
| Dryer V1 | polymer dryer | 14 | Reserved | [Video](https://youtu.be/6hdmUUAdZp0?si=eKOW1WlgkqTpnfdS) | - |
|
||||
| Pelletizier V1 | Filament chopper | 15 | Reserved | | - |
|
||||
|
||||
- [x] [Architecture & Data Flow](./docs/architecture-overview.md)
|
||||
# Current Restructuring Efforts
|
||||
|
||||
- [x] Example Winder V2
|
||||
|
||||
- [ ] Electron
|
||||
|
||||
- Folder Structure
|
||||
- Routing with TanStack Router
|
||||
- Design with Tailwind & Shadcn
|
||||
- ...
|
||||
|
||||
- [ ] Interfacing with Electron/Server
|
||||
|
||||
- [ ] SocketIO
|
||||
- Machine Namespace
|
||||
- Main Namespace
|
||||
- [ ] REST
|
||||
- Machine Mutations
|
||||
- Write Device Identification
|
||||
|
||||
- Server
|
||||
|
||||
- [x] [Threading](./docs/control-loop.md)
|
||||
- [x] [Logging](./docs/logging.md)
|
||||
- [ ] Control Loop Setup
|
||||
- Control Loop Thread
|
||||
- [ ] realtime
|
||||
- Maindevice
|
||||
- Group
|
||||
- Extracting Device Identifications
|
||||
- Identifying Groups
|
||||
- Validating Machines
|
||||
- Run Control Loop
|
||||
- [x] [Control Loop](./docs/control-loop.md)
|
||||
- [x] [Machine/Device Identification](./docs/identification.md)
|
||||
- [ ] Machines
|
||||
- When to create a new Machine?
|
||||
- Versioning
|
||||
- Code sharing
|
||||
- Creating/Validating a Machine
|
||||
- Validation
|
||||
- Configuration
|
||||
- [ ] Machine Implementation Guide
|
||||
- Link: How to create a Device
|
||||
- Link: How to create an Actor
|
||||
- Link: How to create a Machine
|
||||
- API (SocketIO + REST)
|
||||
- Creation/Validation Logic
|
||||
- Optional/Mandatory Devices
|
||||
- Validate Devices
|
||||
- Business Logic
|
||||
- Link: How to create Machine Abstraction (Like Traverse/Puller/...)
|
||||
- Forward `act` in winder.
|
||||
|
||||
- [ ] Control Core
|
||||
|
||||
- [x] [Actors](./docs/actors.md)
|
||||
- [ ] SocketIO
|
||||
- Namespaces & Caching
|
||||
- Joining leaving namespaces
|
||||
- NamespaceId
|
||||
- Caching
|
||||
- Serverside Caching
|
||||
- Clientside Caching
|
||||
- [ ] REST
|
||||
|
||||
- [x] Ethercat HAL
|
||||
|
||||
- [x] [Devices](./docs/devices.md)
|
||||
- [x] [Configuration (CoE)](./docs/coe.md)
|
||||
- [x] [IO](./docs/io.md)
|
||||
- [x] [PDO](./docs/pdo.md)
|
||||
|
||||
- [x] [Ethercat Basics](./docs/ethercat-basics.md)
|
||||
|
||||
- [x] [NixOS Operating System](./docs/nixos/README.md)
|
||||
[](https://www.youtube.com/live/UlbnSVIhfLI?si=ZTotC5B7gd87tUim)
|
||||
*Video: Live stream of software dev meeting*
|
||||
@@ -47,7 +47,7 @@ async fn probe_modbus_tcp_addresses(interface: Interface) -> Vec<ModbusTcpProbe>
|
||||
let prefix = min(24, m.count_ones()); // mask → prefix length
|
||||
let size = 1u32 << (32 - prefix); // number of addresses
|
||||
|
||||
(200..size).map(move |i| SocketAddr::new(Ipv4Addr::from(network + i).into(), 502))
|
||||
(0..size).map(move |i| SocketAddr::new(Ipv4Addr::from(network + i).into(), 502))
|
||||
})
|
||||
.map(|addr| smol::spawn(ping_modbus_device(addr)))
|
||||
.join_all()
|
||||
|
||||
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 6.6 MiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 5.2 MiB |
|
After Width: | Height: | Size: 4.3 MiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 5.9 MiB |
|
After Width: | Height: | Size: 203 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 5.4 MiB |
|
After Width: | Height: | Size: 5.9 MiB |
|
After Width: | Height: | Size: 55 KiB |
@@ -91,3 +91,83 @@ To get started with actual hardware, check out these step-by-step tutorials:
|
||||
- **[Analog Input with EL3021](./minimal-example-el3021.md)** - Reading analog current measurements
|
||||
|
||||
These examples provide complete hardware wiring diagrams and software setup instructions.
|
||||
|
||||
|
||||
# More Docs
|
||||
|
||||
- [x] [Architecture & Data Flow](./docs/architecture-overview.md)
|
||||
|
||||
- [x] Example Winder V2
|
||||
|
||||
- [ ] Electron
|
||||
|
||||
- Folder Structure
|
||||
- Routing with TanStack Router
|
||||
- Design with Tailwind & Shadcn
|
||||
- ...
|
||||
|
||||
- [ ] Interfacing with Electron/Server
|
||||
|
||||
- [ ] SocketIO
|
||||
- Machine Namespace
|
||||
- Main Namespace
|
||||
- [ ] REST
|
||||
- Machine Mutations
|
||||
- Write Device Identification
|
||||
|
||||
- Server
|
||||
|
||||
- [x] [Threading](./docs/control-loop.md)
|
||||
- [x] [Logging](./docs/logging.md)
|
||||
- [ ] Control Loop Setup
|
||||
- Control Loop Thread
|
||||
- [ ] realtime
|
||||
- Maindevice
|
||||
- Group
|
||||
- Extracting Device Identifications
|
||||
- Identifying Groups
|
||||
- Validating Machines
|
||||
- Run Control Loop
|
||||
- [x] [Control Loop](./docs/control-loop.md)
|
||||
- [x] [Machine/Device Identification](./docs/identification.md)
|
||||
- [ ] Machines
|
||||
- When to create a new Machine?
|
||||
- Versioning
|
||||
- Code sharing
|
||||
- Creating/Validating a Machine
|
||||
- Validation
|
||||
- Configuration
|
||||
- [ ] Machine Implementation Guide
|
||||
- Link: How to create a Device
|
||||
- Link: How to create an Actor
|
||||
- Link: How to create a Machine
|
||||
- API (SocketIO + REST)
|
||||
- Creation/Validation Logic
|
||||
- Optional/Mandatory Devices
|
||||
- Validate Devices
|
||||
- Business Logic
|
||||
- Link: How to create Machine Abstraction (Like Traverse/Puller/...)
|
||||
- Forward `act` in winder.
|
||||
|
||||
- [ ] Control Core
|
||||
|
||||
- [x] [Actors](./docs/actors.md)
|
||||
- [ ] SocketIO
|
||||
- Namespaces & Caching
|
||||
- Joining leaving namespaces
|
||||
- NamespaceId
|
||||
- Caching
|
||||
- Serverside Caching
|
||||
- Clientside Caching
|
||||
- [ ] REST
|
||||
|
||||
- [x] Ethercat HAL
|
||||
|
||||
- [x] [Devices](./docs/devices.md)
|
||||
- [x] [Configuration (CoE)](./docs/coe.md)
|
||||
- [x] [IO](./docs/io.md)
|
||||
- [x] [PDO](./docs/pdo.md)
|
||||
|
||||
- [x] [Ethercat Basics](./docs/ethercat-basics.md)
|
||||
|
||||
- [x] [NixOS Operating System](./docs/nixos/README.md)
|
||||
@@ -0,0 +1,196 @@
|
||||
# Minimal Example — Analog Input Wago 750-455
|
||||
|
||||
## Table of Contents
|
||||
1. [Introduction](#1-introduction)
|
||||
2. [Requirements](#2-requirements)
|
||||
3. [Hardware Setup](#3-hardware-setup)
|
||||
4. [Software Setup](#4-software-setup)
|
||||
5. [Demo](#5-demo)
|
||||
6. [References](#6-references)
|
||||
|
||||
## 1. Introduction
|
||||
The Wago 750-455 Analog Input example demonstrates how to measure analog current on a **Wago 750-455 EtherCAT module** using the QiTech machine framework. This module provides 4 independent analog input channels that can read 4-20mA signals.
|
||||
|
||||
## 2. Requirements
|
||||
|
||||
### Hardware
|
||||
- **Wago 750-354 EtherCAT Coupler**
|
||||
- **Wago 750-602 Power Supply** (24V VDC)
|
||||
- **Wago 750-455 EtherCAT Module** (4-channel analog input, 4-20mA)
|
||||
- **Wago 750-600 End Module**
|
||||
- **24 V DC power supply** for the Wago system
|
||||
- **Adjustable DC power supply** (or any other means of producing 4-20mA DC @ <10V)
|
||||
- Jumper / bridge wires (0.5–1.5 mm² recommended)
|
||||
- A **Linux PC** (Ubuntu/Debian recommended)
|
||||
- Standard Ethernet cable
|
||||
- Flat screwdriver
|
||||
|
||||
### Software
|
||||
*(Installation steps in Section 4)*
|
||||
- Rust toolchain
|
||||
- Node.js + npm
|
||||
- Git
|
||||
- QiTech Control repository
|
||||
- EtherCAT HAL (included inside repo)
|
||||
|
||||
## 3. Hardware Setup
|
||||
|
||||
### 3.1 Overview
|
||||
The Wago 750-455 is a 4-channel analog input module that measures current signals in the 4-20mA range. Each channel has its own input terminals and can operate independently.
|
||||
|
||||
#### ⚠️ Safety Warning
|
||||
Always disconnect power before wiring.
|
||||
Working on live EtherCAT modules can cause serious damage or electrical shock.
|
||||
|
||||
### 3.2 Wiring Procedure
|
||||
|
||||
#### 3.2.1 Safe Wiring (Wago Spring Clamp)
|
||||
1. Insert a screwdriver **straight** into the square release hole.
|
||||
2. Insert the stripped wire into the round opening.
|
||||
3. Remove the screwdriver — the spring clamp locks the wire.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### 3.3 Wago 750-455 Module Wiring
|
||||
|
||||
The Wago 750-455 has 4 analog input channels. Each channel requires two terminals:
|
||||
|
||||
**Channel 1 (AI1):**
|
||||
- Terminal 1: ⊕ (Positive input)
|
||||
- Terminal 2: ⊖ (Negative input/Ground)
|
||||
|
||||
**Channel 2 (AI2):**
|
||||
- Terminal 3: ⊕ (Positive input)
|
||||
- Terminal 4: ⊖ (Negative input/Ground)
|
||||
|
||||
**Channel 3 (AI3):**
|
||||
- Terminal 5: ⊕ (Positive input)
|
||||
- Terminal 6: ⊖ (Negative input/Ground)
|
||||
|
||||
**Channel 4 (AI4):**
|
||||
- Terminal 7: ⊕ (Positive input)
|
||||
- Terminal 8: ⊖ (Negative input/Ground)
|
||||
|
||||
For this minimal example, we'll connect at least one channel to an adjustable power supply:
|
||||
|
||||
1. Wire adjustable power supply ⊕ to **Terminal 1** (Channel 1 positive)
|
||||
2. Wire adjustable power supply ⊖ to **Terminal 2** (Channel 1 negative)
|
||||
|
||||
*Repeat for additional channels as needed.*
|
||||
|
||||
---
|
||||
|
||||
### 3.4 Wago I/O System Integration
|
||||
|
||||
The Wago 750-455 module plugs into a Wago fieldbus system. Consult your specific Wago controller/coupler documentation for:
|
||||
- Power supply requirements
|
||||
- Network configuration
|
||||
- Module placement in the I/O rack
|
||||
|
||||
The module automatically connects to the backplane for EtherCAT communication and power.
|
||||
|
||||
---
|
||||
|
||||
### 3.5 Power & Ethernet
|
||||
|
||||
#### Power
|
||||
Ensure your Wago system has adequate 24V DC power connected per manufacturer specifications.
|
||||
|
||||
#### Ethernet
|
||||
Connect your PC to the Wago controller/coupler using a standard Ethernet cable.
|
||||
|
||||
---
|
||||
|
||||
## 4. Software Setup
|
||||
|
||||
### 4.1 Installing on Ubuntu/Debian
|
||||
|
||||
Paste this into your terminal:
|
||||
|
||||
```bash
|
||||
# Press Enter when prompted
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
|
||||
sudo apt update
|
||||
sudo apt install -y npm nodejs git
|
||||
|
||||
git clone git@github.com:qitechgmbh/control.git
|
||||
cd control/electron
|
||||
npm install
|
||||
```
|
||||
|
||||
### 4.2 Running the Backend
|
||||
|
||||
```bash
|
||||
./cargo_run_linux.sh
|
||||
```
|
||||
|
||||
This script:
|
||||
- Builds the backend
|
||||
- Grants required system capabilities (raw sockets)
|
||||
- Starts EtherCAT communication
|
||||
|
||||
Ensure the Wago controller is connected and powered.
|
||||
|
||||
### 4.3 Running the Frontend
|
||||
|
||||
```bash
|
||||
cd electron
|
||||
npm run start
|
||||
```
|
||||
|
||||
This launches the QiTech Control dashboard.
|
||||
|
||||
---
|
||||
|
||||
## 5. Demo
|
||||
|
||||
### 5.1 Assigning Devices in the Dashboard
|
||||
|
||||
Once both backend and frontend are running:
|
||||
|
||||
1. Navigate to **Setup → EtherCAT** page
|
||||
2. You should see your Wago controller and the 750-455 module listed
|
||||
3. Select the Device called 750-354 and assign the Wago AI Test V1 Machine, set the Serial to 1 and set the device role to 0 (Wago Bus Coupler)
|
||||
4. Press write thn restart the backend to load the changes
|
||||
|
||||
### 5.2 Testing Analog Input
|
||||
|
||||
1. Set your adjustable power supply to output a current in the 4-20mA range
|
||||
2. In the dashboard, navigate to the monitoring view
|
||||
3. Observe the analog input value for the connected channel(s)
|
||||
|
||||
### 5.3 Channel Specifications
|
||||
|
||||
The Wago 750-455 provides:
|
||||
- **Input Range:** 4-20mA
|
||||
- **Wiring Error Detection:** Built-in detection for open/short circuit conditions
|
||||
- **4 Independent Channels:** Each can be read separately
|
||||
|
||||
### 5.4 Error Detection
|
||||
|
||||
The module includes wiring error detection. If you see a wiring error flag:
|
||||
- Check that your current source is within the 4-20mA range
|
||||
- Verify all wire connections are secure
|
||||
- Ensure the current loop is properly closed
|
||||
|
||||
---
|
||||
|
||||
## 6. References
|
||||
|
||||
### Wago Documentation
|
||||
- [Wago 750-455 Product Page](https://www.wago.com/de-en/io-systems/4-channel-analog-input/p/750-455)
|
||||
- Wago I/O System manual
|
||||
- EtherCAT configuration guide for Wago systems
|
||||
|
||||
### QiTech Control
|
||||
- [Architecture Overview](../architecture-overview.md)
|
||||
- [EtherCAT Basics](../ethercat-basics.md)
|
||||
- [Device Documentation](../devices.md)
|
||||
- [Getting Started Guide](getting-started.md)
|
||||
|
||||
### Related Examples
|
||||
- [Minimal Example — EL3021](minimal-example-el3021.md) (Single channel analog input)
|
||||
- [Minimal Example — EL2004](minimal-example-el2004.md) (Digital output)
|
||||
@@ -0,0 +1,240 @@
|
||||
# Minimal Example — LED Control on the WAGO750-402
|
||||
A complete hardware + software walkthrough
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
1. [Introduction](#1-introduction)
|
||||
2. [Requirements](#2-requirements)
|
||||
3. [Hardware Setup](#3-hardware-setup)
|
||||
4. [Software Setup](#4-software-setup)
|
||||
5. [Demo](#5-demo)
|
||||
6. [References](#6-references)
|
||||
|
||||
---
|
||||
|
||||
## 1. Introduction
|
||||
The WAGO 750-402 Display Example is a minimal demonstation showing how to display the digital inputs on a WAGO 750-402 EtherCAT terminal using the QiTech machine framework.
|
||||
It represents the simplest possible hardware interaction in the system:
|
||||
Displaying the Digital Inputs using the QiTech Control Dashboard.
|
||||
## 2. Requirements
|
||||
|
||||
### Hardware
|
||||
- WAGO **2787-2144 EtherCAT**
|
||||
- WAGO **750-354 EtherCAT Coupler**
|
||||
- WAGO **750-602 EtherCAT Power Terminal**
|
||||
- WAGO **750-402 EtherCAT Terminal** (4.channle digital input)
|
||||
- WAGO **750-600 EtherCAT Endmodule**
|
||||
- WAGO **852-1322 Ethernet-Switch**
|
||||
- **24V DC Lab power supply**(AC/DC adapter)
|
||||
- A **Linux PC** (Ubuntu/Debian recommended)
|
||||
- Standard Ethernet cable
|
||||
- Flat screwdriver
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
### Software
|
||||
*(Installation steps in Section 4)*
|
||||
|
||||
- Rust tollchain
|
||||
- Node-js + npm
|
||||
- Git
|
||||
- QiTech Control repository
|
||||
- EtherCAT HAL *(include inside repo)*
|
||||
|
||||
## 3. Hardware Setup
|
||||
|
||||
### 3.1 Schematic
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
#### ⚠️ Safety Warning
|
||||
Always disconnect power before wiring.
|
||||
Working on live EtherCAT terminals can cause serious damage or electrical shock.
|
||||
|
||||
### 3.2.1 Safe Wiring Procedure (Beckhoff Recommended)
|
||||
1. Insert a screwdriver **straight** into the square release hole.
|
||||
2. Insert the stripped wire into the round opening.
|
||||
3. Remove the screwdriver — the spring clamp locks the wire.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### 3.2.2 Wiring Steps (Used in This Example)
|
||||
|
||||
We supply power to the other devices by using the **750-602 EtherCAT Power Terminal**
|
||||
|
||||
Perform the following wiring on the Ethernet-Switch:
|
||||
|
||||
1. Red wire (+24V) → PWR+
|
||||
2. Black wire (0V) → RPS-
|
||||
|
||||
#### **Figure 1 —**
|
||||
<img src="../assets/WAGO750_402_switch.JPG" width="400">
|
||||
|
||||
---
|
||||
|
||||
### 3.3 WAGO Wiring the Coupler
|
||||
|
||||
Wire the **750-354 EtherCAT Coupler** to the power supply
|
||||
1. red wire (+24V)
|
||||
2. yellow (0V)
|
||||
|
||||
#### **Figure 2 —**
|
||||
<img src="../assets/WAGO750_402_coupler.JPG" width="400">
|
||||
|
||||
---
|
||||
|
||||
### 3.4 750-602 EtherCAT Power Terminal Integration
|
||||
|
||||
Slide the **750-602 EtherCAT Power Terminal** onto the **right side of the 750-354 Coupler** until it locks.
|
||||
|
||||
Next We need to wire the EtherCat Power Terminal
|
||||
|
||||
Perform the next wiring Steps:
|
||||
|
||||
1. Green wire (Ground) → **Terminal4**
|
||||
2. Red wire (+24V) from **750-602 EtherCAT Power Terminal** → **Terminal 2**
|
||||
3. Black wire(0V) from **750-602 EtherCAT Power Terminal** → **Terminal 3**
|
||||
4. Red wire (+24V) from **750-354 EtherCAT Coupler** → **Terminal 6**
|
||||
5. Yellow wire (0V) from **750-354 EtherCAT Coupler** → **Terminal 7**
|
||||
|
||||
#### **Figure 3 — Wiring**
|
||||
<img src="../assets/WAGO750_402_power_terminal.JPG" width="400">
|
||||
|
||||
---
|
||||
|
||||
## 3.5 750-402 EtherCAT Terminal Integration
|
||||
|
||||
Slide the **750-402 EtherCAT Terminal** onto to **right side of the 750-602 EtherCAT Power Terminal** until it locks.
|
||||
|
||||
Wire from the **24V DC Lab Adapter** → **Terminal 1**
|
||||
|
||||
#### **Figure 4 — Wiring**
|
||||
<img src="../assets/WAGO750_402_terminal.JPG" width="400">
|
||||
|
||||
## 3.6 750-600 Final Assemly
|
||||
|
||||
Slide the **750-600 EtherCAT Endmodule** onto to **right side of the 7750-402 EtherCAT Terminal** until it locks.
|
||||
|
||||
No wiring is required
|
||||
|
||||
#### **Figure 5 — 750-402 + 750-600 Connected**
|
||||
<img src="../assets/WAGO750_402_end_modul.JPG" width="400">
|
||||
|
||||
## 3.7 Power & Etherneet
|
||||
|
||||
### Power
|
||||
|
||||
Connect the 2787-2144 power supply to the power outlet
|
||||
|
||||
### Ehternet
|
||||
|
||||
Use a standard LAN cable to connect to your PC → **852-1322 Ethernet-Switch**
|
||||
|
||||
And another from **852-1322 Ethernet-Switch** → **750-354 EtherCAT Coupler**
|
||||
|
||||
#### **Figure 6 — Ethernet**
|
||||
<img src="../assets/WAGO750_402_ethernet.JPG" width="400">
|
||||
|
||||
## 4. Software Setup
|
||||
|
||||
### 4.1 Installing on Ubuntu/Debian
|
||||
Paste this into your terminal:
|
||||
|
||||
```bash
|
||||
# Press Enter when prompted
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
|
||||
sudo apt update
|
||||
sudo apt install -y npm nodejs git
|
||||
|
||||
git clone git@github.com:qitechgmbh/control.git
|
||||
cd control/electron
|
||||
npm install
|
||||
```
|
||||
### 4.2 Running the Backend
|
||||
```bash
|
||||
./cargo_run_linux.sh
|
||||
```
|
||||
This script:
|
||||
|
||||
- Builds the backend
|
||||
|
||||
- Grants required system capabilities (raw sockets)
|
||||
|
||||
- Starts EtherCAT communication
|
||||
|
||||
Ensure the WAGO750-402 is connected.
|
||||
|
||||
### 4.3 Running the Frontend
|
||||
```bash
|
||||
cd electron
|
||||
npm run start
|
||||
```
|
||||
|
||||
This launches the QiTech Control dashboard.
|
||||
|
||||
## 5. Demo
|
||||
|
||||
### 5.1 Assigning Devices in the Dashboard
|
||||
|
||||
Once the backend + frontend are running, you should see:
|
||||
|
||||
- WAGO750-354 (Coupler)
|
||||
|
||||
- wago module (Digital Input Terminal)
|
||||
|
||||

|
||||
|
||||
Steps:
|
||||
|
||||
- Click Assign on the wago module
|
||||
|
||||
- Select Digital Input Machine V1
|
||||
|
||||

|
||||
|
||||
|
||||
- Enter a serial number except for 0
|
||||
|
||||

|
||||
|
||||
- Click Write
|
||||
|
||||
### 5.2 Diyplaying LED
|
||||
Navigate to:
|
||||
|
||||
Machines → DigitalInputMachine
|
||||
|
||||

|
||||
|
||||
You will see this interface:
|
||||
|
||||

|
||||
|
||||
Now you can apply a Voltage of the Range of 15V-30V (+24V recommended) from the DC lab power supply to the Terminal.
|
||||
The Terminal can read the digital input, which is signaled by the LED on the Terminal and should be displayed in the Control software.
|
||||
|
||||
In our example it lights up the first LED
|
||||
|
||||

|
||||
|
||||
## 6. References
|
||||
This guide incorporates information from official WAGO documentation.
|
||||
All diagrams, product names, and figures belong to Beckhoff Automation GmbH & Co. KG and are used here solely for educational purposes.
|
||||
|
||||
Referenced Manuals
|
||||
|
||||
[WAGO 750-402 Documentation](https://www.wago.com/de/io-systeme/4-kanal-digitaleingang/p/750-402#downloads)
|
||||
|
||||
[WAGO 750-354 Documentation](https://www.wago.com/de/io-systeme/feldbuskoppler-ethercat/p/750-354#details)
|
||||
|
||||
[WAGO 750-602 Documentation](https://www.wago.com/de/io-systeme/potentialeinspeisung/p/750-602#details)
|
||||
|
||||
[WAGO 750-354 Documentation](https://www.wago.com/de/io-systeme/endmodul/p/750-600#details)
|
||||
@@ -0,0 +1,344 @@
|
||||
# Documentation for the US-3202510 220 V Single‑Phase to Three‑Phase 0.75 kW VFD Inverter
|
||||
|
||||
## 1. Specification, Features and Technical Data
|
||||
|
||||
### 1.1 Specification
|
||||
|
||||
- **Control Mode:** V/F Control
|
||||
- **Nominal Voltage:** 220 V
|
||||
- **Rated Power:** 750 W, 400 W, 200 W, 300 W
|
||||
- **Input Voltage:** Single Phase 220 V
|
||||
- **Output Voltage:** Three Phase 220 V
|
||||
- **Dimensions:** 52.00 / 80.00 / 90.00 / 147.00 mm (reference)
|
||||
|
||||
---
|
||||
|
||||
|
||||
### 1.2 Features
|
||||
|
||||
- Output frequency range: **1.0–99.0 Hz**, suitable for general speed control
|
||||
- Built-in intelligent logic controller for simple logic control
|
||||
- Compact size, easy installation, simple commissioning
|
||||
- Clear and easy-to-understand parameters for general applications
|
||||
- Equipped with a built-in speed potentiometer; external potentiometer supported
|
||||
- Specifically designed for **220 V, ≤0.75 kW three-phase AC motors**
|
||||
- Integrated solution for equipment manufacturers, reducing system cost and improving reliability
|
||||
- Electronic thermal overload protection, replacing traditional motor protection devices
|
||||
- External LED indicator output supported for on-site indication
|
||||
- User-friendly interface with simple parameter setting
|
||||
- Adjustable V/F curve for special applications
|
||||
- Shift key allows viewing real-time parameters
|
||||
- Uses a new-generation **IPM module** with comprehensive protection functions
|
||||
|
||||
|
||||
---
|
||||
|
||||
### 1.3 Models and Technical Data
|
||||
|
||||
**Single-phase power supply: 200–240 VAC, 50/60 Hz**
|
||||
|
||||
| Inverter Model | Power Capacity (kVA) | Input Current (A) | Output Current (A) | Compatible Motor (kW) |
|
||||
| ----------------| ----------------------| -------------------| --------------------| -----------------------|
|
||||
| US00R2G1 | 3.0 | 3.2 | 1.6 | 0.2 |
|
||||
| US00R4G1 | 3.8 | 4.3 | 2.1 | 0.4 |
|
||||
| US00R7G1 | 5.6 | 6.0 | 3.1 | 0.75 |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### 1.4 Front Panel and Terminal Diagram
|
||||
|
||||
<img src="../assets/US-3202510_spec.png" alt="VFD Front Panel and Terminal Layout" style="max-width:600px;">
|
||||
|
||||
---
|
||||
|
||||
### 1.5 Dimensions
|
||||
|
||||
<img src="../assets/US-3202510_dimensions.png" alt="VFD Front Panel and Terminal Layout" style="max-width:600px;">
|
||||
|
||||
---
|
||||
|
||||
## 2. Terminals and Wiring
|
||||
|
||||
### 2.1 Main Terminal Wiring
|
||||
|
||||
| Terminal Mark | Name | Description |
|
||||
| ---------------| --------------------------| --------------------------------------------------|
|
||||
| AC, AC | Single-phase power input | Connection point for single-phase 220 V AC power |
|
||||
| U, V, W | Inverter output | Connect to three-phase AC motor |
|
||||
| FG | Ground terminal | Protective earth connection |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Signal Terminal Wiring
|
||||
|
||||
| Category | Terminal Symbol | Terminal Name | Function Description |
|
||||
| ----------------| -----------------| ------------------------| ----------------------------------------------|
|
||||
| Power | 5V, COM | External 5V power | Provides 5V power for external potentiometer |
|
||||
| Analog input | VR, COM | Analog input | Input voltage range: DC 0–5 V |
|
||||
| Digital input | M1, COM | Forward run | Forward rotation control |
|
||||
| Digital input | M2 / D3, COM | Reverse / multi-speed | Reverse rotation or multi-speed stage 3 |
|
||||
| Digital input | D2, COM | Multi-speed terminal 2 | Multi-speed function 2 |
|
||||
| Digital input | D1, COM | Multi-speed terminal 1 | Multi-speed function 1 |
|
||||
| Digital output | M0, 5V | Digital output | For external 5V relay or indicator |
|
||||
| Communication | RS−, RS+ | RS485 interface | MODBUS control |
|
||||
|
||||
---
|
||||
|
||||
### 2.2.1 Signal Terminal Layout
|
||||
|
||||
| Index | Symbol |
|
||||
| -------| --------|
|
||||
| 1 | +5V |
|
||||
| 2 | VR |
|
||||
| 3 | COM |
|
||||
| 4 | M0 |
|
||||
| 5 | M1 |
|
||||
| 6 | M2/D3 |
|
||||
| 7 | RS485- |
|
||||
| 8 | RS485+ |
|
||||
| 9 | D2 |
|
||||
| 10 | D1 |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 3. Control Panel
|
||||
|
||||
### 3.1 Indicator Lights and Display
|
||||
|
||||
| Indicator | Function | Description |
|
||||
| -----------| -------------------| -------------------------------------------------------------|
|
||||
| POWER | Power indicator | Steady ON = power on; Red flashing = keypad locked |
|
||||
| FWD | Forward indicator | Steady ON = motor running forward; Flashing = motor stopped |
|
||||
| REV | Reverse indicator | Steady ON = motor running reverse; Flashing = motor stopped |
|
||||
| Display | Display | Shows current parameters and fault information |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### 3.2 Physical Controls
|
||||
|
||||
| Key / Control | Type | Function (Run Mode) | Function (Parameter Setting Mode) |
|
||||
| ---------------| ---------------| -----------------------------------------------------------| ---------------------------------------------------------------------------|
|
||||
| P.K / SHIFT | Button | View IPM temperature, DC bus voltage/current, motor speed | Shift digit |
|
||||
| MENU / ESC | Button | Enter parameter setting mode | Exit |
|
||||
| SAVE / LOCK | Button | — | Long press to lock/unlock keypad; auto-lock after 3 minutes of inactivity |
|
||||
| FWD / REV | Button | Toggle forward/reverse rotation | — |
|
||||
| ↑ | Button | — | Increase value |
|
||||
| ON / OFF | Button | Start/stop motor | Confirm value |
|
||||
| ↓ | Button | — | Decrease value |
|
||||
| SPEED | Potentiometer | Adjust motor speed | — |
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 4. Parameters
|
||||
|
||||
### 4.1 Editing Parameters
|
||||
|
||||
1. **Enter Edit Mode**
|
||||
- Press the **MENU/ESC** key.
|
||||
- The digital display will flash `-0.1-`.
|
||||
|
||||
2. **Select Function Code**
|
||||
- Use the **↑/↓ keys** to adjust the value and choose the desired **function code**.
|
||||
- Refer to **Table 4.1** below for available function codes.
|
||||
|
||||
3. **Set Sub-Code**
|
||||
- During the setting process, use the **P.K/SHIFT** and **↑/↓ keys** to select the sub-code.
|
||||
- Press **ON/OFF** to enter the sub-code selection.
|
||||
- After selecting the sub-code, press **ON/OFF** again to return to the function code screen (display flashes `-X.X-`).
|
||||
- Repeat for the next function code as needed.
|
||||
|
||||
4. **Save Settings**
|
||||
- After all settings are selected, press the **SAVE/LOCK** key (display flashes `SAVE`).
|
||||
- Press **SAVE/LOCK** again to confirm saving.
|
||||
- When the display stops flashing, the data is saved.
|
||||
|
||||
5. **Operation After Saving**
|
||||
- The VFD will run according to the saved parameters **without needing a power cycle**.
|
||||
|
||||
6. **Exit Without Saving**
|
||||
- Press **MENU/ESC** to exit without saving — previous parameters remain unchanged.
|
||||
- If no key is pressed for **20 seconds**, the interface will automatically return to the run screen.
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Parameter Table
|
||||
|
||||
| Function Code | Definition | Factory Value | Setting Range | Description |
|
||||
| ---------------| -------------------------------------------------| ---------------| ---------------| ---------------------------------------------------------------------------------------------------|
|
||||
| 0.1 | Acceleration Time | 7 | 1–15 | 1–15 corresponds to 5–0.1 s; smaller value = faster (each step is about twice as fast) |
|
||||
| 0.2 | Deceleration Time | 7 | 1–15 | 1–15 corresponds to 5–0.1 s; smaller value = faster (each step is about twice as fast) |
|
||||
| 0.4 | V/F Frequency Compensation | 20 | 5–30 Hz | V/F frequency compensation |
|
||||
| 0.5 | V/F Voltage Compensation | 41 | 25–85 | V/F voltage compensation |
|
||||
| 0.7 | MODBUS Communication Baud Rate | 4 | 024–384 | 096 = 9600 bps |
|
||||
| 0.9 | MODBUS Address | 1 | 1–249 | MODBUS address |
|
||||
| 1.0 | Frequency Source Selection | 2 | 0–4 | 0: Panel keys, 1: Panel potentiometer, 2: External analog VR, 3: MODBUS 485, 4: Multi-speed input |
|
||||
| 1.1 | Command Source Selection | 0 | 0–4 | 2: Power-on forward, 3: Power-on reverse, 4: External port, 0: free stop |
|
||||
| 1.2 | Stop Mode | 1 | 0–2 | 1: Deceleration stop, 2: Immediate stop; 0: MI1 forward/stop, MI2 reverse/stop |
|
||||
| 1.3 | MI Function Selection | 0 | 0–2 | 1: MI1 run/stop, MI2 reverse/forward; 2: MI1 run/stop, MI2 multi-speed |
|
||||
| 1.4 | MO Function Selection | 0 | 0–2 | 1: Setting reached indicator, 2: Fault indicator |
|
||||
| 1.6 | Overheat Protection Value | 90 | 40–100 °C | Overheat protection threshold |
|
||||
| 1.7 | Maximum Operating Frequency | 50.0 | 1.0–99.0 Hz | Maximum frequency setting |
|
||||
| 1.8 | Minimum Operating Frequency | 0 | 0.0–30.0 Hz | Minimum frequency setting |
|
||||
| 1.9 | Rated Operating Frequency | 50.0 | 1.0–99.0 Hz | Default power-on frequency |
|
||||
| 2.0 | Output Voltage Corresponding Frequency | 50.0 | 35–99.0 Hz | Output voltage corresponding frequency |
|
||||
| 2.1 | Multi-speed 1 Set Frequency | 5.0 | 1.0–99.0 Hz | Multi-speed 1 frequency |
|
||||
| 2.2 | Multi-speed 2 Set Frequency | 10.0 | 1.0–99.0 Hz | Multi-speed 2 frequency |
|
||||
| 2.3 | Multi-speed 3 Set Frequency | 20.0 | 1.0–99.0 Hz | Multi-speed 3 frequency |
|
||||
| 2.4 | Multi-speed 4 Set Frequency | 25.0 | 1.0–99.0 Hz | Multi-speed 4 frequency |
|
||||
| 2.5 | Multi-speed 5 Set Frequency | 35.0 | 1.0–99.0 Hz | Multi-speed 5 frequency |
|
||||
| 2.6 | Multi-speed 6 Set Frequency | 40.0 | 1.0–99.0 Hz | Multi-speed 6 frequency |
|
||||
| 2.7 | Multi-speed 7 Set Frequency | 45.0 | 1.0–99.0 Hz | Multi-speed 7 frequency |
|
||||
| 2.8 | Frequency Reached During Run | 45.0 | 1.0–99.0 Hz | Frequency reached during operation |
|
||||
| 3.5 | Motor Pole Pairs | 2 | 1–6 | 2 corresponds to 1500 rpm |
|
||||
| 3.7 | Motor Speed | 1500 | 1–9999 | Motor operating speed |
|
||||
| 3.8 | Multi-speed 0 Set Frequency | 1.0 | 1.0–99.0 Hz | Multi-speed 0 frequency |
|
||||
| 4.0 | Overcurrent Protection Value | 8.00 A | 2.00–9.00 A | Motor protection current setting |
|
||||
| 4.1 | Overvoltage Protection Value | 520.0 V | 100–900 V | Motor protection voltage setting |
|
||||
| 4.2 | Overload Protection Value | 2400 W | 400–2700 W | Motor protection power setting |
|
||||
| 4.3 | Carrier Frequency Setting | 0 | 0–1 | 0: 10 kHz, 1: 20 kHz |
|
||||
| 4.4 | Power-loss Detection Enable | 1 | 0–1 | 0: Off, 1: On; 0 = fan and motor start/stop together |
|
||||
| 4.5 | Fan Operation Mode | 0 | 0–2 | 1: Fan stops 5 s after motor stops; 2: Fan runs continuously until power off |
|
||||
| 4.6 | Motor Speed Synchronization Mode | 0–2 | 0–2 | 1: Motor speed sync within 300 ms after speed change; 2: Motor sync every 1 s |
|
||||
| 5.0 | Bluetooth / RS485 / External Terminal Selection | 0–2 | 0–2 | 1: Serial = Bluetooth; 2: Serial = external expansion |
|
||||
| 6.0 | Expansion Board – Year/Month | — | — | 0x23 = 2023, 0x10 = October |
|
||||
| 6.1 | Expansion Board – Day/Version | — | — | 0x25 = 25th, 0x10 = version 1.0 |
|
||||
| 6.2 | Expansion Board – Response Interval | 1000 | 100–5000 ms | Time interval for expansion board reporting |
|
||||
| 6.3 | Expansion Board – Response Enable | 0–1 | 0–1 | 0: Do not report; 1: Enable reporting |
|
||||
| 6.8 | Expansion Board – Voltage | — | — | Voltage reading, e.g., 503 = 5.03 V |
|
||||
| 9.1 | Restore Factory Settings | — | — | Display flashes `CL`; press confirm to restore |
|
||||
| 9.2 | Firmware Version | 107 | — | Display version number, e.g., V107 |
|
||||
| 9.3 | Output Waveform Selection | 0–1 | 0–1 | 0: Standard sine; 1: Enhanced sine control waveform |
|
||||
| 9.4 | MCU Reset | — | — | Display flashes 8.88; press confirm to reset MCU |
|
||||
| 9.6 | Continuous Motor Speed Display | 0 | 0–1 | 0: Auto return to frequency display after inactivity; 1: auto return to speed display |
|
||||
| 9.7 | Panel 3-Minute Lock Switch | 0 | 0–1 | 0: Auto lock after 3 minutes no operation; 1: No lock |
|
||||
| 9.8 | Motor Speed Display Multiplier | 0 | 0–100 | 0: display off; other: divides speed, e.g., 4 → 1500 rpm becomes 375 rpm (requires 9.6 = 1) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 5. Fault Alarms and Countermeasures
|
||||
|
||||
The VFD has multiple warning indicators and protection functions.
|
||||
When an abnormal fault occurs, the protection function activates and the VFD stops output. The display panel shows a fault code.
|
||||
|
||||
### Table 5.1 – Fault Codes
|
||||
|
||||
| Fault Code | Fault Name | Fault Description | Countermeasure |
|
||||
| ------------| ------------------------------------| ----------------------------------------------------------------------------------------------------| ---------------------------------------------|
|
||||
| E-0.1 | VFD Overheat | VFD temperature exceeds 1.6× set protection value; display flashes; max frequency limited to 30 Hz | After clearing the fault, restart the motor |
|
||||
| E-0.2 | Pulse Overcurrent | VFD current exceeds 4.0× set protection value; motor stops | After clearing the fault, restart the motor |
|
||||
| E-0.4 | VFD Overload | VFD voltage × current exceeds 4.2× set power; display flashes; operation continues | After clearing the fault, restart the motor |
|
||||
| E-0.5 | Approaching IGBT Temperature Limit | VFD temperature >120°C, near IGBT limit; motor stops | After clearing the fault, restart the motor |
|
||||
| E-0.6 | Temperature Sensor Open | VFD temperature sensor open; display flashes; operation continues | After clearing the fault, restart the motor |
|
||||
| E-0.7 | Temperature Sensor Short | VFD temperature sensor shorted; display flashes; operation continues | After clearing the fault, restart the motor |
|
||||
| E-0.8 | 100% Overload | VFD overload warning lasts 6 s; motor stops | After clearing the fault, restart the motor |
|
||||
| E-0.9 | VFD Thermal Protection | Overheat protection warning >1 minute; motor stops | After clearing the fault, restart the motor |
|
||||
| E-1.0 | Bus Overvoltage Protection | VFD voltage exceeds 4.1× set protection value; motor stops | After clearing the fault, restart the motor |
|
||||
| E-1.1 | IGBT VF0 Protection | IGBT module VF0 fault; module overload; motor stops | After clearing the fault, restart the motor |
|
||||
|
||||
## 6. Multi-speed Settings
|
||||
|
||||
| D1 | D2 | D3 | Set Frequency | Corresponding Parameter |
|
||||
| -----| -----| -----| ---------------| -------------------------|
|
||||
| OFF | OFF | OFF | Multi-speed 0 | -3.8- |
|
||||
| ON | OFF | OFF | Multi-speed 1 | -2.1- |
|
||||
| OFF | ON | OFF | Multi-speed 2 | -2.2- |
|
||||
| ON | ON | OFF | Multi-speed 3 | -2.3- |
|
||||
| OFF | OFF | ON | Multi-speed 4 | -2.4- |
|
||||
| ON | OFF | ON | Multi-speed 5 | -2.5- |
|
||||
| OFF | ON | ON | Multi-speed 6 | -2.6- |
|
||||
| ON | ON | ON | Multi-speed 7 | -2.7- |
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 7. Low-Frequency V/F Compensation
|
||||
|
||||
The VFD allows **low-frequency V/F compensation** to improve motor torque under load.
|
||||
|
||||
- Based on the connected load, you can set values in parameters `-0.3-`, `-0.4-`, and `-0.5-` according to the **linear V/F curve** (see Table 7.1).
|
||||
- Low-frequency compensation **increases motor torque**.
|
||||
- You must choose the **upper frequency limit for torque boosting**:
|
||||
- Set the **maximum voltage ratio** in `-0.3-` and `-0.4-` for the desired frequency.
|
||||
- Reference the corresponding or nearest frequency in Table 2.
|
||||
|
||||
**Behavior:**
|
||||
- When operating **above this frequency**, the V/F slope increases → torque boost.
|
||||
- When operating **below this frequency**, the V/F slope decreases → torque decreases.
|
||||
|
||||
**Example:**
|
||||
- Parameter `-0.3-` = 20.0 Hz
|
||||
- Parameter `-0.4-` = 60, 50, 30
|
||||
- Parameter `-0.5-` default = 8
|
||||
- The three V/F curves correspond to different voltage ratios at 20 Hz:
|
||||
- Curve 1: 60% voltage ratio
|
||||
- Curve 2: 55% voltage ratio
|
||||
- Curve 3: 30% voltage ratio
|
||||
|
||||
This allows fine-tuning of torque at low speeds while keeping proper V/F control at higher frequencies.
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Table 7.1 – Linear V/F Curve Values for Low-Frequency Compensation
|
||||
|
||||
<img src="../assets/US-3202510_freq.png" alt="VFD Frequency Compensation Curve" style="max-width:600px;">
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 8. Modbus Interface
|
||||
|
||||
### 8.1 Internal Register List
|
||||
- Communication format: MODBUS-RTU
|
||||
- Data bits: 8
|
||||
- Stop bits: 1
|
||||
- Parity: None
|
||||
- Default baud rate: 9600 bps
|
||||
|
||||
---
|
||||
|
||||
|
||||
### 8.2 Holding Register Group (Read/Write)
|
||||
|
||||
| Hex Addr | Dec Addr | Data Type | Description | Notes |
|
||||
| ---------:| ---------:| -----------------| ----------------------| ---------------------------------------|
|
||||
| 0x0002 | 2 | Unsigned 16-bit | MODBUS set frequency | 500 / 10 = 50.0 Hz |
|
||||
| 0x0003 | 3 | Unsigned 16-bit | MODBUS run command | 1: Forward, 2: Stop, 3: Reverse |
|
||||
| 0x0004 | 4 | Unsigned 16-bit | Acceleration time | 1–15 → 5–0.1s (1 = 5s, 2 = 2.5s etc.) |
|
||||
| 0x0005 | 5 | Unsigned 16-bit | Deceleration time | 1–15 → 5–0.1s (1 = 5s, 2 = 2.5s etc.) |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### 8.3 Input Registers (Read-only)
|
||||
|
||||
| Hex Addr | Dec Addr | Data Type | Description | Notes |
|
||||
| ---------:| ---------:| -----------------| ---------------------------| -------------------------------|
|
||||
| 0x0008 | 8 | Unsigned 16-bit | DC bus voltage | 3100 / 10 = 310.0 V |
|
||||
| 0x0009 | 9 | Unsigned 16-bit | DC bus current | 132 / 100 = 1.32 A |
|
||||
| 0x000A | 10 | Unsigned 16-bit | Drive temperature | 43 = 43 °C |
|
||||
| 0x000B | 11 | Unsigned 16-bit | System status | 0: Idle, 1: Running, 2: Fault |
|
||||
| 0x000C | 12 | Unsigned 16-bit | Error code | See error table below |
|
||||
| 0x000D | 13 | Unsigned 16-bit | Current running frequency | 500 / 10 = 50.0 Hz |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### 8.4 Error Codes
|
||||
|
||||
| Code | Description |
|
||||
| -----:| -------------------------------|
|
||||
| 0 | No fault |
|
||||
| 1 | Pulse overcurrent |
|
||||
| 2 | IGBT overcurrent protection |
|
||||
| 3 | DC bus overvoltage protection |
|
||||
| 4 | Temperature near IGBT limit |
|
||||
| 5 | Inverter thermal protection |
|
||||
| 6 | Inverter overload 100% |
|
||||
| 7 | Inverter power cut |
|
||||
@@ -0,0 +1,241 @@
|
||||
# REST API `/api/v2`
|
||||
|
||||
The Qitech Control Panel exposes a small HTTP interface for discovering machines and reading their current values. All examples below assume you are connected to the panel’s Ethernet subnet and talk directly to the panel at:
|
||||
|
||||
- **Base URL:** `http://10.10.10.1:3001`
|
||||
|
||||
> **Schema note:** This documentation intentionally stays light on field details beyond what is shown in the examples. For the exact datatypes and complete payload shapes, refer to the corresponding Rust types (linked below).
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
The panel does **not** perform HTTP authentication. The expected security model is **network-level isolation**: anything that can send packets to the panel’s Ethernet interface is treated as trusted. This matches common security assumptions in EtherCAT-style control networks. It is therefore suggested to run the entire production line in isolated operation.
|
||||
|
||||
The panel is configured to administer its own subnet `10.10.10.0/24` via DHCP, while Wi-Fi can be used for upstream internet connectivity. To let the outside world communicate with the production line, a router should be placed at the network bondary bridging the production line's subnet and the rest of the network. The router can run in **client/bridge mode** on the line's subnet and expose the panel's API via **port forwading** or through a **reverse proxy** where one can add authentication, logging, rate limiting, etc.
|
||||
|
||||
The following graphic depicts the suggested network topology for a single production line. Many lines could potentially share the same router.
|
||||
|
||||
<p align="center">
|
||||
<img width="769" height="528" alt="Suggested network topology" src="./assets/subnet.png" />
|
||||
</p>
|
||||
|
||||
If DNS is available on the subnet, you may be able to resolve `qitech.control`; otherwise use the static address `10.10.10.1`.
|
||||
|
||||
---
|
||||
|
||||
## List machines `GET /api/v2/machine`
|
||||
|
||||
Returns the set of machines currently known/connected to the panel.
|
||||
|
||||
Machines are identified by:
|
||||
|
||||
- `slug`: the machine type / model identifier (string)
|
||||
- `serial`: the specific machine instance identifier (int)
|
||||
|
||||
Each machine also includes a `legacy_id` to support older **v1** workflows. If a machine reports an issue, the `error` field may be present and non-null (containing an error message).
|
||||
|
||||
### Example request
|
||||
|
||||
```bash
|
||||
curl -X GET "http://10.10.10.1:3001/api/v2/machine"
|
||||
```
|
||||
|
||||
### Example response
|
||||
|
||||
```json
|
||||
{
|
||||
"machines": [
|
||||
{
|
||||
"legacy_id": {
|
||||
"machine_identification": {
|
||||
"vendor": 1,
|
||||
"machine": 7
|
||||
},
|
||||
"serial": 57922
|
||||
},
|
||||
"serial": 57922,
|
||||
"vendor": "QiTech",
|
||||
"slug": "mock",
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"legacy_id": {
|
||||
"machine_identification": {
|
||||
"vendor": 1,
|
||||
"machine": 4
|
||||
},
|
||||
"serial": 57922
|
||||
},
|
||||
"serial": 57922,
|
||||
"vendor": "QiTech",
|
||||
"slug": "extruder_v1",
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"legacy_id": {
|
||||
"machine_identification": {
|
||||
"vendor": 1,
|
||||
"machine": 2
|
||||
},
|
||||
"serial": 57922
|
||||
},
|
||||
"serial": 57922,
|
||||
"vendor": "QiTech",
|
||||
"slug": "winder_v1",
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"legacy_id": {
|
||||
"machine_identification": {
|
||||
"vendor": 1,
|
||||
"machine": 10
|
||||
},
|
||||
"serial": 48879
|
||||
},
|
||||
"serial": 48879,
|
||||
"vendor": "QiTech",
|
||||
"slug": "wago_power_v1",
|
||||
"error": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get current values `GET /api/v2/machine/<slug>/<serial>`
|
||||
|
||||
Returns all currently known values for a single machine.
|
||||
|
||||
Values are categorized into two groups:
|
||||
|
||||
- **State**: requested/commanded values (these typically change only after a state-change request, or if another controller updates them)
|
||||
- **Live Values**: measured/observed values coming from the machine and potentially changing quickly
|
||||
|
||||
This REST endpoint returns **only the current snapshot**, not a stream of live values.
|
||||
To receive continuous updates (via WebSockets), subscribe to the machine namespace (see **WebSockets** below).
|
||||
|
||||
### Example request (mock machine)
|
||||
|
||||
```bash
|
||||
curl -X GET "http://10.10.10.1:3001/api/v2/machine/mock/57922"
|
||||
```
|
||||
|
||||
### Example response
|
||||
|
||||
```json
|
||||
{
|
||||
"machine": {
|
||||
"legacy_id": {
|
||||
"machine_identification": {
|
||||
"vendor": 1,
|
||||
"machine": 7
|
||||
},
|
||||
"serial": 57922
|
||||
},
|
||||
"serial": 57922,
|
||||
"vendor": "QiTech",
|
||||
"slug": "mock",
|
||||
"error": null
|
||||
},
|
||||
"state": {
|
||||
"frequency1": 100.0,
|
||||
"frequency2": 200.0,
|
||||
"frequency3": 500.0,
|
||||
"is_default_state": false,
|
||||
"mode_state": {
|
||||
"mode": "Running"
|
||||
}
|
||||
},
|
||||
"live_values": {
|
||||
"amplitude1": -0.03438523433309566,
|
||||
"amplitude2": -0.06872980145477608,
|
||||
"amplitude3": -0.1711138370170743,
|
||||
"amplitude_sum": -0.27422887280494607
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Change machine state `POST /api/v1/machine/<slug>/<serial>`
|
||||
|
||||
State changes are submitted as **mutations**. The mutation payload is defined per machine type in Rust. Conceptually, each item in the mutation list represents a setter-style operation that is applied by the real-time control loop.
|
||||
|
||||
The API does **not** return the newly-applied state in the POST response. The panel runs a real-time loop and generally won’t block waiting for the physical system to converge. Instead:
|
||||
|
||||
- Submit the mutation via `POST`
|
||||
- Poll `GET /api/v2/machine/<slug>/<serial>` to observe the updated state and/or any reported errors
|
||||
|
||||
### Example request (mock machine)
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
-d \
|
||||
-H "Content-Type: application/json" \
|
||||
"http://10.10.10.1:3001/api/v1/machine/mock/57922"
|
||||
```
|
||||
|
||||
### Example response
|
||||
|
||||
```json
|
||||
null
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## WebSockets
|
||||
|
||||
For continuous updates, subscribe to a machine-specific namespace derived from its `legacy_id`:
|
||||
|
||||
- Namespace: `/machine/<vendor>/<id>/<serial>`
|
||||
|
||||
The stream emits events for:
|
||||
|
||||
- state changes (`StateEvent`)
|
||||
- live value updates (`LiveValuesEvent`)
|
||||
|
||||
Both event payloads use the same machine-specific schema as the `/api/v2` REST responses.
|
||||
|
||||
---
|
||||
|
||||
## List of all machines
|
||||
|
||||
Below is a template you can fill with links to the relevant Rust types (mutations + state/live structs).
|
||||
For each machine, link to:
|
||||
|
||||
- **Mutations:** the request payload type used by `POST /api/v1/machine/<slug>/<serial>`
|
||||
- **State / Live Values:** the response payload types returned by `GET /api/v2/machine/<slug>/<serial>`
|
||||
|
||||
### Machines
|
||||
|
||||
- **winder_v1**
|
||||
|
||||
- Mutations: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/winder2/api.rs#L87>
|
||||
- State: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/winder2/api.rs#L163>
|
||||
- Live Values: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/winder2/api.rs#L143>
|
||||
|
||||
- **extruder_v1**
|
||||
|
||||
- Mutations: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/extruder1/api.rs#L214>
|
||||
- State: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/extruder1/api.rs#L89>
|
||||
- Live Values: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/extruder1/api.rs#L55>
|
||||
|
||||
- **laser_v1**
|
||||
|
||||
- Mutations: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/laser/api.rs#L88>
|
||||
- State: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/laser/api.rs#L32>
|
||||
- Live Values: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/laser/api.rs#L17>
|
||||
|
||||
- **mock**
|
||||
|
||||
- Mutations: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/mock/api.rs#L84>
|
||||
- State: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/mock/api.rs#L38>
|
||||
- Live Values: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/mock/api.rs#L24>
|
||||
|
||||
- **extruder_v2**
|
||||
|
||||
- Mutations: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/extruder2/api.rs#L126>
|
||||
- State: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/extruder2/api.rs#L93>
|
||||
- Live Values: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/extruder2/api.rs#L59>
|
||||
@@ -27,7 +27,6 @@
|
||||
"@tanstack/react-router": "^1.130.17",
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
"@tanstack/router-devtools": "^1.105.0",
|
||||
"@tanstack/zod-adapter": "^1.116.0",
|
||||
"@vitejs/plugin-react": "^4.5.2",
|
||||
"babel-plugin-react-compiler": "^19.0.0-beta-714736e-20250131",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@@ -52,9 +51,9 @@
|
||||
"tailwind-merge": "^3.0.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uplot": "^1.6.32",
|
||||
"xlsx": "^0.18.5",
|
||||
"zod": "^3.24.2",
|
||||
"zod-validation-error": "^3.4.0",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||
"zod": "^4.3.5",
|
||||
"zod-validation-error": "^5.0.0",
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -163,6 +162,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
|
||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
@@ -659,6 +659,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -682,6 +683,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -3129,6 +3131,66 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
||||
"version": "1.7.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.1.0",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||
"version": "1.7.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.1.0",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.0",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "^1.7.1",
|
||||
"@emnapi/runtime": "^1.7.1",
|
||||
"@tybys/wasm-util": "^0.10.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||
"version": "4.1.18",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz",
|
||||
@@ -3221,6 +3283,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.141.1.tgz",
|
||||
"integrity": "sha512-pLQ6ZFCh5s86ewZIAu2wQc2svf+DqttD7CFd1NPSxdEU20KvMMj9RPQdDHj/pbf2VvB3i1nEDxjli/Z0PBvnCQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tanstack/history": "1.141.0",
|
||||
"@tanstack/react-store": "^0.8.0",
|
||||
@@ -3311,6 +3374,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.141.1.tgz",
|
||||
"integrity": "sha512-fR1GGpp6v3dVKu4KIAjEh+Sd0qGLQd/wvCOVHeopSY6aFidXKCzwrS5cBOBqoPPWTKmn6CdW1a0CzFr5Furdog==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tanstack/history": "1.141.0",
|
||||
"@tanstack/store": "^0.8.0",
|
||||
@@ -3408,23 +3472,6 @@
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/zod-adapter": {
|
||||
"version": "1.141.1",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/zod-adapter/-/zod-adapter-1.141.1.tgz",
|
||||
"integrity": "sha512-ZsGxo+/AFZnVdut8g7VkDZ6+xZ94+bHUyXv6FnB5tSmuPVFXuVwRCZnVida7EYYd3SOWpyy222e4Qkpob1xwOw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tanstack/react-router": ">=1.43.2",
|
||||
"zod": "^3.23.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom": {
|
||||
"version": "10.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
|
||||
@@ -3548,8 +3595,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
@@ -3715,6 +3761,7 @@
|
||||
"integrity": "sha512-gqkrWUsS8hcm0r44yn7/xZeV1ERva/nLgrLxFRUGb7aoNMIJfZJ3AC261zDQuOAKC7MiXai1WCpYc48jAHoShQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
@@ -3731,6 +3778,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
|
||||
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -3741,6 +3789,7 @@
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
@@ -3828,6 +3877,7 @@
|
||||
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.49.0",
|
||||
"@typescript-eslint/types": "8.49.0",
|
||||
@@ -4186,6 +4236,7 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -4216,15 +4267,6 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/adler-32": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
|
||||
"integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||
@@ -4258,7 +4300,6 @@
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -4573,6 +4614,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -4726,19 +4768,6 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/cfb": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
|
||||
"integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"adler-32": "~1.3.0",
|
||||
"crc-32": "~1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/chai": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
|
||||
@@ -4857,15 +4886,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/codepage": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
|
||||
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@@ -4930,18 +4950,6 @@
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/crc-32": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
|
||||
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"crc32": "bin/crc32.njs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/create-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||
@@ -4989,7 +4997,8 @@
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/data-urls": {
|
||||
"version": "5.0.0",
|
||||
@@ -5262,8 +5271,7 @@
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
@@ -5809,6 +5817,7 @@
|
||||
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -5869,6 +5878,7 @@
|
||||
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
@@ -5964,6 +5974,30 @@
|
||||
"eslint": ">=7"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react-compiler/node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react-compiler/node_modules/zod-validation-error": {
|
||||
"version": "3.5.4",
|
||||
"resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.5.4.tgz",
|
||||
"integrity": "sha512-+hEiRIiPobgyuFlEojnqjJnhFvg4r/i3cqgcm67eehZf/WBaK3g6cD02YU9mtdVxZjv8CzCA9n/Rhrs3yAAvAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.24.4"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react/node_modules/resolve": {
|
||||
"version": "2.0.0-next.5",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
|
||||
@@ -6248,15 +6282,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/frac": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
|
||||
"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
@@ -6914,6 +6939,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4"
|
||||
},
|
||||
@@ -6947,6 +6973,7 @@
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
|
||||
"integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
@@ -7549,6 +7576,7 @@
|
||||
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cssstyle": "^4.2.1",
|
||||
"data-urls": "^5.0.0",
|
||||
@@ -8082,7 +8110,6 @@
|
||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
}
|
||||
@@ -9502,6 +9529,7 @@
|
||||
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
@@ -9618,7 +9646,6 @@
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
@@ -9634,7 +9661,6 @@
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -9724,6 +9750,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -9733,6 +9760,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -9745,6 +9773,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.68.0.tgz",
|
||||
"integrity": "sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
@@ -9787,8 +9816,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
@@ -10298,18 +10326,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/seroval": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.4.0.tgz",
|
||||
"integrity": "sha512-BdrNXdzlofomLTiRnwJTSEAaGKyHHZkbMXIywOh7zlzp4uZnXErEwl9XZ+N1hJSNpeTtNxWvVwN0wUzAIQ4Hpg==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.0.tgz",
|
||||
"integrity": "sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/seroval-plugins": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.4.0.tgz",
|
||||
"integrity": "sha512-zir1aWzoiax6pbBVjoYVd0O1QQXgIL3eVGBMsBsNmM8Ukq90yGaWlfx0AB9dTS8GPqrOrbXn79vmItCUP9U3BQ==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.0.tgz",
|
||||
"integrity": "sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@@ -10552,38 +10581,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/solid-js": {
|
||||
"version": "1.9.10",
|
||||
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz",
|
||||
"integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==",
|
||||
"version": "1.9.11",
|
||||
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.11.tgz",
|
||||
"integrity": "sha512-WEJtcc5mkh/BnHA6Yrg4whlF8g6QwpmXXRg4P2ztPmcKeHHlH4+djYecBLhSpecZY2RRECXYUwIc/C2r3yzQ4Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.1.0",
|
||||
"seroval": "~1.3.0",
|
||||
"seroval-plugins": "~1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/solid-js/node_modules/seroval": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz",
|
||||
"integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/solid-js/node_modules/seroval-plugins": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.3.tgz",
|
||||
"integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"seroval": "^1.0"
|
||||
"seroval": "~1.5.0",
|
||||
"seroval-plugins": "~1.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sonner": {
|
||||
@@ -10623,18 +10629,6 @@
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/ssf": {
|
||||
"version": "0.11.2",
|
||||
"resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
|
||||
"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"frac": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/stackback": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
|
||||
@@ -10915,7 +10909,8 @@
|
||||
"version": "4.1.18",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
||||
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tailwindcss-animate": {
|
||||
"version": "1.0.7",
|
||||
@@ -11004,6 +10999,7 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -11267,6 +11263,7 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -11603,6 +11600,7 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
@@ -11715,7 +11713,8 @@
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-electron-renderer/-/vite-plugin-electron-renderer-0.14.6.tgz",
|
||||
"integrity": "sha512-oqkWFa7kQIkvHXG7+Mnl1RTroA4sP0yesKatmAy0gjZC4VwUqlvF9IvOpHd1fpLWsqYX/eZlVxlhULNtaQ78Jw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/vite/node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
@@ -11753,6 +11752,7 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -12060,24 +12060,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wmf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
|
||||
"integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/word": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
|
||||
"integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
@@ -12118,19 +12100,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/xlsx": {
|
||||
"version": "0.18.5",
|
||||
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
|
||||
"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
|
||||
"version": "0.20.3",
|
||||
"resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||
"integrity": "sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"adler-32": "~1.3.0",
|
||||
"cfb": "~1.2.1",
|
||||
"codepage": "~1.15.0",
|
||||
"crc-32": "~1.2.1",
|
||||
"ssf": "~0.11.2",
|
||||
"wmf": "~1.0.1",
|
||||
"word": "~0.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"xlsx": "bin/xlsx.njs"
|
||||
},
|
||||
@@ -12210,24 +12183,25 @@
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"version": "4.3.5",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz",
|
||||
"integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/zod-validation-error": {
|
||||
"version": "3.5.4",
|
||||
"resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.5.4.tgz",
|
||||
"integrity": "sha512-+hEiRIiPobgyuFlEojnqjJnhFvg4r/i3cqgcm67eehZf/WBaK3g6cD02YU9mtdVxZjv8CzCA9n/Rhrs3yAAvAw==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-5.0.0.tgz",
|
||||
"integrity": "sha512-hmk+pkyKq7Q71PiWVSDUc3VfpzpvcRHZ3QPw9yEMVvmtCekaMeOHnbr3WbxfrgEnQTv6haGP4cmv0Ojmihzsxw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.24.4"
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/zustand": {
|
||||
|
||||
@@ -75,7 +75,6 @@
|
||||
"@tanstack/react-router": "^1.130.17",
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
"@tanstack/router-devtools": "^1.105.0",
|
||||
"@tanstack/zod-adapter": "^1.116.0",
|
||||
"@vitejs/plugin-react": "^4.5.2",
|
||||
"babel-plugin-react-compiler": "^19.0.0-beta-714736e-20250131",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@@ -100,9 +99,9 @@
|
||||
"tailwind-merge": "^3.0.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uplot": "^1.6.32",
|
||||
"xlsx": "^0.18.5",
|
||||
"zod": "^3.24.2",
|
||||
"zod-validation-error": "^3.4.0",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||
"zod": "^4.3.5",
|
||||
"zod-validation-error": "^5.0.0",
|
||||
"zustand": "^5.0.8"
|
||||
}
|
||||
}
|
||||
@@ -15,25 +15,31 @@ import {
|
||||
machineIdentificationUnique,
|
||||
} from "@/machines/types";
|
||||
import { useRef } from "react";
|
||||
import { rustEnumSchema } from "@/lib/types";
|
||||
import { rustEnum } from "@/lib/types";
|
||||
|
||||
export type EthercatDevices = z.infer<typeof ethercatDevicesSchema>;
|
||||
|
||||
export const ethercatDevicesSchema = z.object({
|
||||
devices: z.array(
|
||||
z.object({
|
||||
configured_address: z.number().int(),
|
||||
name: z.string(),
|
||||
vendor_id: z.number().int(),
|
||||
product_id: z.number().int(),
|
||||
revision: z.number().int(),
|
||||
device_identification: deviceIdentification,
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
// Update the EthercatDevicesEventData schema
|
||||
export const ethercatDevicesEventDataSchema = rustEnumSchema({
|
||||
Initializing: z.boolean(),
|
||||
Done: z.object({
|
||||
devices: z.array(
|
||||
z.object({
|
||||
configured_address: z.number().int(),
|
||||
name: z.string(),
|
||||
vendor_id: z.number().int(),
|
||||
product_id: z.number().int(),
|
||||
revision: z.number().int(),
|
||||
device_identification: deviceIdentification,
|
||||
}),
|
||||
),
|
||||
}),
|
||||
Error: z.string(),
|
||||
});
|
||||
export const ethercatDevicesEventDataSchema = z
|
||||
.object({
|
||||
Initializing: z.boolean(),
|
||||
Done: ethercatDevicesSchema,
|
||||
Error: z.string(),
|
||||
})
|
||||
.check(rustEnum);
|
||||
|
||||
export type EthercatDevicesEventData = z.infer<
|
||||
typeof ethercatDevicesEventDataSchema
|
||||
@@ -62,10 +68,12 @@ export const machinesEventSchema = eventSchema(machinesEventDataSchema);
|
||||
export type MachinesEvent = z.infer<typeof machinesEventSchema>;
|
||||
|
||||
// Keep the EthercatInterfaceDiscovery event
|
||||
export const ethercatInterfaceDiscoveryEventDataSchema = rustEnumSchema({
|
||||
Discovering: z.boolean(),
|
||||
Done: z.string(),
|
||||
});
|
||||
export const ethercatInterfaceDiscoveryEventDataSchema = z
|
||||
.object({
|
||||
Discovering: z.boolean(),
|
||||
Done: z.string(),
|
||||
})
|
||||
.check(rustEnum);
|
||||
|
||||
export type EthercatInterfaceDiscoveryEventData = z.infer<
|
||||
typeof ethercatInterfaceDiscoveryEventDataSchema
|
||||
@@ -110,23 +118,19 @@ export function mainMessageHandler(
|
||||
try {
|
||||
// Apply appropriate caching strategy based on event type
|
||||
if (eventName === "EthercatDevicesEvent") {
|
||||
const validatedEvent = ethercatDevicesEventSchema.parse(event);
|
||||
console.log("EthercatDevicesEvent", validatedEvent);
|
||||
const validatedEvent = event;
|
||||
store.setState((state) => ({
|
||||
...state,
|
||||
ethercatDevices: validatedEvent,
|
||||
}));
|
||||
} else if (eventName === "MachinesEvent") {
|
||||
const validatedEvent = machinesEventSchema.parse(event);
|
||||
console.log("MachinesEvent", validatedEvent);
|
||||
store.setState((state) => ({
|
||||
...state,
|
||||
machines: validatedEvent,
|
||||
}));
|
||||
} else if (eventName === "EthercatInterfaceDiscoveryEvent") {
|
||||
const validatedEvent =
|
||||
ethercatInterfaceDiscoveryEventSchema.parse(event);
|
||||
console.log("EthercatInterfaceDiscoveryEvent", validatedEvent);
|
||||
const validatedEvent = event;
|
||||
store.setState((state) => ({
|
||||
...state,
|
||||
ethercatInterfaceDiscovery: validatedEvent,
|
||||
|
||||
@@ -163,6 +163,7 @@ export const handleEventValidationError = (
|
||||
error: z.ZodError,
|
||||
eventName: string,
|
||||
) => {
|
||||
console.log(error);
|
||||
toastZodError(error, `Event Validation Error for ${eventName}`);
|
||||
throw new Error(`Event validation failed for ${eventName}`);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { PresetData, PresetSchema } from "@/lib/preset/preset";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -14,13 +13,13 @@ import { Separator } from "../ui/separator";
|
||||
import { PresetPreviewEntries, PresetPreviewTable } from "./PresetPreviewTable";
|
||||
import { Input } from "../ui/input";
|
||||
|
||||
export type NewPresetDialogProps<T extends PresetSchema> = {
|
||||
currentState?: PresetData<T>;
|
||||
export type NewPresetDialogProps<T> = {
|
||||
currentState?: T;
|
||||
previewEntries: PresetPreviewEntries<T>;
|
||||
onSave: (name: string) => void;
|
||||
};
|
||||
|
||||
export function NewPresetDialog<T extends PresetSchema>({
|
||||
export function NewPresetDialog<T>({
|
||||
currentState,
|
||||
onSave,
|
||||
previewEntries,
|
||||
|
||||
@@ -60,7 +60,7 @@ export function PresetCardMenu({
|
||||
);
|
||||
}
|
||||
|
||||
export type PresetCardProps<T extends PresetSchema> = {
|
||||
export type PresetCardProps<T> = {
|
||||
preset: Preset<T>;
|
||||
onApply: (preset: Preset<T>) => void;
|
||||
onOverwrite: (preset: Preset<T>) => void;
|
||||
@@ -72,7 +72,7 @@ export type PresetCardProps<T extends PresetSchema> = {
|
||||
isActive?: boolean;
|
||||
};
|
||||
|
||||
export function PresetCard<T extends PresetSchema>({
|
||||
export function PresetCard<T>({
|
||||
preset,
|
||||
onApply,
|
||||
onOverwrite,
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
import React, { Fragment } from "react";
|
||||
import { PresetData, PresetSchema } from "@/lib/preset/preset";
|
||||
import { renderUnitSymbol, renderUnitSyntax, Unit } from "@/control/units";
|
||||
|
||||
export const previewSeparator = undefined;
|
||||
|
||||
type PreviewSeparator = typeof previewSeparator;
|
||||
|
||||
type PresetPreviewEntry<T extends PresetSchema> = {
|
||||
type PresetPreviewEntry<T> = {
|
||||
name: string;
|
||||
unit?: Unit;
|
||||
renderValue: (data: PresetData<T>) => string | undefined;
|
||||
renderValue: (data: T) => string | undefined;
|
||||
};
|
||||
|
||||
export type PresetPreviewEntries<T extends PresetSchema> = (
|
||||
export type PresetPreviewEntries<T> = (
|
||||
| PresetPreviewEntry<T>
|
||||
| PreviewSeparator
|
||||
)[];
|
||||
|
||||
export type PresetPreviewTableProps<T extends PresetSchema> = {
|
||||
data: PresetData<T>;
|
||||
export type PresetPreviewTableProps<T> = {
|
||||
data?: T;
|
||||
entries: PresetPreviewEntries<T>;
|
||||
};
|
||||
|
||||
export function PresetPreviewTable<T extends PresetSchema>({
|
||||
export function PresetPreviewTable<T>({
|
||||
data,
|
||||
entries,
|
||||
}: PresetPreviewTableProps<T>) {
|
||||
@@ -39,7 +38,7 @@ export function PresetPreviewTable<T extends PresetSchema>({
|
||||
);
|
||||
}
|
||||
|
||||
const value = entry.renderValue(data);
|
||||
const value = data !== undefined ? entry.renderValue(data) : undefined;
|
||||
return (
|
||||
<Fragment key={entry.name}>
|
||||
<div>{entry.name}</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Preset, PresetSchema } from "@/lib/preset/preset";
|
||||
import { Preset } from "@/lib/preset/preset";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -13,14 +13,14 @@ import { Icon } from "../Icon";
|
||||
import { Separator } from "../ui/separator";
|
||||
import { PresetPreviewEntries, PresetPreviewTable } from "./PresetPreviewTable";
|
||||
|
||||
export type PresetShowDialogProps<T extends PresetSchema> = {
|
||||
export type PresetShowDialogProps<T> = {
|
||||
preset: Preset<T>;
|
||||
previewEntries: PresetPreviewEntries<T>;
|
||||
onApply: (preset: Preset<T>) => void;
|
||||
hideDate?: boolean;
|
||||
};
|
||||
|
||||
export function PresetShowDialog<T extends PresetSchema>({
|
||||
export function PresetShowDialog<T>({
|
||||
preset,
|
||||
onApply,
|
||||
previewEntries,
|
||||
|
||||
@@ -2,19 +2,19 @@ import React from "react";
|
||||
import { ControlGrid } from "@/control/ControlGrid";
|
||||
import { Page } from "@/components/Page";
|
||||
import { usePresets, UsePresetsParams } from "@/lib/preset/usePresets";
|
||||
import { Preset, PresetSchema } from "@/lib/preset/preset";
|
||||
import { Preset } from "@/lib/preset/preset";
|
||||
import { PresetCard } from "./PresetCard";
|
||||
import { PresetPreviewEntries } from "./PresetPreviewTable";
|
||||
import { NewPresetDialog } from "./NewPresetDialog";
|
||||
import { downloadJson } from "@/lib/download";
|
||||
import { JsonFileInput } from "../FileInput";
|
||||
|
||||
type PresetsPageProps<T extends PresetSchema> = UsePresetsParams<T> & {
|
||||
type PresetsPageProps<T> = UsePresetsParams<T> & {
|
||||
applyPreset: (preset: Preset<T>) => void;
|
||||
previewEntries: PresetPreviewEntries<T>;
|
||||
};
|
||||
|
||||
export function PresetsPage<T extends PresetSchema>({
|
||||
export function PresetsPage<T>({
|
||||
applyPreset,
|
||||
machine_identification,
|
||||
currentState,
|
||||
|
||||
@@ -36,7 +36,6 @@ type Props = {
|
||||
maxSlider?: number; // Override the slider max value
|
||||
maxLabel?: string;
|
||||
step?: number;
|
||||
valueSchema?: z.ZodType<number>;
|
||||
inverted?: boolean;
|
||||
confirmation?: string;
|
||||
renderValue: (value: number) => string;
|
||||
@@ -89,7 +88,6 @@ export function EditValue({
|
||||
description,
|
||||
defaultValue,
|
||||
step = 1,
|
||||
valueSchema: schema,
|
||||
inverted,
|
||||
min,
|
||||
max,
|
||||
@@ -104,7 +102,7 @@ export function EditValue({
|
||||
|
||||
// Form setup
|
||||
const formSchema = z.object({
|
||||
value: schema ?? z.number(),
|
||||
value: z.number(),
|
||||
});
|
||||
type FormSchema = z.infer<typeof formSchema>;
|
||||
|
||||
@@ -191,7 +189,6 @@ export function EditValue({
|
||||
const numericValue = parseFloat(normalizedInput);
|
||||
const hasValidationError =
|
||||
isNaN(numericValue) ||
|
||||
(schema && !schema.safeParse(numericValue).success) ||
|
||||
(max !== undefined && numericValue > max) ||
|
||||
(min !== undefined && numericValue < min);
|
||||
|
||||
@@ -222,15 +219,7 @@ export function EditValue({
|
||||
setValueStringError(false);
|
||||
preventFormSyncRef.current = false;
|
||||
}
|
||||
}, [
|
||||
valueString,
|
||||
valueStringDirty,
|
||||
schema,
|
||||
max,
|
||||
min,
|
||||
form,
|
||||
roundToStepDecimals,
|
||||
]);
|
||||
}, [valueString, valueStringDirty, max, min, form, roundToStepDecimals]);
|
||||
|
||||
// Reset input state to clean form value
|
||||
const resetInput = React.useCallback(() => {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
export function deepEquals<T>(a: T, b: T): boolean {
|
||||
export function deepEquals(
|
||||
a: object | undefined,
|
||||
b: object | undefined,
|
||||
): boolean {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import { machineIdentification } from "@/machines/types";
|
||||
import { z } from "zod";
|
||||
|
||||
export type PresetSchema = z.ZodTypeAny;
|
||||
export type PresetSchema<T> = z.ZodType<T>;
|
||||
|
||||
export const presetSchema = <S extends PresetSchema>(dataSchema: S) =>
|
||||
export const presetSchema = <T>(dataSchema: PresetSchema<T>) =>
|
||||
z.object({
|
||||
id: z.number().int().nonnegative().optional(),
|
||||
name: z.string().nonempty(),
|
||||
lastModified: z.coerce.date(),
|
||||
machineIdentification,
|
||||
schemaVersion: z.number().int().positive(),
|
||||
data: dataSchema,
|
||||
data: dataSchema.optional(),
|
||||
});
|
||||
|
||||
export type Preset<S extends PresetSchema> = z.infer<
|
||||
ReturnType<typeof presetSchema<S>>
|
||||
>;
|
||||
|
||||
export type PresetData<S extends PresetSchema> = z.infer<S>;
|
||||
export type Preset<T> = z.infer<ReturnType<typeof presetSchema<T>>>;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { create } from "zustand";
|
||||
import { persist, PersistStorage, StorageValue } from "zustand/middleware";
|
||||
import { Preset, PresetSchema, presetSchema } from "./preset";
|
||||
import { Preset, presetSchema } from "./preset";
|
||||
import { MachineIdentification } from "@/machines/types";
|
||||
import { z } from "zod";
|
||||
import { toastError } from "@/components/Toast";
|
||||
@@ -33,9 +33,9 @@ type PresetStoreData = {
|
||||
};
|
||||
|
||||
export type PresetStore = PresetStoreData & {
|
||||
insert: <T extends PresetSchema>(preset: Preset<T>) => PersistedPreset;
|
||||
update: <T extends PresetSchema>(preset: Preset<T>) => void;
|
||||
remove: <T extends PresetSchema>(preset: Preset<T>) => void;
|
||||
insert: <T>(preset: Preset<T>) => PersistedPreset;
|
||||
update: <T>(preset: Preset<T>) => void;
|
||||
remove: <T>(preset: Preset<T>) => void;
|
||||
getById: (id: number) => PersistedPreset | undefined;
|
||||
|
||||
setLatestPresetId: (
|
||||
@@ -100,7 +100,7 @@ export const usePresetStore = create<PresetStore>()(
|
||||
presets: [],
|
||||
latestPresetIds: new Map(),
|
||||
|
||||
insert: <T extends PresetSchema>(preset: Preset<T>) => {
|
||||
insert: <T>(preset: Preset<T>) => {
|
||||
const state = get();
|
||||
const { presets } = state;
|
||||
const ids = presets.map((p: PersistedPreset) => p.id);
|
||||
@@ -115,7 +115,7 @@ export const usePresetStore = create<PresetStore>()(
|
||||
return persistedPreset;
|
||||
},
|
||||
|
||||
update: <T extends PresetSchema>(preset: Preset<T>) => {
|
||||
update: <T>(preset: Preset<T>) => {
|
||||
const state = get();
|
||||
const presets: PersistedPreset[] = state.presets.map(
|
||||
(p: PersistedPreset) =>
|
||||
@@ -124,7 +124,7 @@ export const usePresetStore = create<PresetStore>()(
|
||||
set({ ...state, presets });
|
||||
},
|
||||
|
||||
remove: <T extends PresetSchema>(preset: Preset<T>) => {
|
||||
remove: <T>(preset: Preset<T>) => {
|
||||
const state = get();
|
||||
const presets = state.presets.filter(
|
||||
(p: Preset<any>) => p.id !== preset.id,
|
||||
|
||||
@@ -3,13 +3,13 @@ import {
|
||||
machineIdentificationEquals,
|
||||
} from "@/machines/types";
|
||||
import { usePresetStore } from "./presetStore";
|
||||
import { Preset, PresetData, presetSchema, PresetSchema } from "./preset";
|
||||
import { Preset, presetSchema, PresetSchema } from "./preset";
|
||||
import { deepEquals } from "@/lib/objects";
|
||||
import { useEffect } from "react";
|
||||
import { toastError } from "@/components/Toast";
|
||||
import { z } from "zod";
|
||||
|
||||
export type Presets<T extends PresetSchema> = {
|
||||
export type Presets<T> = {
|
||||
get: () => Preset<T>[];
|
||||
createFromCurrentState: (name: string) => Preset<T>;
|
||||
updateFromCurrentState: (preset: Preset<T>) => Preset<T>;
|
||||
@@ -20,15 +20,15 @@ export type Presets<T extends PresetSchema> = {
|
||||
import: (json: any) => void;
|
||||
};
|
||||
|
||||
export type UsePresetsParams<T extends PresetSchema> = {
|
||||
export type UsePresetsParams<T> = {
|
||||
machine_identification: MachineIdentification;
|
||||
schemas: Map<number, T>;
|
||||
schemas: Map<number, PresetSchema<T>>;
|
||||
schemaVersion: number;
|
||||
currentState?: PresetData<T>;
|
||||
defaultState?: PresetData<T>;
|
||||
currentState?: T;
|
||||
defaultState?: T;
|
||||
};
|
||||
|
||||
export function usePresets<T extends PresetSchema>({
|
||||
export function usePresets<T>({
|
||||
machine_identification,
|
||||
schemas,
|
||||
schemaVersion,
|
||||
@@ -49,7 +49,10 @@ export function usePresets<T extends PresetSchema>({
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = presetSchema(schema).parse(preset);
|
||||
const parsed: Preset<T> = {
|
||||
...preset,
|
||||
data: schema.parse(preset.data),
|
||||
};
|
||||
|
||||
if (parsed.schemaVersion < schemaVersion) {
|
||||
parsed.schemaVersion = schemaVersion;
|
||||
@@ -93,7 +96,11 @@ export function usePresets<T extends PresetSchema>({
|
||||
return { ...preset, data: currentState };
|
||||
};
|
||||
|
||||
const updateFromCurrentState = (preset: Preset<T>) => {
|
||||
const updateFromCurrentState = (preset: Preset<T>): Preset<T> => {
|
||||
if (!currentState) {
|
||||
return preset;
|
||||
}
|
||||
|
||||
const data = currentState;
|
||||
|
||||
const newPreset = {
|
||||
@@ -153,12 +160,16 @@ export function usePresets<T extends PresetSchema>({
|
||||
};
|
||||
|
||||
const isActive = (preset: Preset<T>) => {
|
||||
return deepEquals(currentState, preset.data);
|
||||
if (!currentState) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return deepEquals(currentState, preset.data ?? {});
|
||||
};
|
||||
|
||||
const importPreset = (json: any) => {
|
||||
try {
|
||||
const anyPreset = presetSchema(z.any()).parse(json);
|
||||
const anyPreset = presetSchema(z.object()).parse(json);
|
||||
|
||||
if (
|
||||
!machineIdentificationEquals(
|
||||
|
||||
@@ -1,38 +1,17 @@
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* Creates a Zod schema for objects where exactly one property must be defined.
|
||||
* This is useful for handling Rust enum JSON representations where only one variant exists at a time.
|
||||
*
|
||||
* @param schemas A record mapping property names to their Zod schemas
|
||||
* @returns A Zod schema that validates that exactly one property is defined
|
||||
*/
|
||||
export function rustEnumSchema<T extends Record<string, z.ZodType>>(
|
||||
schemas: T,
|
||||
): z.ZodType<{ [K in keyof T]?: z.infer<T[K]> }> {
|
||||
// Create an object schema where all properties are optional
|
||||
const schemaEntries = Object.entries(schemas).map(([key, schema]) => [
|
||||
key,
|
||||
schema.optional(),
|
||||
]);
|
||||
|
||||
const objectSchema = z.object(
|
||||
Object.fromEntries(schemaEntries) as {
|
||||
[K in keyof T]: z.ZodOptional<T[K]>;
|
||||
},
|
||||
export const rustEnum: z.core.CheckFn<object> = (ctx) => {
|
||||
const definedKeys = Object.keys(ctx.value).filter(
|
||||
(key) => ctx.value[key] !== undefined,
|
||||
);
|
||||
|
||||
// Add refinement to ensure exactly one property is defined
|
||||
return objectSchema.refine(
|
||||
(data) => {
|
||||
const definedKeys = Object.keys(data).filter(
|
||||
(key) => data[key] !== undefined,
|
||||
);
|
||||
return definedKeys.length === 1;
|
||||
},
|
||||
{
|
||||
message: "Exactly one property must be defined",
|
||||
path: ["_exclusive"], // Add a path to help with error identification
|
||||
},
|
||||
);
|
||||
}
|
||||
if (definedKeys.length === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.issues.push({
|
||||
code: "custom",
|
||||
message: "Exactly one property must be defined",
|
||||
input: definedKeys,
|
||||
});
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
// super refine function to validate if a string is a valid u16 number
|
||||
export function validateU16<T>(): (value: T, ctx: z.RefinementCtx) => boolean {
|
||||
const refine = (value: T, ctx: z.RefinementCtx) => {
|
||||
console.log("value", value);
|
||||
if (typeof value === "string") {
|
||||
const number = parseInt(value, 10);
|
||||
if (number < 0 || number > 65535) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Wert muss u16 integer sein",
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
return refine;
|
||||
}
|
||||
@@ -6,7 +6,9 @@ import { TimeSeriesValueNumeric } from "@/control/TimeSeriesValue";
|
||||
import { useAquapath1 } from "./useAquapath";
|
||||
import { SelectionGroup } from "@/control/SelectionGroup";
|
||||
import { EditValue } from "@/control/EditValue";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Label } from "@/control/Label";
|
||||
import { HeatingZone } from "../../extruder/HeatingZone";
|
||||
|
||||
export function Aquapath1ControlPage() {
|
||||
const {
|
||||
@@ -16,8 +18,12 @@ export function Aquapath1ControlPage() {
|
||||
back_flow,
|
||||
front_temperature,
|
||||
back_temperature,
|
||||
front_temp_reservoir,
|
||||
back_temp_reservoir,
|
||||
front_revolutions,
|
||||
back_revolutions,
|
||||
front_power,
|
||||
back_power,
|
||||
front_total_energy,
|
||||
back_total_energy,
|
||||
setAquapathMode,
|
||||
setFrontTemperature,
|
||||
setBackTemperature,
|
||||
@@ -29,111 +35,55 @@ export function Aquapath1ControlPage() {
|
||||
const backTargetTemperature =
|
||||
state?.temperature_states?.back.target_temperature ?? 0;
|
||||
|
||||
const frontCurrentTemperature =
|
||||
state?.temperature_states?.front.temperature ?? 0;
|
||||
const backCurrentTemperature =
|
||||
state?.temperature_states?.back.temperature ?? 0;
|
||||
|
||||
const frontCoolingBoundary =
|
||||
frontTargetTemperature + (state?.tolerance_states.front.cooling ?? 0);
|
||||
const frontHeatingBoundary =
|
||||
frontTargetTemperature - (state?.tolerance_states.front.heating ?? 0);
|
||||
const backCoolingBoundary =
|
||||
backTargetTemperature + (state?.tolerance_states.back.cooling ?? 0);
|
||||
const backHeatingBoundary =
|
||||
backTargetTemperature - (state?.tolerance_states.back.heating ?? 0);
|
||||
|
||||
const frontTargetFlow = state?.flow_states.front.should_flow ?? false;
|
||||
const backTargetFlow = state?.flow_states.back.should_flow ?? false;
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<ControlGrid columns={2}>
|
||||
<ControlCard title="Flow Measurements">
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div className="flex flex-col gap-8">
|
||||
<ControlCard title="Reservoir 1">
|
||||
<div className="grid grid-rows-5 gap-4">
|
||||
<div className="flex flex-row">
|
||||
<TimeSeriesValueNumeric
|
||||
label="Flow 1"
|
||||
label="Flow"
|
||||
unit="l/min"
|
||||
timeseries={front_flow}
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="flex flex-row">
|
||||
<TimeSeriesValueNumeric
|
||||
label="Flow 2"
|
||||
unit="l/min"
|
||||
timeseries={back_flow}
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
<Label label="Pump 1">
|
||||
<SelectionGroup<"On" | "Off">
|
||||
value={frontTargetFlow ? "On" : "Off"}
|
||||
orientation="vertical"
|
||||
className="grid h-full grid-cols-2 gap-2"
|
||||
options={{
|
||||
Off: {
|
||||
children: "Off",
|
||||
icon: "lu:CirclePause",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
On: {
|
||||
children: "On",
|
||||
icon: "lu:CirclePlay",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
}}
|
||||
onChange={(value) => {
|
||||
if (value == "On") {
|
||||
setFrontFlow(true);
|
||||
} else {
|
||||
setFrontFlow(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
<Label label="Pump 2">
|
||||
<SelectionGroup<"On" | "Off">
|
||||
value={backTargetFlow ? "On" : "Off"}
|
||||
orientation="vertical"
|
||||
className="grid h-full grid-cols-2 gap-2"
|
||||
options={{
|
||||
Off: {
|
||||
children: "Off",
|
||||
icon: "lu:CirclePause",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
On: {
|
||||
children: "On",
|
||||
icon: "lu:CirclePlay",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
}}
|
||||
onChange={(value) => {
|
||||
if (value == "On") {
|
||||
setBackFlow(true);
|
||||
} else {
|
||||
setBackFlow(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
</div>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard title="Temperature Measurements">
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div className="flex flex-col gap-8">
|
||||
<TimeSeriesValueNumeric
|
||||
label="Reservoir 1"
|
||||
label="Temperature"
|
||||
unit="C"
|
||||
timeseries={front_temperature}
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
/>
|
||||
<Label label="Set Reservoir 1 Target Temperature">
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-4">
|
||||
<Label label="Set Target Temperature">
|
||||
<EditValue
|
||||
title="Set Reservoir 1 Target Temperature"
|
||||
title="Set Target Temperature"
|
||||
min={0}
|
||||
value={frontTargetTemperature}
|
||||
max={80}
|
||||
unit="C"
|
||||
step={0.1}
|
||||
min={0.0}
|
||||
max={80.0}
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
onChange={(val) => {
|
||||
setFrontTemperature(val);
|
||||
@@ -143,23 +93,88 @@ export function Aquapath1ControlPage() {
|
||||
}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
{frontCurrentTemperature < frontHeatingBoundary &&
|
||||
(state?.flow_states.front.flow ?? 0) > 0 && (
|
||||
<Badge variant="default">
|
||||
Power: {front_power.current?.value} W<br />
|
||||
Total: {front_total_energy.current?.value} kWh
|
||||
</Badge>
|
||||
)}
|
||||
{frontCurrentTemperature > frontCoolingBoundary &&
|
||||
frontTargetFlow &&
|
||||
(state?.flow_states.front.flow ?? 0) && (
|
||||
<Badge variant="secondary">Cooling</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="flex flex-row">
|
||||
<TimeSeriesValueNumeric
|
||||
label="Reservoir 2"
|
||||
label="Revolution Speed"
|
||||
unit="%"
|
||||
timeseries={front_revolutions}
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row">
|
||||
<Label label="Pump">
|
||||
<SelectionGroup<"On" | "Off">
|
||||
value={frontTargetFlow ? "On" : "Off"}
|
||||
orientation="vertical"
|
||||
className="grid h-full grid-cols-2 gap-2"
|
||||
options={{
|
||||
Off: {
|
||||
children: "Off",
|
||||
icon: "lu:CirclePause",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
On: {
|
||||
children: "On",
|
||||
icon: "lu:CirclePlay",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
}}
|
||||
onChange={(value) => {
|
||||
setFrontFlow(value == "On");
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard title="Reservoir 2">
|
||||
<div className="grid grid-rows-4 gap-4">
|
||||
<div className="flex flex-row">
|
||||
<TimeSeriesValueNumeric
|
||||
label="Flow"
|
||||
unit="l/min"
|
||||
timeseries={back_flow}
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row">
|
||||
<TimeSeriesValueNumeric
|
||||
label="Temperature"
|
||||
unit="C"
|
||||
timeseries={back_temperature}
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
/>
|
||||
<Label label="Set Reservoir 2 Target Temperature">
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-4">
|
||||
<Label label="Set Target Temperature">
|
||||
<EditValue
|
||||
title="Set Reservoir 2 Target Temperature"
|
||||
title="Set Target Temperature"
|
||||
min={0}
|
||||
value={backTargetTemperature}
|
||||
max={80}
|
||||
unit="C"
|
||||
step={0.1}
|
||||
min={0.0}
|
||||
max={80.0}
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
onChange={(val) => {
|
||||
setBackTemperature(val);
|
||||
@@ -169,6 +184,56 @@ export function Aquapath1ControlPage() {
|
||||
}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
{backCurrentTemperature < backHeatingBoundary &&
|
||||
backTargetFlow &&
|
||||
(state?.flow_states.front.flow ?? 0) && (
|
||||
<Badge variant="default">
|
||||
Power: {back_power.current?.value} W<br />
|
||||
Total: {back_total_energy.current?.value} kWh
|
||||
</Badge>
|
||||
)}
|
||||
{backCurrentTemperature > backCoolingBoundary &&
|
||||
frontTargetFlow &&
|
||||
(state?.flow_states.front.flow ?? 0) && (
|
||||
<Badge variant="secondary">Cooling</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row">
|
||||
<TimeSeriesValueNumeric
|
||||
label="Revolution Speed"
|
||||
unit="%"
|
||||
timeseries={back_revolutions}
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row">
|
||||
<Label label="Pump">
|
||||
<SelectionGroup<"On" | "Off">
|
||||
value={backTargetFlow ? "On" : "Off"}
|
||||
orientation="vertical"
|
||||
className="grid h-full grid-cols-2 gap-2"
|
||||
options={{
|
||||
Off: {
|
||||
children: "Off",
|
||||
icon: "lu:CirclePause",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
On: {
|
||||
children: "On",
|
||||
icon: "lu:CirclePlay",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
}}
|
||||
onChange={(value) => {
|
||||
setBackFlow(value == "On");
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</ControlCard>
|
||||
|
||||
@@ -20,6 +20,12 @@ export function Aquapath1Page() {
|
||||
icon: "lu:ChartSpline",
|
||||
activeLink: "graphs",
|
||||
},
|
||||
{
|
||||
link: "settings",
|
||||
title: "Config",
|
||||
icon: "lu:Settings",
|
||||
activeLink: "settings",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import { Page } from "@/components/Page";
|
||||
import { ControlCard } from "@/control/ControlCard";
|
||||
import { ControlGrid } from "@/control/ControlGrid";
|
||||
import { EditValue } from "@/control/EditValue";
|
||||
import { Label } from "@/control/Label";
|
||||
import { useAquapath1 } from "./useAquapath";
|
||||
import React from "react";
|
||||
|
||||
export function Aquapath1SettingsPage() {
|
||||
const {
|
||||
state,
|
||||
setFrontRevolutions,
|
||||
setBackRevolutions,
|
||||
setFrontHeatingTolerance,
|
||||
setFrontCoolingTolerance,
|
||||
setBackHeatingTolerance,
|
||||
setBackCoolingTolerance,
|
||||
} = useAquapath1();
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<ControlGrid columns={2}>
|
||||
<ControlCard title="Front Fan Revolutions">
|
||||
<Label label="Set Max Revolution Speed">
|
||||
<EditValue
|
||||
title="Set Max Revolution Speed"
|
||||
min={0}
|
||||
value={state?.fan_states.front.revolutions}
|
||||
max={100}
|
||||
unit="%"
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
onChange={(val) => {
|
||||
setFrontRevolutions(val);
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard title="Back Fan Revolutions">
|
||||
<Label label="Set Max Revolution Speed">
|
||||
<EditValue
|
||||
title="Set Max Revolution Speed"
|
||||
min={0}
|
||||
value={state?.fan_states.back.revolutions}
|
||||
max={100}
|
||||
unit="%"
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
onChange={(val) => {
|
||||
setBackRevolutions(val);
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard title="Front Temperature Tolerances">
|
||||
<Label label="Set Heating Tolerance">
|
||||
<EditValue
|
||||
title="Set Heating Tolerance"
|
||||
min={0}
|
||||
value={state?.tolerance_states.front.heating}
|
||||
max={10}
|
||||
step={0.1}
|
||||
unit="C"
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
onChange={(val) => {
|
||||
setFrontHeatingTolerance(val);
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
<Label label="Set Cooling Tolerance">
|
||||
<EditValue
|
||||
title="Set Cooling Tolerance"
|
||||
min={0}
|
||||
value={state?.tolerance_states.front.cooling}
|
||||
max={10}
|
||||
step={0.1}
|
||||
unit="C"
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
onChange={(val) => {
|
||||
setFrontCoolingTolerance(val);
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard title="Back Temperature Tolerances">
|
||||
<Label label="Set Heating Tolerance">
|
||||
<EditValue
|
||||
title="Set Heating Tolerance"
|
||||
min={0}
|
||||
value={state?.tolerance_states.back.heating}
|
||||
max={10}
|
||||
step={0.1}
|
||||
unit="C"
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
onChange={(val) => {
|
||||
setBackHeatingTolerance(val);
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
<Label label="Set Cooling Tolerance">
|
||||
<EditValue
|
||||
title="Set Cooling Tolerance"
|
||||
min={0}
|
||||
value={state?.tolerance_states.back.cooling}
|
||||
max={10}
|
||||
step={0.1}
|
||||
unit="C"
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
onChange={(val) => {
|
||||
setBackCoolingTolerance(val);
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
</ControlGrid>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
@@ -51,6 +51,23 @@ export const flowStatesSchema = z.object({
|
||||
back: flowStateSchema,
|
||||
});
|
||||
|
||||
export const fanStateSchema = z.object({
|
||||
revolutions: z.number(),
|
||||
max_revolutions: z.number(),
|
||||
});
|
||||
export const fanStatesSchema = z.object({
|
||||
back: fanStateSchema,
|
||||
front: fanStateSchema,
|
||||
});
|
||||
|
||||
export const toleranceStateSchema = z.object({
|
||||
heating: z.number(),
|
||||
cooling: z.number(),
|
||||
});
|
||||
export const toleranceStatesSchema = z.object({
|
||||
back: toleranceStateSchema,
|
||||
front: toleranceStateSchema,
|
||||
});
|
||||
/**
|
||||
* Live values event schema (time-series data)
|
||||
*/
|
||||
@@ -61,6 +78,12 @@ export const liveValuesEventDataSchema = z.object({
|
||||
back_temperature: z.number(),
|
||||
front_temp_reservoir: z.number(),
|
||||
back_temp_reservoir: z.number(),
|
||||
front_revolutions: z.number(),
|
||||
back_revolutions: z.number(),
|
||||
back_power: z.number(),
|
||||
front_power: z.number(),
|
||||
front_total_energy: z.number(),
|
||||
back_total_energy: z.number(),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -71,6 +94,8 @@ export const stateEventDataSchema = z.object({
|
||||
mode_state: modeStateSchema,
|
||||
flow_states: flowStatesSchema,
|
||||
temperature_states: tempStatesSchema,
|
||||
fan_states: fanStatesSchema,
|
||||
tolerance_states: toleranceStatesSchema,
|
||||
});
|
||||
|
||||
// ========== Event Schemas with Wrappers ==========
|
||||
@@ -96,6 +121,15 @@ export type Aquapath1NamespaceStore = {
|
||||
|
||||
front_temp_reservoir: TimeSeries;
|
||||
back_temp_reservoir: TimeSeries;
|
||||
|
||||
front_revolutions: TimeSeries;
|
||||
back_revolutions: TimeSeries;
|
||||
|
||||
front_power: TimeSeries;
|
||||
back_power: TimeSeries;
|
||||
|
||||
front_total_energy: TimeSeries;
|
||||
back_total_energy: TimeSeries;
|
||||
};
|
||||
|
||||
const { initialTimeSeries: front_temperature, insert: addTemperature1 } =
|
||||
@@ -108,6 +142,18 @@ const { initialTimeSeries: back_temp_reservoir, insert: addTempReserv2 } =
|
||||
createTimeSeries();
|
||||
const { initialTimeSeries: front_flow, insert: addFlow1 } = createTimeSeries();
|
||||
const { initialTimeSeries: back_flow, insert: addFlow2 } = createTimeSeries();
|
||||
const { initialTimeSeries: front_revolutions, insert: addFan1 } =
|
||||
createTimeSeries();
|
||||
const { initialTimeSeries: back_revolutions, insert: addFan2 } =
|
||||
createTimeSeries();
|
||||
const { initialTimeSeries: front_power, insert: addFrontPower } =
|
||||
createTimeSeries();
|
||||
const { initialTimeSeries: back_power, insert: addBackPower } =
|
||||
createTimeSeries();
|
||||
const { initialTimeSeries: front_total_energy, insert: addFrontEnergy } =
|
||||
createTimeSeries();
|
||||
const { initialTimeSeries: back_total_energy, insert: addBackEnergy } =
|
||||
createTimeSeries();
|
||||
|
||||
/**
|
||||
* Factory function to create a new Aquapath namespace store
|
||||
@@ -125,6 +171,12 @@ export const createAquapath1NamespaceStore =
|
||||
back_flow: back_flow,
|
||||
front_temp_reservoir: front_temp_reservoir,
|
||||
back_temp_reservoir: back_temp_reservoir,
|
||||
front_revolutions: front_revolutions,
|
||||
back_revolutions: back_revolutions,
|
||||
back_power: back_power,
|
||||
front_power: front_power,
|
||||
front_total_energy: front_total_energy,
|
||||
back_total_energy: back_total_energy,
|
||||
};
|
||||
});
|
||||
};
|
||||
@@ -192,6 +244,30 @@ export function aquapath1MessageHandler(
|
||||
value: liveValuesEvent.data.back_temp_reservoir,
|
||||
timestamp: event.ts,
|
||||
}),
|
||||
front_revolutions: addFan1(state.front_revolutions, {
|
||||
value: liveValuesEvent.data.front_revolutions,
|
||||
timestamp: event.ts,
|
||||
}),
|
||||
back_revolutions: addFan2(state.back_revolutions, {
|
||||
value: liveValuesEvent.data.back_revolutions,
|
||||
timestamp: event.ts,
|
||||
}),
|
||||
front_power: addFrontPower(state.front_power, {
|
||||
value: liveValuesEvent.data.front_power,
|
||||
timestamp: event.ts,
|
||||
}),
|
||||
back_power: addBackPower(state.back_power, {
|
||||
value: liveValuesEvent.data.back_power,
|
||||
timestamp: event.ts,
|
||||
}),
|
||||
front_total_energy: addFrontEnergy(state.front_total_energy, {
|
||||
value: liveValuesEvent.data.front_total_energy,
|
||||
timestamp: event.ts,
|
||||
}),
|
||||
back_total_energy: addBackEnergy(state.back_total_energy, {
|
||||
value: liveValuesEvent.data.back_total_energy,
|
||||
timestamp: event.ts,
|
||||
}),
|
||||
}));
|
||||
} else {
|
||||
handleUnhandledEventError(eventName);
|
||||
|
||||
@@ -42,12 +42,18 @@ export function useAquapath1() {
|
||||
const {
|
||||
state,
|
||||
defaultState,
|
||||
front_temperature: front_temperature,
|
||||
back_temperature: back_temperature,
|
||||
front_flow: front_flow,
|
||||
back_flow: back_flow,
|
||||
front_temp_reservoir: front_temp_reservoir,
|
||||
back_temp_reservoir: back_temp_reservoir,
|
||||
front_temperature,
|
||||
back_temperature,
|
||||
front_flow,
|
||||
back_flow,
|
||||
front_temp_reservoir,
|
||||
back_temp_reservoir,
|
||||
front_revolutions,
|
||||
back_revolutions,
|
||||
front_power,
|
||||
back_power,
|
||||
front_total_energy,
|
||||
back_total_energy,
|
||||
} = useAquapath1Namespace(machineIdentification);
|
||||
|
||||
// Single optimistic state for all state management
|
||||
@@ -125,6 +131,89 @@ export function useAquapath1() {
|
||||
);
|
||||
};
|
||||
|
||||
const setFrontRevolutions = (revolutions: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.fan_states.back.revolutions = revolutions;
|
||||
},
|
||||
() => {
|
||||
requestFrontRevolutions({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetFrontRevolutions: revolutions },
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const setBackRevolutions = (revolutions: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.fan_states.back.revolutions = revolutions;
|
||||
},
|
||||
() =>
|
||||
requestBackRevolutions({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetBackRevolutions: revolutions },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setFrontHeatingTolerance = (tolerance: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.tolerance_states.front.heating = tolerance;
|
||||
},
|
||||
() => {
|
||||
requestFrontHeatingTolerance({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetFrontHeatingTolerance: tolerance },
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const setBackHeatingTolerance = (tolerance: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.tolerance_states.back.heating = tolerance;
|
||||
},
|
||||
() => {
|
||||
requestBackHeatingTolerance({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetBackHeatingTolerance: tolerance },
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const setFrontCoolingTolerance = (tolerance: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.tolerance_states.front.cooling = tolerance;
|
||||
},
|
||||
() => {
|
||||
requestFrontCoolingTolerance({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetFrontCoolingTolerance: tolerance },
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const setBackCoolingTolerance = (tolerance: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.tolerance_states.back.cooling = tolerance;
|
||||
},
|
||||
() => {
|
||||
requestBackCoolingTolerance({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetBackCoolingTolerance: tolerance },
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// Mutation hooks
|
||||
const { request: requestAquapathMode } = useMachineMutation(
|
||||
z.object({ SetAquaPathMode: z.enum(["Standby", "Auto"]) }),
|
||||
@@ -141,6 +230,25 @@ export function useAquapath1() {
|
||||
const { request: requestBackFlow } = useMachineMutation(
|
||||
z.object({ SetBackFlow: z.boolean() }),
|
||||
);
|
||||
const { request: requestFrontRevolutions } = useMachineMutation(
|
||||
z.object({ SetFrontRevolutions: z.number() }),
|
||||
);
|
||||
const { request: requestBackRevolutions } = useMachineMutation(
|
||||
z.object({ SetBackRevolutions: z.number() }),
|
||||
);
|
||||
const { request: requestFrontHeatingTolerance } = useMachineMutation(
|
||||
z.object({ SetFrontHeatingTolerance: z.number() }),
|
||||
);
|
||||
const { request: requestFrontCoolingTolerance } = useMachineMutation(
|
||||
z.object({ SetFrontCoolingTolerance: z.number() }),
|
||||
);
|
||||
const { request: requestBackHeatingTolerance } = useMachineMutation(
|
||||
z.object({ SetBackHeatingTolerance: z.number() }),
|
||||
);
|
||||
const { request: requestBackCoolingTolerance } = useMachineMutation(
|
||||
z.object({ SetBackCoolingTolerance: z.number() }),
|
||||
);
|
||||
|
||||
// Helper function for optimistic updates using produce
|
||||
const updateStateOptimistically = (
|
||||
producer: (current: StateEvent) => void,
|
||||
@@ -165,11 +273,23 @@ export function useAquapath1() {
|
||||
back_temperature,
|
||||
front_temp_reservoir,
|
||||
back_temp_reservoir,
|
||||
front_revolutions,
|
||||
back_revolutions,
|
||||
front_power,
|
||||
back_power,
|
||||
front_total_energy,
|
||||
back_total_energy,
|
||||
|
||||
setAquapathMode,
|
||||
setFrontTemperature,
|
||||
setBackTemperature,
|
||||
setFrontFlow,
|
||||
setBackFlow,
|
||||
setFrontRevolutions,
|
||||
setBackRevolutions,
|
||||
setFrontHeatingTolerance,
|
||||
setBackHeatingTolerance,
|
||||
setFrontCoolingTolerance,
|
||||
setBackCoolingTolerance,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { ControlCard } from "@/control/ControlCard";
|
||||
import { Page } from "@/components/Page";
|
||||
import React from "react";
|
||||
import { Label } from "@/control/Label";
|
||||
import { useDigitalInputTestMachine } from "./useDigitalInputTestMachine";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ControlGrid } from "@/control/ControlGrid";
|
||||
import { SelectionGroup } from "@/control/SelectionGroup";
|
||||
import { toastError } from "@/components/Toast";
|
||||
|
||||
export function DigitalInputTestMachineControlPage() {
|
||||
const { state } = useDigitalInputTestMachine();
|
||||
|
||||
const safeState = state ?? { led_on: [false, false, false, false] };
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<ControlCard title="Machine LEDs">
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
{safeState.led_on.map((led, index) => (
|
||||
<Label key={index} label={`LED ${index + 1}`}>
|
||||
<Badge variant={led ? "outline" : "destructive"}>
|
||||
{led ? "On" : "OFF"}
|
||||
</Badge>
|
||||
</Label>
|
||||
))}
|
||||
</div>
|
||||
</ControlCard>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Topbar } from "@/components/Topbar";
|
||||
import { digitalInputTestMachineSerialRoute } from "@/routes/routes";
|
||||
import React from "react";
|
||||
|
||||
export function DigitalInputTestMachinePage() {
|
||||
const { serial } = digitalInputTestMachineSerialRoute.useParams();
|
||||
return (
|
||||
<Topbar
|
||||
pathname={`/_sidebar/machines/digitalInputTestMachine/${serial}`}
|
||||
items={[
|
||||
{
|
||||
link: "control",
|
||||
activeLink: "control",
|
||||
title: "Control",
|
||||
icon: "lu:CirclePlay",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { StoreApi } from "zustand";
|
||||
import { create } from "zustand";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
EventHandler,
|
||||
eventSchema,
|
||||
Event,
|
||||
handleUnhandledEventError,
|
||||
NamespaceId,
|
||||
createNamespaceHookImplementation,
|
||||
ThrottledStoreUpdater,
|
||||
} from "@/client/socketioStore";
|
||||
import { MachineIdentificationUnique } from "@/machines/types";
|
||||
|
||||
// ========== Event Schema ==========
|
||||
|
||||
export const stateEventDataSchema = z.object({
|
||||
led_on: z.array(z.boolean()).length(4),
|
||||
});
|
||||
|
||||
export const stateEventSchema = eventSchema(stateEventDataSchema);
|
||||
|
||||
export type StateEvent = z.infer<typeof stateEventDataSchema>;
|
||||
|
||||
// ========== Store ==========
|
||||
export type DigitalTestMachineNamespaceStore = {
|
||||
state: StateEvent | null;
|
||||
};
|
||||
|
||||
export const createTestMachineNamespaceStore =
|
||||
(): StoreApi<DigitalTestMachineNamespaceStore> =>
|
||||
create<DigitalTestMachineNamespaceStore>(() => ({
|
||||
state: null,
|
||||
}));
|
||||
|
||||
// ========== Message Handler ==========
|
||||
export function digitalInputTestMachineMessageHandler(
|
||||
store: StoreApi<DigitalTestMachineNamespaceStore>,
|
||||
throttledUpdater: ThrottledStoreUpdater<DigitalTestMachineNamespaceStore>,
|
||||
): EventHandler {
|
||||
return (event: Event<any>) => {
|
||||
const updateStore = (
|
||||
updater: (
|
||||
state: DigitalTestMachineNamespaceStore,
|
||||
) => DigitalTestMachineNamespaceStore,
|
||||
) => throttledUpdater.updateWith(updater);
|
||||
|
||||
try {
|
||||
if (event.name === "StateEvent") {
|
||||
const parsed = stateEventSchema.parse(event);
|
||||
updateStore(() => ({ state: parsed.data }));
|
||||
} else {
|
||||
handleUnhandledEventError(event.name);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error processing ${event.name}:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ========== Namespace Hook ==========
|
||||
const useDigitalInputTestMachineNamespaceImplementation =
|
||||
createNamespaceHookImplementation<DigitalTestMachineNamespaceStore>({
|
||||
createStore: createTestMachineNamespaceStore,
|
||||
createEventHandler: digitalInputTestMachineMessageHandler,
|
||||
});
|
||||
|
||||
export function useDigitalInputTestMachineNamespace(
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
): DigitalTestMachineNamespaceStore {
|
||||
const namespaceId: NamespaceId = {
|
||||
type: "machine",
|
||||
machine_identification_unique,
|
||||
};
|
||||
|
||||
return useDigitalInputTestMachineNamespaceImplementation(namespaceId);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Toast, toastError } from "@/components/Toast";
|
||||
import { useStateOptimistic } from "@/lib/useStateOptimistic";
|
||||
import { digitalInputTestMachineSerialRoute } from "@/routes/routes";
|
||||
import { MachineIdentificationUnique } from "@/machines/types";
|
||||
import {
|
||||
useDigitalInputTestMachineNamespace,
|
||||
StateEvent,
|
||||
} from "./digitalInputTestMachineNamespace";
|
||||
import { useMachineMutate } from "@/client/useClient";
|
||||
import { produce } from "immer";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { digitalInputTestMachine } from "@/machines/properties";
|
||||
import { z } from "zod";
|
||||
export function useDigitalInputTestMachine() {
|
||||
const { serial: serialString } =
|
||||
digitalInputTestMachineSerialRoute.useParams();
|
||||
|
||||
// Memoize machine identification
|
||||
const machineIdentification: MachineIdentificationUnique = useMemo(() => {
|
||||
const serial = parseInt(serialString);
|
||||
|
||||
if (isNaN(serial)) {
|
||||
toastError(
|
||||
"Invalid Serial Number",
|
||||
`"${serialString}" is not a valid serial number.`,
|
||||
);
|
||||
|
||||
return {
|
||||
machine_identification: { vendor: 0, machine: 0 },
|
||||
serial: 0,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
machine_identification: digitalInputTestMachine.machine_identification,
|
||||
serial,
|
||||
};
|
||||
}, [serialString]);
|
||||
|
||||
// Namespace state from backend
|
||||
const { state } = useDigitalInputTestMachineNamespace(machineIdentification);
|
||||
|
||||
// Optimistic state
|
||||
const stateOptimistic = useStateOptimistic<StateEvent>();
|
||||
|
||||
useEffect(() => {
|
||||
if (state) stateOptimistic.setReal(state);
|
||||
}, [state, stateOptimistic]);
|
||||
|
||||
return {
|
||||
state: stateOptimistic.value,
|
||||
};
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
PresetPreviewEntries,
|
||||
previewSeparator,
|
||||
} from "@/components/preset/PresetPreviewTable";
|
||||
import { Preset, PresetData } from "@/lib/preset/preset";
|
||||
import { Preset } from "@/lib/preset/preset";
|
||||
|
||||
const extruder1PresetDataSchema = z
|
||||
.object({
|
||||
@@ -31,7 +31,7 @@ const extruder1PresetDataSchema = z
|
||||
})
|
||||
.partial();
|
||||
|
||||
type Extruder2 = typeof extruder1PresetDataSchema;
|
||||
type Extruder2 = z.infer<typeof extruder1PresetDataSchema>;
|
||||
|
||||
const schemas = new Map([[1, extruder1PresetDataSchema]]);
|
||||
|
||||
@@ -39,76 +39,69 @@ const previewEntries: PresetPreviewEntries<Extruder2> = [
|
||||
{
|
||||
name: "Target Front Temperature",
|
||||
unit: "C",
|
||||
renderValue: (data: PresetData<Extruder2>) =>
|
||||
renderValue: (data: Extruder2) =>
|
||||
data.targetFrontHeatingTemperature?.toFixed(1),
|
||||
},
|
||||
{
|
||||
name: "Target Middle Temperature",
|
||||
unit: "C",
|
||||
renderValue: (data: PresetData<Extruder2>) =>
|
||||
renderValue: (data: Extruder2) =>
|
||||
data.targetMiddleHeatingTemperature?.toFixed(1),
|
||||
},
|
||||
{
|
||||
name: "Target Back Temperature",
|
||||
unit: "C",
|
||||
renderValue: (data: PresetData<Extruder2>) =>
|
||||
renderValue: (data: Extruder2) =>
|
||||
data.targetBackHeatingTemperature?.toFixed(1),
|
||||
},
|
||||
{
|
||||
name: "Target Nozzle Temperature",
|
||||
unit: "C",
|
||||
renderValue: (data: PresetData<Extruder2>) =>
|
||||
renderValue: (data: Extruder2) =>
|
||||
data.targetNozzleHeatingTemperature?.toFixed(1),
|
||||
},
|
||||
previewSeparator,
|
||||
{
|
||||
name: "Inverter Regulation",
|
||||
renderValue: (data: PresetData<Extruder2>) => data.inverterRegulation,
|
||||
renderValue: (data: Extruder2) => data.inverterRegulation,
|
||||
},
|
||||
{
|
||||
name: "Target Inverter RPM",
|
||||
unit: "rpm",
|
||||
renderValue: (data: PresetData<Extruder2>) =>
|
||||
data.targetInverterRpm?.toFixed(2),
|
||||
renderValue: (data: Extruder2) => data.targetInverterRpm?.toFixed(2),
|
||||
},
|
||||
{
|
||||
name: "Target Inverter Pressure",
|
||||
unit: "bar",
|
||||
renderValue: (data: PresetData<Extruder2>) =>
|
||||
data.targetInverterPressure?.toFixed(1),
|
||||
renderValue: (data: Extruder2) => data.targetInverterPressure?.toFixed(1),
|
||||
},
|
||||
{
|
||||
name: "Inverter Direction",
|
||||
renderValue: (data: PresetData<Extruder2>) =>
|
||||
data.inverterRotationDirection,
|
||||
renderValue: (data: Extruder2) => data.inverterRotationDirection,
|
||||
},
|
||||
previewSeparator,
|
||||
{
|
||||
name: "Pressure Limit",
|
||||
unit: "bar",
|
||||
renderValue: (data: PresetData<Extruder2>) =>
|
||||
data.pressureLimit?.toFixed(1),
|
||||
renderValue: (data: Extruder2) => data.pressureLimit?.toFixed(1),
|
||||
},
|
||||
{
|
||||
name: "Enable Pressure Limit",
|
||||
renderValue: (data: PresetData<Extruder2>) =>
|
||||
renderValue: (data: Extruder2) =>
|
||||
data.pressureLimitEnabled ? "on" : "off",
|
||||
},
|
||||
previewSeparator,
|
||||
{
|
||||
name: "PID Pressue Kp",
|
||||
renderValue: (data: PresetData<Extruder2>) =>
|
||||
data.pidPressureKp?.toFixed(4),
|
||||
renderValue: (data: Extruder2) => data.pidPressureKp?.toFixed(4),
|
||||
},
|
||||
{
|
||||
name: "PID Pressue Ki",
|
||||
renderValue: (data: PresetData<Extruder2>) =>
|
||||
data.pidPressureKi?.toFixed(4),
|
||||
renderValue: (data: Extruder2) => data.pidPressureKi?.toFixed(4),
|
||||
},
|
||||
{
|
||||
name: "PID Pressue Kd",
|
||||
renderValue: (data: PresetData<Extruder2>) =>
|
||||
data.pidPressureKd?.toFixed(4),
|
||||
renderValue: (data: Extruder2) => data.pidPressureKd?.toFixed(4),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -135,7 +128,7 @@ export function Extruder2PresetsPage() {
|
||||
setPressurePidKp,
|
||||
} = useExtruder2();
|
||||
|
||||
const toPresetData = (s?: typeof state): PresetData<Extruder2> => ({
|
||||
const toPresetData = (s?: typeof state): Extruder2 => ({
|
||||
targetFrontHeatingTemperature: s?.heating_states.front.target_temperature,
|
||||
targetMiddleHeatingTemperature: s?.heating_states.middle.target_temperature,
|
||||
targetBackHeatingTemperature: s?.heating_states.back.target_temperature,
|
||||
@@ -161,32 +154,32 @@ export function Extruder2PresetsPage() {
|
||||
|
||||
const applyPreset = (preset: Preset<Extruder2>) => {
|
||||
setFrontHeatingTemperature(
|
||||
preset.data.targetFrontHeatingTemperature ?? 150.0,
|
||||
preset?.data?.targetFrontHeatingTemperature ?? 150.0,
|
||||
);
|
||||
setMiddleHeatingTemperature(
|
||||
preset.data.targetMiddleHeatingTemperature ?? 150.0,
|
||||
preset?.data?.targetMiddleHeatingTemperature ?? 150.0,
|
||||
);
|
||||
setBackHeatingTemperature(
|
||||
preset.data.targetBackHeatingTemperature ?? 150.0,
|
||||
preset?.data?.targetBackHeatingTemperature ?? 150.0,
|
||||
);
|
||||
setNozzleHeatingTemperature(
|
||||
preset.data.targetNozzleHeatingTemperature ?? 150.0,
|
||||
preset?.data?.targetNozzleHeatingTemperature ?? 150.0,
|
||||
);
|
||||
|
||||
setInverterTargetRpm(preset.data.targetInverterRpm ?? 0);
|
||||
setInverterTargetPressure(preset.data.targetInverterPressure ?? 0);
|
||||
setInverterRegulation(preset.data.inverterRegulation === "RPM");
|
||||
setInverterTargetRpm(preset?.data?.targetInverterRpm ?? 0);
|
||||
setInverterTargetPressure(preset?.data?.targetInverterPressure ?? 0);
|
||||
setInverterRegulation(preset?.data?.inverterRegulation === "RPM");
|
||||
|
||||
setInverterRotationDirection(
|
||||
preset.data.inverterRotationDirection === "Forward",
|
||||
preset?.data?.inverterRotationDirection === "Forward",
|
||||
);
|
||||
|
||||
setExtruderPressureLimit(preset.data.pressureLimit ?? 100.0);
|
||||
setExtruderPressureLimitEnabled(preset.data.pressureLimitEnabled ?? true);
|
||||
setExtruderPressureLimit(preset?.data?.pressureLimit ?? 100.0);
|
||||
setExtruderPressureLimitEnabled(preset?.data?.pressureLimitEnabled ?? true);
|
||||
|
||||
setPressurePidKp(preset.data.pidPressureKp ?? 0.16);
|
||||
setPressurePidKi(preset.data.pidPressureKi ?? 0.0);
|
||||
setPressurePidKd(preset.data.pidPressureKd ?? 0.008);
|
||||
setPressurePidKp(preset?.data?.pidPressureKp ?? 0.16);
|
||||
setPressurePidKi(preset?.data?.pidPressureKi ?? 0.0);
|
||||
setPressurePidKd(preset?.data?.pidPressureKd ?? 0.008);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
PresetPreviewEntries,
|
||||
previewSeparator,
|
||||
} from "@/components/preset/PresetPreviewTable";
|
||||
import { Preset, PresetData } from "@/lib/preset/preset";
|
||||
import { Preset } from "@/lib/preset/preset";
|
||||
|
||||
const extruder3PresetDataSchema = z
|
||||
.object({
|
||||
@@ -31,7 +31,7 @@ const extruder3PresetDataSchema = z
|
||||
})
|
||||
.partial();
|
||||
|
||||
type Extruder3 = typeof extruder3PresetDataSchema;
|
||||
type Extruder3 = z.infer<typeof extruder3PresetDataSchema>;
|
||||
|
||||
const schemas = new Map([[1, extruder3PresetDataSchema]]);
|
||||
|
||||
@@ -39,76 +39,69 @@ const previewEntries: PresetPreviewEntries<Extruder3> = [
|
||||
{
|
||||
name: "Target Front Temperature",
|
||||
unit: "C",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
renderValue: (data: Extruder3) =>
|
||||
data.targetFrontHeatingTemperature?.toFixed(1),
|
||||
},
|
||||
{
|
||||
name: "Target Middle Temperature",
|
||||
unit: "C",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
renderValue: (data: Extruder3) =>
|
||||
data.targetMiddleHeatingTemperature?.toFixed(1),
|
||||
},
|
||||
{
|
||||
name: "Target Back Temperature",
|
||||
unit: "C",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
renderValue: (data: Extruder3) =>
|
||||
data.targetBackHeatingTemperature?.toFixed(1),
|
||||
},
|
||||
{
|
||||
name: "Target Nozzle Temperature",
|
||||
unit: "C",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
renderValue: (data: Extruder3) =>
|
||||
data.targetNozzleHeatingTemperature?.toFixed(1),
|
||||
},
|
||||
previewSeparator,
|
||||
{
|
||||
name: "Inverter Regulation",
|
||||
renderValue: (data: PresetData<Extruder3>) => data.inverterRegulation,
|
||||
renderValue: (data: Extruder3) => data.inverterRegulation,
|
||||
},
|
||||
{
|
||||
name: "Target Inverter RPM",
|
||||
unit: "rpm",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.targetInverterRpm?.toFixed(2),
|
||||
renderValue: (data: Extruder3) => data.targetInverterRpm?.toFixed(2),
|
||||
},
|
||||
{
|
||||
name: "Target Inverter Pressure",
|
||||
unit: "bar",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.targetInverterPressure?.toFixed(1),
|
||||
renderValue: (data: Extruder3) => data.targetInverterPressure?.toFixed(1),
|
||||
},
|
||||
{
|
||||
name: "Inverter Direction",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.inverterRotationDirection,
|
||||
renderValue: (data: Extruder3) => data.inverterRotationDirection,
|
||||
},
|
||||
previewSeparator,
|
||||
{
|
||||
name: "Pressure Limit",
|
||||
unit: "bar",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.pressureLimit?.toFixed(1),
|
||||
renderValue: (data: Extruder3) => data.pressureLimit?.toFixed(1),
|
||||
},
|
||||
{
|
||||
name: "Enable Pressure Limit",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
renderValue: (data: Extruder3) =>
|
||||
data.pressureLimitEnabled ? "on" : "off",
|
||||
},
|
||||
previewSeparator,
|
||||
{
|
||||
name: "PID Pressue Kp",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.pidPressureKp?.toFixed(4),
|
||||
renderValue: (data: Extruder3) => data.pidPressureKp?.toFixed(4),
|
||||
},
|
||||
{
|
||||
name: "PID Pressue Ki",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.pidPressureKi?.toFixed(4),
|
||||
renderValue: (data: Extruder3) => data.pidPressureKi?.toFixed(4),
|
||||
},
|
||||
{
|
||||
name: "PID Pressue Kd",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.pidPressureKd?.toFixed(4),
|
||||
renderValue: (data: Extruder3) => data.pidPressureKd?.toFixed(4),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -135,7 +128,7 @@ export function Extruder3PresetsPage() {
|
||||
setPressurePidKp,
|
||||
} = useExtruder3();
|
||||
|
||||
const toPresetData = (s?: typeof state): PresetData<Extruder3> => ({
|
||||
const toPresetData = (s?: typeof state): Extruder3 => ({
|
||||
targetFrontHeatingTemperature: s?.heating_states.front.target_temperature,
|
||||
targetMiddleHeatingTemperature: s?.heating_states.middle.target_temperature,
|
||||
targetBackHeatingTemperature: s?.heating_states.back.target_temperature,
|
||||
@@ -161,32 +154,32 @@ export function Extruder3PresetsPage() {
|
||||
|
||||
const applyPreset = (preset: Preset<Extruder3>) => {
|
||||
setFrontHeatingTemperature(
|
||||
preset.data.targetFrontHeatingTemperature ?? 150.0,
|
||||
preset?.data?.targetFrontHeatingTemperature ?? 150.0,
|
||||
);
|
||||
setMiddleHeatingTemperature(
|
||||
preset.data.targetMiddleHeatingTemperature ?? 150.0,
|
||||
preset?.data?.targetMiddleHeatingTemperature ?? 150.0,
|
||||
);
|
||||
setBackHeatingTemperature(
|
||||
preset.data.targetBackHeatingTemperature ?? 150.0,
|
||||
preset?.data?.targetBackHeatingTemperature ?? 150.0,
|
||||
);
|
||||
setNozzleHeatingTemperature(
|
||||
preset.data.targetNozzleHeatingTemperature ?? 150.0,
|
||||
preset?.data?.targetNozzleHeatingTemperature ?? 150.0,
|
||||
);
|
||||
|
||||
setInverterTargetRpm(preset.data.targetInverterRpm ?? 0);
|
||||
setInverterTargetPressure(preset.data.targetInverterPressure ?? 0);
|
||||
setInverterRegulation(preset.data.inverterRegulation === "RPM");
|
||||
setInverterTargetRpm(preset?.data?.targetInverterRpm ?? 0);
|
||||
setInverterTargetPressure(preset?.data?.targetInverterPressure ?? 0);
|
||||
setInverterRegulation(preset?.data?.inverterRegulation === "RPM");
|
||||
|
||||
setInverterRotationDirection(
|
||||
preset.data.inverterRotationDirection === "Forward",
|
||||
preset?.data?.inverterRotationDirection === "Forward",
|
||||
);
|
||||
|
||||
setExtruderPressureLimit(preset.data.pressureLimit ?? 100.0);
|
||||
setExtruderPressureLimitEnabled(preset.data.pressureLimitEnabled ?? true);
|
||||
setExtruderPressureLimit(preset?.data?.pressureLimit ?? 100.0);
|
||||
setExtruderPressureLimitEnabled(preset?.data?.pressureLimitEnabled ?? true);
|
||||
|
||||
setPressurePidKp(preset.data.pidPressureKp ?? 0.16);
|
||||
setPressurePidKi(preset.data.pidPressureKi ?? 0.0);
|
||||
setPressurePidKd(preset.data.pidPressureKd ?? 0.008);
|
||||
setPressurePidKp(preset?.data?.pidPressureKp ?? 0.16);
|
||||
setPressurePidKi(preset?.data?.pidPressureKi ?? 0.0);
|
||||
setPressurePidKd(preset?.data?.pidPressureKd ?? 0.008);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { z } from "zod";
|
||||
import { useLaser1 } from "./useLaser1";
|
||||
import { Preset, PresetData } from "@/lib/preset/preset";
|
||||
import { Preset } from "@/lib/preset/preset";
|
||||
import { laser1 } from "@/machines/properties";
|
||||
import { PresetPreviewEntries } from "@/components/preset/PresetPreviewTable";
|
||||
import { PresetsPage } from "@/components/preset/PresetsPage";
|
||||
@@ -14,7 +14,7 @@ const laser1PresetDataSchema = z
|
||||
})
|
||||
.partial();
|
||||
|
||||
type Laser1 = typeof laser1PresetDataSchema;
|
||||
type Laser1 = z.infer<typeof laser1PresetDataSchema>;
|
||||
|
||||
const schemas = new Map([[1, laser1PresetDataSchema]]);
|
||||
|
||||
@@ -22,18 +22,17 @@ const previewEntries: PresetPreviewEntries<Laser1> = [
|
||||
{
|
||||
name: "Target Diameter",
|
||||
unit: "mm",
|
||||
renderValue: (data: PresetData<Laser1>) => data?.targetDiameter?.toFixed(3),
|
||||
renderValue: (data: Laser1) => data?.targetDiameter?.toFixed(3),
|
||||
},
|
||||
{
|
||||
name: "Lower Tolerance",
|
||||
unit: "mm",
|
||||
renderValue: (data: PresetData<Laser1>) => data?.lowerTolerance?.toFixed(3),
|
||||
renderValue: (data: Laser1) => data?.lowerTolerance?.toFixed(3),
|
||||
},
|
||||
{
|
||||
name: "Higher Tolerance",
|
||||
unit: "mm",
|
||||
renderValue: (data: PresetData<Laser1>) =>
|
||||
data?.higherTolerance?.toFixed(3),
|
||||
renderValue: (data: Laser1) => data?.higherTolerance?.toFixed(3),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -47,16 +46,16 @@ export function Laser1PresetsPage() {
|
||||
} = useLaser1();
|
||||
|
||||
const applyPreset = (preset: Preset<Laser1>) => {
|
||||
const targetDiameter = preset.data.targetDiameter ?? 1.75;
|
||||
const lowerTolerance = preset.data?.lowerTolerance ?? 0.05;
|
||||
const higherTolerance = preset.data?.higherTolerance ?? 0.05;
|
||||
const targetDiameter = preset?.data?.targetDiameter ?? 1.75;
|
||||
const lowerTolerance = preset?.data?.lowerTolerance ?? 0.05;
|
||||
const higherTolerance = preset?.data?.higherTolerance ?? 0.05;
|
||||
|
||||
setTargetDiameter(targetDiameter);
|
||||
setLowerTolerance(lowerTolerance);
|
||||
setHigherTolerance(higherTolerance);
|
||||
};
|
||||
|
||||
const toPresetData = (s: typeof state): PresetData<Laser1> => ({
|
||||
const toPresetData = (s: typeof state): Laser1 => ({
|
||||
targetDiameter: s?.laser_state.target_diameter,
|
||||
lowerTolerance: s?.laser_state.lower_tolerance,
|
||||
higherTolerance: s?.laser_state.higher_tolerance,
|
||||
|
||||
@@ -66,19 +66,13 @@ export type Laser1NamespaceStore = {
|
||||
roundness: TimeSeries;
|
||||
};
|
||||
|
||||
// Constants for time durations
|
||||
const HALF_SECOND = 500;
|
||||
|
||||
const { initialTimeSeries: diameter, insert: addDiameter } = createTimeSeries({
|
||||
sampleIntervalLong: HALF_SECOND,
|
||||
});
|
||||
const { initialTimeSeries: diameter, insert: addDiameter } = createTimeSeries();
|
||||
const { initialTimeSeries: x_diameter, insert: addXDiameter } =
|
||||
createTimeSeries({ sampleIntervalLong: HALF_SECOND });
|
||||
createTimeSeries();
|
||||
const { initialTimeSeries: y_diameter, insert: addYDiameter } =
|
||||
createTimeSeries({ sampleIntervalLong: HALF_SECOND });
|
||||
const { initialTimeSeries: roundness, insert: addRoundness } = createTimeSeries(
|
||||
{ sampleIntervalLong: HALF_SECOND },
|
||||
);
|
||||
createTimeSeries();
|
||||
const { initialTimeSeries: roundness, insert: addRoundness } =
|
||||
createTimeSeries();
|
||||
|
||||
/**
|
||||
* Factory function to create a new Laser1 namespace store
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useMock1 } from "./useMock";
|
||||
import { mock1 } from "@/machines/properties";
|
||||
|
||||
import { PresetsPage } from "@/components/preset/PresetsPage";
|
||||
import { Preset, PresetData } from "@/lib/preset/preset";
|
||||
import { Preset } from "@/lib/preset/preset";
|
||||
import { PresetPreviewEntries } from "@/components/preset/PresetPreviewTable";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -15,7 +15,7 @@ const mock1PresetDataSchema = z
|
||||
})
|
||||
.partial();
|
||||
|
||||
type Mock1 = typeof mock1PresetDataSchema;
|
||||
type Mock1 = z.infer<typeof mock1PresetDataSchema>;
|
||||
|
||||
const schemas = new Map([[1, mock1PresetDataSchema]]);
|
||||
|
||||
@@ -23,17 +23,17 @@ const previewEntries: PresetPreviewEntries<Mock1> = [
|
||||
{
|
||||
name: "Frequency 1",
|
||||
unit: "mHz",
|
||||
renderValue: (data: PresetData<Mock1>) => data?.frequency1?.toFixed(3),
|
||||
renderValue: (data: Mock1) => data?.frequency1?.toFixed(3),
|
||||
},
|
||||
{
|
||||
name: "Frequency 2",
|
||||
unit: "mHz",
|
||||
renderValue: (data: PresetData<Mock1>) => data?.frequency2?.toFixed(3),
|
||||
renderValue: (data: Mock1) => data?.frequency2?.toFixed(3),
|
||||
},
|
||||
{
|
||||
name: "Frequency 3",
|
||||
unit: "mHz",
|
||||
renderValue: (data: PresetData<Mock1>) => data?.frequency3?.toFixed(3),
|
||||
renderValue: (data: Mock1) => data?.frequency3?.toFixed(3),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -51,7 +51,7 @@ export function Mock1PresetsPage() {
|
||||
setFrequency3(frequency3);
|
||||
};
|
||||
|
||||
const toPresetData = (s: typeof state): PresetData<Mock1> => ({
|
||||
const toPresetData = (s: typeof state): Mock1 => ({
|
||||
frequency1: s?.frequency1 ?? defaultState?.frequency1,
|
||||
frequency2: s?.frequency2 ?? defaultState?.frequency2,
|
||||
frequency3: s?.frequency3 ?? defaultState?.frequency3,
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import React from "react";
|
||||
|
||||
import { ControlCard } from "@/control/ControlCard";
|
||||
import { Page } from "@/components/Page";
|
||||
import { ControlGrid } from "@/control/ControlGrid";
|
||||
import { TimeSeriesValueNumeric } from "@/control/TimeSeriesValue";
|
||||
import { MinMaxValue, TIMEFRAME_OPTIONS } from "@/control/MinMaxValue";
|
||||
import { EditValue } from "@/control/EditValue";
|
||||
import { Label } from "@/control/Label";
|
||||
|
||||
import { usePellet1 as usePellet1 } from "./usePelletizer";
|
||||
|
||||
import { SelectionGroupBoolean } from "@/control/SelectionGroup";
|
||||
import { createTimeSeries, TimeSeries } from "@/lib/timeseries";
|
||||
import { roundToDecimals } from "@/lib/decimal";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
export function Pellet1ConfigPage() {
|
||||
const {
|
||||
//state
|
||||
state,
|
||||
defaultState,
|
||||
|
||||
//live values
|
||||
frequency,
|
||||
temperature,
|
||||
voltage,
|
||||
current,
|
||||
|
||||
// mutation functions
|
||||
SetRunning,
|
||||
SetDirection,
|
||||
SetFrequencyTarget,
|
||||
SetAccelerationLevel,
|
||||
SetDecelerationLevel,
|
||||
} = usePellet1();
|
||||
|
||||
// Extract values from consolidated state
|
||||
const running = state?.inverter_state?.running ?? false;
|
||||
const direction = state?.inverter_state?.direction ?? true;
|
||||
const frequency_target = state?.inverter_state?.frequency_target ?? 0;
|
||||
const acceleration_level = state?.inverter_state?.acceleration_level ?? 0;
|
||||
const deceleration_level = state?.inverter_state?.deceleration_level ?? 0;
|
||||
|
||||
// Shared timeframe state (default 5 minutes)
|
||||
const [timeframe, setTimeframe] = React.useState<number>(5 * 60 * 1000);
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<ControlGrid columns={2}>
|
||||
<ControlCard title="Advanced Inverter Configuration">
|
||||
<SelectionGroupBoolean
|
||||
value={running}
|
||||
optionTrue={{ children: "On" }}
|
||||
optionFalse={{ children: "Off" }}
|
||||
onChange={(v) => {
|
||||
SetRunning(v)
|
||||
}}
|
||||
/>
|
||||
|
||||
<Label label="Rotation Direction">
|
||||
<SelectionGroupBoolean
|
||||
value={direction}
|
||||
optionTrue={{ children: "Forward" }}
|
||||
optionFalse={{ children: "Backward" }}
|
||||
onChange={(v) => {
|
||||
SetDirection(v)
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
<Label label="Set Target Frequency">
|
||||
<EditValue
|
||||
title="Set Target Frequency"
|
||||
value={frequency_target}
|
||||
unit="Hz"
|
||||
step={0.1}
|
||||
min={0}
|
||||
max={50}
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
onChange={(val) => {
|
||||
SetFrequencyTarget(val);
|
||||
}}
|
||||
defaultValue={5}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Set Acceleration Level">
|
||||
<EditValue
|
||||
title="Set Acceleration Level"
|
||||
value={acceleration_level}
|
||||
unit={undefined}
|
||||
step={1}
|
||||
min={1}
|
||||
max={15}
|
||||
renderValue={(value) => value.toFixed(0)}
|
||||
onChange={(val) => SetAccelerationLevel(val)}
|
||||
defaultValue={7}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Set Deceleration Level">
|
||||
<EditValue
|
||||
title="Set Deceleration Level"
|
||||
value={deceleration_level}
|
||||
unit={undefined}
|
||||
step={1}
|
||||
min={1}
|
||||
max={15}
|
||||
renderValue={(value) => value.toFixed(0)}
|
||||
onChange={(val) => SetDecelerationLevel(val)}
|
||||
defaultValue={7}
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
</ControlGrid>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import React from "react";
|
||||
|
||||
import { ControlCard } from "@/control/ControlCard";
|
||||
import { Page } from "@/components/Page";
|
||||
import { ControlGrid } from "@/control/ControlGrid";
|
||||
import { TimeSeriesValueNumeric } from "@/control/TimeSeriesValue";
|
||||
import { MinMaxValue, TIMEFRAME_OPTIONS } from "@/control/MinMaxValue";
|
||||
import { EditValue } from "@/control/EditValue";
|
||||
import { Label } from "@/control/Label";
|
||||
|
||||
import { usePellet1 as usePellet1 } from "./usePelletizer";
|
||||
|
||||
import { SelectionGroupBoolean } from "@/control/SelectionGroup";
|
||||
import { createTimeSeries, TimeSeries } from "@/lib/timeseries";
|
||||
import { roundToDecimals } from "@/lib/decimal";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
export function Pellet1ControlPage() {
|
||||
const {
|
||||
//state
|
||||
state,
|
||||
defaultState,
|
||||
|
||||
//live values
|
||||
frequency,
|
||||
temperature,
|
||||
voltage,
|
||||
current,
|
||||
|
||||
// mutation functions
|
||||
SetRunning,
|
||||
SetDirection,
|
||||
SetFrequencyTarget,
|
||||
SetAccelerationLevel,
|
||||
SetDecelerationLevel,
|
||||
} = usePellet1();
|
||||
|
||||
// Extract values from consolidated state
|
||||
const running = state?.inverter_state?.running ?? false;
|
||||
const direction = state?.inverter_state?.direction ?? true;
|
||||
const frequency_target = state?.inverter_state?.frequency_target ?? 0;
|
||||
const acceleration_level = state?.inverter_state?.acceleration_level ?? 0;
|
||||
const deceleration_level = state?.inverter_state?.deceleration_level ?? 0;
|
||||
|
||||
// Shared timeframe state (default 5 minutes)
|
||||
const [timeframe, setTimeframe] = React.useState<number>(5 * 60 * 1000);
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<ControlGrid columns={2}>
|
||||
<ControlCard title="Motor Configuration">
|
||||
<SelectionGroupBoolean
|
||||
value={running}
|
||||
optionTrue={{ children: "On" }}
|
||||
optionFalse={{ children: "Off" }}
|
||||
onChange={(v) => {
|
||||
SetRunning(v)
|
||||
}}
|
||||
/>
|
||||
|
||||
<Label label="Speed">
|
||||
<EditValue
|
||||
title="Speed"
|
||||
value={frequency_target * 2}
|
||||
unit="%"
|
||||
step={0.5}
|
||||
min={0}
|
||||
max={100}
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
onChange={(val) => {
|
||||
SetFrequencyTarget(val / 2);
|
||||
}}
|
||||
defaultValue={5}
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard title="Inverter Status">
|
||||
|
||||
{
|
||||
/*
|
||||
|
||||
<Badge
|
||||
className={`text-md ${true ? "bg-white-600 border-green-600 text-green-600" : "bg-red-600 font-bold text-white"} mx-auto h-12 w-[100%] border-3 text-lg`}
|
||||
>
|
||||
{ true ? "Running" : "PULSE OVERCURRENT"}
|
||||
</Badge>
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
<TimeSeriesValueNumeric
|
||||
label="Frequency"
|
||||
unit="Hz"
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
timeseries={ frequency }
|
||||
/>
|
||||
|
||||
<TimeSeriesValueNumeric
|
||||
label="Temperature"
|
||||
unit="C"
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
timeseries={ temperature }
|
||||
/>
|
||||
|
||||
<TimeSeriesValueNumeric
|
||||
label="Voltage"
|
||||
unit="V"
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
timeseries={ voltage }
|
||||
/>
|
||||
|
||||
<TimeSeriesValueNumeric
|
||||
label="Current"
|
||||
unit="A"
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
timeseries={ current }
|
||||
/>
|
||||
</ControlCard>
|
||||
</ControlGrid>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
import { Page } from "@/components/Page";
|
||||
import {
|
||||
AutoSyncedBigGraph,
|
||||
SyncedFloatingControlPanel,
|
||||
useGraphSync,
|
||||
type GraphConfig,
|
||||
} from "@/components/graph";
|
||||
|
||||
import React from "react";
|
||||
import { usePellet1 } from "./usePelletizer";
|
||||
|
||||
export function Pellet1GraphsPage() {
|
||||
const { diameter, x_diameter, y_diameter, roundness, state } = usePellet1();
|
||||
|
||||
const syncHook = useGraphSync("diameter-roundness-group");
|
||||
const targetDiameter = state?.laser_state?.target_diameter ?? 0;
|
||||
const lowerTolerance = state?.laser_state?.lower_tolerance ?? 0;
|
||||
const higherTolerance = state?.laser_state?.higher_tolerance ?? 0;
|
||||
|
||||
const isTwoAxis = !!x_diameter?.current || !!y_diameter?.current;
|
||||
|
||||
// Convert roundness from ratio (0-1) to percentage (0-100)
|
||||
const roundnessPercent = React.useMemo(() => {
|
||||
if (!roundness) return null;
|
||||
|
||||
const transformValue = (
|
||||
val: { value: number; timestamp: number } | null,
|
||||
) => {
|
||||
if (!val) return null;
|
||||
return { value: val.value * 100, timestamp: val.timestamp };
|
||||
};
|
||||
|
||||
const transformSeries = (series: any) => {
|
||||
if (!series || !series.values) return series;
|
||||
return {
|
||||
...series,
|
||||
values: series.values.map(transformValue),
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
current: roundness.current
|
||||
? transformValue(roundness.current)
|
||||
: null,
|
||||
long: transformSeries(roundness.long),
|
||||
short: transformSeries(roundness.short),
|
||||
};
|
||||
}, [roundness]);
|
||||
|
||||
const diameterColor = "#3b82f6"; // blue
|
||||
const xDiameterColor = "#ef4444"; // red
|
||||
const yDiameterColor = "#22c55e"; // green
|
||||
const roundnessColor = "#eab308"; // yellow
|
||||
|
||||
// config for all graphs
|
||||
const baseGraphConfig: GraphConfig = {
|
||||
title: "",
|
||||
defaultTimeWindow: 30 * 60 * 1000, // 30 minutes Standard
|
||||
colors: {
|
||||
grid: "#e2e8f0",
|
||||
axis: "#64748b",
|
||||
background: "#ffffff",
|
||||
},
|
||||
};
|
||||
|
||||
// Diameter-Graph (Diameter, X-Diameter, Y-Diameter)
|
||||
const diameterGraphData = [
|
||||
{
|
||||
newData: diameter,
|
||||
color: diameterColor,
|
||||
title: "Diameter",
|
||||
lines: [
|
||||
// Tolerance for diameter
|
||||
{
|
||||
type: "threshold" as const,
|
||||
value: targetDiameter + higherTolerance,
|
||||
label: "Upper Tolerance",
|
||||
color: diameterColor,
|
||||
dash: [5, 5],
|
||||
},
|
||||
{
|
||||
type: "threshold" as const,
|
||||
value: targetDiameter - lowerTolerance,
|
||||
label: "Lower Tolerance",
|
||||
color: diameterColor,
|
||||
dash: [5, 5],
|
||||
},
|
||||
{
|
||||
type: "target" as const,
|
||||
value: targetDiameter,
|
||||
label: "Target",
|
||||
color: diameterColor,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
...(isTwoAxis && x_diameter
|
||||
? [
|
||||
{
|
||||
newData: x_diameter,
|
||||
color: xDiameterColor,
|
||||
title: "X-Diameter",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(isTwoAxis && y_diameter
|
||||
? [
|
||||
{
|
||||
newData: y_diameter,
|
||||
color: yDiameterColor,
|
||||
title: "Y-Diameter",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
// Roundness-Graph
|
||||
const roundnessGraphData = {
|
||||
newData: roundnessPercent,
|
||||
color: roundnessColor,
|
||||
title: "Roundness (%)",
|
||||
};
|
||||
|
||||
return (
|
||||
<Page className="pb-27">
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* Graph 1: Diameter */}
|
||||
<AutoSyncedBigGraph
|
||||
syncHook={syncHook}
|
||||
newData={diameterGraphData}
|
||||
unit="mm"
|
||||
renderValue={(value) => value.toFixed(3)}
|
||||
config={{
|
||||
...baseGraphConfig,
|
||||
title: "Diameter (mm)",
|
||||
exportFilename: "diameter_data",
|
||||
colors: {
|
||||
...baseGraphConfig.colors,
|
||||
primary: diameterColor,
|
||||
},
|
||||
}}
|
||||
graphId="diameter-graph"
|
||||
/>
|
||||
|
||||
{/* Graph 2: Roundness */}
|
||||
{isTwoAxis && roundnessPercent && (
|
||||
<AutoSyncedBigGraph
|
||||
syncHook={syncHook}
|
||||
newData={roundnessGraphData}
|
||||
unit="%"
|
||||
renderValue={(value) => value.toFixed(2)}
|
||||
config={{
|
||||
...baseGraphConfig,
|
||||
title: "Roundness (%)",
|
||||
exportFilename: "roundness_data",
|
||||
colors: {
|
||||
...baseGraphConfig.colors,
|
||||
primary: roundnessColor,
|
||||
},
|
||||
}}
|
||||
graphId="roundness-graph"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<SyncedFloatingControlPanel controlProps={syncHook.controlProps} />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Page } from "@/components/Page";
|
||||
import { MarkdownWithToc } from "@/components/MarkdownWithToc";
|
||||
import React from "react";
|
||||
import laserManualContent from "@/assets/markdown/laser/manual.md?raw";
|
||||
|
||||
export function Pellet1ManualPage() {
|
||||
return (
|
||||
<Page>
|
||||
<MarkdownWithToc markdownContent={laserManualContent} />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Topbar } from "@/components/Topbar";
|
||||
import { pelletizer1SerialRoute } from "@/routes/routes";
|
||||
import React from "react";
|
||||
|
||||
export function Pellet1Page() {
|
||||
const { serial } = pelletizer1SerialRoute.useParams();
|
||||
return (
|
||||
<Topbar
|
||||
pathname={`/_sidebar/machines/pelletizer1/${serial}`}
|
||||
items={[
|
||||
{
|
||||
link: "control",
|
||||
activeLink: "control",
|
||||
title: "Control",
|
||||
icon: "lu:CirclePlay",
|
||||
},
|
||||
{
|
||||
link: "config",
|
||||
activeLink: "config",
|
||||
title: "Config",
|
||||
icon: "lu:ChartSpline",
|
||||
},
|
||||
// {
|
||||
// link: "graphs",
|
||||
// activeLink: "graphs",
|
||||
// title: "Graphs",
|
||||
// icon: "lu:ChartSpline",
|
||||
// },
|
||||
// {
|
||||
// link: "settings",
|
||||
// activeLink: "settings",
|
||||
// title: "Config",
|
||||
// icon: "lu:ChartSpline",
|
||||
// },
|
||||
// {
|
||||
// link: "manual",
|
||||
// activeLink: "manual",
|
||||
// title: "Manual",
|
||||
// icon: "lu:BookOpen",
|
||||
// },
|
||||
// {
|
||||
// link: "presets",
|
||||
// activeLink: "presets",
|
||||
// title: "Presets",
|
||||
// icon: "lu:Save",
|
||||
// },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import React from "react";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Preset } from "@/lib/preset/preset";
|
||||
import { laser1 } from "@/machines/properties";
|
||||
import { PresetPreviewEntries } from "@/components/preset/PresetPreviewTable";
|
||||
import { PresetsPage } from "@/components/preset/PresetsPage";
|
||||
|
||||
import { usePellet1 } from "./usePelletizer";
|
||||
|
||||
const laser1PresetDataSchema = z
|
||||
.object({
|
||||
targetDiameter: z.number(),
|
||||
lowerTolerance: z.number(),
|
||||
higherTolerance: z.number(),
|
||||
})
|
||||
.partial();
|
||||
|
||||
type Laser1 = z.infer<typeof laser1PresetDataSchema>;
|
||||
|
||||
const schemas = new Map([[1, laser1PresetDataSchema]]);
|
||||
|
||||
const previewEntries: PresetPreviewEntries<Laser1> = [
|
||||
{
|
||||
name: "Target Diameter",
|
||||
unit: "mm",
|
||||
renderValue: (data: Laser1) => data?.targetDiameter?.toFixed(3),
|
||||
},
|
||||
{
|
||||
name: "Lower Tolerance",
|
||||
unit: "mm",
|
||||
renderValue: (data: Laser1) => data?.lowerTolerance?.toFixed(3),
|
||||
},
|
||||
{
|
||||
name: "Higher Tolerance",
|
||||
unit: "mm",
|
||||
renderValue: (data: Laser1) => data?.higherTolerance?.toFixed(3),
|
||||
},
|
||||
];
|
||||
|
||||
export function Pellet1PresetsPage() {
|
||||
const {
|
||||
setTargetDiameter,
|
||||
setLowerTolerance,
|
||||
setHigherTolerance,
|
||||
defaultState,
|
||||
state,
|
||||
} = usePellet1();
|
||||
|
||||
const applyPreset = (preset: Preset<Laser1>) => {
|
||||
const targetDiameter = preset?.data?.targetDiameter ?? 1.75;
|
||||
const lowerTolerance = preset?.data?.lowerTolerance ?? 0.05;
|
||||
const higherTolerance = preset?.data?.higherTolerance ?? 0.05;
|
||||
|
||||
setTargetDiameter(targetDiameter);
|
||||
setLowerTolerance(lowerTolerance);
|
||||
setHigherTolerance(higherTolerance);
|
||||
};
|
||||
|
||||
const toPresetData = (s: typeof state): Laser1 => ({
|
||||
targetDiameter: s?.laser_state.target_diameter,
|
||||
lowerTolerance: s?.laser_state.lower_tolerance,
|
||||
higherTolerance: s?.laser_state.higher_tolerance,
|
||||
});
|
||||
|
||||
return (
|
||||
<PresetsPage
|
||||
machine_identification={laser1.machine_identification}
|
||||
currentState={toPresetData(state)}
|
||||
schemas={schemas}
|
||||
schemaVersion={1}
|
||||
applyPreset={applyPreset}
|
||||
previewEntries={previewEntries}
|
||||
defaultState={toPresetData(defaultState)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* @file pellet1Namespace.ts
|
||||
* @description TypeScript implementation of Pellet1 namespace with Zod schema validation.
|
||||
*/
|
||||
|
||||
import { StoreApi } from "zustand";
|
||||
import { create } from "zustand";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
EventHandler,
|
||||
eventSchema,
|
||||
Event,
|
||||
handleUnhandledEventError,
|
||||
NamespaceId,
|
||||
createNamespaceHookImplementation,
|
||||
ThrottledStoreUpdater,
|
||||
} from "../../../client/socketioStore";
|
||||
import { MachineIdentificationUnique } from "@/machines/types";
|
||||
import {
|
||||
createTimeSeries,
|
||||
TimeSeries,
|
||||
TimeSeriesValue,
|
||||
} from "@/lib/timeseries";
|
||||
|
||||
// ========== Event Schema Definitions ==========
|
||||
/**
|
||||
* Live values from Pellet (30 FPS)
|
||||
*/
|
||||
export const liveValuesEventDataSchema = z.object({
|
||||
inverter_values: z.object({
|
||||
frequency: z.number(),
|
||||
temperature: z.number(),
|
||||
voltage: z.number(),
|
||||
current: z.number(),
|
||||
})
|
||||
});
|
||||
|
||||
/**
|
||||
* State event from Pellet (on state changes only)
|
||||
*/
|
||||
export const stateEventDataSchema = z.object({
|
||||
is_default_state: z.boolean(),
|
||||
|
||||
inverter_state: z.object({
|
||||
running: z.boolean(),
|
||||
direction: z.boolean(),
|
||||
frequency_target: z.number(),
|
||||
acceleration_level: z.number(),
|
||||
deceleration_level: z.number(),
|
||||
}),
|
||||
});
|
||||
|
||||
// ========== Event Schemas with Wrappers ==========
|
||||
export const liveValuesEventSchema = eventSchema(liveValuesEventDataSchema);
|
||||
export const stateEventSchema = eventSchema(stateEventDataSchema);
|
||||
|
||||
// ========== Type Inferences ==========
|
||||
export type LiveValuesEvent = z.infer<typeof liveValuesEventSchema>;
|
||||
export type StateEvent = z.infer<typeof stateEventSchema>;
|
||||
|
||||
export type Pellet1NamespaceStore = {
|
||||
|
||||
state: StateEvent | null;
|
||||
defaultState: StateEvent | null;
|
||||
|
||||
// Time series data for live values
|
||||
frequency: TimeSeries;
|
||||
temperature: TimeSeries;
|
||||
voltage: TimeSeries;
|
||||
current: TimeSeries;
|
||||
};
|
||||
|
||||
// Constants for time durations
|
||||
const HALF_SECOND = 500;
|
||||
|
||||
const { initialTimeSeries: frequency, insert: addFrequency } =
|
||||
createTimeSeries({ sampleIntervalLong: HALF_SECOND });
|
||||
|
||||
const { initialTimeSeries: temperature, insert: addTemperature } =
|
||||
createTimeSeries({ sampleIntervalLong: HALF_SECOND });
|
||||
|
||||
const { initialTimeSeries: voltage, insert: addVoltage } =
|
||||
createTimeSeries({ sampleIntervalLong: HALF_SECOND });
|
||||
|
||||
const { initialTimeSeries: current, insert: addCurrent } =
|
||||
createTimeSeries({ sampleIntervalLong: HALF_SECOND });
|
||||
|
||||
/**
|
||||
* Factory function to create a new Pellet1 namespace store
|
||||
* @returns A new Zustand store instance for Pellet1 namespace
|
||||
*/
|
||||
export const createPellet1NamespaceStore =
|
||||
(): StoreApi<Pellet1NamespaceStore> =>
|
||||
create<Pellet1NamespaceStore>(() => {
|
||||
return {
|
||||
state: null,
|
||||
defaultState: null,
|
||||
frequency,
|
||||
temperature,
|
||||
voltage,
|
||||
current,
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a message handler for Pellet1 namespace events with validation and appropriate caching strategies
|
||||
* @param store The store to update when messages are received
|
||||
* @param throttledUpdater Throttled updater for batching updates at 30 FPS
|
||||
* @returns A message handler function
|
||||
*/
|
||||
export function pellet1MessageHandler(
|
||||
store: StoreApi<Pellet1NamespaceStore>,
|
||||
throttledUpdater: ThrottledStoreUpdater<Pellet1NamespaceStore>,
|
||||
): EventHandler {
|
||||
return (event: Event<any>) =>
|
||||
{
|
||||
const eventName = event.name;
|
||||
|
||||
// Helper function to update store through buffer
|
||||
const updateStore = (updater: (state: Pellet1NamespaceStore) => Pellet1NamespaceStore) =>
|
||||
{
|
||||
throttledUpdater.updateWith(updater);
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// Apply appropriate caching strategy based on event type
|
||||
if (eventName === "StateEvent")
|
||||
{
|
||||
const stateEvent = stateEventSchema.parse(event);
|
||||
|
||||
console.log(stateEvent);
|
||||
|
||||
updateStore((state) => ({
|
||||
...state,
|
||||
state: stateEvent,
|
||||
// only set default state if is_default_state is true
|
||||
defaultState: stateEvent.data.is_default_state
|
||||
? stateEvent
|
||||
: state.defaultState,
|
||||
}));
|
||||
}
|
||||
// Live values events (keep for 1 hour)
|
||||
else if (eventName === "LiveValuesEvent")
|
||||
{
|
||||
const liveValuesEvent = liveValuesEventSchema.parse(event);
|
||||
|
||||
const frequencyValue: TimeSeriesValue = {
|
||||
value: liveValuesEvent.data.inverter_values.frequency,
|
||||
timestamp: event.ts,
|
||||
};
|
||||
|
||||
updateStore((state) => ({ ...state, frequency: addFrequency(state.frequency, frequencyValue) }));
|
||||
|
||||
const temperatureValue: TimeSeriesValue = {
|
||||
value: liveValuesEvent.data.inverter_values.temperature,
|
||||
timestamp: event.ts,
|
||||
};
|
||||
|
||||
updateStore((state) => ({
|
||||
...state,
|
||||
temperature: addTemperature(state.temperature, temperatureValue),
|
||||
}));
|
||||
|
||||
const voltageValue: TimeSeriesValue = {
|
||||
value: liveValuesEvent.data.inverter_values.voltage,
|
||||
timestamp: event.ts,
|
||||
};
|
||||
|
||||
updateStore((state) => ({
|
||||
...state,
|
||||
voltage: addVoltage(state.voltage, voltageValue),
|
||||
}));
|
||||
|
||||
const currentValue: TimeSeriesValue = {
|
||||
value: liveValuesEvent.data.inverter_values.current,
|
||||
timestamp: event.ts,
|
||||
};
|
||||
|
||||
updateStore((state) => ({
|
||||
...state,
|
||||
current: addCurrent(state.current, currentValue),
|
||||
}));
|
||||
|
||||
|
||||
} else {
|
||||
handleUnhandledEventError(eventName);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Unexpected error processing ${eventName} event:`,
|
||||
error,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Pellet1 namespace implementation
|
||||
*/
|
||||
const usePellet1NamespaceImplementation =
|
||||
createNamespaceHookImplementation<Pellet1NamespaceStore>({
|
||||
createStore: createPellet1NamespaceStore,
|
||||
createEventHandler: pellet1MessageHandler,
|
||||
});
|
||||
|
||||
export function usePellet1Namespace(
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
): Pellet1NamespaceStore {
|
||||
// Generate namespace ID from validated machine ID
|
||||
const namespaceId: NamespaceId = {
|
||||
type: "machine",
|
||||
machine_identification_unique,
|
||||
};
|
||||
|
||||
// Use the implementation with validated namespace ID
|
||||
return usePellet1NamespaceImplementation(namespaceId);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const liveValuesEventDataSchema = z.object({
|
||||
frequency: z.number(),
|
||||
temperature: z.number(),
|
||||
voltage: z.number(),
|
||||
current: z.number(),
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const stateEventDataSchema = z.object({
|
||||
is_default_state: z.boolean(),
|
||||
|
||||
inverter_state: z.object({
|
||||
running_state: z.number(),
|
||||
frequency_target: z.number(),
|
||||
acceleration_level: z.number(),
|
||||
deceleration_level: z.number(),
|
||||
|
||||
error_code: z.number().nullable(),
|
||||
system_status: z.number(),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,185 @@
|
||||
import { toastError } from "@/components/Toast";
|
||||
import { useMachineMutate as useMachineMutation } from "@/client/useClient";
|
||||
import { MachineIdentificationUnique } from "@/machines/types";
|
||||
import { pelletizer } from "@/machines/properties";
|
||||
import { pelletizer1SerialRoute } from "@/routes/routes";
|
||||
import { z } from "zod";
|
||||
import { usePellet1Namespace, StateEvent } from "./namespace";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useStateOptimistic } from "@/lib/useStateOptimistic";
|
||||
import { produce } from "immer";
|
||||
|
||||
function usePellet(machine_identification_unique: MachineIdentificationUnique) {
|
||||
// Get consolidated state and live values from namespace
|
||||
const { state, defaultState, frequency, temperature, voltage, current } =
|
||||
usePellet1Namespace(machine_identification_unique);
|
||||
|
||||
// Single optimistic state for all state management
|
||||
const stateOptimistic = useStateOptimistic<StateEvent>();
|
||||
|
||||
useEffect(() => { if (state) { stateOptimistic.setReal(state); } }, [state]);
|
||||
|
||||
// Helper function for optimistic updates using produce
|
||||
const updateStateOptimistically = (
|
||||
producer: (current: StateEvent) => void,
|
||||
serverRequest: () => void,
|
||||
) => {
|
||||
const currentState = stateOptimistic.value;
|
||||
if (currentState && !stateOptimistic.isOptimistic) {
|
||||
stateOptimistic.setOptimistic(produce(currentState, producer));
|
||||
}
|
||||
serverRequest();
|
||||
};
|
||||
|
||||
// Mutation schemas
|
||||
const schemaRunning = z.object({ SetRunning: z.boolean() });
|
||||
const { request: requestRunning } = useMachineMutation(schemaRunning);
|
||||
|
||||
const schemaDirection = z.object({ SetDirection: z.boolean() });
|
||||
const { request: requestDirection } = useMachineMutation(schemaDirection);
|
||||
|
||||
const schemaFrequencyTarget = z.object({ SetFrequencyTarget: z.number() });
|
||||
const { request: requestFrequencyTarget } = useMachineMutation(schemaFrequencyTarget);
|
||||
|
||||
const schemaAccelerationLevel = z.object({ SetAccelerationLevel: z.number() });
|
||||
const { request: requestAccelerationLevel } = useMachineMutation(schemaAccelerationLevel);
|
||||
|
||||
const schemaDecelerationLevel = z.object({ SetDecelerationLevel: z.number() });
|
||||
const { request: requestDecelerationLevel } = useMachineMutation(schemaDecelerationLevel);
|
||||
|
||||
const SetRunning = (running: boolean) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.inverter_state.running = running;
|
||||
},
|
||||
() =>
|
||||
requestRunning({
|
||||
machine_identification_unique,
|
||||
data: {
|
||||
SetRunning: running,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const SetDirection = (forward: boolean) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.inverter_state.direction = forward;
|
||||
},
|
||||
() =>
|
||||
requestDirection({
|
||||
machine_identification_unique,
|
||||
data: {
|
||||
SetDirection: forward,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const SetFrequencyTarget = (frequency_target: number) =>
|
||||
{
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.inverter_state.frequency_target = frequency_target;
|
||||
},
|
||||
() =>
|
||||
requestFrequencyTarget({
|
||||
machine_identification_unique,
|
||||
data: {
|
||||
SetFrequencyTarget: frequency_target,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const SetAccelerationLevel = (acceleration_level: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.inverter_state.acceleration_level = acceleration_level;
|
||||
},
|
||||
() =>
|
||||
requestAccelerationLevel({
|
||||
machine_identification_unique,
|
||||
data: {
|
||||
SetAccelerationLevel: acceleration_level,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const SetDecelerationLevel = (deceleration_level: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.inverter_state.deceleration_level = deceleration_level;
|
||||
},
|
||||
() =>
|
||||
requestDecelerationLevel({
|
||||
machine_identification_unique,
|
||||
data: {
|
||||
SetDecelerationLevel: deceleration_level,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
// Consolidated state
|
||||
state: stateOptimistic.value?.data,
|
||||
|
||||
// Default state for initial values
|
||||
defaultState: defaultState?.data,
|
||||
|
||||
// Live values (TimeSeries)
|
||||
frequency,
|
||||
temperature,
|
||||
voltage,
|
||||
current,
|
||||
|
||||
// Loading states
|
||||
isLoading: stateOptimistic.isOptimistic,
|
||||
isDisabled: !stateOptimistic.isInitialized,
|
||||
|
||||
// Action functions
|
||||
SetRunning,
|
||||
SetDirection,
|
||||
SetFrequencyTarget,
|
||||
SetAccelerationLevel,
|
||||
SetDecelerationLevel,
|
||||
};
|
||||
}
|
||||
|
||||
export function usePellet1()
|
||||
{
|
||||
const { serial: serialString } = pelletizer1SerialRoute.useParams();
|
||||
|
||||
// Memoize the machine identification to keep it stable between renders
|
||||
const machineIdentification: MachineIdentificationUnique = useMemo(() =>
|
||||
{
|
||||
const serial = parseInt(serialString); // Use 0 as fallback if NaN
|
||||
|
||||
if (isNaN(serial)) {
|
||||
toastError(
|
||||
"Invalid Serial Number",
|
||||
`"${serialString}" is not a valid serial number.`,
|
||||
);
|
||||
|
||||
return {
|
||||
machine_identification: {
|
||||
vendor: 0,
|
||||
machine: 0,
|
||||
},
|
||||
serial: 0,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
machine_identification: pelletizer.machine_identification,
|
||||
serial,
|
||||
};
|
||||
}, [serialString]); // Only recreate when serialString changes
|
||||
|
||||
const pellet = usePellet(machineIdentification);
|
||||
|
||||
return { ...pellet };
|
||||
}
|
||||
@@ -302,6 +302,18 @@ export const laser1: MachineProperties = {
|
||||
device_roles: [],
|
||||
};
|
||||
|
||||
export const pelletizer: MachineProperties = {
|
||||
name: "Pelletizer",
|
||||
version: "V1",
|
||||
slug: "pelletizer1",
|
||||
icon: "lu:CircleDotDashed",
|
||||
machine_identification: {
|
||||
vendor: VENDOR_QITECH,
|
||||
machine: 0x000b,
|
||||
},
|
||||
device_roles: [],
|
||||
};
|
||||
|
||||
export const mock1: MachineProperties = {
|
||||
name: "Mock",
|
||||
version: "V1",
|
||||
@@ -462,6 +474,17 @@ export const testmachine: MachineProperties = {
|
||||
},
|
||||
],
|
||||
};
|
||||
export const digitalInputTestMachine: MachineProperties = {
|
||||
name: "Digital Input Machine",
|
||||
version: "V1",
|
||||
slug: "digitalInputTestMachine",
|
||||
icon: "lu:Disc3",
|
||||
machine_identification: {
|
||||
vendor: VENDOR_QITECH,
|
||||
machine: 0x0040,
|
||||
},
|
||||
device_roles: [],
|
||||
};
|
||||
export const wagoPower1: MachineProperties = {
|
||||
name: "WAGO Power",
|
||||
version: "V1",
|
||||
@@ -533,17 +556,44 @@ export const ip20TestMachine: MachineProperties = {
|
||||
],
|
||||
};
|
||||
|
||||
export const wagoAiTestMachine: MachineProperties = {
|
||||
name: "Wago AI Test",
|
||||
version: "V1",
|
||||
slug: "wagoaitestmachine",
|
||||
icon: "lu:Activity",
|
||||
machine_identification: {
|
||||
vendor: VENDOR_QITECH,
|
||||
machine: 0x0036,
|
||||
},
|
||||
device_roles: [
|
||||
{
|
||||
role: 0,
|
||||
role_label: "Wago 750-354 Bus Coupler",
|
||||
allowed_devices: [
|
||||
{
|
||||
vendor_id: 0x21,
|
||||
product_id: 0x07500354,
|
||||
revision: 0x2,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const machineProperties: MachineProperties[] = [
|
||||
winder2,
|
||||
extruder3,
|
||||
extruder2,
|
||||
laser1,
|
||||
pelletizer,
|
||||
mock1,
|
||||
buffer1,
|
||||
wagoPower1,
|
||||
aquapath1,
|
||||
testmachine,
|
||||
analogInputTestMachine,
|
||||
wagoAiTestMachine,
|
||||
digitalInputTestMachine,
|
||||
ip20TestMachine,
|
||||
];
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// each role can only be given once
|
||||
|
||||
import { IconName } from "@/components/Icon";
|
||||
import { rustEnumSchema } from "@/lib/types";
|
||||
import { rustEnum } from "@/lib/types";
|
||||
import { z } from "zod";
|
||||
|
||||
// EK1100 should have role 0
|
||||
@@ -62,9 +62,11 @@ export type DeviceHardwareIdentificationEthercat = z.infer<
|
||||
typeof deviceHardwareIdentificationEthercatSchema
|
||||
>;
|
||||
|
||||
export const deviceHardwareIdentificationSchema = rustEnumSchema({
|
||||
Ethercat: deviceHardwareIdentificationEthercatSchema,
|
||||
});
|
||||
export const deviceHardwareIdentificationSchema = z
|
||||
.object({
|
||||
Ethercat: deviceHardwareIdentificationEthercatSchema,
|
||||
})
|
||||
.check(rustEnum);
|
||||
|
||||
export type DeviceHardwareIdentification = z.infer<
|
||||
typeof deviceHardwareIdentificationSchema
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import { MiniGraph } from "@/components/graph/MiniGraph";
|
||||
import { Page } from "@/components/Page";
|
||||
import { ControlCard } from "@/control/ControlCard";
|
||||
import { ControlGrid } from "@/control/ControlGrid";
|
||||
|
||||
import React from "react";
|
||||
import { useWagoAiTestMachine } from "./useWagoAiTestMachine";
|
||||
|
||||
export function WagoAiTestMachineControl(): React.JSX.Element {
|
||||
const { seriesData, state, updateMeasurementRate } = useWagoAiTestMachine();
|
||||
|
||||
if (!seriesData) return <div>Initializing Sensor...</div>;
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<ControlGrid columns={2}>
|
||||
<ControlCard title="Measurement Control">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>Measurement Rate (Hz)</div>
|
||||
<input
|
||||
type="number"
|
||||
style={{
|
||||
border: "2px solid transparent",
|
||||
borderRadius: "4px",
|
||||
padding: "3px",
|
||||
}}
|
||||
className={"bg-neutral-100 shadow"}
|
||||
value={state.measurementRateHz}
|
||||
onChange={(v) => {
|
||||
const val = Number(v.currentTarget.value);
|
||||
updateMeasurementRate(val);
|
||||
}}
|
||||
min={1}
|
||||
></input>
|
||||
</div>
|
||||
</ControlCard>
|
||||
<ControlCard title="Analog Input 1 (4-20mA)">
|
||||
<div className="flex flex-row">
|
||||
<MiniGraph
|
||||
newData={seriesData.ai1}
|
||||
width={400}
|
||||
renderValue={(v) => v.toFixed(2)}
|
||||
></MiniGraph>
|
||||
<div className="ml-4 flex flex-col justify-center">
|
||||
<div>{seriesData.ai1.current?.value.toFixed(2)} mA</div>
|
||||
{state.wiringErrors?.[0] && (
|
||||
<div className="mt-2 rounded bg-red-100 px-2 py-1 text-sm font-semibold text-red-800">
|
||||
Wiring Error
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ControlCard>
|
||||
<ControlCard title="Analog Input 2 (4-20mA)">
|
||||
<div className="flex flex-row">
|
||||
<MiniGraph
|
||||
newData={seriesData.ai2}
|
||||
width={400}
|
||||
renderValue={(v) => v.toFixed(2)}
|
||||
></MiniGraph>
|
||||
<div className="ml-4 flex flex-col justify-center">
|
||||
<div>{seriesData.ai2.current?.value.toFixed(2)} mA</div>
|
||||
{state.wiringErrors?.[1] && (
|
||||
<div className="mt-2 rounded bg-red-100 px-2 py-1 text-sm font-semibold text-red-800">
|
||||
Wiring Error
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ControlCard>
|
||||
<ControlCard title="Analog Input 3 (4-20mA)">
|
||||
<div className="flex flex-row">
|
||||
<MiniGraph
|
||||
newData={seriesData.ai3}
|
||||
width={400}
|
||||
renderValue={(v) => v.toFixed(2)}
|
||||
></MiniGraph>
|
||||
<div className="ml-4 flex flex-col justify-center">
|
||||
<div>{seriesData.ai3.current?.value.toFixed(2)} mA</div>
|
||||
{state.wiringErrors?.[2] && (
|
||||
<div className="mt-2 rounded bg-red-100 px-2 py-1 text-sm font-semibold text-red-800">
|
||||
Wiring Error
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ControlCard>
|
||||
<ControlCard title="Analog Input 4 (4-20mA)">
|
||||
<div className="flex flex-row">
|
||||
<MiniGraph
|
||||
newData={seriesData.ai4}
|
||||
width={400}
|
||||
renderValue={(v) => v.toFixed(2)}
|
||||
></MiniGraph>
|
||||
<div className="ml-4 flex flex-col justify-center">
|
||||
<div>{seriesData.ai4.current?.value.toFixed(2)} mA</div>
|
||||
{state.wiringErrors?.[3] && (
|
||||
<div className="mt-2 rounded bg-red-100 px-2 py-1 text-sm font-semibold text-red-800">
|
||||
Wiring Error
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ControlCard>
|
||||
</ControlGrid>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Topbar } from "@/components/Topbar";
|
||||
import { wagoAiTestMachineSerialRoute } from "@/routes/routes";
|
||||
import React from "react";
|
||||
|
||||
export function WagoAiTestMachine(): React.JSX.Element {
|
||||
const { serial } = wagoAiTestMachineSerialRoute.useParams();
|
||||
|
||||
return (
|
||||
<Topbar
|
||||
pathname={`/_sidebar/machines/wagoaitestmachine/${serial}`}
|
||||
items={[
|
||||
{
|
||||
link: "control",
|
||||
activeLink: "control",
|
||||
title: "Control",
|
||||
icon: "lu:CirclePlay",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import { useMachineMutate } from "@/client/useClient";
|
||||
import { toastError } from "@/components/Toast";
|
||||
import { useStateOptimistic } from "@/lib/useStateOptimistic";
|
||||
import {
|
||||
createTimeSeries,
|
||||
TimeSeries,
|
||||
TimeSeriesValue,
|
||||
TimeSeriesWithInsert,
|
||||
} from "@/lib/timeseries";
|
||||
import { MachineIdentificationUnique } from "@/machines/types";
|
||||
import { wagoAiTestMachineSerialRoute } from "@/routes/routes";
|
||||
import { produce } from "immer";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import z from "zod";
|
||||
import { wagoAiTestMachine } from "../properties";
|
||||
import {
|
||||
StateEvent,
|
||||
useWagoAiTestMachineNamespace,
|
||||
} from "./useWagoAiTestMachineNamespace";
|
||||
|
||||
export type AnalogInputTimeSeries = {
|
||||
ai1: TimeSeries;
|
||||
ai2: TimeSeries;
|
||||
ai3: TimeSeries;
|
||||
ai4: TimeSeries;
|
||||
};
|
||||
|
||||
export function useWagoAiTestMachine() {
|
||||
const { serial: serialString } = wagoAiTestMachineSerialRoute.useParams();
|
||||
const machineIdentification: MachineIdentificationUnique = useMemo(() => {
|
||||
const serial = parseInt(serialString);
|
||||
if (isNaN(serial)) {
|
||||
toastError(
|
||||
"Invalid Serial Number",
|
||||
`"${serialString}" is not a valid serial number.`,
|
||||
);
|
||||
return {
|
||||
machine_identification: { vendor: 0, machine: 0 },
|
||||
serial: 0,
|
||||
};
|
||||
}
|
||||
return {
|
||||
machine_identification: wagoAiTestMachine.machine_identification,
|
||||
serial,
|
||||
};
|
||||
}, [serialString]);
|
||||
|
||||
const state = useWagoAiTestMachineNamespace({
|
||||
type: "machine",
|
||||
machine_identification_unique: machineIdentification,
|
||||
});
|
||||
|
||||
const mutateMachine = useMachineMutate(
|
||||
z.object({
|
||||
measurement_rate_hz: z.number(),
|
||||
}),
|
||||
);
|
||||
|
||||
const stateOptimistic = useStateOptimistic<StateEvent>();
|
||||
|
||||
const updateStateOptimistically = (
|
||||
producer: (current: StateEvent) => void,
|
||||
serverRequest?: () => void,
|
||||
) => {
|
||||
const currentState = stateOptimistic.value;
|
||||
if (currentState)
|
||||
stateOptimistic.setOptimistic(produce(currentState, producer));
|
||||
serverRequest?.();
|
||||
};
|
||||
|
||||
const updateMeasurementRate = (val: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.measurementRateHz = val;
|
||||
},
|
||||
() => {
|
||||
mutateMachine.request({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { measurement_rate_hz: val },
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// Create time series for each analog input
|
||||
const timeSeriesInstances: {
|
||||
ai1: TimeSeriesWithInsert;
|
||||
ai2: TimeSeriesWithInsert;
|
||||
ai3: TimeSeriesWithInsert;
|
||||
ai4: TimeSeriesWithInsert;
|
||||
} = {
|
||||
ai1: createTimeSeries({
|
||||
sampleIntervalShort: 20,
|
||||
sampleIntervalLong: 1000,
|
||||
retentionDurationShort: 5000,
|
||||
retentionDurationLong: 60 * 60 * 1000,
|
||||
}),
|
||||
ai2: createTimeSeries({
|
||||
sampleIntervalShort: 20,
|
||||
sampleIntervalLong: 1000,
|
||||
retentionDurationShort: 5000,
|
||||
retentionDurationLong: 60 * 60 * 1000,
|
||||
}),
|
||||
ai3: createTimeSeries({
|
||||
sampleIntervalShort: 20,
|
||||
sampleIntervalLong: 1000,
|
||||
retentionDurationShort: 5000,
|
||||
retentionDurationLong: 60 * 60 * 1000,
|
||||
}),
|
||||
ai4: createTimeSeries({
|
||||
sampleIntervalShort: 20,
|
||||
sampleIntervalLong: 1000,
|
||||
retentionDurationShort: 5000,
|
||||
retentionDurationLong: 60 * 60 * 1000,
|
||||
}),
|
||||
};
|
||||
|
||||
const [seriesData, setSeriesData] = useState<AnalogInputTimeSeries | null>({
|
||||
ai1: timeSeriesInstances.ai1.initialTimeSeries,
|
||||
ai2: timeSeriesInstances.ai2.initialTimeSeries,
|
||||
ai3: timeSeriesInstances.ai3.initialTimeSeries,
|
||||
ai4: timeSeriesInstances.ai4.initialTimeSeries,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const analogInputs = state.analogInputs;
|
||||
|
||||
if (analogInputs && seriesData) {
|
||||
const [ai1, ai2, ai3, ai4, timestampStr] = analogInputs;
|
||||
const timestamp = Number(timestampStr);
|
||||
|
||||
const dataPoint1: TimeSeriesValue = {
|
||||
value: ai1 * 1000, // Convert to mA
|
||||
timestamp,
|
||||
};
|
||||
const dataPoint2: TimeSeriesValue = {
|
||||
value: ai2 * 1000,
|
||||
timestamp,
|
||||
};
|
||||
const dataPoint3: TimeSeriesValue = {
|
||||
value: ai3 * 1000,
|
||||
timestamp,
|
||||
};
|
||||
const dataPoint4: TimeSeriesValue = {
|
||||
value: ai4 * 1000,
|
||||
timestamp,
|
||||
};
|
||||
|
||||
setSeriesData({
|
||||
ai1: timeSeriesInstances.ai1.insert(seriesData.ai1, dataPoint1),
|
||||
ai2: timeSeriesInstances.ai2.insert(seriesData.ai2, dataPoint2),
|
||||
ai3: timeSeriesInstances.ai3.insert(seriesData.ai3, dataPoint3),
|
||||
ai4: timeSeriesInstances.ai4.insert(seriesData.ai4, dataPoint4),
|
||||
});
|
||||
}
|
||||
}, [state]);
|
||||
|
||||
return {
|
||||
seriesData,
|
||||
state,
|
||||
updateMeasurementRate,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import {
|
||||
createNamespaceHookImplementation,
|
||||
EventHandler,
|
||||
eventSchema,
|
||||
} from "@/client/socketioStore";
|
||||
import z from "zod";
|
||||
import { create, StoreApi } from "zustand";
|
||||
|
||||
// ========== Event Schema ==========
|
||||
|
||||
export const stateEventDataSchema = z.object({
|
||||
measurementRateHz: z.number().optional(),
|
||||
analogInputs: z
|
||||
.tuple([z.number(), z.number(), z.number(), z.number(), z.string()])
|
||||
.optional(),
|
||||
wiringErrors: z
|
||||
.tuple([z.boolean(), z.boolean(), z.boolean(), z.boolean()])
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const stateEventSchema = eventSchema(stateEventDataSchema);
|
||||
|
||||
export type StateEvent = z.infer<typeof stateEventDataSchema>;
|
||||
|
||||
// ========== Store ==========
|
||||
export type WagoAiTestMachineNamespaceStore = z.infer<
|
||||
typeof stateEventDataSchema
|
||||
>;
|
||||
|
||||
const createMachineStore = (): StoreApi<WagoAiTestMachineNamespaceStore> =>
|
||||
create<WagoAiTestMachineNamespaceStore>(() => ({
|
||||
measurementRateHz: 1,
|
||||
analogInputs: undefined,
|
||||
wiringErrors: undefined,
|
||||
}));
|
||||
|
||||
// ========== Message Handler ==========
|
||||
function wagoAiTestMachineMessageHandler(
|
||||
store: StoreApi<WagoAiTestMachineNamespaceStore>,
|
||||
): EventHandler {
|
||||
return (event) => {
|
||||
const oldState = store.getState();
|
||||
|
||||
switch (event.name) {
|
||||
case "MeasurementRateHz":
|
||||
{
|
||||
const newMeasurementRateHz = event.data["MeasurementRateHz"];
|
||||
if (
|
||||
newMeasurementRateHz &&
|
||||
newMeasurementRateHz !== oldState.measurementRateHz
|
||||
)
|
||||
store.setState({
|
||||
...oldState,
|
||||
measurementRateHz: newMeasurementRateHz,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "AnalogInputs":
|
||||
{
|
||||
const newAnalogInputs = event.data["AnalogInputs"];
|
||||
if (newAnalogInputs && newAnalogInputs !== oldState.analogInputs)
|
||||
store.setState({
|
||||
...oldState,
|
||||
analogInputs: newAnalogInputs,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "WiringErrors":
|
||||
{
|
||||
const newWiringErrors = event.data["WiringErrors"];
|
||||
if (newWiringErrors && newWiringErrors !== oldState.wiringErrors)
|
||||
store.setState({
|
||||
...oldState,
|
||||
wiringErrors: newWiringErrors,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ========== Namespace Hook ==========
|
||||
export const useWagoAiTestMachineNamespace = createNamespaceHookImplementation({
|
||||
createStore: () => createMachineStore(),
|
||||
createEventHandler: wagoAiTestMachineMessageHandler,
|
||||
});
|
||||
@@ -3,7 +3,7 @@ import { useWinder2 } from "./useWinder";
|
||||
import { winder2 } from "@/machines/properties";
|
||||
|
||||
import { PresetsPage } from "@/components/preset/PresetsPage";
|
||||
import { Preset, PresetData } from "@/lib/preset/preset";
|
||||
import { Preset } from "@/lib/preset/preset";
|
||||
import {
|
||||
pullerStateSchema,
|
||||
spoolSpeedControllerStateSchema,
|
||||
@@ -16,19 +16,21 @@ import {
|
||||
|
||||
const winder2PresetDataSchema = z
|
||||
.object({
|
||||
traverse_state: z.object({
|
||||
limit_inner: z.number(),
|
||||
limit_outer: z.number(),
|
||||
step_size: z.number(),
|
||||
padding: z.number(),
|
||||
laserpointer: z.boolean(),
|
||||
}),
|
||||
puller_state: pullerStateSchema,
|
||||
spool_speed_controller_state: spoolSpeedControllerStateSchema,
|
||||
traverse_state: z
|
||||
.object({
|
||||
limit_inner: z.number(),
|
||||
limit_outer: z.number(),
|
||||
step_size: z.number(),
|
||||
padding: z.number(),
|
||||
laserpointer: z.boolean(),
|
||||
})
|
||||
.partial(),
|
||||
puller_state: pullerStateSchema.partial(),
|
||||
spool_speed_controller_state: spoolSpeedControllerStateSchema.partial(),
|
||||
})
|
||||
.deepPartial();
|
||||
.partial();
|
||||
|
||||
type Winder2 = typeof winder2PresetDataSchema;
|
||||
type Winder2 = z.infer<typeof winder2PresetDataSchema>;
|
||||
|
||||
const schemas = new Map([[1, winder2PresetDataSchema]]);
|
||||
|
||||
@@ -36,40 +38,39 @@ const previewEntries: PresetPreviewEntries<Winder2> = [
|
||||
{
|
||||
name: "Inner Traverse Limit",
|
||||
unit: "mm",
|
||||
renderValue: (data: PresetData<Winder2>) =>
|
||||
data?.traverse_state?.limit_inner?.toFixed(1) ?? "N/A",
|
||||
renderValue: (data: Winder2) =>
|
||||
data.traverse_state?.limit_inner?.toFixed(1) ?? "N/A",
|
||||
},
|
||||
{
|
||||
name: "Outer Traverse Limit",
|
||||
unit: "mm",
|
||||
renderValue: (data: PresetData<Winder2>) =>
|
||||
renderValue: (data: Winder2) =>
|
||||
data?.traverse_state?.limit_outer?.toFixed(1) ?? "N/A",
|
||||
},
|
||||
{
|
||||
name: "Traverse Step Size",
|
||||
unit: "mm",
|
||||
renderValue: (data: PresetData<Winder2>) =>
|
||||
renderValue: (data: Winder2) =>
|
||||
data?.traverse_state?.step_size?.toFixed(1) ?? "N/A",
|
||||
},
|
||||
{
|
||||
name: "Traverse Padding",
|
||||
unit: "mm",
|
||||
renderValue: (data: PresetData<Winder2>) =>
|
||||
data?.traverse_state?.padding?.toFixed(1),
|
||||
renderValue: (data: Winder2) => data?.traverse_state?.padding?.toFixed(1),
|
||||
},
|
||||
previewSeparator,
|
||||
{
|
||||
name: "Puller Regulation",
|
||||
renderValue: (data: PresetData<Winder2>) => data?.puller_state?.regulation,
|
||||
renderValue: (data: Winder2) => data?.puller_state?.regulation,
|
||||
},
|
||||
{
|
||||
name: "Puller Direction",
|
||||
renderValue: (data: PresetData<Winder2>) =>
|
||||
renderValue: (data: Winder2) =>
|
||||
data.puller_state?.forward ? "Forward" : "Backward",
|
||||
},
|
||||
{
|
||||
name: "Puller Gear Ratio",
|
||||
renderValue: (data: PresetData<Winder2>) => {
|
||||
renderValue: (data: Winder2) => {
|
||||
const ratio = data.puller_state?.gear_ratio;
|
||||
if (ratio === "OneToOne") return "1:1";
|
||||
if (ratio === "OneToFive") return "1:5";
|
||||
@@ -80,74 +81,73 @@ const previewEntries: PresetPreviewEntries<Winder2> = [
|
||||
{
|
||||
name: "Puller Target Speed",
|
||||
unit: "m/min",
|
||||
renderValue: (data: PresetData<Winder2>) =>
|
||||
data.puller_state?.target_speed?.toFixed(2),
|
||||
renderValue: (data: Winder2) => data.puller_state?.target_speed?.toFixed(2),
|
||||
},
|
||||
{
|
||||
name: "Puller Target Diameter",
|
||||
unit: "mm",
|
||||
renderValue: (data: PresetData<Winder2>) =>
|
||||
renderValue: (data: Winder2) =>
|
||||
data.puller_state?.target_diameter?.toFixed(1),
|
||||
},
|
||||
{
|
||||
name: "Puller Target Diameter",
|
||||
unit: "mm",
|
||||
renderValue: (data: PresetData<Winder2>) =>
|
||||
renderValue: (data: Winder2) =>
|
||||
data.puller_state?.target_diameter?.toFixed(1),
|
||||
},
|
||||
previewSeparator,
|
||||
{
|
||||
name: "Spool Regulation",
|
||||
renderValue: (data: PresetData<Winder2>) =>
|
||||
renderValue: (data: Winder2) =>
|
||||
data.spool_speed_controller_state?.regulation_mode,
|
||||
},
|
||||
{
|
||||
name: "Spool Direction",
|
||||
renderValue: (data: PresetData<Winder2>) =>
|
||||
renderValue: (data: Winder2) =>
|
||||
data.spool_speed_controller_state?.forward ? "Forward" : "Reverse",
|
||||
},
|
||||
{
|
||||
name: "Spool Min Speed",
|
||||
unit: "rpm",
|
||||
renderValue: (data: PresetData<Winder2>) =>
|
||||
renderValue: (data: Winder2) =>
|
||||
data.spool_speed_controller_state?.minmax_min_speed?.toFixed(2),
|
||||
},
|
||||
{
|
||||
name: "Spool Max Speed",
|
||||
unit: "rpm",
|
||||
renderValue: (data: PresetData<Winder2>) =>
|
||||
renderValue: (data: Winder2) =>
|
||||
data.spool_speed_controller_state?.minmax_max_speed?.toFixed(2),
|
||||
},
|
||||
previewSeparator,
|
||||
{
|
||||
name: "Adaptive Spool Tension Target",
|
||||
renderValue: (data: PresetData<Winder2>) =>
|
||||
renderValue: (data: Winder2) =>
|
||||
data.spool_speed_controller_state?.adaptive_tension_target?.toFixed(2),
|
||||
},
|
||||
{
|
||||
name: "Adaptive Spool Learning Rate",
|
||||
renderValue: (data: PresetData<Winder2>) =>
|
||||
renderValue: (data: Winder2) =>
|
||||
data.spool_speed_controller_state?.adaptive_radius_learning_rate?.toFixed(
|
||||
2,
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Adaptive Spool Max Speed Multiplier",
|
||||
renderValue: (data: PresetData<Winder2>) =>
|
||||
renderValue: (data: Winder2) =>
|
||||
data.spool_speed_controller_state?.adaptive_max_speed_multiplier?.toFixed(
|
||||
1,
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Adaptive Spool Acceleration Factor",
|
||||
renderValue: (data: PresetData<Winder2>) =>
|
||||
renderValue: (data: Winder2) =>
|
||||
data.spool_speed_controller_state?.adaptive_acceleration_factor?.toFixed(
|
||||
2,
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Adaptive Spool Deaccel. Urgency",
|
||||
renderValue: (data: PresetData<Winder2>) =>
|
||||
renderValue: (data: Winder2) =>
|
||||
data.spool_speed_controller_state?.adaptive_deacceleration_urgency_multiplier?.toFixed(
|
||||
1,
|
||||
),
|
||||
@@ -230,11 +230,11 @@ export function Winder2PresetsPage() {
|
||||
// );
|
||||
|
||||
enableTraverseLaserpointer(
|
||||
preset.data.traverse_state?.laserpointer ?? false,
|
||||
preset.data?.traverse_state?.laserpointer ?? false,
|
||||
);
|
||||
};
|
||||
|
||||
const toPresetData = (s: typeof state): PresetData<Winder2> => ({
|
||||
const toPresetData = (s: typeof state): Winder2 => ({
|
||||
traverse_state: {
|
||||
limit_inner: s?.traverse_state?.limit_inner,
|
||||
limit_outer: s?.traverse_state?.limit_outer,
|
||||
|
||||
@@ -4,7 +4,6 @@ import React from "react";
|
||||
import { z } from "zod";
|
||||
|
||||
import { ChooseVersionPage } from "@/setup/ChooseVersionPage";
|
||||
import { fallback, zodValidator } from "@tanstack/zod-adapter";
|
||||
import { githubSourceSchema } from "@/setup/GithubSourceDialog";
|
||||
import { SidebarLayout } from "@/components/SidebarLayout";
|
||||
import { SetupPage } from "@/setup/SetupPage";
|
||||
@@ -44,6 +43,12 @@ import { Laser1GraphsPage } from "@/machines/laser/laser1/Laser1Graph";
|
||||
import { Laser1Page } from "@/machines/laser/laser1/Laser1Page";
|
||||
import { Laser1PresetsPage } from "@/machines/laser/laser1/Laser1PresetsPage";
|
||||
|
||||
import { Pellet1Page } from "@/machines/pelletizer/pelletizer1/Page";
|
||||
import { Pellet1ControlPage } from "@/machines/pelletizer/pelletizer1/ControlPage";
|
||||
import { Pellet1GraphsPage } from "@/machines/pelletizer/pelletizer1/Graph";
|
||||
import { Pellet1ConfigPage } from "@/machines/pelletizer/pelletizer1/ConfigPage";
|
||||
import { Pellet1PresetsPage } from "@/machines/pelletizer/pelletizer1/PresetsPage";
|
||||
|
||||
import { Mock1ControlPage } from "@/machines/mock/mock1/Mock1ControlPage";
|
||||
import { Mock1GraphPage } from "@/machines/mock/mock1/Mock1Graph";
|
||||
import { Mock1ManualPage } from "@/machines/mock/mock1/Mock1Manual";
|
||||
@@ -53,12 +58,19 @@ import { Mock1PresetsPage } from "@/machines/mock/mock1/Mock1PresetsPage";
|
||||
import { Aquapath1ControlPage } from "@/machines/aquapath/aquapath1/Aquapath1ControlPage";
|
||||
import { Aquapath1Page } from "@/machines/aquapath/aquapath1/Aquapath1Page";
|
||||
import { Aquapath1GraphPage } from "@/machines/aquapath/aquapath1/Aquapath1Graph";
|
||||
import { Aquapath1SettingsPage } from "@/machines/aquapath/aquapath1/Aquapath1Settings";
|
||||
|
||||
import { TestMachinePage } from "@/machines/testmachine/TestMachinePage";
|
||||
import { TestMachineControlPage } from "@/machines/testmachine/TestMachineControlPage";
|
||||
|
||||
import { AnalogInputTestMachine } from "@/machines/analoginputtestmachine/AnalogInputTestMachinePage";
|
||||
import { AnalogInputTestMachineControl } from "@/machines/analoginputtestmachine/AnalogInputTestMachineControlPage";
|
||||
import { WagoAiTestMachine } from "@/machines/wagoaitestmachine/WagoAiTestMachinePage";
|
||||
import { WagoAiTestMachineControl } from "@/machines/wagoaitestmachine/WagoAiTestMachineControlPage";
|
||||
|
||||
import { DigitalInputTestMachinePage } from "@/machines/digitalinputtestmachine/DigitalInputTestMachinePage";
|
||||
import { DigitalInputTestMachineControlPage } from "@/machines/digitalinputtestmachine/DigitalInputTestMachineControlPage";
|
||||
|
||||
import { IP20TestMachinePage } from "@/machines/ip20testmachine/IP20TestMachinePage";
|
||||
import { IP20TestMachineControlPage } from "@/machines/ip20testmachine/IP20TestMachineControlPage";
|
||||
|
||||
@@ -100,6 +112,30 @@ export const analogInputTestMachineControlRoute = createRoute({
|
||||
component: () => <AnalogInputTestMachineControl />,
|
||||
});
|
||||
|
||||
export const wagoAiTestMachineSerialRoute = createRoute({
|
||||
getParentRoute: () => machinesRoute,
|
||||
path: "wagoaitestmachine/$serial",
|
||||
component: () => <WagoAiTestMachine />,
|
||||
});
|
||||
|
||||
export const wagoAiTestMachineControlRoute = createRoute({
|
||||
getParentRoute: () => wagoAiTestMachineSerialRoute,
|
||||
path: "control",
|
||||
component: () => <WagoAiTestMachineControl />,
|
||||
});
|
||||
|
||||
export const digitalInputTestMachineSerialRoute = createRoute({
|
||||
getParentRoute: () => machinesRoute,
|
||||
path: "digitalInputTestMachine/$serial",
|
||||
component: () => <DigitalInputTestMachinePage />,
|
||||
});
|
||||
|
||||
export const digitalInputTestMachineControlRoute = createRoute({
|
||||
getParentRoute: () => digitalInputTestMachineSerialRoute,
|
||||
path: "control",
|
||||
component: () => <DigitalInputTestMachineControlPage />,
|
||||
});
|
||||
|
||||
export const ip20TestMachineSerialRoute = createRoute({
|
||||
getParentRoute: () => machinesRoute,
|
||||
path: "ip20testmachine/$serial",
|
||||
@@ -255,6 +291,38 @@ export const laser1PresetsRoute = createRoute({
|
||||
component: () => <Laser1PresetsPage />,
|
||||
});
|
||||
|
||||
// --- pelletizer
|
||||
export const pelletizer1SerialRoute = createRoute({
|
||||
getParentRoute: () => machinesRoute,
|
||||
path: "pelletizer1/$serial",
|
||||
component: () => <Pellet1Page />,
|
||||
});
|
||||
|
||||
export const pelletizer1ControlRoute = createRoute({
|
||||
getParentRoute: () => pelletizer1SerialRoute,
|
||||
path: "control",
|
||||
component: () => <Pellet1ControlPage />,
|
||||
});
|
||||
|
||||
export const pelletizer1ConfigRoute = createRoute({
|
||||
getParentRoute: () => pelletizer1SerialRoute,
|
||||
path: "config",
|
||||
component: () => <Pellet1ConfigPage />,
|
||||
});
|
||||
|
||||
export const pelletizer1GraphsRoute = createRoute({
|
||||
getParentRoute: () => pelletizer1SerialRoute,
|
||||
path: "graphs",
|
||||
component: () => <Pellet1GraphsPage />,
|
||||
});
|
||||
|
||||
export const pelletizer1PresetsRoute = createRoute({
|
||||
getParentRoute: () => pelletizer1SerialRoute,
|
||||
path: "presets",
|
||||
component: () => <Pellet1PresetsPage />,
|
||||
});
|
||||
|
||||
// --- mock
|
||||
export const mock1SerialRoute = createRoute({
|
||||
getParentRoute: () => machinesRoute,
|
||||
path: "mock1/$serial",
|
||||
@@ -315,6 +383,12 @@ export const aquapath1ControlRoute = createRoute({
|
||||
component: () => <Aquapath1ControlPage />,
|
||||
});
|
||||
|
||||
export const aquapath1SettingsRoute = createRoute({
|
||||
getParentRoute: () => aquapath1SerialRoute,
|
||||
path: "settings",
|
||||
component: () => <Aquapath1SettingsPage />,
|
||||
});
|
||||
|
||||
export const buffer1SettingsRoute = createRoute({
|
||||
getParentRoute: () => buffer1SerialRoute,
|
||||
path: "settings",
|
||||
@@ -381,9 +455,9 @@ export const metricsRoute = createRoute({
|
||||
|
||||
export const versionSearchSchema = z
|
||||
.object({
|
||||
branch: fallback(z.string().optional(), undefined),
|
||||
commit: fallback(z.string().optional(), undefined),
|
||||
tag: fallback(z.string().optional(), undefined),
|
||||
branch: z.string().optional(),
|
||||
commit: z.string().optional(),
|
||||
tag: z.string().optional(),
|
||||
})
|
||||
.merge(githubSourceSchema)
|
||||
.refine(
|
||||
@@ -405,14 +479,14 @@ export const updateChangelogRoute = createRoute({
|
||||
getParentRoute: () => updateRoute,
|
||||
path: "changelog",
|
||||
component: () => <ChangelogPage />,
|
||||
validateSearch: zodValidator(versionSearchSchema),
|
||||
validateSearch: versionSearchSchema,
|
||||
});
|
||||
|
||||
export const updateExecuteRoute = createRoute({
|
||||
getParentRoute: () => updateRoute,
|
||||
path: "execute",
|
||||
component: () => <UpdateExecutePage />,
|
||||
validateSearch: zodValidator(versionSearchSchema),
|
||||
validateSearch: versionSearchSchema,
|
||||
});
|
||||
|
||||
export const rootTree = RootRoute.addChildren([
|
||||
@@ -434,17 +508,32 @@ export const rootTree = RootRoute.addChildren([
|
||||
laser1GraphsRoute,
|
||||
laser1PresetsRoute,
|
||||
]),
|
||||
|
||||
pelletizer1SerialRoute.addChildren([
|
||||
pelletizer1ControlRoute,
|
||||
pelletizer1GraphsRoute,
|
||||
pelletizer1ConfigRoute,
|
||||
pelletizer1PresetsRoute,
|
||||
]),
|
||||
|
||||
testMachineSerialRoute.addChildren([testMachineControlRoute]),
|
||||
|
||||
analogInputTestMachineSerialRoute.addChildren([
|
||||
analogInputTestMachineControlRoute,
|
||||
]),
|
||||
|
||||
wagoAiTestMachineSerialRoute.addChildren([wagoAiTestMachineControlRoute]),
|
||||
|
||||
digitalInputTestMachineSerialRoute.addChildren([
|
||||
digitalInputTestMachineControlRoute,
|
||||
]),
|
||||
|
||||
ip20TestMachineSerialRoute.addChildren([ip20TestMachineControlRoute]),
|
||||
|
||||
aquapath1SerialRoute.addChildren([
|
||||
aquapath1ControlRoute,
|
||||
aquapath1GraphRoute,
|
||||
aquapath1SettingsRoute,
|
||||
]),
|
||||
|
||||
winder2SerialRoute.addChildren([
|
||||
|
||||
@@ -15,7 +15,13 @@ import {
|
||||
machineProperties,
|
||||
VENDOR_QITECH,
|
||||
} from "@/machines/properties";
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { z } from "zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
@@ -37,7 +43,6 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useFormValues } from "@/lib/useFormValues";
|
||||
import { validateU16 } from "@/lib/validation";
|
||||
import { DeviceRoleComponent } from "@/components/DeviceRole";
|
||||
import { Alert } from "@/components/Alert";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
@@ -54,9 +59,15 @@ type Props = {
|
||||
};
|
||||
|
||||
const formSchema = z.object({
|
||||
machine: z.string().superRefine(validateU16),
|
||||
serial: z.string().superRefine(validateU16),
|
||||
role: z.string().superRefine(validateU16),
|
||||
machine: z
|
||||
.string()
|
||||
.refine((v) => parseInt(v) < 0xffff, { error: "Value too big" }),
|
||||
serial: z
|
||||
.string()
|
||||
.refine((v) => parseInt(v) < 0xffff, { error: "Value too big" }),
|
||||
role: z
|
||||
.string()
|
||||
.refine((v) => parseInt(v) < 0xffff, { error: "Value too big" }),
|
||||
});
|
||||
|
||||
type FormSchema = z.infer<typeof formSchema>;
|
||||
@@ -145,6 +156,15 @@ export function DeviceEeepromDialogContent({ device, setOpen }: ContentProps) {
|
||||
});
|
||||
};
|
||||
|
||||
const updateNumpadPosition = useCallback(() => {
|
||||
if (!numpadOpen || !dialogRef.current) return;
|
||||
const rect = dialogRef.current.getBoundingClientRect();
|
||||
setNumpadPosition({
|
||||
left: rect.right + 20,
|
||||
top: rect.top + rect.height / 2,
|
||||
});
|
||||
}, [numpadOpen]);
|
||||
|
||||
const machinePreset = useMemo(() => {
|
||||
if (!values.machine) return;
|
||||
return getMachineProperties({
|
||||
@@ -182,12 +202,7 @@ export function DeviceEeepromDialogContent({ device, setOpen }: ContentProps) {
|
||||
|
||||
// Position numpad once when it opens
|
||||
useEffect(() => {
|
||||
if (!numpadOpen || !dialogRef.current) return;
|
||||
const rect = dialogRef.current.getBoundingClientRect();
|
||||
setNumpadPosition({
|
||||
left: rect.right + 20,
|
||||
top: rect.top + rect.height / 2,
|
||||
});
|
||||
updateNumpadPosition();
|
||||
}, [numpadOpen]);
|
||||
|
||||
// Close numpad when clicking outside input/numpad
|
||||
@@ -221,6 +236,14 @@ export function DeviceEeepromDialogContent({ device, setOpen }: ContentProps) {
|
||||
}
|
||||
}, [numpadOpen]);
|
||||
|
||||
// Update numpad position whenever the window resizes
|
||||
useEffect(() => {
|
||||
window.addEventListener("resize", updateNumpadPosition);
|
||||
return () => {
|
||||
window.removeEventListener("resize", updateNumpadPosition);
|
||||
};
|
||||
}, [updateNumpadPosition]);
|
||||
|
||||
// Numpad handlers for serial input
|
||||
const numpadHandlers = useMemo(() => {
|
||||
const ensureFocus = () => {
|
||||
|
||||
@@ -24,7 +24,6 @@ import { Input } from "@/components/ui/input";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Icon } from "@/components/Icon";
|
||||
import { TouchButton } from "@/components/touch/TouchButton";
|
||||
import { fallback } from "@tanstack/zod-adapter";
|
||||
|
||||
type Props = {
|
||||
value: GithubSource;
|
||||
@@ -32,9 +31,9 @@ type Props = {
|
||||
};
|
||||
|
||||
export const githubSourceSchema = z.object({
|
||||
githubRepoOwner: fallback(z.string(), "qitechgmbh"),
|
||||
githubRepoName: fallback(z.string(), "control"),
|
||||
githubToken: fallback(z.string().optional().nullable(), undefined),
|
||||
githubRepoOwner: z.string().catch("qitechgmbh"),
|
||||
githubRepoName: z.string().catch("control"),
|
||||
githubToken: z.string().optional(),
|
||||
});
|
||||
|
||||
export type GithubSource = z.infer<typeof githubSourceSchema>;
|
||||
@@ -91,7 +90,7 @@ export function GithubSourceDialogContent({
|
||||
const onSubmit = (values: GithubSource) => {
|
||||
onChange({
|
||||
...values,
|
||||
githubToken: values.githubToken === "" ? null : values.githubToken,
|
||||
githubToken: values.githubToken === "" ? undefined : values.githubToken,
|
||||
});
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
@@ -328,6 +328,8 @@ impl TxPdoObject for Standard22ByteMdp600Input {
|
||||
self.status.overrun_error = bits[6];
|
||||
// Bit7 is reserved/unused
|
||||
self.length = bits[8..8 + 8].load_le::<u8>();
|
||||
// the serial bytes start at bit 16, then we calculate the offset of when the serial bytes buffer is over, which for Standard22ByteMdp600Input is 22bytes
|
||||
// to get the size in bits we do 22 * 8 -> 176bits
|
||||
let serial_bytes = bits[16..(16 + 22 * 8_usize)].chunks_exact(8);
|
||||
for (i, val) in serial_bytes.enumerate() {
|
||||
self.data[i] = val.load_le();
|
||||
|
||||
@@ -7,7 +7,10 @@ use crate::{
|
||||
devices::{
|
||||
DynamicEthercatDevice, Module,
|
||||
wago_modules::{
|
||||
wago_750_402::{WAGO_750_402_MODULE_IDENT, WAGO_750_402_PRODUCT_ID},
|
||||
wago_750_455::{WAGO_750_455_MODULE_IDENT, WAGO_750_455_PRODUCT_ID},
|
||||
wago_750_501::{WAGO_750_501_MODULE_IDENT, WAGO_750_501_PRODUCT_ID},
|
||||
wago_750_530::{WAGO_750_530_MODULE_IDENT, WAGO_750_530_PRODUCT_ID},
|
||||
wago_750_652::{WAGO_750_652_MODULE_IDENT, WAGO_750_652_PRODUCT_ID},
|
||||
wago_750_1506::{WAGO_750_1506_MODULE_IDENT, WAGO_750_1506_PRODUCT_ID},
|
||||
},
|
||||
@@ -227,14 +230,26 @@ impl Wago750_354 {
|
||||
module.has_tx = true;
|
||||
module.has_rx = true;
|
||||
}
|
||||
WAGO_750_455_PRODUCT_ID => {
|
||||
module.has_tx = true;
|
||||
module.has_rx = false;
|
||||
}
|
||||
WAGO_750_501_PRODUCT_ID => {
|
||||
module.has_tx = false;
|
||||
module.has_rx = true;
|
||||
}
|
||||
WAGO_750_530_PRODUCT_ID => {
|
||||
module.has_tx = false;
|
||||
module.has_rx = true;
|
||||
}
|
||||
WAGO_750_652_PRODUCT_ID => {
|
||||
module.has_tx = true;
|
||||
module.has_rx = true;
|
||||
}
|
||||
WAGO_750_402_PRODUCT_ID => {
|
||||
module.has_tx = true;
|
||||
module.has_rx = false;
|
||||
}
|
||||
_ => println!(
|
||||
"Wago-750-354 found Unknown/Unimplemented Module: {}",
|
||||
ident_iom
|
||||
@@ -259,6 +274,8 @@ impl Wago750_354 {
|
||||
let _ = self.get_pdo_offsets(device, false).await;
|
||||
});
|
||||
|
||||
println!("{:?}\n\n{:?}", self.tx_offsets, self.rx_offsets);
|
||||
|
||||
for module in self.slots {
|
||||
match module {
|
||||
Some(m) => {
|
||||
@@ -267,15 +284,24 @@ impl Wago750_354 {
|
||||
m.vendor_id,
|
||||
m.product_id,
|
||||
) {
|
||||
WAGO_750_455_MODULE_IDENT => {
|
||||
Arc::new(RwLock::new(wago_750_455::Wago750_455::new()))
|
||||
}
|
||||
WAGO_750_501_MODULE_IDENT => {
|
||||
Arc::new(RwLock::new(wago_750_501::Wago750_501::new()))
|
||||
}
|
||||
WAGO_750_530_MODULE_IDENT => {
|
||||
Arc::new(RwLock::new(wago_750_530::Wago750_530::new()))
|
||||
}
|
||||
WAGO_750_1506_MODULE_IDENT => {
|
||||
Arc::new(RwLock::new(wago_750_1506::Wago750_1506::new()))
|
||||
}
|
||||
WAGO_750_652_MODULE_IDENT => {
|
||||
Arc::new(RwLock::new(wago_750_652::Wago750_652::new()))
|
||||
}
|
||||
WAGO_750_402_MODULE_IDENT => {
|
||||
Arc::new(RwLock::new(wago_750_402::Wago750_402::new()))
|
||||
}
|
||||
_ => {
|
||||
println!(
|
||||
"{} Missing Implementation for Module Identification: vendor_id: {:?}, module ident: {:?} !",
|
||||
|
||||
@@ -430,6 +430,9 @@ impl IP20EcDi8Do8 {
|
||||
wago_750_652::WAGO_750_652_MODULE_IDENT => {
|
||||
Arc::new(RwLock::new(wago_750_652::Wago750_652::new()))
|
||||
}
|
||||
wago_750_402::WAGO_750_402_MODULE_IDENT => {
|
||||
Arc::new(RwLock::new(wago_750_402::Wago750_402::new()))
|
||||
}
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
pub mod ip20_ec_di8_do8;
|
||||
pub mod wago_750_1506;
|
||||
pub mod wago_750_402;
|
||||
pub mod wago_750_455;
|
||||
pub mod wago_750_501;
|
||||
pub mod wago_750_530;
|
||||
pub mod wago_750_652;
|
||||
@@ -0,0 +1,194 @@
|
||||
use crate::{
|
||||
devices::{
|
||||
DynamicEthercatDevice, EthercatDevice, EthercatDeviceProcessing, EthercatDeviceUsed,
|
||||
EthercatDynamicPDO, Module, NewEthercatDevice, SubDeviceProductTuple,
|
||||
},
|
||||
io::digital_input::{DigitalInputDevice, DigitalInputInput},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Wago750_402InputPort {
|
||||
DI1,
|
||||
DI2,
|
||||
DI3,
|
||||
DI4,
|
||||
}
|
||||
|
||||
impl From<Wago750_402InputPort> for usize {
|
||||
fn from(value: Wago750_402InputPort) -> Self {
|
||||
match value {
|
||||
Wago750_402InputPort::DI1 => 0,
|
||||
Wago750_402InputPort::DI2 => 1,
|
||||
Wago750_402InputPort::DI3 => 2,
|
||||
Wago750_402InputPort::DI4 => 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Wago750_402TxPdo {
|
||||
port1: bool,
|
||||
port2: bool,
|
||||
port3: bool,
|
||||
port4: bool,
|
||||
}
|
||||
|
||||
impl DigitalInputDevice<Wago750_402InputPort> for Wago750_402 {
|
||||
fn get_input(&self, port: Wago750_402InputPort) -> Result<DigitalInputInput, anyhow::Error> {
|
||||
Ok(DigitalInputInput {
|
||||
value: match port {
|
||||
Wago750_402InputPort::DI1 => self.tx_pdo.port1,
|
||||
Wago750_402InputPort::DI2 => self.tx_pdo.port2,
|
||||
Wago750_402InputPort::DI3 => self.tx_pdo.port3,
|
||||
Wago750_402InputPort::DI4 => self.tx_pdo.port4,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Wago750_402 {
|
||||
is_used: bool,
|
||||
tx_bit_offset: usize,
|
||||
// Should always be on but not when calling a constructor
|
||||
module: Option<Module>,
|
||||
tx_pdo: Wago750_402TxPdo,
|
||||
}
|
||||
|
||||
impl DynamicEthercatDevice for Wago750_402 {}
|
||||
|
||||
impl EthercatDynamicPDO for Wago750_402 {
|
||||
fn get_tx_offset(&self) -> usize {
|
||||
self.tx_bit_offset
|
||||
}
|
||||
|
||||
fn get_rx_offset(&self) -> usize {
|
||||
0 //this device has no rx
|
||||
}
|
||||
|
||||
fn set_tx_offset(&mut self, offset: usize) {
|
||||
self.tx_bit_offset = offset
|
||||
}
|
||||
|
||||
fn set_rx_offset(&mut self, _offset: usize) {
|
||||
// it does nothing because the device has no rx
|
||||
}
|
||||
}
|
||||
|
||||
impl EthercatDeviceUsed for Wago750_402 {
|
||||
fn is_used(&self) -> bool {
|
||||
self.is_used
|
||||
}
|
||||
|
||||
fn set_used(&mut self, used: bool) {
|
||||
self.is_used = used;
|
||||
}
|
||||
}
|
||||
|
||||
impl Wago750_402 {}
|
||||
|
||||
impl EthercatDevice for Wago750_402 {
|
||||
// input_len 48 offset: 40
|
||||
fn input(
|
||||
&mut self,
|
||||
input: &bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let base = self.tx_bit_offset;
|
||||
|
||||
let idx1 = base + Into::<usize>::into(Wago750_402InputPort::DI1);
|
||||
self.tx_pdo.port1 = *input.get(idx1).expect("Bit 1 out of bounds");
|
||||
|
||||
let idx2 = base + Into::<usize>::into(Wago750_402InputPort::DI2);
|
||||
self.tx_pdo.port2 = *input.get(idx2).expect("Bit 2 out of bounds");
|
||||
|
||||
let idx3 = base + Into::<usize>::into(Wago750_402InputPort::DI3);
|
||||
self.tx_pdo.port3 = *input.get(idx3).expect("Bit 3 out of bounds");
|
||||
|
||||
let idx4 = base + Into::<usize>::into(Wago750_402InputPort::DI4);
|
||||
self.tx_pdo.port4 = *input.get(idx4).expect("Bit 4 out of bounds");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn input_len(&self) -> usize {
|
||||
4
|
||||
}
|
||||
|
||||
fn output(
|
||||
&self,
|
||||
_output: &mut bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
// println!("output: {} {}",self.rx_bit_offset,_output);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn output_len(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn is_module(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn input_checked(
|
||||
&mut self,
|
||||
_input: &bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn output_checked(
|
||||
&self,
|
||||
_output: &mut bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_module(&self) -> Option<Module> {
|
||||
self.module.clone()
|
||||
}
|
||||
|
||||
fn set_module(&mut self, module: Module) {
|
||||
self.tx_bit_offset = module.tx_offset;
|
||||
self.module = Some(module);
|
||||
}
|
||||
}
|
||||
|
||||
impl EthercatDeviceProcessing for Wago750_402 {
|
||||
fn input_post_process(&mut self) -> Result<(), anyhow::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn output_pre_process(&mut self) -> Result<(), anyhow::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl NewEthercatDevice for Wago750_402 {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
is_used: false,
|
||||
tx_bit_offset: 0,
|
||||
module: None,
|
||||
tx_pdo: Wago750_402TxPdo::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Wago750_402 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Wago750_402")
|
||||
}
|
||||
}
|
||||
|
||||
pub const WAGO_750_402_VENDOR_ID: u32 = 0x00000021;
|
||||
pub const WAGO_750_402_PRODUCT_ID: u32 = 2147483713;
|
||||
pub const WAGO_750_402_MODULE_IDENT: SubDeviceProductTuple =
|
||||
(WAGO_750_402_VENDOR_ID, WAGO_750_402_PRODUCT_ID);
|
||||
@@ -0,0 +1,198 @@
|
||||
use bitvec::field::BitField;
|
||||
|
||||
use crate::devices::{
|
||||
DynamicEthercatDevice, EthercatDevice, EthercatDeviceProcessing, EthercatDeviceUsed,
|
||||
EthercatDynamicPDO, Module, NewEthercatDevice, SubDeviceProductTuple,
|
||||
};
|
||||
use crate::io::analog_input::physical::AnalogInputRange;
|
||||
use crate::io::analog_input::{AnalogInputDevice, AnalogInputInput};
|
||||
use units::electric_current::milliampere;
|
||||
use units::f64::ElectricCurrent;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Wago750_455Port {
|
||||
AI1,
|
||||
AI2,
|
||||
AI3,
|
||||
AI4,
|
||||
}
|
||||
|
||||
impl From<Wago750_455Port> for usize {
|
||||
fn from(value: Wago750_455Port) -> Self {
|
||||
match value {
|
||||
Wago750_455Port::AI1 => 0,
|
||||
Wago750_455Port::AI2 => 16,
|
||||
Wago750_455Port::AI3 => 32,
|
||||
Wago750_455Port::AI4 => 48,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Wago750_455TxPdo {
|
||||
pub ai1: u16,
|
||||
pub ai2: u16,
|
||||
pub ai3: u16,
|
||||
pub ai4: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Wago750_455 {
|
||||
is_used: bool,
|
||||
tx_bit_offset: usize,
|
||||
rx_bit_offset: usize,
|
||||
module: Option<Module>,
|
||||
tx_pdo: Wago750_455TxPdo,
|
||||
}
|
||||
|
||||
impl AnalogInputDevice<Wago750_455Port> for Wago750_455 {
|
||||
fn get_input(&self, port: Wago750_455Port) -> AnalogInputInput {
|
||||
let raw = match port {
|
||||
Wago750_455Port::AI1 => self.tx_pdo.ai1,
|
||||
Wago750_455Port::AI2 => self.tx_pdo.ai2,
|
||||
Wago750_455Port::AI3 => self.tx_pdo.ai3,
|
||||
Wago750_455Port::AI4 => self.tx_pdo.ai4,
|
||||
};
|
||||
let wiring_error = (raw & 0x0003) == 0x0003;
|
||||
let raw_value = (raw & 0x7FF0) as i16;
|
||||
let normalized = self.analog_input_range().raw_to_normalized(raw_value) as f32;
|
||||
AnalogInputInput {
|
||||
normalized,
|
||||
wiring_error,
|
||||
}
|
||||
}
|
||||
|
||||
fn analog_input_range(&self) -> AnalogInputRange {
|
||||
AnalogInputRange::Current {
|
||||
min: ElectricCurrent::new::<milliampere>(4.0),
|
||||
max: ElectricCurrent::new::<milliampere>(20.0),
|
||||
min_raw: 0,
|
||||
max_raw: 0x7FF0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EthercatDeviceUsed for Wago750_455 {
|
||||
fn is_used(&self) -> bool {
|
||||
self.is_used
|
||||
}
|
||||
|
||||
fn set_used(&mut self, used: bool) {
|
||||
self.is_used = used;
|
||||
}
|
||||
}
|
||||
|
||||
impl DynamicEthercatDevice for Wago750_455 {}
|
||||
|
||||
impl EthercatDynamicPDO for Wago750_455 {
|
||||
fn get_tx_offset(&self) -> usize {
|
||||
self.tx_bit_offset
|
||||
}
|
||||
|
||||
fn get_rx_offset(&self) -> usize {
|
||||
self.rx_bit_offset
|
||||
}
|
||||
|
||||
fn set_tx_offset(&mut self, offset: usize) {
|
||||
self.tx_bit_offset = offset
|
||||
}
|
||||
|
||||
fn set_rx_offset(&mut self, offset: usize) {
|
||||
self.rx_bit_offset = offset
|
||||
}
|
||||
}
|
||||
|
||||
impl EthercatDevice for Wago750_455 {
|
||||
fn input(
|
||||
&mut self,
|
||||
input: &bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let base = self.tx_bit_offset;
|
||||
let ai1 = input[base..(base + 16)].load_le::<u16>();
|
||||
let ai2 = input[(base + 16)..(base + 32)].load_le::<u16>();
|
||||
let ai3 = input[(base + 32)..(base + 48)].load_le::<u16>();
|
||||
let ai4 = input[(base + 48)..(base + 64)].load_le::<u16>();
|
||||
|
||||
self.tx_pdo.ai1 = ai1;
|
||||
self.tx_pdo.ai2 = ai2;
|
||||
self.tx_pdo.ai3 = ai3;
|
||||
self.tx_pdo.ai4 = ai4;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn input_len(&self) -> usize {
|
||||
64
|
||||
}
|
||||
|
||||
fn output(
|
||||
&self,
|
||||
_output: &mut bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn output_len(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn is_module(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn input_checked(
|
||||
&mut self,
|
||||
input: &bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
self.input(input)
|
||||
}
|
||||
|
||||
fn output_checked(
|
||||
&self,
|
||||
_output: &mut bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_module(&self) -> Option<Module> {
|
||||
self.module.clone()
|
||||
}
|
||||
|
||||
fn set_module(&mut self, module: Module) {
|
||||
self.tx_bit_offset = module.tx_offset;
|
||||
self.rx_bit_offset = module.rx_offset;
|
||||
self.module = Some(module)
|
||||
}
|
||||
}
|
||||
|
||||
impl EthercatDeviceProcessing for Wago750_455 {}
|
||||
|
||||
impl NewEthercatDevice for Wago750_455 {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
is_used: false,
|
||||
tx_bit_offset: 0,
|
||||
rx_bit_offset: 0,
|
||||
module: None,
|
||||
tx_pdo: Wago750_455TxPdo::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Wago750_455 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Wago750_455")
|
||||
}
|
||||
}
|
||||
|
||||
pub const WAGO_750_455_VENDOR_ID: u32 = 0x00000021;
|
||||
pub const WAGO_750_455_PRODUCT_ID: u32 = 0x045541b3;
|
||||
pub const WAGO_750_455_MODULE_IDENT: SubDeviceProductTuple =
|
||||
(WAGO_750_455_VENDOR_ID, WAGO_750_455_PRODUCT_ID);
|
||||
@@ -0,0 +1,260 @@
|
||||
use crate::devices::{
|
||||
DynamicEthercatDevice, EthercatDevice, EthercatDeviceUsed, EthercatDynamicPDO, Module,
|
||||
SubDeviceProductTuple,
|
||||
};
|
||||
use crate::devices::{EthercatDeviceProcessing, NewEthercatDevice};
|
||||
use crate::io::digital_output::{DigitalOutputDevice, DigitalOutputOutput};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Wago750_530 {
|
||||
is_used: bool,
|
||||
tx_bit_offset: usize,
|
||||
rx_bit_offset: usize,
|
||||
pub rxpdo: Wago750_530RxPdo,
|
||||
module: Option<Module>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Wago750_530Port {
|
||||
Port1,
|
||||
Port2,
|
||||
Port3,
|
||||
Port4,
|
||||
Port5,
|
||||
Port6,
|
||||
Port7,
|
||||
Port8,
|
||||
}
|
||||
|
||||
impl From<Wago750_530Port> for usize {
|
||||
fn from(value: Wago750_530Port) -> Self {
|
||||
match value {
|
||||
Wago750_530Port::Port1 => 0,
|
||||
Wago750_530Port::Port2 => 1,
|
||||
Wago750_530Port::Port3 => 2,
|
||||
Wago750_530Port::Port4 => 3,
|
||||
Wago750_530Port::Port5 => 4,
|
||||
Wago750_530Port::Port6 => 5,
|
||||
Wago750_530Port::Port7 => 6,
|
||||
Wago750_530Port::Port8 => 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Wago750_530RxPdo {
|
||||
pub port1: bool,
|
||||
pub port2: bool,
|
||||
pub port3: bool,
|
||||
pub port4: bool,
|
||||
pub port5: bool,
|
||||
pub port6: bool,
|
||||
pub port7: bool,
|
||||
pub port8: bool,
|
||||
}
|
||||
|
||||
impl DigitalOutputDevice<Wago750_530Port> for Wago750_530 {
|
||||
/// Writes the new output value into the device's RXPDO structure (in-memory PDI).
|
||||
fn set_output(&mut self, port: Wago750_530Port, value: DigitalOutputOutput) {
|
||||
// The DigitalOutputOutput is converted to a bool using the From trait
|
||||
let output_value: bool = value.into();
|
||||
match port {
|
||||
Wago750_530Port::Port1 => {
|
||||
// Modify the internal PDO representation
|
||||
self.rxpdo.port1 = output_value;
|
||||
}
|
||||
Wago750_530Port::Port2 => {
|
||||
// Modify the internal PDO representation
|
||||
self.rxpdo.port2 = output_value;
|
||||
}
|
||||
Wago750_530Port::Port3 => {
|
||||
// Modify the internal PDO representation
|
||||
self.rxpdo.port3 = output_value;
|
||||
}
|
||||
Wago750_530Port::Port4 => {
|
||||
// Modify the internal PDO representation
|
||||
self.rxpdo.port4 = output_value;
|
||||
}
|
||||
Wago750_530Port::Port5 => {
|
||||
// Modify the internal PDO representation
|
||||
self.rxpdo.port5 = output_value;
|
||||
}
|
||||
Wago750_530Port::Port6 => {
|
||||
// Modify the internal PDO representation
|
||||
self.rxpdo.port6 = output_value;
|
||||
}
|
||||
Wago750_530Port::Port7 => {
|
||||
// Modify the internal PDO representation
|
||||
self.rxpdo.port7 = output_value;
|
||||
}
|
||||
Wago750_530Port::Port8 => {
|
||||
// Modify the internal PDO representation
|
||||
self.rxpdo.port8 = output_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads the current output value from the device's RXPDO structure (in-memory PDI).
|
||||
fn get_output(&self, port: Wago750_530Port) -> DigitalOutputOutput {
|
||||
let current_value = match port {
|
||||
Wago750_530Port::Port1 => self.rxpdo.port1,
|
||||
Wago750_530Port::Port2 => self.rxpdo.port2,
|
||||
Wago750_530Port::Port3 => self.rxpdo.port3,
|
||||
Wago750_530Port::Port4 => self.rxpdo.port4,
|
||||
Wago750_530Port::Port5 => self.rxpdo.port5,
|
||||
Wago750_530Port::Port6 => self.rxpdo.port6,
|
||||
Wago750_530Port::Port7 => self.rxpdo.port7,
|
||||
Wago750_530Port::Port8 => self.rxpdo.port8,
|
||||
};
|
||||
|
||||
// Wrap the bool back into the type-safe wrapper
|
||||
DigitalOutputOutput(current_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl EthercatDeviceUsed for Wago750_530 {
|
||||
fn is_used(&self) -> bool {
|
||||
self.is_used
|
||||
}
|
||||
|
||||
fn set_used(&mut self, used: bool) {
|
||||
self.is_used = used;
|
||||
}
|
||||
}
|
||||
|
||||
impl DynamicEthercatDevice for Wago750_530 {}
|
||||
|
||||
impl EthercatDynamicPDO for Wago750_530 {
|
||||
fn get_tx_offset(&self) -> usize {
|
||||
self.tx_bit_offset
|
||||
}
|
||||
|
||||
fn get_rx_offset(&self) -> usize {
|
||||
self.rx_bit_offset
|
||||
}
|
||||
|
||||
fn set_tx_offset(&mut self, offset: usize) {
|
||||
self.tx_bit_offset = offset
|
||||
}
|
||||
|
||||
fn set_rx_offset(&mut self, offset: usize) {
|
||||
self.rx_bit_offset = offset
|
||||
}
|
||||
}
|
||||
|
||||
impl EthercatDevice for Wago750_530 {
|
||||
fn input(
|
||||
&mut self,
|
||||
_input: &bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn input_len(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn output(
|
||||
&self,
|
||||
output: &mut bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
output.set(
|
||||
self.rx_bit_offset + Into::<usize>::into(Wago750_530Port::Port1),
|
||||
self.rxpdo.port1,
|
||||
);
|
||||
output.set(
|
||||
self.rx_bit_offset + Into::<usize>::into(Wago750_530Port::Port2),
|
||||
self.rxpdo.port2,
|
||||
);
|
||||
output.set(
|
||||
self.rx_bit_offset + Into::<usize>::into(Wago750_530Port::Port3),
|
||||
self.rxpdo.port3,
|
||||
);
|
||||
output.set(
|
||||
self.rx_bit_offset + Into::<usize>::into(Wago750_530Port::Port4),
|
||||
self.rxpdo.port4,
|
||||
);
|
||||
output.set(
|
||||
self.rx_bit_offset + Into::<usize>::into(Wago750_530Port::Port5),
|
||||
self.rxpdo.port5,
|
||||
);
|
||||
output.set(
|
||||
self.rx_bit_offset + Into::<usize>::into(Wago750_530Port::Port6),
|
||||
self.rxpdo.port6,
|
||||
);
|
||||
output.set(
|
||||
self.rx_bit_offset + Into::<usize>::into(Wago750_530Port::Port7),
|
||||
self.rxpdo.port7,
|
||||
);
|
||||
output.set(
|
||||
self.rx_bit_offset + Into::<usize>::into(Wago750_530Port::Port8),
|
||||
self.rxpdo.port8,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn output_len(&self) -> usize {
|
||||
8
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn is_module(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn input_checked(
|
||||
&mut self,
|
||||
_input: &bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn output_checked(
|
||||
&self,
|
||||
_output: &mut bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_module(&self) -> Option<crate::devices::Module> {
|
||||
self.module.clone()
|
||||
}
|
||||
|
||||
fn set_module(&mut self, module: crate::devices::Module) {
|
||||
self.tx_bit_offset = module.tx_offset;
|
||||
self.rx_bit_offset = module.rx_offset;
|
||||
self.module = Some(module)
|
||||
}
|
||||
}
|
||||
|
||||
impl EthercatDeviceProcessing for Wago750_530 {}
|
||||
|
||||
impl NewEthercatDevice for Wago750_530 {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
is_used: false,
|
||||
tx_bit_offset: 0,
|
||||
rx_bit_offset: 0,
|
||||
module: None,
|
||||
rxpdo: Wago750_530RxPdo::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Wago750_530 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Wago750_530")
|
||||
}
|
||||
}
|
||||
|
||||
pub const WAGO_750_530_VENDOR_ID: u32 = 0x00000021;
|
||||
pub const WAGO_750_530_PRODUCT_ID: u32 = 2147483778;
|
||||
pub const WAGO_750_530_MODULE_IDENT: SubDeviceProductTuple =
|
||||
(WAGO_750_530_VENDOR_ID, WAGO_750_530_PRODUCT_ID);
|
||||
@@ -1,8 +1,12 @@
|
||||
use bitvec::field::BitField;
|
||||
|
||||
use crate::devices::{
|
||||
DynamicEthercatDevice, EthercatDevice, EthercatDeviceUsed, EthercatDynamicPDO, Module,
|
||||
SubDeviceProductTuple,
|
||||
};
|
||||
use crate::devices::{EthercatDeviceProcessing, NewEthercatDevice};
|
||||
use crate::io::serial_interface::SerialInterfaceDevice;
|
||||
const WAGO750_652_MAX_BUF_LENGTH: usize = 48;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Wago750_652 {
|
||||
@@ -10,6 +14,62 @@ pub struct Wago750_652 {
|
||||
tx_bit_offset: usize,
|
||||
rx_bit_offset: usize,
|
||||
module: Option<Module>,
|
||||
started_init: bool,
|
||||
pub rx_pdo: Wago750_652RxPdo,
|
||||
pub tx_pdo: Wago750_652TxPdo,
|
||||
has_messages_last_toggle: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct Wago750_652Control {
|
||||
pub transmit_request: bool, // Transmit request
|
||||
pub receive_ack: bool, // Receive acknowledge
|
||||
pub init_request: bool, // Initialization request
|
||||
pub send_continuous: bool, // Send continuous
|
||||
pub output_length: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Wago750_652RxPdo {
|
||||
pub control: Wago750_652Control,
|
||||
pub out_buffer: [u8; WAGO750_652_MAX_BUF_LENGTH],
|
||||
}
|
||||
|
||||
impl Default for Wago750_652RxPdo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
control: Default::default(),
|
||||
out_buffer: [0u8; WAGO750_652_MAX_BUF_LENGTH],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct Wago750_652Status {
|
||||
pub transmit_ack: bool, //
|
||||
pub receive_request: bool,
|
||||
pub init_ack: bool,
|
||||
pub buf_full: bool,
|
||||
pub buf_empty: bool, // Buffer is empty (Sending inactive)
|
||||
pub error_parity: bool,
|
||||
pub error_framing: bool, // Frame is corrupt?
|
||||
pub error_overrun: bool, // Byte/s was lost on receive
|
||||
pub input_length: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Wago750_652TxPdo {
|
||||
pub status: Wago750_652Status,
|
||||
pub in_buffer: [u8; WAGO750_652_MAX_BUF_LENGTH],
|
||||
}
|
||||
|
||||
impl Default for Wago750_652TxPdo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
status: Default::default(),
|
||||
in_buffer: [0u8; WAGO750_652_MAX_BUF_LENGTH],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EthercatDeviceUsed for Wago750_652 {
|
||||
@@ -23,7 +83,6 @@ impl EthercatDeviceUsed for Wago750_652 {
|
||||
}
|
||||
|
||||
impl DynamicEthercatDevice for Wago750_652 {}
|
||||
|
||||
impl EthercatDynamicPDO for Wago750_652 {
|
||||
fn get_tx_offset(&self) -> usize {
|
||||
self.tx_bit_offset
|
||||
@@ -48,24 +107,84 @@ impl EthercatDevice for Wago750_652 {
|
||||
*/
|
||||
fn input(
|
||||
&mut self,
|
||||
_input: &bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||
input: &bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
self.tx_pdo.in_buffer.fill(0u8);
|
||||
let base = self.tx_bit_offset;
|
||||
|
||||
self.tx_pdo.status.transmit_ack = *input.get(base + 0).expect("Missing buf_empty bit");
|
||||
self.tx_pdo.status.receive_request = *input.get(base + 1).expect("Missing buf_empty bit");
|
||||
self.tx_pdo.status.init_ack = *input.get(base + 2).expect("Missing buf_empty bit");
|
||||
self.tx_pdo.status.buf_full = *input.get(base + 6).expect("Missing buf_empty bit");
|
||||
self.tx_pdo.status.buf_empty = *input.get(base + 11).expect("Missing buf_empty bit");
|
||||
self.tx_pdo.status.error_parity = *input.get(base + 13).expect("Missing buf_empty bit");
|
||||
self.tx_pdo.status.error_framing = *input.get(base + 14).expect("Missing buf_empty bit");
|
||||
self.tx_pdo.status.error_overrun = *input.get(base + 15).expect("Missing buf_empty bit");
|
||||
// Im sorry but this is what you get by splitting the len across different bytes ...
|
||||
self.tx_pdo.status.input_length = ((*input.get(base + 3).expect("Missing buf_empty bit")
|
||||
as u8)
|
||||
^ ((*input.get(base + 4).expect("Missing buf_empty bit") as u8) << 1)
|
||||
^ ((*input.get(base + 5).expect("Missing buf_empty bit") as u8) << 2)
|
||||
^ ((*input.get(base + 8).expect("Missing buf_empty bit") as u8) << 3)
|
||||
^ ((*input.get(base + 9).expect("Missing buf_empty bit") as u8) << 4)
|
||||
^ ((*input.get(base + 10).expect("Missing buf_empty bit") as u8) << 5))
|
||||
as usize;
|
||||
|
||||
// the serial bytes start at bit 16,
|
||||
// then we calculate the offset of when the serial bytes buffer is over, which for the 24byte pdo is 22bytes
|
||||
// to get the size in bits we do 22 * 8 -> 176bits
|
||||
let serial_bytes = input[(base + 16)..((base + 16) + 22 * 8_usize)].chunks_exact(8);
|
||||
for (i, val) in serial_bytes.enumerate() {
|
||||
self.tx_pdo.in_buffer[i] = val.load_le();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn input_len(&self) -> usize {
|
||||
0
|
||||
24
|
||||
}
|
||||
|
||||
fn output(
|
||||
&self,
|
||||
_output: &mut bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||
output: &mut bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let base = 32;
|
||||
|
||||
let mut ol0_i = 5; // Ol0 starts at bit 5 and goes to bit 7 (inclusive)
|
||||
let mut ol1_i = 8;
|
||||
|
||||
output.set(base + 0, self.rx_pdo.control.transmit_request);
|
||||
output.set(base + 1, self.rx_pdo.control.receive_ack);
|
||||
output.set(base + 2, self.rx_pdo.control.init_request);
|
||||
output.set(base + 3, self.rx_pdo.control.send_continuous);
|
||||
|
||||
for i in 0..3 {
|
||||
// Rust type system proceeds to annoy once again, we need to add a != at the end, because a cast to bool is just too dangeerous obviously
|
||||
output.set(
|
||||
base + ol0_i,
|
||||
((self.rx_pdo.control.output_length >> i) & 1) != 0,
|
||||
);
|
||||
ol0_i += 1;
|
||||
}
|
||||
|
||||
for i in 3..6 {
|
||||
output.set(
|
||||
base + ol1_i,
|
||||
((self.rx_pdo.control.output_length >> i) & 1) != 0,
|
||||
);
|
||||
ol1_i += 1;
|
||||
}
|
||||
println!("{}", self.rx_pdo.control.output_length);
|
||||
let data_start = base + 16;
|
||||
for (i, &byte) in self.rx_pdo.out_buffer.iter().take(22).enumerate() {
|
||||
output[(data_start + i * 8)..(data_start + (i + 1) * 8)].store_le(byte);
|
||||
}
|
||||
//println!("{:?}",self.rx_pdo.out_buffer);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn output_len(&self) -> usize {
|
||||
0
|
||||
24
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
@@ -84,19 +203,6 @@ impl EthercatDevice for Wago750_652 {
|
||||
&mut self,
|
||||
input: &bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
// validate input has correct length
|
||||
let expected = self.input_len();
|
||||
let actual = input.len();
|
||||
if actual != expected {
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::Device::input_checked] Input length is {} ({} bytes) and must be {} bits ({} bytes)",
|
||||
module_path!(),
|
||||
actual,
|
||||
actual / 8,
|
||||
expected,
|
||||
expected / 8
|
||||
));
|
||||
}
|
||||
self.input(input)
|
||||
}
|
||||
|
||||
@@ -105,21 +211,6 @@ impl EthercatDevice for Wago750_652 {
|
||||
output: &mut bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
self.output(output)?;
|
||||
|
||||
// validate input has correct length
|
||||
let expected = self.output_len();
|
||||
let actual = output.len();
|
||||
if output.len() != expected {
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::Device::output_checked] Output length is {} ({} bytes) and must be {} bits ({} bytes)",
|
||||
module_path!(),
|
||||
actual,
|
||||
actual / 8,
|
||||
expected,
|
||||
expected / 8
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -143,6 +234,10 @@ impl NewEthercatDevice for Wago750_652 {
|
||||
tx_bit_offset: 0,
|
||||
rx_bit_offset: 0,
|
||||
module: None,
|
||||
rx_pdo: Wago750_652RxPdo::default(),
|
||||
tx_pdo: Wago750_652TxPdo::default(),
|
||||
started_init: false,
|
||||
has_messages_last_toggle: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,6 +248,108 @@ impl std::fmt::Debug for Wago750_652 {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Wago750_652Port {
|
||||
SI1, // Serial
|
||||
}
|
||||
|
||||
impl SerialInterfaceDevice<Wago750_652Port> for Wago750_652 {
|
||||
fn serial_interface_read_message(&mut self, port: Wago750_652Port) -> Option<Vec<u8>> {
|
||||
if !self.serial_interface_has_messages(port) {
|
||||
return None;
|
||||
} else {
|
||||
let in_len = self.tx_pdo.status.input_length;
|
||||
let received_data = self.tx_pdo.in_buffer[0..in_len].to_vec();
|
||||
|
||||
if received_data.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.has_messages_last_toggle = self.tx_pdo.status.receive_request;
|
||||
self.rx_pdo.control.receive_ack = !self.rx_pdo.control.receive_ack;
|
||||
|
||||
Some(received_data)
|
||||
}
|
||||
}
|
||||
|
||||
fn serial_interface_write_message(
|
||||
&mut self,
|
||||
_port: Wago750_652Port,
|
||||
message: Vec<u8>,
|
||||
) -> Result<bool, anyhow::Error> {
|
||||
if message.len() > WAGO750_652_MAX_BUF_LENGTH {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Message is too long for RxPdo Buffer of 22 bytes!"
|
||||
));
|
||||
}
|
||||
|
||||
if message.is_empty() {
|
||||
return Ok(self.rx_pdo.control.transmit_request == self.tx_pdo.status.transmit_ack);
|
||||
}
|
||||
|
||||
let bytes = message.as_slice();
|
||||
self.rx_pdo.control.output_length = message.len();
|
||||
self.rx_pdo.out_buffer.fill(0u8);
|
||||
self.rx_pdo.out_buffer[0..message.len()].copy_from_slice(&bytes[0..message.len()]);
|
||||
self.rx_pdo.control.transmit_request = !self.rx_pdo.control.transmit_request;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn serial_interface_has_messages(&mut self, _port: Wago750_652Port) -> bool {
|
||||
return self.tx_pdo.status.receive_request != self.has_messages_last_toggle;
|
||||
}
|
||||
|
||||
fn get_serial_encoding(
|
||||
&self,
|
||||
_port: Wago750_652Port,
|
||||
) -> Option<crate::io::serial_interface::SerialEncoding> {
|
||||
None
|
||||
}
|
||||
|
||||
fn get_baudrate(&self, _port: Wago750_652Port) -> Option<u32> {
|
||||
// Right now we cant change it on the fly because WAGO ONLY supports setting via
|
||||
// Codesys OR WAGO I/O Check the way it is set with Codesys is also NOT documented ...
|
||||
// My Hunch is that the 0x2010 0x2011 register can be used to configure that with COE its not documented though
|
||||
None
|
||||
}
|
||||
|
||||
/*
|
||||
Needs to be called multiple times until it returns true, at which point it is ready to be used
|
||||
*/
|
||||
fn serial_interface_initialize(&mut self, port: Wago750_652Port) -> bool {
|
||||
match port {
|
||||
Wago750_652Port::SI1 => {
|
||||
if !self.rx_pdo.control.init_request
|
||||
&& !self.tx_pdo.status.init_ack
|
||||
&& !self.started_init
|
||||
{
|
||||
self.rx_pdo.control.init_request = true;
|
||||
self.started_init = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.rx_pdo.control.init_request && self.tx_pdo.status.init_ack {
|
||||
self.rx_pdo.control.init_request = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.rx_pdo.control.init_request && self.tx_pdo.status.init_ack {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.rx_pdo.control.init_request
|
||||
&& !self.tx_pdo.status.init_ack
|
||||
&& self.started_init
|
||||
{
|
||||
self.has_messages_last_toggle = self.tx_pdo.status.receive_request;
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const WAGO_750_652_VENDOR_ID: u32 = 0x00000021;
|
||||
pub const WAGO_750_652_PRODUCT_ID: u32 = 106043250;
|
||||
pub const WAGO_750_652_MODULE_IDENT: SubDeviceProductTuple =
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = with pkgs; [
|
||||
cargo
|
||||
rustc
|
||||
pkg-config
|
||||
libudev-zero
|
||||
libpcap
|
||||
|
||||
@@ -31,6 +31,13 @@ serialport = "4.7.3"
|
||||
tracing = { version = "0.1.44", features = ["attributes"] }
|
||||
tonic = { version = "0.13.1", optional = true }
|
||||
|
||||
# modbus interfaces
|
||||
modbus = { git = "https://github.com/just-some-entity/modbus-rs.git" }
|
||||
|
||||
# enum stuff
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
|
||||
[dev-dependencies]
|
||||
approx = "0.5.1"
|
||||
textplots = "0.8.7"
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use crate::{MachineAct, analog_input_test_machine::AnalogInputTestMachine};
|
||||
use crate::{
|
||||
MachineAct, MachineMessage, MachineValues, analog_input_test_machine::AnalogInputTestMachine,
|
||||
};
|
||||
|
||||
impl MachineAct for AnalogInputTestMachine {
|
||||
fn act_machine_message(&mut self, msg: crate::MachineMessage) {
|
||||
fn act_machine_message(&mut self, msg: MachineMessage) {
|
||||
match msg {
|
||||
crate::MachineMessage::SubscribeNamespace(namespace) => {
|
||||
MachineMessage::SubscribeNamespace(namespace) => {
|
||||
self.namespace.namespace = Some(namespace);
|
||||
self.emit_measurement_rate();
|
||||
}
|
||||
crate::MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None,
|
||||
crate::MachineMessage::HttpApiJsonRequest(value) => {
|
||||
MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None,
|
||||
MachineMessage::HttpApiJsonRequest(value) => {
|
||||
use crate::MachineApi;
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
crate::MachineMessage::ConnectToMachine(_machine_connection) => {}
|
||||
crate::MachineMessage::DisconnectMachine(_machine_connection) => {}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) => {}
|
||||
MachineMessage::RequestValues(sender) => {
|
||||
sender
|
||||
.send_blocking(MachineValues {
|
||||
state: serde_json::Value::Null,
|
||||
live_values: serde_json::Value::Null,
|
||||
})
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::{AquaPathV1, AquaPathV1Mode};
|
||||
use crate::{MachineAct, MachineMessage};
|
||||
use crate::{MachineAct, MachineMessage, MachineValues};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
impl MachineAct for AquaPathV1 {
|
||||
@@ -45,14 +45,21 @@ impl MachineAct for AquaPathV1 {
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
{}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
{
|
||||
()
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
{}
|
||||
MachineMessage::RequestValues(sender) => {
|
||||
sender
|
||||
.send_blocking(MachineValues {
|
||||
state: serde_json::to_value(self.get_state())
|
||||
.expect("Failed to serialize state"),
|
||||
live_values: serde_json::to_value(self.get_live_values())
|
||||
.expect("Failed to serialize live values"),
|
||||
})
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,12 @@ pub struct LiveValuesEvent {
|
||||
pub back_temperature: f64,
|
||||
pub front_temp_reservoir: f64,
|
||||
pub back_temp_reservoir: f64,
|
||||
pub front_revolutions: f64,
|
||||
pub back_revolutions: f64,
|
||||
pub front_power: f64,
|
||||
pub back_power: f64,
|
||||
pub front_total_energy: f64,
|
||||
pub back_total_energy: f64,
|
||||
}
|
||||
|
||||
impl LiveValuesEvent {
|
||||
@@ -35,6 +41,8 @@ pub struct StateEvent {
|
||||
pub mode_state: ModeState,
|
||||
pub flow_states: FlowStates,
|
||||
pub temperature_states: TempStates,
|
||||
pub fan_states: FanStates,
|
||||
pub tolerance_states: ToleranceStates,
|
||||
}
|
||||
|
||||
impl StateEvent {
|
||||
@@ -70,6 +78,28 @@ pub struct FlowState {
|
||||
pub should_flow: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct FanState {
|
||||
pub revolutions: f64,
|
||||
pub max_revolutions: f64,
|
||||
}
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct FanStates {
|
||||
pub front: FanState,
|
||||
pub back: FanState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct ToleranceState {
|
||||
pub heating: f64,
|
||||
pub cooling: f64,
|
||||
}
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct ToleranceStates {
|
||||
pub front: ToleranceState,
|
||||
pub back: ToleranceState,
|
||||
}
|
||||
|
||||
pub enum AquaPathV1Events {
|
||||
LiveValues(Event<LiveValuesEvent>),
|
||||
State(Event<StateEvent>),
|
||||
@@ -85,6 +115,14 @@ enum Mutation {
|
||||
|
||||
SetFrontFlow(bool),
|
||||
SetBackFlow(bool),
|
||||
|
||||
SetFrontRevolutions(f64),
|
||||
SetBackRevolutions(f64),
|
||||
|
||||
SetFrontHeatingTolerance(f64),
|
||||
SetBackHeatingTolerance(f64),
|
||||
SetFrontCoolingTolerance(f64),
|
||||
SetBackCoolingTolerance(f64),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -145,6 +183,24 @@ impl MachineApi for AquaPathV1 {
|
||||
Mutation::SetFrontFlow(should_pump) => {
|
||||
self.set_should_pump(should_pump, super::AquaPathSideType::Front)
|
||||
}
|
||||
Mutation::SetBackRevolutions(revolution) => {
|
||||
self.set_max_revolutions(revolution, super::AquaPathSideType::Back)
|
||||
}
|
||||
Mutation::SetFrontRevolutions(revolutions) => {
|
||||
self.set_max_revolutions(revolutions, super::AquaPathSideType::Front)
|
||||
}
|
||||
Mutation::SetBackHeatingTolerance(tolerance) => {
|
||||
self.set_heating_tolerance(tolerance, super::AquaPathSideType::Back)
|
||||
}
|
||||
Mutation::SetFrontHeatingTolerance(tolerance) => {
|
||||
self.set_heating_tolerance(tolerance, super::AquaPathSideType::Front)
|
||||
}
|
||||
Mutation::SetBackCoolingTolerance(tolerance) => {
|
||||
self.set_cooling_tolerance(tolerance, super::AquaPathSideType::Back);
|
||||
}
|
||||
Mutation::SetFrontCoolingTolerance(tolerance) => {
|
||||
self.set_cooling_tolerance(tolerance, super::AquaPathSideType::Front);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -5,17 +5,17 @@ use ethercat_hal::io::encoder_input::EncoderInput;
|
||||
use ethercat_hal::io::{
|
||||
analog_output::AnalogOutput, digital_output::DigitalOutput, temperature_input::TemperatureInput,
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::time::Instant;
|
||||
use units::AngularVelocity;
|
||||
use units::angular_velocity::revolution_per_minute;
|
||||
use units::f64::ThermodynamicTemperature;
|
||||
use units::thermodynamic_temperature::degree_celsius;
|
||||
use units::thermodynamic_temperature::{degree_celsius, kelvin};
|
||||
use units::volume_rate::liter_per_minute;
|
||||
#[derive(Debug)]
|
||||
|
||||
pub struct Controller {
|
||||
pub pid: PidController,
|
||||
temperature_pid_output: f64,
|
||||
window_start: Instant,
|
||||
pwm_period: Duration,
|
||||
|
||||
pub temperature: Temperature,
|
||||
pub target_temperature: ThermodynamicTemperature,
|
||||
@@ -27,10 +27,19 @@ pub struct Controller {
|
||||
pub cooling_controller: AnalogOutput,
|
||||
pub cooling_relais: DigitalOutput,
|
||||
|
||||
pub cooling_tolerance: ThermodynamicTemperature,
|
||||
pub heating_tolerance: ThermodynamicTemperature,
|
||||
|
||||
pub current_revolutions: AngularVelocity,
|
||||
pub max_revolutions: AngularVelocity,
|
||||
|
||||
pub heating_relais_1: DigitalOutput,
|
||||
pub temperature_sensor_in: TemperatureInput,
|
||||
pub temperature_sensor_out: TemperatureInput,
|
||||
|
||||
pub power: f64,
|
||||
pub total_energy: f64,
|
||||
|
||||
pub cooling_allowed: bool,
|
||||
pub heating_allowed: bool,
|
||||
|
||||
@@ -48,7 +57,6 @@ impl Controller {
|
||||
kp: f64,
|
||||
ki: f64,
|
||||
kd: f64,
|
||||
pwm_duration: Duration,
|
||||
temp: Temperature,
|
||||
target_tempetature: ThermodynamicTemperature,
|
||||
cooling_controller: AnalogOutput,
|
||||
@@ -56,6 +64,7 @@ impl Controller {
|
||||
heating_relais_1: DigitalOutput,
|
||||
temp_sensor_in: TemperatureInput,
|
||||
temp_sensor_out: TemperatureInput,
|
||||
max_revolutions: AngularVelocity,
|
||||
|
||||
flow: Flow,
|
||||
pump_relais: DigitalOutput,
|
||||
@@ -63,9 +72,7 @@ impl Controller {
|
||||
) -> Self {
|
||||
Self {
|
||||
pid: PidController::new(kp, ki, kd),
|
||||
temperature_pid_output: 0.0,
|
||||
window_start: Instant::now(),
|
||||
pwm_period: pwm_duration,
|
||||
target_temperature: target_tempetature,
|
||||
current_temperature: ThermodynamicTemperature::new::<degree_celsius>(25.0),
|
||||
temp_reservoir: ThermodynamicTemperature::new::<degree_celsius>(25.0),
|
||||
@@ -74,13 +81,24 @@ impl Controller {
|
||||
|
||||
temperature: temp,
|
||||
cooling_controller: cooling_controller,
|
||||
|
||||
cooling_tolerance: ThermodynamicTemperature::new::<degree_celsius>(2.0),
|
||||
heating_tolerance: ThermodynamicTemperature::new::<degree_celsius>(2.0),
|
||||
|
||||
current_revolutions: AngularVelocity::new::<revolution_per_minute>(0.0),
|
||||
max_revolutions: max_revolutions,
|
||||
|
||||
cooling_relais: cooling_relais,
|
||||
heating_relais_1: heating_relais_1,
|
||||
|
||||
cooling_allowed: false,
|
||||
heating_allowed: false,
|
||||
temperature_sensor_in: temp_sensor_in,
|
||||
temperature_sensor_out: temp_sensor_out,
|
||||
|
||||
power: 700.0,
|
||||
total_energy: 0.0,
|
||||
|
||||
flow: flow,
|
||||
pump_relais: pump_relais,
|
||||
flow_sensor: flow_sensor,
|
||||
@@ -174,12 +192,12 @@ impl Controller {
|
||||
|
||||
pub fn turn_cooling_on(&mut self) {
|
||||
self.cooling_relais.set(true);
|
||||
self.cooling_controller.set(10.0);
|
||||
self.temperature.cooling = true;
|
||||
}
|
||||
|
||||
pub fn turn_cooling_off(&mut self) {
|
||||
self.cooling_relais.set(false);
|
||||
self.current_revolutions = AngularVelocity::new::<revolution_per_minute>(0.0);
|
||||
self.temperature.cooling = false;
|
||||
}
|
||||
|
||||
@@ -191,6 +209,7 @@ impl Controller {
|
||||
pub fn turn_heating_off(&mut self) {
|
||||
self.heating_relais_1.set(false);
|
||||
self.temperature.heating = false;
|
||||
self.power = 0.0;
|
||||
}
|
||||
|
||||
pub fn set_should_pump(&mut self, should_pump: bool) {
|
||||
@@ -224,6 +243,35 @@ impl Controller {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_current_revolutions(&self) -> AngularVelocity {
|
||||
self.current_revolutions
|
||||
}
|
||||
|
||||
pub fn get_max_revolutions(&self) -> AngularVelocity {
|
||||
self.max_revolutions
|
||||
}
|
||||
|
||||
pub fn set_max_revolutions(&mut self, revolutions: AngularVelocity) {
|
||||
self.max_revolutions = revolutions;
|
||||
}
|
||||
|
||||
pub fn set_cooling_tolerance(&mut self, tolerance: ThermodynamicTemperature) {
|
||||
self.cooling_tolerance = tolerance;
|
||||
}
|
||||
|
||||
pub fn set_heating_tolerance(&mut self, tolerance: ThermodynamicTemperature) {
|
||||
self.heating_tolerance = tolerance;
|
||||
}
|
||||
|
||||
// no power/energy unit implemented
|
||||
pub fn get_current_power(&self) -> f64 {
|
||||
self.power
|
||||
}
|
||||
|
||||
pub fn get_total_energy(&self) -> f64 {
|
||||
self.total_energy
|
||||
}
|
||||
|
||||
pub fn update(&mut self, now: Instant) -> () {
|
||||
let current_flow = self.get_flow();
|
||||
self.current_flow = current_flow;
|
||||
@@ -250,43 +298,50 @@ impl Controller {
|
||||
// Calculate PID error once
|
||||
let error = self.target_temperature.get::<degree_celsius>()
|
||||
- self.current_temperature.get::<degree_celsius>();
|
||||
let control = self.pid.update(error, now);
|
||||
self.temperature_pid_output = control;
|
||||
|
||||
let elapsed = now.duration_since(self.window_start);
|
||||
if elapsed >= self.pwm_period {
|
||||
self.window_start = now;
|
||||
}
|
||||
let elapsed = now - self.window_start;
|
||||
self.window_start = now;
|
||||
|
||||
// Decide whether to heat or cool based on error
|
||||
if error > 0.0 {
|
||||
if error > self.heating_tolerance.get::<degree_celsius>() {
|
||||
// Need heating (current < target)
|
||||
if self.temperature.cooling {
|
||||
self.turn_cooling_off();
|
||||
}
|
||||
if self.heating_allowed && current_flow > VolumeRate::new::<liter_per_minute>(0.0) {
|
||||
// Only start heating if pump is on
|
||||
let duty = control.clamp(0.0, 1.0);
|
||||
let on_time = self.pwm_period.mul_f64(duty);
|
||||
let on = elapsed < on_time;
|
||||
if on && !self.temperature.heating {
|
||||
self.turn_heating_on();
|
||||
} else if !on && self.temperature.heating {
|
||||
self.turn_heating_off();
|
||||
}
|
||||
self.turn_heating_on();
|
||||
|
||||
self.total_energy += self.get_current_power() * elapsed.as_secs_f64() / 3600.0;
|
||||
} else {
|
||||
// Pump is off or heating not allowed - don't heat
|
||||
if self.temperature.heating {
|
||||
self.turn_heating_off();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if error < -self.cooling_tolerance.get::<degree_celsius>() {
|
||||
// Need cooling (current > target)
|
||||
if self.temperature.heating {
|
||||
self.turn_heating_off();
|
||||
}
|
||||
if self.cooling_allowed && !self.temperature.cooling {
|
||||
self.turn_cooling_on();
|
||||
if self.cooling_allowed && current_flow > VolumeRate::new::<liter_per_minute>(0.0) {
|
||||
if !self.temperature.cooling {
|
||||
self.turn_cooling_on();
|
||||
}
|
||||
|
||||
let max_revolutions = self.get_max_revolutions();
|
||||
let temp_offset = self.current_temperature - self.target_temperature;
|
||||
|
||||
let target_revolutions = (temp_offset.get::<kelvin>() * 10.0)
|
||||
.clamp(0.0, max_revolutions.get::<revolution_per_minute>());
|
||||
|
||||
self.cooling_controller
|
||||
.set(target_revolutions as f32 / 10.0);
|
||||
self.current_revolutions =
|
||||
AngularVelocity::new::<revolution_per_minute>(target_revolutions);
|
||||
} else {
|
||||
if self.temperature.cooling {
|
||||
self.turn_cooling_off();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use api::{ToleranceState, ToleranceStates};
|
||||
use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Instant;
|
||||
use units::angular_velocity::revolution_per_minute;
|
||||
use units::f64::*;
|
||||
use units::{thermodynamic_temperature::degree_celsius, volume_rate::liter_per_minute};
|
||||
|
||||
@@ -9,8 +11,8 @@ use crate::{
|
||||
MACHINE_AQUAPATH_V1, VENDOR_QITECH,
|
||||
aquapath1::{
|
||||
api::{
|
||||
AquaPathV1Events, AquaPathV1Namespace, FlowState, FlowStates, LiveValuesEvent,
|
||||
ModeState, StateEvent, TempState, TempStates,
|
||||
AquaPathV1Events, AquaPathV1Namespace, FanState, FanStates, FlowState, FlowStates,
|
||||
LiveValuesEvent, ModeState, StateEvent, TempState, TempStates,
|
||||
},
|
||||
controller::Controller,
|
||||
},
|
||||
@@ -109,8 +111,8 @@ impl std::fmt::Display for AquaPathV1 {
|
||||
}
|
||||
|
||||
impl AquaPathV1 {
|
||||
pub fn emit_live_values(&mut self) {
|
||||
let live_values = LiveValuesEvent {
|
||||
pub fn get_live_values(&self) -> LiveValuesEvent {
|
||||
LiveValuesEvent {
|
||||
front_temperature: self
|
||||
.front_controller
|
||||
.current_temperature
|
||||
@@ -123,13 +125,28 @@ impl AquaPathV1 {
|
||||
back_flow: self.back_controller.current_flow.get::<liter_per_minute>(),
|
||||
front_temp_reservoir: self.front_controller.temp_reservoir.get::<degree_celsius>(),
|
||||
back_temp_reservoir: self.back_controller.temp_reservoir.get::<degree_celsius>(),
|
||||
};
|
||||
let event = live_values.build();
|
||||
front_revolutions: self
|
||||
.front_controller
|
||||
.current_revolutions
|
||||
.get::<revolution_per_minute>(),
|
||||
back_revolutions: self
|
||||
.back_controller
|
||||
.current_revolutions
|
||||
.get::<revolution_per_minute>(),
|
||||
front_power: self.front_controller.get_current_power(),
|
||||
back_power: self.back_controller.get_current_power(),
|
||||
front_total_energy: self.front_controller.get_total_energy(),
|
||||
back_total_energy: self.back_controller.get_total_energy(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_live_values(&mut self) {
|
||||
let event = self.get_live_values().build();
|
||||
self.namespace.emit(AquaPathV1Events::LiveValues(event));
|
||||
}
|
||||
|
||||
pub fn emit_state(&mut self) {
|
||||
let state = StateEvent {
|
||||
pub fn get_state(&self) -> StateEvent {
|
||||
StateEvent {
|
||||
is_default_state: false,
|
||||
mode_state: ModeState {
|
||||
mode: self.mode.clone(),
|
||||
@@ -167,9 +184,55 @@ impl AquaPathV1 {
|
||||
should_flow: self.back_controller.should_pump,
|
||||
},
|
||||
},
|
||||
};
|
||||
fan_states: FanStates {
|
||||
front: FanState {
|
||||
revolutions: self
|
||||
.front_controller
|
||||
.max_revolutions
|
||||
.get::<revolution_per_minute>(),
|
||||
max_revolutions: self
|
||||
.front_controller
|
||||
.max_revolutions
|
||||
.get::<revolution_per_minute>(),
|
||||
},
|
||||
back: FanState {
|
||||
revolutions: self
|
||||
.back_controller
|
||||
.max_revolutions
|
||||
.get::<revolution_per_minute>(),
|
||||
max_revolutions: self
|
||||
.back_controller
|
||||
.max_revolutions
|
||||
.get::<revolution_per_minute>(),
|
||||
},
|
||||
},
|
||||
tolerance_states: ToleranceStates {
|
||||
front: ToleranceState {
|
||||
heating: self
|
||||
.front_controller
|
||||
.heating_tolerance
|
||||
.get::<degree_celsius>(),
|
||||
cooling: self
|
||||
.front_controller
|
||||
.cooling_tolerance
|
||||
.get::<degree_celsius>(),
|
||||
},
|
||||
back: ToleranceState {
|
||||
heating: self
|
||||
.back_controller
|
||||
.heating_tolerance
|
||||
.get::<degree_celsius>(),
|
||||
cooling: self
|
||||
.back_controller
|
||||
.cooling_tolerance
|
||||
.get::<degree_celsius>(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let event = state.build();
|
||||
pub fn emit_state(&mut self) {
|
||||
let event = self.get_state().build();
|
||||
self.namespace.emit(AquaPathV1Events::State(event));
|
||||
}
|
||||
}
|
||||
@@ -269,3 +332,45 @@ impl AquaPathV1 {
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
impl AquaPathV1 {
|
||||
fn set_max_revolutions(&mut self, revolutions: f64, fan_type: AquaPathSideType) {
|
||||
match fan_type {
|
||||
AquaPathSideType::Back => self
|
||||
.back_controller
|
||||
.set_max_revolutions(AngularVelocity::new::<revolution_per_minute>(revolutions)),
|
||||
AquaPathSideType::Front => self
|
||||
.front_controller
|
||||
.set_max_revolutions(AngularVelocity::new::<revolution_per_minute>(revolutions)),
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
impl AquaPathV1 {
|
||||
fn set_heating_tolerance(&mut self, tolerance: f64, tolerance_type: AquaPathSideType) {
|
||||
match tolerance_type {
|
||||
AquaPathSideType::Back => self
|
||||
.back_controller
|
||||
.set_heating_tolerance(ThermodynamicTemperature::new::<degree_celsius>(tolerance)),
|
||||
AquaPathSideType::Front => self
|
||||
.front_controller
|
||||
.set_heating_tolerance(ThermodynamicTemperature::new::<degree_celsius>(tolerance)),
|
||||
}
|
||||
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
fn set_cooling_tolerance(&mut self, tolerance: f64, tolerance_type: AquaPathSideType) {
|
||||
match tolerance_type {
|
||||
AquaPathSideType::Back => self
|
||||
.back_controller
|
||||
.set_cooling_tolerance(ThermodynamicTemperature::new::<degree_celsius>(tolerance)),
|
||||
AquaPathSideType::Front => self
|
||||
.front_controller
|
||||
.set_cooling_tolerance(ThermodynamicTemperature::new::<degree_celsius>(tolerance)),
|
||||
}
|
||||
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,12 @@ use ethercat_hal::{
|
||||
temperature_input::TemperatureInput,
|
||||
},
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
use units::thermodynamic_temperature::{ThermodynamicTemperature, degree_celsius};
|
||||
use std::time::Instant;
|
||||
use units::{
|
||||
AngularVelocity,
|
||||
angular_velocity::revolution_per_minute,
|
||||
thermodynamic_temperature::{ThermodynamicTemperature, degree_celsius},
|
||||
};
|
||||
|
||||
impl MachineNewTrait for AquaPathV1 {
|
||||
fn new<'maindevice>(params: &MachineNewParams) -> Result<Self, Error> {
|
||||
@@ -131,7 +135,6 @@ impl MachineNewTrait for AquaPathV1 {
|
||||
0.10,
|
||||
0.0,
|
||||
0.015,
|
||||
Duration::from_millis(500),
|
||||
Temperature::default(),
|
||||
ThermodynamicTemperature::new::<degree_celsius>(25.0),
|
||||
ao1,
|
||||
@@ -139,6 +142,7 @@ impl MachineNewTrait for AquaPathV1 {
|
||||
do2,
|
||||
t1,
|
||||
t2,
|
||||
AngularVelocity::new::<revolution_per_minute>(100.0),
|
||||
Flow::default(),
|
||||
do1,
|
||||
enc1,
|
||||
@@ -148,7 +152,6 @@ impl MachineNewTrait for AquaPathV1 {
|
||||
0.10,
|
||||
0.0,
|
||||
0.015,
|
||||
Duration::from_millis(500),
|
||||
Temperature::default(),
|
||||
ThermodynamicTemperature::new::<degree_celsius>(25.0),
|
||||
ao2,
|
||||
@@ -156,6 +159,7 @@ impl MachineNewTrait for AquaPathV1 {
|
||||
do6,
|
||||
t3,
|
||||
t4,
|
||||
AngularVelocity::new::<revolution_per_minute>(100.0),
|
||||
Flow::default(),
|
||||
do5,
|
||||
enc2,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::BufferV1;
|
||||
use crate::{MachineAct, MachineMessage};
|
||||
use crate::{MachineAct, MachineMessage, MachineValues};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
impl MachineAct for BufferV1 {
|
||||
@@ -33,14 +33,21 @@ impl MachineAct for BufferV1 {
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
{}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
{
|
||||
()
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
{}
|
||||
MachineMessage::RequestValues(sender) => {
|
||||
sender
|
||||
.send_blocking(MachineValues {
|
||||
state: serde_json::to_value(self.get_state())
|
||||
.expect("Failed to serialize state"),
|
||||
live_values: serde_json::to_value(self.get_live_values())
|
||||
.expect("Failed to serialize live values"),
|
||||
})
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,21 +48,27 @@ impl BufferV1 {
|
||||
vendor: VENDOR_QITECH,
|
||||
machine: MACHINE_BUFFER_V1,
|
||||
};
|
||||
pub fn emit_live_values(&mut self) {
|
||||
let live_values = LiveValuesEvent {};
|
||||
|
||||
let event = live_values.build();
|
||||
pub fn get_live_values(&mut self) -> LiveValuesEvent {
|
||||
LiveValuesEvent {}
|
||||
}
|
||||
|
||||
pub fn emit_live_values(&mut self) {
|
||||
let event = self.get_live_values().build();
|
||||
self.namespace.emit(BufferV1Events::LiveValues(event));
|
||||
}
|
||||
pub fn emit_state(&mut self) {
|
||||
let state = StateEvent {
|
||||
|
||||
pub fn get_state(&self) -> StateEvent {
|
||||
StateEvent {
|
||||
mode_state: ModeState {
|
||||
mode: self.mode.clone(),
|
||||
},
|
||||
// connected_machine_state: self.connected_winder.to_state(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let event = state.build();
|
||||
pub fn emit_state(&mut self) {
|
||||
let event = self.get_state().build();
|
||||
self.namespace.emit(BufferV1Events::State(event));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
use super::DigitalInputTestMachine;
|
||||
use crate::{MachineAct, MachineMessage, MachineValues};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
impl MachineAct for DigitalInputTestMachine {
|
||||
fn act(&mut self, now: Instant) {
|
||||
if let Ok(msg) = self.api_receiver.try_recv() {
|
||||
self.act_machine_message(msg);
|
||||
}
|
||||
|
||||
if now.duration_since(self.last_state_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
self.emit_state();
|
||||
self.last_state_emit = now;
|
||||
}
|
||||
}
|
||||
|
||||
fn act_machine_message(&mut self, msg: MachineMessage) {
|
||||
match msg {
|
||||
MachineMessage::SubscribeNamespace(namespace) => {
|
||||
self.namespace.namespace = Some(namespace);
|
||||
self.emit_state();
|
||||
}
|
||||
MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None,
|
||||
MachineMessage::HttpApiJsonRequest(value) => {
|
||||
use crate::MachineApi;
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) => {
|
||||
// Does not connect to any Machine; do nothing
|
||||
}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) => {
|
||||
// Does not connect to any Machine; do nothing
|
||||
}
|
||||
MachineMessage::RequestValues(sender) => {
|
||||
sender
|
||||
.send_blocking(MachineValues {
|
||||
state: serde_json::to_value(self.get_state())
|
||||
.expect("Failed to serialize state"),
|
||||
live_values: serde_json::Value::Null,
|
||||
})
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use control_core::socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_first_and_last_event,
|
||||
},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{MachineApi, digital_input_test_machine::DigitalInputTestMachine};
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct StateEvent {
|
||||
pub led_on: [bool; 4],
|
||||
}
|
||||
|
||||
impl StateEvent {
|
||||
pub fn build(&self) -> Event<Self> {
|
||||
Event::new("StateEvent", self.clone())
|
||||
}
|
||||
}
|
||||
pub enum DigitalInputTestMachineEvents {
|
||||
State(Event<StateEvent>),
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "action", content = "value")]
|
||||
pub enum Mutation {
|
||||
SetLed { index: usize, on: bool },
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DigitalInputTestMachineNamespace {
|
||||
pub namespace: Option<Namespace>,
|
||||
}
|
||||
|
||||
impl NamespaceCacheingLogic<DigitalInputTestMachineEvents> for DigitalInputTestMachineNamespace {
|
||||
fn emit(&mut self, events: DigitalInputTestMachineEvents) {
|
||||
let event = Arc::new(events.event_value());
|
||||
let buffer_fn = events.event_cache_fn();
|
||||
if let Some(ns) = &mut self.namespace {
|
||||
ns.emit(event, &buffer_fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CacheableEvents<DigitalInputTestMachineEvents> for DigitalInputTestMachineEvents {
|
||||
fn event_value(&self) -> GenericEvent {
|
||||
match self {
|
||||
DigitalInputTestMachineEvents::State(event) => event.clone().into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn event_cache_fn(&self) -> CacheFn {
|
||||
cache_first_and_last_event()
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineApi for DigitalInputTestMachine {
|
||||
fn api_get_sender(&self) -> smol::channel::Sender<crate::MachineMessage> {
|
||||
self.api_sender.clone()
|
||||
}
|
||||
|
||||
fn api_mutate(&mut self, _value: serde_json::Value) -> Result<(), anyhow::Error> {
|
||||
//Digital Input Test Machine does not Set Values
|
||||
// let mutation: Mutation = serde_json::from_value(value)?;
|
||||
// match mutation {
|
||||
// Mutation::SetLed { index, on } => self.set_led(index, on),
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> Option<Namespace> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
use ethercat_hal::io::digital_input::DigitalInput;
|
||||
use smol::channel::{Receiver, Sender};
|
||||
|
||||
use crate::{
|
||||
AsyncThreadMessage, DIGITAL_INPUT_TEST_MACHINE, Machine, MachineMessage, VENDOR_QITECH,
|
||||
digital_input_test_machine::api::{
|
||||
DigitalInputTestMachineEvents, DigitalInputTestMachineNamespace, StateEvent,
|
||||
},
|
||||
machine_identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
};
|
||||
|
||||
pub mod act;
|
||||
pub mod api;
|
||||
pub mod new;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DigitalInputTestMachine {
|
||||
pub api_receiver: Receiver<MachineMessage>,
|
||||
pub api_sender: Sender<MachineMessage>,
|
||||
pub machine_identification_unique: MachineIdentificationUnique,
|
||||
pub main_sender: Option<Sender<AsyncThreadMessage>>,
|
||||
pub namespace: DigitalInputTestMachineNamespace,
|
||||
pub last_state_emit: Instant,
|
||||
pub led_on: [bool; 4],
|
||||
pub digital_input: [DigitalInput; 4],
|
||||
}
|
||||
|
||||
impl Machine for DigitalInputTestMachine {
|
||||
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
|
||||
self.machine_identification_unique.clone()
|
||||
}
|
||||
|
||||
fn get_main_sender(&self) -> Option<Sender<AsyncThreadMessage>> {
|
||||
self.main_sender.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl DigitalInputTestMachine {
|
||||
pub const MACHINE_IDENTIFICATION: MachineIdentification = MachineIdentification {
|
||||
vendor: VENDOR_QITECH,
|
||||
machine: DIGITAL_INPUT_TEST_MACHINE,
|
||||
};
|
||||
|
||||
pub fn get_state(&self) -> StateEvent {
|
||||
StateEvent {
|
||||
led_on: self.led_on,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_state(&mut self) {
|
||||
//Set Led_On after DigitalInput
|
||||
let mut i = 0;
|
||||
|
||||
for di in &self.digital_input {
|
||||
let value = match di.get_value() {
|
||||
Ok(v) => v,
|
||||
Err(_) => false,
|
||||
};
|
||||
self.led_on[i] = value;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
let event = StateEvent {
|
||||
led_on: self.led_on,
|
||||
}
|
||||
.build();
|
||||
self.namespace
|
||||
.emit(DigitalInputTestMachineEvents::State(event));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
use std::{sync::Arc, time::Instant};
|
||||
|
||||
use anyhow::Error;
|
||||
use ethercat_hal::{
|
||||
devices::{
|
||||
EthercatDevice, downcast_device,
|
||||
wago_750_354::{WAGO_750_354_IDENTITY_A, Wago750_354},
|
||||
wago_modules::wago_750_402::{Wago750_402, Wago750_402InputPort},
|
||||
},
|
||||
io::digital_input::DigitalInput,
|
||||
};
|
||||
use smol::{block_on, lock::RwLock};
|
||||
|
||||
use crate::{
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait,
|
||||
digital_input_test_machine::{DigitalInputTestMachine, api::DigitalInputTestMachineNamespace},
|
||||
get_ethercat_device, validate_no_role_dublicates, validate_same_machine_identification_unique,
|
||||
};
|
||||
|
||||
impl MachineNewTrait for DigitalInputTestMachine {
|
||||
fn new<'maindevice>(params: &MachineNewParams) -> Result<Self, Error> {
|
||||
// validate general stuff
|
||||
let device_identification = params
|
||||
.device_group
|
||||
.iter()
|
||||
.map(|device_identification| device_identification.clone())
|
||||
.collect::<Vec<_>>();
|
||||
validate_same_machine_identification_unique(&device_identification)?;
|
||||
validate_no_role_dublicates(&device_identification)?;
|
||||
|
||||
let hardware = match ¶ms.hardware {
|
||||
MachineNewHardware::Ethercat(x) => x,
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::EtherCATMachine/TestMachine::new] MachineNewHardware is not Ethercat",
|
||||
module_path!()
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
block_on(async {
|
||||
// Example usage of a Wago Coupler and a 750-402 in the first slot, where the Output Port 1,2,3,4 is used
|
||||
let _wago_750_354 = get_ethercat_device::<Wago750_354>(
|
||||
hardware,
|
||||
params,
|
||||
0,
|
||||
[WAGO_750_354_IDENTITY_A].to_vec(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let modules = Wago750_354::initialize_modules(_wago_750_354.1).await?;
|
||||
|
||||
let mut coupler = _wago_750_354.0.write().await;
|
||||
|
||||
for module in modules {
|
||||
coupler.set_module(module);
|
||||
}
|
||||
|
||||
coupler.init_slot_modules(_wago_750_354.1);
|
||||
let dev = coupler.slot_devices.get(0).unwrap().clone().unwrap();
|
||||
let wago750_402: Arc<RwLock<Wago750_402>> = downcast_device::<Wago750_402>(dev).await?;
|
||||
|
||||
let di1 = DigitalInput::new(wago750_402.clone(), Wago750_402InputPort::DI1);
|
||||
let di2 = DigitalInput::new(wago750_402.clone(), Wago750_402InputPort::DI2);
|
||||
let di3 = DigitalInput::new(wago750_402.clone(), Wago750_402InputPort::DI3);
|
||||
let di4 = DigitalInput::new(wago750_402.clone(), Wago750_402InputPort::DI4);
|
||||
|
||||
drop(coupler);
|
||||
|
||||
let (sender, receiver) = smol::channel::unbounded();
|
||||
let mut my_test = Self {
|
||||
api_receiver: receiver,
|
||||
api_sender: sender,
|
||||
machine_identification_unique: params.get_machine_identification_unique(),
|
||||
namespace: DigitalInputTestMachineNamespace {
|
||||
namespace: params.namespace.clone(),
|
||||
},
|
||||
last_state_emit: Instant::now(),
|
||||
led_on: [false; 4],
|
||||
main_sender: params.main_thread_channel.clone(),
|
||||
digital_input: [di1, di2, di3, di4],
|
||||
};
|
||||
my_test.emit_state();
|
||||
Ok(my_test)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::extruder1::ExtruderV2;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::{MachineAct, MachineMessage};
|
||||
use crate::{MachineAct, MachineMessage, MachineValues};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
@@ -41,6 +41,7 @@ impl MachineAct for ExtruderV2 {
|
||||
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
self.update_total_energy(now);
|
||||
self.maybe_emit_state_event();
|
||||
// Emit live values at 30 FPS
|
||||
self.emit_live_values();
|
||||
@@ -61,11 +62,20 @@ impl MachineAct for ExtruderV2 {
|
||||
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) => (),
|
||||
MachineMessage::ConnectToMachine(_machine_connection) => {}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
{
|
||||
()
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
{}
|
||||
MachineMessage::RequestValues(sender) => {
|
||||
sender
|
||||
.send_blocking(MachineValues {
|
||||
state: serde_json::to_value(self.get_state())
|
||||
.expect("Failed to serialize state"),
|
||||
live_values: serde_json::to_value(self.get_live_values())
|
||||
.expect("Failed to serialize live values"),
|
||||
})
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@ use units::{angular_velocity::revolution_per_minute, thermodynamic_temperature::
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl ExtruderV2 {
|
||||
pub fn build_state_event(&mut self) -> StateEvent {
|
||||
use crate::extruder1::api::{TemperaturePid, TemperaturePidStates};
|
||||
pub fn get_state(&self) -> StateEvent {
|
||||
use crate::extruder1::api::TemperaturePidStates;
|
||||
|
||||
StateEvent {
|
||||
is_default_state: !std::mem::replace(&mut self.emitted_default_state, true),
|
||||
is_default_state: !self.emitted_default_state,
|
||||
rotation_state: RotationState {
|
||||
forward: self.screw_speed_controller.get_rotation_direction(),
|
||||
},
|
||||
@@ -141,16 +141,14 @@ impl ExtruderV2 {
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl ExtruderV2 {
|
||||
pub fn emit_state(&mut self) {
|
||||
let state = self.build_state_event();
|
||||
let state = self.get_state();
|
||||
let hash = hash_with_serde_model(self.screw_speed_controller.get_inverter_status());
|
||||
self.last_status_hash = Some(hash);
|
||||
let event = state.build();
|
||||
self.namespace.emit(ExtruderV2Events::State(event));
|
||||
self.emitted_default_state = true;
|
||||
}
|
||||
|
||||
pub fn maybe_emit_state_event(&mut self) {
|
||||
@@ -168,13 +166,8 @@ impl ExtruderV2 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_live_values(&mut self) {
|
||||
use std::time::Instant;
|
||||
let now = Instant::now();
|
||||
let combined_power = self.calculate_combined_power();
|
||||
self.update_total_energy(combined_power, now);
|
||||
|
||||
let live_values = LiveValuesEvent {
|
||||
pub fn get_live_values(&self) -> LiveValuesEvent {
|
||||
LiveValuesEvent {
|
||||
motor_status: self.screw_speed_controller.get_motor_status().into(),
|
||||
pressure: self.screw_speed_controller.get_pressure().get::<bar>(),
|
||||
nozzle_temperature: self
|
||||
@@ -209,11 +202,13 @@ impl ExtruderV2 {
|
||||
middle_power: self
|
||||
.temperature_controller_middle
|
||||
.get_heating_element_wattage(),
|
||||
combined_power,
|
||||
combined_power: self.calculate_combined_power(),
|
||||
total_energy_kwh: self.total_energy_kwh,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let event = live_values.build();
|
||||
pub fn emit_live_values(&mut self) {
|
||||
let event = self.get_live_values().build();
|
||||
self.namespace.emit(ExtruderV2Events::LiveValues(event));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::ExtruderV2;
|
||||
use crate::MachineAct;
|
||||
use crate::MachineMessage;
|
||||
use crate::{MachineMessage, MachineValues};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
impl MachineAct for ExtruderV2 {
|
||||
@@ -40,6 +40,17 @@ impl MachineAct for ExtruderV2 {
|
||||
{
|
||||
()
|
||||
}
|
||||
MachineMessage::RequestValues(sender) => {
|
||||
sender
|
||||
.send_blocking(MachineValues {
|
||||
state: serde_json::to_value(self.build_state_event())
|
||||
.expect("Failed to serialize state"),
|
||||
live_values: serde_json::to_value(self.get_live_values())
|
||||
.expect("Failed to serialize live values"),
|
||||
})
|
||||
.expect("Failed to send values");
|
||||
sender.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,8 +51,8 @@ impl ExtruderV2 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_live_values(&mut self) {
|
||||
let live_values = LiveValuesEvent {
|
||||
pub fn get_live_values(&mut self) -> LiveValuesEvent {
|
||||
LiveValuesEvent {
|
||||
motor_status: self.motor_status.clone(),
|
||||
pressure: self.pressure,
|
||||
nozzle_temperature: self.nozzle_temperature,
|
||||
@@ -65,9 +65,11 @@ impl ExtruderV2 {
|
||||
middle_power: self.middle_power,
|
||||
combined_power: self.combined_power,
|
||||
total_energy_kwh: self.total_energy_kwh,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let event = live_values.build();
|
||||
pub fn emit_live_values(&mut self) {
|
||||
let event = self.get_live_values().build();
|
||||
self.namespace.emit(ExtruderV2Events::LiveValues(event));
|
||||
}
|
||||
|
||||
|
||||