Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb3f4e1503 | ||
|
|
850b2d411d | ||
|
|
008efe6ba1 | ||
|
|
3bd248b9fe | ||
|
|
d73cfe7a02 | ||
|
|
6d2279d44b | ||
|
|
728dd6eb26 | ||
|
|
742fd35d5f | ||
|
|
5e1126e896 | ||
|
|
e91719f902 | ||
|
|
0efa9bcb07 | ||
|
|
37d897d240 | ||
|
|
7488e1d97a | ||
|
|
0a87e45901 | ||
|
|
6205189b14 | ||
|
|
9d7da4f0af | ||
|
|
241b24be43 | ||
|
|
aa77d12879 | ||
|
|
78f183a8e5 | ||
|
|
9f6fc8184e | ||
|
|
ec3bdcb1df | ||
|
|
30292650bc | ||
|
|
1e9bcf9b19 | ||
|
|
1e88979ced |
No files matched your search
@@ -1,3 +1,15 @@
|
|||||||
|
# `2.14.1`
|
||||||
|
_26.01.2026_
|
||||||
|
|
||||||
|
## Laser
|
||||||
|
-[#1079](https://github.com/qitechgmbh/control/pull/1079) Fixed Overflowing of buffer for Graphs causing crashes
|
||||||
|
|
||||||
|
# `2.14.0`
|
||||||
|
_22.01.2026_
|
||||||
|
|
||||||
|
## General
|
||||||
|
- [#1040](https://github.com/qitechgmbh/control/pull/1040) Added API for reading Machine Data
|
||||||
|
|
||||||
# `2.13.0`
|
# `2.13.0`
|
||||||
_07.01.2026_
|
_07.01.2026_
|
||||||
|
|
||||||
@@ -5,7 +17,6 @@ _07.01.2026_
|
|||||||
- [#925](https://github.com/qitechgmbh/control/pull/925) Switch to Setup when selected machine is disconnected
|
- [#925](https://github.com/qitechgmbh/control/pull/925) Switch to Setup when selected machine is disconnected
|
||||||
- [#937](https://github.com/qitechgmbh/control/pull/937) Added minimal Support for WAGO
|
- [#937](https://github.com/qitechgmbh/control/pull/937) Added minimal Support for WAGO
|
||||||
- [#945](https://github.com/qitechgmbh/control/pull/945) Improvements for On-Screen Keyboard usage in Ethercat Setup Page
|
- [#945](https://github.com/qitechgmbh/control/pull/945) Improvements for On-Screen Keyboard usage in Ethercat Setup Page
|
||||||
- []
|
|
||||||
|
|
||||||
## Extruder V3
|
## Extruder V3
|
||||||
- [#930](https://github.com/qitechgmbh/control/pull/930) Added Extruder V3
|
- [#930](https://github.com/qitechgmbh/control/pull/930) Added Extruder V3
|
||||||
|
|||||||
@@ -17,22 +17,22 @@ QiTech Control combines the modularity and reliability of standard EtherCAT term
|
|||||||
|
|
||||||
# Repo Structure
|
# Repo Structure
|
||||||
|
|
||||||
Frontend
|
**Frontend**
|
||||||
|
|
||||||
- `/electron`: Frontend code for the control software built with React and Electron.
|
- `/electron`: Frontend code for the control software built with React and Electron.
|
||||||
|
|
||||||
Backend
|
**Backend**
|
||||||
|
|
||||||
- `/server`: Glue between Beckhoff and Electron. Implements machine logic.
|
- `/server`: Glue between Beckhoff and Electron. Implements machine logic.
|
||||||
- `/ethercat-hal`: Hardware abstraction layer for Beckhoff (and possibly other EtherCat) devices and protocols.
|
- `/ethercat-hal`: Hardware abstraction layer for Beckhoff (and possibly other EtherCat) devices and protocols.
|
||||||
- `/ethercat-hal-derive`: Macros for `ethercat-hal`
|
- `/ethercat-hal-derive`: Macros for `ethercat-hal`
|
||||||
- `/control-core`: Core control logic for the server.
|
- `/control-core`: Core control logic for the server.
|
||||||
|
|
||||||
Operating System
|
**Operating System**
|
||||||
|
|
||||||
- `/nixos`: Custom Linux with realtime kernel & preconfigured for touchscreens.
|
- `/nixos`: Custom Linux with realtime kernel & preconfigured for touchscreens.
|
||||||
|
|
||||||
Other
|
**Other**
|
||||||
|
|
||||||
- `/docs`: Documentation for the project.
|
- `/docs`: Documentation for the project.
|
||||||
|
|
||||||
@@ -54,115 +54,57 @@ We could combine the code of the frontend and backend using [Doxius](https://dio
|
|||||||
|
|
||||||
[Developer Documentation](./docs/developer-docs/)
|
[Developer Documentation](./docs/developer-docs/)
|
||||||
|
|
||||||
## Backend
|
[API Documentation](https://github.com/qitechgmbh/control/blob/master/docs/rest-api.md)
|
||||||
|
|
||||||
|
-> Backend
|
||||||
- Rust stable 1.86^ toolchain (install via [rustup](https://rustup.rs/))
|
- Rust stable 1.86^ toolchain (install via [rustup](https://rustup.rs/))
|
||||||
- `rust-analyzer` extension for VSCode
|
- `rust-analyzer` extension for VSCode
|
||||||
- Set your interface in `server/src/ethercat/init.rs` like `en10`
|
- Set your interface in `server/src/ethercat/init.rs` like `en10`
|
||||||
- Connect a Beckhoff EK1100 to your interface
|
- Connect a Beckhoff EK1100 to your interface
|
||||||
- run `cd server && cargo run` to start the server (localhost:3001)
|
- run `cd server && cargo run` to start the server (localhost:3001)
|
||||||
|
|
||||||
## Frontend
|
-> Frontend
|
||||||
|
|
||||||
- nodejs and npm installed
|
- nodejs and npm installed
|
||||||
- run `cd electron && npm i && npm run start` to start the frontend
|
- run `cd electron && npm i && npm run start` to start the frontend
|
||||||
|
|
||||||
# Machines
|
|
||||||
|
|
||||||
| Machine Type | Version | Release Date | Description | Change to Previous Version | Vendor ID | Machine ID | Implemented | Docs |
|
## Minimal Hardware Examples
|
||||||
| ------------ | ------- | ------------ | --------------------------- | ------------------------------------------------------ | -------------------------- | ---------- | ----------- | ------------------------------- |
|
|
||||||
| Winder | V1 | ??? | Winding Filaments & Similar | - | 1 (Qitech Industries GmbH) | 1 | Reserved | - |
|
|
||||||
| Winder | V2 | 2025 | Winding Filaments & Similar | Reengineered Traverse | 1 (Qitech Industries GmbH) | 2 | Yes | [](./docs/machines/winder-1.md) |
|
|
||||||
| Extruder | V1 | ??? | Single Screw Extruder | - | 1 (Qitech Industries GmbH) | 3 | Reserved | - |
|
|
||||||
| Extruder | V2 | 2025 | Single Screw Extruder | PT100 Thermometers, Optional Additional Heating Zone 4 | 1 (Qitech Industries GmbH) | 4 | Yes | |
|
|
||||||
| Waterway | V1 | 2025 | Filament Water Cooling | - | 1 (Qitech Industries GmbH) | 5 | In Progress | |
|
|
||||||
| Laser | V1 | ??? | Diameter Measuring Laser | - | 1 (Qitech Industries GmbH) | 6 | Yes | |
|
|
||||||
| Mock | - | ??? | Mock Machine for Testing | - | 1 (Qitech Industries GmbH) | 7 | Yes | - |
|
|
||||||
|
|
||||||
# Minimal Hardware Examples
|
|
||||||
|
|
||||||
For complete step-by-step tutorials on setting up your first hardware, including wiring diagrams and software configuration, see the [Getting Started Guide](./docs/developer-docs/getting-started.md#minimal-hardware-examples).
|
For complete step-by-step tutorials on setting up your first hardware, including wiring diagrams and software configuration, see the [Getting Started Guide](./docs/developer-docs/getting-started.md#minimal-hardware-examples).
|
||||||
|
|
||||||
Available examples:
|
| Example | Vendor | Hardware | Description | How-To Video | Docs |
|
||||||
- **LED Control with EL2004** - Digital output control (simplest setup)
|
| :----------------- | :------- | :------------ | :-------------------------------------- | :----------- | :---------------------------- |
|
||||||
- **Analog Input with EL3021** - Reading analog current measurements
|
| **Digital Input** | WAGO | 750-402 | 4-Channel Digital Input | [ ] | - |
|
||||||
|
| **Digital Output** | WAGO | 750-753 | 8-Channel Digital Output | [ ] | - |
|
||||||
|
| **Analog Input** | WAGO | 750-455 | 4-Channel Analog Input | [ ] | - |
|
||||||
|
| **Analog Output** | WAGO | 750-553 | 4-Channel Analog Output | [ ] | - |
|
||||||
|
| **Serial Comms** | WAGO | 750-652 | RS-485 Modbus Module | [ ] | - |
|
||||||
|
| **Stepper Drive** | WAGO | 750-67x | Stepper Controller | [ ] | - |
|
||||||
|
| **Power Supply** | WAGO | 2787-214 | Power Supply Unit | [ ] | - |
|
||||||
|
| **LED Control** | Beckhoff | EL2004 | Digital output control (simplest setup) | [ ] | [](./docs/hardware/el2004.md) |
|
||||||
|
| **Analog Input** | Beckhoff | EL3021 | Reading analog current measurements | [ ] | [](./docs/hardware/el3021.md) |
|
||||||
|
|
||||||
|
# QiTech Machines
|
||||||
|
|
||||||
# More Docs
|
| Machine Name | Description | Machine ID | Implemented | Video | Docs |
|
||||||
|
| :---------------- | :-------------------------- | :--------- | :---------- | :-------------------------------------------------------------- | :------------------------------ |
|
||||||
|
| Winder V1 | Winding Filaments & Similar | 1 | Legacy | [Video](https://youtu.be/4aE4cFhioKA?si=Xdj_LVnFrAYWnLm6) | - |
|
||||||
|
| Winder V2 | Winding Filaments & Similar | 2 | Yes | [Video](https://youtu.be/f2kzh6kpQWE?si=HEYcLOaC9gWSp2Wo) | [](./docs/machines/winder-1.md) |
|
||||||
|
| XL Winder V1 | Large Scale Winder | 3 | Yes | [Video](https://youtu.be/ynI6ioWIQQY?si=4EDVKXbgIqrvGGtY) | - |
|
||||||
|
| Buffer V1 | Filament buffering system | 4 | In Progress | [Video](https://youtu.be/VR5mEdZDPA0?si=eip7dQXU4zaXK7Ev) | - |
|
||||||
|
| Extruder V1 | Single Screw Extruder | 5 | Legacy | [Video](https://youtu.be/gchoG-yGczI?si=bEM4daf5eOVaU5_2) | - |
|
||||||
|
| Extruder V2 | Single Screw Extruder | 6 | Yes | [Video](https://youtu.be/mexRYGDNWa4?si=h7JBz_XKMwKLPenG) | |
|
||||||
|
| Waterway V1 | Filament Water Cooling | 7 | In Progress | [Video](https://youtu.be/_T5z1J8bl_k?si=uOC1hIQ1EAP0cHIF) | |
|
||||||
|
| Laser V1 | Diameter Measuring Laser | 8 | Yes | [Video](https://youtu.be/WDM34lj4afM?si=MZzUKkHCrzH4P0aA) | |
|
||||||
|
| 2-Axis-Laser V1 | Dual Axis Laser | 9 | Yes | [Video](https://youtu.be/WDM34lj4afM?si=MZzUKkHCrzH4P0aA) | - |
|
||||||
|
| Mock | Mock Machine for Testing | 10 | Yes | | - |
|
||||||
|
| Extruder V3 | Single Screw Extruder | 11 | Yes | [Video](https://youtu.be/ipHHuPzCvn4?si=hkZ1b93rVuwDxhFD) | |
|
||||||
|
| Mini Schredder V1 | mini plastic crusher | 12 | Legacy | [Video](https://youtu.be/m8NplNqdu2Q?si=x6zjDktJtpbSMSmu) | - |
|
||||||
|
| Pro Schredder V1 | large plastic crusher | 13 | Legacy | [Video](https://youtu.be/pSaVMqp06pU?si=y61enutRpxKscBm4) | - |
|
||||||
|
| Dryer V1 | polymer dryer | 14 | Reserved | [Video](https://youtu.be/6hdmUUAdZp0?si=eKOW1WlgkqTpnfdS) | - |
|
||||||
|
| Pelletizier V1 | Filament chopper | 15 | Reserved | | - |
|
||||||
|
|
||||||
- [x] [Architecture & Data Flow](./docs/architecture-overview.md)
|
# Current Restructuring Efforts
|
||||||
|
|
||||||
- [x] Example Winder V2
|
[](https://www.youtube.com/live/UlbnSVIhfLI?si=ZTotC5B7gd87tUim)
|
||||||
|
*Video: Live stream of software dev meeting*
|
||||||
- [ ] Electron
|
|
||||||
|
|
||||||
- Folder Structure
|
|
||||||
- Routing with TanStack Router
|
|
||||||
- Design with Tailwind & Shadcn
|
|
||||||
- ...
|
|
||||||
|
|
||||||
- [ ] Interfacing with Electron/Server
|
|
||||||
|
|
||||||
- [ ] SocketIO
|
|
||||||
- Machine Namespace
|
|
||||||
- Main Namespace
|
|
||||||
- [ ] REST
|
|
||||||
- Machine Mutations
|
|
||||||
- Write Device Identification
|
|
||||||
|
|
||||||
- Server
|
|
||||||
|
|
||||||
- [x] [Threading](./docs/control-loop.md)
|
|
||||||
- [x] [Logging](./docs/logging.md)
|
|
||||||
- [ ] Control Loop Setup
|
|
||||||
- Control Loop Thread
|
|
||||||
- [ ] realtime
|
|
||||||
- Maindevice
|
|
||||||
- Group
|
|
||||||
- Extracting Device Identifications
|
|
||||||
- Identifying Groups
|
|
||||||
- Validating Machines
|
|
||||||
- Run Control Loop
|
|
||||||
- [x] [Control Loop](./docs/control-loop.md)
|
|
||||||
- [x] [Machine/Device Identification](./docs/identification.md)
|
|
||||||
- [ ] Machines
|
|
||||||
- When to create a new Machine?
|
|
||||||
- Versioning
|
|
||||||
- Code sharing
|
|
||||||
- Creating/Validating a Machine
|
|
||||||
- Validation
|
|
||||||
- Configuration
|
|
||||||
- [ ] Machine Implementation Guide
|
|
||||||
- Link: How to create a Device
|
|
||||||
- Link: How to create an Actor
|
|
||||||
- Link: How to create a Machine
|
|
||||||
- API (SocketIO + REST)
|
|
||||||
- Creation/Validation Logic
|
|
||||||
- Optional/Mandatory Devices
|
|
||||||
- Validate Devices
|
|
||||||
- Business Logic
|
|
||||||
- Link: How to create Machine Abstraction (Like Traverse/Puller/...)
|
|
||||||
- Forward `act` in winder.
|
|
||||||
|
|
||||||
- [ ] Control Core
|
|
||||||
|
|
||||||
- [x] [Actors](./docs/actors.md)
|
|
||||||
- [ ] SocketIO
|
|
||||||
- Namespaces & Caching
|
|
||||||
- Joining leaving namespaces
|
|
||||||
- NamespaceId
|
|
||||||
- Caching
|
|
||||||
- Serverside Caching
|
|
||||||
- Clientside Caching
|
|
||||||
- [ ] REST
|
|
||||||
|
|
||||||
- [x] Ethercat HAL
|
|
||||||
|
|
||||||
- [x] [Devices](./docs/devices.md)
|
|
||||||
- [x] [Configuration (CoE)](./docs/coe.md)
|
|
||||||
- [x] [IO](./docs/io.md)
|
|
||||||
- [x] [PDO](./docs/pdo.md)
|
|
||||||
|
|
||||||
- [x] [Ethercat Basics](./docs/ethercat-basics.md)
|
|
||||||
|
|
||||||
- [x] [NixOS Operating System](./docs/nixos/README.md)
|
|
||||||
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 6.6 MiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 5.2 MiB |
|
After Width: | Height: | Size: 4.3 MiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 5.9 MiB |
|
After Width: | Height: | Size: 203 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 5.4 MiB |
|
After Width: | Height: | Size: 5.9 MiB |
@@ -91,3 +91,83 @@ To get started with actual hardware, check out these step-by-step tutorials:
|
|||||||
- **[Analog Input with EL3021](./minimal-example-el3021.md)** - Reading analog current measurements
|
- **[Analog Input with EL3021](./minimal-example-el3021.md)** - Reading analog current measurements
|
||||||
|
|
||||||
These examples provide complete hardware wiring diagrams and software setup instructions.
|
These examples provide complete hardware wiring diagrams and software setup instructions.
|
||||||
|
|
||||||
|
|
||||||
|
# More Docs
|
||||||
|
|
||||||
|
- [x] [Architecture & Data Flow](./docs/architecture-overview.md)
|
||||||
|
|
||||||
|
- [x] Example Winder V2
|
||||||
|
|
||||||
|
- [ ] Electron
|
||||||
|
|
||||||
|
- Folder Structure
|
||||||
|
- Routing with TanStack Router
|
||||||
|
- Design with Tailwind & Shadcn
|
||||||
|
- ...
|
||||||
|
|
||||||
|
- [ ] Interfacing with Electron/Server
|
||||||
|
|
||||||
|
- [ ] SocketIO
|
||||||
|
- Machine Namespace
|
||||||
|
- Main Namespace
|
||||||
|
- [ ] REST
|
||||||
|
- Machine Mutations
|
||||||
|
- Write Device Identification
|
||||||
|
|
||||||
|
- Server
|
||||||
|
|
||||||
|
- [x] [Threading](./docs/control-loop.md)
|
||||||
|
- [x] [Logging](./docs/logging.md)
|
||||||
|
- [ ] Control Loop Setup
|
||||||
|
- Control Loop Thread
|
||||||
|
- [ ] realtime
|
||||||
|
- Maindevice
|
||||||
|
- Group
|
||||||
|
- Extracting Device Identifications
|
||||||
|
- Identifying Groups
|
||||||
|
- Validating Machines
|
||||||
|
- Run Control Loop
|
||||||
|
- [x] [Control Loop](./docs/control-loop.md)
|
||||||
|
- [x] [Machine/Device Identification](./docs/identification.md)
|
||||||
|
- [ ] Machines
|
||||||
|
- When to create a new Machine?
|
||||||
|
- Versioning
|
||||||
|
- Code sharing
|
||||||
|
- Creating/Validating a Machine
|
||||||
|
- Validation
|
||||||
|
- Configuration
|
||||||
|
- [ ] Machine Implementation Guide
|
||||||
|
- Link: How to create a Device
|
||||||
|
- Link: How to create an Actor
|
||||||
|
- Link: How to create a Machine
|
||||||
|
- API (SocketIO + REST)
|
||||||
|
- Creation/Validation Logic
|
||||||
|
- Optional/Mandatory Devices
|
||||||
|
- Validate Devices
|
||||||
|
- Business Logic
|
||||||
|
- Link: How to create Machine Abstraction (Like Traverse/Puller/...)
|
||||||
|
- Forward `act` in winder.
|
||||||
|
|
||||||
|
- [ ] Control Core
|
||||||
|
|
||||||
|
- [x] [Actors](./docs/actors.md)
|
||||||
|
- [ ] SocketIO
|
||||||
|
- Namespaces & Caching
|
||||||
|
- Joining leaving namespaces
|
||||||
|
- NamespaceId
|
||||||
|
- Caching
|
||||||
|
- Serverside Caching
|
||||||
|
- Clientside Caching
|
||||||
|
- [ ] REST
|
||||||
|
|
||||||
|
- [x] Ethercat HAL
|
||||||
|
|
||||||
|
- [x] [Devices](./docs/devices.md)
|
||||||
|
- [x] [Configuration (CoE)](./docs/coe.md)
|
||||||
|
- [x] [IO](./docs/io.md)
|
||||||
|
- [x] [PDO](./docs/pdo.md)
|
||||||
|
|
||||||
|
- [x] [Ethercat Basics](./docs/ethercat-basics.md)
|
||||||
|
|
||||||
|
- [x] [NixOS Operating System](./docs/nixos/README.md)
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
# Minimal Example — LED Control on the WAGO750-402
|
||||||
|
A complete hardware + software walkthrough
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
1. [Introduction](#1-introduction)
|
||||||
|
2. [Requirements](#2-requirements)
|
||||||
|
3. [Hardware Setup](#3-hardware-setup)
|
||||||
|
4. [Software Setup](#4-software-setup)
|
||||||
|
5. [Demo](#5-demo)
|
||||||
|
6. [References](#6-references)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Introduction
|
||||||
|
The WAGO 750-402 Display Example is a minimal demonstation showing how to display the digital inputs on a WAGO 750-402 EtherCAT terminal using the QiTech machine framework.
|
||||||
|
It represents the simplest possible hardware interaction in the system:
|
||||||
|
Displaying the Digital Inputs using the QiTech Control Dashboard.
|
||||||
|
## 2. Requirements
|
||||||
|
|
||||||
|
### Hardware
|
||||||
|
- WAGO **2787-2144 EtherCAT**
|
||||||
|
- WAGO **750-354 EtherCAT Coupler**
|
||||||
|
- WAGO **750-602 EtherCAT Power Terminal**
|
||||||
|
- WAGO **750-402 EtherCAT Terminal** (4.channle digital input)
|
||||||
|
- WAGO **750-600 EtherCAT Endmodule**
|
||||||
|
- WAGO **852-1322 Ethernet-Switch**
|
||||||
|
- **24V DC Lab power supply**(AC/DC adapter)
|
||||||
|
- A **Linux PC** (Ubuntu/Debian recommended)
|
||||||
|
- Standard Ethernet cable
|
||||||
|
- Flat screwdriver
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Software
|
||||||
|
*(Installation steps in Section 4)*
|
||||||
|
|
||||||
|
- Rust tollchain
|
||||||
|
- Node-js + npm
|
||||||
|
- Git
|
||||||
|
- QiTech Control repository
|
||||||
|
- EtherCAT HAL *(include inside repo)*
|
||||||
|
|
||||||
|
## 3. Hardware Setup
|
||||||
|
|
||||||
|
### 3.1 Schematic
|
||||||
|
|
||||||
|

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

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

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

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

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

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

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

|
||||||
|
|
||||||
|
## 6. References
|
||||||
|
This guide incorporates information from official WAGO documentation.
|
||||||
|
All diagrams, product names, and figures belong to Beckhoff Automation GmbH & Co. KG and are used here solely for educational purposes.
|
||||||
|
|
||||||
|
Referenced Manuals
|
||||||
|
|
||||||
|
[WAGO 750-402 Documentation](https://www.wago.com/de/io-systeme/4-kanal-digitaleingang/p/750-402#downloads)
|
||||||
|
|
||||||
|
[WAGO 750-354 Documentation](https://www.wago.com/de/io-systeme/feldbuskoppler-ethercat/p/750-354#details)
|
||||||
|
|
||||||
|
[WAGO 750-602 Documentation](https://www.wago.com/de/io-systeme/potentialeinspeisung/p/750-602#details)
|
||||||
|
|
||||||
|
[WAGO 750-354 Documentation](https://www.wago.com/de/io-systeme/endmodul/p/750-600#details)
|
||||||
@@ -162,7 +162,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
|
||||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
"@babel/generator": "^7.28.5",
|
"@babel/generator": "^7.28.5",
|
||||||
@@ -659,7 +658,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
@@ -683,7 +681,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
@@ -3283,7 +3280,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.141.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.141.1.tgz",
|
||||||
"integrity": "sha512-pLQ6ZFCh5s86ewZIAu2wQc2svf+DqttD7CFd1NPSxdEU20KvMMj9RPQdDHj/pbf2VvB3i1nEDxjli/Z0PBvnCQ==",
|
"integrity": "sha512-pLQ6ZFCh5s86ewZIAu2wQc2svf+DqttD7CFd1NPSxdEU20KvMMj9RPQdDHj/pbf2VvB3i1nEDxjli/Z0PBvnCQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/history": "1.141.0",
|
"@tanstack/history": "1.141.0",
|
||||||
"@tanstack/react-store": "^0.8.0",
|
"@tanstack/react-store": "^0.8.0",
|
||||||
@@ -3374,7 +3370,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.141.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.141.1.tgz",
|
||||||
"integrity": "sha512-fR1GGpp6v3dVKu4KIAjEh+Sd0qGLQd/wvCOVHeopSY6aFidXKCzwrS5cBOBqoPPWTKmn6CdW1a0CzFr5Furdog==",
|
"integrity": "sha512-fR1GGpp6v3dVKu4KIAjEh+Sd0qGLQd/wvCOVHeopSY6aFidXKCzwrS5cBOBqoPPWTKmn6CdW1a0CzFr5Furdog==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/history": "1.141.0",
|
"@tanstack/history": "1.141.0",
|
||||||
"@tanstack/store": "^0.8.0",
|
"@tanstack/store": "^0.8.0",
|
||||||
@@ -3595,7 +3590,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
@@ -3761,7 +3757,6 @@
|
|||||||
"integrity": "sha512-gqkrWUsS8hcm0r44yn7/xZeV1ERva/nLgrLxFRUGb7aoNMIJfZJ3AC261zDQuOAKC7MiXai1WCpYc48jAHoShQ==",
|
"integrity": "sha512-gqkrWUsS8hcm0r44yn7/xZeV1ERva/nLgrLxFRUGb7aoNMIJfZJ3AC261zDQuOAKC7MiXai1WCpYc48jAHoShQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.16.0"
|
"undici-types": "~7.16.0"
|
||||||
}
|
}
|
||||||
@@ -3778,7 +3773,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
|
||||||
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
@@ -3789,7 +3783,6 @@
|
|||||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.2.0"
|
"@types/react": "^19.2.0"
|
||||||
}
|
}
|
||||||
@@ -3877,7 +3870,6 @@
|
|||||||
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
|
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.49.0",
|
"@typescript-eslint/scope-manager": "8.49.0",
|
||||||
"@typescript-eslint/types": "8.49.0",
|
"@typescript-eslint/types": "8.49.0",
|
||||||
@@ -4236,7 +4228,6 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -4300,6 +4291,7 @@
|
|||||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
@@ -4614,7 +4606,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -4997,8 +4988,7 @@
|
|||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/data-urls": {
|
"node_modules/data-urls": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
@@ -5271,7 +5261,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/dom-helpers": {
|
"node_modules/dom-helpers": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
@@ -5817,7 +5808,6 @@
|
|||||||
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -5878,7 +5868,6 @@
|
|||||||
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"eslint-config-prettier": "bin/cli.js"
|
"eslint-config-prettier": "bin/cli.js"
|
||||||
},
|
},
|
||||||
@@ -5980,7 +5969,6 @@
|
|||||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
@@ -6939,7 +6927,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.28.4"
|
"@babel/runtime": "^7.28.4"
|
||||||
},
|
},
|
||||||
@@ -6973,7 +6960,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
|
||||||
"integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==",
|
"integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/immer"
|
"url": "https://opencollective.com/immer"
|
||||||
@@ -7576,7 +7562,6 @@
|
|||||||
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
|
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cssstyle": "^4.2.1",
|
"cssstyle": "^4.2.1",
|
||||||
"data-urls": "^5.0.0",
|
"data-urls": "^5.0.0",
|
||||||
@@ -8110,6 +8095,7 @@
|
|||||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"lz-string": "bin/bin.js"
|
"lz-string": "bin/bin.js"
|
||||||
}
|
}
|
||||||
@@ -9529,7 +9515,6 @@
|
|||||||
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
|
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
@@ -9646,6 +9631,7 @@
|
|||||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": "^5.0.1",
|
"ansi-regex": "^5.0.1",
|
||||||
"ansi-styles": "^5.0.0",
|
"ansi-styles": "^5.0.0",
|
||||||
@@ -9661,6 +9647,7 @@
|
|||||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
},
|
},
|
||||||
@@ -9750,7 +9737,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -9760,7 +9746,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
@@ -9773,7 +9758,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.68.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.68.0.tgz",
|
||||||
"integrity": "sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q==",
|
"integrity": "sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
},
|
},
|
||||||
@@ -9816,7 +9800,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/react-lifecycles-compat": {
|
"node_modules/react-lifecycles-compat": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
@@ -10326,11 +10311,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/seroval": {
|
"node_modules/seroval": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.4.2.tgz",
|
||||||
"integrity": "sha512-BdrNXdzlofomLTiRnwJTSEAaGKyHHZkbMXIywOh7zlzp4uZnXErEwl9XZ+N1hJSNpeTtNxWvVwN0wUzAIQ4Hpg==",
|
"integrity": "sha512-N3HEHRCZYn3cQbsC4B5ldj9j+tHdf4JZoYPlcI4rRYu0Xy4qN8MQf1Z08EibzB0WpgRG5BGK08FTrmM66eSzKQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
@@ -10585,6 +10569,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz",
|
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz",
|
||||||
"integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==",
|
"integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.1.0",
|
"csstype": "^3.1.0",
|
||||||
"seroval": "~1.3.0",
|
"seroval": "~1.3.0",
|
||||||
@@ -10606,6 +10591,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.3.tgz",
|
||||||
"integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==",
|
"integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
},
|
},
|
||||||
@@ -10930,8 +10916,7 @@
|
|||||||
"version": "4.1.18",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
||||||
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss-animate": {
|
"node_modules/tailwindcss-animate": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
@@ -11020,7 +11005,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -11284,7 +11268,6 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -11621,7 +11604,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.4.4",
|
"fdir": "^6.4.4",
|
||||||
@@ -11734,8 +11716,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vite-plugin-electron-renderer/-/vite-plugin-electron-renderer-0.14.6.tgz",
|
"resolved": "https://registry.npmjs.org/vite-plugin-electron-renderer/-/vite-plugin-electron-renderer-0.14.6.tgz",
|
||||||
"integrity": "sha512-oqkWFa7kQIkvHXG7+Mnl1RTroA4sP0yesKatmAy0gjZC4VwUqlvF9IvOpHd1fpLWsqYX/eZlVxlhULNtaQ78Jw==",
|
"integrity": "sha512-oqkWFa7kQIkvHXG7+Mnl1RTroA4sP0yesKatmAy0gjZC4VwUqlvF9IvOpHd1fpLWsqYX/eZlVxlhULNtaQ78Jw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/fdir": {
|
"node_modules/vite/node_modules/fdir": {
|
||||||
"version": "6.5.0",
|
"version": "6.5.0",
|
||||||
@@ -11773,7 +11754,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -12208,7 +12188,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz",
|
||||||
"integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==",
|
"integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { ControlCard } from "@/control/ControlCard";
|
||||||
|
import { Page } from "@/components/Page";
|
||||||
|
import React from "react";
|
||||||
|
import { Label } from "@/control/Label";
|
||||||
|
import { useDigitalInputTestMachine } from "./useDigitalInputTestMachine";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { ControlGrid } from "@/control/ControlGrid";
|
||||||
|
import { SelectionGroup } from "@/control/SelectionGroup";
|
||||||
|
import { toastError } from "@/components/Toast";
|
||||||
|
|
||||||
|
export function DigitalInputTestMachineControlPage() {
|
||||||
|
const { state } = useDigitalInputTestMachine();
|
||||||
|
|
||||||
|
const safeState = state ?? { led_on: [false, false, false, false] };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page>
|
||||||
|
<ControlCard title="Machine LEDs">
|
||||||
|
<div className="grid grid-cols-2 gap-6">
|
||||||
|
{safeState.led_on.map((led, index) => (
|
||||||
|
<Label key={index} label={`LED ${index + 1}`}>
|
||||||
|
<Badge variant={led ? "outline" : "destructive"}>
|
||||||
|
{led ? "On" : "OFF"}
|
||||||
|
</Badge>
|
||||||
|
</Label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ControlCard>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { Topbar } from "@/components/Topbar";
|
||||||
|
import { digitalInputTestMachineSerialRoute } from "@/routes/routes";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export function DigitalInputTestMachinePage() {
|
||||||
|
const { serial } = digitalInputTestMachineSerialRoute.useParams();
|
||||||
|
return (
|
||||||
|
<Topbar
|
||||||
|
pathname={`/_sidebar/machines/digitalInputTestMachine/${serial}`}
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
link: "control",
|
||||||
|
activeLink: "control",
|
||||||
|
title: "Control",
|
||||||
|
icon: "lu:CirclePlay",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { StoreApi } from "zustand";
|
||||||
|
import { create } from "zustand";
|
||||||
|
import { z } from "zod";
|
||||||
|
import {
|
||||||
|
EventHandler,
|
||||||
|
eventSchema,
|
||||||
|
Event,
|
||||||
|
handleUnhandledEventError,
|
||||||
|
NamespaceId,
|
||||||
|
createNamespaceHookImplementation,
|
||||||
|
ThrottledStoreUpdater,
|
||||||
|
} from "@/client/socketioStore";
|
||||||
|
import { MachineIdentificationUnique } from "@/machines/types";
|
||||||
|
|
||||||
|
// ========== Event Schema ==========
|
||||||
|
|
||||||
|
export const stateEventDataSchema = z.object({
|
||||||
|
led_on: z.array(z.boolean()).length(4),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const stateEventSchema = eventSchema(stateEventDataSchema);
|
||||||
|
|
||||||
|
export type StateEvent = z.infer<typeof stateEventDataSchema>;
|
||||||
|
|
||||||
|
// ========== Store ==========
|
||||||
|
export type DigitalTestMachineNamespaceStore = {
|
||||||
|
state: StateEvent | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createTestMachineNamespaceStore =
|
||||||
|
(): StoreApi<DigitalTestMachineNamespaceStore> =>
|
||||||
|
create<DigitalTestMachineNamespaceStore>(() => ({
|
||||||
|
state: null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// ========== Message Handler ==========
|
||||||
|
export function digitalInputTestMachineMessageHandler(
|
||||||
|
store: StoreApi<DigitalTestMachineNamespaceStore>,
|
||||||
|
throttledUpdater: ThrottledStoreUpdater<DigitalTestMachineNamespaceStore>,
|
||||||
|
): EventHandler {
|
||||||
|
return (event: Event<any>) => {
|
||||||
|
const updateStore = (
|
||||||
|
updater: (
|
||||||
|
state: DigitalTestMachineNamespaceStore,
|
||||||
|
) => DigitalTestMachineNamespaceStore,
|
||||||
|
) => throttledUpdater.updateWith(updater);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (event.name === "StateEvent") {
|
||||||
|
const parsed = stateEventSchema.parse(event);
|
||||||
|
updateStore(() => ({ state: parsed.data }));
|
||||||
|
} else {
|
||||||
|
handleUnhandledEventError(event.name);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error processing ${event.name}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Namespace Hook ==========
|
||||||
|
const useDigitalInputTestMachineNamespaceImplementation =
|
||||||
|
createNamespaceHookImplementation<DigitalTestMachineNamespaceStore>({
|
||||||
|
createStore: createTestMachineNamespaceStore,
|
||||||
|
createEventHandler: digitalInputTestMachineMessageHandler,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function useDigitalInputTestMachineNamespace(
|
||||||
|
machine_identification_unique: MachineIdentificationUnique,
|
||||||
|
): DigitalTestMachineNamespaceStore {
|
||||||
|
const namespaceId: NamespaceId = {
|
||||||
|
type: "machine",
|
||||||
|
machine_identification_unique,
|
||||||
|
};
|
||||||
|
|
||||||
|
return useDigitalInputTestMachineNamespaceImplementation(namespaceId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { Toast, toastError } from "@/components/Toast";
|
||||||
|
import { useStateOptimistic } from "@/lib/useStateOptimistic";
|
||||||
|
import { digitalInputTestMachineSerialRoute } from "@/routes/routes";
|
||||||
|
import { MachineIdentificationUnique } from "@/machines/types";
|
||||||
|
import {
|
||||||
|
useDigitalInputTestMachineNamespace,
|
||||||
|
StateEvent,
|
||||||
|
} from "./digitalInputTestMachineNamespace";
|
||||||
|
import { useMachineMutate } from "@/client/useClient";
|
||||||
|
import { produce } from "immer";
|
||||||
|
import { useEffect, useMemo } from "react";
|
||||||
|
import { digitalInputTestMachine } from "@/machines/properties";
|
||||||
|
import { z } from "zod";
|
||||||
|
export function useDigitalInputTestMachine() {
|
||||||
|
const { serial: serialString } =
|
||||||
|
digitalInputTestMachineSerialRoute.useParams();
|
||||||
|
|
||||||
|
// Memoize machine identification
|
||||||
|
const machineIdentification: MachineIdentificationUnique = useMemo(() => {
|
||||||
|
const serial = parseInt(serialString);
|
||||||
|
|
||||||
|
if (isNaN(serial)) {
|
||||||
|
toastError(
|
||||||
|
"Invalid Serial Number",
|
||||||
|
`"${serialString}" is not a valid serial number.`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
machine_identification: { vendor: 0, machine: 0 },
|
||||||
|
serial: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
machine_identification: digitalInputTestMachine.machine_identification,
|
||||||
|
serial,
|
||||||
|
};
|
||||||
|
}, [serialString]);
|
||||||
|
|
||||||
|
// Namespace state from backend
|
||||||
|
const { state } = useDigitalInputTestMachineNamespace(machineIdentification);
|
||||||
|
|
||||||
|
// Optimistic state
|
||||||
|
const stateOptimistic = useStateOptimistic<StateEvent>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (state) stateOptimistic.setReal(state);
|
||||||
|
}, [state, stateOptimistic]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
state: stateOptimistic.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -66,19 +66,14 @@ export type Laser1NamespaceStore = {
|
|||||||
roundness: TimeSeries;
|
roundness: TimeSeries;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Constants for time durations
|
const { initialTimeSeries: diameter, insert: addDiameter } =
|
||||||
const HALF_SECOND = 500;
|
createTimeSeries({ sampleIntervalLong: 500 });
|
||||||
|
|
||||||
const { initialTimeSeries: diameter, insert: addDiameter } = createTimeSeries({
|
|
||||||
sampleIntervalLong: HALF_SECOND,
|
|
||||||
});
|
|
||||||
const { initialTimeSeries: x_diameter, insert: addXDiameter } =
|
const { initialTimeSeries: x_diameter, insert: addXDiameter } =
|
||||||
createTimeSeries({ sampleIntervalLong: HALF_SECOND });
|
createTimeSeries({ sampleIntervalLong: 500 });
|
||||||
const { initialTimeSeries: y_diameter, insert: addYDiameter } =
|
const { initialTimeSeries: y_diameter, insert: addYDiameter } =
|
||||||
createTimeSeries({ sampleIntervalLong: HALF_SECOND });
|
createTimeSeries({ sampleIntervalLong: 500 });
|
||||||
const { initialTimeSeries: roundness, insert: addRoundness } = createTimeSeries(
|
const { initialTimeSeries: roundness, insert: addRoundness } =
|
||||||
{ sampleIntervalLong: HALF_SECOND },
|
createTimeSeries({ sampleIntervalLong: 500 });
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory function to create a new Laser1 namespace store
|
* Factory function to create a new Laser1 namespace store
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export type Mock1NamespaceStore = {
|
|||||||
sineWave3: TimeSeries;
|
sineWave3: TimeSeries;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { initialTimeSeries: sineWave, insert: addSineWave } = createTimeSeries();
|
const { initialTimeSeries: sineWave, insert: addSineWave } = createTimeSeries({ retentionDurationShort: 300, sampleIntervalLong: 500 });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory function to create a new Mock1 namespace store
|
* Factory function to create a new Mock1 namespace store
|
||||||
|
|||||||
@@ -462,6 +462,17 @@ export const testmachine: MachineProperties = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
export const digitalInputTestMachine: MachineProperties = {
|
||||||
|
name: "Digital Input Machine",
|
||||||
|
version: "V1",
|
||||||
|
slug: "digitalInputTestMachine",
|
||||||
|
icon: "lu:Disc3",
|
||||||
|
machine_identification: {
|
||||||
|
vendor: VENDOR_QITECH,
|
||||||
|
machine: 0x0040,
|
||||||
|
},
|
||||||
|
device_roles: [],
|
||||||
|
};
|
||||||
export const wagoPower1: MachineProperties = {
|
export const wagoPower1: MachineProperties = {
|
||||||
name: "WAGO Power",
|
name: "WAGO Power",
|
||||||
version: "V1",
|
version: "V1",
|
||||||
@@ -569,6 +580,7 @@ export const machineProperties: MachineProperties[] = [
|
|||||||
testmachine,
|
testmachine,
|
||||||
analogInputTestMachine,
|
analogInputTestMachine,
|
||||||
wagoAiTestMachine,
|
wagoAiTestMachine,
|
||||||
|
digitalInputTestMachine,
|
||||||
ip20TestMachine,
|
ip20TestMachine,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -215,14 +215,15 @@ export type Winder2NamespaceStore = {
|
|||||||
|
|
||||||
//Store Factory and Message Handler -> no param, so default values
|
//Store Factory and Message Handler -> no param, so default values
|
||||||
const { initialTimeSeries: spoolProgress, insert: addSpoolProgress } =
|
const { initialTimeSeries: spoolProgress, insert: addSpoolProgress } =
|
||||||
createTimeSeries();
|
createTimeSeries({ sampleIntervalLong: 500 });
|
||||||
const { initialTimeSeries: traversePosition, insert: addTraversePosition } =
|
const { initialTimeSeries: traversePosition, insert: addTraversePosition } =
|
||||||
createTimeSeries();
|
createTimeSeries({ sampleIntervalLong: 500 });
|
||||||
const { initialTimeSeries: pullerSpeed, insert: addPullerSpeed } =
|
const { initialTimeSeries: pullerSpeed, insert: addPullerSpeed } =
|
||||||
createTimeSeries();
|
createTimeSeries({ sampleIntervalLong: 500 });
|
||||||
const { initialTimeSeries: spoolRpm, insert: addSpoolRpm } = createTimeSeries();
|
const { initialTimeSeries: spoolRpm, insert: addSpoolRpm } =
|
||||||
|
createTimeSeries({ sampleIntervalLong: 500 });
|
||||||
const { initialTimeSeries: tensionArmAngle, insert: addTensionArmAngle } =
|
const { initialTimeSeries: tensionArmAngle, insert: addTensionArmAngle } =
|
||||||
createTimeSeries();
|
createTimeSeries({ sampleIntervalLong: 500 });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory function to create a new Winder2 namespace store
|
* Factory function to create a new Winder2 namespace store
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ import { AnalogInputTestMachine } from "@/machines/analoginputtestmachine/Analog
|
|||||||
import { AnalogInputTestMachineControl } from "@/machines/analoginputtestmachine/AnalogInputTestMachineControlPage";
|
import { AnalogInputTestMachineControl } from "@/machines/analoginputtestmachine/AnalogInputTestMachineControlPage";
|
||||||
import { WagoAiTestMachine } from "@/machines/wagoaitestmachine/WagoAiTestMachinePage";
|
import { WagoAiTestMachine } from "@/machines/wagoaitestmachine/WagoAiTestMachinePage";
|
||||||
import { WagoAiTestMachineControl } from "@/machines/wagoaitestmachine/WagoAiTestMachineControlPage";
|
import { WagoAiTestMachineControl } from "@/machines/wagoaitestmachine/WagoAiTestMachineControlPage";
|
||||||
|
|
||||||
|
import { DigitalInputTestMachinePage } from "@/machines/digitalinputtestmachine/DigitalInputTestMachinePage";
|
||||||
|
import { DigitalInputTestMachineControlPage } from "@/machines/digitalinputtestmachine/DigitalInputTestMachineControlPage";
|
||||||
|
|
||||||
import { IP20TestMachinePage } from "@/machines/ip20testmachine/IP20TestMachinePage";
|
import { IP20TestMachinePage } from "@/machines/ip20testmachine/IP20TestMachinePage";
|
||||||
import { IP20TestMachineControlPage } from "@/machines/ip20testmachine/IP20TestMachineControlPage";
|
import { IP20TestMachineControlPage } from "@/machines/ip20testmachine/IP20TestMachineControlPage";
|
||||||
|
|
||||||
@@ -114,6 +118,18 @@ export const wagoAiTestMachineControlRoute = createRoute({
|
|||||||
component: () => <WagoAiTestMachineControl />,
|
component: () => <WagoAiTestMachineControl />,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const digitalInputTestMachineSerialRoute = createRoute({
|
||||||
|
getParentRoute: () => machinesRoute,
|
||||||
|
path: "digitalInputTestMachine/$serial",
|
||||||
|
component: () => <DigitalInputTestMachinePage />,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const digitalInputTestMachineControlRoute = createRoute({
|
||||||
|
getParentRoute: () => digitalInputTestMachineSerialRoute,
|
||||||
|
path: "control",
|
||||||
|
component: () => <DigitalInputTestMachineControlPage />,
|
||||||
|
});
|
||||||
|
|
||||||
export const ip20TestMachineSerialRoute = createRoute({
|
export const ip20TestMachineSerialRoute = createRoute({
|
||||||
getParentRoute: () => machinesRoute,
|
getParentRoute: () => machinesRoute,
|
||||||
path: "ip20testmachine/$serial",
|
path: "ip20testmachine/$serial",
|
||||||
@@ -462,6 +478,10 @@ export const rootTree = RootRoute.addChildren([
|
|||||||
|
|
||||||
wagoAiTestMachineSerialRoute.addChildren([wagoAiTestMachineControlRoute]),
|
wagoAiTestMachineSerialRoute.addChildren([wagoAiTestMachineControlRoute]),
|
||||||
|
|
||||||
|
digitalInputTestMachineSerialRoute.addChildren([
|
||||||
|
digitalInputTestMachineControlRoute,
|
||||||
|
]),
|
||||||
|
|
||||||
ip20TestMachineSerialRoute.addChildren([ip20TestMachineControlRoute]),
|
ip20TestMachineSerialRoute.addChildren([ip20TestMachineControlRoute]),
|
||||||
|
|
||||||
aquapath1SerialRoute.addChildren([
|
aquapath1SerialRoute.addChildren([
|
||||||
|
|||||||
@@ -15,7 +15,13 @@ import {
|
|||||||
machineProperties,
|
machineProperties,
|
||||||
VENDOR_QITECH,
|
VENDOR_QITECH,
|
||||||
} from "@/machines/properties";
|
} from "@/machines/properties";
|
||||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
@@ -150,6 +156,15 @@ export function DeviceEeepromDialogContent({ device, setOpen }: ContentProps) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateNumpadPosition = useCallback(() => {
|
||||||
|
if (!numpadOpen || !dialogRef.current) return;
|
||||||
|
const rect = dialogRef.current.getBoundingClientRect();
|
||||||
|
setNumpadPosition({
|
||||||
|
left: rect.right + 20,
|
||||||
|
top: rect.top + rect.height / 2,
|
||||||
|
});
|
||||||
|
}, [numpadOpen]);
|
||||||
|
|
||||||
const machinePreset = useMemo(() => {
|
const machinePreset = useMemo(() => {
|
||||||
if (!values.machine) return;
|
if (!values.machine) return;
|
||||||
return getMachineProperties({
|
return getMachineProperties({
|
||||||
@@ -187,12 +202,7 @@ export function DeviceEeepromDialogContent({ device, setOpen }: ContentProps) {
|
|||||||
|
|
||||||
// Position numpad once when it opens
|
// Position numpad once when it opens
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!numpadOpen || !dialogRef.current) return;
|
updateNumpadPosition();
|
||||||
const rect = dialogRef.current.getBoundingClientRect();
|
|
||||||
setNumpadPosition({
|
|
||||||
left: rect.right + 20,
|
|
||||||
top: rect.top + rect.height / 2,
|
|
||||||
});
|
|
||||||
}, [numpadOpen]);
|
}, [numpadOpen]);
|
||||||
|
|
||||||
// Close numpad when clicking outside input/numpad
|
// Close numpad when clicking outside input/numpad
|
||||||
@@ -226,6 +236,14 @@ export function DeviceEeepromDialogContent({ device, setOpen }: ContentProps) {
|
|||||||
}
|
}
|
||||||
}, [numpadOpen]);
|
}, [numpadOpen]);
|
||||||
|
|
||||||
|
// Update numpad position whenever the window resizes
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener("resize", updateNumpadPosition);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("resize", updateNumpadPosition);
|
||||||
|
};
|
||||||
|
}, [updateNumpadPosition]);
|
||||||
|
|
||||||
// Numpad handlers for serial input
|
// Numpad handlers for serial input
|
||||||
const numpadHandlers = useMemo(() => {
|
const numpadHandlers = useMemo(() => {
|
||||||
const ensureFocus = () => {
|
const ensureFocus = () => {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use crate::{
|
|||||||
devices::{
|
devices::{
|
||||||
DynamicEthercatDevice, Module,
|
DynamicEthercatDevice, Module,
|
||||||
wago_modules::{
|
wago_modules::{
|
||||||
|
wago_750_402::{WAGO_750_402_MODULE_IDENT, WAGO_750_402_PRODUCT_ID},
|
||||||
wago_750_455::{WAGO_750_455_MODULE_IDENT, WAGO_750_455_PRODUCT_ID},
|
wago_750_455::{WAGO_750_455_MODULE_IDENT, WAGO_750_455_PRODUCT_ID},
|
||||||
wago_750_501::{WAGO_750_501_MODULE_IDENT, WAGO_750_501_PRODUCT_ID},
|
wago_750_501::{WAGO_750_501_MODULE_IDENT, WAGO_750_501_PRODUCT_ID},
|
||||||
wago_750_530::{WAGO_750_530_MODULE_IDENT, WAGO_750_530_PRODUCT_ID},
|
wago_750_530::{WAGO_750_530_MODULE_IDENT, WAGO_750_530_PRODUCT_ID},
|
||||||
@@ -245,6 +246,10 @@ impl Wago750_354 {
|
|||||||
module.has_tx = true;
|
module.has_tx = true;
|
||||||
module.has_rx = true;
|
module.has_rx = true;
|
||||||
}
|
}
|
||||||
|
WAGO_750_402_PRODUCT_ID => {
|
||||||
|
module.has_tx = true;
|
||||||
|
module.has_rx = false;
|
||||||
|
}
|
||||||
_ => println!(
|
_ => println!(
|
||||||
"Wago-750-354 found Unknown/Unimplemented Module: {}",
|
"Wago-750-354 found Unknown/Unimplemented Module: {}",
|
||||||
ident_iom
|
ident_iom
|
||||||
@@ -294,6 +299,9 @@ impl Wago750_354 {
|
|||||||
WAGO_750_652_MODULE_IDENT => {
|
WAGO_750_652_MODULE_IDENT => {
|
||||||
Arc::new(RwLock::new(wago_750_652::Wago750_652::new()))
|
Arc::new(RwLock::new(wago_750_652::Wago750_652::new()))
|
||||||
}
|
}
|
||||||
|
WAGO_750_402_MODULE_IDENT => {
|
||||||
|
Arc::new(RwLock::new(wago_750_402::Wago750_402::new()))
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
println!(
|
println!(
|
||||||
"{} Missing Implementation for Module Identification: vendor_id: {:?}, module ident: {:?} !",
|
"{} Missing Implementation for Module Identification: vendor_id: {:?}, module ident: {:?} !",
|
||||||
|
|||||||
@@ -430,6 +430,9 @@ impl IP20EcDi8Do8 {
|
|||||||
wago_750_652::WAGO_750_652_MODULE_IDENT => {
|
wago_750_652::WAGO_750_652_MODULE_IDENT => {
|
||||||
Arc::new(RwLock::new(wago_750_652::Wago750_652::new()))
|
Arc::new(RwLock::new(wago_750_652::Wago750_652::new()))
|
||||||
}
|
}
|
||||||
|
wago_750_402::WAGO_750_402_MODULE_IDENT => {
|
||||||
|
Arc::new(RwLock::new(wago_750_402::Wago750_402::new()))
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pub mod ip20_ec_di8_do8;
|
pub mod ip20_ec_di8_do8;
|
||||||
pub mod wago_750_1506;
|
pub mod wago_750_1506;
|
||||||
|
pub mod wago_750_402;
|
||||||
pub mod wago_750_455;
|
pub mod wago_750_455;
|
||||||
pub mod wago_750_501;
|
pub mod wago_750_501;
|
||||||
pub mod wago_750_530;
|
pub mod wago_750_530;
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
use crate::{
|
||||||
|
devices::{
|
||||||
|
DynamicEthercatDevice, EthercatDevice, EthercatDeviceProcessing, EthercatDeviceUsed,
|
||||||
|
EthercatDynamicPDO, Module, NewEthercatDevice, SubDeviceProductTuple,
|
||||||
|
},
|
||||||
|
io::digital_input::{DigitalInputDevice, DigitalInputInput},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Wago750_402InputPort {
|
||||||
|
DI1,
|
||||||
|
DI2,
|
||||||
|
DI3,
|
||||||
|
DI4,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Wago750_402InputPort> for usize {
|
||||||
|
fn from(value: Wago750_402InputPort) -> Self {
|
||||||
|
match value {
|
||||||
|
Wago750_402InputPort::DI1 => 0,
|
||||||
|
Wago750_402InputPort::DI2 => 1,
|
||||||
|
Wago750_402InputPort::DI3 => 2,
|
||||||
|
Wago750_402InputPort::DI4 => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct Wago750_402TxPdo {
|
||||||
|
port1: bool,
|
||||||
|
port2: bool,
|
||||||
|
port3: bool,
|
||||||
|
port4: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DigitalInputDevice<Wago750_402InputPort> for Wago750_402 {
|
||||||
|
fn get_input(&self, port: Wago750_402InputPort) -> Result<DigitalInputInput, anyhow::Error> {
|
||||||
|
Ok(DigitalInputInput {
|
||||||
|
value: match port {
|
||||||
|
Wago750_402InputPort::DI1 => self.tx_pdo.port1,
|
||||||
|
Wago750_402InputPort::DI2 => self.tx_pdo.port2,
|
||||||
|
Wago750_402InputPort::DI3 => self.tx_pdo.port3,
|
||||||
|
Wago750_402InputPort::DI4 => self.tx_pdo.port4,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Wago750_402 {
|
||||||
|
is_used: bool,
|
||||||
|
tx_bit_offset: usize,
|
||||||
|
// Should always be on but not when calling a constructor
|
||||||
|
module: Option<Module>,
|
||||||
|
tx_pdo: Wago750_402TxPdo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynamicEthercatDevice for Wago750_402 {}
|
||||||
|
|
||||||
|
impl EthercatDynamicPDO for Wago750_402 {
|
||||||
|
fn get_tx_offset(&self) -> usize {
|
||||||
|
self.tx_bit_offset
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rx_offset(&self) -> usize {
|
||||||
|
0 //this device has no rx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_tx_offset(&mut self, offset: usize) {
|
||||||
|
self.tx_bit_offset = offset
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_rx_offset(&mut self, _offset: usize) {
|
||||||
|
// it does nothing because the device has no rx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EthercatDeviceUsed for Wago750_402 {
|
||||||
|
fn is_used(&self) -> bool {
|
||||||
|
self.is_used
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_used(&mut self, used: bool) {
|
||||||
|
self.is_used = used;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Wago750_402 {}
|
||||||
|
|
||||||
|
impl EthercatDevice for Wago750_402 {
|
||||||
|
// input_len 48 offset: 40
|
||||||
|
fn input(
|
||||||
|
&mut self,
|
||||||
|
input: &bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||||
|
) -> Result<(), anyhow::Error> {
|
||||||
|
let base = self.tx_bit_offset;
|
||||||
|
|
||||||
|
let idx1 = base + Into::<usize>::into(Wago750_402InputPort::DI1);
|
||||||
|
self.tx_pdo.port1 = *input.get(idx1).expect("Bit 1 out of bounds");
|
||||||
|
|
||||||
|
let idx2 = base + Into::<usize>::into(Wago750_402InputPort::DI2);
|
||||||
|
self.tx_pdo.port2 = *input.get(idx2).expect("Bit 2 out of bounds");
|
||||||
|
|
||||||
|
let idx3 = base + Into::<usize>::into(Wago750_402InputPort::DI3);
|
||||||
|
self.tx_pdo.port3 = *input.get(idx3).expect("Bit 3 out of bounds");
|
||||||
|
|
||||||
|
let idx4 = base + Into::<usize>::into(Wago750_402InputPort::DI4);
|
||||||
|
self.tx_pdo.port4 = *input.get(idx4).expect("Bit 4 out of bounds");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_len(&self) -> usize {
|
||||||
|
4
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output(
|
||||||
|
&self,
|
||||||
|
_output: &mut bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||||
|
) -> Result<(), anyhow::Error> {
|
||||||
|
// println!("output: {} {}",self.rx_bit_offset,_output);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_len(&self) -> usize {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_module(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_checked(
|
||||||
|
&mut self,
|
||||||
|
_input: &bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||||
|
) -> Result<(), anyhow::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_checked(
|
||||||
|
&self,
|
||||||
|
_output: &mut bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
|
||||||
|
) -> Result<(), anyhow::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_module(&self) -> Option<Module> {
|
||||||
|
self.module.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_module(&mut self, module: Module) {
|
||||||
|
self.tx_bit_offset = module.tx_offset;
|
||||||
|
self.module = Some(module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EthercatDeviceProcessing for Wago750_402 {
|
||||||
|
fn input_post_process(&mut self) -> Result<(), anyhow::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_pre_process(&mut self) -> Result<(), anyhow::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NewEthercatDevice for Wago750_402 {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
is_used: false,
|
||||||
|
tx_bit_offset: 0,
|
||||||
|
module: None,
|
||||||
|
tx_pdo: Wago750_402TxPdo::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Wago750_402 {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "Wago750_402")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const WAGO_750_402_VENDOR_ID: u32 = 0x00000021;
|
||||||
|
pub const WAGO_750_402_PRODUCT_ID: u32 = 2147483713;
|
||||||
|
pub const WAGO_750_402_MODULE_IDENT: SubDeviceProductTuple =
|
||||||
|
(WAGO_750_402_VENDOR_ID, WAGO_750_402_PRODUCT_ID);
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
use super::DigitalInputTestMachine;
|
||||||
|
use crate::{MachineAct, MachineMessage, MachineValues};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
impl MachineAct for DigitalInputTestMachine {
|
||||||
|
fn act(&mut self, now: Instant) {
|
||||||
|
if let Ok(msg) = self.api_receiver.try_recv() {
|
||||||
|
self.act_machine_message(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if now.duration_since(self.last_state_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
||||||
|
self.emit_state();
|
||||||
|
self.last_state_emit = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn act_machine_message(&mut self, msg: MachineMessage) {
|
||||||
|
match msg {
|
||||||
|
MachineMessage::SubscribeNamespace(namespace) => {
|
||||||
|
self.namespace.namespace = Some(namespace);
|
||||||
|
self.emit_state();
|
||||||
|
}
|
||||||
|
MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None,
|
||||||
|
MachineMessage::HttpApiJsonRequest(value) => {
|
||||||
|
use crate::MachineApi;
|
||||||
|
let _res = self.api_mutate(value);
|
||||||
|
}
|
||||||
|
MachineMessage::ConnectToMachine(_machine_connection) => {
|
||||||
|
// Does not connect to any Machine; do nothing
|
||||||
|
}
|
||||||
|
MachineMessage::DisconnectMachine(_machine_connection) => {
|
||||||
|
// Does not connect to any Machine; do nothing
|
||||||
|
}
|
||||||
|
MachineMessage::RequestValues(sender) => {
|
||||||
|
sender
|
||||||
|
.send_blocking(MachineValues {
|
||||||
|
state: serde_json::to_value(self.get_state())
|
||||||
|
.expect("Failed to serialize state"),
|
||||||
|
live_values: serde_json::Value::Null,
|
||||||
|
})
|
||||||
|
.expect("Failed to send values");
|
||||||
|
sender.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use control_core::socketio::{
|
||||||
|
event::{Event, GenericEvent},
|
||||||
|
namespace::{
|
||||||
|
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_first_and_last_event,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{MachineApi, digital_input_test_machine::DigitalInputTestMachine};
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, Clone)]
|
||||||
|
pub struct StateEvent {
|
||||||
|
pub led_on: [bool; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StateEvent {
|
||||||
|
pub fn build(&self) -> Event<Self> {
|
||||||
|
Event::new("StateEvent", self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub enum DigitalInputTestMachineEvents {
|
||||||
|
State(Event<StateEvent>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(tag = "action", content = "value")]
|
||||||
|
pub enum Mutation {
|
||||||
|
SetLed { index: usize, on: bool },
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DigitalInputTestMachineNamespace {
|
||||||
|
pub namespace: Option<Namespace>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NamespaceCacheingLogic<DigitalInputTestMachineEvents> for DigitalInputTestMachineNamespace {
|
||||||
|
fn emit(&mut self, events: DigitalInputTestMachineEvents) {
|
||||||
|
let event = Arc::new(events.event_value());
|
||||||
|
let buffer_fn = events.event_cache_fn();
|
||||||
|
if let Some(ns) = &mut self.namespace {
|
||||||
|
ns.emit(event, &buffer_fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CacheableEvents<DigitalInputTestMachineEvents> for DigitalInputTestMachineEvents {
|
||||||
|
fn event_value(&self) -> GenericEvent {
|
||||||
|
match self {
|
||||||
|
DigitalInputTestMachineEvents::State(event) => event.clone().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event_cache_fn(&self) -> CacheFn {
|
||||||
|
cache_first_and_last_event()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MachineApi for DigitalInputTestMachine {
|
||||||
|
fn api_get_sender(&self) -> smol::channel::Sender<crate::MachineMessage> {
|
||||||
|
self.api_sender.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn api_mutate(&mut self, _value: serde_json::Value) -> Result<(), anyhow::Error> {
|
||||||
|
//Digital Input Test Machine does not Set Values
|
||||||
|
// let mutation: Mutation = serde_json::from_value(value)?;
|
||||||
|
// match mutation {
|
||||||
|
// Mutation::SetLed { index, on } => self.set_led(index, on),
|
||||||
|
// }
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn api_event_namespace(&mut self) -> Option<Namespace> {
|
||||||
|
self.namespace.namespace.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use control_core::socketio::namespace::NamespaceCacheingLogic;
|
||||||
|
use ethercat_hal::io::digital_input::DigitalInput;
|
||||||
|
use smol::channel::{Receiver, Sender};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
AsyncThreadMessage, DIGITAL_INPUT_TEST_MACHINE, Machine, MachineMessage, VENDOR_QITECH,
|
||||||
|
digital_input_test_machine::api::{
|
||||||
|
DigitalInputTestMachineEvents, DigitalInputTestMachineNamespace, StateEvent,
|
||||||
|
},
|
||||||
|
machine_identification::{MachineIdentification, MachineIdentificationUnique},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod act;
|
||||||
|
pub mod api;
|
||||||
|
pub mod new;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DigitalInputTestMachine {
|
||||||
|
pub api_receiver: Receiver<MachineMessage>,
|
||||||
|
pub api_sender: Sender<MachineMessage>,
|
||||||
|
pub machine_identification_unique: MachineIdentificationUnique,
|
||||||
|
pub main_sender: Option<Sender<AsyncThreadMessage>>,
|
||||||
|
pub namespace: DigitalInputTestMachineNamespace,
|
||||||
|
pub last_state_emit: Instant,
|
||||||
|
pub led_on: [bool; 4],
|
||||||
|
pub digital_input: [DigitalInput; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Machine for DigitalInputTestMachine {
|
||||||
|
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
|
||||||
|
self.machine_identification_unique.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_main_sender(&self) -> Option<Sender<AsyncThreadMessage>> {
|
||||||
|
self.main_sender.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DigitalInputTestMachine {
|
||||||
|
pub const MACHINE_IDENTIFICATION: MachineIdentification = MachineIdentification {
|
||||||
|
vendor: VENDOR_QITECH,
|
||||||
|
machine: DIGITAL_INPUT_TEST_MACHINE,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn get_state(&self) -> StateEvent {
|
||||||
|
StateEvent {
|
||||||
|
led_on: self.led_on,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emit_state(&mut self) {
|
||||||
|
//Set Led_On after DigitalInput
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
for di in &self.digital_input {
|
||||||
|
let value = match di.get_value() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => false,
|
||||||
|
};
|
||||||
|
self.led_on[i] = value;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let event = StateEvent {
|
||||||
|
led_on: self.led_on,
|
||||||
|
}
|
||||||
|
.build();
|
||||||
|
self.namespace
|
||||||
|
.emit(DigitalInputTestMachineEvents::State(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
use std::{sync::Arc, time::Instant};
|
||||||
|
|
||||||
|
use anyhow::Error;
|
||||||
|
use ethercat_hal::{
|
||||||
|
devices::{
|
||||||
|
EthercatDevice, downcast_device,
|
||||||
|
wago_750_354::{WAGO_750_354_IDENTITY_A, Wago750_354},
|
||||||
|
wago_modules::wago_750_402::{Wago750_402, Wago750_402InputPort},
|
||||||
|
},
|
||||||
|
io::digital_input::DigitalInput,
|
||||||
|
};
|
||||||
|
use smol::{block_on, lock::RwLock};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
MachineNewHardware, MachineNewParams, MachineNewTrait,
|
||||||
|
digital_input_test_machine::{DigitalInputTestMachine, api::DigitalInputTestMachineNamespace},
|
||||||
|
get_ethercat_device, validate_no_role_dublicates, validate_same_machine_identification_unique,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl MachineNewTrait for DigitalInputTestMachine {
|
||||||
|
fn new<'maindevice>(params: &MachineNewParams) -> Result<Self, Error> {
|
||||||
|
// validate general stuff
|
||||||
|
let device_identification = params
|
||||||
|
.device_group
|
||||||
|
.iter()
|
||||||
|
.map(|device_identification| device_identification.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
validate_same_machine_identification_unique(&device_identification)?;
|
||||||
|
validate_no_role_dublicates(&device_identification)?;
|
||||||
|
|
||||||
|
let hardware = match ¶ms.hardware {
|
||||||
|
MachineNewHardware::Ethercat(x) => x,
|
||||||
|
_ => {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"[{}::EtherCATMachine/TestMachine::new] MachineNewHardware is not Ethercat",
|
||||||
|
module_path!()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
block_on(async {
|
||||||
|
// Example usage of a Wago Coupler and a 750-402 in the first slot, where the Output Port 1,2,3,4 is used
|
||||||
|
let _wago_750_354 = get_ethercat_device::<Wago750_354>(
|
||||||
|
hardware,
|
||||||
|
params,
|
||||||
|
0,
|
||||||
|
[WAGO_750_354_IDENTITY_A].to_vec(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let modules = Wago750_354::initialize_modules(_wago_750_354.1).await?;
|
||||||
|
|
||||||
|
let mut coupler = _wago_750_354.0.write().await;
|
||||||
|
|
||||||
|
for module in modules {
|
||||||
|
coupler.set_module(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
coupler.init_slot_modules(_wago_750_354.1);
|
||||||
|
let dev = coupler.slot_devices.get(0).unwrap().clone().unwrap();
|
||||||
|
let wago750_402: Arc<RwLock<Wago750_402>> = downcast_device::<Wago750_402>(dev).await?;
|
||||||
|
|
||||||
|
let di1 = DigitalInput::new(wago750_402.clone(), Wago750_402InputPort::DI1);
|
||||||
|
let di2 = DigitalInput::new(wago750_402.clone(), Wago750_402InputPort::DI2);
|
||||||
|
let di3 = DigitalInput::new(wago750_402.clone(), Wago750_402InputPort::DI3);
|
||||||
|
let di4 = DigitalInput::new(wago750_402.clone(), Wago750_402InputPort::DI4);
|
||||||
|
|
||||||
|
drop(coupler);
|
||||||
|
|
||||||
|
let (sender, receiver) = smol::channel::unbounded();
|
||||||
|
let mut my_test = Self {
|
||||||
|
api_receiver: receiver,
|
||||||
|
api_sender: sender,
|
||||||
|
machine_identification_unique: params.get_machine_identification_unique(),
|
||||||
|
namespace: DigitalInputTestMachineNamespace {
|
||||||
|
namespace: params.namespace.clone(),
|
||||||
|
},
|
||||||
|
last_state_emit: Instant::now(),
|
||||||
|
led_on: [false; 4],
|
||||||
|
main_sender: params.main_thread_channel.clone(),
|
||||||
|
digital_input: [di1, di2, di3, di4],
|
||||||
|
};
|
||||||
|
my_test.emit_state();
|
||||||
|
Ok(my_test)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ pub mod analog_input_test_machine;
|
|||||||
pub mod aquapath1;
|
pub mod aquapath1;
|
||||||
#[cfg(not(feature = "mock-machine"))]
|
#[cfg(not(feature = "mock-machine"))]
|
||||||
pub mod buffer1;
|
pub mod buffer1;
|
||||||
|
pub mod digital_input_test_machine;
|
||||||
pub mod extruder1;
|
pub mod extruder1;
|
||||||
pub mod extruder2;
|
pub mod extruder2;
|
||||||
pub mod ip20_test_machine;
|
pub mod ip20_test_machine;
|
||||||
@@ -47,6 +48,7 @@ pub const TEST_MACHINE: u16 = 0x0033;
|
|||||||
pub const IP20_TEST_MACHINE: u16 = 0x0034;
|
pub const IP20_TEST_MACHINE: u16 = 0x0034;
|
||||||
pub const ANALOG_INPUT_TEST_MACHINE: u16 = 0x0035;
|
pub const ANALOG_INPUT_TEST_MACHINE: u16 = 0x0035;
|
||||||
pub const WAGO_AI_TEST_MACHINE: u16 = 0x0036;
|
pub const WAGO_AI_TEST_MACHINE: u16 = 0x0036;
|
||||||
|
pub const DIGITAL_INPUT_TEST_MACHINE: u16 = 0x0040;
|
||||||
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use smol::lock::RwLock;
|
use smol::lock::RwLock;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ impl MachineAct for MockMachine {
|
|||||||
};
|
};
|
||||||
// Only emit live values if machine is in Running mode
|
// Only emit live values if machine is in Running mode
|
||||||
// The live values are updated approximately 30 times per second
|
// The live values are updated approximately 30 times per second
|
||||||
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) {
|
if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 60.0) {
|
||||||
self.emit_live_values();
|
self.emit_live_values();
|
||||||
self.last_measurement_emit = now;
|
self.last_measurement_emit = now;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::analog_input_test_machine::AnalogInputTestMachine;
|
use crate::analog_input_test_machine::AnalogInputTestMachine;
|
||||||
|
use crate::digital_input_test_machine::DigitalInputTestMachine;
|
||||||
use crate::ip20_test_machine::IP20TestMachine;
|
use crate::ip20_test_machine::IP20TestMachine;
|
||||||
use crate::wago_ai_test_machine::WagoAiTestMachine;
|
use crate::wago_ai_test_machine::WagoAiTestMachine;
|
||||||
#[cfg(feature = "mock-machine")]
|
#[cfg(feature = "mock-machine")]
|
||||||
@@ -131,6 +132,8 @@ lazy_static! {
|
|||||||
|
|
||||||
mc.register::<WagoAiTestMachine>(WagoAiTestMachine::MACHINE_IDENTIFICATION);
|
mc.register::<WagoAiTestMachine>(WagoAiTestMachine::MACHINE_IDENTIFICATION);
|
||||||
|
|
||||||
|
mc.register::<DigitalInputTestMachine>(DigitalInputTestMachine::MACHINE_IDENTIFICATION);
|
||||||
|
|
||||||
mc
|
mc
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,6 @@ impl MachineNewTrait for TestMachine {
|
|||||||
let do2 = DigitalOutput::new(el2004.clone(), EL2004Port::DO2);
|
let do2 = DigitalOutput::new(el2004.clone(), EL2004Port::DO2);
|
||||||
let do3 = DigitalOutput::new(el2004.clone(), EL2004Port::DO3);
|
let do3 = DigitalOutput::new(el2004.clone(), EL2004Port::DO3);
|
||||||
let do4 = DigitalOutput::new(el2004.clone(), EL2004Port::DO4);
|
let do4 = DigitalOutput::new(el2004.clone(), EL2004Port::DO4);
|
||||||
|
|
||||||
let (sender, receiver) = smol::channel::unbounded();
|
let (sender, receiver) = smol::channel::unbounded();
|
||||||
let mut my_test = Self {
|
let mut my_test = Self {
|
||||||
api_receiver: receiver,
|
api_receiver: receiver,
|
||||||
|
|||||||
@@ -126,6 +126,16 @@ in {
|
|||||||
value = "-20";
|
value = "-20";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
# needed so dnsmasq can get managed by server
|
||||||
|
security.polkit.extraConfig = ''
|
||||||
|
polkit.addRule(function(action, subject) {
|
||||||
|
if (action.id == "org.freedesktop.systemd1.manage-units" &&
|
||||||
|
action.lookup("unit") == "dnsmasq.service" &&
|
||||||
|
subject.user == "${cfg.user}") {
|
||||||
|
return polkit.Result.YES;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
'';
|
||||||
|
|
||||||
# Desktop integration
|
# Desktop integration
|
||||||
xdg.mime.enable = true;
|
xdg.mime.enable = true;
|
||||||
|
|||||||
@@ -322,10 +322,12 @@ in {
|
|||||||
|
|
||||||
services.dnsmasq = {
|
services.dnsmasq = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
resolveLocalQueries = false;
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
interface = "enp1s0"; # only this interface
|
interface = "enp1s0"; # only this interface
|
||||||
bind-interfaces = true;
|
bind-interfaces = true;
|
||||||
|
server = [ "1.1.1.1" "8.8.8.8" ];
|
||||||
|
|
||||||
# DHCP subnet + pool
|
# DHCP subnet + pool
|
||||||
dhcp-range = "10.10.10.50,10.10.10.250,255.255.255.0,12h";
|
dhcp-range = "10.10.10.50,10.10.10.250,255.255.255.0,12h";
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ buildNpmPackage rec {
|
|||||||
ELECTRON_SKIP_BINARY_DOWNLOAD = 1;
|
ELECTRON_SKIP_BINARY_DOWNLOAD = 1;
|
||||||
|
|
||||||
makeCacheWritable = true;
|
makeCacheWritable = true;
|
||||||
npmDepsHash = "sha256-UHPIoyMTzr7jkvbpZsePaNJ6s76h3R/0pcnbCkOcdYo=";
|
npmDepsHash = "sha256-twp+30+hPKoMUojuGyJI1R6dknWWx9i5waqFw2FlHPc=";
|
||||||
npmFlags = [ "--no-audit" "--no-fund" ];
|
npmFlags = [ "--no-audit" "--no-fund" ];
|
||||||
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use ethercat_hal::devices::wago_modules::ip20_ec_di8_do8::{
|
|||||||
IP20_EC_DI8_DO8_PRODUCT_ID, IP20_EC_DI8_DO8_VENDOR_ID, IP20EcDi8Do8,
|
IP20_EC_DI8_DO8_PRODUCT_ID, IP20_EC_DI8_DO8_VENDOR_ID, IP20EcDi8Do8,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::utils::{start_dnsmasq, stop_dnsmasq};
|
||||||
use ethercrab::std::ethercat_now;
|
use ethercrab::std::ethercat_now;
|
||||||
use ethercrab::{MainDevice, MainDeviceConfig, PduStorage, RetryBehaviour, Timeouts};
|
use ethercrab::{MainDevice, MainDeviceConfig, PduStorage, RetryBehaviour, Timeouts};
|
||||||
use machines::machine_identification::{
|
use machines::machine_identification::{
|
||||||
@@ -172,6 +173,14 @@ pub async fn setup_loop(
|
|||||||
) -> Result<EthercatSetup, anyhow::Error> {
|
) -> Result<EthercatSetup, anyhow::Error> {
|
||||||
tracing::info!("Starting Ethercat PDU loop");
|
tracing::info!("Starting Ethercat PDU loop");
|
||||||
|
|
||||||
|
let res = stop_dnsmasq();
|
||||||
|
match res {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => tracing::error!("Failed to stop dnsmasq: {:?}", e),
|
||||||
|
};
|
||||||
|
// Small Timeout to ensure interfaces get released
|
||||||
|
smol::Timer::after(Duration::from_millis(1500)).await;
|
||||||
|
|
||||||
// Setup ethercrab tx/rx task
|
// Setup ethercrab tx/rx task
|
||||||
let pdu_storage = Box::leak(Box::new(PduStorage::<MAX_FRAMES, MAX_PDU_DATA>::new()));
|
let pdu_storage = Box::leak(Box::new(PduStorage::<MAX_FRAMES, MAX_PDU_DATA>::new()));
|
||||||
let (tx, rx, pdu) = pdu_storage.try_split().expect("can only split once");
|
let (tx, rx, pdu) = pdu_storage.try_split().expect("can only split once");
|
||||||
@@ -481,6 +490,12 @@ pub async fn setup_loop(
|
|||||||
main_namespace.emit(MainNamespaceEvents::EthercatDevicesEvent(event));
|
main_namespace.emit(MainNamespaceEvents::EthercatDevicesEvent(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let res = start_dnsmasq();
|
||||||
|
match res {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(e) => tracing::error!("Failed to start dnsmasq: {:?}", e),
|
||||||
|
};
|
||||||
|
|
||||||
Ok(EthercatSetup {
|
Ok(EthercatSetup {
|
||||||
devices,
|
devices,
|
||||||
group: group_op,
|
group: group_op,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ use machines::{
|
|||||||
};
|
};
|
||||||
#[cfg(feature = "development-build")]
|
#[cfg(feature = "development-build")]
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use utils::start_dnsmasq;
|
||||||
|
|
||||||
use app_state::{HotThreadMessage, SharedState};
|
use app_state::{HotThreadMessage, SharedState};
|
||||||
use ethercat::ethercat_discovery_info::send_ethercat_discovering;
|
use ethercat::ethercat_discovery_info::send_ethercat_discovering;
|
||||||
@@ -57,6 +58,7 @@ pub mod panic;
|
|||||||
pub mod performance_metrics;
|
pub mod performance_metrics;
|
||||||
pub mod rest;
|
pub mod rest;
|
||||||
pub mod socketio;
|
pub mod socketio;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
pub async fn send_empty_machines_event(shared_state: Arc<SharedState>) {
|
pub async fn send_empty_machines_event(shared_state: Arc<SharedState>) {
|
||||||
shared_state.current_machines_meta.lock().await.clear();
|
shared_state.current_machines_meta.lock().await.clear();
|
||||||
@@ -137,7 +139,6 @@ pub async fn start_interface_discovery(
|
|||||||
let interface = find_ethercat_interface().await;
|
let interface = find_ethercat_interface().await;
|
||||||
tracing::info!("Inferface found {}, setting up EtherCAT loop", interface);
|
tracing::info!("Inferface found {}, setting up EtherCAT loop", interface);
|
||||||
set_ethercat_iface(interface.clone());
|
set_ethercat_iface(interface.clone());
|
||||||
|
|
||||||
let res = setup_loop(&interface, app_state.clone()).await;
|
let res = setup_loop(&interface, app_state.clone()).await;
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
@@ -152,9 +153,10 @@ pub async fn start_interface_discovery(
|
|||||||
module_path!(),
|
module_path!(),
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
|
// if this doesnt work, unlucky
|
||||||
|
let _ = start_dnsmasq();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
send_ethercat_found(app_state.clone(), &interface).await;
|
send_ethercat_found(app_state.clone(), &interface).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,6 +340,7 @@ fn main() {
|
|||||||
let app_state = Arc::new(shared_state);
|
let app_state = Arc::new(shared_state);
|
||||||
let _loop_thread = start_loop_thread(receiver, CYCLE_TARGET_TIME);
|
let _loop_thread = start_loop_thread(receiver, CYCLE_TARGET_TIME);
|
||||||
let _ = start_api_thread(app_state.clone());
|
let _ = start_api_thread(app_state.clone());
|
||||||
|
|
||||||
spawn_runtime_metrics_sampler(RuntimeMetricsConfig {
|
spawn_runtime_metrics_sampler(RuntimeMetricsConfig {
|
||||||
csv_path: "runtime_metrics.csv".to_string(),
|
csv_path: "runtime_metrics.csv".to_string(),
|
||||||
interval: Duration::from_secs(1),
|
interval: Duration::from_secs(1),
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
pub fn start_dnsmasq() -> std::io::Result<()> {
|
||||||
|
// action can be "start", "stop", or "restart"
|
||||||
|
let status = Command::new("systemctl")
|
||||||
|
.arg("start")
|
||||||
|
.arg("dnsmasq.service")
|
||||||
|
.status()?;
|
||||||
|
|
||||||
|
if status.success() {
|
||||||
|
tracing::info!("Successfully started dnsmasq");
|
||||||
|
} else {
|
||||||
|
tracing::info!("Failed to start dnsmasq");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop_dnsmasq() -> std::io::Result<()> {
|
||||||
|
// action can be "start", "stop", or "restart"
|
||||||
|
let status = Command::new("systemctl")
|
||||||
|
.arg("stop")
|
||||||
|
.arg("dnsmasq.service")
|
||||||
|
.status()?;
|
||||||
|
|
||||||
|
if status.success() {
|
||||||
|
tracing::info!("Successfully stopped");
|
||||||
|
} else {
|
||||||
|
tracing::info!("Failed to stop dnsmasq");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||