Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
265100d699 |
No files matched your search
@@ -4,4 +4,4 @@ dist/
|
||||
Cargo.lock
|
||||
dhat-heap.json
|
||||
build.sh
|
||||
shell.nix
|
||||
|
||||
@@ -74,9 +74,7 @@ We could combine the code of the frontend and backend using [Doxius](https://dio
|
||||
| Laser | V1 | ??? | Diameter Measuring Laser | - | 1 (Qitech Industries GmbH) | 6 | Yes | |
|
||||
| Mock | - | ??? | Mock Machine for Testing | - | 1 (Qitech Industries GmbH) | 7 | Yes | - |
|
||||
|
||||
# More Docs docs
|
||||
|
||||
/assets
|
||||
# More Docs
|
||||
|
||||
- [x] [Architecture & Data Flow](./docs/architecture-overview.md)
|
||||
|
||||
@@ -156,225 +154,53 @@ We could combine the code of the frontend and backend using [Doxius](https://dio
|
||||
- [x] [NixOS Operating System](./docs/nixos/README.md)
|
||||
|
||||
|
||||
## Minimal Example — LED Control on the EL2004
|
||||
A complete hardware + software walkthrough
|
||||
## Minimal Example
|
||||
|
||||
# Hello EtherCAT - LED Control on the EL2004
|
||||
|
||||
1. Introduction
|
||||
2. Requirements
|
||||
3. Wiring / Hardware Setup
|
||||
4. Software Overview
|
||||
5. Step-by-Step Implementation
|
||||
6. Final Code Listing
|
||||
7. Expected Behavior
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
1. [Introduction](#1-introduction)
|
||||
2. [Requirements](#2-requirements)
|
||||
3. [Hardware Setup](#3-hardware-setup)
|
||||
4. [Software Setup](#4-software-setup)
|
||||
5. [Demo](#5-demo)
|
||||
6. [References](#6-references)
|
||||
7. [Acknowledgements](#7-acknowledgements)
|
||||
## 1. Introduction
|
||||
|
||||
The EL2004 LED Toggle Example is a minimal demonstration showing how to control digital outputs on a Beckhoff EL2004 EtherCAT terminal using the existing QiTech machine framework. It is designed as the simplest possible working example of hardware interaction: turning an LED on and off.
|
||||
|
||||
---
|
||||
|
||||
# 1. Introduction
|
||||
## 2. Requirements
|
||||
|
||||
The EL2004 LED Toggle Example is a minimal demonstration showing how to control digital outputs on a **Beckhoff EL2004 EtherCAT terminal** using the QiTech machine framework.
|
||||
It represents the simplest possible hardware interaction in the system:
|
||||
**toggling LED outputs using the QiTech Control Dashboard.**
|
||||
### Hardware
|
||||
- Beckhoff EL2004 EtherCAT Terminal (Digital Output, 4 channels)
|
||||
- Beckhoff EK1100 EtherCAT Coupler
|
||||
- Power supply for EK1100 and EL2004 module (AC/DC Adapter with 24V Output )
|
||||
- Connecting wires
|
||||
- A PC connected to the EtherCAT network
|
||||
|
||||
### Software
|
||||
- Rust toolchain (for building backend machine code)
|
||||
- Node.js and npm/yarn (for frontend control interface)
|
||||
- The project repository cloned locally
|
||||
- Access to EtherCAT drivers and any required HAL bindings (available in the QITech control repository)
|
||||
|
||||
---
|
||||
|
||||
# 2. Requirements
|
||||
## 3. Wiring / Hardware Setup
|
||||
|
||||
## Hardware
|
||||
- Beckhoff **EL2004 EtherCAT Terminal** (4-channel digital output)
|
||||
- Beckhoff **EK1100 EtherCAT Coupler**
|
||||
- **24 V DC power supply** (AC/DC adapter + DC hollow plug)
|
||||
- Jumper / bridge wires (0.5–1.5 mm² recommended)
|
||||
- A **Linux PC** (Ubuntu/Debian recommended)
|
||||
- Standard Ethernet cable
|
||||
- Flat screwdriver
|
||||
### Schematic
|
||||
<img width="1123" height="859" alt="Screenshot 2025-12-03 at 16 12 26" src="https://github.com/user-attachments/assets/a04278d9-7f6a-442d-9ea7-7ace9ac44872" />
|
||||
|
||||
## Software
|
||||
*(Installation steps in Section 4)*
|
||||
- Rust toolchain
|
||||
- Node.js + npm
|
||||
- Git
|
||||
- QiTech Control repository
|
||||
- EtherCAT HAL (included inside repo)
|
||||
|
||||
---
|
||||
### Needed Hardware Overview
|
||||
|
||||
# 3. Hardware Setup
|
||||
<img width="813" height="1079" alt="Screenshot 2025-12-03 at 16 12 42" src="https://github.com/user-attachments/assets/af34a581-a668-4c6b-9d7f-062355813b6a" />
|
||||
|
||||
## 3.1 Schematic
|
||||
|
||||

|
||||
Figure 1: Beckhoff EK1100 Bus Coupler — the device circles in red is a simple 24V Power Source
|
||||
|
||||
---
|
||||
|
||||
## 3.2 EK1100 Wiring
|
||||
|
||||
This wiring configuration powers the EL2004 and prepares it for LED control.
|
||||
It is not the only possible wiring but is the **simplest functional setup**.
|
||||
|
||||
### ⚠️ Safety Warning
|
||||
Always disconnect power before wiring.
|
||||
Working on live EtherCAT terminals can cause serious damage or electrical shock.
|
||||
|
||||
---
|
||||
|
||||
## 3.2.1 Safe Wiring Procedure (Beckhoff Recommended)
|
||||
|
||||
1. Insert a screwdriver **straight** into the square release hole.
|
||||
2. Insert the stripped wire into the round opening.
|
||||
3. Remove the screwdriver — the spring clamp locks the wire.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 3.2.2 Wiring Steps (Used in This Example)
|
||||
|
||||
We supply power using a **DC hollow-plug adapter**, like this one:
|
||||
https://www.amazon.de/dp/B093FTFZ8Q
|
||||
|
||||
Perform the following wiring on the EK1100:
|
||||
|
||||
1. Red wire **(+24 V)** → Terminal **2**
|
||||
2. Black wire **(0 V)** → Terminal **3**
|
||||
3. Jumper wire from **Terminal 1 → Terminal 6**
|
||||
4. Jumper wire from **Terminal 5 → Terminal 7**
|
||||
|
||||
After wiring, your module should look like **Figure 1**.
|
||||
|
||||
---
|
||||
|
||||
### **Figure 1 — EK1100 Minimal Wiring**
|
||||
<img src="./docs/assets/ek1100.jpeg" width="400">
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 3.3 EL2004 Integration
|
||||
|
||||
Slide the EL2004 onto the **right side of the EK1100** until it locks.
|
||||
The EtherCAT E-Bus and power contacts connect automatically — **no wiring required**.
|
||||
|
||||
### **Figure 2 — EL2004 Terminal**
|
||||
<img src="./docs/assets/el2004.jpeg" width="400">
|
||||
|
||||
---
|
||||
|
||||
## 3.4 Final Assembled Setup
|
||||
|
||||
### **Figure 3 — EK1100 + EL2004 Connected**
|
||||
<img src="./docs/assets/complete.jpeg" width="400">
|
||||
|
||||
---
|
||||
|
||||
## 3.5 Power & Ethernet
|
||||
|
||||
### Power
|
||||
Connect the 24 V adapter to the hollow plug used earlier.
|
||||
**Example AC/DC Adapter (Figure 4):**
|
||||
|
||||
<img src="./docs/assets/adapter.jpeg" width="400">
|
||||
|
||||
|
||||
### Ethernet
|
||||
Use a standard LAN cable to connect your PC → EK1100.
|
||||
The final powered up and connected setup should look like this:
|
||||
|
||||
<img src="./docs/assets/power.jpeg" width="400">
|
||||
|
||||
---
|
||||
|
||||
# 4. Software Setup
|
||||
|
||||
## 4.1 Installing on Ubuntu/Debian
|
||||
|
||||
Paste this into your terminal:
|
||||
|
||||
```bash
|
||||
# Press Enter when prompted
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
|
||||
sudo apt update
|
||||
sudo apt install -y npm nodejs git
|
||||
|
||||
git clone git@github.com:qitechgmbh/control.git
|
||||
cd control/electron
|
||||
npm install
|
||||
```
|
||||
## 4.2 Running the Backend
|
||||
```bash
|
||||
./cargo_run_linux.sh
|
||||
```
|
||||
This script:
|
||||
|
||||
- Builds the backend
|
||||
|
||||
- Grants required system capabilities (raw sockets)
|
||||
|
||||
- Starts EtherCAT communication
|
||||
|
||||
Ensure the EK1100 is connected.
|
||||
|
||||
## 4.3 Running the Frontend
|
||||
```bash
|
||||
cd electron
|
||||
npm run start
|
||||
```
|
||||
This launches the QiTech Control dashboard.
|
||||
# 5. Demo
|
||||
|
||||
## 5.1 Assigning Devices in the Dashboard
|
||||
|
||||
Once the backend + frontend are running, you should see:
|
||||
|
||||
EK1100 Coupler
|
||||
|
||||
EL2004 Digital Output Terminal
|
||||

|
||||
|
||||
Steps:
|
||||
|
||||
- Click Assign on the EK1100
|
||||
|
||||
- Select TestMachine V1
|
||||
|
||||

|
||||
|
||||
|
||||
- Enter a serial number (use the same for EK1100 + EL2004)
|
||||
|
||||

|
||||
|
||||
- Click Write
|
||||
|
||||
- Repeat for the EL2004
|
||||
|
||||
## 5.2 Testing LED Control
|
||||
Navigate to:
|
||||
|
||||
Machines → TestMachine
|
||||

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

|
||||
|
||||
**You can now toggle the four digital outputs of the EL2004.**
|
||||
# 6. References
|
||||
|
||||
This guide incorporates information from official Beckhoff documentation.
|
||||
All diagrams, product names, and figures belong to Beckhoff Automation GmbH & Co. KG and are used here solely for educational purposes.
|
||||
|
||||
Referenced Manuals
|
||||
|
||||
[Beckhoff EK1100 Documentation](https://download.beckhoff.com/download/Document/io/ethercat-terminals/ek110x_ek15xx_en.pdf)
|
||||
|
||||
[Beckhoff EL2004 Documentation](https://download.beckhoff.com/download/Document/io/ethercat-terminals/el20xx_el2124_de.pdf)
|
||||
|
||||
# 7. Acknowledgements
|
||||
|
||||
This tutorial is inspired by the clarity and educational quality of Beckhoff manuals.
|
||||
All wiring illustrations and hardware descriptions in this guide are provided for demonstration purposes only and do not replace official Beckhoff installation guidelines.
|
||||
|
||||
Special thanks to the QiTech engineering team for providing the backend architecture, EtherCAT HAL abstraction, and the TestMachine framework that makes this example possible.
|
||||
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 2.3 MiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 206 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 74 KiB |
@@ -91,7 +91,7 @@
|
||||
"globals": "^16.2.0",
|
||||
"jsdom": "^26.0.0",
|
||||
"postcss": "^8.5.2",
|
||||
"prettier": "^3.7.3",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||
"tailwindcss": "^4.1.4",
|
||||
"ts-node": "^10.9.2",
|
||||
@@ -175,6 +175,7 @@
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz",
|
||||
"integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
@@ -638,6 +639,7 @@
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -660,6 +662,7 @@
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -1145,6 +1148,7 @@
|
||||
"integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.1",
|
||||
"fs-extra": "^9.0.1",
|
||||
@@ -4091,6 +4095,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.130.17.tgz",
|
||||
"integrity": "sha512-F1tyMEbX7PJclnA25hAFXhLKq4jqeODvvaogsd0/NX02Kmz3QoT9GV+rgpv2TxamU6RwjRgihSH06Dn5hM6SOw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tanstack/history": "1.130.12",
|
||||
"@tanstack/react-store": "^0.7.0",
|
||||
@@ -4172,6 +4177,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.130.17.tgz",
|
||||
"integrity": "sha512-0AH4s/52X2shYLifr9hR+6R6NpGsBp1M3LyjBTZm6Jlkud+nOhkkRxKxYS7l+JErRlffdxVHxMdoTnKcQ+duiQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tanstack/history": "1.130.12",
|
||||
"@tanstack/store": "^0.7.0",
|
||||
@@ -4405,8 +4411,7 @@
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
@@ -4563,6 +4568,7 @@
|
||||
"integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.10.0"
|
||||
}
|
||||
@@ -4579,6 +4585,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.16.tgz",
|
||||
"integrity": "sha512-WBM/nDbEZmDUORKnh5i1bTnAz6vTohUf9b8esSMu+b24+srbaxa04UbJgWx78CVfNXA20sNu0odEIluZDFdCog==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
@@ -4588,6 +4595,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz",
|
||||
"integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
|
||||
"devOptional": true,
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.0.0"
|
||||
}
|
||||
@@ -4670,6 +4678,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz",
|
||||
"integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.38.0",
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
@@ -5024,6 +5033,7 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -5471,6 +5481,7 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001726",
|
||||
"electron-to-chromium": "^1.5.173",
|
||||
@@ -6163,7 +6174,8 @@
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/data-urls": {
|
||||
"version": "5.0.0",
|
||||
@@ -6436,8 +6448,7 @@
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
@@ -7437,6 +7448,7 @@
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz",
|
||||
"integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -7497,6 +7509,7 @@
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
|
||||
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
@@ -8803,6 +8816,7 @@
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6"
|
||||
},
|
||||
@@ -8865,6 +8879,7 @@
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
|
||||
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
@@ -9539,6 +9554,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
|
||||
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cssstyle": "^4.2.1",
|
||||
"data-urls": "^5.0.0",
|
||||
@@ -10104,7 +10120,6 @@
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
}
|
||||
@@ -12045,11 +12060,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.7.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.3.tgz",
|
||||
"integrity": "sha512-QgODejq9K3OzoBbuyobZlUhznP5SKwPqp+6Q6xw6o8gnhr4O85L2U915iM2IDcfF2NPXVaM9zlo9tdwipnYwzg==",
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
||||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
@@ -12163,7 +12178,6 @@
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
@@ -12178,7 +12192,6 @@
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -12307,6 +12320,7 @@
|
||||
"version": "19.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
|
||||
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -12315,6 +12329,7 @@
|
||||
"version": "19.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
|
||||
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
@@ -12326,6 +12341,7 @@
|
||||
"version": "7.62.0",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz",
|
||||
"integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
@@ -12366,8 +12382,7 @@
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
@@ -13118,6 +13133,7 @@
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz",
|
||||
"integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
@@ -13907,7 +13923,8 @@
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
|
||||
"integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="
|
||||
"integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tailwindcss-animate": {
|
||||
"version": "1.0.7",
|
||||
@@ -14009,7 +14026,8 @@
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
|
||||
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tiny-warning": {
|
||||
"version": "1.0.3",
|
||||
@@ -14060,6 +14078,7 @@
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -14371,6 +14390,7 @@
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"devOptional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -14732,6 +14752,7 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
@@ -14853,6 +14874,7 @@
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -15396,6 +15418,7 @@
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"globals": "^16.2.0",
|
||||
"jsdom": "^26.0.0",
|
||||
"postcss": "^8.5.2",
|
||||
"prettier": "^3.7.3",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||
"tailwindcss": "^4.1.4",
|
||||
"ts-node": "^10.9.2",
|
||||
|
||||
@@ -152,7 +152,7 @@ export function Extruder2ControlPage() {
|
||||
unit="rpm"
|
||||
title="Target Output RPM"
|
||||
min={0}
|
||||
max={44}
|
||||
max={106}
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
onChange={setInverterTargetRpm}
|
||||
/>
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
import { ControlCard } from "@/control/ControlCard";
|
||||
import { Page } from "@/components/Page";
|
||||
import React from "react";
|
||||
import { ControlGrid } from "@/control/ControlGrid";
|
||||
import {
|
||||
SelectionGroup,
|
||||
SelectionGroupBoolean,
|
||||
} from "@/control/SelectionGroup";
|
||||
import { HeatingZone } from "../HeatingZone";
|
||||
import { Label } from "@/control/Label";
|
||||
import { EditValue } from "@/control/EditValue";
|
||||
import { roundToDecimals } from "@/lib/decimal";
|
||||
import { useExtruder3 } from "./useExtruder";
|
||||
import { TimeSeriesValueNumeric } from "@/control/TimeSeriesValue";
|
||||
import { StatusBadge } from "@/control/StatusBadge";
|
||||
|
||||
export function Extruder3ControlPage() {
|
||||
const {
|
||||
state,
|
||||
defaultState,
|
||||
nozzleTemperature,
|
||||
nozzlePower,
|
||||
frontTemperature,
|
||||
frontPower,
|
||||
backTemperature,
|
||||
backPower,
|
||||
middleTemperature,
|
||||
middlePower,
|
||||
pressure,
|
||||
|
||||
motorScrewRpm,
|
||||
motorPower,
|
||||
motorCurrent,
|
||||
totalEnergyKWh,
|
||||
combinedPower,
|
||||
|
||||
setExtruderMode,
|
||||
setBackHeatingTemperature,
|
||||
setFrontHeatingTemperature,
|
||||
setMiddleHeatingTemperature,
|
||||
setNozzleHeatingTemperature,
|
||||
setInverterRegulation,
|
||||
setInverterTargetPressure,
|
||||
setInverterTargetRpm,
|
||||
|
||||
isLoading,
|
||||
isDisabled,
|
||||
} = useExtruder3();
|
||||
|
||||
function isZoneReadyForExtrusion(
|
||||
temperature: number,
|
||||
targetTemperature: number,
|
||||
) {
|
||||
// if temperature is 90% of the target temperature, then we are ready for extrusion
|
||||
return temperature >= 0.9 * targetTemperature && targetTemperature > 0.0;
|
||||
}
|
||||
|
||||
const allZonesReadyForExtrude = () => {
|
||||
const frontReady = isZoneReadyForExtrusion(
|
||||
frontTemperature.current?.value ?? 0,
|
||||
state?.heating_states.front.target_temperature ?? 1,
|
||||
);
|
||||
const middleReady = isZoneReadyForExtrusion(
|
||||
middleTemperature.current?.value ?? 0,
|
||||
state?.heating_states.middle.target_temperature ?? 1,
|
||||
);
|
||||
const backReady = isZoneReadyForExtrusion(
|
||||
backTemperature.current?.value ?? 0,
|
||||
state?.heating_states.back.target_temperature ?? 1,
|
||||
);
|
||||
const nozzleReady = isZoneReadyForExtrusion(
|
||||
nozzleTemperature.current?.value ?? 0,
|
||||
state?.heating_states.nozzle.target_temperature ?? 1,
|
||||
);
|
||||
|
||||
return frontReady && middleReady && backReady && nozzleReady;
|
||||
};
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<ControlGrid>
|
||||
<HeatingZone
|
||||
title={"Heating Front"}
|
||||
heatingState={state?.heating_states.front}
|
||||
heatingTimeSeries={frontTemperature}
|
||||
heatingPower={frontPower}
|
||||
onChangeTargetTemp={setFrontHeatingTemperature}
|
||||
min={0}
|
||||
max={300}
|
||||
/>
|
||||
<HeatingZone
|
||||
title={"Heating Middle"}
|
||||
heatingState={state?.heating_states.middle}
|
||||
heatingTimeSeries={middleTemperature}
|
||||
heatingPower={middlePower}
|
||||
onChangeTargetTemp={setMiddleHeatingTemperature}
|
||||
min={0}
|
||||
max={300}
|
||||
/>
|
||||
<HeatingZone
|
||||
title={"Heating Back"}
|
||||
heatingState={state?.heating_states.back}
|
||||
heatingTimeSeries={backTemperature}
|
||||
heatingPower={backPower}
|
||||
onChangeTargetTemp={setBackHeatingTemperature}
|
||||
min={0}
|
||||
max={300}
|
||||
/>
|
||||
<HeatingZone
|
||||
title={"Heating Nozzle"}
|
||||
heatingState={state?.heating_states.nozzle}
|
||||
heatingTimeSeries={nozzleTemperature}
|
||||
heatingPower={nozzlePower}
|
||||
onChangeTargetTemp={setNozzleHeatingTemperature}
|
||||
min={0}
|
||||
max={300}
|
||||
/>
|
||||
<ControlCard className="bg-red" title="Screw Drive">
|
||||
{state?.inverter_status_state.overload_warning == true ? (
|
||||
<StatusBadge variant="error">
|
||||
Inverter is overloaded! Please check the extruder and reduce load
|
||||
if necessary.
|
||||
</StatusBadge>
|
||||
) : state?.inverter_status_state.fault_occurence == true ? (
|
||||
<StatusBadge variant="error">
|
||||
Inverter encountered an error!! Press the restart button in Config
|
||||
</StatusBadge>
|
||||
) : state?.inverter_status_state.running == true &&
|
||||
state.inverter_status_state.fault_occurence == false ? (
|
||||
<StatusBadge variant="success">Running</StatusBadge>
|
||||
) : null}
|
||||
{state?.inverter_status_state.running == false &&
|
||||
state.inverter_status_state.fault_occurence == false && (
|
||||
<StatusBadge variant="success">Healthy</StatusBadge>
|
||||
)}
|
||||
|
||||
<Label label="Regulation">
|
||||
<SelectionGroupBoolean
|
||||
value={state?.regulation_state.uses_rpm}
|
||||
optionTrue={{ children: "RPM" }}
|
||||
optionFalse={{ children: "Pressure" }}
|
||||
onChange={setInverterRegulation}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
/>
|
||||
</Label>
|
||||
<div className="flex flex-row flex-wrap gap-4">
|
||||
<Label label="Target Output RPM">
|
||||
<EditValue
|
||||
value={state?.screw_state.target_rpm}
|
||||
defaultValue={defaultState?.screw_state.target_rpm}
|
||||
unit="rpm"
|
||||
title="Target Output RPM"
|
||||
min={0}
|
||||
max={100}
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
onChange={setInverterTargetRpm}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Target Pressure">
|
||||
<EditValue
|
||||
value={state?.pressure_state.target_bar}
|
||||
defaultValue={defaultState?.pressure_state.target_bar}
|
||||
unit="bar"
|
||||
title="Target Pressure"
|
||||
min={0.0}
|
||||
max={270.0}
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
onChange={setInverterTargetPressure}
|
||||
/>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex flex-row flex-wrap gap-4">
|
||||
<TimeSeriesValueNumeric
|
||||
label="Rpm"
|
||||
unit="rpm"
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
timeseries={motorScrewRpm}
|
||||
/>
|
||||
|
||||
{state?.pressure_state?.wiring_error && (
|
||||
<StatusBadge variant="error">
|
||||
Cant Measure Pressure! Check Pressure Sensor Wiring!
|
||||
</StatusBadge>
|
||||
)}
|
||||
<TimeSeriesValueNumeric
|
||||
label="Pressure"
|
||||
unit="bar"
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
timeseries={pressure}
|
||||
/>
|
||||
</div>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard className="bg-red" title="Mode">
|
||||
<SelectionGroup<"Standby" | "Heat" | "Extrude">
|
||||
value={state?.mode_state.mode}
|
||||
orientation="vertical"
|
||||
className="grid h-full grid-cols-2 gap-2"
|
||||
options={{
|
||||
Standby: {
|
||||
children: "Standby",
|
||||
icon: "lu:CirclePause",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
Heat: {
|
||||
children: "Heat",
|
||||
icon: "lu:Flame",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
Extrude: {
|
||||
children: "Extrude",
|
||||
icon: "lu:ArrowBigLeftDash",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
confirmation: allZonesReadyForExtrude()
|
||||
? undefined
|
||||
: "Temperature is too low. Are you sure you want to extrude?",
|
||||
},
|
||||
}}
|
||||
onChange={setExtruderMode}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
/>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard className="bg-blue" title="Power Consumption">
|
||||
<TimeSeriesValueNumeric
|
||||
label="Total Power"
|
||||
unit="W"
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
timeseries={combinedPower}
|
||||
/>
|
||||
<TimeSeriesValueNumeric
|
||||
label="Motor Power"
|
||||
unit="W"
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
timeseries={motorPower}
|
||||
/>
|
||||
<TimeSeriesValueNumeric
|
||||
label="Motor Current"
|
||||
unit="A"
|
||||
renderValue={(value) => roundToDecimals(value, 1)}
|
||||
timeseries={motorCurrent}
|
||||
/>
|
||||
<TimeSeriesValueNumeric
|
||||
label="Total Energy Consumption"
|
||||
unit="kWh"
|
||||
renderValue={(value) => roundToDecimals(value, 3)}
|
||||
timeseries={totalEnergyKWh}
|
||||
/>
|
||||
</ControlCard>
|
||||
</ControlGrid>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
@@ -1,325 +0,0 @@
|
||||
import { Page } from "@/components/Page";
|
||||
import {
|
||||
AutoSyncedBigGraph,
|
||||
SyncedFloatingControlPanel,
|
||||
useGraphSync,
|
||||
type GraphConfig,
|
||||
} from "@/components/graph";
|
||||
import React from "react";
|
||||
import { useExtruder3 } from "./useExtruder";
|
||||
|
||||
export function Extruder3GraphsPage() {
|
||||
const {
|
||||
state,
|
||||
nozzleTemperature,
|
||||
nozzlePower,
|
||||
frontTemperature,
|
||||
frontPower,
|
||||
backTemperature,
|
||||
backPower,
|
||||
middleTemperature,
|
||||
middlePower,
|
||||
pressure,
|
||||
motorScrewRpm,
|
||||
motorCurrent,
|
||||
motorPower,
|
||||
combinedPower,
|
||||
} = useExtruder3();
|
||||
|
||||
const syncHook = useGraphSync("extruder-graphs");
|
||||
|
||||
// Base config
|
||||
const baseConfig: GraphConfig = {
|
||||
defaultTimeWindow: 30 * 60 * 1000, // 30 minutes
|
||||
exportFilename: "extruder_data",
|
||||
showLegend: true,
|
||||
title: "extruder data",
|
||||
};
|
||||
|
||||
const temperatureData = [
|
||||
...(nozzleTemperature
|
||||
? [
|
||||
{
|
||||
newData: nozzleTemperature,
|
||||
title: "Nozzle",
|
||||
color: "#ef4444",
|
||||
lines:
|
||||
state?.heating_states.nozzle?.target_temperature !== undefined
|
||||
? [
|
||||
{
|
||||
type: "target" as const,
|
||||
value: state.heating_states.nozzle.target_temperature,
|
||||
color: "#ef4444",
|
||||
show: true,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(frontTemperature
|
||||
? [
|
||||
{
|
||||
newData: frontTemperature,
|
||||
title: "Front",
|
||||
color: "#f59e0b",
|
||||
lines:
|
||||
state?.heating_states.front?.target_temperature !== undefined
|
||||
? [
|
||||
{
|
||||
type: "target" as const,
|
||||
value: state.heating_states.front.target_temperature,
|
||||
color: "#f59e0b",
|
||||
show: true,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(middleTemperature
|
||||
? [
|
||||
{
|
||||
newData: middleTemperature,
|
||||
title: "Middle",
|
||||
color: "#8b5cf6",
|
||||
lines:
|
||||
state?.heating_states.middle?.target_temperature !== undefined
|
||||
? [
|
||||
{
|
||||
type: "target" as const,
|
||||
value: state.heating_states.middle.target_temperature,
|
||||
color: "#8b5cf6",
|
||||
show: true,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(backTemperature
|
||||
? [
|
||||
{
|
||||
newData: backTemperature,
|
||||
title: "Back",
|
||||
color: "#3b82f6",
|
||||
lines:
|
||||
state?.heating_states.back?.target_temperature !== undefined
|
||||
? [
|
||||
{
|
||||
type: "target" as const,
|
||||
value: state.heating_states.back.target_temperature,
|
||||
color: "#3b82f6",
|
||||
show: true,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
const temperatureConfig: GraphConfig = {
|
||||
...baseConfig,
|
||||
title: "Temperatures",
|
||||
exportFilename: "temperatures_data",
|
||||
colors: {
|
||||
primary: "#ef4444",
|
||||
grid: "#e2e8f0",
|
||||
axis: "#64748b",
|
||||
background: "#ffffff",
|
||||
},
|
||||
};
|
||||
|
||||
// Combined Power Graph (no target lines for power)
|
||||
const powerData = [
|
||||
...(nozzlePower
|
||||
? [
|
||||
{
|
||||
newData: nozzlePower,
|
||||
title: "Nozzle",
|
||||
color: "#ef4444",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(frontPower
|
||||
? [
|
||||
{
|
||||
newData: frontPower,
|
||||
title: "Front",
|
||||
color: "#f59e0b",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(middlePower
|
||||
? [
|
||||
{
|
||||
newData: middlePower,
|
||||
title: "Middle",
|
||||
color: "#8b5cf6",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(backPower
|
||||
? [
|
||||
{
|
||||
newData: backPower,
|
||||
title: "Back",
|
||||
color: "#3b82f6",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(motorPower
|
||||
? [
|
||||
{
|
||||
newData: motorPower,
|
||||
title: "Motor",
|
||||
color: "#10b981",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(combinedPower
|
||||
? [
|
||||
{
|
||||
newData: combinedPower,
|
||||
title: "Total",
|
||||
color: "#000000",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
const powerConfig: GraphConfig = {
|
||||
...baseConfig,
|
||||
title: "Power Outputs",
|
||||
exportFilename: "power_data",
|
||||
colors: {
|
||||
primary: "#10b981",
|
||||
grid: "#e2e8f0",
|
||||
axis: "#64748b",
|
||||
background: "#ffffff",
|
||||
},
|
||||
};
|
||||
|
||||
const currentConfig: GraphConfig = {
|
||||
...baseConfig,
|
||||
title: "Motor Current",
|
||||
exportFilename: "motor_current_data",
|
||||
colors: {
|
||||
primary: "#3b82f6",
|
||||
grid: "#e2e8f0",
|
||||
axis: "#64748b",
|
||||
background: "#ffffff",
|
||||
},
|
||||
};
|
||||
|
||||
// Pressure Graph with connected target line
|
||||
const pressureConfig: GraphConfig = {
|
||||
...baseConfig,
|
||||
title: "Pressure",
|
||||
exportFilename: "pressure_data",
|
||||
colors: {
|
||||
primary: "#3b82f6",
|
||||
grid: "#e2e8f0",
|
||||
axis: "#64748b",
|
||||
background: "#ffffff",
|
||||
},
|
||||
};
|
||||
|
||||
// RPM Graph with connected target line
|
||||
const rpmConfig: GraphConfig = {
|
||||
...baseConfig,
|
||||
title: "RPM",
|
||||
exportFilename: "rpm_data",
|
||||
colors: {
|
||||
primary: "#8b5cf6",
|
||||
grid: "#e2e8f0",
|
||||
axis: "#64748b",
|
||||
background: "#ffffff",
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<Page className="pb-25">
|
||||
<div className="flex flex-col gap-4">
|
||||
<AutoSyncedBigGraph
|
||||
syncHook={syncHook}
|
||||
newData={{
|
||||
newData: pressure,
|
||||
color: "#3b82f6",
|
||||
lines:
|
||||
state?.pressure_state.target_bar !== undefined
|
||||
? [
|
||||
{
|
||||
type: "target" as const,
|
||||
value: state.pressure_state.target_bar,
|
||||
color: "#3b82f6",
|
||||
show: true,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
}}
|
||||
config={pressureConfig}
|
||||
unit="bar"
|
||||
renderValue={(value) => value.toFixed(2)}
|
||||
graphId="pressure-graph"
|
||||
/>
|
||||
|
||||
<AutoSyncedBigGraph
|
||||
syncHook={syncHook}
|
||||
newData={temperatureData}
|
||||
config={temperatureConfig}
|
||||
unit="C"
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
graphId="combined-temperatures"
|
||||
/>
|
||||
|
||||
<AutoSyncedBigGraph
|
||||
syncHook={syncHook}
|
||||
newData={powerData}
|
||||
config={powerConfig}
|
||||
unit="W"
|
||||
renderValue={(value) => value.toFixed(1)}
|
||||
graphId="combined-power"
|
||||
/>
|
||||
|
||||
<AutoSyncedBigGraph
|
||||
syncHook={syncHook}
|
||||
newData={{
|
||||
newData: motorCurrent,
|
||||
color: "#3b82f6",
|
||||
}}
|
||||
config={currentConfig}
|
||||
unit="A"
|
||||
renderValue={(value) => value.toFixed(2)}
|
||||
graphId="motor-current"
|
||||
/>
|
||||
|
||||
<AutoSyncedBigGraph
|
||||
syncHook={syncHook}
|
||||
newData={{
|
||||
newData: motorScrewRpm,
|
||||
color: "#8b5cf6",
|
||||
lines:
|
||||
state?.screw_state.target_rpm !== undefined
|
||||
? [
|
||||
{
|
||||
type: "target" as const,
|
||||
value: state.screw_state.target_rpm,
|
||||
color: "#8b5cf6",
|
||||
show: true,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
}}
|
||||
config={rpmConfig}
|
||||
unit="rpm"
|
||||
renderValue={(value) => value.toFixed(0)}
|
||||
graphId="rpm-graph"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SyncedFloatingControlPanel controlProps={syncHook.controlProps} />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Page } from "@/components/Page";
|
||||
import { MarkdownWithToc } from "@/components/MarkdownWithToc";
|
||||
import React from "react";
|
||||
import extruderManualContent from "@/assets/markdown/extruder/manual.md?raw";
|
||||
|
||||
export function ExtruderV3ManualPage() {
|
||||
return (
|
||||
<Page>
|
||||
<MarkdownWithToc markdownContent={extruderManualContent} />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { Topbar } from "@/components/Topbar";
|
||||
import { extruder3Route } from "@/routes/routes";
|
||||
import React from "react";
|
||||
|
||||
export function Extruder3Page() {
|
||||
const { serial } = extruder3Route.useParams();
|
||||
return (
|
||||
<Topbar
|
||||
pathname={`/_sidebar/machines/extruder3/${serial}`}
|
||||
items={[
|
||||
{
|
||||
link: "control",
|
||||
title: "Control",
|
||||
icon: "lu:CirclePlay",
|
||||
activeLink: "control",
|
||||
},
|
||||
{
|
||||
link: "graphs",
|
||||
title: "Graphs",
|
||||
icon: "lu:ChartSpline",
|
||||
activeLink: "graphs",
|
||||
},
|
||||
{
|
||||
link: "settings",
|
||||
title: "Config",
|
||||
icon: "lu:Settings",
|
||||
activeLink: "settings",
|
||||
},
|
||||
{
|
||||
link: "manual",
|
||||
title: "Manual",
|
||||
icon: "lu:BookOpen",
|
||||
activeLink: "manual",
|
||||
},
|
||||
{
|
||||
link: "presets",
|
||||
title: "Presets",
|
||||
icon: "lu:Save",
|
||||
activeLink: "presets",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
import React from "react";
|
||||
import { PresetsPage } from "@/components/preset/PresetsPage";
|
||||
import { extruder3 } from "@/machines/properties";
|
||||
import { useExtruder3 } from "./useExtruder";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
PresetPreviewEntries,
|
||||
previewSeparator,
|
||||
} from "@/components/preset/PresetPreviewTable";
|
||||
import { Preset, PresetData } from "@/lib/preset/preset";
|
||||
|
||||
const extruder3PresetDataSchema = z
|
||||
.object({
|
||||
targetFrontHeatingTemperature: z.number(),
|
||||
targetMiddleHeatingTemperature: z.number(),
|
||||
targetBackHeatingTemperature: z.number(),
|
||||
targetNozzleHeatingTemperature: z.number(),
|
||||
|
||||
targetInverterRpm: z.number(),
|
||||
targetInverterPressure: z.number(),
|
||||
inverterRegulation: z.enum(["RPM", "Pressure"]),
|
||||
|
||||
inverterRotationDirection: z.enum(["Forward", "Backward"]),
|
||||
|
||||
pressureLimit: z.number(),
|
||||
pressureLimitEnabled: z.boolean(),
|
||||
|
||||
pidPressureKp: z.number(),
|
||||
pidPressureKi: z.number(),
|
||||
pidPressureKd: z.number(),
|
||||
})
|
||||
.partial();
|
||||
|
||||
type Extruder3 = typeof extruder3PresetDataSchema;
|
||||
|
||||
const schemas = new Map([[1, extruder3PresetDataSchema]]);
|
||||
|
||||
const previewEntries: PresetPreviewEntries<Extruder3> = [
|
||||
{
|
||||
name: "Target Front Temperature",
|
||||
unit: "C",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.targetFrontHeatingTemperature?.toFixed(1),
|
||||
},
|
||||
{
|
||||
name: "Target Middle Temperature",
|
||||
unit: "C",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.targetMiddleHeatingTemperature?.toFixed(1),
|
||||
},
|
||||
{
|
||||
name: "Target Back Temperature",
|
||||
unit: "C",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.targetBackHeatingTemperature?.toFixed(1),
|
||||
},
|
||||
{
|
||||
name: "Target Nozzle Temperature",
|
||||
unit: "C",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.targetNozzleHeatingTemperature?.toFixed(1),
|
||||
},
|
||||
previewSeparator,
|
||||
{
|
||||
name: "Inverter Regulation",
|
||||
renderValue: (data: PresetData<Extruder3>) => data.inverterRegulation,
|
||||
},
|
||||
{
|
||||
name: "Target Inverter RPM",
|
||||
unit: "rpm",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.targetInverterRpm?.toFixed(2),
|
||||
},
|
||||
{
|
||||
name: "Target Inverter Pressure",
|
||||
unit: "bar",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.targetInverterPressure?.toFixed(1),
|
||||
},
|
||||
{
|
||||
name: "Inverter Direction",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.inverterRotationDirection,
|
||||
},
|
||||
previewSeparator,
|
||||
{
|
||||
name: "Pressure Limit",
|
||||
unit: "bar",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.pressureLimit?.toFixed(1),
|
||||
},
|
||||
{
|
||||
name: "Enable Pressure Limit",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.pressureLimitEnabled ? "on" : "off",
|
||||
},
|
||||
previewSeparator,
|
||||
{
|
||||
name: "PID Pressue Kp",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.pidPressureKp?.toFixed(4),
|
||||
},
|
||||
{
|
||||
name: "PID Pressue Ki",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.pidPressureKi?.toFixed(4),
|
||||
},
|
||||
{
|
||||
name: "PID Pressue Kd",
|
||||
renderValue: (data: PresetData<Extruder3>) =>
|
||||
data.pidPressureKd?.toFixed(4),
|
||||
},
|
||||
];
|
||||
|
||||
export function Extruder3PresetsPage() {
|
||||
const {
|
||||
state,
|
||||
defaultState,
|
||||
|
||||
setFrontHeatingTemperature,
|
||||
setMiddleHeatingTemperature,
|
||||
setBackHeatingTemperature,
|
||||
setNozzleHeatingTemperature,
|
||||
|
||||
setInverterTargetRpm,
|
||||
setInverterTargetPressure,
|
||||
setInverterRegulation,
|
||||
setInverterRotationDirection,
|
||||
|
||||
setExtruderPressureLimit,
|
||||
setExtruderPressureLimitEnabled,
|
||||
|
||||
setPressurePidKd,
|
||||
setPressurePidKi,
|
||||
setPressurePidKp,
|
||||
} = useExtruder3();
|
||||
|
||||
const toPresetData = (s?: typeof state): PresetData<Extruder3> => ({
|
||||
targetFrontHeatingTemperature: s?.heating_states.front.target_temperature,
|
||||
targetMiddleHeatingTemperature: s?.heating_states.middle.target_temperature,
|
||||
targetBackHeatingTemperature: s?.heating_states.back.target_temperature,
|
||||
targetNozzleHeatingTemperature: s?.heating_states.nozzle.target_temperature,
|
||||
|
||||
targetInverterRpm: s?.screw_state.target_rpm,
|
||||
targetInverterPressure: s?.pressure_state.target_bar,
|
||||
inverterRegulation: s?.regulation_state.uses_rpm ? "RPM" : "Pressure",
|
||||
|
||||
inverterRotationDirection: s?.rotation_state?.forward
|
||||
? "Forward"
|
||||
: "Backward",
|
||||
|
||||
pressureLimit: s?.extruder_settings_state.pressure_limit,
|
||||
pressureLimitEnabled: s?.extruder_settings_state.pressure_limit_enabled,
|
||||
|
||||
pidPressureKp: s?.pid_settings.pressure.kp,
|
||||
pidPressureKi: s?.pid_settings.pressure.ki,
|
||||
pidPressureKd: s?.pid_settings.pressure.kd,
|
||||
});
|
||||
|
||||
const defaults = toPresetData(defaultState);
|
||||
|
||||
const applyPreset = (preset: Preset<Extruder3>) => {
|
||||
setFrontHeatingTemperature(
|
||||
preset.data.targetFrontHeatingTemperature ?? 150.0,
|
||||
);
|
||||
setMiddleHeatingTemperature(
|
||||
preset.data.targetMiddleHeatingTemperature ?? 150.0,
|
||||
);
|
||||
setBackHeatingTemperature(
|
||||
preset.data.targetBackHeatingTemperature ?? 150.0,
|
||||
);
|
||||
setNozzleHeatingTemperature(
|
||||
preset.data.targetNozzleHeatingTemperature ?? 150.0,
|
||||
);
|
||||
|
||||
setInverterTargetRpm(preset.data.targetInverterRpm ?? 0);
|
||||
setInverterTargetPressure(preset.data.targetInverterPressure ?? 0);
|
||||
setInverterRegulation(preset.data.inverterRegulation === "RPM");
|
||||
|
||||
setInverterRotationDirection(
|
||||
preset.data.inverterRotationDirection === "Forward",
|
||||
);
|
||||
|
||||
setExtruderPressureLimit(preset.data.pressureLimit ?? 100.0);
|
||||
setExtruderPressureLimitEnabled(preset.data.pressureLimitEnabled ?? true);
|
||||
|
||||
setPressurePidKp(preset.data.pidPressureKp ?? 0.16);
|
||||
setPressurePidKi(preset.data.pidPressureKi ?? 0.0);
|
||||
setPressurePidKd(preset.data.pidPressureKd ?? 0.008);
|
||||
};
|
||||
|
||||
return (
|
||||
<PresetsPage
|
||||
machine_identification={extruder3.machine_identification}
|
||||
currentState={toPresetData(state)}
|
||||
schemas={schemas}
|
||||
schemaVersion={1}
|
||||
applyPreset={applyPreset}
|
||||
previewEntries={previewEntries}
|
||||
defaultState={defaults}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { Page } from "@/components/Page";
|
||||
import { ControlCard } from "@/control/ControlCard";
|
||||
import { Label } from "@/control/Label";
|
||||
import { SelectionGroupBoolean } from "@/control/SelectionGroup";
|
||||
import { EditValue } from "@/control/EditValue";
|
||||
import { roundToDecimals } from "@/lib/decimal";
|
||||
import { useExtruder3 } from "./useExtruder";
|
||||
import { ControlGrid } from "@/control/ControlGrid";
|
||||
|
||||
export function Extruder3SettingsPage() {
|
||||
const {
|
||||
state,
|
||||
defaultState,
|
||||
setInverterRotationDirection,
|
||||
resetInverter,
|
||||
setExtruderPressureLimit,
|
||||
setExtruderPressureLimitEnabled,
|
||||
setPressurePidKp,
|
||||
setPressurePidKi,
|
||||
setPressurePidKd,
|
||||
setTemperaturePidValue,
|
||||
} = useExtruder3();
|
||||
|
||||
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<ControlCard className="bg-red" title="Inverter Settings">
|
||||
<Label label="Rotation Direction">
|
||||
<SelectionGroupBoolean
|
||||
value={state?.rotation_state.forward}
|
||||
optionTrue={{ children: "Forward" }}
|
||||
optionFalse={{ children: "Backward" }}
|
||||
onChange={setInverterRotationDirection}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
<Label label="Reset Inverter">
|
||||
<button
|
||||
onClick={resetInverter}
|
||||
className="inline-block w-fit max-w-max rounded bg-red-600 px-4 py-4 text-base whitespace-nowrap text-white hover:bg-red-700"
|
||||
style={{ minWidth: "auto", width: "fit-content" }}
|
||||
>
|
||||
Reset Inverter
|
||||
</button>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard className="bg-red" title="Extruder Settings">
|
||||
<Label label="Nozzle Pressure Limit">
|
||||
<EditValue
|
||||
value={state?.extruder_settings_state.pressure_limit}
|
||||
defaultValue={defaultState?.extruder_settings_state.pressure_limit}
|
||||
unit="bar"
|
||||
title="Nozzle Pressure Limit"
|
||||
min={0}
|
||||
max={350}
|
||||
renderValue={(value) => roundToDecimals(value, 0)}
|
||||
onChange={setExtruderPressureLimit}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Nozzle Pressure Limit Enabled">
|
||||
<SelectionGroupBoolean
|
||||
value={state?.extruder_settings_state.pressure_limit_enabled}
|
||||
optionTrue={{ children: "Enabled" }}
|
||||
optionFalse={{ children: "Disabled" }}
|
||||
onChange={setExtruderPressureLimitEnabled}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Show Advanced PID Settings">
|
||||
<SelectionGroupBoolean
|
||||
value={showAdvanced}
|
||||
optionTrue={{ children: "Show" }}
|
||||
optionFalse={{ children: "Hide" }}
|
||||
onChange={setShowAdvanced}
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
|
||||
{showAdvanced && (
|
||||
<>
|
||||
<ControlCard title="Pressure PID Settings ">
|
||||
<Label label="Kp">
|
||||
<EditValue
|
||||
value={state?.pid_settings.pressure.kp}
|
||||
defaultValue={defaultState?.pid_settings.pressure.kp}
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.01}
|
||||
renderValue={(v) => roundToDecimals(v, 2)}
|
||||
onChange={setPressurePidKp}
|
||||
title="Pressure PID KP"
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Ki">
|
||||
<EditValue
|
||||
value={state?.pid_settings.pressure.ki}
|
||||
defaultValue={defaultState?.pid_settings.pressure.ki}
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.01}
|
||||
renderValue={(v) => roundToDecimals(v, 2)}
|
||||
onChange={setPressurePidKi}
|
||||
title="Pressure PID KI"
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Kd">
|
||||
<EditValue
|
||||
value={state?.pid_settings.pressure.kd}
|
||||
defaultValue={defaultState?.pid_settings.pressure.kd}
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.01}
|
||||
renderValue={(v) => roundToDecimals(v, 2)}
|
||||
onChange={setPressurePidKd}
|
||||
title="Pressure PID KD"
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
<ControlGrid>
|
||||
<ControlCard title="Temperature PID Settings (Front) ">
|
||||
<Label label="Kp">
|
||||
<EditValue
|
||||
value={state?.pid_settings.temperature.front.kp}
|
||||
defaultValue={defaultState?.pid_settings.temperature.front.kp}
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.001}
|
||||
renderValue={(v) => roundToDecimals(v, 3)}
|
||||
onChange={(v) => setTemperaturePidValue("front", "kp", v)}
|
||||
title="Temperature PID KP"
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Ki">
|
||||
<EditValue
|
||||
value={state?.pid_settings.temperature.front.ki}
|
||||
defaultValue={defaultState?.pid_settings.temperature.front.ki}
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.001}
|
||||
renderValue={(v) => roundToDecimals(v, 3)}
|
||||
onChange={(v) => setTemperaturePidValue("front", "ki", v)}
|
||||
title="Temperature PID KI"
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Kd">
|
||||
<EditValue
|
||||
value={state?.pid_settings.temperature.front.kd}
|
||||
defaultValue={defaultState?.pid_settings.temperature.front.kd}
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.001}
|
||||
renderValue={(v) => roundToDecimals(v, 3)}
|
||||
onChange={(v) => setTemperaturePidValue("front", "kd", v)}
|
||||
title="Temperature PID KD"
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
<ControlCard title="Temperature PID Settings (Middle) ">
|
||||
<Label label="Kp">
|
||||
<EditValue
|
||||
value={state?.pid_settings.temperature.middle.kp}
|
||||
defaultValue={
|
||||
defaultState?.pid_settings.temperature.middle.kp
|
||||
}
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.001}
|
||||
renderValue={(v) => roundToDecimals(v, 3)}
|
||||
onChange={(v) => setTemperaturePidValue("middle", "kp", v)}
|
||||
title="Temperature PID KP"
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Ki">
|
||||
<EditValue
|
||||
value={state?.pid_settings.temperature.middle.ki}
|
||||
defaultValue={
|
||||
defaultState?.pid_settings.temperature.middle.ki
|
||||
}
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.001}
|
||||
renderValue={(v) => roundToDecimals(v, 3)}
|
||||
onChange={(v) => setTemperaturePidValue("middle", "ki", v)}
|
||||
title="Temperature PID KI"
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Kd">
|
||||
<EditValue
|
||||
value={state?.pid_settings.temperature.middle.kd}
|
||||
defaultValue={
|
||||
defaultState?.pid_settings.temperature.middle.kd
|
||||
}
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.001}
|
||||
renderValue={(v) => roundToDecimals(v, 3)}
|
||||
onChange={(v) => setTemperaturePidValue("middle", "kd", v)}
|
||||
title="Temperature PID KD"
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
<ControlCard title="Temperature PID Settings (Back) ">
|
||||
<Label label="Kp">
|
||||
<EditValue
|
||||
value={state?.pid_settings.temperature.back.kp}
|
||||
defaultValue={defaultState?.pid_settings.temperature.back.kp}
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.001}
|
||||
renderValue={(v) => roundToDecimals(v, 3)}
|
||||
onChange={(v) => setTemperaturePidValue("back", "kp", v)}
|
||||
title="Temperature PID KP"
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Ki">
|
||||
<EditValue
|
||||
value={state?.pid_settings.temperature.back.ki}
|
||||
defaultValue={defaultState?.pid_settings.temperature.back.ki}
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.001}
|
||||
renderValue={(v) => roundToDecimals(v, 3)}
|
||||
onChange={(v) => setTemperaturePidValue("back", "ki", v)}
|
||||
title="Temperature PID KI"
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Kd">
|
||||
<EditValue
|
||||
value={state?.pid_settings.temperature.back.kd}
|
||||
defaultValue={defaultState?.pid_settings.temperature.back.kd}
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.001}
|
||||
renderValue={(v) => roundToDecimals(v, 3)}
|
||||
onChange={(v) => setTemperaturePidValue("back", "kd", v)}
|
||||
title="Temperature PID KD"
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
<ControlCard title="Temperature PID Settings (Nozzle) ">
|
||||
<Label label="Kp">
|
||||
<EditValue
|
||||
value={state?.pid_settings.temperature.nozzle.kp}
|
||||
defaultValue={
|
||||
defaultState?.pid_settings.temperature.nozzle.kp
|
||||
}
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.001}
|
||||
renderValue={(v) => roundToDecimals(v, 3)}
|
||||
onChange={(v) => setTemperaturePidValue("nozzle", "kp", v)}
|
||||
title="Temperature PID KP"
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Ki">
|
||||
<EditValue
|
||||
value={state?.pid_settings.temperature.nozzle.ki}
|
||||
defaultValue={
|
||||
defaultState?.pid_settings.temperature.nozzle.ki
|
||||
}
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.001}
|
||||
renderValue={(v) => roundToDecimals(v, 3)}
|
||||
onChange={(v) => setTemperaturePidValue("nozzle", "ki", v)}
|
||||
title="Temperature PID KI"
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Kd">
|
||||
<EditValue
|
||||
value={state?.pid_settings.temperature.nozzle.kd}
|
||||
defaultValue={
|
||||
defaultState?.pid_settings.temperature.nozzle.kd
|
||||
}
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.001}
|
||||
renderValue={(v) => roundToDecimals(v, 3)}
|
||||
onChange={(v) => setTemperaturePidValue("nozzle", "kd", v)}
|
||||
title="Temperature PID KD"
|
||||
/>
|
||||
</Label>
|
||||
</ControlCard>
|
||||
</ControlGrid>
|
||||
</>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
@@ -1,433 +0,0 @@
|
||||
import { StoreApi } from "zustand";
|
||||
import { create } from "zustand";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
EventHandler,
|
||||
eventSchema,
|
||||
Event,
|
||||
NamespaceId,
|
||||
createNamespaceHookImplementation,
|
||||
ThrottledStoreUpdater,
|
||||
handleUnhandledEventError,
|
||||
} from "../../../client/socketioStore";
|
||||
import { MachineIdentificationUnique } from "@/machines/types";
|
||||
import { createTimeSeries, TimeSeries } from "@/lib/timeseries";
|
||||
import { useMemo } from "react";
|
||||
|
||||
// ========== Event Schema Definitions ==========
|
||||
|
||||
/**
|
||||
* Machine operation mode enum
|
||||
*/
|
||||
export const modeSchema = z.enum(["Standby", "Heat", "Extrude"]);
|
||||
export type Mode = z.infer<typeof modeSchema>;
|
||||
|
||||
export const liveMotorStatusDataSchema = z.object({
|
||||
screw_rpm: z.number(),
|
||||
frequency: z.number(),
|
||||
current: z.number(),
|
||||
power: z.number(),
|
||||
});
|
||||
export type MotorStatus = z.infer<typeof liveMotorStatusDataSchema>;
|
||||
/**
|
||||
* Consolidated live values event schema (30FPS data)
|
||||
*/
|
||||
export const liveValuesEventDataSchema = z.object({
|
||||
motor_status: liveMotorStatusDataSchema,
|
||||
pressure: z.number(),
|
||||
nozzle_temperature: z.number(),
|
||||
front_temperature: z.number(),
|
||||
back_temperature: z.number(),
|
||||
middle_temperature: z.number(),
|
||||
nozzle_power: z.number(),
|
||||
front_power: z.number(),
|
||||
back_power: z.number(),
|
||||
middle_power: z.number(),
|
||||
combined_power: z.number(),
|
||||
total_energy_kwh: z.number(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Rotation state schema
|
||||
*/
|
||||
export const rotationStateSchema = z.object({
|
||||
forward: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Mode state schema
|
||||
*/
|
||||
export const modeStateSchema = z.object({
|
||||
mode: modeSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* Regulation state schema
|
||||
*/
|
||||
export const regulationStateSchema = z.object({
|
||||
uses_rpm: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Pressure state schema
|
||||
*/
|
||||
export const pressureStateSchema = z.object({
|
||||
target_bar: z.number(),
|
||||
wiring_error: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Screw state schema
|
||||
*/
|
||||
export const screwStateSchema = z.object({
|
||||
target_rpm: z.number(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Heating state schema
|
||||
*/
|
||||
export const heatingStateSchema = z.object({
|
||||
target_temperature: z.number(),
|
||||
wiring_error: z.boolean(),
|
||||
});
|
||||
|
||||
export type HeatingState = z.infer<typeof heatingStateSchema>;
|
||||
|
||||
/**
|
||||
* Heating states schema
|
||||
*/
|
||||
export const heatingStatesSchema = z.object({
|
||||
nozzle: heatingStateSchema,
|
||||
front: heatingStateSchema,
|
||||
back: heatingStateSchema,
|
||||
middle: heatingStateSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* Extruder settings state schema
|
||||
*/
|
||||
export const extruderSettingsStateSchema = z.object({
|
||||
pressure_limit: z.number(),
|
||||
pressure_limit_enabled: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Inverter status state schema
|
||||
*/
|
||||
export const inverterStatusStateSchema = z.object({
|
||||
running: z.boolean(),
|
||||
forward_running: z.boolean(),
|
||||
reverse_running: z.boolean(),
|
||||
up_to_frequency: z.boolean(),
|
||||
overload_warning: z.boolean(),
|
||||
no_function: z.boolean(),
|
||||
output_frequency_detection: z.boolean(),
|
||||
abc_fault: z.boolean(),
|
||||
fault_occurence: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* PID settings schema
|
||||
*/
|
||||
export const pidSettingsSchema = z.object({
|
||||
temperature: z.object({
|
||||
front: z.object({
|
||||
ki: z.number(),
|
||||
kp: z.number(),
|
||||
kd: z.number(),
|
||||
zone: z.string(),
|
||||
}),
|
||||
middle: z.object({
|
||||
ki: z.number(),
|
||||
kp: z.number(),
|
||||
kd: z.number(),
|
||||
zone: z.string(),
|
||||
}),
|
||||
back: z.object({
|
||||
ki: z.number(),
|
||||
kp: z.number(),
|
||||
kd: z.number(),
|
||||
zone: z.string(),
|
||||
}),
|
||||
nozzle: z.object({
|
||||
ki: z.number(),
|
||||
kp: z.number(),
|
||||
kd: z.number(),
|
||||
zone: z.string(),
|
||||
}),
|
||||
}),
|
||||
pressure: z.object({
|
||||
ki: z.number(),
|
||||
kp: z.number(),
|
||||
kd: z.number(),
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Consolidated state event schema (state changes only)
|
||||
*/
|
||||
export const stateEventDataSchema = z.object({
|
||||
is_default_state: z.boolean(),
|
||||
rotation_state: rotationStateSchema,
|
||||
mode_state: modeStateSchema,
|
||||
regulation_state: regulationStateSchema,
|
||||
pressure_state: pressureStateSchema,
|
||||
screw_state: screwStateSchema,
|
||||
heating_states: heatingStatesSchema,
|
||||
extruder_settings_state: extruderSettingsStateSchema,
|
||||
inverter_status_state: inverterStatusStateSchema,
|
||||
pid_settings: pidSettingsSchema,
|
||||
});
|
||||
|
||||
// ========== Event Schemas with Wrappers ==========
|
||||
|
||||
export const liveValuesEventSchema = eventSchema(liveValuesEventDataSchema);
|
||||
export const stateEventSchema = eventSchema(stateEventDataSchema);
|
||||
|
||||
// ========== Type Inferences ==========
|
||||
|
||||
export type StateEvent = z.infer<typeof stateEventSchema>;
|
||||
|
||||
// Additional exports for backward compatibility
|
||||
export const SetRegulationSchema = z.object({
|
||||
uses_rpm: z.boolean(),
|
||||
});
|
||||
|
||||
export const mode = z.object({
|
||||
mode: modeSchema,
|
||||
});
|
||||
|
||||
export type Extruder3NamespaceStore = {
|
||||
// Single state event from server
|
||||
state: StateEvent | null;
|
||||
defaultState: StateEvent | null;
|
||||
|
||||
// Time series data for live values
|
||||
motorFrequency: TimeSeries;
|
||||
motorCurrent: TimeSeries;
|
||||
motorScrewRpm: TimeSeries;
|
||||
motorPower: TimeSeries;
|
||||
|
||||
pressure: TimeSeries;
|
||||
nozzleTemperature: TimeSeries;
|
||||
frontTemperature: TimeSeries;
|
||||
backTemperature: TimeSeries;
|
||||
middleTemperature: TimeSeries;
|
||||
nozzlePower: TimeSeries;
|
||||
frontPower: TimeSeries;
|
||||
middlePower: TimeSeries;
|
||||
backPower: TimeSeries;
|
||||
|
||||
// Combined power consumption and energy
|
||||
combinedPower: TimeSeries;
|
||||
totalEnergyKWh: TimeSeries;
|
||||
};
|
||||
|
||||
// Constants for time durations
|
||||
const TWENTY_MILLISECOND = 20;
|
||||
const ONE_SECOND = 1000;
|
||||
const FIVE_SECOND = 5 * ONE_SECOND;
|
||||
const ONE_HOUR = 60 * 60 * ONE_SECOND;
|
||||
|
||||
const { initialTimeSeries: pressure, insert: addPressure } = createTimeSeries(
|
||||
TWENTY_MILLISECOND,
|
||||
ONE_SECOND,
|
||||
FIVE_SECOND,
|
||||
ONE_HOUR,
|
||||
);
|
||||
|
||||
const { initialTimeSeries: backTemperature, insert: addBackTemperature } =
|
||||
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
|
||||
|
||||
const { initialTimeSeries: frontTemperature, insert: addFrontTemperature } =
|
||||
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
|
||||
|
||||
const { initialTimeSeries: middleTemperature, insert: addMiddleTemperature } =
|
||||
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
|
||||
|
||||
const { initialTimeSeries: nozzleTemperature, insert: addNozzleTemperature } =
|
||||
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
|
||||
|
||||
const { initialTimeSeries: nozzlePower, insert: addNozzlePower } =
|
||||
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
|
||||
|
||||
const { initialTimeSeries: frontPower, insert: addFrontPower } =
|
||||
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
|
||||
|
||||
const { initialTimeSeries: middlePower, insert: addMiddlePower } =
|
||||
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
|
||||
|
||||
const { initialTimeSeries: backPower, insert: addBackPower } = createTimeSeries(
|
||||
TWENTY_MILLISECOND,
|
||||
ONE_SECOND,
|
||||
FIVE_SECOND,
|
||||
ONE_HOUR,
|
||||
);
|
||||
|
||||
const { initialTimeSeries: combinedPower, insert: addCombinedPower } =
|
||||
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
|
||||
|
||||
const { initialTimeSeries: totalEnergyKWh, insert: addTotalEnergyKWh } =
|
||||
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
|
||||
|
||||
const { initialTimeSeries: motorCurrent, insert: addMotorCurrent } =
|
||||
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
|
||||
|
||||
const { initialTimeSeries: motorFrequency, insert: addMotorFrequency } =
|
||||
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
|
||||
|
||||
const { initialTimeSeries: motorScrewRpm, insert: addMotorScrewRpm } =
|
||||
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
|
||||
|
||||
const { initialTimeSeries: motorPower, insert: addMotorPower } =
|
||||
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
|
||||
|
||||
export function extruder3MessageHandler(
|
||||
store: StoreApi<Extruder3NamespaceStore>,
|
||||
throttledUpdater: ThrottledStoreUpdater<Extruder3NamespaceStore>,
|
||||
): EventHandler {
|
||||
return (event: Event<any>) => {
|
||||
const eventName = event.name;
|
||||
|
||||
// Helper function to update store through buffer
|
||||
const updateStore = (
|
||||
updater: (state: Extruder3NamespaceStore) => Extruder3NamespaceStore,
|
||||
) => {
|
||||
throttledUpdater.updateWith(updater);
|
||||
};
|
||||
|
||||
try {
|
||||
if (eventName === "StateEvent") {
|
||||
console.log(event);
|
||||
const stateEvent = stateEventSchema.parse(event);
|
||||
updateStore((state) => ({
|
||||
...state,
|
||||
state: stateEvent,
|
||||
// only set default state if is_default_state is true
|
||||
defaultState: stateEvent.data.is_default_state
|
||||
? stateEvent
|
||||
: state.defaultState,
|
||||
}));
|
||||
} else if (eventName === "LiveValuesEvent") {
|
||||
const liveValuesEvent = liveValuesEventSchema.parse(event);
|
||||
const timestamp = event.ts;
|
||||
updateStore((state) => ({
|
||||
...state,
|
||||
motorScrewRpm: addMotorScrewRpm(state.motorScrewRpm, {
|
||||
value: liveValuesEvent.data.motor_status.screw_rpm,
|
||||
timestamp,
|
||||
}),
|
||||
motorCurrent: addMotorCurrent(state.motorCurrent, {
|
||||
value: liveValuesEvent.data.motor_status.current,
|
||||
timestamp,
|
||||
}),
|
||||
motorFrequency: addMotorFrequency(state.motorFrequency, {
|
||||
value: liveValuesEvent.data.motor_status.frequency,
|
||||
timestamp,
|
||||
}),
|
||||
motorPower: addMotorPower(state.motorPower, {
|
||||
value: liveValuesEvent.data.motor_status.power,
|
||||
timestamp,
|
||||
}),
|
||||
pressure: addPressure(state.pressure, {
|
||||
value: liveValuesEvent.data.pressure,
|
||||
timestamp,
|
||||
}),
|
||||
nozzleTemperature: addNozzleTemperature(state.nozzleTemperature, {
|
||||
value: liveValuesEvent.data.nozzle_temperature,
|
||||
timestamp,
|
||||
}),
|
||||
frontTemperature: addFrontTemperature(state.frontTemperature, {
|
||||
value: liveValuesEvent.data.front_temperature,
|
||||
timestamp,
|
||||
}),
|
||||
backTemperature: addBackTemperature(state.backTemperature, {
|
||||
value: liveValuesEvent.data.back_temperature,
|
||||
timestamp,
|
||||
}),
|
||||
middleTemperature: addMiddleTemperature(state.middleTemperature, {
|
||||
value: liveValuesEvent.data.middle_temperature,
|
||||
timestamp,
|
||||
}),
|
||||
nozzlePower: addNozzlePower(state.nozzlePower, {
|
||||
value: liveValuesEvent.data.nozzle_power,
|
||||
timestamp,
|
||||
}),
|
||||
frontPower: addFrontPower(state.frontPower, {
|
||||
value: liveValuesEvent.data.front_power,
|
||||
timestamp,
|
||||
}),
|
||||
middlePower: addMiddlePower(state.middlePower, {
|
||||
value: liveValuesEvent.data.middle_power,
|
||||
timestamp,
|
||||
}),
|
||||
backPower: addBackPower(state.backPower, {
|
||||
value: liveValuesEvent.data.back_power,
|
||||
timestamp,
|
||||
}),
|
||||
combinedPower: addCombinedPower(state.combinedPower, {
|
||||
value: liveValuesEvent.data.combined_power,
|
||||
timestamp,
|
||||
}),
|
||||
totalEnergyKWh: addTotalEnergyKWh(state.totalEnergyKWh, {
|
||||
value: liveValuesEvent.data.total_energy_kwh,
|
||||
timestamp,
|
||||
}),
|
||||
}));
|
||||
} else {
|
||||
handleUnhandledEventError(eventName);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Unexpected error processing ${eventName} event:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const createExtruder3NamespaceStore =
|
||||
(): StoreApi<Extruder3NamespaceStore> =>
|
||||
create<Extruder3NamespaceStore>(() => {
|
||||
return {
|
||||
state: null,
|
||||
defaultState: null,
|
||||
|
||||
motorCurrent,
|
||||
motorFrequency,
|
||||
motorScrewRpm,
|
||||
motorPower,
|
||||
|
||||
pressure,
|
||||
nozzleTemperature,
|
||||
frontTemperature,
|
||||
backTemperature,
|
||||
middleTemperature,
|
||||
nozzlePower,
|
||||
frontPower,
|
||||
backPower,
|
||||
middlePower,
|
||||
combinedPower,
|
||||
totalEnergyKWh,
|
||||
};
|
||||
});
|
||||
|
||||
const useExtruder3NamespaceImplementation =
|
||||
createNamespaceHookImplementation<Extruder3NamespaceStore>({
|
||||
createStore: createExtruder3NamespaceStore,
|
||||
createEventHandler: extruder3MessageHandler,
|
||||
});
|
||||
|
||||
export function useExtruder3Namespace(
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
): Extruder3NamespaceStore {
|
||||
// Generate namespace ID from validated machine ID
|
||||
const namespaceId = useMemo<NamespaceId>(
|
||||
() => ({
|
||||
type: "machine",
|
||||
machine_identification_unique,
|
||||
}),
|
||||
[machine_identification_unique],
|
||||
);
|
||||
|
||||
// Use the implementation with validated namespace ID
|
||||
return useExtruder3NamespaceImplementation(namespaceId);
|
||||
}
|
||||
@@ -1,448 +0,0 @@
|
||||
import { toastError } from "@/components/Toast";
|
||||
import { useMachineMutate as useMachineMutation } from "@/client/useClient";
|
||||
import { useStateOptimistic } from "@/lib/useStateOptimistic";
|
||||
import { MachineIdentificationUnique } from "@/machines/types";
|
||||
import { extruder3 } from "@/machines/properties";
|
||||
import { extruder3Route } from "@/routes/routes";
|
||||
import { z } from "zod";
|
||||
import { StateEvent, Mode, useExtruder3Namespace } from "./extruder3Namespace";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { produce } from "immer";
|
||||
|
||||
export function useExtruder3() {
|
||||
const { serial: serialString } = extruder3Route.useParams();
|
||||
|
||||
// Memoize the machine identification to keep it stable between renders
|
||||
const machineIdentification: MachineIdentificationUnique = useMemo(() => {
|
||||
const serial = parseInt(serialString);
|
||||
|
||||
if (isNaN(serial)) {
|
||||
toastError(
|
||||
"Invalid Serial Number",
|
||||
`"${serialString}" is not a valid serial number.`,
|
||||
);
|
||||
|
||||
return {
|
||||
machine_identification: {
|
||||
vendor: 0,
|
||||
machine: 0,
|
||||
},
|
||||
serial: 0,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
machine_identification: extruder3.machine_identification,
|
||||
serial,
|
||||
};
|
||||
}, [serialString]);
|
||||
|
||||
// Get consolidated state and live values from namespace
|
||||
const {
|
||||
state,
|
||||
defaultState,
|
||||
motorCurrent,
|
||||
motorFrequency,
|
||||
motorScrewRpm,
|
||||
motorPower,
|
||||
pressure,
|
||||
|
||||
nozzleTemperature,
|
||||
frontTemperature,
|
||||
backTemperature,
|
||||
middleTemperature,
|
||||
nozzlePower,
|
||||
frontPower,
|
||||
middlePower,
|
||||
backPower,
|
||||
combinedPower,
|
||||
totalEnergyKWh,
|
||||
} = useExtruder3Namespace(machineIdentification);
|
||||
|
||||
// Single optimistic state for all state management
|
||||
const stateOptimistic = useStateOptimistic<StateEvent>();
|
||||
|
||||
useEffect(() => {
|
||||
if (state) {
|
||||
stateOptimistic.setReal(state);
|
||||
}
|
||||
}, [state]);
|
||||
|
||||
// Helper function for optimistic updates using produce
|
||||
const updateStateOptimistically = (
|
||||
producer: (current: StateEvent) => void,
|
||||
serverRequest: () => void,
|
||||
) => {
|
||||
const currentState = stateOptimistic.value;
|
||||
if (currentState && !stateOptimistic.isOptimistic) {
|
||||
stateOptimistic.setOptimistic(produce(currentState, producer));
|
||||
}
|
||||
serverRequest();
|
||||
};
|
||||
|
||||
// Action functions with verb-first names
|
||||
const setInverterRotationDirection = (forward: boolean) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.rotation_state.forward = forward;
|
||||
},
|
||||
() =>
|
||||
requestInverterRotationDirection({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetInverterRotationDirection: forward },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setExtruderMode = (mode: Mode) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.mode_state.mode = mode;
|
||||
},
|
||||
() =>
|
||||
requestExtruderMode({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetExtruderMode: mode },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setInverterRegulation = (usesRpm: boolean) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.regulation_state.uses_rpm = usesRpm;
|
||||
},
|
||||
() =>
|
||||
requestInverterRegulation({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetInverterRegulation: usesRpm },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setInverterTargetRpm = (rpm: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.screw_state.target_rpm = rpm;
|
||||
},
|
||||
() =>
|
||||
requestInverterTargetRpm({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetInverterTargetRpm: rpm },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setInverterTargetPressure = (pressure: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.pressure_state.target_bar = pressure;
|
||||
},
|
||||
() =>
|
||||
requestInverterTargetPressure({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetInverterTargetPressure: pressure },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setNozzleHeatingTemperature = (temperature: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.heating_states.nozzle.target_temperature = temperature;
|
||||
},
|
||||
() =>
|
||||
requestNozzleHeatingTemperature({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetNozzleHeatingTemperature: temperature },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setFrontHeatingTemperature = (temperature: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.heating_states.front.target_temperature = temperature;
|
||||
},
|
||||
() =>
|
||||
requestFrontHeatingTemperature({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetFrontHeatingTargetTemperature: temperature },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setBackHeatingTemperature = (temperature: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.heating_states.back.target_temperature = temperature;
|
||||
},
|
||||
() =>
|
||||
requestBackHeatingTemperature({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetBackHeatingTargetTemperature: temperature },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setMiddleHeatingTemperature = (temperature: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.heating_states.middle.target_temperature = temperature;
|
||||
},
|
||||
() =>
|
||||
requestMiddleHeatingTemperature({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetMiddleHeatingTemperature: temperature },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setExtruderPressureLimit = (pressureLimit: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.extruder_settings_state.pressure_limit = pressureLimit;
|
||||
},
|
||||
() =>
|
||||
requestExtruderPressureLimit({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetExtruderPressureLimit: pressureLimit },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setExtruderPressureLimitEnabled = (enabled: boolean) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.extruder_settings_state.pressure_limit_enabled = enabled;
|
||||
},
|
||||
() =>
|
||||
requestExtruderPressureLimitEnabled({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetExtruderPressureLimitIsEnabled: enabled },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setPressurePidKp = (kp: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.pid_settings.pressure.kp = kp;
|
||||
},
|
||||
() => {
|
||||
const currentState = stateOptimistic.value;
|
||||
if (currentState) {
|
||||
const settings = produce(
|
||||
currentState.data.pid_settings.pressure,
|
||||
(draft) => {
|
||||
draft.kp = kp;
|
||||
},
|
||||
);
|
||||
requestPressurePidSettings({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPressurePidSettings: settings },
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const setPressurePidKi = (ki: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.pid_settings.pressure.ki = ki;
|
||||
},
|
||||
() => {
|
||||
const currentState = stateOptimistic.value;
|
||||
if (currentState) {
|
||||
const settings = produce(
|
||||
currentState.data.pid_settings.pressure,
|
||||
(draft) => {
|
||||
draft.ki = ki;
|
||||
},
|
||||
);
|
||||
requestPressurePidSettings({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPressurePidSettings: settings },
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const setPressurePidKd = (kd: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.pid_settings.pressure.kd = kd;
|
||||
},
|
||||
() => {
|
||||
const currentState = stateOptimistic.value;
|
||||
if (currentState) {
|
||||
const settings = produce(
|
||||
currentState.data.pid_settings.pressure,
|
||||
(draft) => {
|
||||
draft.kd = kd;
|
||||
},
|
||||
);
|
||||
requestPressurePidSettings({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetPressurePidSettings: settings },
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const setTemperaturePidValue = (
|
||||
zone: "front" | "middle" | "back" | "nozzle",
|
||||
key: "kp" | "ki" | "kd",
|
||||
value: number,
|
||||
) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.pid_settings.temperature[zone][key] = value;
|
||||
},
|
||||
() => {
|
||||
const currentState = stateOptimistic.value;
|
||||
if (currentState) {
|
||||
const settings = produce(
|
||||
currentState.data.pid_settings.temperature[zone],
|
||||
(draft) => {
|
||||
draft[key] = value;
|
||||
},
|
||||
);
|
||||
requestTemperaturePidSettings({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetTemperaturePidSettings: settings },
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const resetInverter = () => {
|
||||
// No optimistic update needed for reset
|
||||
requestResetInverter({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { ResetInverter: true },
|
||||
});
|
||||
};
|
||||
|
||||
// Mutation hooks
|
||||
const { request: requestInverterRotationDirection } = useMachineMutation(
|
||||
z.object({ SetInverterRotationDirection: z.boolean() }),
|
||||
);
|
||||
|
||||
const { request: requestExtruderMode } = useMachineMutation(
|
||||
z.object({ SetExtruderMode: z.enum(["Heat", "Extrude", "Standby"]) }),
|
||||
);
|
||||
|
||||
const { request: requestInverterRegulation } = useMachineMutation(
|
||||
z.object({ SetInverterRegulation: z.boolean() }),
|
||||
);
|
||||
|
||||
const { request: requestInverterTargetRpm } = useMachineMutation(
|
||||
z.object({ SetInverterTargetRpm: z.number() }),
|
||||
);
|
||||
|
||||
const { request: requestInverterTargetPressure } = useMachineMutation(
|
||||
z.object({ SetInverterTargetPressure: z.number() }),
|
||||
);
|
||||
|
||||
const { request: requestNozzleHeatingTemperature } = useMachineMutation(
|
||||
z.object({ SetNozzleHeatingTemperature: z.number() }),
|
||||
);
|
||||
|
||||
const { request: requestFrontHeatingTemperature } = useMachineMutation(
|
||||
z.object({ SetFrontHeatingTargetTemperature: z.number() }),
|
||||
);
|
||||
|
||||
const { request: requestBackHeatingTemperature } = useMachineMutation(
|
||||
z.object({ SetBackHeatingTargetTemperature: z.number() }),
|
||||
);
|
||||
|
||||
const { request: requestMiddleHeatingTemperature } = useMachineMutation(
|
||||
z.object({ SetMiddleHeatingTemperature: z.number() }),
|
||||
);
|
||||
|
||||
const { request: requestExtruderPressureLimit } = useMachineMutation(
|
||||
z.object({ SetExtruderPressureLimit: z.number() }),
|
||||
);
|
||||
|
||||
const { request: requestExtruderPressureLimitEnabled } = useMachineMutation(
|
||||
z.object({ SetExtruderPressureLimitIsEnabled: z.boolean() }),
|
||||
);
|
||||
|
||||
const { request: requestPressurePidSettings } = useMachineMutation(
|
||||
z.object({
|
||||
SetPressurePidSettings: z.object({
|
||||
ki: z.number(),
|
||||
kp: z.number(),
|
||||
kd: z.number(),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const { request: requestTemperaturePidSettings } = useMachineMutation(
|
||||
z.object({
|
||||
SetTemperaturePidSettings: z.object({
|
||||
ki: z.number(),
|
||||
kp: z.number(),
|
||||
kd: z.number(),
|
||||
zone: z.string(),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const { request: requestResetInverter } = useMachineMutation(
|
||||
z.object({ ResetInverter: z.boolean() }),
|
||||
);
|
||||
|
||||
return {
|
||||
// Consolidated state
|
||||
state: stateOptimistic.value?.data,
|
||||
|
||||
// Default state for initial values
|
||||
defaultState: defaultState?.data,
|
||||
|
||||
// Individual live values (TimeSeries)
|
||||
motorCurrent,
|
||||
motorFrequency,
|
||||
motorScrewRpm,
|
||||
motorPower,
|
||||
|
||||
pressure,
|
||||
nozzleTemperature,
|
||||
frontTemperature,
|
||||
backTemperature,
|
||||
middleTemperature,
|
||||
nozzlePower,
|
||||
frontPower,
|
||||
middlePower,
|
||||
backPower,
|
||||
combinedPower,
|
||||
totalEnergyKWh,
|
||||
|
||||
// Loading states
|
||||
isLoading: stateOptimistic.isOptimistic,
|
||||
isDisabled: !stateOptimistic.isInitialized,
|
||||
|
||||
// Action functions (verb-first)
|
||||
setInverterRotationDirection,
|
||||
setExtruderMode,
|
||||
setInverterRegulation,
|
||||
setInverterTargetRpm,
|
||||
setInverterTargetPressure,
|
||||
setNozzleHeatingTemperature,
|
||||
setFrontHeatingTemperature,
|
||||
setBackHeatingTemperature,
|
||||
setMiddleHeatingTemperature,
|
||||
setExtruderPressureLimit,
|
||||
setExtruderPressureLimitEnabled,
|
||||
setPressurePidKp,
|
||||
setPressurePidKi,
|
||||
setPressurePidKd,
|
||||
setTemperaturePidValue,
|
||||
resetInverter,
|
||||
};
|
||||
}
|
||||
@@ -103,94 +103,6 @@ export const winder2: MachineProperties = {
|
||||
],
|
||||
};
|
||||
|
||||
export const extruder3: MachineProperties = {
|
||||
name: "Extruder V3",
|
||||
version: "V3",
|
||||
slug: "extruder3",
|
||||
icon: "qi:Extruder",
|
||||
machine_identification: {
|
||||
vendor: VENDOR_QITECH,
|
||||
machine: 0x0016,
|
||||
},
|
||||
device_roles: [
|
||||
{
|
||||
role: 0,
|
||||
role_label: "Bus Coupler",
|
||||
allowed_devices: [
|
||||
{
|
||||
vendor_id: 2,
|
||||
product_id: 0x44c2c52,
|
||||
revision: 0x120000,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: 1,
|
||||
role_label: "Inverter Interface",
|
||||
allowed_devices: [
|
||||
{
|
||||
vendor_id: 2,
|
||||
product_id: 394604626,
|
||||
revision: 1376256,
|
||||
},
|
||||
{
|
||||
vendor_id: 2,
|
||||
product_id: 394604626,
|
||||
revision: 0x140000,
|
||||
},
|
||||
{
|
||||
vendor_id: 2,
|
||||
product_id: 394604626,
|
||||
revision: 0x160000,
|
||||
},
|
||||
{
|
||||
vendor_id: 2,
|
||||
product_id: 394604626,
|
||||
revision: 0x100000,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: 2,
|
||||
role_label: "Heating Elements",
|
||||
allowed_devices: [
|
||||
{
|
||||
vendor_id: 2,
|
||||
product_id: 131346514,
|
||||
revision: 1179648,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: 3,
|
||||
role_label: "Pressure Sensor",
|
||||
allowed_devices: [
|
||||
{
|
||||
vendor_id: 2,
|
||||
product_id: 197996626,
|
||||
revision: 1310720,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: 4,
|
||||
role_label: "Thermometers",
|
||||
allowed_devices: [
|
||||
{
|
||||
vendor_id: 2,
|
||||
product_id: 0xc843052,
|
||||
revision: 1441792,
|
||||
},
|
||||
{
|
||||
vendor_id: 2,
|
||||
product_id: 0xc843052,
|
||||
revision: 0x150000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const extruder2: MachineProperties = {
|
||||
name: "Extruder",
|
||||
version: "V2",
|
||||
@@ -427,50 +339,14 @@ export const aquapath1: MachineProperties = {
|
||||
},
|
||||
],
|
||||
};
|
||||
export const testmachine: MachineProperties = {
|
||||
name: "TestMachine",
|
||||
version: "V1",
|
||||
slug: "testmachine",
|
||||
icon: "lu:Disc3",
|
||||
machine_identification: {
|
||||
vendor: VENDOR_QITECH,
|
||||
machine: 0x0033,
|
||||
},
|
||||
device_roles: [
|
||||
{
|
||||
role: 0,
|
||||
role_label: "Bus Coupler",
|
||||
allowed_devices: [
|
||||
{
|
||||
vendor_id: 2,
|
||||
product_id: 0x44c2c52,
|
||||
revision: 0x120000,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: 1,
|
||||
role_label: "EL2004",
|
||||
allowed_devices: [
|
||||
{
|
||||
vendor_id: 2,
|
||||
product_id: 0x7d43052,
|
||||
revision: 0x120000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const machineProperties: MachineProperties[] = [
|
||||
winder2,
|
||||
extruder3,
|
||||
extruder2,
|
||||
laser1,
|
||||
mock1,
|
||||
buffer1,
|
||||
aquapath1,
|
||||
testmachine,
|
||||
];
|
||||
|
||||
export const getMachineProperties = (
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import { ControlCard } from "@/control/ControlCard";
|
||||
import { Page } from "@/components/Page";
|
||||
import React from "react";
|
||||
import { ControlGrid } from "@/control/ControlGrid";
|
||||
import { SelectionGroup } from "@/control/SelectionGroup";
|
||||
import { Label } from "@/control/Label";
|
||||
import { useTestMachine } from "./useTestMachine";
|
||||
|
||||
export function TestMachineControlPage() {
|
||||
const { state, setLed, setAllLeds } = useTestMachine();
|
||||
|
||||
const safeState = state ?? { led_on: [false, false, false, false] };
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<ControlGrid columns={2}>
|
||||
{/* LED Controls */}
|
||||
<ControlCard title="Machine LEDs">
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
{safeState.led_on.map((led, index) => (
|
||||
<Label key={index} label={`LED ${index + 1}`}>
|
||||
<SelectionGroup<"On" | "Off">
|
||||
value={led ? "On" : "Off"}
|
||||
orientation="vertical"
|
||||
className="grid h-full grid-cols-2 gap-2"
|
||||
options={{
|
||||
Off: {
|
||||
children: "Off",
|
||||
icon: "lu:CirclePause",
|
||||
isActiveClassName: "bg-red-600",
|
||||
className: "h-full",
|
||||
},
|
||||
On: {
|
||||
children: "On",
|
||||
icon: "lu:CirclePlay",
|
||||
isActiveClassName: "bg-green-600",
|
||||
className: "h-full",
|
||||
},
|
||||
}}
|
||||
onChange={(value) => setLed(index, value === "On")}
|
||||
/>
|
||||
</Label>
|
||||
))}
|
||||
</div>
|
||||
</ControlCard>
|
||||
|
||||
{/* Master LED Control */}
|
||||
<ControlCard title="Master LED Control">
|
||||
<SelectionGroup<"On" | "Off">
|
||||
value={safeState.led_on.every(Boolean) ? "On" : "Off"}
|
||||
orientation="horizontal"
|
||||
options={{
|
||||
Off: { children: "Turn All Off" },
|
||||
On: { children: "Turn All On" },
|
||||
}}
|
||||
onChange={(value) => setAllLeds(value === "On")}
|
||||
/>
|
||||
</ControlCard>
|
||||
</ControlGrid>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Topbar } from "@/components/Topbar";
|
||||
import { testMachineSerialRoute } from "@/routes/routes";
|
||||
import React from "react";
|
||||
|
||||
export function TestMachinePage() {
|
||||
const { serial } = testMachineSerialRoute.useParams();
|
||||
return (
|
||||
<Topbar
|
||||
pathname={`/_sidebar/machines/testmachine/${serial}`}
|
||||
items={[
|
||||
{
|
||||
link: "control",
|
||||
activeLink: "control",
|
||||
title: "Control",
|
||||
icon: "lu:CirclePlay",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import { StoreApi } from "zustand";
|
||||
import { create } from "zustand";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
EventHandler,
|
||||
eventSchema,
|
||||
Event,
|
||||
handleUnhandledEventError,
|
||||
NamespaceId,
|
||||
createNamespaceHookImplementation,
|
||||
ThrottledStoreUpdater,
|
||||
} from "@/client/socketioStore";
|
||||
import { MachineIdentificationUnique } from "@/machines/types";
|
||||
|
||||
// ========== Event Schema ==========
|
||||
|
||||
export const stateEventDataSchema = z.object({
|
||||
led_on: z.array(z.boolean()).length(4),
|
||||
});
|
||||
|
||||
export const stateEventSchema = eventSchema(stateEventDataSchema);
|
||||
|
||||
export type StateEvent = z.infer<typeof stateEventDataSchema>;
|
||||
|
||||
// ========== Store ==========
|
||||
export type TestMachineNamespaceStore = {
|
||||
state: StateEvent | null;
|
||||
};
|
||||
|
||||
export const createTestMachineNamespaceStore =
|
||||
(): StoreApi<TestMachineNamespaceStore> =>
|
||||
create<TestMachineNamespaceStore>(() => ({
|
||||
state: null,
|
||||
}));
|
||||
|
||||
// ========== Message Handler ==========
|
||||
export function testMachineMessageHandler(
|
||||
store: StoreApi<TestMachineNamespaceStore>,
|
||||
throttledUpdater: ThrottledStoreUpdater<TestMachineNamespaceStore>,
|
||||
): EventHandler {
|
||||
return (event: Event<any>) => {
|
||||
const updateStore = (
|
||||
updater: (state: TestMachineNamespaceStore) => TestMachineNamespaceStore,
|
||||
) => throttledUpdater.updateWith(updater);
|
||||
|
||||
try {
|
||||
if (event.name === "StateEvent") {
|
||||
const parsed = stateEventSchema.parse(event);
|
||||
updateStore(() => ({ state: parsed.data }));
|
||||
} else {
|
||||
handleUnhandledEventError(event.name);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error processing ${event.name}:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ========== Namespace Hook ==========
|
||||
const useTestMachineNamespaceImplementation =
|
||||
createNamespaceHookImplementation<TestMachineNamespaceStore>({
|
||||
createStore: createTestMachineNamespaceStore,
|
||||
createEventHandler: testMachineMessageHandler,
|
||||
});
|
||||
|
||||
export function useTestMachineNamespace(
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
): TestMachineNamespaceStore {
|
||||
const namespaceId: NamespaceId = {
|
||||
type: "machine",
|
||||
machine_identification_unique,
|
||||
};
|
||||
|
||||
return useTestMachineNamespaceImplementation(namespaceId);
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import { toastError } from "@/components/Toast";
|
||||
import { useStateOptimistic } from "@/lib/useStateOptimistic";
|
||||
import { testMachineSerialRoute } from "@/routes/routes";
|
||||
import { MachineIdentificationUnique } from "@/machines/types";
|
||||
import { useTestMachineNamespace, StateEvent } from "./testMachineNamespace";
|
||||
import { useMachineMutate } from "@/client/useClient";
|
||||
import { produce } from "immer";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { testmachine } from "@/machines/properties";
|
||||
import { z } from "zod";
|
||||
export function useTestMachine() {
|
||||
const { serial: serialString } = testMachineSerialRoute.useParams();
|
||||
|
||||
// Memoize machine identification
|
||||
const machineIdentification: MachineIdentificationUnique = useMemo(() => {
|
||||
const serial = parseInt(serialString);
|
||||
|
||||
if (isNaN(serial)) {
|
||||
toastError(
|
||||
"Invalid Serial Number",
|
||||
`"${serialString}" is not a valid serial number.`,
|
||||
);
|
||||
|
||||
return {
|
||||
machine_identification: { vendor: 0, machine: 0 },
|
||||
serial: 0,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
machine_identification: testmachine.machine_identification,
|
||||
serial,
|
||||
};
|
||||
}, [serialString]);
|
||||
|
||||
// Namespace state from backend
|
||||
const { state } = useTestMachineNamespace(machineIdentification);
|
||||
|
||||
// Optimistic state
|
||||
const stateOptimistic = useStateOptimistic<StateEvent>();
|
||||
|
||||
useEffect(() => {
|
||||
if (state) stateOptimistic.setReal(state);
|
||||
}, [state, stateOptimistic]);
|
||||
|
||||
// Generic mutation sender
|
||||
const { request: sendMutation } = useMachineMutate(
|
||||
z.object({
|
||||
action: z.string(),
|
||||
value: z.any(),
|
||||
}),
|
||||
);
|
||||
|
||||
const updateStateOptimistically = (
|
||||
producer: (current: StateEvent) => void,
|
||||
serverRequest?: () => void,
|
||||
) => {
|
||||
const currentState = stateOptimistic.value;
|
||||
if (currentState)
|
||||
stateOptimistic.setOptimistic(produce(currentState, producer));
|
||||
serverRequest?.();
|
||||
};
|
||||
|
||||
const setLed = (index: number, on: boolean) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.led_on[index] = on;
|
||||
},
|
||||
() =>
|
||||
sendMutation({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { action: "SetLed", value: { index, on } },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setAllLeds = (on: boolean) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.led_on = [on, on, on, on];
|
||||
},
|
||||
() =>
|
||||
sendMutation({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { action: "SetAllLeds", value: { on } },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
state: stateOptimistic.value,
|
||||
setLed,
|
||||
setAllLeds,
|
||||
};
|
||||
}
|
||||
@@ -1,62 +1,46 @@
|
||||
import { createRoute, Outlet } from "@tanstack/react-router";
|
||||
import { RootRoute } from "./__root";
|
||||
import React from "react";
|
||||
import { z } from "zod";
|
||||
|
||||
import { ChooseVersionPage } from "@/setup/ChooseVersionPage";
|
||||
import { fallback, zodValidator } from "@tanstack/zod-adapter";
|
||||
import { githubSourceSchema } from "@/setup/GithubSourceDialog";
|
||||
import { SidebarLayout } from "@/components/SidebarLayout";
|
||||
import { SetupPage } from "@/setup/SetupPage";
|
||||
import { EthercatPage } from "@/setup/EthercatPage";
|
||||
import { MachinesPage } from "@/setup/MachinesPage";
|
||||
import { ChangelogPage } from "@/setup/ChangelogPage";
|
||||
import { TroubleshootPage } from "@/setup/Trobleshoot";
|
||||
import { UpdateExecutePage } from "@/setup/UpdateExecutePage";
|
||||
|
||||
import { Winder2Page } from "@/machines/winder/winder2/Winder2Page";
|
||||
import { Winder2ControlPage } from "@/machines/winder/winder2/Winder2ControlPage";
|
||||
import { Winder2ManualPage } from "@/machines/winder/winder2/Winder2Manual";
|
||||
import { Winder2SettingPage } from "@/machines/winder/winder2/Winder2Settings";
|
||||
import { Winder2GraphsPage } from "@/machines/winder/winder2/Winder2Graphs";
|
||||
import { Winder2PresetsPage } from "@/machines/winder/winder2/Winder2PresetsPage";
|
||||
|
||||
import { Extruder2Page } from "@/machines/extruder/extruder2/Extruder2Page";
|
||||
import { Extruder2ControlPage } from "@/machines/extruder/extruder2/Extruder2ControlPage";
|
||||
import { Extruder2SettingsPage } from "@/machines/extruder/extruder2/Extruder2Settings";
|
||||
import { ExtruderV2ManualPage } from "@/machines/extruder/extruder2/Extruder2Manual";
|
||||
import { Extruder2GraphsPage } from "@/machines/extruder/extruder2/Extruder2Graph";
|
||||
import { Extruder2PresetsPage } from "@/machines/extruder/extruder2/Extruder2PresetsPage";
|
||||
|
||||
import { Extruder3Page } from "@/machines/extruder/extruder3/Extruder3Page";
|
||||
import { Extruder3ControlPage } from "@/machines/extruder/extruder3/Extruder3ControlPage";
|
||||
import { Extruder3SettingsPage } from "@/machines/extruder/extruder3/Extruder3Settings";
|
||||
import { ExtruderV3ManualPage } from "@/machines/extruder/extruder3/Extruder3Manual";
|
||||
import { Extruder3GraphsPage } from "@/machines/extruder/extruder3/Extruder3Graph";
|
||||
import { Extruder3PresetsPage } from "@/machines/extruder/extruder3/Extruder3PresetsPage";
|
||||
import { ChooseVersionPage } from "@/setup/ChooseVersionPage";
|
||||
import { fallback, zodValidator } from "@tanstack/zod-adapter";
|
||||
|
||||
import { Buffer1ControlPage } from "@/machines/buffer/buffer1/Buffer1ControlPage";
|
||||
import { Buffer1Page } from "@/machines/buffer/buffer1/Buffer1Page";
|
||||
import { Buffer1SettingsPage } from "@/machines/buffer/buffer1/Buffer1Settings";
|
||||
|
||||
import { ChangelogPage } from "@/setup/ChangelogPage";
|
||||
import { Extruder2GraphsPage } from "@/machines/extruder/extruder2/Extruder2Graph";
|
||||
import { Extruder2PresetsPage } from "@/machines/extruder/extruder2/Extruder2PresetsPage";
|
||||
import { githubSourceSchema } from "@/setup/GithubSourceDialog";
|
||||
import { Laser1ControlPage } from "@/machines/laser/laser1/Laser1ControlPage";
|
||||
import { Laser1GraphsPage } from "@/machines/laser/laser1/Laser1Graph";
|
||||
import { Laser1Page } from "@/machines/laser/laser1/Laser1Page";
|
||||
|
||||
import { Mock1ControlPage } from "@/machines/mock/mock1/Mock1ControlPage";
|
||||
import { Mock1GraphPage } from "@/machines/mock/mock1/Mock1Graph";
|
||||
import { Mock1ManualPage } from "@/machines/mock/mock1/Mock1Manual";
|
||||
import { Mock1Page } from "@/machines/mock/mock1/Mock1Page";
|
||||
import { Mock1PresetsPage } from "@/machines/mock/mock1/Mock1PresetsPage";
|
||||
|
||||
import { TroubleshootPage } from "@/setup/Trobleshoot";
|
||||
import { UpdateExecutePage } from "@/setup/UpdateExecutePage";
|
||||
import { Winder2PresetsPage } from "@/machines/winder/winder2/Winder2PresetsPage";
|
||||
import { Aquapath1ControlPage } from "@/machines/aquapath/aquapath1/Aquapath1ControlPage";
|
||||
import { Aquapath1Page } from "@/machines/aquapath/aquapath1/Aquapath1Page";
|
||||
import { Aquapath1GraphPage } from "@/machines/aquapath/aquapath1/Aquapath1Graph";
|
||||
|
||||
import { TestMachinePage } from "@/machines/testmachine/TestMachinePage";
|
||||
import { TestMachineControlPage } from "@/machines/testmachine/TestMachineControlPage";
|
||||
import { z } from "zod";
|
||||
import { Laser1PresetsPage } from "@/machines/laser/laser1/Laser1PresetsPage";
|
||||
|
||||
// make a route tree like this
|
||||
// _mainNavigation/machines/winder2/$serial/control
|
||||
// _mainNavigation/configuration/a
|
||||
@@ -64,18 +48,6 @@ import { Laser1PresetsPage } from "@/machines/laser/laser1/Laser1PresetsPage";
|
||||
// the mainNavigation has a custom layout
|
||||
// the winder2 winder2 and configuration also have a custom layout
|
||||
// the leaf routes are just pages
|
||||
export const testMachineSerialRoute = createRoute({
|
||||
getParentRoute: () => machinesRoute,
|
||||
path: "testmachine/$serial",
|
||||
component: () => <TestMachinePage />,
|
||||
});
|
||||
|
||||
// Leaf route: control page
|
||||
export const testMachineControlRoute = createRoute({
|
||||
getParentRoute: () => testMachineSerialRoute,
|
||||
path: "control",
|
||||
component: () => <TestMachineControlPage />,
|
||||
});
|
||||
|
||||
export const sidebarRoute = createRoute({
|
||||
getParentRoute: () => RootRoute,
|
||||
@@ -124,42 +96,6 @@ export const extruder2PresetsRoute = createRoute({
|
||||
component: () => <Extruder2PresetsPage />,
|
||||
});
|
||||
|
||||
export const extruder3Route = createRoute({
|
||||
getParentRoute: () => machinesRoute,
|
||||
path: "extruder3/$serial",
|
||||
component: () => <Extruder3Page />,
|
||||
});
|
||||
|
||||
export const extruder3ControlRoute = createRoute({
|
||||
getParentRoute: () => extruder3Route,
|
||||
path: "control",
|
||||
component: () => <Extruder3ControlPage />,
|
||||
});
|
||||
|
||||
export const extruder3SettingsRoute = createRoute({
|
||||
getParentRoute: () => extruder3Route,
|
||||
path: "settings",
|
||||
component: () => <Extruder3SettingsPage />,
|
||||
});
|
||||
|
||||
export const extruder3ManualRoute = createRoute({
|
||||
getParentRoute: () => extruder3Route,
|
||||
path: "manual",
|
||||
component: () => <ExtruderV3ManualPage />,
|
||||
});
|
||||
|
||||
export const extruder3GraphsRoute = createRoute({
|
||||
getParentRoute: () => extruder3Route,
|
||||
path: "graphs",
|
||||
component: () => <Extruder3GraphsPage />,
|
||||
});
|
||||
|
||||
export const extruder3PresetsRoute = createRoute({
|
||||
getParentRoute: () => extruder3Route,
|
||||
path: "presets",
|
||||
component: () => <Extruder3PresetsPage />,
|
||||
});
|
||||
|
||||
export const winder2SerialRoute = createRoute({
|
||||
getParentRoute: () => machinesRoute,
|
||||
path: "winder2/$serial",
|
||||
@@ -377,7 +313,6 @@ export const rootTree = RootRoute.addChildren([
|
||||
laser1GraphsRoute,
|
||||
laser1PresetsRoute,
|
||||
]),
|
||||
testMachineSerialRoute.addChildren([testMachineControlRoute]),
|
||||
|
||||
aquapath1SerialRoute.addChildren([
|
||||
aquapath1ControlRoute,
|
||||
@@ -400,14 +335,6 @@ export const rootTree = RootRoute.addChildren([
|
||||
extruder2PresetsRoute,
|
||||
]),
|
||||
|
||||
extruder3Route.addChildren([
|
||||
extruder3ControlRoute,
|
||||
extruder3SettingsRoute,
|
||||
extruder3ManualRoute,
|
||||
extruder3GraphsRoute,
|
||||
extruder3PresetsRoute,
|
||||
]),
|
||||
|
||||
mock1SerialRoute.addChildren([
|
||||
mock1ControlRoute,
|
||||
mock1GraphRoute,
|
||||
|
||||
@@ -45,12 +45,12 @@ impl MachineAct for AquaPathV1 {
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
/*Doesnt connec to any Machine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
/*Doesnt connec to any Machine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
|
||||
@@ -33,12 +33,12 @@ impl MachineAct for BufferV1 {
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
/*Doesnt connec to any Macine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
/*Doesnt connec to any Machine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ impl MachineAct for ExtruderV2 {
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) => (),
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
/*Doesnt connec to any Machine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
|
||||
@@ -251,11 +251,8 @@ impl ExtruderV2 {
|
||||
|
||||
pub fn set_regulation(&mut self, uses_rpm: bool) {
|
||||
if !self.screw_speed_controller.get_uses_rpm() && uses_rpm {
|
||||
self.screw_speed_controller.set_target_screw_rpm(
|
||||
self.screw_speed_controller.target_rpm,
|
||||
AngularVelocity::new::<revolution_per_minute>(1500.0),
|
||||
4,
|
||||
);
|
||||
self.screw_speed_controller
|
||||
.set_target_screw_rpm(self.screw_speed_controller.target_rpm);
|
||||
self.screw_speed_controller.set_uses_rpm(uses_rpm);
|
||||
}
|
||||
|
||||
@@ -273,11 +270,8 @@ impl ExtruderV2 {
|
||||
}
|
||||
|
||||
pub fn set_target_rpm(&mut self, rpm: f64) {
|
||||
self.screw_speed_controller.set_target_screw_rpm(
|
||||
AngularVelocity::new::<revolution_per_minute>(rpm),
|
||||
AngularVelocity::new::<revolution_per_minute>(1500.0),
|
||||
4,
|
||||
);
|
||||
self.screw_speed_controller
|
||||
.set_target_screw_rpm(AngularVelocity::new::<revolution_per_minute>(rpm));
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
|
||||
@@ -443,6 +443,7 @@ impl MitsubishiCS80 {
|
||||
pub fn set_frequency_target(&mut self, frequency: Frequency) {
|
||||
let mut request: MitsubishiCS80Request =
|
||||
MitsubishiCS80Requests::WriteRunningFrequency.into();
|
||||
|
||||
let result = self.convert_frequency_to_word(frequency);
|
||||
let bytes = result.to_le_bytes();
|
||||
request.request.data[2] = bytes[1];
|
||||
|
||||
@@ -85,8 +85,6 @@ impl MachineNewTrait for ExtruderV2 {
|
||||
// so we can't drop subdevices unless this machine is dropped, which is bad
|
||||
smol::block_on(async {
|
||||
// Role 0 - Buscoupler EK1100
|
||||
|
||||
use control_core::transmission::fixed::FixedTransmission;
|
||||
let _ek1100 =
|
||||
get_ethercat_device::<EK1100>(hardware, params, 0, [EK1100_IDENTITY_A].to_vec());
|
||||
|
||||
@@ -216,13 +214,8 @@ impl MachineNewTrait for ExtruderV2 {
|
||||
let target_pressure = Pressure::new::<bar>(0.0);
|
||||
let target_rpm = AngularVelocity::new::<revolution_per_minute>(0.0);
|
||||
|
||||
let screw_speed_controller = ScrewSpeedController::new(
|
||||
inverter,
|
||||
target_pressure,
|
||||
target_rpm,
|
||||
pressure_sensor,
|
||||
FixedTransmission::new(1.0 / 34.0),
|
||||
);
|
||||
let screw_speed_controller =
|
||||
ScrewSpeedController::new(inverter, target_pressure, target_rpm, pressure_sensor);
|
||||
let (sender, receiver) = smol::channel::unbounded();
|
||||
|
||||
let mut extruder: ExtruderV2 = Self {
|
||||
|
||||
@@ -41,7 +41,6 @@ impl ScrewSpeedController {
|
||||
target_pressure: Pressure,
|
||||
target_rpm: AngularVelocity,
|
||||
pressure_sensor: AnalogInput,
|
||||
transmission: FixedTransmission,
|
||||
) -> Self {
|
||||
let now = Instant::now();
|
||||
Self {
|
||||
@@ -54,8 +53,7 @@ impl ScrewSpeedController {
|
||||
pressure_sensor,
|
||||
uses_rpm: true,
|
||||
forward_rotation: true,
|
||||
transmission: transmission,
|
||||
//FixedTransmission::new(1.0 / 34.0),
|
||||
transmission: FixedTransmission::new(1.0 / 34.0),
|
||||
motor_on: false,
|
||||
nozzle_pressure_limit: Pressure::new::<bar>(100.0),
|
||||
nozzle_pressure_limit_enabled: true,
|
||||
@@ -109,22 +107,15 @@ impl ScrewSpeedController {
|
||||
self.target_pressure = target_pressure;
|
||||
}
|
||||
|
||||
pub fn set_target_screw_rpm(
|
||||
&mut self,
|
||||
target_rpm: AngularVelocity,
|
||||
_motor_rpm_rating: AngularVelocity,
|
||||
motor_poles: usize,
|
||||
) {
|
||||
pub fn set_target_screw_rpm(&mut self, target_rpm: AngularVelocity) {
|
||||
// Use uom here and perhaps clamp it
|
||||
let target_motor_rpm = self
|
||||
.transmission
|
||||
.calculate_angular_velocity_input(target_rpm);
|
||||
|
||||
self.target_rpm = target_rpm;
|
||||
|
||||
let target_frequency: Frequency = Frequency::new::<hertz>(
|
||||
target_motor_rpm.get::<revolution_per_minute>() as f64 / 120.0 * motor_poles as f64,
|
||||
);
|
||||
let target_frequency =
|
||||
Frequency::new::<cycle_per_minute>(target_motor_rpm.get::<revolution_per_minute>());
|
||||
|
||||
self.inverter.set_frequency_target(target_frequency);
|
||||
}
|
||||
@@ -215,7 +206,14 @@ impl ScrewSpeedController {
|
||||
pub fn update(&mut self, now: Instant, is_extruding: bool) {
|
||||
// TODO: move this logic elsewhere or make non async
|
||||
smol::block_on(self.inverter.act(now));
|
||||
|
||||
let wiring_error = self.get_wiring_error();
|
||||
if wiring_error {
|
||||
// emit in act
|
||||
}
|
||||
|
||||
let measured_pressure = self.get_pressure();
|
||||
|
||||
if !self.uses_rpm && !is_extruding && self.motor_on {
|
||||
let frequency = Frequency::new::<hertz>(0.0);
|
||||
self.inverter.set_frequency_target(frequency);
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use std::time::Instant;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::{MachineAct, MachineMessage};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use super::ExtruderV3;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl MachineAct for ExtruderV3 {
|
||||
fn act(&mut self, now: Instant) {
|
||||
use std::time::Duration;
|
||||
|
||||
let msg = self.api_receiver.try_recv();
|
||||
match msg {
|
||||
Ok(msg) => {
|
||||
let _res = self.act_machine_message(msg);
|
||||
}
|
||||
Err(_) => (),
|
||||
};
|
||||
|
||||
self.temperature_controller_back.update(now);
|
||||
self.temperature_controller_nozzle.update(now);
|
||||
self.temperature_controller_front.update(now);
|
||||
self.temperature_controller_middle.update(now);
|
||||
|
||||
if self.mode == super::ExtruderV3Mode::Extrude {
|
||||
self.screw_speed_controller.update(now, true);
|
||||
} else {
|
||||
self.screw_speed_controller.update(now, false);
|
||||
}
|
||||
|
||||
if self.mode == super::ExtruderV3Mode::Standby {
|
||||
self.turn_heating_off();
|
||||
}
|
||||
|
||||
if self.mode == super::ExtruderV3Mode::Extrude
|
||||
&& !self.screw_speed_controller.get_motor_enabled()
|
||||
{
|
||||
self.switch_to_heat();
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
self.maybe_emit_state_event();
|
||||
// Emit live values at 30 FPS
|
||||
self.emit_live_values();
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
}
|
||||
|
||||
fn act_machine_message(&mut self, msg: MachineMessage) {
|
||||
match msg {
|
||||
MachineMessage::SubscribeNamespace(namespace) => {
|
||||
self.namespace.namespace = Some(namespace);
|
||||
self.emit_state();
|
||||
tracing::info!("extruder1 received subscribe");
|
||||
}
|
||||
MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None,
|
||||
MachineMessage::HttpApiJsonRequest(value) => {
|
||||
use crate::MachineApi;
|
||||
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) => (),
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::extruder1::{
|
||||
api::{
|
||||
ExtruderSettingsState, HeatingStates, InverterStatusState, PidSettings, PidSettingsStates,
|
||||
PressureState, RegulationState, RotationState, ScrewState, TemperaturePid,
|
||||
},
|
||||
mitsubishi_cs80::MotorStatus,
|
||||
};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::{MachineApi, MachineMessage};
|
||||
use control_core::socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_first_and_last_event,
|
||||
},
|
||||
};
|
||||
use control_core_derive::BuildEvent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use serde_json::Value;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use smol::channel::Sender;
|
||||
use tracing::instrument;
|
||||
use units::{
|
||||
angular_velocity::revolution_per_minute, electric_current::ampere, electric_potential::volt,
|
||||
frequency::hertz,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use super::ExtruderV3;
|
||||
use super::ExtruderV3Mode;
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize)]
|
||||
pub struct MotorStatusValues {
|
||||
pub screw_rpm: f64, // rpm of motor
|
||||
pub frequency: f64, // frequency of motor
|
||||
pub voltage: f64, // volt used for motor
|
||||
pub current: f64, // current used for the motor
|
||||
pub power: f64, // power in watts
|
||||
}
|
||||
|
||||
impl From<MotorStatus> for MotorStatusValues {
|
||||
fn from(status: MotorStatus) -> Self {
|
||||
let voltage = status.voltage.get::<volt>();
|
||||
let current = status.current.get::<ampere>();
|
||||
|
||||
Self {
|
||||
screw_rpm: status.rpm.get::<revolution_per_minute>(),
|
||||
frequency: status.frequency.get::<hertz>(),
|
||||
voltage,
|
||||
current,
|
||||
power: voltage * current,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
pub struct LiveValuesEvent {
|
||||
/// screw rpm
|
||||
pub motor_status: MotorStatusValues,
|
||||
/// pressure in bar
|
||||
pub pressure: f64,
|
||||
/// nozzle temperature in celsius
|
||||
pub nozzle_temperature: f64,
|
||||
/// front temperature in celsius
|
||||
pub front_temperature: f64,
|
||||
/// back temperature in celsius
|
||||
pub back_temperature: f64,
|
||||
/// middle temperature in celsius
|
||||
pub middle_temperature: f64,
|
||||
/// nozzle heating power in watts
|
||||
pub nozzle_power: f64,
|
||||
/// front heating power in watts
|
||||
pub front_power: f64,
|
||||
/// back heating power in watts
|
||||
pub back_power: f64,
|
||||
/// middle heating power in watts
|
||||
pub middle_power: f64,
|
||||
/// combined power consumption in watts
|
||||
pub combined_power: f64,
|
||||
/// total energy consumption in kWh
|
||||
pub total_energy_kwh: f64,
|
||||
}
|
||||
|
||||
impl LiveValuesEvent {
|
||||
pub fn build(&self) -> Event<Self> {
|
||||
Event::new("LiveValuesEvent", self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, PartialEq, BuildEvent)]
|
||||
pub struct StateEvent {
|
||||
pub is_default_state: bool,
|
||||
/// rotation state
|
||||
pub rotation_state: RotationState,
|
||||
/// mode state
|
||||
pub mode_state: ModeState,
|
||||
/// regulation state
|
||||
pub regulation_state: RegulationState,
|
||||
/// pressure state
|
||||
pub pressure_state: PressureState,
|
||||
/// screw state
|
||||
pub screw_state: ScrewState,
|
||||
/// heating states
|
||||
pub heating_states: HeatingStates,
|
||||
/// extruder settings state
|
||||
pub extruder_settings_state: ExtruderSettingsState,
|
||||
/// inverter status state
|
||||
pub inverter_status_state: InverterStatusState,
|
||||
/// pid settings
|
||||
pub pid_settings: PidSettingsStates,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ModeState {
|
||||
pub mode: ExtruderV3Mode,
|
||||
}
|
||||
|
||||
pub enum ExtruderV3Events {
|
||||
LiveValues(Event<LiveValuesEvent>),
|
||||
State(Event<StateEvent>),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub enum Mutation {
|
||||
/// INVERTER
|
||||
/// Frequency Control
|
||||
// Set Rotation also starts the motor
|
||||
SetInverterRotationDirection(bool),
|
||||
SetInverterTargetPressure(f64),
|
||||
SetInverterTargetRpm(f64),
|
||||
SetInverterRegulation(bool),
|
||||
|
||||
//Mode
|
||||
SetExtruderMode(ExtruderV3Mode),
|
||||
SetFrontHeatingTargetTemperature(f64),
|
||||
SetBackHeatingTargetTemperature(f64),
|
||||
SetMiddleHeatingTemperature(f64),
|
||||
SetNozzleHeatingTemperature(f64),
|
||||
|
||||
// SetPressure
|
||||
SetExtruderPressureLimit(f64),
|
||||
SetExtruderPressureLimitIsEnabled(bool),
|
||||
|
||||
// Pid Configure
|
||||
SetPressurePidSettings(PidSettings),
|
||||
SetTemperaturePidSettings(TemperaturePid),
|
||||
|
||||
// Reset
|
||||
ResetInverter(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExtruderV3Namespace {
|
||||
pub namespace: Option<Namespace>,
|
||||
}
|
||||
|
||||
impl NamespaceCacheingLogic<ExtruderV3Events> for ExtruderV3Namespace {
|
||||
#[instrument(skip_all)]
|
||||
fn emit(&mut self, events: ExtruderV3Events) {
|
||||
let event = Arc::new(events.event_value());
|
||||
let buffer_fn = events.event_cache_fn();
|
||||
|
||||
match &mut self.namespace {
|
||||
Some(ns) => ns.emit(event, &buffer_fn),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CacheableEvents<Self> for ExtruderV3Events {
|
||||
fn event_value(&self) -> GenericEvent {
|
||||
match self {
|
||||
Self::LiveValues(event) => event.into(),
|
||||
Self::State(event) => event.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn event_cache_fn(&self) -> CacheFn {
|
||||
let cache_first_and_last = cache_first_and_last_event();
|
||||
match self {
|
||||
Self::LiveValues(_) => cache_first_and_last,
|
||||
Self::State(_) => cache_first_and_last,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl MachineApi for ExtruderV3 {
|
||||
fn api_get_sender(&self) -> Sender<MachineMessage> {
|
||||
self.api_sender.clone()
|
||||
}
|
||||
|
||||
fn api_mutate(&mut self, request_body: Value) -> Result<(), anyhow::Error> {
|
||||
// there are multiple Modbus Frames that are "prebuilt"
|
||||
use crate::extruder1::HeatingType;
|
||||
let control: Mutation = serde_json::from_value(request_body)?;
|
||||
match control {
|
||||
Mutation::SetExtruderMode(mode) => self.set_mode_state(mode),
|
||||
Mutation::SetInverterRotationDirection(forward) => self.set_rotation_state(forward),
|
||||
Mutation::SetInverterRegulation(uses_rpm) => self.set_regulation(uses_rpm),
|
||||
Mutation::SetInverterTargetPressure(bar) => self.set_target_pressure(bar),
|
||||
Mutation::SetInverterTargetRpm(rpm) => self.set_target_rpm(rpm),
|
||||
Mutation::ResetInverter(_) => self.reset_inverter(),
|
||||
|
||||
Mutation::SetFrontHeatingTargetTemperature(temp) => {
|
||||
self.set_target_temperature(temp, HeatingType::Front)
|
||||
}
|
||||
Mutation::SetMiddleHeatingTemperature(temp) => {
|
||||
self.set_target_temperature(temp, HeatingType::Middle)
|
||||
}
|
||||
Mutation::SetBackHeatingTargetTemperature(temp) => {
|
||||
self.set_target_temperature(temp, HeatingType::Back)
|
||||
}
|
||||
Mutation::SetNozzleHeatingTemperature(temp) => {
|
||||
self.set_target_temperature(temp, HeatingType::Nozzle)
|
||||
}
|
||||
Mutation::SetExtruderPressureLimit(pressure_limit) => {
|
||||
self.set_nozzle_pressure_limit(pressure_limit);
|
||||
}
|
||||
Mutation::SetExtruderPressureLimitIsEnabled(enabled) => {
|
||||
self.set_nozzle_pressure_limit_is_enabled(enabled);
|
||||
}
|
||||
|
||||
Mutation::SetPressurePidSettings(settings) => {
|
||||
self.configure_pressure_pid(settings);
|
||||
}
|
||||
|
||||
Mutation::SetTemperaturePidSettings(settings) => {
|
||||
self.configure_temperature_pid(settings);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> Option<Namespace> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,355 +0,0 @@
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
// Contains Implementations for All functions that use emit_state
|
||||
use crate::extruder1::{
|
||||
HeatingType,
|
||||
api::{
|
||||
ExtruderSettingsState, HeatingState, HeatingStates, InverterStatusState, PidSettings,
|
||||
PidSettingsStates, PressureState, RegulationState, RotationState, ScrewState,
|
||||
TemperaturePid,
|
||||
},
|
||||
};
|
||||
#[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 units::angular_velocity::AngularVelocity;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use units::pressure::{Pressure, bar};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use units::thermodynamic_temperature::ThermodynamicTemperature;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use units::{angular_velocity::revolution_per_minute, thermodynamic_temperature::degree_celsius};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use super::{ExtruderV3, ExtruderV3Mode, api::StateEvent};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl ExtruderV3 {
|
||||
pub fn build_state_event(&mut self) -> StateEvent {
|
||||
use crate::{
|
||||
extruder1::api::{TemperaturePid, TemperaturePidStates},
|
||||
extruder2::api::ModeState,
|
||||
};
|
||||
|
||||
StateEvent {
|
||||
is_default_state: !std::mem::replace(&mut self.emitted_default_state, true),
|
||||
rotation_state: RotationState {
|
||||
forward: self.screw_speed_controller.get_rotation_direction(),
|
||||
},
|
||||
mode_state: ModeState {
|
||||
mode: self.mode.clone(),
|
||||
},
|
||||
regulation_state: RegulationState {
|
||||
uses_rpm: self.screw_speed_controller.get_uses_rpm(),
|
||||
},
|
||||
pressure_state: PressureState {
|
||||
target_bar: self
|
||||
.screw_speed_controller
|
||||
.get_target_pressure()
|
||||
.get::<bar>(),
|
||||
wiring_error: self.screw_speed_controller.get_wiring_error(),
|
||||
},
|
||||
screw_state: ScrewState {
|
||||
target_rpm: self
|
||||
.screw_speed_controller
|
||||
.get_target_rpm()
|
||||
.get::<revolution_per_minute>(),
|
||||
},
|
||||
heating_states: HeatingStates {
|
||||
nozzle: HeatingState {
|
||||
target_temperature: self
|
||||
.temperature_controller_nozzle
|
||||
.heating
|
||||
.target_temperature
|
||||
.get::<degree_celsius>(),
|
||||
wiring_error: self.temperature_controller_nozzle.heating.wiring_error,
|
||||
},
|
||||
front: HeatingState {
|
||||
target_temperature: self
|
||||
.temperature_controller_front
|
||||
.heating
|
||||
.target_temperature
|
||||
.get::<degree_celsius>(),
|
||||
wiring_error: self.temperature_controller_front.heating.wiring_error,
|
||||
},
|
||||
back: HeatingState {
|
||||
target_temperature: self
|
||||
.temperature_controller_back
|
||||
.heating
|
||||
.target_temperature
|
||||
.get::<degree_celsius>(),
|
||||
wiring_error: self.temperature_controller_back.heating.wiring_error,
|
||||
},
|
||||
middle: HeatingState {
|
||||
target_temperature: self
|
||||
.temperature_controller_middle
|
||||
.heating
|
||||
.target_temperature
|
||||
.get::<degree_celsius>(),
|
||||
wiring_error: self.temperature_controller_middle.heating.wiring_error,
|
||||
},
|
||||
},
|
||||
extruder_settings_state: ExtruderSettingsState {
|
||||
pressure_limit: self
|
||||
.screw_speed_controller
|
||||
.get_nozzle_pressure_limit()
|
||||
.get::<bar>(),
|
||||
pressure_limit_enabled: self
|
||||
.screw_speed_controller
|
||||
.get_nozzle_pressure_limit_enabled(),
|
||||
},
|
||||
inverter_status_state: InverterStatusState {
|
||||
running: self.screw_speed_controller.inverter.status.running,
|
||||
forward_running: self.screw_speed_controller.inverter.status.forward_running,
|
||||
reverse_running: self.screw_speed_controller.inverter.status.reverse_running,
|
||||
up_to_frequency: self.screw_speed_controller.inverter.status.su,
|
||||
overload_warning: self.screw_speed_controller.inverter.status.ol,
|
||||
no_function: self.screw_speed_controller.inverter.status.no_function,
|
||||
output_frequency_detection: self.screw_speed_controller.inverter.status.fu,
|
||||
abc_fault: self.screw_speed_controller.inverter.status.abc_,
|
||||
fault_occurence: self.screw_speed_controller.inverter.status.fault_occurence,
|
||||
},
|
||||
pid_settings: PidSettingsStates {
|
||||
temperature: TemperaturePidStates {
|
||||
front: TemperaturePid {
|
||||
ki: self.temperature_controller_front.pid.get_ki(),
|
||||
kp: self.temperature_controller_front.pid.get_kp(),
|
||||
kd: self.temperature_controller_front.pid.get_kd(),
|
||||
zone: String::from("front"),
|
||||
},
|
||||
middle: TemperaturePid {
|
||||
ki: self.temperature_controller_middle.pid.get_ki(),
|
||||
kp: self.temperature_controller_middle.pid.get_kp(),
|
||||
kd: self.temperature_controller_middle.pid.get_kd(),
|
||||
zone: String::from("middle"),
|
||||
},
|
||||
back: TemperaturePid {
|
||||
ki: self.temperature_controller_back.pid.get_ki(),
|
||||
kp: self.temperature_controller_back.pid.get_kp(),
|
||||
kd: self.temperature_controller_back.pid.get_kd(),
|
||||
zone: String::from("back"),
|
||||
},
|
||||
nozzle: TemperaturePid {
|
||||
ki: self.temperature_controller_nozzle.pid.get_ki(),
|
||||
kp: self.temperature_controller_nozzle.pid.get_kp(),
|
||||
kd: self.temperature_controller_nozzle.pid.get_kd(),
|
||||
zone: String::from("nozzle"),
|
||||
},
|
||||
},
|
||||
pressure: PidSettings {
|
||||
ki: self.screw_speed_controller.pid.get_ki(),
|
||||
kp: self.screw_speed_controller.pid.get_kp(),
|
||||
kd: self.screw_speed_controller.pid.get_kd(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl ExtruderV3 {
|
||||
pub fn emit_state(&mut self) {
|
||||
use super::api::ExtruderV3Events;
|
||||
|
||||
let state = self.build_state_event();
|
||||
let hash = hash_with_serde_model(self.screw_speed_controller.get_inverter_status());
|
||||
self.last_status_hash = Some(hash);
|
||||
let event = state.build();
|
||||
self.namespace.emit(ExtruderV3Events::State(event));
|
||||
}
|
||||
|
||||
pub fn maybe_emit_state_event(&mut self) {
|
||||
let old_status_hash = match self.last_status_hash {
|
||||
Some(event) => event,
|
||||
None => {
|
||||
self.emit_state();
|
||||
return;
|
||||
}
|
||||
};
|
||||
let status = self.screw_speed_controller.get_inverter_status();
|
||||
let new_status_hash = hash_with_serde_model(status);
|
||||
if new_status_hash != old_status_hash {
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_live_values(&mut self) {
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::extruder2::api::{ExtruderV3Events, LiveValuesEvent};
|
||||
let now = Instant::now();
|
||||
let combined_power = self.calculate_combined_power();
|
||||
self.update_total_energy(combined_power, now);
|
||||
|
||||
let live_values = LiveValuesEvent {
|
||||
motor_status: self.screw_speed_controller.get_motor_status().into(),
|
||||
pressure: self.screw_speed_controller.get_pressure().get::<bar>(),
|
||||
nozzle_temperature: self
|
||||
.temperature_controller_nozzle
|
||||
.heating
|
||||
.temperature
|
||||
.get::<degree_celsius>(),
|
||||
front_temperature: self
|
||||
.temperature_controller_front
|
||||
.heating
|
||||
.temperature
|
||||
.get::<degree_celsius>(),
|
||||
back_temperature: self
|
||||
.temperature_controller_back
|
||||
.heating
|
||||
.temperature
|
||||
.get::<degree_celsius>(),
|
||||
middle_temperature: self
|
||||
.temperature_controller_middle
|
||||
.heating
|
||||
.temperature
|
||||
.get::<degree_celsius>(),
|
||||
nozzle_power: self
|
||||
.temperature_controller_nozzle
|
||||
.get_heating_element_wattage(),
|
||||
front_power: self
|
||||
.temperature_controller_front
|
||||
.get_heating_element_wattage(),
|
||||
back_power: self
|
||||
.temperature_controller_back
|
||||
.get_heating_element_wattage(),
|
||||
middle_power: self
|
||||
.temperature_controller_middle
|
||||
.get_heating_element_wattage(),
|
||||
combined_power,
|
||||
total_energy_kwh: self.total_energy_kwh,
|
||||
};
|
||||
|
||||
let event = live_values.build();
|
||||
self.namespace.emit(ExtruderV3Events::LiveValues(event));
|
||||
}
|
||||
|
||||
// === Steuerungsfunktionen mit emit_state ===
|
||||
|
||||
pub fn set_nozzle_pressure_limit_is_enabled(&mut self, enabled: bool) {
|
||||
self.screw_speed_controller
|
||||
.set_nozzle_pressure_limit_is_enabled(enabled);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_nozzle_pressure_limit(&mut self, pressure: f64) {
|
||||
self.screw_speed_controller
|
||||
.set_nozzle_pressure_limit(Pressure::new::<bar>(pressure));
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn enable_heating(&mut self) {
|
||||
self.temperature_controller_back.allow_heating();
|
||||
self.temperature_controller_front.allow_heating();
|
||||
self.temperature_controller_middle.allow_heating();
|
||||
self.temperature_controller_nozzle.allow_heating();
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_rotation_state(&mut self, forward: bool) {
|
||||
self.screw_speed_controller.set_rotation_direction(forward);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_mode_state(&mut self, mode: ExtruderV3Mode) {
|
||||
self.switch_mode(mode);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_regulation(&mut self, uses_rpm: bool) {
|
||||
if !self.screw_speed_controller.get_uses_rpm() && uses_rpm {
|
||||
self.screw_speed_controller.set_target_screw_rpm(
|
||||
self.screw_speed_controller.target_rpm,
|
||||
AngularVelocity::new::<revolution_per_minute>(3000.0),
|
||||
2,
|
||||
);
|
||||
self.screw_speed_controller.set_uses_rpm(uses_rpm);
|
||||
}
|
||||
|
||||
if self.screw_speed_controller.get_uses_rpm() && !uses_rpm {
|
||||
self.screw_speed_controller.set_uses_rpm(uses_rpm);
|
||||
self.screw_speed_controller.start_pressure_regulation();
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_target_pressure(&mut self, pressure: f64) {
|
||||
self.screw_speed_controller
|
||||
.set_target_pressure(Pressure::new::<bar>(pressure));
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_target_rpm(&mut self, rpm: f64) {
|
||||
self.screw_speed_controller.set_target_screw_rpm(
|
||||
AngularVelocity::new::<revolution_per_minute>(rpm),
|
||||
AngularVelocity::new::<revolution_per_minute>(3000.0),
|
||||
2,
|
||||
);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_target_temperature(&mut self, target_temperature: f64, heating_type: HeatingType) {
|
||||
let target_temp = ThermodynamicTemperature::new::<degree_celsius>(target_temperature);
|
||||
|
||||
match heating_type {
|
||||
HeatingType::Nozzle => self
|
||||
.temperature_controller_nozzle
|
||||
.set_target_temperature(target_temp),
|
||||
HeatingType::Front => self
|
||||
.temperature_controller_front
|
||||
.set_target_temperature(target_temp),
|
||||
HeatingType::Back => self
|
||||
.temperature_controller_back
|
||||
.set_target_temperature(target_temp),
|
||||
HeatingType::Middle => self
|
||||
.temperature_controller_middle
|
||||
.set_target_temperature(target_temp),
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn configure_pressure_pid(&mut self, settings: PidSettings) {
|
||||
self.screw_speed_controller
|
||||
.pid
|
||||
.configure(settings.ki, settings.kp, settings.kd);
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn configure_temperature_pid(&mut self, settings: TemperaturePid) {
|
||||
match settings.zone.as_str() {
|
||||
"front" => {
|
||||
self.temperature_controller_front.pid.configure(
|
||||
settings.ki,
|
||||
settings.kp,
|
||||
settings.kd,
|
||||
);
|
||||
}
|
||||
"middle" => {
|
||||
self.temperature_controller_middle.pid.configure(
|
||||
settings.ki,
|
||||
settings.kp,
|
||||
settings.kd,
|
||||
);
|
||||
}
|
||||
"back" => {
|
||||
self.temperature_controller_back.pid.configure(
|
||||
settings.ki,
|
||||
settings.kp,
|
||||
settings.kd,
|
||||
);
|
||||
}
|
||||
"nozzle" => {
|
||||
self.temperature_controller_nozzle.pid.configure(
|
||||
settings.ki,
|
||||
settings.kp,
|
||||
settings.kd,
|
||||
);
|
||||
}
|
||||
_ => tracing::warn!("Unknown zone: {}", settings.zone),
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
use super::ExtruderV2;
|
||||
use crate::MachineAct;
|
||||
use crate::MachineMessage;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
impl MachineAct for ExtruderV2 {
|
||||
fn act(&mut self, now: Instant) {
|
||||
let msg = self.api_receiver.try_recv();
|
||||
match msg {
|
||||
Ok(msg) => {
|
||||
let _res = self.act_machine_message(msg);
|
||||
}
|
||||
Err(_) => (),
|
||||
};
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
self.maybe_emit_state_event();
|
||||
// Emit live values at 30 FPS
|
||||
self.emit_live_values();
|
||||
self.last_measurement_emit = now;
|
||||
}
|
||||
}
|
||||
|
||||
fn act_machine_message(&mut self, msg: crate::MachineMessage) {
|
||||
match msg {
|
||||
MachineMessage::SubscribeNamespace(namespace) => {
|
||||
self.namespace.namespace = Some(namespace);
|
||||
self.emit_state();
|
||||
tracing::info!("extruder1 received subscribe");
|
||||
}
|
||||
MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None,
|
||||
MachineMessage::HttpApiJsonRequest(value) => {
|
||||
use crate::MachineApi;
|
||||
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) => (),
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connec to any Machine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
use super::ExtruderV2;
|
||||
use crate::{
|
||||
MachineApi,
|
||||
extruder1::{HeatingType, api::Mutation},
|
||||
};
|
||||
|
||||
impl MachineApi for ExtruderV2 {
|
||||
fn api_mutate(&mut self, request_body: serde_json::Value) -> Result<(), anyhow::Error> {
|
||||
// there are multiple Modbus Frames that are "prebuilt"
|
||||
let control: Mutation = serde_json::from_value(request_body)?;
|
||||
match control {
|
||||
Mutation::SetExtruderMode(mode) => self.set_mode_state(mode),
|
||||
Mutation::SetInverterRotationDirection(forward) => self.set_rotation_state(forward),
|
||||
Mutation::SetInverterRegulation(uses_rpm) => self.set_regulation(uses_rpm),
|
||||
Mutation::SetInverterTargetPressure(bar) => self.set_target_pressure(bar),
|
||||
Mutation::SetInverterTargetRpm(rpm) => self.set_target_rpm(rpm),
|
||||
Mutation::ResetInverter(_) => (),
|
||||
Mutation::SetFrontHeatingTargetTemperature(temp) => {
|
||||
self.set_target_temperature(temp, HeatingType::Front)
|
||||
}
|
||||
Mutation::SetMiddleHeatingTemperature(temp) => {
|
||||
self.set_target_temperature(temp, HeatingType::Middle)
|
||||
}
|
||||
Mutation::SetBackHeatingTargetTemperature(temp) => {
|
||||
self.set_target_temperature(temp, HeatingType::Back)
|
||||
}
|
||||
Mutation::SetNozzleHeatingTemperature(temp) => {
|
||||
self.set_target_temperature(temp, HeatingType::Nozzle)
|
||||
}
|
||||
Mutation::SetExtruderPressureLimit(pressure_limit) => {
|
||||
self.set_nozzle_pressure_limit(pressure_limit);
|
||||
}
|
||||
Mutation::SetExtruderPressureLimitIsEnabled(enabled) => {
|
||||
self.set_nozzle_pressure_limit_is_enabled(enabled);
|
||||
}
|
||||
|
||||
Mutation::SetPressurePidSettings(settings) => {
|
||||
self.configure_pressure_pid(settings);
|
||||
}
|
||||
|
||||
Mutation::SetTemperaturePidSettings(settings) => {
|
||||
self.configure_temperature_pid(settings);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(
|
||||
&mut self,
|
||||
) -> std::option::Option<control_core::socketio::namespace::Namespace> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
|
||||
fn api_get_sender(&self) -> smol::channel::Sender<crate::MachineMessage> {
|
||||
self.api_sender.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
use crate::extruder1::{
|
||||
ExtruderV2Mode, HeatingType,
|
||||
api::{ExtruderV2Events, LiveValuesEvent, ModeState, PidSettings, StateEvent, TemperaturePid},
|
||||
mock::ExtruderV2,
|
||||
};
|
||||
|
||||
use control_core::{
|
||||
helpers::hasher_serializer::hash_with_serde_model,
|
||||
socketio::{event::BuildEvent, namespace::NamespaceCacheingLogic},
|
||||
};
|
||||
|
||||
impl ExtruderV2 {
|
||||
pub fn build_state_event(&mut self) -> StateEvent {
|
||||
// bad performance wise, but doesnt matter its only a mock machine
|
||||
StateEvent {
|
||||
is_default_state: !std::mem::replace(&mut self.emitted_default_state, true),
|
||||
rotation_state: self.rotation_state.clone(),
|
||||
mode_state: self.mode_state.clone(),
|
||||
regulation_state: self.regulation_state.clone(),
|
||||
pressure_state: self.pressure_state.clone(),
|
||||
screw_state: self.screw_state.clone(),
|
||||
heating_states: self.heating_states.clone(),
|
||||
extruder_settings_state: self.extruder_settings_state.clone(),
|
||||
inverter_status_state: self.inverter_status_state.clone(),
|
||||
pid_settings: self.pid_settings.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtruderV2 {
|
||||
pub fn emit_state(&mut self) {
|
||||
let state = self.build_state_event();
|
||||
let hash = hash_with_serde_model(self.inverter_status_state.clone());
|
||||
self.last_status_hash = Some(hash);
|
||||
let event = state.build();
|
||||
self.namespace.emit(ExtruderV2Events::State(event));
|
||||
}
|
||||
|
||||
pub fn maybe_emit_state_event(&mut self) {
|
||||
let old_status_hash = match self.last_status_hash {
|
||||
Some(event) => event,
|
||||
None => {
|
||||
self.emit_state();
|
||||
return;
|
||||
}
|
||||
};
|
||||
let status = self.inverter_status_state.clone();
|
||||
let new_status_hash = hash_with_serde_model(status);
|
||||
if new_status_hash != old_status_hash {
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_live_values(&mut self) {
|
||||
let live_values = LiveValuesEvent {
|
||||
motor_status: self.motor_status.clone(),
|
||||
pressure: self.pressure,
|
||||
nozzle_temperature: self.nozzle_temperature,
|
||||
front_temperature: self.front_temperature,
|
||||
back_temperature: self.back_temperature,
|
||||
middle_temperature: self.middle_temperature,
|
||||
nozzle_power: self.nozzle_power,
|
||||
front_power: self.front_power,
|
||||
back_power: self.back_power,
|
||||
middle_power: self.middle_power,
|
||||
combined_power: self.combined_power,
|
||||
total_energy_kwh: self.total_energy_kwh,
|
||||
};
|
||||
|
||||
let event = live_values.build();
|
||||
self.namespace.emit(ExtruderV2Events::LiveValues(event));
|
||||
}
|
||||
|
||||
pub fn set_nozzle_pressure_limit_is_enabled(&mut self, enabled: bool) {
|
||||
self.extruder_settings_state.pressure_limit_enabled = enabled;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_nozzle_pressure_limit(&mut self, pressure: f64) {
|
||||
self.extruder_settings_state.pressure_limit = pressure;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn enable_heating(&mut self) {
|
||||
self.back_heating_allowed = true;
|
||||
self.front_heating_allowed = true;
|
||||
self.nozzle_heating_allowed = true;
|
||||
self.back_heating_allowed = true;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_rotation_state(&mut self, forward: bool) {
|
||||
self.rotation_state.forward = forward;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_mode_state(&mut self, mode: ExtruderV2Mode) {
|
||||
self.mode_state = ModeState { mode };
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_regulation(&mut self, uses_rpm: bool) {
|
||||
self.regulation_state.uses_rpm = uses_rpm;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_target_pressure(&mut self, pressure: f64) {
|
||||
self.target_pressure = pressure;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_target_rpm(&mut self, rpm: f64) {
|
||||
self.screw_state.target_rpm = rpm;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn set_target_temperature(&mut self, target_temperature: f64, heating_type: HeatingType) {
|
||||
match heating_type {
|
||||
HeatingType::Nozzle => {
|
||||
self.heating_states.nozzle.target_temperature = target_temperature
|
||||
}
|
||||
HeatingType::Front => self.heating_states.front.target_temperature = target_temperature,
|
||||
HeatingType::Back => self.heating_states.back.target_temperature = target_temperature,
|
||||
HeatingType::Middle => {
|
||||
self.heating_states.middle.target_temperature = target_temperature
|
||||
}
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn configure_pressure_pid(&mut self, settings: PidSettings) {
|
||||
self.pid_settings.pressure.ki = settings.ki;
|
||||
self.pid_settings.pressure.kp = settings.kp;
|
||||
self.pid_settings.pressure.kd = settings.kd;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn configure_temperature_pid(&mut self, settings: TemperaturePid) {
|
||||
match settings.zone.as_str() {
|
||||
"front" => {
|
||||
self.pid_settings.temperature.front.ki = settings.ki;
|
||||
self.pid_settings.temperature.front.kp = settings.kp;
|
||||
self.pid_settings.temperature.front.kd = settings.kd;
|
||||
}
|
||||
"middle" => {
|
||||
self.pid_settings.temperature.middle.ki = settings.ki;
|
||||
self.pid_settings.temperature.middle.kp = settings.kp;
|
||||
self.pid_settings.temperature.middle.kd = settings.kd;
|
||||
}
|
||||
"back" => {
|
||||
self.pid_settings.temperature.back.ki = settings.ki;
|
||||
self.pid_settings.temperature.back.kp = settings.kp;
|
||||
self.pid_settings.temperature.back.kd = settings.kd;
|
||||
}
|
||||
"nozzle" => {
|
||||
self.pid_settings.temperature.nozzle.ki = settings.ki;
|
||||
self.pid_settings.temperature.nozzle.kp = settings.kp;
|
||||
self.pid_settings.temperature.nozzle.kd = settings.kd;
|
||||
}
|
||||
_ => tracing::warn!("Unknown zone: {}", settings.zone),
|
||||
}
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use smol::channel::Receiver;
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use smol::channel::Sender;
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use crate::{AsyncThreadMessage, Machine};
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use crate::{
|
||||
MachineIdentificationUnique, MachineMessage, machine_identification::MachineIdentification,
|
||||
};
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use std::time::Instant;
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use crate::{
|
||||
MACHINE_EXTRUDER_V1, VENDOR_QITECH,
|
||||
extruder1::{
|
||||
ExtruderV2Mode,
|
||||
api::{
|
||||
ExtruderSettingsState, ExtruderV2Namespace, HeatingStates, InverterStatusState,
|
||||
ModeState, MotorStatusValues, PidSettingsStates, PressureState, RegulationState,
|
||||
RotationState, ScrewState,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Just checking mock-machine feature here to exclude these modules from compilation entirely
|
||||
#[cfg(feature = "mock-machine")]
|
||||
pub mod act;
|
||||
#[cfg(feature = "mock-machine")]
|
||||
pub mod api;
|
||||
#[cfg(feature = "mock-machine")]
|
||||
pub mod mock_emit;
|
||||
#[cfg(feature = "mock-machine")]
|
||||
pub mod new;
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
impl Machine for ExtruderV2 {
|
||||
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
|
||||
self.machine_identification_unique.clone()
|
||||
}
|
||||
|
||||
fn get_main_sender(&self) -> Option<Sender<AsyncThreadMessage>> {
|
||||
self.main_sender.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
#[derive(Debug)]
|
||||
pub struct ExtruderV2 {
|
||||
api_receiver: Receiver<MachineMessage>,
|
||||
api_sender: Sender<MachineMessage>,
|
||||
main_sender: Option<Sender<AsyncThreadMessage>>,
|
||||
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
|
||||
namespace: ExtruderV2Namespace,
|
||||
last_measurement_emit: Instant,
|
||||
last_status_hash: Option<u64>,
|
||||
mode: ExtruderV2Mode,
|
||||
/// Energy tracking for total consumption calculation
|
||||
pub total_energy_kwh: f64,
|
||||
pub last_energy_calculation_time: Option<Instant>,
|
||||
/// will be initalized as false and set to true by `emit_state`
|
||||
/// This way we can signal to the client that the first state emission is a default state
|
||||
emitted_default_state: bool,
|
||||
pub target_pressure: f64,
|
||||
|
||||
pub is_default_state: bool,
|
||||
/// rotation state
|
||||
pub rotation_state: RotationState,
|
||||
/// mode state
|
||||
pub mode_state: ModeState,
|
||||
/// regulation state
|
||||
pub regulation_state: RegulationState,
|
||||
/// pressure state
|
||||
pub pressure_state: PressureState,
|
||||
/// screw state
|
||||
pub screw_state: ScrewState,
|
||||
/// heating states
|
||||
pub heating_states: HeatingStates,
|
||||
/// extruder settings state
|
||||
pub extruder_settings_state: ExtruderSettingsState,
|
||||
/// inverter status state
|
||||
pub inverter_status_state: InverterStatusState,
|
||||
/// pid settings
|
||||
pub pid_settings: PidSettingsStates,
|
||||
|
||||
pub motor_status: MotorStatusValues,
|
||||
/// pressure in bar
|
||||
pub pressure: f64,
|
||||
/// nozzle temperature in celsius
|
||||
pub nozzle_temperature: f64,
|
||||
/// front temperature in celsius
|
||||
pub front_temperature: f64,
|
||||
/// back temperature in celsius
|
||||
pub back_temperature: f64,
|
||||
/// middle temperature in celsius
|
||||
pub middle_temperature: f64,
|
||||
/// nozzle heating power in watts
|
||||
pub nozzle_power: f64,
|
||||
/// front heating power in watts
|
||||
pub front_power: f64,
|
||||
/// back heating power in watts
|
||||
pub back_power: f64,
|
||||
/// middle heating power in watts
|
||||
pub middle_power: f64,
|
||||
/// combined power consumption in watts
|
||||
pub combined_power: f64,
|
||||
|
||||
pub nozzle_heating_allowed: bool,
|
||||
|
||||
pub front_heating_allowed: bool,
|
||||
|
||||
pub back_heating_allowed: bool,
|
||||
|
||||
pub middle_heating_allowed: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
impl ExtruderV2 {
|
||||
pub const MACHINE_IDENTIFICATION: MachineIdentification = MachineIdentification {
|
||||
vendor: VENDOR_QITECH,
|
||||
machine: MACHINE_EXTRUDER_V1,
|
||||
};
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
use crate::{
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait,
|
||||
extruder1::{
|
||||
ExtruderV2Mode,
|
||||
api::{
|
||||
ExtruderSettingsState, ExtruderV2Namespace, HeatingState, HeatingStates,
|
||||
InverterStatusState, ModeState, MotorStatusValues, PidSettings, PidSettingsStates,
|
||||
PressureState, RegulationState, RotationState, ScrewState, TemperaturePid,
|
||||
TemperaturePidStates,
|
||||
},
|
||||
mock::ExtruderV2,
|
||||
},
|
||||
};
|
||||
|
||||
impl MachineNewTrait for ExtruderV2 {
|
||||
fn new(params: &MachineNewParams<'_, '_, '_, '_, '_, '_, '_>) -> Result<Self, anyhow::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// Mock machine can work with either Serial or Ethercat hardware
|
||||
// For the mock machine, we don't need to actually use the hardware
|
||||
// We just validate that we have the expected hardware type
|
||||
match params.hardware {
|
||||
MachineNewHardware::Serial(_) => {
|
||||
// For serial mode, we could potentially use the serial device if needed
|
||||
// but for a mock machine, we'll just note it and proceed
|
||||
}
|
||||
MachineNewHardware::Ethercat(_) => {
|
||||
// For ethercat mode, we could potentially use the ethercat devices
|
||||
// but for a mock machine, we'll just note it and proceed
|
||||
}
|
||||
}
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
let (sender, receiver) = smol::channel::unbounded();
|
||||
|
||||
let mut extruder_mock_machine = Self {
|
||||
main_sender: params.main_thread_channel.clone(),
|
||||
api_receiver: receiver,
|
||||
api_sender: sender,
|
||||
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
|
||||
emitted_default_state: false,
|
||||
last_status_hash: None,
|
||||
total_energy_kwh: 0.0,
|
||||
last_energy_calculation_time: None,
|
||||
is_default_state: false,
|
||||
rotation_state: RotationState { forward: false },
|
||||
mode_state: ModeState {
|
||||
mode: ExtruderV2Mode::Standby,
|
||||
},
|
||||
regulation_state: RegulationState { uses_rpm: true },
|
||||
pressure_state: PressureState {
|
||||
target_bar: 0.0,
|
||||
wiring_error: false,
|
||||
},
|
||||
screw_state: ScrewState { target_rpm: 0.0 },
|
||||
heating_states: HeatingStates {
|
||||
nozzle: HeatingState {
|
||||
target_temperature: 0.0,
|
||||
wiring_error: false,
|
||||
},
|
||||
front: HeatingState {
|
||||
target_temperature: 0.0,
|
||||
wiring_error: false,
|
||||
},
|
||||
back: HeatingState {
|
||||
target_temperature: 0.0,
|
||||
wiring_error: false,
|
||||
},
|
||||
middle: HeatingState {
|
||||
target_temperature: 0.0,
|
||||
wiring_error: false,
|
||||
},
|
||||
},
|
||||
extruder_settings_state: ExtruderSettingsState {
|
||||
pressure_limit: 200.0,
|
||||
pressure_limit_enabled: false,
|
||||
},
|
||||
inverter_status_state: InverterStatusState {
|
||||
running: false,
|
||||
forward_running: true,
|
||||
reverse_running: true,
|
||||
up_to_frequency: false,
|
||||
overload_warning: false,
|
||||
no_function: false,
|
||||
output_frequency_detection: false,
|
||||
abc_fault: false,
|
||||
fault_occurence: false,
|
||||
},
|
||||
pid_settings: PidSettingsStates {
|
||||
temperature: TemperaturePidStates {
|
||||
front: TemperaturePid {
|
||||
ki: 0.1,
|
||||
kp: 0.1,
|
||||
kd: 0.1,
|
||||
zone: String::from("front"),
|
||||
},
|
||||
middle: TemperaturePid {
|
||||
ki: 0.1,
|
||||
kp: 0.1,
|
||||
kd: 0.1,
|
||||
zone: String::from("middle"),
|
||||
},
|
||||
back: TemperaturePid {
|
||||
ki: 0.1,
|
||||
kp: 0.1,
|
||||
kd: 0.1,
|
||||
zone: String::from("back"),
|
||||
},
|
||||
nozzle: TemperaturePid {
|
||||
ki: 0.1,
|
||||
kp: 0.1,
|
||||
kd: 0.1,
|
||||
zone: String::from("nozzle"),
|
||||
},
|
||||
},
|
||||
pressure: PidSettings {
|
||||
ki: 0.0,
|
||||
kp: 0.0,
|
||||
kd: 0.0,
|
||||
},
|
||||
},
|
||||
motor_status: MotorStatusValues {
|
||||
screw_rpm: 0.0,
|
||||
frequency: 0.0,
|
||||
voltage: 0.0,
|
||||
current: 0.0,
|
||||
power: 0.0,
|
||||
},
|
||||
pressure: 0.0,
|
||||
nozzle_temperature: 0.0,
|
||||
front_temperature: 0.0,
|
||||
back_temperature: 0.0,
|
||||
middle_temperature: 0.0,
|
||||
nozzle_power: 0.0,
|
||||
front_power: 0.0,
|
||||
back_power: 0.0,
|
||||
middle_power: 0.0,
|
||||
combined_power: 0.0,
|
||||
nozzle_heating_allowed: false,
|
||||
front_heating_allowed: false,
|
||||
back_heating_allowed: false,
|
||||
middle_heating_allowed: false,
|
||||
target_pressure: 0.0,
|
||||
};
|
||||
|
||||
extruder_mock_machine.emit_state();
|
||||
|
||||
Ok(extruder_mock_machine)
|
||||
}
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use std::time::Instant;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use api::ExtruderV3Namespace;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use smol::channel::Receiver;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use smol::channel::Sender;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use units::electric_current::ampere;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use units::electric_potential::volt;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::MACHINE_EXTRUDER_V2;
|
||||
use crate::{AsyncThreadMessage, Machine};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::{
|
||||
MachineMessage, VENDOR_QITECH,
|
||||
extruder1::{
|
||||
screw_speed_controller::ScrewSpeedController, temperature_controller::TemperatureController,
|
||||
},
|
||||
machine_identification::{MachineIdentification, MachineIdentificationUnique},
|
||||
};
|
||||
|
||||
pub mod act;
|
||||
pub mod api;
|
||||
pub mod emit;
|
||||
pub mod mock;
|
||||
pub mod new;
|
||||
pub mod temperature_controller;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ExtruderV3Mode {
|
||||
Standby,
|
||||
Heat,
|
||||
Extrude,
|
||||
}
|
||||
|
||||
pub enum HeatingType {
|
||||
Nozzle,
|
||||
Front,
|
||||
Back,
|
||||
Middle,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
#[derive(Debug)]
|
||||
pub struct ExtruderV3 {
|
||||
api_receiver: Receiver<MachineMessage>,
|
||||
api_sender: Sender<MachineMessage>,
|
||||
main_sender: Option<Sender<AsyncThreadMessage>>,
|
||||
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
namespace: ExtruderV3Namespace,
|
||||
|
||||
last_measurement_emit: Instant,
|
||||
last_status_hash: Option<u64>,
|
||||
mode: ExtruderV3Mode,
|
||||
|
||||
screw_speed_controller: ScrewSpeedController,
|
||||
temperature_controller_front: TemperatureController,
|
||||
temperature_controller_middle: TemperatureController,
|
||||
temperature_controller_back: TemperatureController,
|
||||
temperature_controller_nozzle: TemperatureController,
|
||||
|
||||
/// Energy tracking for total consumption calculation
|
||||
total_energy_kwh: f64,
|
||||
last_energy_calculation_time: Option<Instant>,
|
||||
|
||||
/// will be initalized as false and set to true by `emit_state`
|
||||
/// This way we can signal to the client that the first state emission is a default state
|
||||
emitted_default_state: bool,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl Machine for ExtruderV3 {
|
||||
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
|
||||
self.machine_identification_unique.clone()
|
||||
}
|
||||
|
||||
fn get_main_sender(&self) -> Option<Sender<AsyncThreadMessage>> {
|
||||
self.main_sender.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl std::fmt::Display for ExtruderV3 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "ExtruderV3")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl ExtruderV3 {
|
||||
pub const MACHINE_IDENTIFICATION: MachineIdentification = MachineIdentification {
|
||||
vendor: VENDOR_QITECH,
|
||||
machine: MACHINE_EXTRUDER_V2,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl ExtruderV3 {
|
||||
/// Calculate combined power consumption in watts
|
||||
fn calculate_combined_power(&mut self) -> f64 {
|
||||
let motor_power = {
|
||||
let motor_status = &self.screw_speed_controller.inverter.motor_status;
|
||||
let voltage = motor_status.voltage.get::<volt>();
|
||||
let current = motor_status.current.get::<ampere>();
|
||||
voltage * current
|
||||
};
|
||||
let nozzle_power = self
|
||||
.temperature_controller_nozzle
|
||||
.get_heating_element_wattage();
|
||||
let front_power = self
|
||||
.temperature_controller_front
|
||||
.get_heating_element_wattage();
|
||||
let back_power = self
|
||||
.temperature_controller_back
|
||||
.get_heating_element_wattage();
|
||||
let middle_power = self
|
||||
.temperature_controller_middle
|
||||
.get_heating_element_wattage();
|
||||
|
||||
motor_power + nozzle_power + front_power + back_power + middle_power
|
||||
}
|
||||
|
||||
/// Update total energy consumption in kWh
|
||||
fn update_total_energy(&mut self, current_power_watts: f64, now: Instant) {
|
||||
if let Some(last_time) = self.last_energy_calculation_time {
|
||||
let time_delta_hours = now.duration_since(last_time).as_secs_f64() / 3600.0;
|
||||
let energy_delta_kwh = (current_power_watts / 1000.0) * time_delta_hours;
|
||||
self.total_energy_kwh += energy_delta_kwh;
|
||||
}
|
||||
self.last_energy_calculation_time = Some(now);
|
||||
}
|
||||
|
||||
// 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();
|
||||
self.temperature_controller_middle.disable();
|
||||
self.temperature_controller_nozzle.disable();
|
||||
}
|
||||
|
||||
fn switch_to_standby(&mut self) {
|
||||
match self.mode {
|
||||
ExtruderV3Mode::Standby => (),
|
||||
ExtruderV3Mode::Heat => {
|
||||
self.turn_heating_off();
|
||||
self.screw_speed_controller.reset_pid();
|
||||
}
|
||||
ExtruderV3Mode::Extrude => {
|
||||
self.turn_heating_off();
|
||||
self.screw_speed_controller.turn_motor_off();
|
||||
self.screw_speed_controller.reset_pid();
|
||||
}
|
||||
};
|
||||
self.mode = ExtruderV3Mode::Standby;
|
||||
}
|
||||
|
||||
fn switch_to_heat(&mut self) {
|
||||
match self.mode {
|
||||
ExtruderV3Mode::Standby => self.enable_heating(),
|
||||
ExtruderV3Mode::Heat => (),
|
||||
ExtruderV3Mode::Extrude => {
|
||||
self.screw_speed_controller.turn_motor_off();
|
||||
self.screw_speed_controller.reset_pid();
|
||||
}
|
||||
}
|
||||
self.mode = ExtruderV3Mode::Heat;
|
||||
}
|
||||
|
||||
fn switch_to_extrude(&mut self) {
|
||||
match self.mode {
|
||||
ExtruderV3Mode::Standby => {
|
||||
self.screw_speed_controller.turn_motor_on();
|
||||
self.enable_heating();
|
||||
self.screw_speed_controller.reset_pid();
|
||||
}
|
||||
ExtruderV3Mode::Heat => {
|
||||
self.screw_speed_controller.turn_motor_on();
|
||||
self.enable_heating();
|
||||
self.screw_speed_controller.reset_pid();
|
||||
}
|
||||
ExtruderV3Mode::Extrude => (),
|
||||
}
|
||||
self.mode = ExtruderV3Mode::Extrude;
|
||||
}
|
||||
|
||||
fn switch_mode(&mut self, mode: ExtruderV3Mode) {
|
||||
if self.mode == mode {
|
||||
return;
|
||||
}
|
||||
|
||||
match mode {
|
||||
ExtruderV3Mode::Standby => self.switch_to_standby(),
|
||||
ExtruderV3Mode::Heat => self.switch_to_heat(),
|
||||
ExtruderV3Mode::Extrude => self.switch_to_extrude(),
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_inverter(&mut self) {
|
||||
self.screw_speed_controller.inverter.reset_inverter();
|
||||
}
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::{MachineNewParams, MachineNewTrait, get_ethercat_device};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use anyhow::Error;
|
||||
|
||||
#[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,
|
||||
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 units::angular_velocity::AngularVelocity;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use units::angular_velocity::revolution_per_minute;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use units::pressure::Pressure;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use units::pressure::bar;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use ethercat_hal::{
|
||||
devices::{
|
||||
el2004::{EL2004, EL2004_IDENTITY_A, EL2004Port},
|
||||
el3021::{EL3021, EL3021_IDENTITY_A, EL3021Port},
|
||||
el3204::{EL3204, EL3204_IDENTITY_A, EL3204_IDENTITY_B, EL3204Port},
|
||||
},
|
||||
io::{
|
||||
analog_input::AnalogInput, digital_output::DigitalOutput,
|
||||
serial_interface::SerialInterface, temperature_input::TemperatureInput,
|
||||
},
|
||||
};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use units::thermodynamic_temperature::{ThermodynamicTemperature, degree_celsius};
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::extruder1::temperature_controller::TemperatureController;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use super::ExtruderV3;
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
impl MachineNewTrait for ExtruderV3 {
|
||||
fn new<'maindevice>(params: &MachineNewParams) -> Result<Self, Error> {
|
||||
// validate general stuff
|
||||
|
||||
use crate::{
|
||||
MachineNewHardware, MachineNewHardwareEthercat, validate_no_role_dublicates,
|
||||
validate_same_machine_identification_unique,
|
||||
};
|
||||
|
||||
let device_identification = params.device_group.to_vec();
|
||||
|
||||
validate_same_machine_identification_unique(&device_identification)?;
|
||||
validate_no_role_dublicates(&device_identification)?;
|
||||
|
||||
let hardware: &&MachineNewHardwareEthercat<'_, '_, '_> = match ¶ms.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
|
||||
smol::block_on(async {
|
||||
// Role 0 - Buscoupler EK1100
|
||||
|
||||
use control_core::transmission::fixed::FixedTransmission;
|
||||
|
||||
use crate::{
|
||||
extruder1::{
|
||||
Heating, mitsubishi_cs80::MitsubishiCS80,
|
||||
screw_speed_controller::ScrewSpeedController,
|
||||
},
|
||||
extruder2::{ExtruderV3Mode, api::ExtruderV3Namespace},
|
||||
};
|
||||
let _ek1100 =
|
||||
get_ethercat_device::<EK1100>(hardware, params, 0, [EK1100_IDENTITY_A].to_vec());
|
||||
let el6021 = {
|
||||
let identities = [
|
||||
EL6021_IDENTITY_A,
|
||||
EL6021_IDENTITY_B,
|
||||
EL6021_IDENTITY_C,
|
||||
EL6021_IDENTITY_D,
|
||||
]
|
||||
.to_vec();
|
||||
let device = get_ethercat_device::<EL6021>(hardware, params, 1, identities).await?;
|
||||
|
||||
device
|
||||
.0
|
||||
.write()
|
||||
.await
|
||||
.write_config(&device.1, &EL6021Configuration::default())
|
||||
.await?;
|
||||
{
|
||||
let mut device_guard = device.0.write().await;
|
||||
device_guard.set_used(true);
|
||||
}
|
||||
device.0
|
||||
};
|
||||
|
||||
let el2004 =
|
||||
get_ethercat_device::<EL2004>(hardware, params, 2, [EL2004_IDENTITY_A].to_vec())
|
||||
.await?
|
||||
.0;
|
||||
|
||||
let el3021 =
|
||||
get_ethercat_device::<EL3021>(hardware, params, 3, [EL3021_IDENTITY_A].to_vec())
|
||||
.await?
|
||||
.0;
|
||||
|
||||
let el3204 = get_ethercat_device::<EL3204>(
|
||||
hardware,
|
||||
params,
|
||||
4,
|
||||
[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);
|
||||
let t3 = TemperatureInput::new(el3204.clone(), EL3204Port::T3);
|
||||
let t4 = TemperatureInput::new(el3204, EL3204Port::T4);
|
||||
|
||||
// For the Relais
|
||||
let digital_out_1 = DigitalOutput::new(el2004.clone(), EL2004Port::DO1);
|
||||
let digital_out_2 = DigitalOutput::new(el2004.clone(), EL2004Port::DO2);
|
||||
let digital_out_3 = DigitalOutput::new(el2004.clone(), EL2004Port::DO3);
|
||||
let digital_out_4 = DigitalOutput::new(el2004.clone(), EL2004Port::DO4);
|
||||
|
||||
let pressure_sensor = AnalogInput::new(el3021, EL3021Port::AI1);
|
||||
// The Extruders temparature Controllers should disable the relais when the max_temperature is reached
|
||||
let extruder_max_temperature = ThermodynamicTemperature::new::<degree_celsius>(300.0);
|
||||
// Only front heating on: These values work 0.08, 0.001, 0.007, Overshoot 0.5 undershoot ~0.7 (Problems when starting far away because of integral)
|
||||
let temperature_controller_front = TemperatureController::new(
|
||||
0.16,
|
||||
0.0,
|
||||
0.008,
|
||||
ThermodynamicTemperature::new::<degree_celsius>(150.0),
|
||||
extruder_max_temperature,
|
||||
t1,
|
||||
digital_out_1,
|
||||
Heating::default(),
|
||||
Duration::from_millis(500),
|
||||
700.0,
|
||||
1.0,
|
||||
);
|
||||
|
||||
// Only front heating on: These values work 0.08, 0.001, 0.007, Overshoot 0.5 undershoot ~0.7 (Problems when starting far away because of integral)
|
||||
let temperature_controller_middle = TemperatureController::new(
|
||||
0.16,
|
||||
0.0,
|
||||
0.008,
|
||||
ThermodynamicTemperature::new::<degree_celsius>(150.0),
|
||||
extruder_max_temperature,
|
||||
t2,
|
||||
digital_out_2,
|
||||
Heating::default(),
|
||||
Duration::from_millis(500),
|
||||
700.0,
|
||||
1.0,
|
||||
);
|
||||
|
||||
// Only front heating on: These values work 0.08, 0.001, 0.007, Overshoot 0.5 undershoot ~0.7 (Problems when starting far away because of integral)
|
||||
let temperature_controller_back = TemperatureController::new(
|
||||
0.16,
|
||||
0.0,
|
||||
0.008,
|
||||
ThermodynamicTemperature::new::<degree_celsius>(150.0),
|
||||
extruder_max_temperature,
|
||||
t3,
|
||||
digital_out_3,
|
||||
Heating::default(),
|
||||
Duration::from_millis(500),
|
||||
700.0,
|
||||
1.0,
|
||||
);
|
||||
|
||||
// Only front heating on: These values work 0.08, 0.001, 0.007, Overshoot 0.5 undershoot ~0.7 (Problems when starting far away because of integral)
|
||||
let temperature_controller_nozzle = TemperatureController::new(
|
||||
0.16,
|
||||
0.0,
|
||||
0.008,
|
||||
ThermodynamicTemperature::new::<degree_celsius>(150.0),
|
||||
extruder_max_temperature,
|
||||
t4,
|
||||
digital_out_4,
|
||||
Heating::default(),
|
||||
Duration::from_millis(500),
|
||||
200.0,
|
||||
0.95,
|
||||
);
|
||||
|
||||
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);
|
||||
|
||||
let screw_speed_controller = ScrewSpeedController::new(
|
||||
inverter,
|
||||
target_pressure,
|
||||
target_rpm,
|
||||
pressure_sensor,
|
||||
FixedTransmission::new(1.0 / 30.0),
|
||||
);
|
||||
let (sender, receiver) = smol::channel::unbounded();
|
||||
|
||||
let mut extruder: ExtruderV3 = Self {
|
||||
main_sender: params.main_thread_channel.clone(),
|
||||
api_receiver: receiver,
|
||||
api_sender: sender,
|
||||
machine_identification_unique: params.get_machine_identification_unique(),
|
||||
namespace: ExtruderV3Namespace {
|
||||
namespace: params.namespace.clone(),
|
||||
},
|
||||
last_measurement_emit: Instant::now(),
|
||||
mode: ExtruderV3Mode::Standby,
|
||||
total_energy_kwh: 0.0,
|
||||
last_energy_calculation_time: None,
|
||||
temperature_controller_front,
|
||||
temperature_controller_middle,
|
||||
temperature_controller_back,
|
||||
temperature_controller_nozzle,
|
||||
screw_speed_controller,
|
||||
emitted_default_state: false,
|
||||
last_status_hash: None,
|
||||
};
|
||||
extruder.emit_state();
|
||||
Ok(extruder)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
use crate::extruder1::Heating;
|
||||
use control_core::controllers::pid::PidController;
|
||||
use ethercat_hal::io::{digital_output::DigitalOutput, temperature_input::TemperatureInput};
|
||||
use std::time::{Duration, Instant};
|
||||
use units::f64::*;
|
||||
use units::thermodynamic_temperature::degree_celsius;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
pub struct TemperatureController {
|
||||
pub pid: PidController,
|
||||
temperature_sensor: TemperatureInput,
|
||||
relais: DigitalOutput,
|
||||
pub heating: Heating,
|
||||
pub target_temp: ThermodynamicTemperature,
|
||||
window_start: Instant,
|
||||
heating_allowed: bool,
|
||||
pwm_period: Duration,
|
||||
max_temperature: ThermodynamicTemperature,
|
||||
temperature_pid_output: f64,
|
||||
heating_element_wattage: f64,
|
||||
max_clamp: f64,
|
||||
}
|
||||
|
||||
impl TemperatureController {
|
||||
pub fn disable(&mut self) {
|
||||
self.relais.set(false);
|
||||
self.heating.heating = false;
|
||||
self.disallow_heating();
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
kp: f64,
|
||||
ki: f64,
|
||||
kd: f64,
|
||||
target_temp: ThermodynamicTemperature,
|
||||
max_temperature: ThermodynamicTemperature,
|
||||
temperature_sensor: TemperatureInput,
|
||||
relais: DigitalOutput,
|
||||
heating: Heating,
|
||||
pwm_duration: Duration,
|
||||
heating_element_wattage: f64,
|
||||
max_clamp: f64,
|
||||
) -> Self {
|
||||
Self {
|
||||
pid: PidController::new(kp, ki, kd),
|
||||
target_temp,
|
||||
window_start: Instant::now(),
|
||||
temperature_sensor,
|
||||
relais,
|
||||
heating,
|
||||
heating_allowed: false,
|
||||
pwm_period: pwm_duration,
|
||||
max_temperature,
|
||||
temperature_pid_output: 0.0,
|
||||
heating_element_wattage,
|
||||
max_clamp,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_target_temperature(&mut self, temp: ThermodynamicTemperature) {
|
||||
self.heating.target_temperature = temp;
|
||||
}
|
||||
|
||||
pub const fn disallow_heating(&mut self) {
|
||||
self.heating_allowed = false;
|
||||
}
|
||||
|
||||
pub const fn allow_heating(&mut self) {
|
||||
self.heating_allowed = true;
|
||||
}
|
||||
|
||||
pub fn get_heating_element_wattage(&mut self) -> f64 {
|
||||
self.temperature_pid_output * self.heating_element_wattage
|
||||
}
|
||||
|
||||
pub fn update(&mut self, now: Instant) {
|
||||
self.temperature_pid_output = 0.0;
|
||||
|
||||
let temperature = self.temperature_sensor.get_temperature();
|
||||
let temperature_celsius = ThermodynamicTemperature::new::<degree_celsius>(
|
||||
temperature.as_ref().unwrap_or(&0.0).to_owned(),
|
||||
);
|
||||
|
||||
self.heating.wiring_error = temperature.is_err();
|
||||
self.heating.temperature = temperature_celsius;
|
||||
|
||||
if self.heating.temperature > self.max_temperature {
|
||||
// disable the relais and return
|
||||
self.relais.set(false);
|
||||
self.heating.heating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if self.heating_allowed {
|
||||
let error: f64 = self.heating.target_temperature.get::<degree_celsius>()
|
||||
- self.heating.temperature.get::<degree_celsius>();
|
||||
|
||||
let control = self.pid.update(error, now); // PID output
|
||||
// Clamp PID output to 0.0 – 1.0 (as duty cycle)
|
||||
let duty = control.clamp(0.0, self.max_clamp);
|
||||
|
||||
self.temperature_pid_output = duty;
|
||||
|
||||
let elapsed = now.duration_since(self.window_start);
|
||||
|
||||
// Restart window if needed
|
||||
if elapsed >= self.pwm_period {
|
||||
self.window_start = now;
|
||||
}
|
||||
// Compare duty cycle to elapsed time
|
||||
let on_time = self.pwm_period.mul_f64(duty);
|
||||
|
||||
// Relay is ON if within duty cycle window
|
||||
let on = elapsed < on_time;
|
||||
self.relais.set(on);
|
||||
self.heating.heating = on;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,12 +57,12 @@ impl MachineAct for LaserMachine {
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
/*Doesnt connec to any Macine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
/*Doesnt connec to any Machine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
|
||||
@@ -19,13 +19,11 @@ pub mod aquapath1;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
pub mod buffer1;
|
||||
pub mod extruder1;
|
||||
pub mod extruder2;
|
||||
pub mod laser;
|
||||
pub mod machine_identification;
|
||||
pub mod mock;
|
||||
pub mod registry;
|
||||
pub mod serial;
|
||||
pub mod test_machine;
|
||||
pub mod winder2;
|
||||
|
||||
pub const VENDOR_QITECH: u16 = 0x0001;
|
||||
@@ -36,8 +34,6 @@ pub const MACHINE_MOCK: u16 = 0x0007;
|
||||
pub const MACHINE_AQUAPATH_V1: u16 = 0x0009;
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
pub const MACHINE_BUFFER_V1: u16 = 0x0008;
|
||||
pub const MACHINE_EXTRUDER_V2: u16 = 0x0016;
|
||||
pub const TEST_MACHINE: u16 = 0x0033;
|
||||
|
||||
use serde_json::Value;
|
||||
use smol::lock::RwLock;
|
||||
|
||||
@@ -48,12 +48,12 @@ impl MachineAct for MockMachine {
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
/*Doesnt connec to any Macine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) =>
|
||||
/*Doesnt connect to any Machine so do nothing*/
|
||||
/*Doesnt connec to any Machine do nothing*/
|
||||
{
|
||||
()
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
#[cfg(feature = "mock-machine")]
|
||||
use crate::{extruder1::mock::ExtruderV2, mock::MockMachine, winder2::mock::Winder2};
|
||||
|
||||
use crate::{
|
||||
Machine, MachineNewParams, extruder1::ExtruderV2, machine_identification::MachineIdentification,
|
||||
};
|
||||
use crate::{Machine, MachineNewParams, machine_identification::MachineIdentification};
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
use crate::{
|
||||
aquapath1::AquaPathV1, buffer1::BufferV1, extruder2::ExtruderV3, laser::LaserMachine,
|
||||
test_machine::TestMachine, winder2::Winder2,
|
||||
aquapath1::AquaPathV1, buffer1::BufferV1, extruder1::ExtruderV2, laser::LaserMachine,
|
||||
winder2::Winder2,
|
||||
};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
@@ -87,11 +85,8 @@ lazy_static! {
|
||||
pub static ref MACHINE_REGISTRY: MachineRegistry = {
|
||||
let mut mc = MachineRegistry::new();
|
||||
mc.register::<Winder2>(Winder2::MACHINE_IDENTIFICATION);
|
||||
|
||||
mc.register::<ExtruderV2>(ExtruderV2::MACHINE_IDENTIFICATION);
|
||||
|
||||
mc.register::<ExtruderV3>(ExtruderV3::MACHINE_IDENTIFICATION);
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
mc.register::<MockMachine>(MockMachine::MACHINE_IDENTIFICATION);
|
||||
|
||||
@@ -103,7 +98,6 @@ lazy_static! {
|
||||
|
||||
#[cfg(not(feature = "mock-machine"))]
|
||||
mc.register::<AquaPathV1>(AquaPathV1::MACHINE_IDENTIFICATION);
|
||||
mc.register::<TestMachine>(TestMachine::MACHINE_IDENTIFICATION);
|
||||
mc
|
||||
};
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
use super::TestMachine;
|
||||
use crate::{MachineAct, MachineMessage};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
impl MachineAct for TestMachine {
|
||||
fn act(&mut self, now: Instant) {
|
||||
if let Ok(msg) = self.api_receiver.try_recv() {
|
||||
self.act_machine_message(msg);
|
||||
}
|
||||
|
||||
if now.duration_since(self.last_state_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||
self.emit_state();
|
||||
self.last_state_emit = now;
|
||||
}
|
||||
}
|
||||
|
||||
fn act_machine_message(&mut self, msg: MachineMessage) {
|
||||
match msg {
|
||||
MachineMessage::SubscribeNamespace(namespace) => {
|
||||
self.namespace.namespace = Some(namespace);
|
||||
self.emit_state();
|
||||
}
|
||||
MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None,
|
||||
MachineMessage::HttpApiJsonRequest(value) => {
|
||||
use crate::MachineApi;
|
||||
let _res = self.api_mutate(value);
|
||||
}
|
||||
MachineMessage::ConnectToMachine(_machine_connection) => {
|
||||
// Does not connect to any Machine; do nothing
|
||||
}
|
||||
MachineMessage::DisconnectMachine(_machine_connection) => {
|
||||
// Does not connect to any Machine; do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
use super::TestMachine;
|
||||
use crate::{MachineApi, MachineMessage};
|
||||
use control_core::socketio::{
|
||||
event::{Event, GenericEvent},
|
||||
namespace::{
|
||||
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_first_and_last_event,
|
||||
},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct StateEvent {
|
||||
pub led_on: [bool; 4],
|
||||
}
|
||||
|
||||
impl StateEvent {
|
||||
pub fn build(&self) -> Event<Self> {
|
||||
Event::new("StateEvent", self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum TestMachineEvents {
|
||||
State(Event<StateEvent>),
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "action", content = "value")]
|
||||
pub enum Mutation {
|
||||
SetLed { index: usize, on: bool },
|
||||
SetAllLeds { on: bool },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TestMachineNamespace {
|
||||
pub namespace: Option<Namespace>,
|
||||
}
|
||||
|
||||
impl NamespaceCacheingLogic<TestMachineEvents> for TestMachineNamespace {
|
||||
fn emit(&mut self, events: TestMachineEvents) {
|
||||
let event = Arc::new(events.event_value());
|
||||
let buffer_fn = events.event_cache_fn();
|
||||
if let Some(ns) = &mut self.namespace {
|
||||
ns.emit(event, &buffer_fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CacheableEvents<TestMachineEvents> for TestMachineEvents {
|
||||
fn event_value(&self) -> GenericEvent {
|
||||
match self {
|
||||
TestMachineEvents::State(event) => event.clone().into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn event_cache_fn(&self) -> CacheFn {
|
||||
cache_first_and_last_event()
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineApi for TestMachine {
|
||||
fn api_get_sender(&self) -> smol::channel::Sender<MachineMessage> {
|
||||
self.api_sender.clone()
|
||||
}
|
||||
|
||||
fn api_mutate(&mut self, request_body: Value) -> Result<(), anyhow::Error> {
|
||||
let mutation: Mutation = serde_json::from_value(request_body)?;
|
||||
match mutation {
|
||||
Mutation::SetLed { index, on } => self.set_led(index, on),
|
||||
Mutation::SetAllLeds { on } => self.set_all_leds(on),
|
||||
}
|
||||
|
||||
for (led, &on) in self.leds.iter().zip(self.led_on.iter()) {
|
||||
led.set(on);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> Option<Namespace> {
|
||||
self.namespace.namespace.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
use crate::machine_identification::{MachineIdentification, MachineIdentificationUnique};
|
||||
use crate::test_machine::api::{StateEvent, TestMachineEvents};
|
||||
use crate::{AsyncThreadMessage, Machine, MachineMessage};
|
||||
use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
use smol::channel::{Receiver, Sender};
|
||||
use std::time::Instant;
|
||||
pub mod act;
|
||||
pub mod api;
|
||||
pub mod new;
|
||||
use crate::test_machine::api::TestMachineNamespace;
|
||||
use crate::{TEST_MACHINE, VENDOR_QITECH};
|
||||
use ethercat_hal::io::digital_output::DigitalOutput;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestMachine {
|
||||
pub api_receiver: Receiver<MachineMessage>,
|
||||
pub api_sender: Sender<MachineMessage>,
|
||||
pub machine_identification_unique: MachineIdentificationUnique,
|
||||
pub namespace: TestMachineNamespace,
|
||||
pub last_state_emit: Instant,
|
||||
pub led_on: [bool; 4],
|
||||
pub main_sender: Option<Sender<AsyncThreadMessage>>,
|
||||
pub leds: [DigitalOutput; 4],
|
||||
}
|
||||
|
||||
impl Machine for TestMachine {
|
||||
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
|
||||
self.machine_identification_unique.clone()
|
||||
}
|
||||
|
||||
fn get_main_sender(&self) -> Option<Sender<AsyncThreadMessage>> {
|
||||
self.main_sender.clone()
|
||||
}
|
||||
}
|
||||
impl TestMachine {
|
||||
pub const MACHINE_IDENTIFICATION: MachineIdentification = MachineIdentification {
|
||||
vendor: VENDOR_QITECH,
|
||||
machine: TEST_MACHINE,
|
||||
};
|
||||
}
|
||||
|
||||
impl TestMachine {
|
||||
pub fn emit_state(&mut self) {
|
||||
let event = StateEvent {
|
||||
led_on: self.led_on,
|
||||
}
|
||||
.build();
|
||||
|
||||
self.namespace.emit(TestMachineEvents::State(event));
|
||||
}
|
||||
|
||||
/// Set the state of a specific LED
|
||||
pub fn set_led(&mut self, index: usize, on: bool) {
|
||||
if index < self.led_on.len() {
|
||||
self.led_on[index] = on;
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
|
||||
/// Set all LEDs at once
|
||||
pub fn set_all_leds(&mut self, on: bool) {
|
||||
self.led_on = [on; 4];
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
use crate::test_machine::TestMachine;
|
||||
use crate::test_machine::api::TestMachineNamespace;
|
||||
use smol::block_on;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::{
|
||||
MachineNewHardware, MachineNewParams, MachineNewTrait, get_ethercat_device,
|
||||
validate_no_role_dublicates, validate_same_machine_identification_unique,
|
||||
};
|
||||
use anyhow::Error;
|
||||
use ethercat_hal::devices::el2004::{EL2004, EL2004_IDENTITY_A, EL2004Port};
|
||||
use ethercat_hal::io::digital_output::DigitalOutput;
|
||||
|
||||
impl MachineNewTrait for TestMachine {
|
||||
fn new<'maindevice>(params: &MachineNewParams) -> Result<Self, Error> {
|
||||
// validate general stuff
|
||||
let device_identification = params
|
||||
.device_group
|
||||
.iter()
|
||||
.map(|device_identification| device_identification.clone())
|
||||
.collect::<Vec<_>>();
|
||||
validate_same_machine_identification_unique(&device_identification)?;
|
||||
validate_no_role_dublicates(&device_identification)?;
|
||||
|
||||
let hardware = match ¶ms.hardware {
|
||||
MachineNewHardware::Ethercat(x) => x,
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"[{}::MachineNewTrait/TestMachine::new] MachineNewHardware is not Ethercat",
|
||||
module_path!()
|
||||
));
|
||||
}
|
||||
};
|
||||
block_on(async {
|
||||
let el2004 =
|
||||
get_ethercat_device::<EL2004>(hardware, params, 1, [EL2004_IDENTITY_A].to_vec())
|
||||
.await?
|
||||
.0;
|
||||
let do1 = DigitalOutput::new(el2004.clone(), EL2004Port::DO1);
|
||||
let do2 = DigitalOutput::new(el2004.clone(), EL2004Port::DO2);
|
||||
let do3 = DigitalOutput::new(el2004.clone(), EL2004Port::DO3);
|
||||
let do4 = DigitalOutput::new(el2004.clone(), EL2004Port::DO4);
|
||||
let (sender, receiver) = smol::channel::unbounded();
|
||||
let mut my_test = Self {
|
||||
api_receiver: receiver,
|
||||
api_sender: sender,
|
||||
machine_identification_unique: params.get_machine_identification_unique(),
|
||||
namespace: TestMachineNamespace {
|
||||
namespace: params.namespace.clone(),
|
||||
},
|
||||
last_state_emit: Instant::now(),
|
||||
led_on: [false; 4],
|
||||
main_sender: params.main_thread_channel.clone(),
|
||||
leds: [do1, do2, do3, do4],
|
||||
};
|
||||
my_test.emit_state();
|
||||
Ok(my_test)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -6,18 +6,19 @@ use crate::{
|
||||
app_state::SharedState,
|
||||
ethercat::config::{MAX_FRAMES, MAX_PDU_DATA, MAX_SUBDEVICES, PDI_LEN},
|
||||
};
|
||||
use control_core::realtime::set_core_affinity;
|
||||
use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
#[cfg(all(target_os = "linux", not(feature = "development-build")))]
|
||||
use control_core::{irq_handling::set_irq_affinity, realtime::set_realtime_priority};
|
||||
use ethercat_hal::debugging::diagnosis_history::get_most_recent_diagnosis_message;
|
||||
use ethercat_hal::devices::devices_from_subdevices;
|
||||
use ethercrab::std::ethercat_now;
|
||||
use ethercrab::{MainDevice, MainDeviceConfig, PduStorage, RetryBehaviour, Timeouts};
|
||||
use machines::machine_identification::{
|
||||
DeviceHardwareIdentification, DeviceHardwareIdentificationEthercat, DeviceIdentification,
|
||||
DeviceIdentificationIdentified, MachineIdentificationUnique, read_device_identifications,
|
||||
};
|
||||
|
||||
use control_core::realtime::set_core_affinity;
|
||||
use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||
use ethercat_hal::devices::devices_from_subdevices;
|
||||
use ethercrab::std::ethercat_now;
|
||||
use ethercrab::{MainDevice, MainDeviceConfig, PduStorage, RetryBehaviour, Timeouts};
|
||||
use machines::registry::{MACHINE_REGISTRY, MachineRegistry};
|
||||
use machines::{Machine, MachineNewHardware, MachineNewHardwareEthercat, MachineNewParams};
|
||||
use smol::channel::Sender;
|
||||
@@ -155,6 +156,7 @@ pub async fn set_ethercat_devices<const MAX_SUBDEVICES: usize, const MAX_PDI: us
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
let _ = shared_state
|
||||
.rt_machine_creation_channel
|
||||
.send(crate::app_state::HotThreadMessage::AddMachines(machines))
|
||||
|
||||
@@ -324,7 +324,7 @@ fn main() {
|
||||
#[cfg(feature = "development-build")]
|
||||
let running = setup_ctrlc_handler();
|
||||
|
||||
const CYCLE_TARGET_TIME: Duration = Duration::from_micros(700);
|
||||
const CYCLE_TARGET_TIME: Duration = Duration::from_micros(300);
|
||||
|
||||
// for the "hot thread"
|
||||
let (sender, receiver) = smol::channel::unbounded();
|
||||
|
||||