Compare commits

..
Author SHA1 Message Date
Robin Krämer ff13baf257 Merge pull request #779 from qitechgmbh/changelog-2.9.0
updated Changelog
2025-10-06 14:25:39 +02:00
kraemr 8f5df07c19 updated Changelog 2025-10-06 14:24:43 +02:00
Robin Krämer 299b38515a Merge pull request #778 from qitechgmbh/777-increase-max-step-size
Increased Winder Step Size
2025-10-06 14:12:24 +02:00
kraemr 2b073aeb76 Increased Winder Step Size 2025-10-06 13:56:20 +02:00
Robin Krämer 7a6f92f8a4 Merge pull request #771 from qitechgmbh/770-update-machine-implementation-status-in-the-readme
Update machine documentation to reflect current implementation status
2025-10-02 17:07:13 +02:00
Christian 2e283ea95f Update machine documentation to reflect current implementation status 2025-10-02 17:03:55 +02:00
Robin Krämer 829bb5ea1c Merge pull request #625 from qitechgmbh/improved-disconnect-handling
Introduce machine connection
2025-10-02 14:26:07 +02:00
Oshgnacknak 0be18aaed0 Derive machines from macro again
This time we also add get_machine_identification_unique.

Also fix import warnings
2025-10-02 13:58:54 +02:00
Oshgnacknak ffe0f09061 Clean up machine event creation 2025-10-01 16:50:04 +02:00
nixos-install.sh 730d61a18d Don't clear ethercat slots, we want to keep the namespaces 2025-10-01 15:57:03 +02:00
Oshgnacknak fc07525976 Don't remove, only disconnect serial machines
This allows use to keep the namespaces
2025-10-01 15:57:03 +02:00
Oshgnacknak c48953aa6b Introduce machine connection
This is to hold on to the namespace of
a machine even if the devices of that
machine has disconnected and to allow
reattaching to the namespace once the
devices reconnects.

Additionally to the new struct,
a wast refactor took place cleaning up
connections to machines, cross connections
between machines the way we downcast
machines downcasts.
2025-10-01 15:57:03 +02:00
Melvin Spieß 0c948b436d Merge pull request #764 from qitechgmbh/dependabot/cargo/axum-0.8.6
Bump axum from 0.8.4 to 0.8.6
2025-10-01 12:05:48 +02:00
Robin Krämer 1b3f92dd1c Merge pull request #725 from qitechgmbh/425-fix-terminal-component-issues-v2
425 fix terminal component issues v2
2025-10-01 11:58:55 +02:00
dependabot[bot] c29c549a1c Bump axum from 0.8.4 to 0.8.6
Bumps [axum](https://github.com/tokio-rs/axum) from 0.8.4 to 0.8.6.
- [Release notes](https://github.com/tokio-rs/axum/releases)
- [Changelog](https://github.com/tokio-rs/axum/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/axum/compare/axum-v0.8.4...axum-v0.8.6)

---
updated-dependencies:
- dependency-name: axum
  dependency-version: 0.8.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-01 09:57:52 +00:00
kraemr 17622cacde set log level to "info" for rust 2025-10-01 11:56:29 +02:00
kraemr 0b423454ec fixed line duplication in Terminal Component on Update Page 2025-10-01 11:56:29 +02:00
kraemr 81ec98f670 dont clear log, causes react errors 2025-10-01 11:56:29 +02:00
93094049+kraemr@users.noreply.github.com 1aed425544 eslint bypass for control character 2025-10-01 11:56:29 +02:00
93094049+kraemr@users.noreply.github.com da080ace68 try using clear functions before interactions 2025-10-01 11:56:29 +02:00
93094049+kraemr@users.noreply.github.com ded8dd2d2d added react-virtualized 2025-10-01 11:56:29 +02:00
93094049+kraemr@users.noreply.github.com edd83ba559 added virtualized list 2025-10-01 11:56:29 +02:00
Melvin Spieß 7d1d8559a5 Merge pull request #672 from qitechgmbh/671-refactor-machine-creation
reduced code duplication in new.rs of machines
2025-10-01 11:56:17 +02:00
kraemr f0bb9bfed1 fixed lifetimes of get_ethercat_device and refactored buffer 2025-10-01 11:52:13 +02:00
93094049+kraemr@users.noreply.github.com c6ea057736 renamed helper function 2025-10-01 11:52:13 +02:00
93094049+kraemr@users.noreply.github.com 90a00198a7 reduced code duplication in new.rs of machines 2025-10-01 11:52:12 +02:00
Melvin Spieß f8241547fe Merge pull request #762 from qitechgmbh/dependabot/cargo/erased-serde-0.4.8
Bump erased-serde from 0.4.6 to 0.4.8
2025-10-01 11:50:54 +02:00
Melvin Spieß 1cb5d51c13 Merge pull request #760 from qitechgmbh/dependabot/npm_and_yarn/electron/tanstack/react-router-1.130.17
Bump @tanstack/react-router from 1.130.12 to 1.130.17 in /electron
2025-10-01 11:38:53 +02:00
Melvin Spieß c42cdf8db1 Merge pull request #759 from qitechgmbh/dependabot/npm_and_yarn/electron/radix-ui/react-navigation-menu-1.2.14
Bump @radix-ui/react-navigation-menu from 1.2.13 to 1.2.14 in /electron
2025-10-01 11:38:22 +02:00
Melvin Spieß 69014ef8c6 Merge pull request #758 from qitechgmbh/dependabot/npm_and_yarn/electron/types/react-19.1.16
Bump @types/react from 19.1.9 to 19.1.16 in /electron
2025-10-01 11:35:53 +02:00
Melvin Spieß 5faab29a2b Merge pull request #763 from qitechgmbh/dependabot/cargo/libc-0.2.176
Bump libc from 0.2.175 to 0.2.176
2025-10-01 11:35:31 +02:00
dependabot[bot] 9a8b16ca69 Bump @radix-ui/react-navigation-menu from 1.2.13 to 1.2.14 in /electron
Bumps [@radix-ui/react-navigation-menu](https://github.com/radix-ui/primitives) from 1.2.13 to 1.2.14.
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

---
updated-dependencies:
- dependency-name: "@radix-ui/react-navigation-menu"
  dependency-version: 1.2.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-01 09:32:49 +00:00
Melvin Spieß 53f3c66214 Merge pull request #765 from qitechgmbh/dependabot/cargo/regex-1.11.3
Bump regex from 1.11.2 to 1.11.3
2025-10-01 11:30:59 +02:00
dependabot[bot] 432bf0d6ba Bump libc from 0.2.175 to 0.2.176
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.175 to 0.2.176.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.176/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.175...0.2.176)

---
updated-dependencies:
- dependency-name: libc
  dependency-version: 0.2.176
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-01 09:30:49 +00:00
dependabot[bot] f3990f35f1 Bump erased-serde from 0.4.6 to 0.4.8
Bumps [erased-serde](https://github.com/dtolnay/erased-serde) from 0.4.6 to 0.4.8.
- [Release notes](https://github.com/dtolnay/erased-serde/releases)
- [Commits](https://github.com/dtolnay/erased-serde/compare/0.4.6...0.4.8)

---
updated-dependencies:
- dependency-name: erased-serde
  dependency-version: 0.4.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-01 09:30:40 +00:00
dependabot[bot] 6f17412363 Bump regex from 1.11.2 to 1.11.3
Bumps [regex](https://github.com/rust-lang/regex) from 1.11.2 to 1.11.3.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.11.2...1.11.3)

---
updated-dependencies:
- dependency-name: regex
  dependency-version: 1.11.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-01 09:28:49 +00:00
dependabot[bot] 68907b9750 Bump @types/react from 19.1.9 to 19.1.16 in /electron
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 19.1.9 to 19.1.16.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-version: 19.1.16
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-01 09:26:34 +00:00
dependabot[bot] e7007e1980 Bump @tanstack/react-router from 1.130.12 to 1.130.17 in /electron
Bumps [@tanstack/react-router](https://github.com/TanStack/router/tree/HEAD/packages/react-router) from 1.130.12 to 1.130.17.
- [Release notes](https://github.com/TanStack/router/releases)
- [Commits](https://github.com/TanStack/router/commits/v1.130.17/packages/react-router)

---
updated-dependencies:
- dependency-name: "@tanstack/react-router"
  dependency-version: 1.130.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-01 09:26:27 +00:00
Melvin Spieß 3bcd96d73c Merge pull request #766 from qitechgmbh/dependabot/cargo/anyhow-1.0.100
Bump anyhow from 1.0.99 to 1.0.100
2025-10-01 11:09:51 +02:00
dependabot[bot] 8a77c62a5e Bump anyhow from 1.0.99 to 1.0.100
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.99 to 1.0.100.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.99...1.0.100)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-version: 1.0.100
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-01 02:24:32 +00:00
kraemr 93add82ab5 updated Changelog 2025-09-30 15:29:27 +02:00
Robin Krämer 690b1f7ab3 Merge pull request #757 from qitechgmbh/revert-ethercat-setup
fixed ethercat setup timeouts and retry
2025-09-29 17:19:33 +02:00
59 changed files with 1401 additions and 1393 deletions

No files matched your search

+30 -2
View File
@@ -1,3 +1,31 @@
# `2.9.0`
_06.10.2025_
## Winder V2
- [#778](https://github.com/qitechgmbh/control/pull/778) Increased Winder Traverse Step size limit
## General
- [#725](https://github.com/qitechgmbh/control/pull/725) Improved troubleshooting and update page
- [#625](https://github.com/qitechgmbh/control/pull/625) Improved stability of Machine-Connections
# `2.8.0`
_30.09.2025_
## Laser V1
- [#741](https://github.com/qitechgmbh/control/pull/741) Added roundness measurement to two-axis Laser
## Winder V2
- [#745](https://github.com/qitechgmbh/control/pull/745), [#756](https://github.com/qitechgmbh/control/pull/756) improved Winder Frontend
## General
- [#447](https://github.com/qitechgmbh/control/pull/447) added AquaPath Machine
# `2.7.0`
_17.09.2025_
@@ -10,9 +38,9 @@ _17.09.2025_
- [#727](https://github.com/qitechgmbh/control/pull/727) Removed estimated diameter
## Laser V1/V2
## Laser V1
- [#731](https://github.com/qitechgmbh/control/pull/731) Added LaserV2 with two-axis measurements
- [#731](https://github.com/qitechgmbh/control/pull/731) Added Laser with two-axis measurements
## General
Generated
+30 -21
View File
@@ -99,9 +99,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.99"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "approx"
@@ -284,9 +284,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "axum"
version = "0.8.4"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871"
dependencies = [
"axum-core",
"axum-macros",
@@ -304,8 +304,7 @@ dependencies = [
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"serde_core",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
@@ -319,9 +318,9 @@ dependencies = [
[[package]]
name = "axum-core"
version = "0.5.2"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22"
dependencies = [
"bytes",
"futures-core",
@@ -330,7 +329,6 @@ dependencies = [
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper",
"tower-layer",
"tower-service",
@@ -955,11 +953,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "erased-serde"
version = "0.4.6"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7"
checksum = "259d404d09818dec19332e31d94558aeb442fea04c817006456c24b5460bbd4b"
dependencies = [
"serde",
"serde_core",
"typeid",
]
@@ -1729,9 +1728,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.175"
version = "0.2.176"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
[[package]]
name = "libloading"
@@ -2381,9 +2380,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.11.2"
version = "1.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c"
dependencies = [
"aho-corasick",
"memchr",
@@ -2393,9 +2392,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.9"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
dependencies = [
"aho-corasick",
"memchr",
@@ -2541,18 +2540,28 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.219"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
+5 -5
View File
@@ -64,12 +64,12 @@ We could combine the code of the frontend and backend using [Doxius](https://dio
| 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 | In Progress | [](./docs/machines/winder-1.md) |
| 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 | Not Yet | |
| Waterway | V1 | 2025 | Filament Water Cooling | - | 1 (Qitech Industries GmbH) | 5 | Not Yet | |
| Laser | V1 | ??? | Diameter Measuring Laser | - | 1 (Qitech Industries GmbH) | 6 | Not Yet | |
| Mock | - | ??? | Mock Machine for Testing | - | 1 (Qitech Industries GmbH) | 7 | Not Yet | - |
| 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 | - |
# More Docs
+6 -1
View File
@@ -40,10 +40,15 @@ fn machine_derive2(item: TokenStream2) -> Result<TokenStream2, Error> {
let ast: DeriveInput = syn::parse2(item)?;
let ident = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let expanded = quote! {
impl #impl_generics control_core::machines::Machine for #ident #ty_generics #where_clause {}
impl #impl_generics control_core::machines::Machine for #ident #ty_generics #where_clause {
fn get_machine_identification_unique(&self) -> control_core::machines::identification::MachineIdentificationUnique {
self.machine_identification_unique.clone()
}
}
impl #impl_generics control_core::machines::AnyGetters for #ident #ty_generics #where_clause {
fn as_any(&self) -> &dyn std::any::Any {
+4 -4
View File
@@ -7,9 +7,9 @@ edition = "2024"
workspace = true
[dependencies]
anyhow = "1.0.99"
anyhow = "1.0.100"
axum = "0.8.4"
axum = "0.8.6"
ethercat_hal = { path = "../ethercat-hal" }
ethercrab = "0.6"
@@ -24,14 +24,14 @@ bitvec = { version = "1.0.1", features = ["alloc"] }
crc = "3.3.0"
serial = "0.4.0"
tracing = "0.1.41"
erased-serde = "0.4.6"
erased-serde = "0.4.8"
core_affinity = "0.8.3"
control_core_derive = { version = "0.1.0", path = "../control-core-derive" }
tokio = { version = "1.45.1", features = ["sync"], optional = true }
[target.'cfg(unix)'.dependencies]
libc = "0.2.175"
libc = "0.2.176"
[dev-dependencies]
approx = "0.5.1"
+36
View File
@@ -0,0 +1,36 @@
use anyhow::anyhow;
use smol::lock::Mutex;
use std::any::Any;
use std::sync::Arc;
pub trait Downcast<T> {
fn downcast(&self) -> Result<T, anyhow::Error>;
}
pub trait DowncastRef<T> {
fn downcast_ref(&self) -> Result<&T, anyhow::Error>;
}
impl<T: 'static, U: Any + 'static> Downcast<Arc<Mutex<T>>> for Arc<Mutex<U>> {
fn downcast(&self) -> Result<Arc<Mutex<T>>, anyhow::Error> {
// Acquire a read lock on the RwLock
let lock = self.lock_blocking();
// Check if the inner type can be downcasted to T
let any: &dyn Any = &*lock;
if any.is::<T>() {
// Clone the Arc and return it as the desired type
let cloned: Arc<Mutex<dyn Any>> = self.clone();
// Transmute the Arc to the desired type
unsafe {
let arc = Arc::from_raw(Arc::into_raw(cloned) as *const Mutex<T>);
Ok(arc)
}
} else {
Err(anyhow!("[{}::downcast] Downcast failed", module_path!()))
}
}
}
+1
View File
@@ -1,5 +1,6 @@
pub mod controllers;
pub mod converters;
pub mod downcast;
pub mod ethercat;
pub mod helpers;
pub mod irq_handling;
+3 -1
View File
@@ -1,10 +1,12 @@
use serde_json::Value;
use smol::lock::Mutex;
use std::sync::Arc;
use crate::socketio::namespace::Namespace;
pub trait MachineApi {
fn api_mutate(&mut self, value: Value) -> Result<(), anyhow::Error>;
fn api_event_namespace(&mut self) -> &mut Namespace;
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>>;
/// Returns a list of available video stream identifiers for this machine
#[cfg(feature = "video-streaming")]
+208
View File
@@ -0,0 +1,208 @@
use crate::downcast::Downcast;
use crate::machines::identification::MachineIdentificationUnique;
use crate::machines::manager::MachineManager;
use crate::socketio::{event::GenericEvent, namespace::Namespace};
use serde::Serialize;
use smol::block_on;
use smol::lock::RwLock;
use std::sync::Arc;
use std::sync::Weak;
use super::Machine;
use anyhow::anyhow;
use smol::{channel::Sender, lock::Mutex};
use socketioxide::extract::SocketRef;
#[derive(Debug)]
pub enum MachineConnection<M: Machine + ?Sized>
where
Self: Sized,
{
Error(anyhow::Error),
Disconnected,
Connected(Arc<Mutex<M>>),
}
#[derive(Debug)]
pub struct MachineSlot<M: Machine + ?Sized>
where
Self: Sized,
{
pub machine_connection: MachineConnection<M>,
pub namespace: Arc<Mutex<Namespace>>,
}
pub type MachineConnectionGeneric = MachineConnection<dyn Machine>;
pub type MachineSlotGeneric = MachineSlot<dyn Machine>;
impl<M: Machine> Clone for MachineConnection<M> {
fn clone(&self) -> Self {
match self {
Self::Connected(m) => Self::Connected(m.clone()),
Self::Error(e) => Self::Error(anyhow!(e.to_string())),
Self::Disconnected => Self::Disconnected,
}
}
}
impl<M: Machine + ?Sized> MachineConnection<M> {
pub fn to_error(&self) -> Option<anyhow::Error> {
match self {
MachineConnection::Error(err) => Some(anyhow!(err.to_string())),
Self::Disconnected => Some(anyhow!("Machine is disconnected")),
_ => None,
}
}
pub fn to_machine(&self) -> Option<Arc<Mutex<M>>> {
match self {
Self::Connected(machine) => Some(machine.clone()),
_ => None,
}
}
}
impl<M: Machine + ?Sized> MachineSlot<M> {
pub fn new(socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>) -> Self {
let namespace = Namespace::new(socket_queue_tx);
Self {
machine_connection: MachineConnection::Disconnected,
namespace: Arc::new(Mutex::new(namespace)),
}
}
pub const fn is_connected(&self) -> bool {
matches!(self.machine_connection, MachineConnection::Connected(_))
}
}
pub trait CrossConnectableMachine<
F: CrossConnectableMachine<F, T>,
T: CrossConnectableMachine<T, F>,
>: Machine
{
fn get_cross_connection(&mut self) -> &mut MachineCrossConnection<F, T>;
}
#[derive(Debug)]
pub struct MachineCrossConnection<
F: CrossConnectableMachine<F, T>,
T: CrossConnectableMachine<T, F>,
> {
machine_manager: Weak<RwLock<MachineManager>>,
machine_identification_unique: MachineIdentificationUnique,
connected_machine: Weak<Mutex<MachineSlot<T>>>,
_make_compiler_happy: std::marker::PhantomData<F>, // This marks tells the typechecker, that we
// need F in our type arguemnts. We are ok with
// it being used only in a fake field. Without
// F being fixed, we would not be anble to implement
// reverse connecting and disconnecting properly,
// as the type on the other side would decay
// to an unknown type. See also https://github.com/qitechgmbh/control/pull/625#discussion_r2379566315
}
#[derive(Serialize, Debug, Clone)]
pub struct MachineCrossConnectionState {
/// Connected Machine
pub machine_identification_unique: Option<MachineIdentificationUnique>,
pub is_available: bool,
}
impl<F: CrossConnectableMachine<F, T>, T: CrossConnectableMachine<T, F>>
MachineCrossConnection<F, T>
{
pub fn new(
manager: Weak<RwLock<MachineManager>>,
machine_identification_unique: &MachineIdentificationUnique,
) -> Self {
Self {
machine_manager: manager,
connected_machine: Weak::new(),
machine_identification_unique: machine_identification_unique.clone(),
_make_compiler_happy: std::marker::PhantomData,
}
}
pub fn set_connected_machine(
&mut self,
machine_identification_unique: &MachineIdentificationUnique,
) {
if let Some(machine_manager) = self.machine_manager.upgrade() {
let machine_manager = block_on(machine_manager.read());
let slot: Arc<Mutex<MachineSlotGeneric>> =
match machine_manager.get(&machine_identification_unique) {
Some(c) => c,
None => return,
};
let slot: Arc<Mutex<MachineSlot<T>>> = match Downcast::downcast(&slot) {
Ok(s) => s,
Err(_) => return,
};
self.connected_machine = Arc::downgrade(&slot);
} else {
panic!("The machine manager has died, cannot do anything at this point");
}
}
pub fn reverse_connect(&self) {
if let Some(slot) = self.connected_machine.upgrade() {
let slot = &mut slot.lock_blocking();
if let MachineConnection::Connected(machine) = &slot.machine_connection {
let mut machine = machine.lock_blocking();
let cross_connection = machine.get_cross_connection();
if !cross_connection.is_connected() {
cross_connection.set_connected_machine(&self.machine_identification_unique);
}
};
}
}
pub fn disconnect(&mut self) {
self.connected_machine = Weak::new();
}
pub fn reverse_disconnect(&mut self) {
if let Some(slot) = self.connected_machine.upgrade() {
let slot = slot.lock_blocking();
if let MachineConnection::Connected(machine) = &slot.machine_connection {
let mut machine = machine.lock_blocking();
let cross_connection = machine.get_cross_connection();
cross_connection.disconnect();
};
}
}
pub fn is_connected(&self) -> bool {
let arc = self.connected_machine.upgrade();
arc.is_some()
}
pub fn to_state(&self) -> MachineCrossConnectionState {
if let Some(slot) = self.connected_machine.upgrade() {
let connection = &slot.lock_blocking().machine_connection;
if let MachineConnection::Connected(machine) = connection {
let machine = machine.lock_blocking();
return MachineCrossConnectionState {
machine_identification_unique: Some(
machine.get_machine_identification_unique(),
),
is_available: true,
};
}
}
MachineCrossConnectionState {
machine_identification_unique: None,
is_available: false,
}
}
}
+88 -66
View File
@@ -1,3 +1,9 @@
use super::{
Machine,
identification::{DeviceIdentification, DeviceIdentificationIdentified},
new::MachineNewHardwareSerial,
registry::MachineRegistry,
};
use smol::{
channel::Sender,
lock::{Mutex, RwLock},
@@ -6,6 +12,9 @@ use socketioxide::extract::SocketRef;
use crate::{
machines::{
connection::{
MachineConnection, MachineConnectionGeneric, MachineSlot, MachineSlotGeneric,
},
identification::MachineIdentificationUnique,
new::{MachineNewHardware, MachineNewHardwareEthercat, MachineNewParams},
},
@@ -17,18 +26,10 @@ use std::{
sync::{Arc, Weak},
};
use super::{
Machine,
identification::{DeviceIdentification, DeviceIdentificationIdentified},
new::MachineNewHardwareSerial,
registry::MachineRegistry,
};
#[derive(Debug)]
pub struct MachineManager {
pub ethercat_machines:
HashMap<MachineIdentificationUnique, Result<Arc<Mutex<dyn Machine>>, anyhow::Error>>,
pub serial_machines:
HashMap<MachineIdentificationUnique, Result<Arc<Mutex<dyn Machine>>, anyhow::Error>>,
pub ethercat_machines: HashMap<MachineIdentificationUnique, Arc<Mutex<MachineSlotGeneric>>>,
pub serial_machines: HashMap<MachineIdentificationUnique, Arc<Mutex<MachineSlotGeneric>>>,
}
impl Default for MachineManager {
@@ -53,9 +54,6 @@ impl MachineManager {
socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>,
machine_manager: Weak<RwLock<Self>>,
) {
// empty ethercat machines
self.ethercat_machines.clear();
// group devices by machine device identification
let device_grouping_result = group_devices_by_identification(device_identifications);
@@ -69,10 +67,18 @@ impl MachineManager {
// iterate over all identified device groups but ignore unaffected machines
for device_group in device_grouping_result.device_groups.iter() {
// get the machine identification
let machine_identification = match device_group.first() {
Some(device_identification) => &device_identification.device_machine_identification,
None => continue, // Skip this group if empty
};
let machine_identification_unique: MachineIdentificationUnique =
match device_group.first() {
Some(device_identification) => device_identification
.device_machine_identification
.machine_identification_unique
.clone(),
None => continue, // Skip this group if empty
};
let slot =
self.get_or_create_slot(socket_queue_tx.clone(), machine_identification_unique);
let mut slot = slot.lock_blocking();
// create the machine
let new_machine = machine_registry.new_machine(&MachineNewParams {
@@ -80,23 +86,40 @@ impl MachineManager {
hardware: &machine_new_hardware,
socket_queue_tx: socket_queue_tx.clone(),
machine_manager: machine_manager.clone(),
namespace: slot.namespace.clone(),
});
// insert the machine into the ethercat machines map
self.ethercat_machines.insert(
machine_identification.machine_identification_unique.clone(),
new_machine,
);
slot.machine_connection = match new_machine {
Err(err) => MachineConnectionGeneric::Error(err),
Ok(machine) => MachineConnectionGeneric::Connected(machine),
};
}
}
fn get_or_create_slot(
&mut self,
socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>,
machine_identification: MachineIdentificationUnique,
) -> Arc<Mutex<MachineSlotGeneric>> {
if let Some(slot) = self.get(&machine_identification) {
return slot;
}
let slot = Arc::new(Mutex::new(MachineSlot::new(socket_queue_tx)));
self.ethercat_machines
.insert(machine_identification, slot.clone());
return slot;
}
pub fn get(
&self,
machine_identification: &MachineIdentificationUnique,
) -> Option<&Result<Arc<Mutex<dyn Machine>>, anyhow::Error>> {
) -> Option<Arc<Mutex<MachineSlotGeneric>>> {
self.ethercat_machines
.get(machine_identification)
.or_else(|| self.serial_machines.get(machine_identification))
.map(|x| x.clone())
}
pub fn add_serial_device(
@@ -115,21 +138,28 @@ impl MachineManager {
.try_into()
.expect("Serial devices always have machine identification");
let machine_identification: MachineIdentificationUnique = device_identification_identified
.device_machine_identification
.machine_identification_unique
.clone();
let slot = self.get_or_create_slot(socket_queue_tx.clone(), machine_identification);
let mut slot = slot.lock_blocking();
let new_machine = machine_registry.new_machine(&MachineNewParams {
device_group: &vec![device_identification_identified.clone()],
hardware: &MachineNewHardware::Serial(&hardware),
socket_queue_tx,
machine_manager,
machine_manager: machine_manager.clone(),
namespace: slot.namespace.clone(),
});
tracing::info!("Adding serial machine {:?}", new_machine);
slot.machine_connection = match new_machine {
Err(err) => MachineConnectionGeneric::Error(err),
Ok(machine) => MachineConnectionGeneric::Connected(machine),
};
self.serial_machines.insert(
device_identification_identified
.device_machine_identification
.machine_identification_unique,
new_machine,
);
tracing::info!("Adding serial machine {:?}", slot);
}
pub fn remove_serial_device(&mut self, device_identification: &DeviceIdentification) {
@@ -144,53 +174,45 @@ impl MachineManager {
device_identification_identified
);
self.serial_machines.remove(
&device_identification_identified
.device_machine_identification
.machine_identification_unique,
);
let machine_identification_unique = &device_identification_identified
.device_machine_identification
.machine_identification_unique;
if let Some(slot) = self.serial_machines.get(machine_identification_unique) {
let mut slot = slot.lock_blocking();
slot.machine_connection = MachineConnection::Disconnected;
}
}
pub fn get_machine_weak(
pub fn get_ethercat_weak(
&self,
machine_identification: &MachineIdentificationUnique,
) -> Option<Weak<Mutex<dyn Machine>>> {
let machine = self.ethercat_machines.get(machine_identification);
let machine = match machine {
Some(machine) => machine,
None => {
return None;
}
};
let machine = match machine {
Ok(machine) => machine,
Err(_) => {
return None;
}
};
let machine_weak = Arc::downgrade(machine);
Some(machine_weak)
self.get_weak(&self.ethercat_machines, machine_identification)
}
pub fn get_serial_weak(
&self,
machine_identification: &MachineIdentificationUnique,
) -> Option<Weak<Mutex<dyn Machine>>> {
let machine = self.serial_machines.get(machine_identification);
let machine = match machine {
Some(machine) => machine,
None => {
return None;
self.get_weak(&self.serial_machines, machine_identification)
}
fn get_weak(
&self,
machines: &HashMap<MachineIdentificationUnique, Arc<Mutex<MachineSlotGeneric>>>,
machine_identification: &MachineIdentificationUnique,
) -> Option<Weak<Mutex<dyn Machine>>> {
let slot = machines.get(machine_identification).clone();
return slot.and_then(|slot| {
let connection = &slot.lock_blocking().machine_connection;
match connection {
MachineConnectionGeneric::Error(_) => None,
MachineConnectionGeneric::Disconnected => None,
MachineConnectionGeneric::Connected(machine) => Some(Arc::downgrade(&machine)),
}
};
let machine = match machine {
Ok(machine) => machine,
Err(_) => {
return None;
}
};
let machine_weak = Arc::downgrade(machine);
Some(machine_weak)
});
}
}
+10 -8
View File
@@ -1,22 +1,25 @@
use std::{collections::HashMap, sync::Arc};
use std::collections::HashMap;
use std::sync::Arc;
use smol::lock::Mutex;
use super::{Machine, identification::MachineIdentificationUnique, manager::MachineManager};
use crate::machines::connection::MachineSlotGeneric;
use super::{identification::MachineIdentificationUnique, manager::MachineManager};
pub enum MachineManagerIter<'a> {
EthercatMachines {
iter: std::collections::hash_map::Iter<
'a,
MachineIdentificationUnique,
Result<Arc<Mutex<dyn Machine>>, anyhow::Error>,
Arc<Mutex<MachineSlotGeneric>>,
>,
},
SerialMachines {
iter: std::collections::hash_map::Iter<
'a,
MachineIdentificationUnique,
Result<Arc<Mutex<dyn Machine>>, anyhow::Error>,
Arc<Mutex<MachineSlotGeneric>>,
>,
},
Done,
@@ -24,14 +27,13 @@ pub enum MachineManagerIter<'a> {
pub struct MachineManagerIterator<'a> {
iter: MachineManagerIter<'a>,
serial_machines:
&'a HashMap<MachineIdentificationUnique, Result<Arc<Mutex<dyn Machine>>, anyhow::Error>>,
serial_machines: &'a HashMap<MachineIdentificationUnique, Arc<Mutex<MachineSlotGeneric>>>,
}
impl<'a> Iterator for MachineManagerIterator<'a> {
type Item = (
&'a MachineIdentificationUnique,
&'a Result<Arc<Mutex<dyn Machine>>, anyhow::Error>,
&'a Arc<Mutex<MachineSlotGeneric>>,
);
fn next(&mut self) -> Option<Self::Item> {
@@ -71,7 +73,7 @@ impl<'a> Iterator for MachineManagerIterator<'a> {
impl MachineManager {
// Returns an iterator over all machines (both ethercat and serial)
pub fn iter(&'_ self) -> MachineManagerIterator<'_> {
pub fn iter(&self) -> MachineManagerIterator<'_> {
MachineManagerIterator {
iter: MachineManagerIter::EthercatMachines {
iter: self.ethercat_machines.iter(),
+8 -68
View File
@@ -1,83 +1,23 @@
use anyhow::anyhow;
use api::MachineApi;
use new::MachineNewTrait;
use serde::Serialize;
use smol::lock::Mutex;
use std::any::Any;
use std::fmt::Debug;
use std::sync::{Arc, Weak};
use crate::machines::identification::MachineIdentificationUnique;
use crate::machines::new::MachineAct;
use crate::machines::{
api::MachineApi, identification::MachineIdentificationUnique, new::MachineAct,
};
pub mod api;
pub mod connection;
pub mod identification;
pub mod manager;
pub mod manager_iter;
pub mod new;
pub mod registry;
pub trait Machine: MachineAct + MachineNewTrait + MachineApi + AnyGetters + Debug + Send {}
pub trait Machine: MachineAct + MachineNewTrait + MachineApi + Any + Debug + Send + Sync {
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique;
}
pub trait AnyGetters: Any {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
/// Casts a `dyn Machine` to a specific machine type
pub async fn downcast_machine<T: Machine>(
machine: Arc<Mutex<dyn Machine>>,
) -> Result<Arc<Mutex<T>>, anyhow::Error> {
// Acquire a read lock on the RwLock
let read_lock = machine.lock().await;
// Check if the inner type can be downcasted to T
if read_lock.as_any().is::<T>() {
// Clone the Arc and return it as the desired type
let cloned_machine = Arc::clone(&machine);
// Transmute the Arc to the desired type
unsafe {
Ok(Arc::from_raw(
Arc::into_raw(cloned_machine) as *const Mutex<T>
))
}
} else {
Err(anyhow!(
"[{}::downcast_machine] Downcast failed",
module_path!()
))
}
}
/// Trait to get strong count to see if Weak has reference
pub trait GetStrongCount {
fn get_strong_count(&self) -> usize;
}
impl<T> GetStrongCount for Weak<Mutex<T>> {
fn get_strong_count(&self) -> usize {
self.strong_count()
}
}
#[derive(Debug)]
pub struct ConnectedMachine<T: GetStrongCount> {
pub machine_identification_unique: MachineIdentificationUnique,
pub machine: T,
}
#[derive(Serialize, Debug, Clone, PartialEq, Eq)]
pub struct ConnectedMachineData {
pub machine_identification_unique: MachineIdentificationUnique,
pub is_available: bool,
}
impl<T> From<&ConnectedMachine<T>> for ConnectedMachineData
where
T: GetStrongCount,
{
fn from(value: &ConnectedMachine<T>) -> Self {
Self {
machine_identification_unique: value.machine_identification_unique.clone(),
is_available: value.machine.get_strong_count() != 0,
}
}
}
+22 -2
View File
@@ -1,4 +1,8 @@
use crate::{machines::manager::MachineManager, serial::SerialDevice};
use crate::{
machines::{identification::MachineIdentificationUnique, manager::MachineManager},
serial::SerialDevice,
socketio::namespace::Namespace,
};
use super::identification::DeviceIdentificationIdentified;
use anyhow::Error;
@@ -6,7 +10,10 @@ use ethercat_hal::{
devices::EthercatDevice, helpers::ethercrab_types::EthercrabSubDevicePreoperational,
};
use ethercrab::{SubDevice, SubDeviceRef};
use smol::{channel::Sender, lock::RwLock};
use smol::{
channel::Sender,
lock::{Mutex, RwLock},
};
use socketioxide::extract::SocketRef;
use std::{
sync::{Arc, Weak},
@@ -49,6 +56,19 @@ pub struct MachineNewParams<
>,
pub socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>,
pub machine_manager: Weak<RwLock<MachineManager>>,
pub namespace: Arc<Mutex<Namespace>>,
}
impl MachineNewParams<'_, '_, '_, '_, '_, '_, '_> {
pub fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
self.device_group
.first()
.expect("device group must have at least one device")
.device_machine_identification
.machine_identification_unique
.clone()
}
}
pub enum MachineNewHardware<
+144 -24
View File
@@ -15,7 +15,7 @@
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-navigation-menu": "^1.2.5",
"@radix-ui/react-navigation-menu": "^1.2.14",
"@radix-ui/react-popover": "^1.1.7",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.3",
@@ -24,7 +24,7 @@
"@radix-ui/react-toggle": "^1.1.1",
"@radix-ui/react-toggle-group": "^1.1.2",
"@tanstack/react-query": "^5.84.2",
"@tanstack/react-router": "^1.105.0",
"@tanstack/react-router": "^1.130.17",
"@tanstack/react-table": "^8.21.2",
"@tanstack/router-devtools": "^1.105.0",
"@tanstack/zod-adapter": "^1.116.0",
@@ -43,6 +43,7 @@
"react-hook-form": "^7.57.0",
"react-i18next": "^15.4.1",
"react-markdown": "^10.1.0",
"react-virtualized": "^9.22.6",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"socket.io-client": "^4.8.1",
@@ -77,8 +78,9 @@
"@types/electron-squirrel-startup": "^1.0.2",
"@types/eslint-config-prettier": "^6.11.3",
"@types/node": "^24.2.1",
"@types/react": "^19.0.10",
"@types/react": "^19.1.16",
"@types/react-dom": "^19.0.4",
"@types/react-virtualized": "^9.22.2",
"electron": "^37.3.1",
"electron-playwright-helpers": "^1.7.1",
"eslint": "^9.20.1",
@@ -2655,18 +2657,19 @@
}
},
"node_modules/@radix-ui/react-navigation-menu": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.13.tgz",
"integrity": "sha512-WG8wWfDiJlSF5hELjwfjSGOXcBR/ZMhBFCGYe8vERpC39CQYZeq1PQ2kaYHdye3V95d06H89KGMsVCIE4LWo3g==",
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz",
"integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.2",
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-collection": "1.1.7",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-direction": "1.1.1",
"@radix-ui/react-dismissable-layer": "1.1.10",
"@radix-ui/react-dismissable-layer": "1.1.11",
"@radix-ui/react-id": "1.1.1",
"@radix-ui/react-presence": "1.1.4",
"@radix-ui/react-presence": "1.1.5",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-callback-ref": "1.1.1",
"@radix-ui/react-use-controllable-state": "1.2.2",
@@ -2689,6 +2692,63 @@
}
}
},
"node_modules/@radix-ui/react-navigation-menu/node_modules/@radix-ui/primitive": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
"license": "MIT"
},
"node_modules/@radix-ui/react-navigation-menu/node_modules/@radix-ui/react-dismissable-layer": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
"integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-callback-ref": "1.1.1",
"@radix-ui/react-use-escape-keydown": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-navigation-menu/node_modules/@radix-ui/react-presence": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
"integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.14.tgz",
@@ -3964,13 +4024,14 @@
}
},
"node_modules/@tanstack/react-router": {
"version": "1.130.12",
"resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.130.12.tgz",
"integrity": "sha512-7BYgOpGc1vK8MH1LIFLLBudGpH46GQy+hewnP7dNQJ4KHmkwPHv958L1IMA9jU/rs5g1ZH5n1f33BAMOBXUMYQ==",
"version": "1.130.17",
"resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.130.17.tgz",
"integrity": "sha512-F1tyMEbX7PJclnA25hAFXhLKq4jqeODvvaogsd0/NX02Kmz3QoT9GV+rgpv2TxamU6RwjRgihSH06Dn5hM6SOw==",
"license": "MIT",
"dependencies": {
"@tanstack/history": "1.130.12",
"@tanstack/react-store": "^0.7.0",
"@tanstack/router-core": "1.130.12",
"@tanstack/router-core": "1.130.17",
"isbot": "^5.1.22",
"tiny-invariant": "^1.3.3",
"tiny-warning": "^1.0.3"
@@ -4044,9 +4105,10 @@
}
},
"node_modules/@tanstack/router-core": {
"version": "1.130.12",
"resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.130.12.tgz",
"integrity": "sha512-emq3cRU9Na1hnIToojzkfJcOZm/MG2bv9M+Kr/elUxEf83enGEwQXC1EKezTuwNgeJrOv8vPJdEhWM7IQodnHQ==",
"version": "1.130.17",
"resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.130.17.tgz",
"integrity": "sha512-0AH4s/52X2shYLifr9hR+6R6NpGsBp1M3LyjBTZm6Jlkud+nOhkkRxKxYS7l+JErRlffdxVHxMdoTnKcQ+duiQ==",
"license": "MIT",
"dependencies": {
"@tanstack/history": "1.130.12",
"@tanstack/store": "^0.7.0",
@@ -4442,10 +4504,18 @@
"undici-types": "~7.10.0"
}
},
"node_modules/@types/prop-types": {
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "19.1.9",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.9.tgz",
"integrity": "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==",
"version": "19.1.16",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.16.tgz",
"integrity": "sha512-WBM/nDbEZmDUORKnh5i1bTnAz6vTohUf9b8esSMu+b24+srbaxa04UbJgWx78CVfNXA20sNu0odEIluZDFdCog==",
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
}
@@ -4459,6 +4529,17 @@
"@types/react": "^19.0.0"
}
},
"node_modules/@types/react-virtualized": {
"version": "9.22.2",
"resolved": "https://registry.npmjs.org/@types/react-virtualized/-/react-virtualized-9.22.2.tgz",
"integrity": "sha512-0Eg/ME3OHYWGxs+/n4VelfYrhXssireZaa1Uqj5SEkTpSaBu5ctFGOCVxcOqpGXRiEdrk/7uho9tlZaryCIjHA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
"@types/react": "*"
}
},
"node_modules/@types/responselike": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
@@ -6295,6 +6376,16 @@
"dev": true,
"peer": true
},
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -9917,7 +10008,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
@@ -11301,7 +11391,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -12087,7 +12176,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -12097,8 +12185,7 @@
"node_modules/prop-types/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/property-information": {
"version": "7.1.0",
@@ -12226,6 +12313,12 @@
"dev": true,
"peer": true
},
"node_modules/react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
"license": "MIT"
},
"node_modules/react-markdown": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz",
@@ -12326,6 +12419,33 @@
}
}
},
"node_modules/react-virtualized": {
"version": "9.22.6",
"resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.6.tgz",
"integrity": "sha512-U5j7KuUQt3AaMatlMJ0UJddqSiX+Km0YJxSqbAzIiGw5EmNz0khMyqP2hzgu4+QUtm+QPIrxzUX4raJxmVJnHg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.7.2",
"clsx": "^1.0.4",
"dom-helpers": "^5.1.3",
"loose-envify": "^1.4.0",
"prop-types": "^15.7.2",
"react-lifecycles-compat": "^3.0.4"
},
"peerDependencies": {
"react": "^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-virtualized/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/read-binary-file-arch": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz",
+5 -3
View File
@@ -42,8 +42,9 @@
"@types/electron-squirrel-startup": "^1.0.2",
"@types/eslint-config-prettier": "^6.11.3",
"@types/node": "^24.2.1",
"@types/react": "^19.0.10",
"@types/react": "^19.1.16",
"@types/react-dom": "^19.0.4",
"@types/react-virtualized": "^9.22.2",
"electron": "^37.3.1",
"electron-playwright-helpers": "^1.7.1",
"eslint": "^9.20.1",
@@ -71,7 +72,7 @@
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-navigation-menu": "^1.2.5",
"@radix-ui/react-navigation-menu": "^1.2.14",
"@radix-ui/react-popover": "^1.1.7",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.3",
@@ -80,7 +81,7 @@
"@radix-ui/react-toggle": "^1.1.1",
"@radix-ui/react-toggle-group": "^1.1.2",
"@tanstack/react-query": "^5.84.2",
"@tanstack/react-router": "^1.105.0",
"@tanstack/react-router": "^1.130.17",
"@tanstack/react-table": "^8.21.2",
"@tanstack/router-devtools": "^1.105.0",
"@tanstack/zod-adapter": "^1.116.0",
@@ -99,6 +100,7 @@
"react-hook-form": "^7.57.0",
"react-i18next": "^15.4.1",
"react-markdown": "^10.1.0",
"react-virtualized": "^9.22.6",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"socket.io-client": "^4.8.1",
+78 -123
View File
@@ -1,20 +1,32 @@
import { cva } from "class-variance-authority";
import React, { useEffect, useRef, useState } from "react";
import { Icon } from "./Icon";
import {
List,
CellMeasurer,
CellMeasurerCache,
ListRowRenderer,
AutoSizer,
} from "react-virtualized";
// Create cache (outside the component so it persists across renders)
const cache = new CellMeasurerCache({
defaultHeight: 20,
fixedWidth: true,
});
type Props = {
lines: string[];
autoScroll?: boolean; // Optional prop to control if terminal should auto-scroll
className?: string; // Optional prop to control terminal height
autoScroll?: boolean;
className?: string;
title?: string;
exportPrefix?: string; // Optional prefix for exported log files
exportPrefix?: string;
};
const terminalStyle = cva([
"flex flex-col overflow-hidden rounded-md border border-neutral-700 font-mono text-sm",
]);
// Color mapping for ANSI color codes
const colorMap: Record<string, string> = {
"30": "text-gray-900",
"31": "text-red-600",
@@ -32,7 +44,6 @@ const colorMap: Record<string, string> = {
"95": "text-purple-400",
"96": "text-cyan-400",
"97": "text-white",
// Background colors
"40": "bg-gray-900",
"41": "bg-red-600",
"42": "bg-green-600",
@@ -43,36 +54,26 @@ const colorMap: Record<string, string> = {
"47": "bg-gray-200",
};
// Parse ANSI color codes in text
const parseColorCodes = (text: string) => {
// Split by ANSI escape sequences
// eslint-disable-next-line no-control-regex
/* eslint-disable-next-line no-control-regex */
const parts = text.split(/(\x1b\[\d+m)/g);
let currentClass = "";
const result: { text: string; className: string }[] = [];
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
if (part.startsWith("\x1b[")) {
// This is a color code
const code = part.slice(2, -1); // Extract the number from \x1b[XXm
const code = part.slice(2, -1);
currentClass = colorMap[code] || "";
} else if (part) {
// This is text content
result.push({ text: part, className: currentClass });
}
}
return result;
};
// Function to strip ANSI color codes for plain text copy
const stripColorCodes = (text: string): string => {
const stripColorCodes = (text: string): string =>
// eslint-disable-next-line no-control-regex
return text.replace(/\x1b\[\d+m/g, "");
};
text.replace(/\x1b\[\d+m/g, "");
export function Terminal({
lines,
@@ -81,99 +82,88 @@ export function Terminal({
title = "Terminal",
exportPrefix,
}: Props) {
const terminalRef = useRef<HTMLDivElement>(null);
const [isScrolledToBottom, setIsScrolledToBottom] = useState(true);
const listRef = useRef<List>(null);
const [copySuccess, setCopySuccess] = useState(false);
const [exportSuccess, setExportSuccess] = useState(false);
// Handle scrolling
// Auto-scroll to bottom when new lines arrive
useEffect(() => {
const terminal = terminalRef.current;
if (!terminal) return;
// If auto-scroll is enabled and user was at bottom, scroll to bottom when lines change
if (autoScroll && isScrolledToBottom) {
terminal.scrollTop = terminal.scrollHeight;
if (autoScroll && listRef.current) {
listRef.current.scrollToRow(lines.length - 1);
}
}, [lines, autoScroll, isScrolledToBottom]);
}, [lines, autoScroll]);
// Handle scroll events to detect if user is at bottom
const handleScroll = () => {
const terminal = terminalRef.current;
if (!terminal) return;
const isAtBottom =
Math.abs(
terminal.scrollHeight - terminal.clientHeight - terminal.scrollTop,
) < 10; // Small threshold to account for rounding errors
setIsScrolledToBottom(isAtBottom);
};
// Handle copy to clipboard
const handleCopy = async () => {
// Strip ANSI color codes and join lines
const plainText = lines.map((line) => stripColorCodes(line)).join("\n");
const plainText = lines.map(stripColorCodes).join("\n");
try {
await navigator.clipboard.writeText(plainText);
setCopySuccess(true);
// Reset copy success message after 2 seconds
setTimeout(() => {
setCopySuccess(false);
}, 2000);
setTimeout(() => setCopySuccess(false), 2000);
} catch (err) {
console.error("Failed to copy text: ", err);
}
};
// Handle export to file
const handleExport = () => {
if (!exportPrefix) return;
// Strip ANSI color codes and join lines
const plainText = lines.map((line) => stripColorCodes(line)).join("\n");
// Create timestamp for filename
const plainText = lines.map(stripColorCodes).join("\n");
const timestamp = new Date()
.toISOString()
.replace(/[:.]/g, "-")
.slice(0, -5);
const filename = `${exportPrefix}_${timestamp}.log`;
// Create blob and download
const blob = new Blob([plainText], { type: "text/plain" });
const url = URL.createObjectURL(blob);
// Create temporary link element and trigger download
const link = document.createElement("a");
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Clean up
URL.revokeObjectURL(url);
// Show success feedback
setExportSuccess(true);
setTimeout(() => {
setExportSuccess(false);
}, 2000);
setTimeout(() => setExportSuccess(false), 2000);
};
const rowRenderer: ListRowRenderer = ({ index, key, parent, style }) => {
const colorParts = parseColorCodes(lines[index] || "");
return (
<CellMeasurer
key={key}
cache={cache}
parent={parent}
columnIndex={0}
rowIndex={index}
>
<div
style={style}
className="bg-black px-2 py-1 whitespace-pre-wrap text-white"
>
{colorParts.length > 0
? colorParts.map((part, partIndex) => (
<span
key={partIndex}
className={part.className || "text-white"}
>
{part.text || " "}
</span>
))
: lines[index] || " "}
</div>
</CellMeasurer>
);
};
return (
<div className={terminalStyle({ className })}>
{/* Terminal header */}
{/* Header */}
<div className="flex items-center justify-between border-b border-neutral-700 bg-neutral-800 px-4 py-2">
<div className="text-xs text-neutral-400">{title}</div>
<div className="flex items-center gap-2">
<button
onClick={handleCopy}
className="flex items-center text-xs text-neutral-400 transition-colors hover:text-neutral-200"
title="Copy to clipboard"
>
{copySuccess ? (
<>
@@ -191,7 +181,6 @@ export function Terminal({
<button
onClick={handleExport}
className="flex items-center text-xs text-neutral-400 transition-colors hover:text-neutral-200"
title="Export logs to file"
>
{exportSuccess ? (
<>
@@ -209,62 +198,28 @@ export function Terminal({
</div>
</div>
{/* Terminal content with custom scrollbar styling */}
<div
ref={terminalRef}
onScroll={handleScroll}
className={`scrollbar-thin scrollbar-thumb-neutral-600 scrollbar-track-transparent flex-grow overflow-y-auto bg-neutral-900 p-4 text-neutral-300`}
style={{
scrollbarWidth: "thin",
scrollbarColor: "rgb(82 82 91) transparent",
}}
>
<style>{`
Add commentMore actions
/* For Webkit browsers (Chrome, Safari) */
.scrollbar-thin::-webkit-scrollbar {
width: 6px;
}
.scrollbar-thin::-webkit-scrollbar-track {
background: transparent;
}
.scrollbar-thin::-webkit-scrollbar-thumb {
background-color: rgb(82 82 91);
border-radius: 3px;
}
/* For Firefox */
.scrollbar-thin {
scrollbar-width: thin;
scrollbar-color: rgb(82 82 91) transparent;
}
`}</style>
{lines.map((line, index) => {
const colorParts = parseColorCodes(line);
return (
<div key={index} className="whitespace-pre-wrap">
{colorParts.length > 0
? colorParts.map((part, partIndex) => (
<span key={partIndex} className={part.className}>
{part.text || " "}
</span>
))
: line || " "}
</div>
);
})}
{/* Virtualized List */}
<div className="flex-grow bg-black text-white">
<AutoSizer>
{({ width, height }) => (
<List
ref={listRef}
width={width}
height={height}
deferredMeasurementCache={cache}
rowHeight={cache.rowHeight}
rowCount={lines.length}
rowRenderer={rowRenderer}
overscanRowCount={5}
/>
)}
</AutoSizer>
</div>
{/* Status bar */}
{/* Footer */}
<div className="flex items-center justify-between bg-neutral-800 px-4 py-1 text-xs text-neutral-400">
<div>{lines.length} lines</div>
<div>
{isScrolledToBottom ? "At bottom" : "Scrolled up"} |
{autoScroll ? " Auto-scroll enabled" : " Auto-scroll disabled"}
</div>
<div>{autoScroll ? "Auto-scroll enabled" : "Auto-scroll disabled"}</div>
</div>
</div>
);
@@ -18,17 +18,39 @@ type UpdateExecuteInvokeParams = {
export function exposeUpdateContext() {
const { contextBridge, ipcRenderer } = window.require("electron");
let currentLogListener: ((event: any, log: string) => void) | null = null;
let currentEndListener: ((event: any, params: any) => void) | null = null;
contextBridge.exposeInMainWorld("update", {
execute: (params: UpdateExecuteInvokeParams) =>
ipcRenderer.invoke(UPDATE_EXECUTE, params),
cancel: () => ipcRenderer.invoke(UPDATE_CANCEL),
onLog: (callback: (log: string) => void) =>
ipcRenderer.on(UPDATE_LOG, (_event, log: string) => {
onLog: (callback: (log: string) => void) => {
if (currentLogListener) {
ipcRenderer.removeListener(UPDATE_LOG, currentLogListener);
}
currentLogListener = (_event, log: string) => {
callback(log);
}),
onEnd: (callback: (params: { success: boolean; error?: string }) => void) =>
ipcRenderer.on(UPDATE_END, (_event, params) => {
};
ipcRenderer.on(UPDATE_LOG, currentLogListener);
},
onEnd: (
callback: (params: { success: boolean; error?: string }) => void,
) => {
if (currentEndListener) {
ipcRenderer.removeListener(UPDATE_END, currentEndListener);
}
currentEndListener = (_event, params) => {
callback(params);
}),
};
ipcRenderer.on(UPDATE_END, currentEndListener);
},
});
}
+18 -3
View File
@@ -1,16 +1,28 @@
import type { UpdateInfo } from "@/stores/updateStore";
import { useUpdateStore } from "@/stores/updateStore";
let currentLogListener: ((line: string) => void) | null = null;
export async function updateExecute(
source: UpdateInfo,
onLog: (log: string) => void,
): Promise<{ success: boolean; error?: string }> {
return new Promise((resolve) => {
window.update.onLog(onLog);
// Remove previous listener if exists
if (currentLogListener) {
window.update.onLog(() => {}); // clear previous
currentLogListener = null;
}
currentLogListener = onLog;
window.update.onLog(currentLogListener);
window.update.execute(source);
window.update.onEnd((params) => {
window.update.onLog(() => {});
window.update.onLog(() => {}); // remove listener
window.update.onEnd(() => {});
currentLogListener = null;
resolve(params);
});
});
@@ -40,7 +52,10 @@ export async function updateCancel(): Promise<{
success: boolean;
error?: string;
}> {
return window.update.cancel();
const result = await window.update.cancel();
window.update.onLog(() => {}); // remove listener
window.update.onEnd(() => {}); // remove listener
return result;
}
// Enhanced helper that automatically manages store state
@@ -44,7 +44,7 @@ export function Winder2SettingPage() {
unit="mm"
step={0.05}
min={0.1}
max={10}
max={75}
defaultValue={defaultState?.traverse_state?.step_size}
renderValue={(value) => roundToDecimals(value, 2)}
onChange={(value) => setTraverseStepSize(value)}
+4 -3
View File
@@ -11,15 +11,16 @@ import { toast } from "sonner";
export function TroubleshootPage() {
const [isRebootLoading, setIsRebootLoading] = useState(false);
const [isRestartLoading, setIsRestartLoading] = useState(false);
const { getLogsBySource, clearLogs } = useLogsStore();
const { getLogsBySource } = useLogsStore();
// Perhaps we just need to clear the logs ?
// Get backend logs for display
const backendLogs = getLogsBySource("qitech-control-server");
const logLines = backendLogs.map((log) => log.raw);
console.log(backendLogs);
const handleRebootHmi = async () => {
setIsRebootLoading(true);
try {
const result = await rebootHmi();
if (result.success) {
+5
View File
@@ -22,6 +22,8 @@ export function UpdateExecutePage() {
startUpdate,
stopUpdate,
addTerminalLine,
clearTerminalLines,
resetUpdateState,
} = useUpdateStore();
// Set update info from search params when component mounts or search changes
@@ -49,6 +51,7 @@ export function UpdateExecutePage() {
};
startUpdate();
// Perhaps we just need to clear the logs ?
const res = await updateExecute(updateInfo, addTerminalLine);
stopUpdate();
@@ -66,12 +69,14 @@ export function UpdateExecutePage() {
const res = await updateCancelWithStore();
if (res.success) {
toast.info("Update cancelled successfully");
clearTerminalLines();
} else {
toast.error("Failed to cancel update: " + res.error);
}
} catch (error: any) {
toast.error("Failed to cancel update: " + error.message);
}
resetUpdateState();
};
return (
+1 -1
View File
@@ -33,7 +33,6 @@ const initialState: LogsState = {
export const useLogsStore = create<LogsStore>((set, get) => ({
...initialState,
addLogEntry: (entry) =>
set(
produce((state: LogsState) => {
@@ -69,6 +68,7 @@ export const useLogsStore = create<LogsStore>((set, get) => ({
),
getLogsBySource: (source) => {
console.log(source);
const state = get();
return state.entries.filter((entry) => entry.source === source);
},
+9 -4
View File
@@ -78,11 +78,16 @@ export const useUpdateStore = create<UpdateStore>((set) => ({
addTerminalLine: (line) =>
set(
produce((state: UpdateState) => {
state.terminalLines.push(line);
console.log(state.terminalLines);
// Keep only last 10000 lines to prevent memory issues
if (state.terminalLines.length > 10000) {
state.terminalLines.splice(0, state.terminalLines.length - 10000);
const lastLine = state.terminalLines[state.terminalLines.length - 1];
if (line !== lastLine) {
state.terminalLines.push(line);
// Keep only last 10000 lines to prevent memory issues
if (state.terminalLines.length > 10000) {
state.terminalLines.splice(0, state.terminalLines.length - 10000);
}
}
}),
),
+1 -1
View File
@@ -14,7 +14,7 @@ tokio = { version = "1.45.1", features = ["macros", "rt-multi-thread"] }
ethercat_hal = { path = "../ethercat-hal" }
control_core = { path = "../control-core" }
futures = "0.3.31"
anyhow = "1.0.99"
anyhow = "1.0.100"
[package.metadata.cargo-machete]
+1 -1
View File
@@ -7,7 +7,7 @@ edition = "2024"
workspace = true
[dependencies]
anyhow = "1.0.99"
anyhow = "1.0.100"
ethercrab = "0.6"
ethercat_hal_derive = { version = "0.1.0", path = "../ethercat-hal-derive" }
bitvec = { version = "1.0.1", features = ["alloc"] }
+3 -3
View File
@@ -16,7 +16,7 @@ control_core_derive = { path = "../control-core-derive" }
uom = { version = "0.36.0", default-features = false, features = ["f64"] }
serde = "1.0.217"
anyhow = "1.0.99"
anyhow = "1.0.100"
bitvec = "1.0.1"
lazy_static = "1.5.0"
@@ -26,14 +26,14 @@ euclid = "0.22.11"
# concurrency
smol = "2.0.2"
tokio = { version = "1.45.1", features = ["rt-multi-thread"] }
regex = "1.11.2"
regex = "1.11.3"
futures = "0.3.31"
# web
serde_json = "1.0.143"
socketioxide = { version = "0.17.2", features = ["msgpack"] }
tower-http = { version = "0.6.6", features = ["cors", "trace", "fs"] }
axum = { version = "0.8.4", features = ["macros"] }
axum = { version = "0.8.6", features = ["macros"] }
# serial
serialport = "4.7.3"
+19
View File
@@ -1,6 +1,7 @@
use crate::ethercat::config::{MAX_SUBDEVICES, PDI_LEN};
use crate::performance_metrics::EthercatPerformanceMetrics;
use crate::serial::registry::SERIAL_DEVICE_REGISTRY;
use crate::socketio::main_namespace::machines_event::MachineObj;
use crate::socketio::namespaces::Namespaces;
use control_core::machines::Machine;
use control_core::machines::identification::{DeviceIdentification, MachineIdentificationUnique};
@@ -90,4 +91,22 @@ impl AppState {
performance_metrics: Arc::new(RwLock::new(EthercatPerformanceMetrics::new())),
}
}
pub fn get_machine_objs(&self) -> Vec<MachineObj> {
let machines = self.machines.read_blocking();
machines
.iter()
.map(|machine| {
let error = {
let slot = machine.1.lock_blocking();
slot.machine_connection.to_error().map(|e| e.to_string())
};
MachineObj {
machine_identification_unique: machine.0.clone(),
error,
}
})
.collect()
}
}
+1 -1
View File
@@ -235,7 +235,7 @@ pub async fn setup_loop(
.write()
.await
.main_namespace;
let event = MachinesEventBuilder().build(app_state_clone.clone()).await;
let event = MachinesEventBuilder().build(app_state_clone.clone());
main_namespace.emit(MainNamespaceEvents::MachinesEvent(event));
});
+3 -1
View File
@@ -1,6 +1,7 @@
use crate::app_state::AppState;
use crate::panic::{PanicDetails, send_panic};
use bitvec::prelude::*;
use control_core::machines::connection::MachineConnection;
use control_core::realtime::{set_core_affinity, set_realtime_priority};
use smol::channel::Sender;
use std::sync::Arc;
@@ -144,7 +145,8 @@ pub async fn loop_once<'maindevice>(app_state: Arc<AppState>) -> Result<(), anyh
let now = std::time::Instant::now();
for machine in machine_guard.iter() {
if let Ok(machine) = machine.1 {
let connection = &machine.1.lock_blocking().machine_connection;
if let MachineConnection::Connected(machine) = connection {
// if the machine is currenlty locked (likely processing API call)
// we skip the machine
if let Some(mut machine_guard) = machine.try_lock() {
+6 -13
View File
@@ -13,8 +13,7 @@ use control_core::{
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use smol::channel::Sender;
use socketioxide::extract::SocketRef;
use smol::lock::Mutex;
use tracing::instrument;
#[derive(Serialize, Debug, Clone, Default)]
@@ -94,7 +93,7 @@ enum Mutation {
#[derive(Debug)]
pub struct AquaPathV1Namespace {
pub namespace: Namespace,
pub namespace: Arc<Mutex<Namespace>>,
}
impl NamespaceCacheingLogic<AquaPathV1Events> for AquaPathV1Namespace {
@@ -102,15 +101,9 @@ impl NamespaceCacheingLogic<AquaPathV1Events> for AquaPathV1Namespace {
fn emit(&mut self, events: AquaPathV1Events) {
let event = Arc::new(events.event_value());
let buffer_fn = events.event_cache_fn();
self.namespace.emit(event, &buffer_fn);
}
}
impl AquaPathV1Namespace {
pub fn new(socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>) -> Self {
Self {
namespace: Namespace::new(socket_queue_tx),
}
let mut namespace = self.namespace.lock_blocking();
namespace.emit(event, &buffer_fn);
}
}
@@ -156,7 +149,7 @@ impl MachineApi for AquaPathV1 {
Ok(())
}
fn api_event_namespace(&mut self) -> &mut Namespace {
&mut self.namespace.namespace
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
self.namespace.namespace.clone()
}
}
+5 -2
View File
@@ -1,8 +1,9 @@
use control_core::{
machines::identification::MachineIdentification, socketio::namespace::NamespaceCacheingLogic,
machines::identification::{MachineIdentification, MachineIdentificationUnique},
socketio::namespace::NamespaceCacheingLogic,
};
use control_core_derive::Machine;
use control_core_derive::Machine;
use serde::{Deserialize, Serialize};
use std::time::Instant;
use uom::si::{
@@ -79,12 +80,14 @@ impl Default for Flow {
#[derive(Debug, Machine)]
pub struct AquaPathV1 {
machine_identification_unique: MachineIdentificationUnique,
namespace: AquaPathV1Namespace,
mode: AquaPathV1Mode,
last_measurement_emit: Instant,
front_controller: Controller,
back_controller: Controller,
}
impl AquaPathV1 {
pub const MACHINE_IDENTIFICATION: MachineIdentification = MachineIdentification {
vendor: VENDOR_QITECH,
+4 -1
View File
@@ -338,7 +338,10 @@ impl MachineNewTrait for AquaPathV1 {
);
let mut water_cooling = Self {
namespace: AquaPathV1Namespace::new(params.socket_queue_tx.clone()),
machine_identification_unique: params.get_machine_identification_unique(),
namespace: AquaPathV1Namespace {
namespace: params.namespace.clone(),
},
mode: AquaPathV1Mode::Standby,
last_measurement_emit: Instant::now(),
front_controller,
+1
View File
@@ -2,6 +2,7 @@ use std::time::{Duration, Instant};
use super::BufferV1;
use control_core::machines::new::MachineAct;
impl MachineAct for BufferV1 {
fn act(&mut self, now: Instant) {
// if last measurement is older than 1 second, emit a new measurement
+11 -15
View File
@@ -2,7 +2,10 @@ use std::{sync::Arc, time::Duration};
use super::{BufferV1, BufferV1Mode};
use control_core::{
machines::{api::MachineApi, identification::MachineIdentificationUnique},
machines::{
api::MachineApi, connection::MachineCrossConnectionState,
identification::MachineIdentificationUnique,
},
socketio::{
event::{Event, GenericEvent},
namespace::{
@@ -13,8 +16,7 @@ use control_core::{
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use smol::channel::Sender;
use socketioxide::extract::SocketRef;
use smol::lock::Mutex;
use tracing::instrument;
#[derive(Serialize, Debug, Clone, Default)]
@@ -31,7 +33,7 @@ pub struct StateEvent {
/// mode state
pub mode_state: ModeState,
/// connected machine state
pub connected_machine_state: ConnectedMachineState,
pub connected_machine_state: MachineCrossConnectionState,
}
impl StateEvent {
@@ -78,7 +80,7 @@ enum Mutation {
#[derive(Debug)]
pub struct Buffer1Namespace {
pub namespace: Namespace,
pub namespace: Arc<Mutex<Namespace>>,
}
impl NamespaceCacheingLogic<BufferV1Events> for Buffer1Namespace {
@@ -86,15 +88,9 @@ impl NamespaceCacheingLogic<BufferV1Events> for Buffer1Namespace {
fn emit(&mut self, events: BufferV1Events) {
let event = Arc::new(events.event_value());
let buffer_fn = events.event_cache_fn();
self.namespace.emit(event, &buffer_fn);
}
}
impl Buffer1Namespace {
pub fn new(socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>) -> Self {
Self {
namespace: Namespace::new(socket_queue_tx),
}
let mut namespace = self.namespace.lock_blocking();
namespace.emit(event, &buffer_fn);
}
}
@@ -132,7 +128,7 @@ impl MachineApi for BufferV1 {
Ok(())
}
fn api_event_namespace(&mut self) -> &mut Namespace {
&mut self.namespace.namespace
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
self.namespace.namespace.clone()
}
}
+18 -88
View File
@@ -5,26 +5,20 @@ pub mod new;
use api::{Buffer1Namespace, BufferV1Events, LiveValuesEvent, ModeState, StateEvent};
use buffer_tower_controller::BufferTowerController;
use control_core::machines::connection::{CrossConnectableMachine, MachineCrossConnection};
use control_core::{
machines::{
ConnectedMachine, ConnectedMachineData, downcast_machine,
identification::{MachineIdentification, MachineIdentificationUnique},
manager::MachineManager,
},
socketio::namespace::NamespaceCacheingLogic,
};
use control_core_derive::Machine;
use futures::executor::block_on;
use serde::{Deserialize, Serialize};
use smol::lock::{Mutex, RwLock};
use std::{
sync::{Arc, Weak},
time::Instant,
};
use smol::lock::RwLock;
use std::{sync::Weak, time::Instant};
use crate::machines::{
MACHINE_BUFFER_V1, VENDOR_QITECH, buffer1::api::ConnectedMachineState, winder2::Winder2,
};
use crate::machines::{MACHINE_BUFFER_V1, VENDOR_QITECH, winder2::Winder2};
#[derive(Debug, Machine)]
pub struct BufferV1 {
@@ -40,12 +34,18 @@ pub struct BufferV1 {
pub machine_identification_unique: MachineIdentificationUnique,
// connected machines
pub connected_winder: Option<ConnectedMachine<Weak<Mutex<Winder2>>>>,
pub connected_winder: MachineCrossConnection<BufferV1, Winder2>,
// mode
mode: BufferV1Mode,
}
impl CrossConnectableMachine<BufferV1, Winder2> for BufferV1 {
fn get_cross_connection(&mut self) -> &mut MachineCrossConnection<BufferV1, Winder2> {
&mut self.connected_winder
}
}
impl std::fmt::Display for BufferV1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "BufferV1")
@@ -57,9 +57,6 @@ impl BufferV1 {
vendor: VENDOR_QITECH,
machine: MACHINE_BUFFER_V1,
};
}
impl BufferV1 {
pub fn emit_live_values(&mut self) {
let live_values = LiveValuesEvent {};
@@ -72,28 +69,13 @@ impl BufferV1 {
mode_state: ModeState {
mode: self.mode.clone(),
},
connected_machine_state: ConnectedMachineState {
machine_identification_unique: self.connected_winder.as_ref().map(
|connected_machine| {
ConnectedMachineData::from(connected_machine).machine_identification_unique
},
),
is_available: self
.connected_winder
.as_ref()
.map(|connected_machine| {
ConnectedMachineData::from(connected_machine).is_available
})
.unwrap_or(false),
},
connected_machine_state: self.connected_winder.to_state(),
};
let event = state.build();
self.namespace.emit(BufferV1Events::State(event));
}
}
impl BufferV1 {
// To be implemented
fn fill_buffer(&mut self) {
todo!();
@@ -146,17 +128,13 @@ impl BufferV1 {
BufferV1Mode::EmptyingBuffer => self.switch_to_emptying(),
}
}
}
impl BufferV1 {
fn set_mode_state(&mut self, mode: BufferV1Mode) {
self.switch_mode(mode);
self.emit_state();
}
}
/// Connecting/Disconnecting machine
impl BufferV1 {
/// Connecting/Disconnecting machine
/// set connected winder
pub fn set_connected_winder(
&mut self,
@@ -168,34 +146,13 @@ impl BufferV1 {
) {
return;
}
let machine_manager_arc = match self.machine_manager.upgrade() {
Some(machine_manager_arc) => machine_manager_arc,
None => return,
};
let machine_manager_guard = block_on(machine_manager_arc.read());
let winder2_weak = machine_manager_guard.get_machine_weak(&machine_identification_unique);
let winder2_weak = match winder2_weak {
Some(winder2_weak) => winder2_weak,
None => return,
};
let winder2_strong = match winder2_weak.upgrade() {
Some(winder2_strong) => winder2_strong,
None => return,
};
let winder2: Arc<Mutex<Winder2>> = block_on(downcast_machine::<Winder2>(winder2_strong))
.expect("failed downcasting machine");
let machine = Arc::downgrade(&winder2);
self.connected_winder = Some(ConnectedMachine {
machine_identification_unique,
machine,
});
self.connected_winder
.set_connected_machine(&machine_identification_unique);
self.emit_state();
self.reverse_connect();
self.connected_winder.reverse_connect();
}
/// disconnect winder
@@ -209,36 +166,9 @@ impl BufferV1 {
) {
return;
}
if let Some(connected) = &self.connected_winder {
if let Some(winder2_arc) = connected.machine.upgrade() {
let future = async move {
let mut winder2 = winder2_arc.lock().await;
if winder2.connected_buffer.is_some() {
winder2.connected_buffer = None;
winder2.emit_state();
}
};
smol::spawn(future).detach();
}
}
self.connected_winder = None;
self.emit_state();
}
/// initiate connection from winder to buffer
pub fn reverse_connect(&mut self) {
let machine_identification_unique = self.machine_identification_unique.clone();
if let Some(connected) = &self.connected_winder {
if let Some(winder2_arc) = connected.machine.upgrade() {
let future = async move {
let mut winder2 = winder2_arc.lock().await;
if winder2.connected_buffer.is_none() {
winder2.set_connected_buffer(machine_identification_unique);
}
};
smol::spawn(future).detach();
}
}
self.connected_winder.disconnect();
self.emit_state();
}
}
+106 -190
View File
@@ -1,208 +1,125 @@
use std::time::Instant;
use anyhow::Error;
use control_core::machines::identification::DeviceHardwareIdentification;
use control_core::machines::connection::MachineCrossConnection;
use control_core::machines::new::{
MachineNewHardware, MachineNewParams, MachineNewTrait, get_device_identification_by_role,
get_ethercat_device_by_index, get_subdevice_by_index, validate_no_role_dublicates,
MachineNewHardware, MachineNewParams, MachineNewTrait, validate_no_role_dublicates,
validate_same_machine_identification_unique,
};
use ethercat_hal::coe::ConfigurableDevice;
use ethercat_hal::devices::ek1100::EK1100;
use ethercat_hal::devices::ek1100::EK1100_IDENTITY_A;
use ethercat_hal::devices::el7031_0030::coe::EL7031_0030Configuration;
use ethercat_hal::devices::el7031_0030::pdo::EL7031_0030PredefinedPdoAssignment;
use ethercat_hal::devices::el7031_0030::{self, EL7031_0030, EL7031_0030_IDENTITY_A};
use ethercat_hal::devices::el7041_0052::coe::EL7041_0052Configuration;
use ethercat_hal::devices::el7041_0052::{EL7041_0052, EL7041_0052_IDENTITY_A, EL7041_0052Port};
use ethercat_hal::devices::{EthercatDeviceUsed, downcast_device, subdevice_identity_to_tuple};
use ethercat_hal::io::stepper_velocity_el70x1::StepperVelocityEL70x1;
use ethercat_hal::shared_config;
use ethercat_hal::shared_config::el70x1::{EL70x1OperationMode, StmMotorConfiguration};
use ethercat_hal::{
coe::ConfigurableDevice,
devices::el7031_0030::coe::EL7031_0030Configuration,
devices::el7031_0030::pdo::EL7031_0030PredefinedPdoAssignment,
devices::el7041_0052::coe::EL7041_0052Configuration,
devices::{
EthercatDeviceUsed,
ek1100::{EK1100, EK1100_IDENTITY_A},
el7031_0030::{EL7031_0030, EL7031_0030_IDENTITY_A},
el7041_0052::{EL7041_0052, EL7041_0052_IDENTITY_A, EL7041_0052Port},
},
io::stepper_velocity_el70x1::StepperVelocityEL70x1,
shared_config,
shared_config::el70x1::{EL70x1OperationMode, StmMotorConfiguration},
};
use crate::machines::buffer1::BufferV1Mode;
use crate::machines::buffer1::buffer_tower_controller::BufferTowerController;
use crate::machines::get_ethercat_device;
use super::{BufferV1, api::Buffer1Namespace};
impl MachineNewTrait for BufferV1 {
fn new<'maindevice>(params: &MachineNewParams) -> Result<Self, Error> {
fn new<'maindevice>(
params: &MachineNewParams<'maindevice, '_, '_, '_, '_, '_, '_>,
) -> Result<Self, Error> {
// validate general stuff
let device_identification = params.device_group.to_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!(
"[{}::MachineNewTrait/Buffer::new] MachineNewHardware is not Ethercat",
module_path!()
));
}
};
let hardware: &&control_core::machines::new::MachineNewHardwareEthercat<'_, '_, '_> =
match &params.hardware {
MachineNewHardware::Ethercat(x) => x,
_ => {
return Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Buffer::new] MachineNewHardware is not Ethercat",
module_path!()
));
}
};
smol::block_on(async {
// Role 0
// Buscoupler
// EK1100
// Role 0 - Buscoupler EK1100
let _ek1100 =
get_ethercat_device::<EK1100>(hardware, params, 0, [EK1100_IDENTITY_A].to_vec())
.await?
.0;
// Role 1 - Stepper Buffer EL7041-0052
let (el7041, subdevice) = get_ethercat_device::<EL7041_0052>(
hardware,
params,
1,
[EL7041_0052_IDENTITY_A].to_vec(),
)
.await?;
let el7041_config = EL7041_0052Configuration {
stm_features: shared_config::el70x1::StmFeatures {
operation_mode: EL70x1OperationMode::DirectVelocity,
..Default::default()
},
stm_motor: StmMotorConfiguration {
max_current: 6000,
..Default::default()
},
..Default::default()
};
el7041
.write()
.await
.write_config(&subdevice, &el7041_config)
.await?;
{
let device_identification =
get_device_identification_by_role(params.device_group, 0)?;
let device_hardware_identification_ethercat =
match &device_identification.device_hardware_identification {
DeviceHardwareIdentification::Ethercat(
device_hardware_identification_ethercat,
) => device_hardware_identification_ethercat,
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Buffer::new] Device with role 0 is not Ethercat",
module_path!()
))?, //uncommented
};
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
let subdevice = get_subdevice_by_index(hardware.subdevices, subdevice_index)?;
let subdevice_identity = subdevice.identity();
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
EK1100_IDENTITY_A => {
let ethercat_device = get_ethercat_device_by_index(
hardware.ethercat_devices,
subdevice_index,
)?;
downcast_device::<EK1100>(ethercat_device).await?
}
_ => {
return Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Buffer::new] Device with role 0 is not an EK1100",
module_path!()
));
}
};
{
let mut device_guard = device.write().await;
device_guard.set_used(true);
}
let mut device_guard = el7041.write().await;
device_guard.set_used(true);
}
// Role 1
// 1x Stepper Buffer
// EL7041-0052
let (el7041, _el7041_config) = {
let device_identification =
get_device_identification_by_role(params.device_group, 1)?;
let device_hardware_identification_ethercat =
match &device_identification.device_hardware_identification {
DeviceHardwareIdentification::Ethercat(
device_hardware_identification_ethercat,
) => device_hardware_identification_ethercat,
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Buffer::new] Device with role 1 is not Ethercat",
module_path!()
))?,
};
let subdevice = get_subdevice_by_index(
hardware.subdevices,
device_hardware_identification_ethercat.subdevice_index,
)?;
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
let subdevice_identity = subdevice.identity();
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
EL7041_0052_IDENTITY_A => {
let ethercat_device = get_ethercat_device_by_index(
hardware.ethercat_devices,
subdevice_index,
)?;
downcast_device::<EL7041_0052>(ethercat_device).await?
}
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Buffer::new] Device with role 1 is not an EL7041-0052",
module_path!()
))?,
};
let config = EL7041_0052Configuration {
stm_features: shared_config::el70x1::StmFeatures {
operation_mode: EL70x1OperationMode::DirectVelocity,
..Default::default()
},
stm_motor: StmMotorConfiguration {
max_current: 6000,
..Default::default()
},
// Role 2 - Stepper Puller EL7031-0030
let (el7031, subdevice) = get_ethercat_device::<EL7031_0030>(
hardware,
params,
2,
[EL7031_0030_IDENTITY_A].to_vec(),
)
.await?;
let el7031_config = EL7031_0030Configuration {
stm_features: ethercat_hal::devices::el7031_0030::coe::StmFeatures {
operation_mode: EL70x1OperationMode::DirectVelocity,
// Max Speed of 1000 steps/s
// Max @ 8cm diameter = approx 75 m/min
speed_range: shared_config::el70x1::EL70x1SpeedRange::Steps1000,
..Default::default()
};
device
.write()
.await
.write_config(subdevice, &config)
.await?;
{
let mut device_guard = device.write().await;
device_guard.set_used(true);
}
(device, config)
},
stm_motor: StmMotorConfiguration {
max_current: 1500,
..Default::default()
},
pdo_assignment: EL7031_0030PredefinedPdoAssignment::VelocityControlCompact,
..Default::default()
};
// Role 2
// 1x Stepper Puller
// EL7031
let (_el7031_0030, _el7031_0030_config) = {
let device_identification =
get_device_identification_by_role(params.device_group, 2)?;
let device_hardware_identification_ethercat =
match &device_identification.device_hardware_identification {
DeviceHardwareIdentification::Ethercat(
device_hardware_identification_ethercat,
) => device_hardware_identification_ethercat,
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Buffer::new] Device with role 2 is not Ethercat",
module_path!()
))?,
};
let subdevice = get_subdevice_by_index(
hardware.subdevices,
device_hardware_identification_ethercat.subdevice_index,
)?;
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
let subdevice_identity = subdevice.identity();
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
EL7031_0030_IDENTITY_A => {
let ethercat_device = get_ethercat_device_by_index(
hardware.ethercat_devices,
subdevice_index,
)?;
downcast_device::<EL7031_0030>(ethercat_device).await?
}
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Buffer1::new] Device with role 2 is not an EL7031-0030",
module_path!()
))?,
};
let config = EL7031_0030Configuration {
stm_features: el7031_0030::coe::StmFeatures {
operation_mode: EL70x1OperationMode::DirectVelocity,
// Max Speed of 1000 steps/s
// Max @ 8cm diameter = approx 75 m/min
speed_range: shared_config::el70x1::EL70x1SpeedRange::Steps1000,
..Default::default()
},
stm_motor: StmMotorConfiguration {
max_current: 1500,
..Default::default()
},
pdo_assignment: EL7031_0030PredefinedPdoAssignment::VelocityControlCompact,
..Default::default()
};
device
.write()
.await
.write_config(subdevice, &config)
.await?;
{
let mut device_guard = device.write().await;
device_guard.set_used(true);
}
(device, config)
};
el7031
.write()
.await
.write_config(&subdevice, &el7031_config)
.await?;
{
let mut device_guard = el7031.write().await;
device_guard.set_used(true);
}
// Controller
let buffer_tower_controller = BufferTowerController::new(StepperVelocityEL70x1::new(
@@ -210,23 +127,22 @@ impl MachineNewTrait for BufferV1 {
EL7041_0052Port::STM1,
));
let machine_id = params
.device_group
.first()
.expect("device group must have at least one device")
.device_machine_identification
.machine_identification_unique
.clone();
let machine_identification_unique = params.get_machine_identification_unique();
// create buffer instance
let mut buffer: Self = Self {
namespace: Buffer1Namespace::new(params.socket_queue_tx.clone()),
let mut buffer: BufferV1 = Self {
namespace: Buffer1Namespace {
namespace: params.namespace.clone(),
},
last_measurement_emit: Instant::now(),
mode: BufferV1Mode::Standby,
buffer_tower_controller,
machine_manager: params.machine_manager.clone(),
machine_identification_unique: machine_id,
connected_winder: None,
machine_identification_unique: machine_identification_unique.clone(),
connected_winder: MachineCrossConnection::new(
params.machine_manager.clone(),
&machine_identification_unique,
),
};
buffer.emit_state();
Ok(buffer)
+17 -24
View File
@@ -1,26 +1,25 @@
use super::{ExtruderV2Mode, HeatingType, mitsubishi_cs80::MotorStatus};
use super::{ExtruderV2Mode, mitsubishi_cs80::MotorStatus};
#[cfg(not(feature = "mock-machine"))]
use super::ExtruderV2;
#[cfg(feature = "mock-machine")]
use super::mock::ExtruderV2;
#[cfg(not(feature = "mock-machine"))]
use crate::machines::extruder1::HeatingType;
use control_core::{
machines::api::MachineApi,
socketio::{
event::{Event, GenericEvent},
namespace::{
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_duration,
cache_first_and_last_event,
},
#[cfg(not(feature = "mock-machine"))]
use control_core::machines::api::MachineApi;
use control_core::socketio::{
event::{Event, GenericEvent},
namespace::{
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_duration,
cache_first_and_last_event,
},
};
use control_core_derive::BuildEvent;
use serde::{Deserialize, Serialize};
#[cfg(not(feature = "mock-machine"))]
use serde_json::Value;
use smol::channel::Sender;
use socketioxide::extract::SocketRef;
use smol::lock::Mutex;
use std::{sync::Arc, time::Duration};
use tracing::instrument;
use uom::si::{
@@ -225,7 +224,7 @@ pub enum Mutation {
#[derive(Debug)]
pub struct ExtruderV2Namespace {
pub namespace: Namespace,
pub namespace: Arc<Mutex<Namespace>>,
}
impl NamespaceCacheingLogic<ExtruderV2Events> for ExtruderV2Namespace {
@@ -233,15 +232,9 @@ impl NamespaceCacheingLogic<ExtruderV2Events> for ExtruderV2Namespace {
fn emit(&mut self, events: ExtruderV2Events) {
let event = Arc::new(events.event_value());
let buffer_fn = events.event_cache_fn();
self.namespace.emit(event, &buffer_fn);
}
}
impl ExtruderV2Namespace {
pub fn new(socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>) -> Self {
Self {
namespace: Namespace::new(socket_queue_tx),
}
let mut namespace = self.namespace.lock_blocking();
namespace.emit(event, &buffer_fn);
}
}
@@ -303,7 +296,7 @@ impl MachineApi for ExtruderV2 {
Ok(())
}
fn api_event_namespace(&mut self) -> &mut Namespace {
&mut self.namespace.namespace
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
self.namespace.namespace.clone()
}
}
+15 -12
View File
@@ -8,16 +8,21 @@ use crate::machines::extruder1::{
RotationState, ScrewState, StateEvent,
},
};
use control_core::{
helpers::hasher_serializer::hash_with_serde_model,
socketio::{event::BuildEvent, namespace::NamespaceCacheingLogic},
};
use uom::si::{
angular_velocity::revolution_per_minute,
f64::{AngularVelocity, Pressure, ThermodynamicTemperature},
pressure::bar,
thermodynamic_temperature::degree_celsius,
};
#[cfg(not(feature = "mock-machine"))]
use control_core::helpers::hasher_serializer::hash_with_serde_model;
#[cfg(not(feature = "mock-machine"))]
use control_core::socketio::event::BuildEvent;
#[cfg(not(feature = "mock-machine"))]
use control_core::socketio::namespace::NamespaceCacheingLogic;
#[cfg(not(feature = "mock-machine"))]
use uom::si::{angular_velocity::revolution_per_minute, thermodynamic_temperature::degree_celsius};
#[cfg(not(feature = "mock-machine"))]
use uom::si::angular_velocity::AngularVelocity;
#[cfg(not(feature = "mock-machine"))]
use uom::si::pressure::{Pressure, bar};
#[cfg(not(feature = "mock-machine"))]
use uom::si::thermodynamic_temperature::ThermodynamicTemperature;
#[cfg(not(feature = "mock-machine"))]
impl ExtruderV2 {
@@ -199,7 +204,6 @@ impl ExtruderV2 {
}
pub fn set_nozzle_pressure_limit(&mut self, pressure: f64) {
use uom::si::pressure::bar;
self.screw_speed_controller
.set_nozzle_pressure_limit(Pressure::new::<bar>(pressure));
self.emit_state();
@@ -244,7 +248,6 @@ impl ExtruderV2 {
}
pub fn set_target_rpm(&mut self, rpm: f64) {
use uom::si::angular_velocity::revolution_per_minute;
self.screw_speed_controller
.set_target_screw_rpm(AngularVelocity::new::<revolution_per_minute>(rpm));
self.emit_state();
+5 -2
View File
@@ -1,5 +1,8 @@
use crate::machines::extruder1::{HeatingType, api::Mutation, mock::ExtruderV2};
use control_core::machines::api::MachineApi;
use control_core::socketio::namespace::Namespace;
use smol::lock::Mutex;
use std::sync::Arc;
impl MachineApi for ExtruderV2 {
fn api_mutate(&mut self, request_body: serde_json::Value) -> Result<(), anyhow::Error> {
@@ -38,7 +41,7 @@ impl MachineApi for ExtruderV2 {
Ok(())
}
fn api_event_namespace(&mut self) -> &mut control_core::socketio::namespace::Namespace {
&mut self.namespace.namespace
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
self.namespace.namespace.clone()
}
}
@@ -1,6 +1,8 @@
#[cfg(feature = "mock-machine")]
use control_core::machines::identification::MachineIdentification;
#[cfg(feature = "mock-machine")]
use control_core::machines::identification::MachineIdentificationUnique;
#[cfg(feature = "mock-machine")]
use control_core_derive::Machine;
#[cfg(feature = "mock-machine")]
use std::time::Instant;
@@ -31,6 +33,7 @@ pub mod new;
#[cfg(feature = "mock-machine")]
#[derive(Debug, Machine)]
pub struct ExtruderV2 {
machine_identification_unique: MachineIdentificationUnique,
namespace: ExtruderV2Namespace,
last_measurement_emit: Instant,
last_status_hash: Option<u64>,
+4 -1
View File
@@ -32,7 +32,10 @@ impl control_core::machines::new::MachineNewTrait for ExtruderV2 {
let now = std::time::Instant::now();
let mut extruder_mock_machine = Self {
namespace: ExtruderV2Namespace::new(params.socket_queue_tx.clone()),
machine_identification_unique: params.get_machine_identification_unique(),
namespace: ExtruderV2Namespace {
namespace: params.namespace.clone(),
},
last_measurement_emit: now,
mode: ExtruderV2Mode::Standby, // Start in standby mode
+26 -11
View File
@@ -1,15 +1,26 @@
use crate::machines::{MACHINE_EXTRUDER_V1, VENDOR_QITECH};
use api::ExtruderV2Namespace;
use control_core::machines::identification::MachineIdentification;
use control_core_derive::Machine;
use screw_speed_controller::ScrewSpeedController;
use serde::{Deserialize, Serialize};
#[cfg(not(feature = "mock-machine"))]
use std::time::Instant;
use temperature_controller::TemperatureController;
use uom::si::{
electric_current::ampere, electric_potential::volt, f64::ThermodynamicTemperature,
thermodynamic_temperature::degree_celsius,
#[cfg(not(feature = "mock-machine"))]
use control_core::machines::identification::{MachineIdentification, MachineIdentificationUnique};
#[cfg(not(feature = "mock-machine"))]
use control_core_derive::Machine;
use serde::{Deserialize, Serialize};
#[cfg(not(feature = "mock-machine"))]
use uom::si::electric_current::ampere;
#[cfg(not(feature = "mock-machine"))]
use uom::si::electric_potential::volt;
use uom::si::{f64::ThermodynamicTemperature, thermodynamic_temperature::degree_celsius};
#[cfg(not(feature = "mock-machine"))]
use crate::machines::{
MACHINE_EXTRUDER_V1, VENDOR_QITECH,
extruder1::{
api::ExtruderV2Namespace, screw_speed_controller::ScrewSpeedController,
temperature_controller::TemperatureController,
},
};
pub mod act;
pub mod api;
pub mod emit;
@@ -52,9 +63,10 @@ pub enum HeatingType {
Middle,
}
#[derive(Debug, Machine)]
#[cfg(not(feature = "mock-machine"))]
#[derive(Debug, Machine)]
pub struct ExtruderV2 {
machine_identification_unique: MachineIdentificationUnique,
namespace: ExtruderV2Namespace,
last_measurement_emit: Instant,
last_status_hash: Option<u64>,
@@ -126,6 +138,9 @@ impl ExtruderV2 {
}
// Funktionen ohne emit_state bleiben hier
// Set all relais to ZERO
// We dont need a function to enable again though, as the act Loop will detect the mode
fn turn_heating_off(&mut self) {
self.temperature_controller_back.disable();
self.temperature_controller_front.disable();
+99 -264
View File
@@ -1,64 +1,87 @@
#[cfg(not(feature = "mock-machine"))]
use super::{
ExtruderV2, ExtruderV2Mode, Heating, api::ExtruderV2Namespace, mitsubishi_cs80::MitsubishiCS80,
screw_speed_controller::ScrewSpeedController,
};
use crate::machines::extruder1::temperature_controller::TemperatureController;
use crate::machines::get_ethercat_device;
#[cfg(not(feature = "mock-machine"))]
use anyhow::Error;
use control_core::machines::{
identification::DeviceHardwareIdentification,
new::{
MachineNewHardware, MachineNewParams, MachineNewTrait, get_device_identification_by_role,
get_ethercat_device_by_index, get_subdevice_by_index, validate_no_role_dublicates,
validate_same_machine_identification_unique,
},
#[cfg(not(feature = "mock-machine"))]
use control_core::machines::new::MachineNewHardware;
#[cfg(not(feature = "mock-machine"))]
use control_core::machines::new::{MachineNewParams, MachineNewTrait};
#[cfg(not(feature = "mock-machine"))]
use control_core::machines::new::{
validate_no_role_dublicates, validate_same_machine_identification_unique,
};
#[cfg(not(feature = "mock-machine"))]
use ethercat_hal::coe::ConfigurableDevice;
#[cfg(not(feature = "mock-machine"))]
use ethercat_hal::devices::{
EthercatDeviceUsed,
ek1100::EK1100,
ek1100::EK1100_IDENTITY_A,
el1002::EL1002,
el1002::EL1002_IDENTITY_A,
el6021::EL6021,
el6021::EL6021Configuration,
el6021::EL6021Port,
el6021::{EL6021_IDENTITY_A, EL6021_IDENTITY_B, EL6021_IDENTITY_C, EL6021_IDENTITY_D},
};
#[cfg(not(feature = "mock-machine"))]
use std::time::Duration;
#[cfg(not(feature = "mock-machine"))]
use std::time::Instant;
#[cfg(not(feature = "mock-machine"))]
use uom::si::angular_velocity::AngularVelocity;
#[cfg(not(feature = "mock-machine"))]
use uom::si::angular_velocity::revolution_per_minute;
#[cfg(not(feature = "mock-machine"))]
use uom::si::pressure::Pressure;
#[cfg(not(feature = "mock-machine"))]
use uom::si::pressure::bar;
#[cfg(not(feature = "mock-machine"))]
use ethercat_hal::{
coe::ConfigurableDevice,
devices::{
EthercatDeviceUsed, downcast_device,
ek1100::{EK1100, EK1100_IDENTITY_A},
el1002::{EL1002, EL1002_IDENTITY_A},
el2004::{EL2004, EL2004_IDENTITY_A, EL2004Port},
el3021::{EL3021, EL3021_IDENTITY_A, EL3021Port},
el3204::{EL3204, EL3204_IDENTITY_A, EL3204_IDENTITY_B, EL3204Port},
el6021::{
self, EL6021, EL6021_IDENTITY_A, EL6021_IDENTITY_B, EL6021_IDENTITY_C,
EL6021_IDENTITY_D, EL6021Configuration,
},
subdevice_identity_to_tuple,
},
io::{
analog_input::AnalogInput, digital_output::DigitalOutput,
serial_interface::SerialInterface, temperature_input::TemperatureInput,
},
};
use std::time::{Duration, Instant};
use uom::si::{
angular_velocity::revolution_per_minute,
f64::{AngularVelocity, Pressure, ThermodynamicTemperature},
pressure::bar,
thermodynamic_temperature::degree_celsius,
#[cfg(not(feature = "mock-machine"))]
use uom::si::thermodynamic_temperature::{ThermodynamicTemperature, degree_celsius};
#[cfg(not(feature = "mock-machine"))]
use crate::machines::extruder1::temperature_controller::TemperatureController;
#[cfg(not(feature = "mock-machine"))]
use super::{
ExtruderV2, ExtruderV2Mode, Heating, api::ExtruderV2Namespace, mitsubishi_cs80::MitsubishiCS80,
screw_speed_controller::ScrewSpeedController,
};
#[cfg(not(feature = "mock-machine"))]
impl MachineNewTrait for ExtruderV2 {
fn new<'maindevice>(params: &MachineNewParams) -> Result<Self, Error> {
// validate general stuff
let device_identification = params.device_group.to_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!(
"[{}::MachineNewTrait/Extruder2::new] MachineNewHardware is not Ethercat",
module_path!()
));
}
};
let hardware: &&control_core::machines::new::MachineNewHardwareEthercat<'_, '_, '_> =
match &params.hardware {
MachineNewHardware::Ethercat(x) => x,
_ => {
return Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Extruder2::new] MachineNewHardware is not Ethercat",
module_path!()
));
}
};
// using block_on because making this funciton async creates a lifetime issue
// if its async the compiler thinks &subdevices is persisted in the future which might never execute
// so we can't drop subdevices unless this machine is dropped, which is bad
@@ -66,245 +89,55 @@ impl MachineNewTrait for ExtruderV2 {
// Role 0
// Buscoupler
// EK1100
{
let device_identification =
get_device_identification_by_role(params.device_group, 0)?;
let device_hardware_identification_ethercat = match &device_identification
.device_hardware_identification
{
DeviceHardwareIdentification::Ethercat(
device_hardware_identification_ethercat,
) => device_hardware_identification_ethercat,
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/ExtruderV2::new] Device with role 0 is not Ethercat",
module_path!()
))?, //uncommented
};
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
let subdevice = get_subdevice_by_index(hardware.subdevices, subdevice_index)?;
let subdevice_identity = subdevice.identity();
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
EK1100_IDENTITY_A => {
let ethercat_device = get_ethercat_device_by_index(
hardware.ethercat_devices,
subdevice_index,
)?;
downcast_device::<EK1100>(ethercat_device).await?
}
_ => {
return Err(anyhow::anyhow!(
"[{}::MachineNewTrait/ExtruderV2::new] Device with role 0 is not an EK1100",
module_path!()
));
}
};
{
let mut device_guard = device.write().await;
device_guard.set_used(true);
}
}
let _ek1100 =
get_ethercat_device::<EK1100>(hardware, params, 0, [EK1100_IDENTITY_A].to_vec());
// What is its use ?
let _el1002 = {
let device_identification =
get_device_identification_by_role(params.device_group, 1)?;
let device_hardware_identification_ethercat = match &device_identification
.device_hardware_identification
{
DeviceHardwareIdentification::Ethercat(
device_hardware_identification_ethercat,
) => device_hardware_identification_ethercat,
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/ExtruderV2::new] Device with role 1 is not Ethercat",
module_path!()
))?, //uncommented
};
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
let subdevice = get_subdevice_by_index(hardware.subdevices, subdevice_index)?;
let subdevice_identity = subdevice.identity();
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
EL1002_IDENTITY_A => {
let ethercat_device = get_ethercat_device_by_index(
hardware.ethercat_devices,
subdevice_index,
)?;
downcast_device::<EL1002>(ethercat_device).await?
}
_ => {
return Err(anyhow::anyhow!(
"[{}::MachineNewTrait/ExtruderV2::new] Device with role 1 is not an EL1002",
module_path!()
));
}
};
{
let mut device_guard = device.write().await;
device_guard.set_used(true);
}
device
};
let _el1002 =
get_ethercat_device::<EL1002>(hardware, params, 1, [EL1002_IDENTITY_A].to_vec())
.await?;
let el6021 = {
let device_identification =
get_device_identification_by_role(params.device_group, 2)?;
let device_hardware_identification_ethercat = match &device_identification
.device_hardware_identification
{
DeviceHardwareIdentification::Ethercat(
device_hardware_identification_ethercat,
) => device_hardware_identification_ethercat,
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/ExtruderV2::new] Device with role 2 is not Ethercat",
module_path!()
))?, //uncommented
};
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
let subdevice = get_subdevice_by_index(hardware.subdevices, subdevice_index)?;
let subdevice_identity = subdevice.identity();
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
EL6021_IDENTITY_A | EL6021_IDENTITY_B | EL6021_IDENTITY_C
| EL6021_IDENTITY_D => {
let ethercat_device = get_ethercat_device_by_index(
hardware.ethercat_devices,
subdevice_index,
)?;
downcast_device::<EL6021>(ethercat_device).await?
}
_ => {
return Err(anyhow::anyhow!(
"[{}::MachineNewTrait/ExtruderV2::new] Device with role 2 is not an EL6021",
module_path!()
));
}
};
let identities = [
EL6021_IDENTITY_A,
EL6021_IDENTITY_B,
EL6021_IDENTITY_C,
EL6021_IDENTITY_D,
]
.to_vec();
let device = get_ethercat_device::<EL6021>(hardware, params, 2, identities).await?;
device
.0
.write()
.await
.write_config(subdevice, &EL6021Configuration::default())
.write_config(&device.1, &EL6021Configuration::default())
.await?;
{
let mut device_guard = device.write().await;
let mut device_guard = device.0.write().await;
device_guard.set_used(true);
}
device
device.0
};
let el2004 = {
let device_identification =
get_device_identification_by_role(params.device_group, 3)?;
let device_hardware_identification_ethercat = match &device_identification
.device_hardware_identification
{
DeviceHardwareIdentification::Ethercat(
device_hardware_identification_ethercat,
) => device_hardware_identification_ethercat,
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/ExtruderV2::new] Device with role 3 is not Ethercat",
module_path!()
))?, //uncommented
};
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
let subdevice = get_subdevice_by_index(hardware.subdevices, subdevice_index)?;
let subdevice_identity = subdevice.identity();
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
EL2004_IDENTITY_A => {
let ethercat_device = get_ethercat_device_by_index(
hardware.ethercat_devices,
subdevice_index,
)?;
downcast_device::<EL2004>(ethercat_device).await?
}
_ => {
return Err(anyhow::anyhow!(
"[{}::MachineNewTrait/ExtruderV2::new] Device with role 3 is not an EL2004",
module_path!()
));
}
};
{
let mut device_guard = device.write().await;
device_guard.set_used(true);
}
device
};
let el2004 =
get_ethercat_device::<EL2004>(hardware, params, 3, [EL2004_IDENTITY_A].to_vec())
.await?
.0;
let el3021 = {
let device_identification =
get_device_identification_by_role(params.device_group, 4)?;
let device_hardware_identification_ethercat = match &device_identification
.device_hardware_identification
{
DeviceHardwareIdentification::Ethercat(
device_hardware_identification_ethercat,
) => device_hardware_identification_ethercat,
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/ExtruderV2::new] Device with role 4 is not Ethercat",
module_path!()
))?, //uncommented
};
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
let subdevice = get_subdevice_by_index(hardware.subdevices, subdevice_index)?;
let subdevice_identity = subdevice.identity();
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
EL3021_IDENTITY_A => {
let ethercat_device = get_ethercat_device_by_index(
hardware.ethercat_devices,
subdevice_index,
)?;
downcast_device::<EL3021>(ethercat_device).await?
}
_ => {
return Err(anyhow::anyhow!(
"[{}::MachineNewTrait/ExtruderV2::new] Device with role 4 is not an EL3021",
module_path!()
));
}
};
{
let mut device_guard = device.write().await;
device_guard.set_used(true);
}
device
};
let el3021 =
get_ethercat_device::<EL3021>(hardware, params, 4, [EL3021_IDENTITY_A].to_vec())
.await?
.0;
let el3204 = {
let device_identification =
get_device_identification_by_role(params.device_group, 5)?;
let device_hardware_identification_ethercat = match &device_identification
.device_hardware_identification
{
DeviceHardwareIdentification::Ethercat(
device_hardware_identification_ethercat,
) => device_hardware_identification_ethercat,
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/ExtruderV2::new] Device with role 5 is not Ethercat",
module_path!()
))?, //uncommented
};
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
let subdevice = get_subdevice_by_index(hardware.subdevices, subdevice_index)?;
let subdevice_identity = subdevice.identity();
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
EL3204_IDENTITY_A | EL3204_IDENTITY_B => {
let ethercat_device = get_ethercat_device_by_index(
hardware.ethercat_devices,
subdevice_index,
)?;
downcast_device::<EL3204>(ethercat_device).await?
}
_ => {
return Err(anyhow::anyhow!(
"[{}::MachineNewTrait/ExtruderV2::new] Device with role 5 is not an EL3204",
module_path!()
));
}
};
{
let mut device_guard = device.write().await;
device_guard.set_used(true);
}
device
};
let el3204 = get_ethercat_device::<EL3204>(
hardware,
params,
5,
[EL3204_IDENTITY_A, EL3204_IDENTITY_B].to_vec(),
)
.await?
.0;
let t1 = TemperatureInput::new(el3204.clone(), EL3204Port::T1);
let t2 = TemperatureInput::new(el3204.clone(), EL3204Port::T2);
@@ -380,8 +213,7 @@ impl MachineNewTrait for ExtruderV2 {
0.95,
);
let inverter =
MitsubishiCS80::new(SerialInterface::new(el6021, el6021::EL6021Port::SI1));
let inverter = MitsubishiCS80::new(SerialInterface::new(el6021, EL6021Port::SI1));
let target_pressure = Pressure::new::<bar>(0.0);
let target_rpm = AngularVelocity::new::<revolution_per_minute>(0.0);
@@ -389,8 +221,11 @@ impl MachineNewTrait for ExtruderV2 {
let screw_speed_controller =
ScrewSpeedController::new(inverter, target_pressure, target_rpm, pressure_sensor);
let mut extruder: Self = Self {
namespace: ExtruderV2Namespace::new(params.socket_queue_tx.clone()),
let mut extruder: ExtruderV2 = Self {
machine_identification_unique: params.get_machine_identification_unique(),
namespace: ExtruderV2Namespace {
namespace: params.namespace.clone(),
},
last_measurement_emit: Instant::now(),
mode: ExtruderV2Mode::Standby,
total_energy_kwh: 0.0,
+7 -14
View File
@@ -12,8 +12,7 @@ use control_core::{
use control_core_derive::BuildEvent;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use smol::channel::Sender;
use socketioxide::extract::SocketRef;
use smol::lock::Mutex;
use std::{sync::Arc, time::Duration};
use tracing::instrument;
@@ -62,15 +61,7 @@ pub enum LaserEvents {
#[derive(Debug)]
pub struct LaserMachineNamespace {
pub namespace: Namespace,
}
impl LaserMachineNamespace {
pub fn new(socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>) -> Self {
Self {
namespace: Namespace::new(socket_queue_tx),
}
}
pub namespace: Arc<Mutex<Namespace>>,
}
impl CacheableEvents<Self> for LaserEvents {
@@ -107,7 +98,9 @@ impl NamespaceCacheingLogic<LaserEvents> for LaserMachineNamespace {
fn emit(&mut self, events: LaserEvents) {
let event = Arc::new(events.event_value());
let buffer_fn = events.event_cache_fn();
self.namespace.emit(event, &buffer_fn);
let mut namespace = self.namespace.lock_blocking();
namespace.emit(event, &buffer_fn);
}
}
@@ -128,7 +121,7 @@ impl MachineApi for LaserMachine {
Ok(())
}
fn api_event_namespace(&mut self) -> &mut Namespace {
&mut self.namespace.namespace
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
self.namespace.namespace.clone()
}
}
+4 -3
View File
@@ -4,7 +4,8 @@ use crate::{
};
use api::{LaserEvents, LaserMachineNamespace, LaserState, LiveValuesEvent, StateEvent};
use control_core::{
machines::identification::MachineIdentification, socketio::namespace::NamespaceCacheingLogic,
machines::identification::{MachineIdentification, MachineIdentificationUnique},
socketio::namespace::NamespaceCacheingLogic,
};
use control_core_derive::Machine;
use smol::lock::RwLock;
@@ -17,6 +18,8 @@ pub mod new;
#[derive(Debug, Machine)]
pub struct LaserMachine {
machine_identification_unique: MachineIdentificationUnique,
// drivers
laser: Arc<RwLock<Laser>>,
@@ -43,9 +46,7 @@ impl LaserMachine {
vendor: VENDOR_QITECH,
machine: MACHINE_LASER_V1,
};
}
impl LaserMachine {
///diameter in mm
pub fn emit_live_values(&mut self) {
let diameter = self.diameter.get::<millimeter>();
+8 -7
View File
@@ -4,11 +4,9 @@ use crate::serial::{devices::laser::Laser, registry::SERIAL_DEVICE_REGISTRY};
use super::{LaserMachine, LaserTarget, api::LaserMachineNamespace};
use anyhow::Error;
use control_core::machines::new::MachineNewTrait;
use uom::{
ConstZero,
si::{f64::Length, length::millimeter},
};
use control_core::machines::new::{MachineNewHardware, MachineNewTrait};
use uom::ConstZero;
use uom::si::{f64::Length, length::millimeter};
impl MachineNewTrait for LaserMachine {
fn new(
@@ -18,7 +16,7 @@ impl MachineNewTrait for LaserMachine {
Self: Sized,
{
let hardware_serial = match params.hardware {
control_core::machines::new::MachineNewHardware::Serial(serial) => *serial,
MachineNewHardware::Serial(serial) => *serial,
_ => return Err(Error::msg("Invalid hardware type for LaserMachine")),
};
@@ -37,8 +35,11 @@ impl MachineNewTrait for LaserMachine {
diameter: Length::new::<millimeter>(1.75),
};
let mut laser_machine = Self {
machine_identification_unique: params.get_machine_identification_unique(),
laser,
namespace: LaserMachineNamespace::new(params.socket_queue_tx.clone()),
namespace: LaserMachineNamespace {
namespace: params.namespace.clone(),
},
last_measurement_emit: Instant::now(),
laser_target,
emitted_default_state: false,
+7 -14
View File
@@ -12,8 +12,7 @@ use control_core::{
use control_core_derive::BuildEvent;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use smol::channel::Sender;
use socketioxide::extract::SocketRef;
use smol::lock::Mutex;
use std::{sync::Arc, time::Duration};
use tracing::instrument;
@@ -61,15 +60,7 @@ pub enum MockEvents {
#[derive(Debug)]
pub struct MockMachineNamespace {
pub namespace: Namespace,
}
impl MockMachineNamespace {
pub fn new(socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>) -> Self {
Self {
namespace: Namespace::new(socket_queue_tx),
}
}
pub namespace: Arc<Mutex<Namespace>>,
}
impl CacheableEvents<Self> for MockEvents {
@@ -106,7 +97,9 @@ impl NamespaceCacheingLogic<MockEvents> for MockMachineNamespace {
fn emit(&mut self, events: MockEvents) {
let event = Arc::new(events.event_value());
let buffer_fn = events.event_cache_fn();
self.namespace.emit(event, &buffer_fn);
let mut namespace = self.namespace.lock_blocking();
namespace.emit(event, &buffer_fn);
}
}
@@ -130,7 +123,7 @@ impl MachineApi for MockMachine {
Ok(())
}
fn api_event_namespace(&mut self) -> &mut Namespace {
&mut self.namespace.namespace
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
self.namespace.namespace.clone()
}
}
+4 -3
View File
@@ -1,5 +1,6 @@
use crate::machines::{MACHINE_MOCK, VENDOR_QITECH};
use api::{LiveValuesEvent, MockEvents, MockMachineNamespace, Mode, ModeState, StateEvent};
use control_core::machines::identification::MachineIdentificationUnique;
use control_core::socketio::event::BuildEvent;
use control_core::{
machines::identification::MachineIdentification, socketio::namespace::NamespaceCacheingLogic,
@@ -19,6 +20,8 @@ pub mod new;
#[derive(Debug, Machine)]
pub struct MockMachine {
machine_identification_unique: MachineIdentificationUnique,
// socketio
namespace: MockMachineNamespace,
last_measurement_emit: Instant,
@@ -40,12 +43,10 @@ pub struct MockMachine {
impl MockMachine {
pub const MACHINE_IDENTIFICATION: MachineIdentification = MachineIdentification {
vendor: VENDOR_QITECH,
machine: MACHINE_MOCK,
vendor: VENDOR_QITECH,
};
}
impl MockMachine {
/// Emit live values data event with the current sine wave amplitude
pub fn emit_live_values(&mut self) {
let now = Instant::now();
+7 -4
View File
@@ -5,12 +5,12 @@ use super::{
api::{MockMachineNamespace, Mode},
};
use anyhow::Error;
use control_core::machines::new::{MachineNewHardware, MachineNewTrait};
use control_core::machines::new::{MachineNewHardware, MachineNewParams, MachineNewTrait};
use uom::si::{f64::Frequency, frequency::hertz};
impl MachineNewTrait for MockMachine {
fn new(
params: &control_core::machines::new::MachineNewParams<'_, '_, '_, '_, '_, '_, '_>,
fn new<'maindevice, 'subdevices>(
params: &MachineNewParams<'maindevice, 'subdevices, '_, '_, '_, '_, '_>,
) -> Result<Self, Error>
where
Self: Sized,
@@ -32,7 +32,10 @@ impl MachineNewTrait for MockMachine {
let now = Instant::now();
let mut mock_machine = Self {
namespace: MockMachineNamespace::new(params.socket_queue_tx.clone()),
machine_identification_unique: params.get_machine_identification_unique(),
namespace: MockMachineNamespace {
namespace: params.namespace.clone(),
},
last_measurement_emit: now,
t_0: now, // Initialize start time to current time
frequency1: Frequency::new::<hertz>(0.1), // Default frequency1 of 100 mHz
+123
View File
@@ -1,3 +1,18 @@
use std::sync::Arc;
use control_core::machines::{
identification::{DeviceHardwareIdentification, DeviceHardwareIdentificationEthercat},
new::{
MachineNewParams, get_device_identification_by_role, get_ethercat_device_by_index,
get_subdevice_by_index,
},
};
use ethercat_hal::devices::{
EthercatDevice, SubDeviceIdentityTuple, downcast_device, subdevice_identity_to_tuple,
};
use ethercrab::{SubDevice, SubDeviceRef};
use smol::lock::RwLock;
pub mod aquapath1;
pub mod buffer1;
pub mod extruder1;
@@ -13,3 +28,111 @@ pub const MACHINE_LASER_V1: u16 = 0x0006;
pub const MACHINE_MOCK: u16 = 0x0007;
pub const MACHINE_BUFFER_V1: u16 = 0x0008;
pub const MACHINE_AQUAPATH_V1: u16 = 0x0009;
async fn get_device_ident<
'maindevice,
'subdevices,
'device_identifications_identified,
'ethercat_devices,
'machine_new_hardware_etehrcat,
'machine_new_hardware_serial,
'machine_new_hardware,
>(
params: &MachineNewParams<
'maindevice,
'subdevices,
'device_identifications_identified,
'ethercat_devices,
'machine_new_hardware_etehrcat,
'machine_new_hardware_serial,
'machine_new_hardware,
>,
role: u16,
) -> Result<DeviceHardwareIdentificationEthercat, anyhow::Error> {
let device_identification = get_device_identification_by_role(params.device_group, role)?;
let device_hardware_identification_ethercat =
match &device_identification.device_hardware_identification {
DeviceHardwareIdentification::Ethercat(device_hardware_identification_ethercat) => {
device_hardware_identification_ethercat
}
_ => {
return Err(anyhow::anyhow!(
"[{}::MachineNewTrait/ExtruderV2::new] Device with role {} is not Ethercat",
module_path!(),
role
));
}
};
return Ok(device_hardware_identification_ethercat.clone());
}
async fn get_ethercat_device<
'maindevice,
'subdevices,
'device_identifications_identified,
'ethercat_devices,
'machine_new_hardware_etehrcat,
'machine_new_hardware_serial,
'machine_new_hardware,
T,
>(
hardware: &&control_core::machines::new::MachineNewHardwareEthercat<
'maindevice,
'subdevices,
'ethercat_devices,
>,
params: &MachineNewParams<
'maindevice,
'subdevices,
'device_identifications_identified,
'ethercat_devices,
'machine_new_hardware_etehrcat,
'machine_new_hardware_serial,
'machine_new_hardware,
>,
role: u16,
expected_identities: Vec<SubDeviceIdentityTuple>,
) -> Result<
(
Arc<RwLock<T>>,
&'subdevices SubDeviceRef<'subdevices, &'subdevices SubDevice>,
),
anyhow::Error,
>
where
T: 'static + Send + Sync + EthercatDevice,
{
let device_hardware_identification_ethercat = get_device_ident(params, role).await?;
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
let subdevice = get_subdevice_by_index(hardware.subdevices, subdevice_index)?;
let subdevice_identity = subdevice.identity();
let actual_identity = subdevice_identity_to_tuple(&subdevice_identity);
let mut matched_any_identity = false;
for identity in expected_identities.clone() {
if actual_identity == identity {
matched_any_identity = true;
}
}
if !matched_any_identity {
return Err(anyhow::anyhow!(
"[{}::MachineNewTrait/ExtruderV2::new] Device identity mismatch: expected {:?}",
module_path!(),
expected_identities
));
}
let ethercat_device =
get_ethercat_device_by_index(&hardware.ethercat_devices, subdevice_index)?;
let device = downcast_device::<T>(ethercat_device).await?;
{
let mut device_guard = device.write().await;
device_guard.set_used(true);
}
Ok((device, subdevice))
}
+11 -22
View File
@@ -1,6 +1,9 @@
use super::{Winder2, Winder2Mode, puller_speed_controller::PullerRegulationMode};
use control_core::{
machines::{api::MachineApi, identification::MachineIdentificationUnique},
machines::{
api::MachineApi, connection::MachineCrossConnectionState,
identification::MachineIdentificationUnique,
},
socketio::{
event::{Event, GenericEvent},
namespace::{
@@ -13,8 +16,7 @@ use control_core::{
use control_core_derive::BuildEvent;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use smol::channel::Sender;
use socketioxide::extract::SocketRef;
use smol::lock::Mutex;
use std::{
sync::Arc,
time::{Duration, Instant},
@@ -141,7 +143,7 @@ pub struct StateEvent {
/// spool speed controller state
pub spool_speed_controller_state: SpoolSpeedControllerState,
/// connected machine state
pub connected_machine_state: ConnectedMachineState,
pub connected_machine_state: MachineCrossConnectionState,
}
#[derive(Serialize, Debug, Clone)]
@@ -237,13 +239,6 @@ pub struct SpoolSpeedControllerState {
pub adaptive_deacceleration_urgency_multiplier: f64,
}
#[derive(Serialize, Debug, Clone)]
pub struct ConnectedMachineState {
/// Connected Machine
pub machine_identification_unique: Option<MachineIdentificationUnique>,
pub is_available: bool,
}
pub enum Winder2Events {
LiveValues(Event<LiveValuesEvent>),
State(Event<StateEvent>),
@@ -251,7 +246,7 @@ pub enum Winder2Events {
#[derive(Debug)]
pub struct Winder2Namespace {
pub namespace: Namespace,
pub namespace: Arc<Mutex<Namespace>>,
}
impl NamespaceCacheingLogic<Winder2Events> for Winder2Namespace {
@@ -259,15 +254,9 @@ impl NamespaceCacheingLogic<Winder2Events> for Winder2Namespace {
fn emit(&mut self, events: Winder2Events) {
let event = Arc::new(events.event_value());
let buffer_fn = events.event_cache_fn();
self.namespace.emit(event, &buffer_fn);
}
}
impl Winder2Namespace {
pub fn new(socket_queue_tx: Sender<(SocketRef, Arc<GenericEvent>)>) -> Self {
Self {
namespace: Namespace::new(socket_queue_tx),
}
let mut namespace = self.namespace.lock_blocking();
namespace.emit(event, &buffer_fn);
}
}
@@ -341,7 +330,7 @@ impl MachineApi for Winder2 {
Ok(())
}
fn api_event_namespace(&mut self) -> &mut Namespace {
&mut self.namespace.namespace
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
self.namespace.namespace.clone()
}
}
+27 -99
View File
@@ -9,6 +9,9 @@ pub mod puller_speed_controller;
pub mod spool_speed_controller;
pub mod tension_arm;
pub mod traverse_controller;
use std::{fmt::Debug, sync::Weak, time::Instant};
use api::{
LiveValuesEvent, ModeState, PullerState, SpoolAutomaticActionMode, SpoolAutomaticActionState,
SpoolSpeedControllerState, StateEvent, TensionArmState, TraverseState, Winder2Events,
@@ -18,7 +21,7 @@ use control_core::socketio::event::BuildEvent;
use control_core::{
converters::angular_step_converter::AngularStepConverter,
machines::{
ConnectedMachine, ConnectedMachineData, downcast_machine,
connection::{CrossConnectableMachine, MachineCrossConnection},
identification::{MachineIdentification, MachineIdentificationUnique},
manager::MachineManager,
},
@@ -30,15 +33,9 @@ use ethercat_hal::io::{
digital_input::DigitalInput, digital_output::DigitalOutput,
stepper_velocity_el70x1::StepperVelocityEL70x1,
};
use futures::executor::block_on;
use puller_speed_controller::{PullerRegulationMode, PullerSpeedController};
use smol::lock::{Mutex, RwLock};
use smol::lock::RwLock;
use spool_speed_controller::SpoolSpeedController;
use std::{
fmt::Debug,
sync::{Arc, Weak},
time::Instant,
};
use tension_arm::TensionArm;
use traverse_controller::TraverseController;
use uom::{
@@ -52,9 +49,7 @@ use uom::{
},
};
use crate::machines::{
MACHINE_WINDER_V1, VENDOR_QITECH, buffer1::BufferV1, winder2::api::ConnectedMachineState,
};
use crate::machines::{MACHINE_WINDER_V1, VENDOR_QITECH, buffer1::BufferV1};
#[derive(Debug)]
pub struct SpoolAutomaticAction {
@@ -86,7 +81,7 @@ pub struct Winder2 {
pub machine_identification_unique: MachineIdentificationUnique,
// connected machines
pub connected_buffer: Option<ConnectedMachine<Weak<Mutex<BufferV1>>>>,
pub connected_buffer: MachineCrossConnection<Winder2, BufferV1>,
// mode
pub mode: Winder2Mode,
@@ -109,15 +104,19 @@ pub struct Winder2 {
emitted_default_state: bool,
}
impl CrossConnectableMachine<Winder2, BufferV1> for Winder2 {
fn get_cross_connection(&mut self) -> &mut MachineCrossConnection<Winder2, BufferV1> {
&mut self.connected_buffer
}
}
impl Winder2 {
pub const MACHINE_IDENTIFICATION: MachineIdentification = MachineIdentification {
vendor: VENDOR_QITECH,
machine: MACHINE_WINDER_V1,
};
}
/// Implement Traverse
impl Winder2 {
/// Implement Traverse
fn set_laser(&mut self, value: bool) {
self.laser.set(value);
self.emit_state();
@@ -311,20 +310,7 @@ impl Winder2 {
spool_required_meters: self.spool_automatic_action.target_length.get::<meter>(),
spool_automatic_action_mode: self.spool_automatic_action.mode.clone(),
},
connected_machine_state: ConnectedMachineState {
machine_identification_unique: self.connected_buffer.as_ref().map(
|connected_machine| {
ConnectedMachineData::from(connected_machine).machine_identification_unique
},
),
is_available: self
.connected_buffer
.as_ref()
.map(|connected_machine| {
ConnectedMachineData::from(connected_machine).is_available
})
.unwrap_or(false),
},
connected_machine_state: self.connected_buffer.to_state(),
}
}
@@ -385,12 +371,8 @@ impl Winder2 {
&& !self.traverse_controller.is_traversing()
&& self.mode != Winder2Mode::Wind
}
}
impl Winder2 {}
/// Implement Mode
impl Winder2 {
/// Implement Mode
fn set_mode(&mut self, mode: &Winder2Mode) {
let should_update = *mode != Winder2Mode::Wind || self.can_wind();
@@ -577,20 +559,16 @@ impl Winder2 {
// Update the internal state
self.puller_mode = mode;
}
}
/// Implement Tension Arm
impl Winder2 {
/// Implement Tension Arm
fn tension_arm_zero(&mut self) {
self.tension_arm.zero();
self.emit_live_values(); // For angle update
// For state update
self.emit_state();
}
}
/// Implement Spool
impl Winder2 {
/// Implement Spool
/// called by `act`
pub fn sync_spool_speed(&mut self, t: Instant) {
let angular_velocity = self.spool_speed_controller.update_speed(
@@ -670,10 +648,8 @@ impl Winder2 {
self.spool_automatic_action.progress += meters_pulled_this_interval;
self.spool_automatic_action.progress_last_check = now;
}
}
/// Implement Puller
impl Winder2 {
/// Implement Puller
/// called by `act`
pub fn sync_puller_speed(&mut self, t: Instant) {
let angular_velocity = self.puller_speed_controller.calc_angular_velocity(t);
@@ -777,10 +753,8 @@ impl Winder2 {
.set_adaptive_deacceleration_urgency_multiplier(deacceleration_urgency_multiplier);
self.emit_state();
}
}
/// implement machine connection
impl Winder2 {
/// implement machine connection
/// set connected buffer
pub fn set_connected_buffer(
&mut self,
@@ -792,33 +766,12 @@ impl Winder2 {
) {
return;
}
let machine_manager_arc = match self.machine_manager.upgrade() {
Some(machine_manager_arc) => machine_manager_arc,
None => return,
};
let machine_manager_guard = block_on(machine_manager_arc.read());
let buffer_weak = machine_manager_guard.get_machine_weak(&machine_identification_unique);
let buffer_weak = match buffer_weak {
Some(buffer_weak) => buffer_weak,
None => return,
};
let buffer_strong = match buffer_weak.upgrade() {
Some(buffer_strong) => buffer_strong,
None => return,
};
let buffer: Arc<Mutex<BufferV1>> = block_on(downcast_machine::<BufferV1>(buffer_strong))
.expect("failed downcasting machine");
self.connected_buffer
.set_connected_machine(&machine_identification_unique);
self.connected_buffer.reverse_connect();
let machine = Arc::downgrade(&buffer);
self.connected_buffer = Some(ConnectedMachine {
machine_identification_unique,
machine,
});
self.emit_state();
self.reverse_connect();
}
/// disconnect buffer
@@ -832,36 +785,11 @@ impl Winder2 {
) {
return;
}
if let Some(connected) = &self.connected_buffer {
if let Some(buffer_arc) = connected.machine.upgrade() {
let future = async move {
let mut buffer = buffer_arc.lock().await;
if buffer.connected_winder.is_some() {
buffer.connected_winder = None;
buffer.emit_state();
}
};
smol::spawn(future).detach();
}
}
self.connected_buffer = None;
self.emit_state();
}
/// initiate connection from buffer to winder
pub fn reverse_connect(&mut self) {
let machine_identification_unique = self.machine_identification_unique.clone();
if let Some(connected) = &self.connected_buffer {
if let Some(buffer_arc) = connected.machine.upgrade() {
let future = async move {
let mut buffer = buffer_arc.lock().await;
if buffer.connected_winder.is_none() {
buffer.set_connected_winder(machine_identification_unique);
}
};
smol::spawn(future).detach();
}
}
self.connected_buffer.reverse_disconnect();
self.connected_buffer.disconnect();
self.emit_state();
}
}
+67 -214
View File
@@ -3,16 +3,16 @@ use std::time::Instant;
use super::api::Winder2Namespace;
use super::tension_arm::TensionArm;
use super::{Winder2, Winder2Mode};
use crate::machines::get_ethercat_device;
use crate::machines::winder2::puller_speed_controller::PullerSpeedController;
use crate::machines::winder2::spool_speed_controller::SpoolSpeedController;
use crate::machines::winder2::traverse_controller::TraverseController;
use anyhow::Error;
use control_core::converters::angular_step_converter::AngularStepConverter;
use control_core::converters::linear_step_converter::LinearStepConverter;
use control_core::machines::identification::DeviceHardwareIdentification;
use control_core::machines::connection::MachineCrossConnection;
use control_core::machines::new::{
MachineNewHardware, MachineNewParams, MachineNewTrait, get_device_identification_by_role,
get_ethercat_device_by_index, get_subdevice_by_index, validate_no_role_dublicates,
MachineNewHardware, MachineNewParams, MachineNewTrait, validate_no_role_dublicates,
validate_same_machine_identification_unique,
};
use control_core::uom_extensions::velocity::meter_per_minute;
@@ -31,7 +31,6 @@ use ethercat_hal::devices::el7031_0030::{
};
use ethercat_hal::devices::el7041_0052::coe::EL7041_0052Configuration;
use ethercat_hal::devices::el7041_0052::{EL7041_0052, EL7041_0052_IDENTITY_A, EL7041_0052Port};
use ethercat_hal::devices::{EthercatDeviceUsed, downcast_device, subdevice_identity_to_tuple};
use ethercat_hal::devices::{ek1100::EK1100_IDENTITY_A, el2002::EL2002_IDENTITY_A};
use ethercat_hal::io::analog_input::AnalogInput;
use ethercat_hal::io::digital_input::DigitalInput;
@@ -65,121 +64,31 @@ impl MachineNewTrait for Winder2 {
// if its async the compiler thinks &subdevices is persisted in the future which might never execute
// so we can't drop subdevices unless this machine is dropped, which is bad
smol::block_on(async {
// Role 0
// Buscoupler
// EK1100
{
let device_identification =
get_device_identification_by_role(params.device_group, 0)?;
let device_hardware_identification_ethercat =
match &device_identification.device_hardware_identification {
DeviceHardwareIdentification::Ethercat(
device_hardware_identification_ethercat,
) => device_hardware_identification_ethercat,
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Winder2::new] Device with role 0 is not Ethercat",
module_path!()
))?, //uncommented
};
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
let subdevice = get_subdevice_by_index(hardware.subdevices, subdevice_index)?;
let subdevice_identity = subdevice.identity();
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
EK1100_IDENTITY_A => {
let ethercat_device = get_ethercat_device_by_index(
hardware.ethercat_devices,
subdevice_index,
)?;
downcast_device::<EK1100>(ethercat_device).await?
}
_ => {
return Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Winder2::new] Device with role 0 is not an EK1100",
module_path!()
));
}
};
{
let mut device_guard = device.write().await;
device_guard.set_used(true);
}
}
// Role 0: Buscoupler EK1100
let _ek1100 =
get_ethercat_device::<EK1100>(hardware, params, 0, vec![EK1100_IDENTITY_A]).await?;
// Role 1
// 2x Digitalausgang
// EL2002
let el2002 = {
let device_identification =
get_device_identification_by_role(params.device_group, 1)?;
let device_hardware_identification_ethercat =
match &device_identification.device_hardware_identification {
DeviceHardwareIdentification::Ethercat(
device_hardware_identification_ethercat,
) => device_hardware_identification_ethercat,
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Winder2::new] Device with role 1 is not Ethercat",
module_path!()
))?, //uncommented
};
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
let subdevice = get_subdevice_by_index(hardware.subdevices, subdevice_index)?;
let subdevice_identity = subdevice.identity();
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
EL2002_IDENTITY_A | EL2002_IDENTITY_B => {
let ethercat_device = get_ethercat_device_by_index(
hardware.ethercat_devices,
subdevice_index,
)?;
downcast_device::<EL2002>(ethercat_device).await?
}
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Winder2::new] Device with role 1 is not an EL2002",
module_path!()
))?,
};
{
let mut device_guard = device.write().await;
device_guard.set_used(true);
}
device
};
// Role 1: 2x Digital outputs EL2002
let el2002 = get_ethercat_device::<EL2002>(
hardware,
params,
1,
vec![EL2002_IDENTITY_A, EL2002_IDENTITY_B],
)
.await?
.0;
// Role 2
// 1x Stepper Spool
// EL7041-0052
let (el7041, _el7041_config) = {
let device_identification =
get_device_identification_by_role(params.device_group, 2)?;
let device_hardware_identification_ethercat =
match &device_identification.device_hardware_identification {
DeviceHardwareIdentification::Ethercat(
device_hardware_identification_ethercat,
) => device_hardware_identification_ethercat,
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Winder2::new] Device with role 2 is not Ethercat",
module_path!()
))?,
};
let subdevice = get_subdevice_by_index(
hardware.subdevices,
device_hardware_identification_ethercat.subdevice_index,
)?;
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
let subdevice_identity = subdevice.identity();
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
EL7041_0052_IDENTITY_A => {
let ethercat_device = get_ethercat_device_by_index(
hardware.ethercat_devices,
subdevice_index,
)?;
downcast_device::<EL7041_0052>(ethercat_device).await?
}
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Winder2::new] Device with role 3 is not an EL7041-0052",
module_path!()
))?,
};
let config = EL7041_0052Configuration {
// Role 2: Stepper Spool EL7041-0052
let el7041 = {
let device = get_ethercat_device::<EL7041_0052>(
hardware,
params,
2,
vec![EL7041_0052_IDENTITY_A],
)
.await?;
let el7041_config = EL7041_0052Configuration {
stm_features: shared_config::el70x1::StmFeatures {
operation_mode: EL70x1OperationMode::DirectVelocity,
..Default::default()
@@ -190,59 +99,29 @@ impl MachineNewTrait for Winder2 {
},
..Default::default()
};
device
.0
.write()
.await
.write_config(subdevice, &config)
.write_config(&device.1, &el7041_config)
.await?;
{
let mut device_guard = device.write().await;
device_guard.set_used(true);
}
(device, config)
device.0
};
// Role 3
// 1x Stepper Traverse
// EL7031
let (el7031, _el7031_config) = {
let device_identification =
get_device_identification_by_role(params.device_group, 3)?;
let device_hardware_identification_ethercat =
match &device_identification.device_hardware_identification {
DeviceHardwareIdentification::Ethercat(
device_hardware_identification_ethercat,
) => device_hardware_identification_ethercat,
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Winder2::new] Device with role 3 is not Ethercat",
module_path!()
))?,
};
let subdevice = get_subdevice_by_index(
hardware.subdevices,
device_hardware_identification_ethercat.subdevice_index,
)?;
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
let subdevice_identity = subdevice.identity();
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
EL7031_IDENTITY_A | EL7031_IDENTITY_B => {
let ethercat_device = get_ethercat_device_by_index(
hardware.ethercat_devices,
subdevice_index,
)?;
downcast_device::<EL7031>(ethercat_device).await?
}
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Winder2::new] Device with role 4 is not an EL7031",
module_path!()
))?,
};
let config = EL7031Configuration {
// Role 3: Stepper Traverse EL7031
let el7031 = {
let device = get_ethercat_device::<EL7031>(
hardware,
params,
3,
vec![EL7031_IDENTITY_A, EL7031_IDENTITY_B],
)
.await?;
let el7031_config = EL7031Configuration {
stm_features: shared_config::el70x1::StmFeatures {
operation_mode: EL70x1OperationMode::DirectVelocity,
// Max Speed of 1000 steps/s
// Max @ 9cm diameter = approx 85 m/min
// Max @ 20cm diameter = approx 185 m/min
speed_range: shared_config::el70x1::EL70x1SpeedRange::Steps1000,
..Default::default()
},
@@ -253,58 +132,30 @@ impl MachineNewTrait for Winder2 {
pdo_assignment: EL7031PredefinedPdoAssignment::VelocityControlCompact,
..Default::default()
};
device
.0
.write()
.await
.write_config(subdevice, &config)
.write_config(&device.1, &el7031_config)
.await?;
{
let mut device_guard = device.write().await;
device_guard.set_used(true);
}
(device, config)
device.0
};
// Role 4
// 1x Stepper Puller
// EL7031
let (el7031_0030, _el7031_0030_config) = {
let device_identification =
get_device_identification_by_role(params.device_group, 4)?;
let device_hardware_identification_ethercat =
match &device_identification.device_hardware_identification {
DeviceHardwareIdentification::Ethercat(
device_hardware_identification_ethercat,
) => device_hardware_identification_ethercat,
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Winder2::new] Device with role 4 is not Ethercat",
module_path!()
))?,
};
let subdevice = get_subdevice_by_index(
hardware.subdevices,
device_hardware_identification_ethercat.subdevice_index,
)?;
let subdevice_index = device_hardware_identification_ethercat.subdevice_index;
let subdevice_identity = subdevice.identity();
let device = match subdevice_identity_to_tuple(&subdevice_identity) {
EL7031_0030_IDENTITY_A => {
let ethercat_device = get_ethercat_device_by_index(
hardware.ethercat_devices,
subdevice_index,
)?;
downcast_device::<EL7031_0030>(ethercat_device).await?
}
_ => Err(anyhow::anyhow!(
"[{}::MachineNewTrait/Winder2::new] Device with role 5 is not an EL7031-0030",
module_path!()
))?,
};
let config = EL7031_0030Configuration {
// Role 4: Stepper Puller EL7031-0030
let el7031_0030 = {
let device = get_ethercat_device::<EL7031_0030>(
hardware,
params,
4,
vec![EL7031_0030_IDENTITY_A],
)
.await?;
let el7031_0030_config = EL7031_0030Configuration {
stm_features: el7031_0030::coe::StmFeatures {
operation_mode: EL70x1OperationMode::DirectVelocity,
// Max Speed of 1000 steps/s
// Max @ 8cm diameter = approx 75 m/min
speed_range: shared_config::el70x1::EL70x1SpeedRange::Steps1000,
..Default::default()
},
@@ -316,15 +167,12 @@ impl MachineNewTrait for Winder2 {
..Default::default()
};
device
.0
.write()
.await
.write_config(subdevice, &config)
.write_config(&device.1, &el7031_0030_config)
.await?;
{
let mut device_guard = device.write().await;
device_guard.set_used(true);
}
(device, config)
device.0
};
let mode = Winder2Mode::Standby;
@@ -350,7 +198,9 @@ impl MachineNewTrait for Winder2 {
EL7031_0030AnalogInputPort::AI1,
)),
laser: DigitalOutput::new(el2002, EL2002Port::DO1),
namespace: Winder2Namespace::new(params.socket_queue_tx.clone()),
namespace: Winder2Namespace {
namespace: params.namespace.clone(),
},
mode: mode.clone(),
spool_step_converter: AngularStepConverter::new(200),
spool_speed_controller: SpoolSpeedController::new(),
@@ -380,7 +230,10 @@ impl MachineNewTrait for Winder2 {
},
machine_manager: params.machine_manager.clone(),
machine_identification_unique: machine_id,
connected_buffer: None,
connected_buffer: MachineCrossConnection::new(
params.machine_manager.clone(),
&params.get_machine_identification_unique(),
),
};
// initalize events
+2 -1
View File
@@ -14,7 +14,6 @@ use app_state::AppState;
use mock::init::init_mock;
use std::{sync::Arc, time::Duration};
use ethercat::init::init_ethercat;
use r#loop::init_loop;
use rest::init::init_api;
#[cfg(not(feature = "mock-machine"))]
@@ -23,6 +22,8 @@ use serial::init::init_serial;
#[cfg(all(not(target_env = "msvc"), not(feature = "dhat-heap")))]
use jemalloc_stats::init_jemalloc_stats;
#[cfg(not(feature = "mock-machine"))]
use crate::ethercat::init::init_ethercat;
use crate::panic::init_panic;
use crate::socketio::queue::init_socketio_queue;
+3 -3
View File
@@ -25,7 +25,7 @@ pub fn init_mock(app_state: Arc<AppState>) -> Result<(), anyhow::Error> {
};
// Create the mock serial device
match MockSerialDevice::new_serial(&serial_params) {
let _ = match MockSerialDevice::new_serial(&serial_params) {
Ok((device_identification, mock_serial_device)) => {
// Add the mock device to the machine manager
{
@@ -47,7 +47,7 @@ pub fn init_mock(app_state: Arc<AppState>) -> Result<(), anyhow::Error> {
.write()
.await
.main_namespace;
let event = MachinesEventBuilder().build(app_state_event.clone()).await;
let event = MachinesEventBuilder().build(app_state_event.clone());
main_namespace.emit(MainNamespaceEvents::MachinesEvent(event));
Ok::<(), anyhow::Error>(())
}
@@ -79,7 +79,7 @@ pub fn init_mock(app_state: Arc<AppState>) -> Result<(), anyhow::Error> {
.write()
.await
.main_namespace;
let event = MachinesEventBuilder().build(app_state_event.clone()).await;
let event = MachinesEventBuilder().build(app_state_event.clone());
main_namespace.emit(MainNamespaceEvents::MachinesEvent(event));
Ok(())
}
+17 -7
View File
@@ -3,7 +3,10 @@ use crate::{
rest::util::{ResponseUtil, ResponseUtilError},
};
use axum::{Json, body::Body, extract::State, http::Response};
use control_core::rest::mutation::{MachineMutationBody, MutationResponse};
use control_core::{
machines::connection::MachineConnection,
rest::mutation::{MachineMutationBody, MutationResponse},
};
use serde_json::Value;
use std::sync::Arc;
@@ -27,7 +30,7 @@ async fn _post_machine_mutate(
let machines_guard = app_state.machines.read().await;
// find machine with given identification in hashmap
let machine = machines_guard
let slot = machines_guard
.get(&body.machine_identification_unique)
.ok_or(anyhow::anyhow!(
"[{}::_post_machine_mutate] Machine not found {:?}",
@@ -35,14 +38,21 @@ async fn _post_machine_mutate(
body.machine_identification_unique,
))?;
// check machine for error
let machine = match machine {
Ok(m) => m,
Err(e) => {
// check machine for valid connection
let connection = &slot.lock_blocking().machine_connection;
let machine = match connection {
MachineConnection::Connected(m) => m,
MachineConnection::Error(error) => {
return Err(anyhow::anyhow!(
"[{}::_post_machine_mutate] Machine has error: {}",
module_path!(),
e
error
));
}
MachineConnection::Disconnected => {
return Err(anyhow::anyhow!(
"[{}::_post_machine_mutate] Machine is disconnected",
module_path!()
));
}
};
+1 -2
View File
@@ -68,8 +68,7 @@ pub fn init_serial(
.write()
.await
.main_namespace;
let event =
MachinesEventBuilder().build(app_state_event.clone()).await;
let event = MachinesEventBuilder().build(app_state_event.clone());
main_namespace.emit(MainNamespaceEvents::MachinesEvent(event));
});
}
@@ -20,20 +20,8 @@ pub struct MachinesEventBuilder();
impl MachinesEventBuilder {
const NAME: &'static str = "MachinesEvent";
pub async fn build(&self, app_state: Arc<AppState>) -> Event<MachinesEvent> {
let mut machine_objs: Vec<_> = vec![];
// add machines
let machines_guard = app_state.machines.read().await;
for machine in machines_guard.iter() {
machine_objs.push(MachineObj {
machine_identification_unique: machine.0.clone(),
error: match machine.1 {
Ok(_) => None,
Err(e) => Some(e.to_string()),
},
});
}
pub fn build(&self, app_state: Arc<AppState>) -> Event<MachinesEvent> {
let machine_objs = app_state.get_machine_objs();
Event::new(
Self::NAME,
MachinesEvent {
+19 -10
View File
@@ -1,7 +1,8 @@
use std::sync::Arc;
use control_core::socketio::{
event::GenericEvent, namespace::Namespace, namespace_id::NamespaceId,
use control_core::{
machines::connection::MachineConnection,
socketio::{event::GenericEvent, namespace::Namespace, namespace_id::NamespaceId},
};
use smol::channel::Sender;
use socketioxide::extract::SocketRef;
@@ -34,8 +35,8 @@ impl Namespaces {
let machines_guard = app_state.machines.read().await;
// get machine
let machine = match machines_guard.get(&machine_identification_unique) {
Some(machine) => machine,
let slot = match machines_guard.get(&machine_identification_unique) {
Some(slot) => slot,
None => {
callback(Err(anyhow::anyhow!(
"[{}::Namespaces::appply_mut] Machine {:?} not found",
@@ -45,24 +46,32 @@ impl Namespaces {
return;
}
};
let slot = slot.lock_blocking();
// check if machine has error
let machine = match machine {
Ok(machine) => machine,
Err(err) => {
let machine = match &slot.machine_connection {
MachineConnection::Error(error) => {
callback(Err(anyhow::anyhow!(
"[{}::Namespaces::appply_mut] Machine {:?} has error: {}",
module_path!(),
machine_identification_unique,
err
error
)));
return;
}
MachineConnection::Disconnected => {
callback(Err(anyhow::anyhow!(
"[{}::Namespaces::appply_mut] Machine {:?} has disconnected",
module_path!(),
machine_identification_unique
)));
return;
}
MachineConnection::Connected(mutex) => mutex,
};
let mut machine_guard = machine.lock().await;
let namespace = machine_guard.api_event_namespace();
callback(Ok(namespace));
callback(Ok(&mut namespace.lock_blocking()));
}
}
}