Compare commits

..
Author SHA1 Message Date
kraemr 5e1b74a583 removed extraneous configuration 2026-01-21 18:41:09 +01:00
kraemr ee93bb150d fixed formatting 2026-01-21 18:33:30 +01:00
kraemr 208fe5734f fixed linter error 2026-01-21 18:30:18 +01:00
kraemr 2abb6b2ce0 fixed formatting 2026-01-21 18:26:40 +01:00
kraemr 6f986d3c45 fixed potential error where a failed setup_loop kills dns 2026-01-21 18:25:04 +01:00
66 changed files with 327 additions and 4071 deletions

No files matched your search

+1 -12
View File
@@ -1,15 +1,3 @@
# `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_
@@ -17,6 +5,7 @@ _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
Generated
+156 -204
View File
@@ -127,16 +127,16 @@ dependencies = [
"futures-lite",
"parking",
"polling",
"rustix 1.1.3",
"rustix 1.1.2",
"slab",
"windows-sys 0.61.2",
]
[[package]]
name = "async-lock"
version = "3.4.2"
version = "3.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc"
dependencies = [
"event-listener",
"event-listener-strategy",
@@ -169,7 +169,7 @@ dependencies = [
"cfg-if",
"event-listener",
"futures-lite",
"rustix 1.1.3",
"rustix 1.1.2",
]
[[package]]
@@ -184,7 +184,7 @@ dependencies = [
"cfg-if",
"futures-core",
"futures-io",
"rustix 1.1.3",
"rustix 1.1.2",
"signal-hook-registry",
"slab",
"windows-sys 0.61.2",
@@ -204,7 +204,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
@@ -221,7 +221,7 @@ checksum = "99e1aca718ea7b89985790c94aad72d77533063fe00bc497bb79a7c2dae6a661"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
@@ -232,9 +232,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "axum"
version = "0.8.8"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425"
dependencies = [
"axum-core",
"axum-macros",
@@ -266,9 +266,9 @@ dependencies = [
[[package]]
name = "axum-core"
version = "0.5.6"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22"
dependencies = [
"bytes",
"futures-core",
@@ -291,7 +291,7 @@ checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
@@ -372,9 +372,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.19.1"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "bytemuck"
@@ -405,9 +405,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.2.54"
version = "1.2.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -427,9 +427,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
version = "0.4.43"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
dependencies = [
"iana-time-zone",
"num-traits",
@@ -465,18 +465,18 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.54"
version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.54"
version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
dependencies = [
"anstyle",
"clap_lex",
@@ -484,9 +484,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.7"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
[[package]]
name = "colored"
@@ -539,7 +539,7 @@ version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
@@ -580,9 +580,9 @@ dependencies = [
[[package]]
name = "crc"
version = "3.4.0"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675"
dependencies = [
"crc-catalog",
]
@@ -668,9 +668,9 @@ dependencies = [
[[package]]
name = "data-encoding"
version = "2.10.0"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
[[package]]
name = "deluxe"
@@ -682,7 +682,7 @@ dependencies = [
"deluxe-macros",
"once_cell",
"proc-macro2",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
@@ -695,7 +695,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
@@ -705,12 +705,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f87546d9c837f0b7557e47b8bd6eae52c3c223141b76aa233c345c9ab41d9117"
dependencies = [
"deluxe-core",
"heck 0.4.1",
"heck",
"if_chain",
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
@@ -721,7 +721,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
@@ -962,7 +962,7 @@ dependencies = [
"deluxe",
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
@@ -1014,14 +1014,14 @@ dependencies = [
"criterion",
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
name = "euclid"
version = "0.22.13"
version = "0.22.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63"
checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48"
dependencies = [
"num-traits",
]
@@ -1055,9 +1055,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "find-msvc-tools"
version = "0.1.8"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]]
name = "fnv"
@@ -1170,9 +1170,9 @@ checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]]
name = "h2"
version = "0.4.13"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
dependencies = [
"atomic-waker",
"bytes",
@@ -1223,9 +1223,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.16.1"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
[[package]]
name = "heapless"
@@ -1243,12 +1243,6 @@ 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"
@@ -1257,11 +1251,12 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "http"
version = "1.4.0"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
dependencies = [
"bytes",
"fnv",
"itoa",
]
@@ -1344,9 +1339,9 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.19"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f"
checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56"
dependencies = [
"bytes",
"futures-channel",
@@ -1357,7 +1352,7 @@ dependencies = [
"hyper",
"libc",
"pin-project-lite",
"socket2 0.6.2",
"socket2 0.6.1",
"tokio",
"tower-service",
"tracing",
@@ -1395,9 +1390,9 @@ checksum = "cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb"
[[package]]
name = "indexmap"
version = "2.13.0"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
dependencies = [
"equivalent",
"hashbrown",
@@ -1471,15 +1466,15 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.17"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "js-sys"
version = "0.3.85"
version = "0.3.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65"
dependencies = [
"once_cell",
"wasm-bindgen",
@@ -1493,9 +1488,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.180"
version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "libloading"
@@ -1556,9 +1551,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.29"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "mach2"
@@ -1582,14 +1577,11 @@ dependencies = [
"ethercrab",
"euclid",
"lazy_static",
"modbus",
"serde",
"serde_json",
"serialport",
"smol",
"socketioxide",
"strum",
"strum_macros 0.26.4",
"textplots",
"tonic",
"tracing",
@@ -1668,26 +1660,15 @@ checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536"
[[package]]
name = "mio"
version = "1.1.1"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
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#07d0f3396a62cac23ca171e5a783c3c1616e9be8"
dependencies = [
"crc",
"heapless",
"libc",
"strum_macros 0.27.2",
]
[[package]]
name = "nb"
version = "0.1.3"
@@ -1841,10 +1822,16 @@ dependencies = [
]
[[package]]
name = "pcap"
version = "2.4.0"
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2eecc2ddc671ec563b5b39f846556aade68a65d1afb14d8fe6b30b0457d75"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pcap"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83cdabc34a80d9ec3563694cc31423fba6bb9bab4f31a9a5d5b85f29bd6d660a"
dependencies = [
"bitflags 1.3.2",
"errno 0.2.8",
@@ -1863,9 +1850,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pest"
version = "2.8.5"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7"
checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4"
dependencies = [
"memchr",
"ucd-trie",
@@ -1873,9 +1860,9 @@ dependencies = [
[[package]]
name = "pest_derive"
version = "2.8.5"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed"
checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de"
dependencies = [
"pest",
"pest_generator",
@@ -1883,22 +1870,22 @@ dependencies = [
[[package]]
name = "pest_generator"
version = "2.8.5"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5"
checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
name = "pest_meta"
version = "2.8.5"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365"
checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a"
dependencies = [
"pest",
"sha2",
@@ -1921,7 +1908,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
@@ -1995,7 +1982,7 @@ dependencies = [
"concurrent-queue",
"hermit-abi",
"pin-project-lite",
"rustix 1.1.3",
"rustix 1.1.2",
"windows-sys 0.61.2",
]
@@ -2020,9 +2007,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.106"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
@@ -2085,9 +2072,9 @@ dependencies = [
[[package]]
name = "rand_core"
version = "0.9.5"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom",
]
@@ -2141,28 +2128,31 @@ dependencies = [
[[package]]
name = "rmp"
version = "0.8.15"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c"
checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4"
dependencies = [
"byteorder",
"num-traits",
"paste",
]
[[package]]
name = "rmp-serde"
version = "1.3.1"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155"
checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db"
dependencies = [
"byteorder",
"rmp",
"serde",
]
[[package]]
name = "rustc-demangle"
version = "0.1.27"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
[[package]]
name = "rustc-hash"
@@ -2185,9 +2175,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "1.1.3"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
dependencies = [
"bitflags 2.10.0",
"errno 0.3.14",
@@ -2204,9 +2194,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.22"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "same-file"
@@ -2231,7 +2221,7 @@ checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
@@ -2261,20 +2251,20 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
name = "serde_json"
version = "1.0.149"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
"zmij",
]
[[package]]
@@ -2391,11 +2381,10 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
version = "1.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
dependencies = [
"errno 0.3.14",
"libc",
]
@@ -2464,9 +2453,9 @@ dependencies = [
[[package]]
name = "socket2"
version = "0.6.2"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
dependencies = [
"libc",
"windows-sys 0.60.2",
@@ -2571,37 +2560,6 @@ 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"
@@ -2615,9 +2573,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.114"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2",
"quote",
@@ -2648,22 +2606,22 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.18"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
@@ -2702,15 +2660,15 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.49.0"
version = "1.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
dependencies = [
"bytes",
"libc",
"mio",
"pin-project-lite",
"socket2 0.6.2",
"socket2 0.6.1",
"tokio-macros",
"windows-sys 0.61.2",
]
@@ -2723,14 +2681,14 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
name = "tokio-stream"
version = "0.1.18"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
dependencies = [
"futures-core",
"pin-project-lite",
@@ -2751,9 +2709,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.18"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
dependencies = [
"bytes",
"futures-core",
@@ -2810,9 +2768,9 @@ dependencies = [
[[package]]
name = "tower"
version = "0.5.3"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
dependencies = [
"futures-core",
"futures-util",
@@ -2829,9 +2787,9 @@ dependencies = [
[[package]]
name = "tower-http"
version = "0.6.8"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
dependencies = [
"bitflags 2.10.0",
"bytes",
@@ -2885,7 +2843,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
@@ -2900,9 +2858,9 @@ dependencies = [
[[package]]
name = "tracing-journald"
version = "0.3.2"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d3a81ed245bfb62592b1e2bc153e77656d94ee6a0497683a65a12ccaf2438d0"
checksum = "fc0b4143302cf1022dac868d521e36e8b27691f72c84b3311750d5188ebba657"
dependencies = [
"libc",
"tracing-core",
@@ -2922,9 +2880,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.22"
version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
dependencies = [
"chrono",
"matchers",
@@ -2982,18 +2940,18 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
[[package]]
name = "unescaper"
version = "0.1.8"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4064ed685c487dbc25bd3f0e9548f2e34bab9d18cefc700f9ec2dba74ba1138e"
checksum = "c01d12e3a56a4432a8b436f293c25f4808bdf9e9f9f98f9260bba1f1bc5a1f26"
dependencies = [
"thiserror",
]
[[package]]
name = "unicase"
version = "2.9.0"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
[[package]]
name = "unicode-ident"
@@ -3077,18 +3035,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.2+wasi-0.2.9"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.108"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60"
dependencies = [
"cfg-if",
"once_cell",
@@ -3099,9 +3057,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.108"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -3109,22 +3067,22 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.108"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.108"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76"
dependencies = [
"unicode-ident",
]
@@ -3181,7 +3139,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
@@ -3192,7 +3150,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
@@ -3438,9 +3396,9 @@ dependencies = [
[[package]]
name = "wit-bindgen"
version = "0.51.0"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "wyz"
@@ -3453,26 +3411,20 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.34"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.34"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
"syn 2.0.111",
]
[[package]]
name = "zmij"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439"
+98 -40
View File
@@ -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,57 +54,115 @@ We could combine the code of the frontend and backend using [Doxius](https://dio
[Developer Documentation](./docs/developer-docs/)
[API Documentation](https://github.com/qitechgmbh/control/blob/master/docs/rest-api.md)
## Backend
-> 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
## Minimal Hardware Examples
| 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
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).
| 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) |
Available examples:
- **LED Control with EL2004** - Digital output control (simplest setup)
- **Analog Input with EL3021** - Reading analog current measurements
# QiTech Machines
| 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 | | - |
# More Docs
# Current Restructuring Efforts
- [x] [Architecture & Data Flow](./docs/architecture-overview.md)
[![](https://img.youtube.com/vi/UlbnSVIhfLI/maxresdefault.jpg)](https://www.youtube.com/live/UlbnSVIhfLI?si=ZTotC5B7gd87tUim)
*Video: Live stream of software dev meeting*
- [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)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 MiB

-80
View File
@@ -91,83 +91,3 @@ 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)
@@ -1,240 +0,0 @@
# 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
![](../assets/WAGO750_402_schematic.png)
---
#### ⚠️ 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.
![](../assets/wiring.png)
---
### 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)
![](../assets/WAGO750_402_discovery.png)
Steps:
- Click Assign on the wago module
- Select Digital Input Machine V1
![](../assets/WAGO750_402_setmachine.png)
- Enter a serial number except for 0
![](../assets/WAGO750_402_serserial.png)
- Click Write
### 5.2 Diyplaying LED
Navigate to:
Machines → DigitalInputMachine
![](../assets/WAGO750_402_machinedetected.png)
You will see this interface:
![](../assets/WAGO750_402_machinecontrol1.png)
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
![](../assets/WAGO750_402_machinecontrol2.png)
## 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)
-344
View File
@@ -1,344 +0,0 @@
# 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 |
+33 -12
View File
@@ -10326,9 +10326,9 @@
}
},
"node_modules/seroval": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.0.tgz",
"integrity": "sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.4.0.tgz",
"integrity": "sha512-BdrNXdzlofomLTiRnwJTSEAaGKyHHZkbMXIywOh7zlzp4uZnXErEwl9XZ+N1hJSNpeTtNxWvVwN0wUzAIQ4Hpg==",
"license": "MIT",
"peer": true,
"engines": {
@@ -10336,9 +10336,9 @@
}
},
"node_modules/seroval-plugins": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.0.tgz",
"integrity": "sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.4.0.tgz",
"integrity": "sha512-zir1aWzoiax6pbBVjoYVd0O1QQXgIL3eVGBMsBsNmM8Ukq90yGaWlfx0AB9dTS8GPqrOrbXn79vmItCUP9U3BQ==",
"license": "MIT",
"engines": {
"node": ">=10"
@@ -10581,15 +10581,36 @@
}
},
"node_modules/solid-js": {
"version": "1.9.11",
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.11.tgz",
"integrity": "sha512-WEJtcc5mkh/BnHA6Yrg4whlF8g6QwpmXXRg4P2ztPmcKeHHlH4+djYecBLhSpecZY2RRECXYUwIc/C2r3yzQ4Q==",
"version": "1.9.10",
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz",
"integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==",
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.1.0",
"seroval": "~1.5.0",
"seroval-plugins": "~1.5.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",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"seroval": "^1.0"
}
},
"node_modules/sonner": {
@@ -1,31 +0,0 @@
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>
);
}
@@ -1,20 +0,0 @@
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",
},
]}
/>
);
}
@@ -1,78 +0,0 @@
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);
}
@@ -1,53 +0,0 @@
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,
};
}
@@ -66,13 +66,19 @@ export type Laser1NamespaceStore = {
roundness: TimeSeries;
};
const { initialTimeSeries: diameter, insert: addDiameter } = createTimeSeries();
// Constants for time durations
const HALF_SECOND = 500;
const { initialTimeSeries: diameter, insert: addDiameter } = createTimeSeries({
sampleIntervalLong: HALF_SECOND,
});
const { initialTimeSeries: x_diameter, insert: addXDiameter } =
createTimeSeries();
createTimeSeries({ sampleIntervalLong: HALF_SECOND });
const { initialTimeSeries: y_diameter, insert: addYDiameter } =
createTimeSeries();
const { initialTimeSeries: roundness, insert: addRoundness } =
createTimeSeries();
createTimeSeries({ sampleIntervalLong: HALF_SECOND });
const { initialTimeSeries: roundness, insert: addRoundness } = createTimeSeries(
{ sampleIntervalLong: HALF_SECOND },
);
/**
* Factory function to create a new Laser1 namespace store
@@ -1,117 +0,0 @@
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>
);
}
@@ -1,154 +0,0 @@
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 { SelectionGroup, 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">
<SelectionGroup<"Off" | "On">
value={running ? "On" : "Off"}
orientation="vertical"
className="grid grid-cols-2 gap-2"
options={{
Off: {
children: "Off",
icon: "lu:CirclePause",
isActiveClassName: "bg-red-600",
className: "h-full",
},
On: {
children: "On",
icon: "lu:CirclePlay",
isActiveClassName: "bg-green-600",
className: "h-full",
},
}}
onChange={(v) => SetRunning(v == "On")}
/>
<Label label="Speed">
<div className="flex flex-row flex-wrap gap-4">
<EditValue
title="Speed"
value={frequency_target / 14.3}
unit="rpm"
step={0.1}
min={0}
max={3.5}
renderValue={(value) => value.toFixed(1)}
onChange={(val) => {
SetFrequencyTarget(val * 14.3);
}}
defaultValue={2}
/>
<EditValue
h-full v-full
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}
/>
</div>
</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>
);
}
@@ -1,168 +0,0 @@
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>
);
}
@@ -1,12 +0,0 @@
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>
);
}
@@ -1,50 +0,0 @@
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",
// },
]}
/>
);
}
@@ -1,77 +0,0 @@
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)}
/>
);
}
@@ -1,219 +0,0 @@
/**
* @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);
}
@@ -1,8 +0,0 @@
import { z } from "zod";
export const liveValuesEventDataSchema = z.object({
frequency: z.number(),
temperature: z.number(),
voltage: z.number(),
current: z.number(),
});
@@ -1,15 +0,0 @@
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(),
}),
});
@@ -1,185 +0,0 @@
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 };
}
-25
View File
@@ -302,18 +302,6 @@ 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",
@@ -474,17 +462,6 @@ 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",
@@ -585,7 +562,6 @@ export const machineProperties: MachineProperties[] = [
extruder3,
extruder2,
laser1,
pelletizer,
mock1,
buffer1,
wagoPower1,
@@ -593,7 +569,6 @@ export const machineProperties: MachineProperties[] = [
testmachine,
analogInputTestMachine,
wagoAiTestMachine,
digitalInputTestMachine,
ip20TestMachine,
];
-66
View File
@@ -43,12 +43,6 @@ 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";
@@ -67,10 +61,6 @@ import { AnalogInputTestMachine } from "@/machines/analoginputtestmachine/Analog
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";
@@ -124,18 +114,6 @@ export const wagoAiTestMachineControlRoute = createRoute({
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",
@@ -291,38 +269,6 @@ 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",
@@ -508,14 +454,6 @@ export const rootTree = RootRoute.addChildren([
laser1GraphsRoute,
laser1PresetsRoute,
]),
pelletizer1SerialRoute.addChildren([
pelletizer1ControlRoute,
pelletizer1GraphsRoute,
pelletizer1ConfigRoute,
pelletizer1PresetsRoute,
]),
testMachineSerialRoute.addChildren([testMachineControlRoute]),
analogInputTestMachineSerialRoute.addChildren([
@@ -524,10 +462,6 @@ export const rootTree = RootRoute.addChildren([
wagoAiTestMachineSerialRoute.addChildren([wagoAiTestMachineControlRoute]),
digitalInputTestMachineSerialRoute.addChildren([
digitalInputTestMachineControlRoute,
]),
ip20TestMachineSerialRoute.addChildren([ip20TestMachineControlRoute]),
aquapath1SerialRoute.addChildren([
+7 -25
View File
@@ -15,13 +15,7 @@ import {
machineProperties,
VENDOR_QITECH,
} from "@/machines/properties";
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -156,15 +150,6 @@ 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({
@@ -202,7 +187,12 @@ export function DeviceEeepromDialogContent({ device, setOpen }: ContentProps) {
// Position numpad once when it opens
useEffect(() => {
updateNumpadPosition();
if (!numpadOpen || !dialogRef.current) return;
const rect = dialogRef.current.getBoundingClientRect();
setNumpadPosition({
left: rect.right + 20,
top: rect.top + rect.height / 2,
});
}, [numpadOpen]);
// Close numpad when clicking outside input/numpad
@@ -236,14 +226,6 @@ 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 = () => {
-8
View File
@@ -7,7 +7,6 @@ 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},
@@ -246,10 +245,6 @@ impl Wago750_354 {
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
@@ -299,9 +294,6 @@ impl Wago750_354 {
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,9 +430,6 @@ 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,6 +1,5 @@
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;
@@ -1,194 +0,0 @@
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);
-7
View File
@@ -31,13 +31,6 @@ 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", features = ["linux-rtu-transport"] }
# enum stuff
strum = "0.26"
strum_macros = "0.26"
[dev-dependencies]
approx = "0.5.1"
textplots = "0.8.7"
@@ -1,46 +0,0 @@
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();
}
}
}
}
@@ -1,77 +0,0 @@
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()
}
}
@@ -1,73 +0,0 @@
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));
}
}
@@ -1,87 +0,0 @@
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 &params.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)
})
}
}
-4
View File
@@ -21,12 +21,10 @@ pub mod analog_input_test_machine;
pub mod aquapath1;
#[cfg(not(feature = "mock-machine"))]
pub mod buffer1;
pub mod digital_input_test_machine;
pub mod extruder1;
pub mod extruder2;
pub mod ip20_test_machine;
pub mod laser;
pub mod pelletizer;
pub mod machine_identification;
pub mod mock;
pub mod registry;
@@ -40,7 +38,6 @@ pub const VENDOR_QITECH: u16 = 0x0001;
pub const MACHINE_WINDER_V1: u16 = 0x0002;
pub const MACHINE_EXTRUDER_V1: u16 = 0x0004;
pub const MACHINE_LASER_V1: u16 = 0x0006;
pub const MACHINE_PELLETIZER: u16 = 0x000B;
pub const MACHINE_MOCK: u16 = 0x0007;
pub const MACHINE_BUFFER_V1: u16 = 0x0008;
pub const MACHINE_AQUAPATH_V1: u16 = 0x0009;
@@ -50,7 +47,6 @@ pub const TEST_MACHINE: u16 = 0x0033;
pub const IP20_TEST_MACHINE: u16 = 0x0034;
pub const ANALOG_INPUT_TEST_MACHINE: u16 = 0x0035;
pub const WAGO_AI_TEST_MACHINE: u16 = 0x0036;
pub const DIGITAL_INPUT_TEST_MACHINE: u16 = 0x0040;
use serde_json::Value;
use smol::lock::RwLock;
-2
View File
@@ -54,7 +54,6 @@ impl MachineIdentification {
x if x == MACHINE_WINDER_V1 => "winder_v1".to_string(),
x if x == MACHINE_EXTRUDER_V1 => "extruder_v1".to_string(),
x if x == MACHINE_LASER_V1 => "laser_v1".to_string(),
x if x == MACHINE_PELLETIZER => "pelletizer_v1".to_string(),
x if x == MACHINE_MOCK => "mock".to_string(),
x if x == MACHINE_AQUAPATH_V1 => "aquapath_v1".to_string(),
x if x == MACHINE_BUFFER_V1 => "buffer_v1".to_string(),
@@ -182,7 +181,6 @@ use crate::MACHINE_BUFFER_V1;
use crate::MACHINE_EXTRUDER_V1;
use crate::MACHINE_EXTRUDER_V2;
use crate::MACHINE_LASER_V1;
use crate::MACHINE_PELLETIZER;
use crate::MACHINE_MOCK;
use crate::MACHINE_WAGO_POWER_V1;
use crate::MACHINE_WINDER_V1;
-126
View File
@@ -1,126 +0,0 @@
use std::time::{Duration, Instant};
use serde::de::VariantAccess;
use units::frequency::hertz;
use crate::MachineAct;
use crate::MachineMessage;
use crate::MachineValues;
use super::Pelletizer;
impl MachineAct for Pelletizer
{
fn act(&mut self, now: Instant)
{
if let Ok(msg) = self.api_receiver.try_recv()
{
self.act_machine_message(msg);
};
let should_emit =
now.duration_since(self.last_measurement_emit)
> Duration::from_secs_f64(1.0 / 12.0);
let mut mutated: bool = false;
let mut inverter_snapshot_id: u64 = 0;
{
let mut inverter = smol::block_on(async {
self.inverter.write().await
});
if let Some(value) = self.mutation_request.running.take()
{
inverter.set_running(value);
mutated = true;
}
if let Some(value) = self.mutation_request.direction.take()
{
inverter.set_direction(value);
mutated = true;
}
if let Some(value) = self.mutation_request.frequency.take()
{
inverter.set_frequency_target((value * 10.0) as u16);
mutated = true;
}
if let Some(value) = self.mutation_request.accleration_level.take()
{
inverter.set_acceleration_level(value);
mutated = true;
}
if let Some(value) = self.mutation_request.decleration_level.take()
{
inverter.set_deceleration_level(value);
mutated = true;
}
if should_emit { inverter.refresh_status(); }
if mutated { }
inverter.update();
inverter_snapshot_id = inverter.config.snapshot_id;
} // drop lock
if should_emit
{
if self.inverter_snapshot_id != inverter_snapshot_id
{
self.emit_state();
self.inverter_snapshot_id = inverter_snapshot_id;
}
self.emit_live_values();
self.last_measurement_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 => match &mut self.namespace.namespace
{
Some(namespace) =>
{
tracing::info!("UnsubscribeNamespace");
namespace.socket_queue_tx.close();
namespace.sockets.clear();
namespace.events.clear();
}
None => todo!(),
},
MachineMessage::HttpApiJsonRequest(value) =>
{
use crate::MachineApi;
let _res = self.api_mutate(value);
}
MachineMessage::ConnectToMachine(_machine_connection) => {}
MachineMessage::DisconnectMachine(_machine_connection) => {}
MachineMessage::RequestValues(sender) =>
{
tracing::error!("REQUESTED VALUES");
let state = serde_json::to_value(self.create_state_event()).expect("Failed to serialize state");
let live_values = serde_json::to_value(self.create_live_values_event()).expect("Failed to serialize live values");
sender.send_blocking(MachineValues{ state, live_values }).expect("Failed to send values");
sender.close();
}
}
}
}
-156
View File
@@ -1,156 +0,0 @@
use crate::{MachineApi, MachineMessage};
use super::Pelletizer;
use control_core::socketio::{
event::{Event, GenericEvent},
namespace::{
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_first_and_last_event,
},
};
use control_core_derive::BuildEvent;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use smol::channel::Sender;
use std::sync::Arc;
use tracing::instrument;
#[derive(Serialize, Debug, Clone)]
pub struct LiveValuesEvent
{
pub inverter_values: InverterLiveValues
}
#[derive(Serialize, Debug, Clone)]
pub struct InverterLiveValues
{
pub frequency: f64,
pub temperature: f64,
pub voltage: f64,
pub current: f64,
}
impl LiveValuesEvent {
pub fn build(&self) -> Event<Self> {
Event::new("LiveValuesEvent", self.clone())
}
}
#[derive(Serialize, Debug, Clone, PartialEq, BuildEvent)]
pub struct StateEvent {
pub is_default_state: bool,
pub inverter_state: InverterState,
}
impl StateEvent {
pub fn build(&self) -> Event<Self> {
Event::new("StateEvent", self.clone())
}
}
#[derive(Serialize, Debug, Clone, PartialEq)]
pub struct InverterState
{
pub running: bool,
pub direction: bool,
pub frequency_target: f64,
pub acceleration_level: u16,
pub deceleration_level: u16,
}
#[derive(Serialize, Debug, Clone, PartialEq)]
pub struct RotationState
{
pub forward: bool,
}
pub enum PelletMachineEvents {
LiveValues(Event<LiveValuesEvent>),
State(Event<StateEvent>),
}
#[derive(Debug)]
pub struct PelletMachineNamespace {
pub namespace: Option<Namespace>,
}
#[derive(Debug, Deserialize, Serialize)]
/// Mutation for controlling the Pellet machine
enum Mutation
{
SetRunning(bool),
SetDirection(bool),
SetFrequencyTarget(f64),
SetAccelerationLevel(u16),
SetDecelerationLevel(u16),
}
impl MachineApi for Pelletizer {
fn api_get_sender(&self) -> Sender<MachineMessage> {
self.api_sender.clone()
}
fn api_mutate(&mut self, request_body: Value) -> Result<(), anyhow::Error>
{
let mutation: Mutation = serde_json::from_value(request_body)?;
match mutation
{
Mutation::SetRunning(running) =>
{
self.mutation_request.running = Some(running);
}
Mutation::SetDirection(forward) =>
{
self.mutation_request.direction = Some(forward);
}
Mutation::SetFrequencyTarget(frequency) =>
{
self.mutation_request.frequency = Some(frequency);
}
Mutation::SetAccelerationLevel(level) =>
{
self.mutation_request.accleration_level = Some(level);
}
Mutation::SetDecelerationLevel(level) =>
{
self.mutation_request.decleration_level = Some(level);
}
}
Ok(())
}
fn api_event_namespace(&mut self) -> Option<Namespace> {
self.namespace.namespace.clone()
}
}
//TODO; rename NamespaceCacheingLogic to NamespaceCachingLogic
impl NamespaceCacheingLogic<PelletMachineEvents> for PelletMachineNamespace {
#[instrument(skip_all)]
fn emit(&mut self, events: PelletMachineEvents) {
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<Self> for PelletMachineEvents {
fn event_value(&self) -> GenericEvent {
match self {
Self::LiveValues(event) => event.into(),
Self::State(event) => event.into(),
}
}
fn event_cache_fn(&self) -> CacheFn {
let cache_first_and_last = cache_first_and_last_event();
match self {
Self::LiveValues(_) => cache_first_and_last,
Self::State(_) => cache_first_and_last,
}
}
}
-71
View File
@@ -1,71 +0,0 @@
use crate::pelletizer::Pelletizer;
use crate::pelletizer::api::{InverterLiveValues, InverterState, LiveValuesEvent, PelletMachineEvents, StateEvent};
use control_core::socketio::event::BuildEvent;
use control_core::socketio::namespace::NamespaceCacheingLogic;
impl Pelletizer
{
pub fn emit_state(&mut self)
{
let event = self.create_state_event().build();
self.namespace.emit(PelletMachineEvents::State(event));
self.emitted_default_state = true;
}
pub fn emit_live_values(&mut self)
{
let event = self.create_live_values_event().build();
self.namespace.emit(PelletMachineEvents::LiveValues(event));
}
pub fn create_state_event(&self) -> StateEvent
{
let inverter = smol::block_on(async {
self.inverter.read().await
});
StateEvent
{
is_default_state: !self.emitted_default_state,
inverter_state: InverterState {
running: inverter.config.running,
direction: inverter.config.direction,
frequency_target: (inverter.config.frequency_target as f64) / 10.0,
acceleration_level: inverter.config.acceleration_level,
deceleration_level: inverter.config.deceleration_level,
},
}
}
pub fn create_live_values_event(&self) -> LiveValuesEvent
{
let inverter = smol::block_on(async {
self.inverter.read().await
});
if let Some(status) = inverter.status
{
return LiveValuesEvent {
inverter_values: InverterLiveValues {
voltage: status.voltage.value,
current: status.current.value,
temperature: status.temperature.value,
frequency: status.frequency.value,
}
};
}
LiveValuesEvent {
inverter_values: InverterLiveValues {
voltage: 0.0,
current: 0.0,
temperature: 0.0,
frequency: 0.0,
}
}
}
}
-116
View File
@@ -1,116 +0,0 @@
//use serde::{Deserialize, Serialize};
use crate::{
MACHINE_PELLETIZER, MachineMessage, VENDOR_QITECH,
machine_identification::{MachineIdentification, MachineIdentificationUnique},
};
use smol::{
channel::{Receiver, Sender},
lock::RwLock,
};
use socketioxide::extract::SocketRef;
use crate::{AsyncThreadMessage, serial::devices::us_3202510::US3202510};
use std::{sync::Arc, time::Instant};
pub mod act;
pub mod api;
pub mod new;
pub mod emit;
use crate::Machine;
use api::{PelletMachineNamespace};
#[derive(Debug)]
pub struct Pelletizer
{
// stuff for every machine
api_receiver: Receiver<MachineMessage>,
api_sender: Sender<MachineMessage>,
main_sender: Option<Sender<AsyncThreadMessage>>,
machine_identification_unique: MachineIdentificationUnique,
emitted_default_state: bool,
// socketio
namespace: PelletMachineNamespace,
last_measurement_emit: Instant,
// machine specific
inverter: Arc<RwLock<US3202510>>,
inverter_snapshot_id: u64,
mutation_request: MutationRequests
}
#[derive(Debug)]
pub struct MutationRequests
{
running: Option<bool>,
direction: Option<bool>,
frequency: Option<f64>,
accleration_level: Option<u16>,
decleration_level: Option<u16>,
}
impl Machine for Pelletizer
{
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 PelletMachineNamespace
{
pub async fn disconnect_all(&self)
{
for socket in self.connected_sockets().await
{
let _ = socket.disconnect();
}
}
async fn connected_sockets(&self) -> Vec<SocketRef>
{
if self.namespace.is_none()
{ vec![] }
else
{ self.namespace.clone().unwrap().sockets.clone() }
}
}
impl Drop for Pelletizer
{
fn drop(&mut self)
{
tracing::info!(
"[Pelletizer::{:?}] Dropping machine and disconnecting clients...",
self.machine_identification_unique
);
smol::block_on(self.namespace.disconnect_all());
}
}
impl Pelletizer
{
pub const MACHINE_IDENTIFICATION: MachineIdentification = MachineIdentification {
vendor: VENDOR_QITECH,
machine: MACHINE_PELLETIZER,
};
pub fn update(&mut self)
{
//let laser_data = smol::block_on(async { self.inverter.read().await.get_data().await });
}
}
-61
View File
@@ -1,61 +0,0 @@
use std::time::Instant;
use crate::pelletizer::MutationRequests;
use crate::serial::devices::us_3202510::US3202510;
use crate::serial::{registry::SERIAL_DEVICE_REGISTRY};
use crate::{MachineNewHardware, MachineNewTrait};
use super::{Pelletizer, api::PelletMachineNamespace};
use anyhow::Error;
impl MachineNewTrait for Pelletizer
{
fn new(params: &crate::MachineNewParams<'_, '_, '_, '_, '_, '_, '_>) -> Result<Self, Error>
where
Self: Sized,
{
let hardware_serial = match params.hardware
{
MachineNewHardware::Serial(serial) => *serial,
_ => return Err(Error::msg("Invalid hardware type for Pelletizer")),
};
let inverter = match smol::block_on(
SERIAL_DEVICE_REGISTRY.downcast_arc_rwlock::<US3202510>(hardware_serial.device.clone()),
) {
Ok(inverter) => inverter,
Err(_) => return Err(Error::msg("Failed to downcast to US3202510")),
};
let (sender, receiver) = smol::channel::unbounded();
let pelletizer = Self {
main_sender: params.main_thread_channel.clone(),
api_receiver: receiver,
api_sender: sender,
machine_identification_unique: params.get_machine_identification_unique(),
namespace: PelletMachineNamespace {
namespace: params.namespace.clone(),
},
emitted_default_state: false,
last_measurement_emit: Instant::now(),
inverter,
inverter_snapshot_id: 0,
mutation_request: MutationRequests {
running: None,
direction: None,
frequency: None,
accleration_level: None,
decleration_level: None,
}
};
Ok(pelletizer)
}
}
+1 -7
View File
@@ -1,5 +1,4 @@
use crate::analog_input_test_machine::AnalogInputTestMachine;
use crate::digital_input_test_machine::DigitalInputTestMachine;
use crate::ip20_test_machine::IP20TestMachine;
use crate::wago_ai_test_machine::WagoAiTestMachine;
#[cfg(feature = "mock-machine")]
@@ -17,7 +16,7 @@ use crate::extruder1::ExtruderV2;
#[cfg(not(feature = "mock-machine"))]
use crate::{
aquapath1::AquaPathV1, buffer1::BufferV1, extruder2::ExtruderV3, laser::LaserMachine,
pelletizer::Pelletizer, winder2::Winder2,
winder2::Winder2,
};
use crate::test_machine::TestMachine;
@@ -118,9 +117,6 @@ lazy_static! {
#[cfg(not(feature = "mock-machine"))]
mc.register::<LaserMachine>(LaserMachine::MACHINE_IDENTIFICATION);
#[cfg(not(feature = "mock-machine"))]
mc.register::<Pelletizer>(Pelletizer::MACHINE_IDENTIFICATION);
#[cfg(not(feature = "mock-machine"))]
mc.register::<BufferV1>(BufferV1::MACHINE_IDENTIFICATION);
@@ -135,8 +131,6 @@ lazy_static! {
mc.register::<WagoAiTestMachine>(WagoAiTestMachine::MACHINE_IDENTIFICATION);
mc.register::<DigitalInputTestMachine>(DigitalInputTestMachine::MACHINE_IDENTIFICATION);
mc
};
}
-1
View File
@@ -1,7 +1,6 @@
#[cfg(feature = "mock-machine")]
pub mod extruder_mock;
pub mod laser;
pub mod us_3202510;
#[cfg(feature = "mock-machine")]
pub mod mock;
#[cfg(feature = "mock-machine")]
@@ -1,279 +0,0 @@
use modbus::{RequestResult, rtu::{DispatchError}};
// external deps
use serde::{Deserialize, Serialize};
// use modbus::ExceptionCode
// internal deps
pub use request::Request;
use units::{Frequency, electric_current::ampere, electric_potential::volt, frequency::hertz, thermodynamic_temperature::{degree_celsius, kelvin}};
// modules
mod register;
mod request;
mod serial_device;
mod transport;
use crate::serial::devices::us_3202510::{register::{HoldingRegister, InputRegister}, transport::{CustomTransport, EntryContext}};
type ModbusInterface = modbus::rtu::Interface<CustomTransport, EntryContext, 25>;
#[derive(Debug)]
pub struct US3202510
{
pub path: String,
pub config: Config,
pub status: Option<Status>,
failed_attempts: u8,
interface: ModbusInterface,
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Config
{
pub snapshot_id: u64,
pub running: bool,
pub direction: bool,
pub frequency_target: u16, // 0 - 99hz
pub frequency_min: u16,
pub frequency_max: u16,
pub acceleration_level: u16, // 1 - 15
pub deceleration_level: u16, // 1 - 15
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Status
{
pub frequency: units::Frequency, // 1 - 99hz
pub voltage: units::ElectricPotential,
pub current: units::ElectricCurrent,
pub temperature: units::ThermodynamicTemperature,
pub operation_status: OperationStatus,
}
#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Direction
{
#[default]
Stopped,
Forward,
Reverse,
}
#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum OperationStatus
{
#[default]
Idle,
Running,
Fault,
}
impl US3202510
{
pub fn update(&mut self)
{
if self.config.snapshot_id == 0
{
self.refresh_config();
self.config.snapshot_id += 1;
}
match self.interface.await_result()
{
Ok(result) =>
{
self.handle_result(result);
},
Err(e) =>
{
match e
{
modbus::rtu::ReceiveError::NoPendingRequest => {},
e =>
{
tracing::error!("Error reciving result: {:?}", e);
}
}
},
}
if !self.interface.has_pending_request()
{
match self.interface.dispatch_next_request()
{
Ok(_) => {},
Err(e) =>
{
match e
{
DispatchError::RequestPending => {},
DispatchError::QueueEmpty => {},
DispatchError::Transport(err) =>
{
tracing::error!("Error sending request: {}", err);
},
DispatchError::BridgeDropped =>
{
tracing::error!("Bridge dropped");
},
}
},
}
}
}
fn queue_request(&mut self, request: Request)
{
let data = request.to_interface_request();
match self.interface.queue_request(data.type_id, data.payload, data.priority)
{
Ok(_) => {},
Err(x) =>
{
match x
{
modbus::QueueItemError::QueueFull => { tracing::error!("QueueFull!"); },
modbus::QueueItemError::DuplicateItem => { tracing::error!("DuplicateItem!"); },
}
},
}
}
pub fn refresh_status(&mut self)
{
self.queue_request(Request::RefreshStatus);
}
pub fn refresh_config(&mut self)
{
self.queue_request(Request::RefreshState);
}
pub fn set_running(&mut self, running: bool)
{
if self.config.running == running { return; }
match running
{
true =>
{
let value: u16 = match self.config.direction
{
true => 1,
false => 3,
};
self.queue_request(Request::SetRotationState(value));
},
false => { self.queue_request(Request::SetRotationState(2)); },
}
self.config.running = running;
}
pub fn set_direction(&mut self, direction: bool)
{
if self.config.direction != direction && self.config.running
{
let value: u16 = if direction { 1 } else { 3 };
self.queue_request(Request::SetRotationState(value));
}
self.config.direction = direction;
}
pub fn set_frequency_target(&mut self, frequency: u16)
{
self.config.frequency_target = frequency.min(990);
self.queue_request(Request::SetFrequencyTarget(self.config.frequency_target));
}
pub fn set_acceleration_level(&mut self, acceleration_level: u16)
{
self.config.acceleration_level = acceleration_level.clamp(1, 15);
self.queue_request(Request::SetAccelerationLevel(self.config.acceleration_level));
}
pub fn set_deceleration_level(&mut self, deceleration_level: u16)
{
self.config.deceleration_level = deceleration_level.clamp(1, 15);
self.queue_request(Request::SetDecelerationLevel(self.config.deceleration_level));
}
fn handle_result(&mut self, response: RequestResult)
{
match response
{
RequestResult::ReadHoldingRegisters(data) =>
{
let frequency_target: u16 =
*data.result.get(0)
.unwrap_or(&0);
let _run_command: u16 =
*data.result.get(1)
.unwrap_or(&0);
let acceleration_level: u16 =
*data.result.get(2)
.unwrap_or(&0);
let deceleration_level: u16 =
*data.result.get(3)
.unwrap_or(&0);
self.config.running = _run_command != 2;
self.config.frequency_target = frequency_target;
self.config.acceleration_level = acceleration_level;
self.config.deceleration_level = deceleration_level;
self.config.snapshot_id += 1;
},
RequestResult::ReadInputRegisters(data) =>
{
let frequency: u16 = *data.result.get(InputRegister::CurrentFrequency as usize).unwrap_or(&0);
let voltage: u16 = *data.result.get(InputRegister::BusVoltage as usize).unwrap_or(&0);
let current: u16 = *data.result.get(InputRegister::LineCurrent as usize).unwrap_or(&0);
let temperature: u16 = *data.result.get(InputRegister::DriveTemperature as usize).unwrap_or(&0);
let operation_status: u16 = *data.result.get(InputRegister::SystemStatus as usize).unwrap_or(&0);
self.status = Some(Status {
frequency: units::Frequency::new::<hertz>((frequency as f64) / 10.0),
voltage: units::ElectricPotential::new::<volt>((voltage as f64) / 10.0),
current: units::ElectricCurrent::new::<ampere>((current as f64) / 10.0),
temperature: units::ThermodynamicTemperature::new::<kelvin>((temperature as f64)),
operation_status: if operation_status == 0 { OperationStatus::Idle } else if operation_status == 1 {OperationStatus::Running } else { OperationStatus::Fault },
});
},
RequestResult::PresetHoldingRegister(request_result_data) =>
{
tracing::error!("PresetHoldingRegister: WORKED");
self.config.snapshot_id += 1;
},
RequestResult::Exception(request_result_data) =>
{
tracing::error!("Received exception: {:?}", request_result_data.result);
},
}
}
}
#[cfg(test)]
mod tests
{
#[test]
fn test_requests() {}
}
@@ -1,83 +0,0 @@
use strum_macros::{EnumCount};
#[derive(Debug, Clone, Copy, EnumCount)]
pub enum HoldingRegister
{
/// Register 0x0002
SetFrequency = 0x2,
/// Register 0x0003
#[allow(dead_code)]
RunCommand = 0x3,
/// Register 0x0004
#[allow(dead_code)]
AccelerationTime = 0x4,
/// Register 0x0005
#[allow(dead_code)]
DecelerationTime = 0x5,
}
#[derive(Debug, Clone, Copy, EnumCount)]
pub enum InputRegister
{
/// Register 0x0008
#[allow(dead_code)]
BusVoltage,
/// Register 0x0009
#[allow(dead_code)]
LineCurrent,
/// Register 0x000A
#[allow(dead_code)]
DriveTemperature,
/// Register 0x000B
#[allow(dead_code)]
SystemStatus,
/// Register 0x000C
#[allow(dead_code)]
ErrorCode,
/// Register 0x000D
#[allow(dead_code)]
CurrentFrequency,
}
impl HoldingRegister
{
pub const OFFSET: u16 = 0x2;
pub const fn address(self) -> u16
{
match self
{
Self::SetFrequency => Self::OFFSET, // 0x0002
Self::RunCommand => Self::OFFSET + 1, // 0x0003
Self::AccelerationTime => Self::OFFSET + 2, // 0x0004
Self::DecelerationTime => Self::OFFSET + 3, // 0x0005
}
}
}
impl InputRegister
{
pub const OFFSET: u16 = 0x8;
#[allow(dead_code)]
pub const fn address(self) -> u16
{
match self
{
Self::BusVoltage => Self::OFFSET, // 0x0008
Self::LineCurrent => Self::OFFSET + 1, // 0x0009
Self::DriveTemperature => Self::OFFSET + 2, // 0x000A
Self::SystemStatus => Self::OFFSET + 3, // 0x000B
Self::ErrorCode => Self::OFFSET + 4, // 0x000C
Self::CurrentFrequency => Self::OFFSET + 5, // 0x000D
}
}
}
@@ -1,117 +0,0 @@
use modbus::request::WriteRegister;
use strum::EnumCount;
use strum_macros::{EnumCount};
use crate::serial::devices::us_3202510::register::HoldingRegister;
use crate::serial::devices::us_3202510::register::InputRegister;
use modbus::Request as InterfaceRequest;
use modbus::request::ReadRegisters;
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
pub enum Request
{
RefreshStatus,
RefreshState,
SetRotationState(u16),
SetFrequencyTarget(u16),
SetAccelerationLevel(u16),
SetDecelerationLevel(u16),
}
#[derive(Debug)]
pub struct RequestData
{
pub type_id: u32,
pub priority: u32,
pub payload: InterfaceRequest,
}
impl Request
{
pub fn to_interface_request(&self) -> RequestData
{
match self
{
Request::RefreshStatus =>
{
let payload =
InterfaceRequest::ReadInputRegisters(
ReadRegisters
{
start_address: InputRegister::OFFSET,
quantity: InputRegister::COUNT as u16,
}
);
RequestData { type_id: 0, priority: 10, payload }
}
Request::RefreshState =>
{
let payload =
InterfaceRequest::ReadHoldingRegisters(
ReadRegisters
{
start_address: HoldingRegister::OFFSET,
quantity: HoldingRegister::COUNT as u16,
}
);
RequestData { type_id: 1, priority: 20, payload }
}
Request::SetRotationState(value) =>
{
let payload =
InterfaceRequest::PresetHoldingRegister(
WriteRegister
{
address: HoldingRegister::RunCommand as u16,
value: *value,
}
);
RequestData { type_id: 2, priority: 100, payload }
},
Request::SetFrequencyTarget(value) =>
{
let payload =
InterfaceRequest::PresetHoldingRegister(
WriteRegister
{
address: HoldingRegister::SetFrequency as u16,
value: *value,
}
);
RequestData { type_id: 3, priority: 50, payload }
}
Request::SetAccelerationLevel(value) =>
{
let payload =
InterfaceRequest::PresetHoldingRegister(
WriteRegister
{
address: HoldingRegister::AccelerationTime as u16,
value: *value,
}
);
RequestData { type_id: 4, priority: 30, payload }
},
Request::SetDecelerationLevel(value) =>
{
let payload =
InterfaceRequest::PresetHoldingRegister(
WriteRegister
{
address: HoldingRegister::DecelerationTime as u16,
value: *value,
}
);
RequestData { type_id: 5, priority: 30, payload }
},
}
}
}
@@ -1,114 +0,0 @@
use anyhow::anyhow;
use modbus::{Request, request::ReadRegisters};
use smol::lock::RwLock;
use control_core::{
helpers::hashing::{byte_folding_u16, hash_djb2},
};
use units::{ConstZero, Frequency};
use super::US3202510;
use crate::{
MACHINE_PELLETIZER,
SerialDevice,
SerialDeviceNew,
SerialDeviceNewParams,
VENDOR_QITECH,
machine_identification::{
DeviceHardwareIdentification, DeviceHardwareIdentificationSerial, DeviceIdentification, DeviceMachineIdentification, MachineIdentification, MachineIdentificationUnique
}, serial::devices::us_3202510::{Config, ModbusInterface, request, transport::CustomTransport}
};
use serialport::{DataBits, FlowControl, Parity, StopBits};
use std::{sync::Arc, time::Duration};
impl SerialDevice for US3202510 {}
impl SerialDeviceNew for US3202510
{
fn new_serial(
params: &SerialDeviceNewParams,
) -> Result<(DeviceIdentification, Arc<RwLock<Self>>), anyhow::Error>
{
let hash = hash_djb2(params.path.as_bytes());
let serial = byte_folding_u16(&hash.to_le_bytes());
let device_identification = DeviceIdentification {
device_machine_identification: Some(DeviceMachineIdentification {
machine_identification_unique: MachineIdentificationUnique {
machine_identification: MachineIdentification {
vendor: VENDOR_QITECH,
machine: MACHINE_PELLETIZER,
},
serial,
},
role: 0,
}),
device_hardware_identification: DeviceHardwareIdentification::Serial(
DeviceHardwareIdentificationSerial {
path: params.path.clone(),
},
),
};
// let interface_config = modbus_rtu::Config {
// slave_id: 1,
// path: params.path.clone(),
// data_bits: DataBits::Eight,
// parity: Parity::None,
// stop_bits: StopBits::One,
// flow_control: FlowControl::None,
// timeout: Duration::from_millis(1000),
// baudrate: 9600,
// machine_operation_delay: Duration::from_millis(100),
// };
let port =
serialport::new(params.path.clone(), 9600)
.data_bits(DataBits::Eight)
.parity(Parity::None)
.flow_control(FlowControl::None)
.baud_rate(9600)
.stop_bits(StopBits::One)
.timeout(Duration::from_millis(500))
.open()
.expect("");
let transport = CustomTransport::new(port, 1);
let interface = ModbusInterface::new(transport);
// let request: [u8; 8] = [
// 0x01, // slave id
// 0x04, // Read Input Registers
// 0x00, 0x08, // start address
// 0x00, 0x06, // quantity
// 0xB1, 0xCA, // CRC (lo, hi)
// ];
let _self = Arc::new(RwLock::new(Self {
path: params.path.clone(),
config: Config {
running: false,
direction: true,
frequency_target: 0,
frequency_max: 50,
frequency_min: 0,
acceleration_level: 7,
deceleration_level: 7,
snapshot_id: 0,
},
status: None,
failed_attempts: 0,
interface,
}));
Ok((device_identification, _self))
}
}
@@ -1,178 +0,0 @@
use std::io;
use std::time::{Duration, Instant};
use modbus::WithContext;
use modbus::rtu::{FrameParseError, Transport as TransportTrait};
use modbus::rtu::{DispatchError, ReceiveError};
use modbus::rtu::Frame;
#[derive(Debug)]
pub struct CustomTransport
{
port: std::sync::Mutex<Box<dyn serialport::SerialPort>>,
slave_id: u8,
}
// #[derive(Debug, Clone, PartialEq)]
// pub struct EntryContext
// {
// type_id: u32
// }
pub type EntryContext = u32;
impl CustomTransport
{
pub fn new(port: Box<dyn serialport::SerialPort>, slave_id: u8) -> Self
{
let p = std::sync::Mutex::new(port);
Self { port: p ,slave_id }
}
}
// implement InterfaceTransport for your RTU frames
impl TransportTrait<EntryContext> for CustomTransport
{
type Error = io::Error;
fn slave_id(&self) -> u8 { self.slave_id }
fn try_send(
&mut self,
item: WithContext<Frame, EntryContext>
) -> Result<(), DispatchError<Self::Error>>
{
let mut port = self.port.lock().unwrap();
match port.write_all(item.payload.bytes())
{
Ok(_) => {},
Err(e) =>
{
tracing::error!("Error: try_send: {:?}", e);
return Err(DispatchError::BridgeDropped);
},
}
match port.flush()
{
Ok(_) => {},
Err(e) =>
{
tracing::error!("Error: try_send: {:?}", e);
return Err(DispatchError::BridgeDropped);
},
}
Ok(())
}
fn try_recv(
&mut self
) -> Result<Option<Frame>, ReceiveError<Self::Error>>
{
let mut port = self.port.lock().unwrap();
let mut buf = [0u8; 256];
match port.read(&mut buf)
{
Ok(0) => Ok(None),
Ok(n) =>
{
match Frame::from_bytes(&buf[..n])
{
Ok(frame) => Ok(Some(frame)),
Err(e) =>
{
Err(ReceiveError::ParseError(e))
},
}
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(None),
Err(e) => Err(ReceiveError::Transport(e)),
}
}
fn send(
&mut self,
item: WithContext<Frame, EntryContext>
) -> Result<(), DispatchError<Self::Error>>
{
let mut port = self.port.lock().unwrap();
match port.write_all(item.payload.bytes())
{
Ok(_) => {},
Err(e) =>
{
tracing::error!("Error: try_send: {:?}", e);
},
}
match port.flush()
{
Ok(_) => {},
Err(e) =>
{
tracing::error!("Error: try_send: {:?}", e);
},
}
Ok(())
}
fn recv(&mut self) -> Result<Frame, ReceiveError<Self::Error>>
{
let mut port = self.port.lock().unwrap();
let mut buf = [0u8; 256];
let mut pos = 0;
let mut now: Instant = Instant::now();
let timeout = Duration::from_secs(1);
loop
{
match port.read(&mut buf[pos..])
{
Ok(0) =>
{
return Err(ReceiveError::ParseError(FrameParseError::TooSmall(0)));
}
Ok(n) =>
{
pos += n;
if let Ok(frame) = Frame::from_bytes(&buf[..pos])
{
return Ok(frame);
}
now = Instant::now(); // reset timeout timer
}
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock =>
{
if now.elapsed() >= timeout
{
if pos > 0
{
match Frame::from_bytes(&buf[..pos])
{
Ok(frame) => { return Ok(frame); },
Err(e) => { return Err(ReceiveError::ParseError(e)); },
}
}
return Err(ReceiveError::Timeout);
}
std::thread::sleep(Duration::from_millis(150));
}
Err(e) => return Err(ReceiveError::Transport(e)),
}
}
}
}
-6
View File
@@ -3,7 +3,6 @@ use lazy_static::lazy_static;
use crate::{
SerialDevice, SerialDeviceIdentification, machine_identification::DeviceIdentification,
serial::devices::laser::Laser,
serial::devices::us_3202510::US3202510,
};
#[cfg(feature = "mock-machine")]
@@ -110,11 +109,6 @@ lazy_static! {
product_id: 0x6001,
});
sdr.register::<US3202510>(SerialDeviceIdentification {
vendor_id: 0x0403,
product_id: 0x6001,
});
// Register MockSerialDevice when mock-machine feature is enabled
#[cfg(feature = "mock-machine")]
sdr.register::<MockSerialDevice>(SerialDeviceIdentification {
+1
View File
@@ -78,6 +78,7 @@ impl MachineNewTrait for TestMachine {
let do2 = DigitalOutput::new(el2004.clone(), EL2004Port::DO2);
let do3 = DigitalOutput::new(el2004.clone(), EL2004Port::DO3);
let do4 = DigitalOutput::new(el2004.clone(), EL2004Port::DO4);
let (sender, receiver) = smol::channel::unbounded();
let mut my_test = Self {
api_receiver: receiver,
+1 -1
View File
@@ -10,7 +10,7 @@ buildNpmPackage rec {
ELECTRON_SKIP_BINARY_DOWNLOAD = 1;
makeCacheWritable = true;
npmDepsHash = "sha256-wzVjsgcZsViPOyQKlKTb5gJIeY416KyCSiheSXSRZoc=";
npmDepsHash = "sha256-UHPIoyMTzr7jkvbpZsePaNJ6s76h3R/0pcnbCkOcdYo=";
npmFlags = [ "--no-audit" "--no-fund" ];
installPhase = ''
+18 -13
View File
@@ -3,9 +3,16 @@ use crate::{
socketio::main_namespace::machines_event::MachineObj,
};
use machines::{
AsyncThreadMessage, MachineConnection, MachineNewHardware, MachineNewHardwareSerial, MachineNewParams, SerialDevice, SerialDeviceIdentification, SerialDeviceNew, SerialDeviceNewParams, laser::LaserMachine, machine_identification::{
AsyncThreadMessage, MachineConnection, MachineNewHardware, MachineNewHardwareSerial,
MachineNewParams, SerialDevice, SerialDeviceIdentification, SerialDeviceNew,
SerialDeviceNewParams,
laser::LaserMachine,
machine_identification::{
DeviceIdentification, DeviceIdentificationIdentified, MachineIdentificationUnique,
}, pelletizer::Pelletizer, registry::{MACHINE_REGISTRY, MachineRegistry}, serial::{devices::{laser::Laser, us_3202510::US3202510}, init::SerialDetection}, winder2::api::GenericEvent
},
registry::{MACHINE_REGISTRY, MachineRegistry},
serial::{devices::laser::Laser, init::SerialDetection},
winder2::api::GenericEvent,
};
#[cfg(feature = "development-build")]
use std::sync::atomic::{AtomicBool, Ordering};
@@ -157,20 +164,19 @@ pub async fn handle_serial_device_hotplug(
app_state: Arc<SharedState>,
map: HashMap<String, UsbPortInfo>,
) {
// IDENTIFIER OF THE USB DEVICE, NOT THE ACTUAL DEVICE
let usb_interface_id = SerialDeviceIdentification {
let laser_ident = SerialDeviceIdentification {
vendor_id: 0x0403,
product_id: 0x6001,
};
let path = SerialDetection::get_path_by_id(usb_interface_id, map);
let laser = SerialDetection::get_path_by_id(laser_ident, map);
let mut unique_ident: Option<MachineIdentificationUnique> = None;
{
let machines = app_state.current_machines_meta.lock().await;
for machine in machines.iter()
{
if machine.machine_identification_unique.machine_identification == Pelletizer::MACHINE_IDENTIFICATION
for machine in machines.iter() {
if machine.machine_identification_unique.machine_identification
== LaserMachine::MACHINE_IDENTIFICATION
{
unique_ident = Some(machine.machine_identification_unique.clone());
break;
@@ -179,12 +185,11 @@ pub async fn handle_serial_device_hotplug(
}
// Machine isnt connected, so add it back
if path.is_some() && unique_ident.is_none() {
if laser.is_some() && unique_ident.is_none() {
let serial_params = SerialDeviceNewParams {
path: path.unwrap(),
path: laser.unwrap(),
};
match US3202510::new_serial(&serial_params) {
match Laser::new_serial(&serial_params) {
Ok((device_identification, serial_device)) => {
add_serial_device(
app_state.clone(),
@@ -197,7 +202,7 @@ pub async fn handle_serial_device_hotplug(
}
_ => (),
};
} else if path.is_none() && unique_ident.is_some() {
} else if laser.is_none() && unique_ident.is_some() {
let unique_ident = unique_ident.unwrap();
app_state
.clone()