Compare commits

...
Author SHA1 Message Date
Christian d4250606a1 Add machine serial ID display to GluetexOverviewPage 2026-02-17 14:51:43 +01:00
Christian 15733dab56 Fix slave tension arm speed being high when in a negative angle 2026-02-17 14:33:20 +01:00
Christian 45e4ca94a7 Add monitoring alert handling to disable Production mode in GluetexOverviewPage 2026-02-17 14:30:55 +01:00
Christian 345802e9eb Add monitoring alert handling to disable Production mode in GluetexOverviewPage 2026-02-17 14:27:19 +01:00
Christian e0a6e50f70 Add delay functionality for Optris voltage monitoring in Gluetex 2026-02-17 14:23:42 +01:00
Christian 930d1c887f format 2026-02-17 13:25:16 +01:00
Christian e427a449ba Switch to Setup mode immediately upon error detection in GluetexErrorBanner 2026-02-17 13:23:26 +01:00
Christian 93f62640c3 Implement emergency stop without heaters for tension arm and voltage monitor triggers 2026-02-17 13:17:19 +01:00
Christian 4e7fc0ee91 Add Winder Mode selection to GluetexOverviewPage 2026-02-17 13:05:28 +01:00
Christian ea5ca42e49 Update emergency stop method to set machine mode to hold instead of standby 2026-02-17 12:59:40 +01:00
Christian 99b7d92884 Add support for resuming traverse from saved position in Gluetex machine 2026-02-17 12:55:10 +01:00
Christian a055e47634 Add estimated time remaining 2026-02-17 12:28:45 +01:00
Christian 7ed3ee87bf Remove unused pattern state display from GluetexHeatersPage 2026-02-17 10:01:26 +01:00
Christian a3319900fb Fix DeviceEepromDialogContent: update Select component to handle value changes 2026-02-17 09:57:15 +01:00
Christian 5df48baaf9 Add support for extra analog inputs in Gluetex machine 2026-02-16 16:24:00 +01:00
Christian 10d9e9e6db Add extra digital outputs support for Gluetex machine 2026-02-16 16:17:29 +01:00
Christian 8a1695f9fb Add default backend extended state for Gluetex namespace 2026-02-16 16:02:41 +01:00
Christian 24b5145163 Fix typo 2026-02-16 16:02:41 +01:00
Christian a095669aea Add gluetex mock feature: implement mock machine functionality and update build scripts 2026-02-16 16:02:41 +01:00
Christian c4bcf82c14 Implement valve control functionality: add state management, mutations, and UI components for valve operations 2026-02-16 16:01:36 +01:00
Christian d8fe3b90a4 Refactor sleep timer functionality: restrict countdown to Setup mode and update related methods 2026-02-16 16:01:36 +01:00
Christian ba4d86ebd3 Implement Addon Motor 5 Tension Control: add state management, UI components, and backend integration 2026-02-16 16:01:36 +01:00
Christian 5045af7eb7 Add status_out field to StateEvent and gluetexNamespace for output handling 2026-02-16 16:01:36 +01:00
Christian 860cddc6c5 Add status output handling to Gluetex: update status based on current activity 2026-02-16 16:01:36 +01:00
Christian eaeb5d345c Fix issues from rebases and other temporary changes 2026-02-16 16:01:36 +01:00
Christian 28217fe839 Automatically switch to Heating mode for auto-tuning in useGluetex hook 2026-02-16 16:01:36 +01:00
Christian a5b51968d3 Enhance StatusBadge and HeatingZone components: add 'info' variant to StatusBadge and integrate auto-tuning status display in HeatingZone 2026-02-16 16:01:36 +01:00
Christian 1cbcf499e4 Add manual homing functionality for addon motor 3 2026-02-16 16:01:36 +01:00
Christian d6b0dd966c Update layout in GluetexAddonsPage: change Stepper 3 container to a vertical flex layout 2026-02-16 16:01:36 +01:00
Christian 45ec2ac257 Update sleep timer logic: allow operation in all modes and simplify check method 2026-02-16 16:01:36 +01:00
Christian b7ba698a26 Add OptrisVoltageGraph component and integrate into GluetexGraphsPage for voltage monitoring 2026-02-16 16:01:36 +01:00
Christian 25e0a7665a Refactor Gluetex monitoring checks: streamline function signatures for clarity and consistency 2026-02-16 16:01:36 +01:00
Christian c6b46838fe Make Gluetex monitoring more object based 2026-02-16 16:01:36 +01:00
Christian c4fe451534 Enhance preset application logic: update onApply prop types to support Promise<void> and implement deepSubsetEquals for state comparison in GluetexPresetsPage 2026-02-16 16:01:36 +01:00
Christian f650a8e538 Refactor dialog content layout: enhance flex properties for better responsiveness and overflow handling in NewPresetDialog and PresetShowDialog components 2026-02-16 16:01:36 +01:00
Christian 644d7585cf Refactor Gluetex preset data schema: streamline heating states, add spool automatic action, and update related logic for preset application 2026-02-16 16:01:36 +01:00
Christian 6615d1c1e2 Add order information management: implement state schema, action functions, and UI integration for order number, serial number, and product description 2026-02-16 16:01:36 +01:00
Christian fd87017687 Implement voltage monitoring for Optris sensors: add state management, UI components, and emergency stop logic 2026-02-16 16:01:36 +01:00
Christian d7cede7dc0 Refactor quality control state: rename temperature to optris and update related logic for voltage handling 2026-02-16 16:01:36 +01:00
Christian 05fcca77c3 Implement separate tension arm monitoring for winder, addon, and slave arms with corresponding state management and UI components 2026-02-16 16:01:36 +01:00
Christian 577bff45ef Enhance GluetexErrorBanner: add operation mode switching and navigation on dialog dismissal 2026-02-16 16:01:36 +01:00
Christian 3b5c92f8bb Add debounce logic for tension arm monitoring and update error handling in Gluetex 2026-02-16 16:01:36 +01:00
Christian 7c06ad4313 Change order number and serial number types from String to u32 in OrderInfo and related structures 2026-02-16 16:01:36 +01:00
Christian 3f7958645e Add GluetexErrorBanner component to display system errors in Gluetex pages 2026-02-16 16:01:36 +01:00
Christian fe59209783 Fix state assignment for tension arm in Gluetex machine 2026-02-16 16:01:36 +01:00
Christian 16d0f7cc56 Refactor operation modes in Gluetex machine: rename and update logic for Setup and Production modes 2026-02-16 16:01:36 +01:00
Christian 91083550a8 Update PID parameters for temperature controllers in Gluetex machine 2026-02-16 16:01:36 +01:00
Christian ed8c64b473 Add operation mode management to Gluetex machine 2026-02-16 16:01:36 +01:00
Christian e4750ee823 Rename "Tension Arm" to "Slave Tension Arm" for clarity in GluetexAddonsPage 2026-02-16 16:01:36 +01:00
Christian b14c38b5e7 Add order information handling to Gluetex machine API and state management 2026-02-16 16:01:36 +01:00
Christian 18aefa2867 Add optris voltage readings to Gluetex machine and update related components 2026-02-16 16:01:36 +01:00
Christian 36ce437499 Add Gluetex machine constant to machine identification module 2026-02-16 16:01:36 +01:00
Christian 37c9e143a8 Add Gluetex machine identification and update REST API routes, remove hardcoded ui check 2026-02-16 16:01:36 +01:00
Christian 868089a6bf fix slave puller logic 2026-02-16 16:01:36 +01:00
Christian 258b4409c7 Refactor toPresetData function in GluetexPresetsPage to handle optional state properties and improve data structure for preset states 2026-02-16 16:01:36 +01:00
Christian 3dbf9cc304 Refactor Gluetex to use target angle and sensitivity for slave puller control 2026-02-16 16:01:36 +01:00
Christian 02eb00883c Refactor Slave Puller API to use target angle and sensitivity for speed control 2026-02-16 16:01:36 +01:00
Christian ff16df1304 Refactor sleep timer handling in Gluetex to reset on user interactions and remove activity detection logic 2026-02-16 16:01:36 +01:00
Christian b67065b9c3 Fix slave speed calculation in Gluetex by referencing the correct tension arm 2026-02-16 16:01:36 +01:00
Christian d0ed404afa Fix endstop detection logic in Gluetex by correcting voltage threshold condition 2026-02-16 16:01:36 +01:00
Christian 2d9ca0e702 Refactor code formatting 2026-02-16 16:01:36 +01:00
Christian 44e59d9d9a Refactor sleep timer handling in Gluetex to improve state emission logic and variable naming 2026-02-16 16:01:36 +01:00
Christian 7b950c93a0 Update pid algorithm 2026-02-16 16:01:36 +01:00
Christian 73032f6b46 Implement sleep mode handling and state emission in Gluetex 2026-02-16 16:01:36 +01:00
Christian f5413d9b8e Fix slave and addon tension arm switch and sample interval 2026-02-16 16:01:36 +01:00
Christian c609e8dc6a Refactor addon motor references from motor 5 as konturrad to motor 3 2026-02-16 16:01:36 +01:00
Christian 394b5fd8ea Refactor GluetexAddonsPage and GluetexOverviewPage to enhance heating mode and heater power display 2026-02-16 16:01:36 +01:00
Christian 18b471af39 Refactor Stepper 5 controls layout for improved readability and organization 2026-02-16 16:01:36 +01:00
Christian 0505609a01 Add product description input dialog and update order information layout in GluetexOverviewPage 2026-02-16 16:01:36 +01:00
Christian 7062d28ff1 Add heating mode selection to GluetexHeatersPage with ControlCard and SelectionGroup 2026-02-16 16:01:36 +01:00
Christian 4c7bce5462 Enhance GluetexGraphsPage with additional slave and addon tension arm angle and puller speed data support 2026-02-16 16:01:36 +01:00
Christian ce7a128b2d Organize settings and make it collapsible 2026-02-16 16:01:36 +01:00
Christian 8f19b135e1 Add support for "min" unit in unit icon and rendering functions 2026-02-16 16:01:36 +01:00
Christian 49ed575ee2 Add sleep timer functionality with state management and UI integration 2026-02-16 16:01:36 +01:00
Christian 2970c4e924 Put heater graphs into one 2026-02-16 16:01:35 +01:00
Christian f200f38862 Add temperature zone graphs and integrate them into GluetexGraphsPage and update pid values 2026-02-16 16:01:35 +01:00
Christian c7df114544 Add motor direction control and organize settings 2026-02-16 16:01:35 +01:00
Christian 1594c3a0b6 Refactor autotuning result handling to prevent retaining tuner state after completion or failure 2026-02-16 16:01:35 +01:00
Christian 34a44a1b17 fix datatypes in GluetexPresetsPage 2026-02-16 16:01:35 +01:00
Christian d3b05a0a89 Implement Gluetex preset functionality with detailed state management and UI integration 2026-02-16 16:01:35 +01:00
Christian 223dd74c24 fix format and switch addon motor 4 and slave puller. 2026-02-16 16:01:35 +01:00
Christian 14859506f3 Prevent duplicate toast notifications for PID auto-tuning completion events 2026-02-16 16:01:35 +01:00
Christian 740350f920 Update with new pid values 2026-02-16 16:01:35 +01:00
Christian c527f85ebc Update PID auto-tuning configuration: extend max duration to 1 hour and adjust peak tracking logic 2026-02-16 16:01:35 +01:00
Christian c2f3086cde Refactor PID auto-tuning configuration: update parameters for improved temperature oscillation and control 2026-02-16 16:01:35 +01:00
Christian 60233c3e7f Enhance PID auto-tuning: update configuration parameters for improved performance and stability 2026-02-16 16:01:35 +01:00
Christian 066bee0511 Refactor PID auto-tuner: streamline configuration, enhance peak detection, and improve output control logic 2026-02-16 16:01:35 +01:00
Christian be3c604294 Switch PID tuning method from Ziegler-Nichols to Tyreus-Luyben for improved temperature control in thermal systems 2026-02-16 16:01:35 +01:00
Christian d64edbd9c0 Update PID tuning parameters for Gluetex to improve temperature control accuracy 2026-02-16 16:01:35 +01:00
Christian 63d11fbfe7 Increase maximum duration for PID auto-tuning from 20 to 40 minutes for improved tuning accuracy 2026-02-16 16:01:35 +01:00
Christian 5ad56880ee Enhance PID auto-tuning: add progress tracking, cycle counting, and update configuration parameters for improved performance 2026-02-16 16:01:35 +01:00
Christian 5522b87f49 Refactor PID auto-tuning configuration: replace relay amplitude with hysteresis and update related logic 2026-02-16 16:01:35 +01:00
Christian fe5767ca50 remove role 12 from gluetex 2026-02-16 16:01:35 +01:00
Christian 6e7b6c028e fix machine id 2026-02-16 16:01:35 +01:00
Christian a4aa3524e6 Add auto pid tuning 2026-02-16 16:01:35 +01:00
Christian 30bec6e3c3 Fix rebase issues 2026-02-16 16:01:35 +01:00
Christian 03ce5cf6a2 Fix reference to addon tension arm in slave puller speed controller update 2026-02-16 16:01:35 +01:00
Christian 9ac46f4857 Add EL3062 configuration and initialization in Gluetex machine 2026-02-16 16:01:35 +01:00
Christian b111c8e5c3 Add support for addon voltage inputs in Gluetex machine 2026-02-16 16:01:35 +01:00
Christian 0c0c9de2f2 Refactor code for improved readability and consistency in addon motor controller and registry modules 2026-02-16 16:01:35 +01:00
Christian 6606cfdcce Add support for Konturlänge and Pause settings in Addon Motor 5 functionality 2026-02-16 16:01:35 +01:00
snacj 7f46fdc666 Fixed Imports 2026-02-16 16:01:35 +01:00
Christian e67a147278 Swap functionality of slave and addon tension arms in Gluetex machine API 2026-02-16 16:01:35 +01:00
Christian a3277f299a Restructuring of Gluetex Winder files 2026-02-16 16:01:35 +01:00
Christian d1f07e8d01 Add support for Addon Motor 5 in Gluetex machine, including state management and UI integration 2026-02-16 16:01:35 +01:00
Christian a5da005f7b Add slave puller state schema and update related types in Gluetex machine 2026-02-16 16:01:35 +01:00
Christian b91e62fad7 Add overview page and update routing for Gluetex machine 2026-02-16 16:01:35 +01:00
Christian e7bfcf6942 Implement user preference for enabling slave puller feature in Gluetex machine 2026-02-16 16:01:35 +01:00
Christian fbdf3d0521 Refactor PID tuning parameters for temperature controllers in Gluetex machine 2026-02-16 16:01:35 +01:00
Christian 1ef69df731 Add heating control functions for Gluetex machine and update state handling 2026-02-16 16:01:35 +01:00
Christian 8d652ce4c3 Add tension arm monitoring functionality to Gluetex machine 2026-02-16 16:01:35 +01:00
Christian e83967b4d3 Update navigation in MachinesPage to use dynamic serial parameter 2026-02-16 16:01:35 +01:00
Christian 68476c0ce6 Add addon tension arm functionality to Gluetex machine 2026-02-16 16:01:35 +01:00
Christian 36f3de7626 Refactor tension arm handling in GluetexAddonsPage to use slave tension arm variables 2026-02-16 16:01:35 +01:00
Christian 605fa3a7fb Add heating control functionality to Gluetex machine
- Introduced heating enabled state management in the Gluetex API and emit modules.
- Updated the Gluetex state schema to include heating states.
- Implemented helper functions to derive heating mode from backend state.
- Enhanced useGluetex hook to manage heating state updates.
2026-02-16 16:01:35 +01:00
Christian c9e7bb4973 Update Gluetex routing and machine identification handling to support dynamic serial numbers 2026-02-16 16:01:35 +01:00
Christian 3792196b5a Update Gluetex machine configuration to use EL7031-0030 for addon motors 3 and 4 2026-02-16 16:01:35 +01:00
Christian b3ee0f9d60 Update Gluetex machine identity vectors to include EL7031_0030_IDENTITY_A 2026-02-16 16:01:35 +01:00
Christian 2c5597285b Add additional Gluetex machine configurations and update error messages for identity mismatches 2026-02-16 16:01:35 +01:00
Christian ee3c2c0214 Add vendor ID to Gluetex machine properties and update role assignment logic in DeviceEepromDialogContent 2026-02-16 16:01:35 +01:00
Christian ea91efd811 Fix device role assignment in DeviceEepromDialogContent to use actual role number 2026-02-16 16:01:35 +01:00
Christian 1a5ee2fb98 Update Gluetex machine roles and device configurations for EL2008 2026-02-16 16:01:35 +01:00
Christian e919e49f25 Add Gluetex machine properties and device roles 2026-02-16 16:01:35 +01:00
Christian 446daf26b0 Add heating PID settings and controls for multiple zones in Gluetex 2026-02-16 16:01:17 +01:00
Christian c3db0a110e Format LinearStepConverter initialization for better readability in tests 2026-02-16 16:01:17 +01:00
Christian 01f2b49778 Update LinearStepConverter initialization in tests to use from_circumference method 2026-02-16 16:01:17 +01:00
Christian f445d44809 Remove unused import and update function parameter name in SlavePullerSpeedController 2026-02-16 16:01:17 +01:00
Christian 11c6f806c6 Implement slave puller functionality with state management and control in Gluetex 2026-02-16 16:01:17 +01:00
Christian eb085fa80c Add addon motor support with state management and control functions, remove cutting unit 2026-02-16 16:01:17 +01:00
Christian 315ed1b31c Implement heating control features for Gluetex machine, including temperature controllers and power management for heating zones. 2026-02-16 16:01:17 +01:00
Christian d83488a471 Add temperature sensor integration to Gluetex machine and update UI components 2026-02-16 16:01:17 +01:00
Christian 3efee724e1 Implement rust backend for default winding operations 2026-02-16 16:01:17 +01:00
Christian 3dd1b86ac5 Add Gluetex Heaters page and integrate simulated temperature and power data 2026-02-16 16:00:20 +01:00
Christian 3f2e40e774 Implement Gluetex Addons page and integrate motor ratio controls 2026-02-16 16:00:20 +01:00
Christian 5543eb7c83 Add temperature control features to Gluetex machine interface 2026-02-16 16:00:20 +01:00
Christian 63367211a1 Add default winder to gluetex device with dummy data 2026-02-16 16:00:19 +01:00
Christian af0b5deff0 Merge pull request #1135 from qitechgmbh/991-graph-tab-in-top-navigation-bar-not-connecting-to-graph-page-2
fixed the graph page not not connecting to navigation bar  bug
2026-02-13 08:06:53 +01:00
Christian 743a589c1d Update README.md to enhance video descriptions and add additional video section 2026-02-12 17:04:35 +01:00
Achraf Jalleli ee7550d89f Merge pull request #1127 from qitechgmbh/1042-implement-minimal-machine-example-for-wago-750-530
1042 implement minimal machine example for wago 750 530
2026-02-12 13:37:26 +01:00
Achraf JalleliandCopilot ec13f715aa slug fix
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-12 13:22:14 +01:00
Achraf Jalleli 2af746e7a4 Add files via upload 2026-02-12 13:14:04 +01:00
JalleliAchraf 3af3fb9d79 used array.fill 2026-02-12 11:22:11 +01:00
Achraf Jalleli ff3103bec2 Merge branch 'master' into 1042-implement-minimal-machine-example-for-wago-750-530 2026-02-12 11:00:23 +01:00
Hans Sebastian Pöhlmann 5eb3c39f84 1043 implement minimal machine example for wago 750 1506 (#1106)
* wago 750_1506 minimal machine backend apparently done, frontend still unfinished

* wago 750_1506 frontend apparently done, but untested

* added bus coupler to device roles
2026-02-12 10:54:06 +01:00
Robin Krämer 2c715d6a8e fixed initialization by raising timeouts for eeprom,eeprom write lock (#1136)
* fixed initialization by raising timeouts for eeprom,eeprom write lock

* fixed formatting
2026-02-11 14:24:48 +01:00
JalleliAchraf fb12001721 fixed the graph page not not connecting to navigation bar bug 2026-02-10 18:29:04 +01:00
Robin Krämer ad94754fcf Added missing revision for Watercooling (#1130) 2026-02-09 15:14:30 +01:00
Achraf Jalleli b919721c3a Update npmDepsHash for electron package 2026-02-06 16:20:26 +01:00
JalleliAchraf a428a2aa02 formatting 2026-02-06 16:12:25 +01:00
JalleliAchraf 7b479c6021 Merge branch '1042-implement-minimal-machine-example-for-wago-750-530' of github.com:qitechgmbh/control into 1042-implement-minimal-machine-example-for-wago-750-530 2026-02-06 16:09:03 +01:00
JalleliAchraf 17ac24c79d minor improvements 2026-02-06 16:08:25 +01:00
JalleliAchraf 8c7a04b616 formatting fix 2026-02-06 15:57:55 +01:00
github-actions[bot] fe248c79ae chore: update npmDepsHash to sha256-/7zfLof91S97OmW8dBeaVajYEv3r2eYOZ8Zb2ahykaQ= 2026-02-06 14:54:58 +00:00
JalleliAchraf 038df09611 removed unecessary differences between this branch and master 2026-02-06 15:49:21 +01:00
JalleliAchraf 9e43d8d431 several small fixes 2026-02-06 15:44:13 +01:00
JalleliAchraf d364263d6e wago digital output minimal machine 2026-02-06 15:22:00 +01:00
Christian 376e4a4ea2 Merge pull request #1117 from qitechgmbh/1100-update-the-main-readme
1100 update the main readme
2026-02-05 21:39:54 +01:00
Robin Krämer 0b30ae9a85 removed excessive logging, simplified socketio task with actual error printed … (#1122)
* removed excessive logging, simplified socketio task with actual error message

* fixed formatting
2026-02-05 17:34:48 +01:00
Christian 528952238d Update issue templates 2026-02-05 14:54:10 +01:00
Robin Krämer 8c32ce2a2b loop now uses Different Cycle time when development-build is set (#1112)
* loop now uses Different Cycle time when development-build is set

* fixed formatting

* hid stop_dnsmasq import behind feature flag

* added feature gate for start_dnsmasq in main
2026-02-05 11:52:56 +01:00
Christian 579ef0d2e2 Enhance video section in README.md with descriptive captions for better user engagement 2026-02-04 19:39:34 +01:00
Christian b6263e90d6 Refactor video section in README.md for improved layout and clarity 2026-02-04 19:32:33 +01:00
Christian f67f098ff3 better wording 2026-02-04 19:30:21 +01:00
Christian 1a3c26b789 Update issue templates 2026-02-04 18:30:07 +01:00
Oshgnacknak 49f0c9f289 Merge pull request #1113 from qitechgmbh/add-issue-templates
Add issue templates
2026-02-04 15:40:33 +01:00
Oshgnacknak e60daed274 Merge pull request #1116 from qitechgmbh/1115-remove-gitattributes-and-gitmodules
1115 Remove .gitattributes and .gitmodules files
2026-02-04 15:35:35 +01:00
Oshgnacknak 9a9120398e Merge pull request #1097 from qitechgmbh/dependabot/cargo/serde_json-1.0.149
Bump serde_json from 1.0.145 to 1.0.149
2026-02-04 15:22:07 +01:00
Christian dec342498c Fix link to Device Examples in README.md for improved navigation 2026-02-04 15:21:06 +01:00
Christian bc8cc5dd3d Update README.md to enhance documentation structure and clarity 2026-02-04 15:18:19 +01:00
Oshgnacknak 4a1f3d125d Merge pull request #1096 from qitechgmbh/dependabot/cargo/syn-2.0.114
Bump syn from 2.0.111 to 2.0.114
2026-02-04 15:07:29 +01:00
dependabot[bot] 1d1eeb3d48 Bump serde_json from 1.0.145 to 1.0.149
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.145 to 1.0.149.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.145...v1.0.149)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-version: 1.0.149
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-04 14:06:09 +00:00
Oshgnacknak c395d4e172 Merge pull request #1094 from qitechgmbh/dependabot/cargo/euclid-0.22.13
Bump euclid from 0.22.11 to 0.22.13
2026-02-04 15:05:33 +01:00
Oshgnacknak 5009c5ca75 Merge pull request #1092 from qitechgmbh/dependabot/cargo/axum-0.8.8
Bump axum from 0.8.7 to 0.8.8
2026-02-04 15:03:41 +01:00
Oshgnacknak 50dbc46b8c Merge pull request #1089 from qitechgmbh/dependabot/cargo/tracing-subscriber-0.3.22
Bump tracing-subscriber from 0.3.20 to 0.3.22
2026-02-04 15:01:40 +01:00
Oshgnacknak 774e7e2f8b Merge pull request #1108 from qitechgmbh/dependabot/cargo/bytes-1.11.1
Bump bytes from 1.11.0 to 1.11.1
2026-02-04 14:59:55 +01:00
Oshgnacknak 270872fe81 Merge pull request #1114 from qitechgmbh/1081-automate-npm-deps-hash-for-electronnix
feat: automate npm deps hash update for Electron
2026-02-04 14:33:01 +01:00
Oshgnacknak a749f59186 Merge pull request #1110 from qitechgmbh/1109-remove-hooks
1109 remove hooks folder
2026-02-04 14:28:37 +01:00
Christian bae1de3c54 Remove .gitattributes and .gitmodules files 2026-02-04 13:52:33 +01:00
Christian 7681dae32c feat: automate npmDepsHash updates for Electron
- Add GitHub Actions workflow to automatically calculate and update npmDepsHash
- Workflow triggers on package-lock.json changes in PRs
- Uses nix prefetch-npm-deps to calculate correct hash
- Automatically commits hash updates to the PR
- Supports manual triggering via workflow_dispatch
2026-02-04 13:40:58 +01:00
ChristianandCopilot 2d98d369e1 Update .github/ISSUE_TEMPLATE/feature_request.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-04 13:04:29 +01:00
Christian 310ac42a88 Update issue templates
Add issue templates for better feature requests or bug reports.
2026-02-04 12:57:35 +01:00
Christian 182df9c569 Merge pull request #1107 from qitechgmbh/electron-deps
electron deps
2026-02-03 22:05:39 +01:00
Christian 153a719d64 remove Git hooks and pre-commit script 2026-02-03 21:56:06 +01:00
Christian 84603d1594 update hash 2026-02-03 21:53:28 +01:00
Christian 17b2e3e297 clear hash 2026-02-03 21:52:02 +01:00
Christian fb1cf33c7f Update package lock 2026-02-03 21:44:19 +01:00
dependabot[bot] ae2d0fa8b1 Bump bytes from 1.11.0 to 1.11.1
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.11.0 to 1.11.1.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.11.0...v1.11.1)

---
updated-dependencies:
- dependency-name: bytes
  dependency-version: 1.11.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-03 19:43:19 +00:00
ᴏxᴏ.ᴍɪᴋᴀ ed06b63b77 Merge pull request #1083 from qitechgmbh/1076-minimum-hardware-documentation-links
1076 minimum hardware documentation links
2026-02-03 17:15:21 +01:00
Oshgnacknak 9f0f45d19c Fix hash 2026-02-03 16:26:07 +01:00
dependabot[bot] a75c0ff08a Bump zustand from 5.0.9 to 5.0.11 in /electron
Bumps [zustand](https://github.com/pmndrs/zustand) from 5.0.9 to 5.0.11.
- [Release notes](https://github.com/pmndrs/zustand/releases)
- [Commits](https://github.com/pmndrs/zustand/compare/v5.0.9...v5.0.11)

---
updated-dependencies:
- dependency-name: zustand
  dependency-version: 5.0.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-03 16:19:11 +01:00
dependabot[bot] 205432c926 Bump vitest from 3.2.4 to 4.0.18 in /electron
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 3.2.4 to 4.0.18.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.18/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 4.0.18
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-03 16:18:11 +01:00
dependabot[bot] 781783464b Bump eslint from 9.39.1 to 9.39.2 in /electron
Bumps [eslint](https://github.com/eslint/eslint) from 9.39.1 to 9.39.2.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.39.1...v9.39.2)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.39.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-03 16:16:43 +01:00
dependabot[bot] 4fa2a1a221 Bump react-i18next from 15.7.4 to 16.5.4 in /electron
Bumps [react-i18next](https://github.com/i18next/react-i18next) from 15.7.4 to 16.5.4.
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v15.7.4...v16.5.4)

---
updated-dependencies:
- dependency-name: react-i18next
  dependency-version: 16.5.4
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-03 16:14:36 +01:00
Oshgnacknak b734468741 Fix event names in wago power machine 2026-02-03 16:14:02 +01:00
dependabot[bot] 1ed4ae3d2c Bump socket.io-client from 4.8.1 to 4.8.3 in /electron
Bumps [socket.io-client](https://github.com/socketio/socket.io) from 4.8.1 to 4.8.3.
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/socket.io-client@4.8.1...socket.io-client@4.8.3)

---
updated-dependencies:
- dependency-name: socket.io-client
  dependency-version: 4.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-03 15:57:02 +01:00
dependabot[bot] 8095222cad Bump syn from 2.0.111 to 2.0.114
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.111 to 2.0.114.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.111...2.0.114)

---
updated-dependencies:
- dependency-name: syn
  dependency-version: 2.0.114
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-03 12:37:25 +00:00
dependabot[bot] d19af0923c Bump axum from 0.8.7 to 0.8.8
Bumps [axum](https://github.com/tokio-rs/axum) from 0.8.7 to 0.8.8.
- [Release notes](https://github.com/tokio-rs/axum/releases)
- [Changelog](https://github.com/tokio-rs/axum/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/axum/compare/axum-v0.8.7...axum-v0.8.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-03 12:37:25 +00:00
Robin Krämer 08ce5a37bd aquapath now works with all the other machines connected (#1103)
* aquapath now works with all the other machines connected

* fixed formatting

* removed unused import

* fixed formatting AGAIN
2026-02-03 13:36:07 +01:00
Robin Krämer 78c7667d96 Merge pull request #1087 from qitechgmbh/fix-wago-init-logic
fixed Wago Initialization Logic
2026-02-02 11:02:44 +01:00
dependabot[bot] bef3389874 Bump euclid from 0.22.11 to 0.22.13
Bumps [euclid](https://github.com/servo/euclid) from 0.22.11 to 0.22.13.
- [Release notes](https://github.com/servo/euclid/releases)
- [Commits](https://github.com/servo/euclid/commits)

---
updated-dependencies:
- dependency-name: euclid
  dependency-version: 0.22.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-01 02:25:36 +00:00
dependabot[bot] 985937c227 Bump tracing-subscriber from 0.3.20 to 0.3.22
Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from 0.3.20 to 0.3.22.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.20...tracing-subscriber-0.3.22)

---
updated-dependencies:
- dependency-name: tracing-subscriber
  dependency-version: 0.3.22
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-01 02:25:04 +00:00
kraemr 22be4b79f0 fixed linter errors and added logic for 750_430 2026-01-30 16:33:50 +01:00
kraemr be84edf0fd removed unused parameter from ModulePDOMapping 2026-01-30 16:23:22 +01:00
kraemr ff3e787a03 fixed Wago Initialization Logic
fixed formatting
2026-01-30 16:16:10 +01:00
Christian 093663c4e6 Merge pull request #1086 from qitechgmbh/1085-change-heater-power-for-extruder-v3
Update heater power to the real values for extruder v3
2026-01-30 15:14:59 +01:00
Christian 1b2d6df459 Merge pull request #967 from qitechgmbh/Hamids-Branch
BECKHOFF minimal example motor
2026-01-30 13:24:54 +01:00
Christian cd2e15cb29 Update heater power to the real values for extruder v3 2026-01-30 13:23:46 +01:00
Christian f1571c0daa Merge pull request #1082 from qitechgmbh/revert-1077-1076-minimum-hardware-documentation-links
Revert "docs(readme): add documentation links and update machine table"
2026-01-29 14:50:33 +01:00
Christian 96e0fb83ef Revert "docs(readme): add documentation links and update machine table" 2026-01-29 14:50:10 +01:00
Christian 94cffbf717 Merge pull request #1077 from qitechgmbh/1076-minimum-hardware-documentation-links
docs(readme): add documentation links and update machine table
2026-01-29 13:22:14 +01:00
EinsPommes 38b2e907f9 docs(readme): update documentation links and improve machine table clarity 2026-01-28 17:16:10 +01:00
ᴏxᴏ.ᴍɪᴋᴀ 7e5498751a Merge branch 'master' into 1076-minimum-hardware-documentation-links 2026-01-28 17:13:52 +01:00
Christian eef9d63de2 Merge pull request #1074 from qitechgmbh/dependabot/npm_and_yarn/electron/multi-c5fe2c841a
Bump seroval and solid-js in /electron
2026-01-27 11:00:51 +01:00
Christian d9d9b9d344 update hash 2026-01-27 10:47:20 +01:00
Christian 2ff3d8df56 remove deps hash 2026-01-27 10:43:38 +01:00
Christian 462672bb6a Add peer dependency flag to multiple packages in package-lock.json 2026-01-27 10:38:58 +01:00
dependabot[bot] 84d539a4fa Bump seroval and solid-js in /electron
Bumps [seroval](https://github.com/lxsmnsyc/seroval) and [solid-js](https://github.com/solidjs/solid). These dependencies needed to be updated together.

Updates `seroval` from 1.3.2 to 1.5.0
- [Release notes](https://github.com/lxsmnsyc/seroval/releases)
- [Commits](https://github.com/lxsmnsyc/seroval/commits)

Updates `solid-js` from 1.9.10 to 1.9.11
- [Release notes](https://github.com/solidjs/solid/releases)
- [Changelog](https://github.com/solidjs/solid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/solidjs/solid/commits)

---
updated-dependencies:
- dependency-name: seroval
  dependency-version: 1.5.0
  dependency-type: indirect
- dependency-name: solid-js
  dependency-version: 1.9.11
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-27 10:38:58 +01:00
Robin Krämer 850b2d411d Merge pull request #1080 from qitechgmbh/changelog-2.14.1
updated Changelog
2026-01-26 16:37:03 +01:00
kraemr 008efe6ba1 updated Changelog 2026-01-26 16:36:09 +01:00
Robin KrämerandOshgnacknak 3bd248b9fe Don't use half sec interval (#1079)
Co-authored-by: Oshgnacknak <osh@oshgnacknak.de>
2026-01-26 16:29:11 +01:00
Oshgnacknak d73cfe7a02 Don't use half sec interval 2026-01-26 13:19:08 +01:00
EinsPommes 6a6c09e283 docs(readme): add documentation links and update machine table 2026-01-25 22:03:53 +01:00
dependabot[bot] 6d2279d44b Merge pull request #1062 from qitechgmbh/dependabot/npm_and_yarn/electron/seroval-1.4.2
Bump seroval from 1.4.0 to 1.4.2 in /electron
2026-01-25 17:00:04 +01:00
Oshgnacknak 61b9ac8a39 Merge branch 'master' into Hamids-Branch 2026-01-23 16:14:22 +01:00
Oshgnacknak 728dd6eb26 Merge pull request #1066 from qitechgmbh/update_numpad_position
Numpad position now updating on resize
2026-01-23 16:10:00 +01:00
Hans Sebastian Pöhlmann 742fd35d5f Numpad position now updating on resize 2026-01-23 15:56:58 +01:00
Milan von dem Bussche 5e1126e896 Revise machine table in README.md
Updated the machine table format and added version details.
2026-01-23 12:38:14 +01:00
Milan von dem Bussche e91719f902 Update getting started documentation with new sections 2026-01-23 12:37:30 +01:00
Milan von dem Bussche 0efa9bcb07 Revise README structure and add documentation links
Updated README to enhance structure and clarity, added minimal hardware examples section, and included API documentation link.
2026-01-23 12:33:58 +01:00
Milan von dem Bussche 37d897d240 Revise machine table with new entries and videos
Updated machine specifications and added video links for various machines.
2026-01-23 11:38:36 +01:00
Robin Krämer 7488e1d97a updated Change Log (#1069) 2026-01-22 20:35:25 +01:00
Christian bb169e3b33 Fix formatting in registry.rs by removing extra blank line 2026-01-22 14:11:19 +01:00
Christian 3d735f8abd Merge branch 'master' into Hamids-Branch 2026-01-22 13:48:58 +01:00
Christian 0a87e45901 Merge pull request #1058 from qitechgmbh/1041-implement-wago-750-402-terminal
1041 implement wago 750 402 terminal
2026-01-22 13:43:37 +01:00
Renosch 6205189b14 Christians Request Resolved 2026-01-22 13:00:42 +01:00
Renosch 9d7da4f0af Resolved 2026-01-22 12:39:30 +01:00
Renosch 241b24be43 Resolved 2026-01-22 12:06:06 +01:00
Renosch aa77d12879 Documentation with Images 2026-01-22 12:05:16 +01:00
Renosch 78f183a8e5 Immplement Digital Input Machine WAGo 750-402 Final Product 2026-01-22 12:05:16 +01:00
Renosch 9f6fc8184e Working Digital Input Machine 2026-01-22 12:05:16 +01:00
Renosch ec3bdcb1df it now funktional! 2026-01-22 12:05:16 +01:00
Renosch 30292650bc finaly the DigitalInputTestMachines Tab is working 2026-01-22 12:05:16 +01:00
Renosch 1e9bcf9b19 alpha version Frontend and Backend 2026-01-22 12:04:49 +01:00
Robin Krämer 1e88979ced dnsmasq now only runs after ethercat is OP (#1063) 2026-01-21 18:46:06 +01:00
Christian a9a973e336 Merge branch 'master' into Hamids-Branch 2026-01-21 17:11:58 +01:00
Oshgnacknak 1112fbaae2 Merge pull request #990 from qitechgmbh/983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page
983 refactor improve aquapath UI layout and add fan speed configuration page
2026-01-21 15:55:32 +01:00
Oshgnacknak 1abe0bbbdb Fix firewall (#1059)
* Configure firewall to only trust lan interface
2026-01-21 15:53:30 +01:00
Oshgnacknak 3644d16cb5 Merge branch 'master' into 983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page 2026-01-21 15:44:59 +01:00
Christian cbd82ee7c3 Merge pull request #1056 from qitechgmbh/959-wago-analog-input-klemme-750-455
959 wago analog input klemme 750 455
2026-01-21 15:19:38 +01:00
Christian b66a59dba9 Fix naming 2026-01-21 14:33:18 +01:00
Christian 9dbc962dbc Update file paths 2026-01-21 14:33:18 +01:00
Christian 93e2558803 Translate comments from German to English 2026-01-21 14:33:18 +01:00
Christian 8cf7480833 Fix path in routes.tsx 2026-01-21 14:33:18 +01:00
Christian 08d655effc Fix naming and folder structure 2026-01-21 14:33:18 +01:00
Christian c1c867fca1 Change minimal motor readme for current implementation 2026-01-21 14:33:18 +01:00
Hamid-el 1378c00e1a added .idea to .gitignore 2026-01-21 14:33:18 +01:00
Hamid-el 85f17befbe removed .idea 2026-01-21 14:33:18 +01:00
Hamid-el f2e764f93a finished stepper motor + doc finished (real version3) 2026-01-21 14:33:18 +01:00
Hamid-el 9adeeda490 finished stepper motor + doc finished (real version2) 2026-01-21 14:33:18 +01:00
Hamid-el 68bf075172 finished stepper motor + doc finished (real version) 2026-01-21 14:33:18 +01:00
Hamid-el 7d23190939 stepper motor doc finished 2026-01-21 14:33:18 +01:00
Hamid-el 5c68e2c78a finished stepper motor 2026-01-21 14:33:18 +01:00
Hamid-el 980e14a96e stepper motor with ethercat beckhoff Ek1100 and EL7031 0030 + UI works 2026-01-21 14:33:18 +01:00
Hamid-el d5fb45e4e3 stepper motor with ethercat beckhoff Ek1100 and EL7031 works 2026-01-21 14:33:18 +01:00
Hamid-el cdb7ab1e0c small adjustment 2026-01-21 14:33:18 +01:00
Hamid-el da934e0bb3 first version of manually running a stepper motor with ethercat beckhoff Ek1100 and EL7031
# Conflicts:
#	machines/src/aquapath1/act.rs
#	machines/src/registry.rs
#	server/src/ethercat/setup.rs
2026-01-21 14:33:18 +01:00
Christian 06ea89d500 Refactor Wago AI Test Machine components: improve layout structure, optimize wiring error display, and clean up code formatting. 2026-01-20 13:22:30 +01:00
Christian 0868a90513 Update minimal example documentation for Wago 750-455: refine hardware requirements, enhance wiring instructions, and clarify testing steps. 2026-01-20 13:17:04 +01:00
Christian 39fb295b02 Remove debug print statements 2026-01-20 13:17:04 +01:00
Christian 85dbae7458 Beginning of the dev doc 455 2026-01-20 13:17:04 +01:00
Christian 6ce2174e25 Add wiring error handling to Wago AI Test Machine interface 2026-01-20 13:17:04 +01:00
Christian 3ccaa1cd26 Fix wiring error detection and adjust raw value range for Wago750_455 analog input 2026-01-20 13:17:04 +01:00
Christian 72fec6dc9e Implement Wago AI Test Machine with analog input support and control interface 2026-01-20 13:17:04 +01:00
Christian 547e4ac2bd Add Wago750_455 analog input device implementation 2026-01-20 13:15:04 +01:00
Robin Krämer 5fbdf7b9ab Merge pull request #1053 from qitechgmbh/fix-ethercat-devices-event
fixed broken schema for ethercat devices event
2026-01-20 13:09:02 +01:00
kraemr 85bd4b0d96 fixed formatting 2026-01-20 12:59:39 +01:00
kraemr dfce760521 fixed validation error 2026-01-20 12:58:15 +01:00
kraemr 30c97bf9aa fixed formatting 2026-01-19 16:00:00 +01:00
Oshgnacknak 2c8373adcf Fix rust enum zod check 2026-01-19 15:54:52 +01:00
kraemr d08972254c fixed broken schema for ethercat devices event 2026-01-19 15:12:43 +01:00
Christian a76d39e0cd Merge pull request #1016 from qitechgmbh/1015-fix-xlsx-vulnerabilities
Update xlsx dependency to version 0.20.3 for improved security
2026-01-19 13:23:34 +01:00
Christian a74d0dbb80 Set npmDepsHash for Electron package 2026-01-18 16:57:30 +01:00
Christian 17ab24739d Remove npmDepsHash for improved compatibility 2026-01-18 15:21:11 +01:00
Christian 09f0ea3454 Updated package-lock 2026-01-18 15:00:50 +01:00
Christian 0dd2494ea0 Update xlsx dependency to version 0.20.3 for improved security 2026-01-17 11:41:48 +01:00
Oshgnacknak 502c6d010e Merge remote-tracking branch 'origin/master' into 983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page 2026-01-16 13:43:42 +01:00
Robin Krämer 57c1cda229 Merge pull request #1040 from qitechgmbh/1001-rest-api
Rest API /api/v2
2026-01-15 17:06:34 +01:00
Oshgnacknak 8c2b20b148 Rest API: Docs 2026-01-15 16:57:09 +01:00
Oshgnacknak 12e7f245ce Rest API: Allow posting mutations to each machine 2026-01-15 16:57:09 +01:00
Oshgnacknak 671e36fa0f Rest API: Getting values from all machines 2026-01-15 16:57:09 +01:00
Oshgnacknak 1ffd7eade6 Rest API: Getting values from all mock machines 2026-01-15 16:57:09 +01:00
Oshgnacknak 703bbfc9e6 Rest API: Get mock winder v1 values 2026-01-15 16:57:09 +01:00
Oshgnacknak 057afb0b2e List machines in rest api 2026-01-15 16:57:09 +01:00
Robin Krämer 94bd29c734 Merge pull request #1048 from qitechgmbh/zod4
Upgrade to zod v4
2026-01-15 11:37:41 +01:00
Oshgnacknak d078b7b143 Fix nix hash 2026-01-14 22:03:07 +01:00
dependabot[bot] acbeb98f4f Upgrade to zod v4.
To make this work, I lot if typescript types had to be fixed.
In general, they became a little simpler, so the update is worth it.
Also removes zod-adapter and upgrades zod-validation-error to work with zod v4.

From depbot:

Bumps [zod](https://github.com/colinhacks/zod) from 3.25.76 to 4.3.4.
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v3.25.76...v4.3.4)

---
updated-dependencies:
- dependency-name: zod
  dependency-version: 4.3.4
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-14 21:51:23 +01:00
Robin Krämer cab8bf062d Merge pull request #1046 from qitechgmbh/1038-implement-wago-750-652-driver
added implementation for wago 750-652 serial
2026-01-14 14:45:28 +01:00
kraemr 9e04a7fa33 added implementation for wago 750-652 serial 2026-01-14 14:12:01 +01:00
Robin Krämer c92d4847ca Merge pull request #1045 from qitechgmbh/impl-wago-530-driver
added Wago750_530 implementation
2026-01-14 11:57:57 +01:00
kraemr aceeaa08c2 fixed formatting 2026-01-14 11:09:36 +01:00
kraemr 35946d0c88 added Wago750_530 implementation 2026-01-14 11:03:30 +01:00
Oshgnacknak e864a91caa Merge remote-tracking branch 'origin/master' into 983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page 2026-01-13 16:58:54 +01:00
Oshgnacknak a5b9a0b1d5 Merge pull request #1039 from qitechgmbh/883-implement-modbus-tcp-for-wago-2789-9052
Fix modbus discovery
2026-01-11 23:19:10 +01:00
Oshgnacknak 412d625138 Fix modbus discovery 2026-01-09 18:12:33 +01:00
Oshgnacknak 1ec20074e9 Rewrite doc for renderUnitSyntax (#981) 2026-01-08 12:22:28 +01:00
4d796f8422 Implement Wago Power Supply Machine (#915)
* Add timeout to Modbus TCP connection

* Remove machine new trait from Machine trait

Machines should not need to know, how they where created.
Only in the machine registry do we assume we know the constructor.

* Initialize very basic WAGO machine

* Most basic WAGO power page

* Basic machine now known its namespace

* Display values from namespace

* Read live values from wago power supply

* Allow turning power on and off

* Allow setting mode

* Actually set registers correctly

* Show error if mutation fails

* Turn power on/off in UI

* Formatting

* Use actual serial number from device

* Fix loop logic

* Enable dhcp and dns server on lan interface

* Loop until modbus device has been found

* Format

* Fix time series creation

* Convert asserts into Err values

This is to make sure that the device cannot panic our backend.

---------

Co-authored-by: nixos-install.sh <nixos-install.sh@localhost>
Co-authored-by: Robin Krämer <93094049+kraemr@users.noreply.github.com>
2026-01-08 12:01:18 +01:00
Robin Krämer 1406d6c724 Merge pull request #1035 from qitechgmbh/changelog-2.13
added ChangeLog
2026-01-07 18:34:00 +01:00
rob 33441b95a5 added ChangeLog 2026-01-07 18:21:09 +01:00
kraemr 8a3f312675 Revert "Merge pull request #943 from qitechgmbh/892-add-datasheets-to-the-machines-manual-tab"
This reverts commit 40e17ef5f1, reversing
changes made to f65d100cd5.
2026-01-07 18:01:14 +01:00
Robin Krämer 5c327428d6 Merge pull request #1034 from qitechgmbh/1032-fix-metrics-page
1032 fix metrics page
2026-01-07 15:57:12 +01:00
rob 4e4d31cad1 changed metrics for frontend 2026-01-07 15:51:59 +01:00
rob 031fdf25a9 changed minor_faults to per sec in frontend 2026-01-07 15:33:06 +01:00
Oshgnacknak 857cda1455 Merge pull request #1013 from qitechgmbh/Oshgnacknak-patch-2
Update nixos docs
2026-01-07 15:17:21 +01:00
OshgnacknakandThomas Symalla de5f3a5cfc Update docs/nixos/README.md
Co-authored-by: Thomas Symalla <github@thomassymalla.de>
2026-01-07 13:23:47 +01:00
Oshgnacknak 2da7af522c Update nixos docs 2026-01-07 13:23:47 +01:00
Robin Krämer 712284e2f0 Merge pull request #1030 from qitechgmbh/fixes-2.13
Fixes 2.13
2026-01-07 13:16:01 +01:00
rob 402c47a81b removed need for el5152 hack, aquapath now usable with other machines 2026-01-06 16:49:34 +01:00
rob 54a000a280 fixed formatting 2026-01-06 16:49:34 +01:00
rob 41fe118144 removed print again 2026-01-06 16:49:34 +01:00
rob aaa6ff604f fixded unnecessary additional version in machine name 2026-01-06 16:49:34 +01:00
rob a350bb378f added debug print 2026-01-06 16:49:34 +01:00
rob aa2212820e fixed ethercat setup 2026-01-06 16:49:34 +01:00
Robin Krämer 578e70119d Merge pull request #1025 from qitechgmbh/dependabot/cargo/tracing-0.1.44
Bump tracing from 0.1.41 to 0.1.44
2026-01-06 16:06:29 +01:00
dependabot[bot] e8796e00af Bump tracing from 0.1.41 to 0.1.44
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.41 to 0.1.44.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.41...tracing-0.1.44)

---
updated-dependencies:
- dependency-name: tracing
  dependency-version: 0.1.44
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-05 10:23:54 +01:00
Christian 735f27770a Merge pull request #1019 from qitechgmbh/615-test-nixos-builds-in-ci-cd
Rework github pipeline
2026-01-05 07:17:47 +01:00
Christian 700a255157 Merge pull request #971 from qitechgmbh/946-create-docs-for-xtrem-protocol
Added docs
2026-01-05 07:12:08 +01:00
nixos-install.sh ec06275c08 Split and merge nix stores 2026-01-04 23:02:25 +00:00
nixos-install.sh 11d0c8bb53 Add lock files to nix store cache hash 2026-01-04 22:04:46 +00:00
nixos-install.sh 535f24f1d1 Add derivations to store so they appear in the cache 2026-01-04 20:57:15 +00:00
nixos-install.sh 172913d8ec Fix server.nix dependency caching 2026-01-04 20:16:42 +00:00
nixos-install.sh 0f99c54587 Format Nix Files 2026-01-04 18:34:03 +00:00
nixos-install.sh f89d70c5f6 Build whole nix flake in ci 2026-01-04 18:32:15 +00:00
Oshgnacknak 0fc070be69 Build rust in nix inside ci 2026-01-04 18:27:46 +00:00
Oshgnacknak b93eaf530e Build electron in nix inside ci 2026-01-04 18:27:46 +00:00
Oshgnacknak 76d8504651 Redo electron nix build 2026-01-04 18:27:46 +00:00
Oshgnacknak 06acd43ff9 Replace electron forge with vite-plugin-electron 2026-01-04 18:27:46 +00:00
Oshgnacknak 870090c950 Split rust build jobs.
Also use existing rustup action.
2026-01-04 18:27:46 +00:00
Oshgnacknak 65777f10ab Split electron build jobs 2026-01-04 18:27:46 +00:00
Oshgnacknak 67d145de56 Don't run electron workflow twice.
Only run on master push and on pulls to master.
Also use setup-node's built-in cache.
2026-01-04 18:27:46 +00:00
93094049+kraemr@users.noreply.github.com d45d1ed01a test 2026-01-04 18:27:46 +00:00
93094049+kraemr@users.noreply.github.com 0d6c7bc0a5 fixed incorrect path 2026-01-04 18:27:46 +00:00
93094049+kraemr@users.noreply.github.com 15f013f710 fix path 2026-01-04 18:27:46 +00:00
93094049+kraemr@users.noreply.github.com a32e26a041 stubbed hardware config 2026-01-04 18:27:46 +00:00
93094049+kraemr@users.noreply.github.com 41c7e1f74e ci/cd fix 2026-01-04 18:27:46 +00:00
93094049+kraemr@users.noreply.github.com 5a856e6e2b added --impure flag 2026-01-04 18:27:46 +00:00
93094049+kraemr@users.noreply.github.com 8ca16d447e removed buid from nix workflow 2026-01-04 18:27:46 +00:00
93094049+kraemr@users.noreply.github.com b99549895b added nixos test 2026-01-04 18:27:46 +00:00
Christian 07cbaa4518 Merge pull request #1017 from qitechgmbh/fix-nixos-build
Fix nixos electron build
2026-01-02 18:55:07 +01:00
Oshgnacknak c78f5ff5a0 Fix compile warnings 2026-01-02 11:06:23 +01:00
nixos-install.sh 33946a6fa3 Fix nixos electron build 2026-01-02 11:06:23 +01:00
Milan von dem Bussche 02dc67bb85 Revise QiTech Control main description in README
Expanded description of QiTech Control to highlight its open-source nature and modern software paradigms.
2025-12-31 14:43:12 +01:00
MLobsien 2d2415a801 fix: unused imports 2025-12-20 17:48:57 +01:00
MLobsien b4cce1dba4 fix: unued fields in controller 2025-12-20 17:47:45 +01:00
MLobsien f80cdb05d5 fix: cooling state display in UI 2025-12-20 17:43:50 +01:00
MLobsien f24f31259b fix: temperature calculation
- resolves #987
- however, total_energy calculation is incorrect but stable
2025-12-20 17:31:55 +01:00
MLobsien 6411f9ff80 feat: display heating energy consumption in UI 2025-12-20 15:50:22 +01:00
MLobsien 8b462d5a1d Fix: target_revolutions -> max_revolutions in controller 2025-12-20 10:58:04 +01:00
JalleliAchraf d8a0039db3 fixed double import 2025-12-19 20:52:01 +01:00
Achraf Jalleli dec8ea481c Merge pull request #994 from qitechgmbh/master
merge
2025-12-19 20:45:18 +01:00
EinsPommes 0c8b8388d0 auto-redirect to setup when machine disconnects 2025-12-19 20:42:11 +01:00
MLobsien d3a56710d6 fix: format 2025-12-19 18:49:24 +01:00
MLobsien f9f67d3309 fix: update fans periodically 2025-12-19 18:10:47 +01:00
MLobsien 66122ad957 fix: rename target_revolutions to max_revolutions 2025-12-19 17:54:35 +01:00
MLobsien 95feaf46bf Merge branch '983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page' of github.com:qitechgmbh/control into 983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page 2025-12-19 17:51:20 +01:00
MLobsien 88f14e0329 fix: emit max_revolutions
- resolves an issue in the UI where the setting wouldnt update
2025-12-19 17:50:00 +01:00
15i1i-s7v 5bf19af844 fix: cargo fmt issues 2025-12-19 17:38:11 +01:00
MLobsien 19e2a337cf Merge branch '983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page' of github.com:qitechgmbh/control into 983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page 2025-12-19 17:27:30 +01:00
MLobsien 0fd47cfcb1 fix: adjust fan speed in backend
- fine tuning is still necessary
2025-12-19 17:25:58 +01:00
15i1i-s7v e0548d9a65 Merge branch '983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page' of github.com:qitechgmbh/control into 983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page 2025-12-19 17:14:54 +01:00
15i1i-s7v 259ed30dd5 style(aquapath): improve control layout spacing and remove unused import 2025-12-19 17:10:49 +01:00
MLobsien 2e028f0c0f fix: front and back cooling were switched
- use Badge instead of Status Badge
2025-12-19 16:58:15 +01:00
MLobsien 1328c6ce03 Feat: display heating/cooling state in control page 2025-12-19 16:13:48 +01:00
MLobsien adc06623cd Fix: website layout
- variable naming issues in the frontend namespace
- emit state in backend after request to update UI
2025-12-19 14:29:38 +01:00
MLobsien 172ad0ab28 Merge branch '983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page' of github.com:qitechgmbh/control into 983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page 2025-12-19 13:39:40 +01:00
MLobsien bfabb5aa13 feat: UI for temperature tolerance control
- added ui components
- correct frontend api to match backend
2025-12-19 13:38:14 +01:00
15i1i-s7v 94d5a844cf feat: add heating and cooling tolerance functions for front and back
- Added `setFrontHeatingTolerance` and `setBackHeatingTolerance` functions to update heating tolerance values.
- Added `setFrontCoolingTolerance` and `setBackCoolingTolerance` functions to update cooling tolerance values.
- Updated `fanStateSchema` to use `max_revolutions` instead of `target_revolutions`.
- Introduced mutation hooks for handling tolerance updates for front and back heating/cooling.
2025-12-19 13:36:28 +01:00
MLobsien 75c528a430 Merge branch '983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page' of github.com:qitechgmbh/control into 983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page 2025-12-19 11:53:14 +01:00
MLobsien 36045455db WIP: API for heating/cooling tolerance 2025-12-19 11:52:26 +01:00
15i1i-s7v bb12f96723 style: apply code formatting
- Reformatted `controller.rs` and `mod.rs` for better readability.
- Adjusted indentation and spacing for consistency.
2025-12-19 11:48:38 +01:00
15i1i-s7v 8151c99944 fix: simplify cooling logic and remove unnecessary .clone()
- Used `.abs()` directly on the temperature difference instead of duplicating the operation.
- Updated the condition for activating the cooling relay to depend on `cooling_allowed`.
- Refactored the fan speed control to pass the revolutions directly without converting to `AngularVelocity`.
2025-12-19 11:44:24 +01:00
MLobsien 6408010aff Add heating tolerance for controller 2025-12-19 11:23:21 +01:00
MLobsien c96c96217a Merge branch '983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page' of github.com:qitechgmbh/control into 983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page 2025-12-19 11:14:36 +01:00
MLobsien 93d179a81d WIP: adjust cooling automatically 2025-12-19 11:13:57 +01:00
15i1i-s7v 0f6f2240fc fix: electron fmt issues 2025-12-19 10:48:40 +01:00
15i1i-s7v 74505c6c22 fix: fmt issues resolved 2025-12-19 10:41:23 +01:00
15i1i-s7v 3dfb879101 fix: clean up imports and destructure variables
- Removed unused `use` import in `useAquapath.ts`.
- Added missing `React` import in `Aquapath1Settings.tsx`.
- Refactored variable destructuring in `useAquapath.ts` for readability.
2025-12-19 10:35:42 +01:00
MLobsien 44d4c650cc Fix: format 2025-12-18 17:45:07 +01:00
MLobsien 3121679205 feat: control aquapath fans via UI
- time series is displayed on control page
- device is properly addressed
2025-12-18 17:34:08 +01:00
MLobsien 9fe5e582b0 Refactor: use EditValue components instead of TouchSlider 2025-12-18 16:52:43 +01:00
MLobsien 3e6b838757 Refactor(aquapath1): rename zod fields to match backend
- 'front/back_fan_rpm' -> 'front/back_revolutions'
- integrate a graph view of the fan speed to the config page
2025-12-18 15:56:57 +01:00
MLobsien 3d7bcfecca Feat: Add aquapath fan control to controllers update function 2025-12-18 11:40:27 +01:00
Snacj 3791ee785d Fixed Spelling 2025-12-18 11:15:00 +01:00
snacj 7341dee976 Added docs 2025-12-18 11:15:00 +01:00
MLobsien 63a472a1ba refactor(aquapath1): rename fan RPM to revolutions and add target tracking
- Rename all occurrences of 'rpm' to 'revolutions' to align with physical
  property naming conventions.
- Update FanState and Controller to distinguish between current and
  target revolutions.
- Refactor Controller fields to use the AngularVelocity type for improved
  type safety and unit handling.
- Update MachineApi mutations and event building to reflect the new
  data structure.
2025-12-18 10:56:42 +01:00
MLobsien df6b9e72c0 Merge branch '983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page' of github.com:qitechgmbh/control into 983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page
fix indentation
2025-12-18 10:22:16 +01:00
MLobsien 6ad4b9c8bb WIP: add fan support 2025-12-18 10:19:52 +01:00
15i1i-s7v 4fcc8dfe12 fix: Change fan RPM type from f32 to f64 for precision
- Updated the `front_fan_rpm` and `back_fan_rpm` fields in `LiveValuesEvent` struct from f32 to f64.
- Adjusted the `FanState` struct to use f64 for the `rpm` field.
- Modified the `SetFrontFan` and `SetBackFan` mutation variants to expect f64 values instead of f32.
2025-12-18 10:08:10 +01:00
MLobsien 1da64c9cea WIP: AquaPath settings page can be rendered 2025-12-17 17:25:06 +01:00
MLobsien 9b6912be8d WIP: Added fan rpm to AquaPath API 2025-12-17 17:10:22 +01:00
15i1i-s7v 3beed9478c feat(aquapath): add fan RPM data and related schemas to live values (untested)
- Introduced fan RPM values for both front and back fans to AquaPathV1 live values.
- Updated TypeScript schemas to include fan state and RPM data.
- Added time series for tracking fan RPM.
- Updated event handling to include fan RPM data (untested).
2025-12-17 16:06:40 +01:00
15i1i-s7v 3715728fc6 feat: Add fan RPM data to AquaPathV1 live values
- Included front and back fan RPM values from the cooling controllers.
2025-12-17 15:23:59 +01:00
MLobsien 5beee78cad Add a second slider to AquaPath Config Page
- Both fans should be adjustable individually
- Therefor the second slider for the other fan
2025-12-17 15:05:01 +01:00
MLobsien 32fe774e62 Merge branch '983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page' of github.com:qitechgmbh/control into 983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page 2025-12-17 15:01:22 +01:00
MLobsien dead77b49c Change intern representation of fan rpm to f32 2025-12-17 14:58:43 +01:00
MLobsien d7aa976eb1 Merge branch '983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page' of github.com:qitechgmbh/control into 983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page 2025-12-17 14:53:47 +01:00
15i1i-s7v 6f9e89d616 fix(aquapath): correct SetFrontFan mutation name typo 2025-12-17 14:53:25 +01:00
MLobsien bb3f6c0d5a Feat: Implement fan rpm
- extend 'emit_state' to integrate FanState
- implement fan rpm setter function for AquaPathV1 struct
2025-12-17 14:51:46 +01:00
15i1i-s7v 620974ac59 feat(aquapath): implement fan RPM monitoring and control API
Add support for reading fan speeds and setting target RPM values
for both front and back fans in the Aquapath V1 machine.
2025-12-17 14:47:36 +01:00
15i1i-s7v 6c78ff95e4 Merge branch '983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page' of github.com:qitechgmbh/control into 983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page 2025-12-17 13:30:19 +01:00
15i1i-s7v a814236bfa Add settings page and route for Aquapath1 2025-12-17 13:27:52 +01:00
MLobsien 2bee030410 set max temperature of aquapath to 80°C 2025-12-17 12:11:58 +01:00
Hamid-el 285ad59b08 Merge branch '983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page' of https://github.com/qitechgmbh/control into 983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page 2025-12-17 11:58:43 +01:00
Hamid-el f8b861cb39 WIP: add configpage for aquapath 2025-12-17 11:57:17 +01:00
MLobsien f6a3aa6458 Refactor: reorganize 'Control' Page of the AquaPath Machine
- data is now displayed per device instead of per measurement type
2025-12-17 11:40:51 +01:00
MLobsien dd5f3e8e73 Merge branch '983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page' of github.com:qitechgmbh/control into 983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page 2025-12-17 10:36:28 +01:00
EinsPommes f50e32af02 fix: prettier formatting 2025-12-17 10:35:20 +01:00
MLobsien 345e083edd auto-redirect to setup when machine disconnects 2025-12-17 10:35:20 +01:00
MLobsien 5660ddd05f Merge branch '983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page' of github.com:qitechgmbh/control into 983-refactor-improve-aquapath-ui-layout-and-add-fan-speed-configuration-page 2025-12-17 10:17:41 +01:00
rob c4360850af wago modules detected and wrapped as an EtherCatDevice 2025-12-17 10:13:15 +01:00
rob ce2123c88c added logic for initializing wago modules on a 750_354 2025-12-17 10:13:15 +01:00
Christian 520f33bf29 fix: clean up import formatting in registry.rs 2025-12-17 10:13:15 +01:00
Christian c620f1db7f fix mock machines in dev build 2025-12-17 10:13:15 +01:00
Christian d352e88d6f feat: add mock machine build step to CI workflow 2025-12-17 10:13:15 +01:00
EinsPommes cc009b31f3 fix: prettier formatting 2025-12-17 10:13:15 +01:00
EinsPommes b5c189ab49 transform roundness to percentage and update related components 2025-12-17 10:13:15 +01:00
EinsPommes 64f57277f2 fix: prettier formatting 2025-12-17 10:13:15 +01:00
EinsPommes af8ee60fe8 fix: clarify machine existence check in SidebarLayout 2025-12-17 10:13:15 +01:00
EinsPommes beb2e3cc2d remove console.log statement 2025-12-17 10:13:15 +01:00
EinsPommes 0e2bcfd7bc fix: handle machine disconnects properly
The app now detects when a machine disconnects and redirects you
to the setup page. Before, it would just stay on the machine page
even though the machine was gone.

Fixed both backend (USB detection) and frontend (auto-navigation).
Added error handling for parseInt and pathMatch array access.
2025-12-17 10:13:15 +01:00
EinsPommes 417ffb23c2 update: redirect to setup when machine disconnects 2025-12-17 10:13:15 +01:00
EinsPommes a57d4cdf36 auto-redirect to setup when machine disconnects 2025-12-17 10:13:15 +01:00
313 changed files with 30784 additions and 8081 deletions

No files matched your search

-3
View File
@@ -1,3 +0,0 @@
.png filter=lfs diff=lfs merge=lfs -text
.jpg filter=lfs diff=lfs merge=lfs -text
.jpeg filter=lfs diff=lfs merge=lfs -text
+40
View File
@@ -0,0 +1,40 @@
---
name: Bug report
about: Create a report to help us improve
title: 'BUG: '
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Navigate to '...'
2. Select/Press '....'
3. Execute operation '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots/Photos**
If applicable, add screenshots from the HMI display or photos of the machine screen to help explain your problem.
**Machine Information (please complete the following):**
- Machine Type: [Winder2, Laser, Aquapath...]
- Control Software Version: [e.g. 2.4.1]
- Settings: [e.g. Different Gear Ratios, PID Settings, …]
**Device Information (if issue occurs on external device):**
- Device Type: [e.g. Tablet, PLC, Remote Control Panel]
- OS: [e.g. Windows 10, Linux, etc.]
- Connection Type: [e.g. Ethernet, USB]
**Error Messages/Codes**
If applicable, provide exact error codes or messages displayed or found in the troubleshooting terminal.
**Additional context**
Add any other context about the problem here (e.g., recent software updates, environmental factors).
+33
View File
@@ -0,0 +1,33 @@
---
name: Feature request
about: Suggest an idea for this project
title: 'Feat: '
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. During operation, I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions, workarounds, or features you've considered.
**Use Case/Application:**
- Machine Type(s): [e.g. All machines, Winder, Aquapath, ...]
- Frequency of Use: [e.g. Daily, During setup, Occasionally]
**Expected Benefits:**
- Efficiency Improvement: [e.g. Reduces setup time by X minutes, Eliminates manual steps]
- Safety Enhancement: [e.g. Prevents operator error, Adds safety checks]
- Production Impact: [e.g. Increases throughput, Reduces downtime, Improves quality]
- User Experience: [e.g. Simplifies workflow, Reduces training time]
**Mockups/Sketches**
If applicable, add sketches, mockups, or photos showing how the feature would appear on the HMI display or integrate with the machine.
**Additional context**
Add any other context, industry standards, or examples from other control systems about the feature request here.
+52 -29
View File
@@ -3,50 +3,73 @@ name: Electron
on:
push:
branches:
- "**"
- master
pull_request:
branches:
- "*"
- master
defaults:
run:
working-directory: electron
jobs:
build-electron:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: electron
steps:
- name: Setup Node.js
- &checkout
name: Checkout
uses: actions/checkout@v4
- &setup-node
name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "18" # or '20' for latest LTS
node-version: 20
cache: "npm"
cache-dependency-path: |
electron/package-lock.json
- uses: actions/checkout@v4
- &install
name: Install dependencies
run: npm ci
- name: Cache NPM dependencies
uses: actions/cache@v3
with:
path: |
~/.npm
node_modules/
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- name: Install
run: npm i
- name: Build
- name: Compile
run: tsc
- name: Format
run: npm run format
- name: Build
run: npm run build
- name: Lint
run: npm run lint
test:
runs-on: ubuntu-latest
needs: build
steps:
- *checkout
- *setup-node
- *install
- name: Test
run: npm run test
- name: Package
run: npm run package
format:
runs-on: ubuntu-latest
steps:
- *checkout
- *setup-node
- *install
- name: Format
run: npm run format
lint:
runs-on: ubuntu-latest
steps:
- *checkout
- *setup-node
- *install
- name: Lint
run: npm run lint
+96
View File
@@ -0,0 +1,96 @@
name: Nix CI
on:
push:
branches:
- master
pull_request:
branches:
- master
env:
NIX_PATH: nixpkgs=channel:nixos-25.05
jobs:
electron:
runs-on: ubuntu-latest
steps:
- &checkout
name: Check out repository
uses: actions/checkout@v4
- &nix-install
uses: nixbuild/nix-quick-install-action@v34
with:
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- uses: nix-community/cache-nix-action@v6
with:
primary-key: nix-electron-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock', './electron/package-lock.json') }}
restore-prefixes-first-match: nix-${{ runner.os }}-
purge: true
purge-prefixes: nix-electron-${{ runner.os }}-
- name: Build Electron in Nix
run: nix build ".#packages.x86_64-linux.electron"
rust:
runs-on: ubuntu-latest
steps:
- *checkout
- *nix-install
- uses: nix-community/cache-nix-action@v6
with:
primary-key: nix-server-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock', './Cargo.lock') }}
restore-prefixes-first-match: nix-${{ runner.os }}-
purge: true
purge-prefixes: nix-server-${{ runner.os }}-
- name: Build Server in Nix
run: nix build ".#packages.x86_64-linux.server"
build:
runs-on: ubuntu-latest
needs: [electron, rust]
steps:
- *checkout
- *nix-install
- name: Use both Electron and Server Nix Store
uses: nix-community/cache-nix-action@v6
with:
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock', './Cargo.lock', './electron/package-lock.json') }}
# if no hit on the primary key, merge individual caches
restore-prefixes-all-matches: |
nix-electron-${{ runner.os }}-
nix-server-${{ runner.os }}-
- name: Build Flake and System Configuration
run: nix build ".#nixosConfigurations.nixos.config.system.build.toplevel" --impure
format:
runs-on: ubuntu-latest
steps:
- *checkout
- *nix-install
- name: Format
run: nix run 'nixpkgs#nixfmt-classic' $(find . nixos -name '*.nix')
check:
runs-on: ubuntu-latest
steps:
- *checkout
- *nix-install
- name: Build Flake and Configuration.nix
run: nix flake check "." --impure
+46 -10
View File
@@ -3,9 +3,10 @@ name: Rust
on:
push:
branches:
- "**"
- master
pull_request:
branches: "*"
branches:
- master
env:
CARGO_TERM_COLOR: always
@@ -16,14 +17,24 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Install libudev development package
- &udev-install
name: Install libudev development package
run: sudo apt-get update && sudo apt-get install -y libudev-dev
- uses: actions/checkout@v4
- name: Set Rust version
run: rustup override set ${{ env.RUST_VERSION }}
- name: Install Formatter
run: rustup component add rustfmt
- name: Cache Rust dependencies
- &checkout
uses: actions/checkout@v4
- &setup-rust
name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.RUST_VERSION }}
profile: minimal
override: true
components: rustfmt
- &cargo-cache
name: Cache Rust dependencies
uses: actions/cache@v3
with:
path: |
@@ -41,11 +52,36 @@ jobs:
RUSTFLAGS: "-D warnings"
run: cargo build --verbose
format:
runs-on: ubuntu-latest
steps:
- *checkout
- *setup-rust
- name: Format
run: cargo fmt --check --verbose
test:
runs-on: ubuntu-latest
steps:
- *udev-install
- *checkout
- *setup-rust
- *cargo-cache
- name: Run tests
run: cargo test --verbose
mock:
runs-on: ubuntu-latest
steps:
- *udev-install
- *checkout
- *setup-rust
- *cargo-cache
- name: Mock Machine
run: cargo build --features development-build --features mock-machine
run: cargo build --features "development-build,mock-machine"
+80
View File
@@ -0,0 +1,80 @@
name: Update npm deps hash
on:
pull_request:
paths:
- 'electron/package-lock.json'
- 'electron/package.json'
workflow_dispatch:
env:
NIX_PATH: nixpkgs=channel:nixos-25.05
permissions:
contents: write
pull-requests: write
actions: write
jobs:
update-hash:
runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.full_name == github.repository
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v34
with:
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- name: Calculate new npm deps hash
id: calc-hash
run: |
# Calculate the hash using nix run with nixpkgs
NEW_HASH=$(nix run 'nixpkgs#prefetch-npm-deps' electron/package-lock.json)
echo "hash=$NEW_HASH" >> $GITHUB_OUTPUT
echo "Calculated hash: $NEW_HASH"
- name: Update npmDepsHash in electron.nix
run: |
NEW_HASH="${{ steps.calc-hash.outputs.hash }}"
sed -i "s|npmDepsHash = \".*\";|npmDepsHash = \"$NEW_HASH\";|" nixos/packages/electron.nix
- name: Check if hash changed
id: check-changes
run: |
if git diff --quiet nixos/packages/electron.nix; then
echo "changed=false" >> $GITHUB_OUTPUT
echo "No changes needed - hash is already correct"
else
echo "changed=true" >> $GITHUB_OUTPUT
echo "Hash updated in electron.nix"
fi
- name: Commit and push if changed
if: steps.check-changes.outputs.changed == 'true'
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add nixos/packages/electron.nix
git commit -m "chore: update npmDepsHash to ${{ steps.calc-hash.outputs.hash }}"
git push
- name: Comment on PR
if: steps.check-changes.outputs.changed == 'true'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: 'Automatically updated `npmDepsHash` to `${{ steps.calc-hash.outputs.hash }}`\n\n**Next step:** Trigger the checks by pushing an empty commit:\n```bash\ngit commit --allow-empty -m "trigger ci" && git push\n```'
})
+2 -1
View File
@@ -7,4 +7,5 @@ build.sh
shell.nix
node_modules/
runtime_metrics.csv
compile_metrics.csv
compile_metrics.csv
.idea/
View File
Whitespace-only changes.
+32
View File
@@ -1,3 +1,35 @@
# `2.14.1`
_26.01.2026_
## Laser
-[#1079](https://github.com/qitechgmbh/control/pull/1079) Fixed Overflowing of buffer for Graphs causing crashes
# `2.14.0`
_22.01.2026_
## General
- [#1040](https://github.com/qitechgmbh/control/pull/1040) Added API for reading Machine Data
# `2.13.0`
_07.01.2026_
## General
- [#925](https://github.com/qitechgmbh/control/pull/925) Switch to Setup when selected machine is disconnected
- [#937](https://github.com/qitechgmbh/control/pull/937) Added minimal Support for WAGO
- [#945](https://github.com/qitechgmbh/control/pull/945) Improvements for On-Screen Keyboard usage in Ethercat Setup Page
## Extruder V3
- [#930](https://github.com/qitechgmbh/control/pull/930) Added Extruder V3
## Laser
- [#924](https://github.com/qitechgmbh/control/pull/924) Roundness is now displayed as a percentage
- [#885](https://github.com/qitechgmbh/control/pull/885) Added Min and Max Diameter
- [#849](https://github.com/qitechgmbh/control/pull/849) Added a Warning if Filament is out of Spec
## Aquapath
- [#976](https://github.com/qitechgmbh/control/pull/976) Improved Stability
# `2.12.0`
_17.11.2025_
Generated
+56 -46
View File
@@ -204,7 +204,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
@@ -221,7 +221,7 @@ checksum = "99e1aca718ea7b89985790c94aad72d77533063fe00bc497bb79a7c2dae6a661"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
@@ -232,9 +232,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "axum"
version = "0.8.7"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425"
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
dependencies = [
"axum-core",
"axum-macros",
@@ -291,7 +291,7 @@ checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
@@ -390,9 +390,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.11.0"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
dependencies = [
"serde",
]
@@ -539,7 +539,7 @@ version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
@@ -682,7 +682,7 @@ dependencies = [
"deluxe-macros",
"once_cell",
"proc-macro2",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
@@ -695,7 +695,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
@@ -710,7 +710,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
@@ -721,7 +721,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
@@ -962,14 +962,13 @@ dependencies = [
"deluxe",
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
name = "ethercrab"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbb13becfa15713adb09b27fdb74468b827cdc78aa99aea638a89c1b0db3ce94"
source = "git+https://github.com/v-morlock/ethercrab?branch=main#d297d13389d85a4edb4e69cc98f9dd7f12626cac"
dependencies = [
"async-io",
"atomic-waker",
@@ -998,8 +997,7 @@ dependencies = [
[[package]]
name = "ethercrab-wire"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d15828cb020fb44c92ca67e1ee3c122c9621c2e00d383d066e488f0619023ba2"
source = "git+https://github.com/v-morlock/ethercrab?branch=main#d297d13389d85a4edb4e69cc98f9dd7f12626cac"
dependencies = [
"ethercrab-wire-derive",
"heapless",
@@ -1008,20 +1006,19 @@ dependencies = [
[[package]]
name = "ethercrab-wire-derive"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e790b8011211ed70babfcacbd4277b91ee21402ad5ba6a2c293db02e2f12919"
source = "git+https://github.com/v-morlock/ethercrab?branch=main#d297d13389d85a4edb4e69cc98f9dd7f12626cac"
dependencies = [
"criterion",
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
name = "euclid"
version = "0.22.11"
version = "0.22.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48"
checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63"
dependencies = [
"num-traits",
]
@@ -1878,7 +1875,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
@@ -1908,7 +1905,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
@@ -2221,7 +2218,7 @@ checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
@@ -2251,20 +2248,20 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
name = "serde_json"
version = "1.0.145"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
"zmij",
]
[[package]]
@@ -2332,6 +2329,7 @@ dependencies = [
"smol",
"socketioxide",
"spin_sleep",
"ta",
"textplots",
"tokio",
"tower-http",
@@ -2573,9 +2571,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.111"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2",
"quote",
@@ -2588,6 +2586,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
[[package]]
name = "ta"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "609409d472a0a7d8d4dd9e19891bbdef546b9dce670c3057d0e02192dc541226"
[[package]]
name = "tap"
version = "1.0.1"
@@ -2621,7 +2625,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
@@ -2681,7 +2685,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
@@ -2825,9 +2829,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
version = "0.1.41"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"log",
"pin-project-lite",
@@ -2837,20 +2841,20 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.30"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
name = "tracing-core"
version = "0.1.34"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
"valuable",
@@ -2880,9 +2884,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.20"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
dependencies = [
"chrono",
"matchers",
@@ -3074,7 +3078,7 @@ dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
"wasm-bindgen-shared",
]
@@ -3139,7 +3143,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
@@ -3150,7 +3154,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
@@ -3426,5 +3430,11 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
"syn 2.0.114",
]
[[package]]
name = "zmij"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445"
+45 -131
View File
@@ -2,162 +2,76 @@
# QiTech Control
QiTech Control is an industrial control panel software for the next generation of QiTech recycling machines built on top of Beckhoff Automation hardware.
QiTech Control is an open-source framework designed to bring modern software development practices to certified industrial hardware.
[![](https://img.youtube.com/vi/KI3YeBwfV-s/maxresdefault.jpg)](https://www.youtube.com/watch?v=KI3YeBwfV-s)
*Click here to watch a video demo of our software.*
It frees developers from proprietary, license-heavy PLC ecosystems and rigid "point-and-click" workflows that are no longer adequate for today's complex automation challenges.
[![](https://img.youtube.com/vi/55egCAkQgyM/maxresdefault.jpg)](https://youtu.be/55egCAkQgyM)
*Click here to watch a full explainer Video of our Software.*
QiTech Control combines the modularity and reliability of standard EtherCAT terminals (e.g., WAGO, Beckhoff) with the power of a modern Rust & React stack.
# Repo Structure
## Documentation
Frontend
**[View Full Documentation Wiki](https://github.com/qitechgmbh/control/wiki)**
- `/electron`: Frontend code for the control software built with React and Electron.
- [Getting Started](https://github.com/qitechgmbh/control/wiki/Getting-Started)
- [Architecture Overview](https://github.com/qitechgmbh/control/wiki/Architecture-Overview)
- [EtherCAT Basics](https://github.com/qitechgmbh/control/wiki/Ethercat-Basics)
- [Device Examples](https://github.com/qitechgmbh/control/wiki/Hardware-Examples)
- [REST API Reference](https://github.com/qitechgmbh/control/wiki/Rest-Api)
Backend
## Videos
- `/server`: Glue between Beckhoff and Electron. Implements machine logic.
- `/ethercat-hal`: Hardware abstraction layer for Beckhoff (and possibly other EtherCat) devices and protocols.
- `/ethercat-hal-derive`: Macros for `ethercat-hal`
- `/control-core`: Core control logic for the server.
| Software Demo | Full Explainer |
|---------------|----------------|
| [![](https://img.youtube.com/vi/KI3YeBwfV-s/maxresdefault.jpg)](https://www.youtube.com/watch?v=KI3YeBwfV-s)<br/>*Watch a demo of the control software in action* | [![](https://img.youtube.com/vi/55egCAkQgyM/maxresdefault.jpg)](https://youtu.be/55egCAkQgyM)<br/>*Watch a complete overview of QiTech Control* |
Operating System
## Repository Structure
- `/nixos`: Custom Linux with realtime kernel & preconfigured for touchscreens.
- **`/electron`** - React + Electron frontend
- **`/server`** - Rust backend implementing machine logic
- **`/ethercat-hal`** - Hardware abstraction layer for EtherCAT devices
- **`/control-core`** - Core control logic
- **`/nixos`** - Custom Linux OS with realtime kernel
Other
## Technology Stack
- `/docs`: Documentation for the project.
**Backend:** Rust with [Ethercrab](https://github.com/ethercrab-rs/ethercrab) for EtherCAT, [SocketIO](https://socket.io/) for real-time communication, [axum](https://docs.rs/axum/latest/axum/) for REST API
# Technology Choices
## Backend
To interface with Beckhoff and other EtherCAT devices we need an EtherCAT master software. Possibilities are [PySOEM](https://github.com/bnjmnp/pysoem) (Python), [SOEM](https://github.com/OpenEtherCATsociety/SOEM) (C) and [Ethercrab](https://github.com/ethercrab-rs/ethercrab) (Rust). For realtime operation only C and Rust are suitable. We chose Rust because of safety and confidence in the written code.
[SocketIO](https://socket.io/) was chosen for performant event driven communication from the backend to the server. But we still use REST with [axum](https://docs.rs/axum/latest/axum/) for the communication thet benefits from the request/response model.
We use [Smol](https://github.com/smol-rs/smol) for EtherCAT IO in the control loop for it's performance and [Tokio](https://tokio.rs/) for server IO because of it's ecosystem and maturity.
## Frontend
We could combine the code of the frontend and backend using [Doxius](https://dioxuslabs.com/) but it lacks good Linux support. We chose [Electron](https://www.electronjs.org/) with [React](https://react.dev/) for it's maturity and ecosystem. For the UI we use [Shadcn](https://ui.shadcn.com/) components and [Tailwind](https://tailwindcss.com/) for styling. For routing we use [TanStack Router](https://tanstack.com/router/v1).
# Dev Setup
[Developer Documentation](./docs/developer-docs/)
## Backend
- Rust stable 1.86^ toolchain (install via [rustup](https://rustup.rs/))
- `rust-analyzer` extension for VSCode
- Set your interface in `server/src/ethercat/init.rs` like `en10`
- Connect a Beckhoff EK1100 to your interface
- run `cd server && cargo run` to start the server (localhost:3001)
## Frontend
- nodejs and npm installed
- run `cd electron && npm i && npm run start` to start the frontend
# Machines
| Machine Type | Version | Release Date | Description | Change to Previous Version | Vendor ID | Machine ID | Implemented | Docs |
| ------------ | ------- | ------------ | --------------------------- | ------------------------------------------------------ | -------------------------- | ---------- | ----------- | ------------------------------- |
| Winder | V1 | ??? | Winding Filaments & Similar | - | 1 (Qitech Industries GmbH) | 1 | Reserved | - |
| Winder | V2 | 2025 | Winding Filaments & Similar | Reengineered Traverse | 1 (Qitech Industries GmbH) | 2 | Yes | [](./docs/machines/winder-1.md) |
| Extruder | V1 | ??? | Single Screw Extruder | - | 1 (Qitech Industries GmbH) | 3 | Reserved | - |
| Extruder | V2 | 2025 | Single Screw Extruder | PT100 Thermometers, Optional Additional Heating Zone 4 | 1 (Qitech Industries GmbH) | 4 | Yes | |
| Waterway | V1 | 2025 | Filament Water Cooling | - | 1 (Qitech Industries GmbH) | 5 | In Progress | |
| Laser | V1 | ??? | Diameter Measuring Laser | - | 1 (Qitech Industries GmbH) | 6 | Yes | |
| Mock | - | ??? | Mock Machine for Testing | - | 1 (Qitech Industries GmbH) | 7 | Yes | - |
# Minimal Hardware Examples
For complete step-by-step tutorials on setting up your first hardware, including wiring diagrams and software configuration, see the [Getting Started Guide](./docs/developer-docs/getting-started.md#minimal-hardware-examples).
Available examples:
- **LED Control with EL2004** - Digital output control (simplest setup)
- **Analog Input with EL3021** - Reading analog current measurements
**Frontend:** [Electron](https://www.electronjs.org/) + [React](https://react.dev/) with [Shadcn](https://ui.shadcn.com/) components and [Tailwind](https://tailwindcss.com/) styling
# More Docs
## Hardware Examples
- [x] [Architecture & Data Flow](./docs/architecture-overview.md)
QiTech Control supports a wide range of EtherCAT hardware from WAGO and Beckhoff. We provide complete step-by-step tutorials with wiring diagrams and software configuration for many common modules.
- [x] Example Winder V2
**[View all hardware examples and tutorials on the wiki](https://github.com/qitechgmbh/control/wiki/Hardware-Examples)**
- [ ] Electron
Quick links to popular examples:
- [EL2004 - LED Control](https://github.com/qitechgmbh/control/wiki/Minimal-Example-El2004) (simplest setup for beginners)
- [EL3021 - Analog Input](https://github.com/qitechgmbh/control/wiki/Minimal-Example-El3021)
- [EL7031 - Stepper Motor Control](https://github.com/qitechgmbh/control/wiki/Minimal-Example-El7031-Motor)
- [WAGO 750-402 - Digital Input](https://github.com/qitechgmbh/control/wiki/Minimal-Example-Wago750-402)
- [WAGO 750-455 - Analog Input](https://github.com/qitechgmbh/control/wiki/Minimal-Example-Wago-750-455)
- Folder Structure
- Routing with TanStack Router
- Design with Tailwind & Shadcn
- ...
## QiTech Machines
- [ ] Interfacing with Electron/Server
QiTech Control powers 10+ production machines for filament extrusion, winding, measurement, and material processing.
- [ ] SocketIO
- Machine Namespace
- Main Namespace
- [ ] REST
- Machine Mutations
- Write Device Identification
**[View complete machine catalog on the wiki](https://github.com/qitechgmbh/control/wiki/QiTech-Machines)**
- 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.
## Contributing
- [ ] Control Core
This is an open-source project. Contributions are welcome! Please see the [wiki](https://github.com/qitechgmbh/control/wiki) for development guidelines.
- [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)
## Additional Videos
- [x] [Ethercat Basics](./docs/ethercat-basics.md)
| Episode Highlights | |
|--------------------|--------------------|
| [![](https://img.youtube.com/vi/U2wNN6sF4to/maxresdefault.jpg)](https://www.youtube.com/watch?v=U2wNN6sF4to&t=163s)<br/>**Day in the Software Team**<br/>*Behind the scenes with Robin and how the team ships control software.* | [![](https://img.youtube.com/vi/R5WGAY3WRWA/maxresdefault.jpg)](https://www.youtube.com/watch?v=R5WGAY3WRWA&t=1s)<br/>**Software Stack Day**<br/>*A fast tour through the tools, stack, and daily workflow.* |
| [![](https://img.youtube.com/vi/xyaHwxXguw4/maxresdefault.jpg)](https://www.youtube.com/watch?v=xyaHwxXguw4&t=32s)<br/>**First Client**<br/>*A milestone week building the first real client project.* | [![](https://img.youtube.com/vi/moI7z2PVdF4/maxresdefault.jpg)](https://www.youtube.com/watch?v=moI7z2PVdF4)<br/>**Steelworks Week**<br/>*A deep-dive week on a demanding steelworks order.* |
- [x] [NixOS Operating System](./docs/nixos/README.md)
## License
See [LICENSE](LICENSE) file for details.
+6 -3
View File
@@ -4,10 +4,13 @@ set -e
FEATURE=""
# Check if "mock-machine" was passed as an argument
# Check if mock features were passed as an argument
if [[ "$1" == "mock-machine" ]]; then
echo "Building Debug Code with mock-machine feature"
FEATURE="--features mock-machine"
elif [[ "$1" == "gluetex-mock" ]]; then
echo "Building Debug Code with gluetex-mock feature"
FEATURE="--features gluetex-mock"
fi
if [[ "$1" == "release" ]]; then
@@ -18,8 +21,8 @@ else
cargo build $FEATURE --features development-build
fi
# Only setcap if not building with mock-machine
if [ "$1" != "mock-machine" ]; then
# Only setcap if not building with mock features
if [ "$1" != "mock-machine" ] && [ "$1" != "gluetex-mock" ]; then
echo "Setting capabilities for server executable"
sudo setcap 'cap_dac_override,cap_net_raw,cap_sys_nice,cap_ipc_lock=eip' ./target/debug/server
+3 -3
View File
@@ -9,11 +9,11 @@ workspace = true
[dependencies]
anyhow = "1.0.100"
axum = "0.8.6"
axum = "0.8.8"
ethercat_hal = { path = "../ethercat-hal" }
units = { path = "../units" }
ethercrab = "0.6"
ethercrab = { git = "https://github.com/v-morlock/ethercrab", branch = "main" }
interfaces = "0.0.9"
serde = "1.0.219"
serialport = "4.7.3"
@@ -21,7 +21,7 @@ smol = "2.0.2"
socketioxide = "0.17.2"
bitvec = { version = "1.0.1", features = ["alloc"] }
crc = "3.3.0"
tracing = "0.1.41"
tracing = "0.1.44"
erased-serde = "0.4.8"
core_affinity = "0.8.3"
control_core_derive = { version = "0.1.0", path = "../control-core-derive" }
+1
View File
@@ -1,4 +1,5 @@
pub mod clamping_timeagnostic_pid;
pub mod first_degree_motion;
pub mod pid;
pub mod pid_autotuner;
pub mod second_degree_motion;
@@ -0,0 +1,438 @@
//! PID Auto-tuner based on Klipper's algorithm
//!
//! This implementation uses the Astrom-Hagglund relay method to identify
//! system oscillations, then applies Ziegler-Nichols tuning rules to
//! calculate PID parameters.
//!
//! The algorithm works by:
//! 1. Heating to target + delta, then cooling to target - delta repeatedly
//! 2. Recording temperature peaks during these oscillations
//! 3. Calculating the ultimate gain (Ku) and period (Tu) from peak data
//! 4. Applying Ziegler-Nichols rules to derive Kp, Ki, Kd values
use std::time::Instant;
/// Configuration for PID auto-tuning
#[derive(Debug, Clone)]
pub struct AutoTuneConfig {
/// Temperature oscillation amplitude (e.g., ±5°C)
pub tune_delta: f64,
/// Maximum heating power (0.0 to 1.0)
pub max_power: f64,
/// Maximum duration for auto-tuning in seconds
pub max_duration_secs: f64,
}
impl Default for AutoTuneConfig {
fn default() -> Self {
Self {
tune_delta: 5.0,
max_power: 1.0,
max_duration_secs: 3600.0, // 60 minutes
}
}
}
/// Result of PID auto-tuning
#[derive(Debug, Clone)]
pub struct AutoTuneResult {
pub kp: f64,
pub ki: f64,
pub kd: f64,
/// Ultimate gain (Ku) used in calculation
pub ku: f64,
/// Ultimate period (Tu) used in calculation
pub tu: f64,
}
/// State of the auto-tuning process
#[derive(Debug, Clone, Copy, PartialEq)]
enum AutoTuneState {
NotStarted,
Running,
Completed,
Failed,
}
/// PID auto-tuner implementation
#[derive(Debug)]
pub struct PidAutoTuner {
config: AutoTuneConfig,
state: AutoTuneState,
// Target temperature
target_temp: f64,
// Heating control
heating: bool,
// Peak tracking
peak_value: f64,
peak_time: Instant,
peaks: Vec<(f64, Instant)>,
// Timing
start_time: Option<Instant>,
// Results
pub result: Option<AutoTuneResult>,
}
impl PidAutoTuner {
/// Create a new PID auto-tuner with given configuration
pub fn new(config: AutoTuneConfig) -> Self {
Self {
config,
state: AutoTuneState::NotStarted,
target_temp: 0.0,
heating: false,
peak_value: 0.0,
peak_time: Instant::now(),
peaks: Vec::new(),
start_time: None,
result: None,
}
}
/// Start the auto-tuning process
pub fn start(&mut self, now: Instant, target_temp: f64) {
self.state = AutoTuneState::Running;
self.target_temp = target_temp;
self.heating = true;
self.peak_value = 9999999.0; // When heating, track minimum (valley)
self.peak_time = now;
self.peaks.clear();
self.start_time = Some(now);
self.result = None;
}
/// Stop the auto-tuning process
pub fn stop(&mut self) {
if self.state == AutoTuneState::Running {
self.state = AutoTuneState::Failed;
}
}
/// Update the auto-tuner with current temperature
/// Returns (duty_cycle, completed)
pub fn update(&mut self, current_temp: f64, now: Instant) -> (f64, bool) {
if self.state != AutoTuneState::Running {
return (0.0, true);
}
// Check for timeout
if let Some(start) = self.start_time {
let elapsed = now.duration_since(start).as_secs_f64();
if elapsed > self.config.max_duration_secs {
self.state = AutoTuneState::Failed;
return (0.0, true);
}
}
// Temperature targets for oscillation
let upper_target = self.target_temp + self.config.tune_delta;
let lower_target = self.target_temp - self.config.tune_delta;
// Track peak values BEFORE checking for crossing
if self.heating {
// When heating, track minimum (valley)
if current_temp < self.peak_value {
self.peak_value = current_temp;
self.peak_time = now;
}
} else {
// When cooling, track maximum (peak)
if current_temp > self.peak_value {
self.peak_value = current_temp;
self.peak_time = now;
}
}
// Check if we've crossed the target and need to switch heating/cooling
if self.heating && current_temp >= upper_target {
self.heating = false;
self.record_peak();
} else if !self.heating && current_temp <= lower_target {
self.heating = true;
self.record_peak();
}
// Check if we have enough peaks to calculate PID
if self.peaks.len() >= 12 {
self.calculate_pid();
self.state = AutoTuneState::Completed;
return (0.0, true);
}
// Return appropriate duty cycle
let duty_cycle = if self.heating {
self.config.max_power
} else {
0.0
};
(duty_cycle, false)
}
/// Record a peak in the oscillation
fn record_peak(&mut self) {
self.peaks.push((self.peak_value, self.peak_time));
// Reset peak tracking for next cycle (following Klipper's logic)
if self.heating {
self.peak_value = 9999999.0; // When heating, we want to find the minimum (valley)
} else {
self.peak_value = -9999999.0; // When cooling, we want to find the maximum (peak)
}
}
/// Calculate PID parameters from recorded peaks
fn calculate_pid(&mut self) {
if self.peaks.len() < 4 {
self.state = AutoTuneState::Failed;
return;
}
// Find the cycle times and select the median cycle
let mut cycle_times: Vec<(f64, usize)> = Vec::new();
for pos in 4..self.peaks.len() {
let time_diff = self.peaks[pos]
.1
.duration_since(self.peaks[pos - 2].1)
.as_secs_f64();
cycle_times.push((time_diff, pos));
}
// Sort by time and pick the median
cycle_times.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
let midpoint_pos = if cycle_times.is_empty() {
self.state = AutoTuneState::Failed;
return;
} else {
cycle_times[cycle_times.len() / 2].1
};
// Calculate PID parameters using this cycle
if let Some(result) = self.calculate_pid_at_position(midpoint_pos) {
self.result = Some(result);
self.state = AutoTuneState::Completed;
} else {
self.state = AutoTuneState::Failed;
}
}
/// Calculate PID at a specific peak position using Astrom-Hagglund and Ziegler-Nichols
fn calculate_pid_at_position(&self, pos: usize) -> Option<AutoTuneResult> {
if pos < 2 || pos >= self.peaks.len() {
return None;
}
// Calculate temperature amplitude and period
let temp_diff = self.peaks[pos].0 - self.peaks[pos - 1].0;
let time_diff = self.peaks[pos]
.1
.duration_since(self.peaks[pos - 2].1)
.as_secs_f64();
// Astrom-Hagglund method to estimate Ku and Tu
let amplitude = 0.5 * temp_diff.abs();
let ku = 4.0 * self.config.max_power / (std::f64::consts::PI * amplitude);
let tu = time_diff;
// Ziegler-Nichols method to generate PID parameters
let ti = 0.5 * tu;
let td = 0.125 * tu;
let kp = 0.4 * ku;
let ki = kp / ti;
let kd = kp * td;
Some(AutoTuneResult { kp, ki, kd, ku, tu })
}
/// Check if auto-tuning is completed successfully
pub fn is_completed(&self) -> bool {
self.state == AutoTuneState::Completed
}
/// Check if auto-tuning failed
pub fn is_failed(&self) -> bool {
self.state == AutoTuneState::Failed
}
/// Check if auto-tuning is running
pub fn is_running(&self) -> bool {
self.state == AutoTuneState::Running
}
/// Get the current state
pub fn state(&self) -> &str {
match self.state {
AutoTuneState::NotStarted => "not_started",
AutoTuneState::Running => "running",
AutoTuneState::Completed => "completed",
AutoTuneState::Failed => "failed",
}
}
/// Get progress as a percentage (0-100)
pub fn get_progress_percent(&self) -> f64 {
if self.state != AutoTuneState::Running {
return if self.state == AutoTuneState::Completed {
100.0
} else {
0.0
};
}
// Progress is based on number of peaks collected (need 12)
let progress = (self.peaks.len() as f64 / 12.0) * 100.0;
progress.min(99.0) // Cap at 99% until actually completed
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_autotuner_creation() {
let config = AutoTuneConfig::default();
let tuner = PidAutoTuner::new(config);
assert_eq!(tuner.state(), "not_started");
assert_eq!(tuner.get_progress_percent(), 0.0);
}
#[test]
fn test_autotuner_start() {
let config = AutoTuneConfig::default();
let mut tuner = PidAutoTuner::new(config);
let now = Instant::now();
tuner.start(now, 150.0);
assert_eq!(tuner.state(), "running");
assert!(tuner.is_running());
assert_eq!(tuner.target_temp, 150.0);
}
#[test]
fn test_autotuner_stop() {
let config = AutoTuneConfig::default();
let mut tuner = PidAutoTuner::new(config);
let now = Instant::now();
tuner.start(now, 150.0);
tuner.stop();
assert!(tuner.is_failed());
}
#[test]
fn test_autotuner_oscillation() {
let config = AutoTuneConfig {
tune_delta: 5.0,
max_power: 1.0,
max_duration_secs: 3600.0, // 1 hour
};
let mut tuner = PidAutoTuner::new(config);
let mut now = Instant::now();
tuner.start(now, 150.0);
// Simulate temperature oscillations
// Should heat to 155, then cool to 145, repeatedly
let mut temp = 140.0;
let mut completed = false;
let mut iterations = 0;
for cycle in 0..20 {
// Heating phase
while temp < 155.0 && !completed && iterations < 1000 {
temp += 0.5; // Simulate temperature rise
now += Duration::from_millis(100);
let (duty_cycle, done) = tuner.update(temp, now);
completed = done;
iterations += 1;
if !completed && !tuner.heating {
// We switched to cooling during heating phase - this is expected near the boundary
println!("Switched to cooling at temp={}", temp);
break;
}
if completed || temp >= 155.0 {
break;
}
}
if completed {
break;
}
// Cooling phase
while temp > 145.0 && !completed && iterations < 1000 {
temp -= 0.3; // Simulate temperature drop
now += Duration::from_millis(100);
let (duty_cycle, done) = tuner.update(temp, now);
completed = done;
iterations += 1;
if !completed && tuner.heating {
// We switched to heating during cooling phase - this is expected near the boundary
println!("Switched to heating at temp={}", temp);
break;
}
if completed || temp <= 145.0 {
break;
}
}
if completed {
break;
}
// Check progress
if cycle % 3 == 0 {
let progress = tuner.get_progress_percent();
println!("Cycle {}: Progress {}%", cycle, progress);
}
}
assert!(
completed,
"Auto-tuning should complete after sufficient oscillations"
);
assert!(tuner.is_completed(), "Should be in completed state");
assert!(tuner.result.is_some(), "Should have a result");
let result = tuner.result.as_ref().unwrap();
println!(
"Auto-tune result: Kp={:.3}, Ki={:.3}, Kd={:.3}",
result.kp, result.ki, result.kd
);
println!("Ku={:.3}, Tu={:.3}", result.ku, result.tu);
// Basic sanity checks
assert!(result.kp > 0.0, "Kp should be positive");
assert!(result.ki > 0.0, "Ki should be positive");
assert!(result.kd > 0.0, "Kd should be positive");
}
#[test]
fn test_autotuner_timeout() {
let config = AutoTuneConfig {
tune_delta: 5.0,
max_power: 1.0,
max_duration_secs: 1.0, // Very short timeout
};
let mut tuner = PidAutoTuner::new(config);
let mut now = Instant::now();
tuner.start(now, 150.0);
// Wait for timeout
now += Duration::from_secs(2);
let (_, completed) = tuner.update(150.0, now);
assert!(completed, "Should timeout");
assert!(tuner.is_failed(), "Should fail on timeout");
}
}
+65
View File
@@ -0,0 +1,65 @@
use std::process::Command;
use anyhow::Result;
use interfaces::Interface;
pub mod modbus_tcp_discovery;
/// Returns true if the given network interface is Ethernet.
/// Prevents testing wlan,loopback and other non ethernet devices
fn is_ethernet(interface: &str) -> std::io::Result<bool> {
#[cfg(target_os = "linux")]
{
let base_path = format!("/sys/class/net/{}", interface);
let type_path = std::path::Path::new(&base_path).join("type");
let iface_type = std::fs::read_to_string(&type_path)?.trim().to_string();
// 1 means Ethernet
let reports_as_ethernet = iface_type == "1";
// Double-check that it's not a wireless interface
let uevent_path = std::path::Path::new(&base_path).join("uevent");
let uevent = std::fs::read_to_string(&uevent_path).unwrap_or_default();
// If "DEVTYPE=wlan" appears, it's lying
let actually_wifi = uevent.contains("DEVTYPE=wlan");
Ok(reports_as_ethernet && !actually_wifi)
}
#[cfg(not(target_os = "linux"))]
return Ok(true);
}
pub fn get_interfaces() -> Result<Vec<Interface>> {
let vec = Interface::get_all()
.map_err(|e| anyhow::anyhow!("Failed to get network interfaces: {}", e))?
.into_iter()
.filter(|iface| {
iface.is_up()
&& iface.is_running()
&& !iface.is_loopback()
&& is_ethernet(&iface.name).unwrap_or(false)
&& !iface.name.starts_with("bridge")
&& !iface.name.starts_with("utun") // Exclude tunnel interfaces
&& !iface.name.starts_with("awdl") // Exclude Apple Wireless Direct Link
&& !iface.name.starts_with("anpi") // Exclude Apple Network Privacy Interface
&& !iface.name.starts_with("llw") // Exclude Apple low-latency WLAN
})
.collect::<Vec<_>>();
Ok(vec)
}
/// Sets a network interface to unmanaged by NetworkManager.
/// Returns true if the command succeeded.
pub fn set_interface_managed(interface: &str, managed: bool) -> bool {
let managed_str = match managed {
true => "yes",
false => "no",
};
tracing::info!(
"set_interface_managed for {} managed was set to: {}",
interface,
managed_str
);
let status = Command::new("nmcli")
.args(["dev", "set", interface, "managed", managed_str])
.status();
matches!(status, Ok(s) if s.success())
}
@@ -0,0 +1,78 @@
use crate::ethernet::get_interfaces;
use crate::{futures::FutureIteratorExt, modbus::tcp::ModbusTcpDevice};
use anyhow::{Result, bail};
use interfaces::Interface;
use std::cmp::min;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
pub struct ModbusTcpProbe {
pub addr: SocketAddr,
pub serial: u16,
}
pub async fn probe_modbus_tcp() -> Vec<ModbusTcpProbe> {
let interfaces = match get_interfaces() {
Ok(x) => x,
Err(_) => return vec![],
};
interfaces
.into_iter()
.map(probe_modbus_tcp_addresses)
.join_all()
.await
.into_iter()
.flatten()
.collect()
}
async fn probe_modbus_tcp_addresses(interface: Interface) -> Vec<ModbusTcpProbe> {
let out: Vec<ModbusTcpProbe> = interface
.addresses
.iter()
.filter_map(|addr| {
let a = addr.addr.map(|a| a.ip());
let m = addr.mask.map(|a| a.ip());
match (a, m) {
(Some(IpAddr::V4(addr)), Some(IpAddr::V4(mask))) => Some((addr, mask)),
_ => None,
}
})
.flat_map(|(addr, mask)| {
let a = u32::from(addr);
let m = u32::from(mask);
let network = a & m;
let prefix = min(24, m.count_ones()); // mask → prefix length
let size = 1u32 << (32 - prefix); // number of addresses
(0..size).map(move |i| SocketAddr::new(Ipv4Addr::from(network + i).into(), 502))
})
.map(|addr| smol::spawn(ping_modbus_device(addr)))
.join_all()
.await
.into_iter()
.filter_map(|x| x.ok())
.collect();
out
}
async fn ping_modbus_device(addr: SocketAddr) -> Result<ModbusTcpProbe> {
let mut device = ModbusTcpDevice::new(addr).await?;
let module_number1 = device.get_u32(0x2).await?;
let module_number2 = device.get_u32(0x4).await?;
if module_number1 != 0x2787_2144 || module_number2 != 0x0000_0000 {
bail!("Unknown modbus tcp device!");
}
let serial = device.get_u32(0x000A).await?;
Ok(ModbusTcpProbe {
addr,
serial: serial as u16,
})
}
+26
View File
@@ -0,0 +1,26 @@
use core::future::Future;
// Extension trait for iterators of futures
pub trait FutureIteratorExt<F>: Iterator<Item = F> + Sized
where
F: Future,
{
/// Await all futures and return their outputs
fn join_all(self) -> impl Future<Output = Vec<F::Output>>;
}
impl<I, F> FutureIteratorExt<F> for I
where
I: Iterator<Item = F> + Sized,
F: Future,
{
async fn join_all(self) -> Vec<F::Output> {
let mut out = Vec::new();
for fut in self {
out.push(fut.await);
}
out
}
}
+2
View File
@@ -2,6 +2,8 @@ pub mod controllers;
pub mod converters;
pub mod downcast;
pub mod ethercat;
pub mod ethernet;
pub mod futures;
pub mod helpers;
pub mod irq_handling;
pub mod modbus;
+1
View File
@@ -1,4 +1,5 @@
pub mod modbus_serial_interface;
pub mod tcp;
use anyhow::Error;
use crc::{CRC_16_MODBUS, Crc};
+263
View File
@@ -0,0 +1,263 @@
use std::fmt::Debug;
use std::io::ErrorKind;
use std::net::SocketAddr;
use std::time::Duration;
use anyhow::Context;
use anyhow::Result;
use anyhow::bail;
use smol::Timer;
use smol::future::FutureExt;
use smol::io;
use smol::io::AsyncReadExt;
use smol::io::AsyncWriteExt;
use smol::net::TcpStream;
const PROTOCOL_ID: u16 = 0;
const UNIT_ID: u8 = 0;
const READ_HOLDING_FUNCTION_CODE: u8 = 3;
const WRITE_HOLDING_FUNCTION_CODE: u8 = 16;
const READ_HOLDING_LENGTH: u16 = 6; // Unit ID 00 + Func Code 03 + Start Address XXXX + Quantity XXXX = 6 bytes
const WRITE_HOLDING_LENGTH_WITHOUT_DATA: u16 = 7; // Unit ID 00 + Func Code 16 + Start Address XXXX + Quantity XXXX + Num Data Bytes XX = 7 bytes
struct Packet {
buf: Vec<u8>,
}
impl Packet {
pub const fn new() -> Self {
Self { buf: Vec::new() }
}
pub fn add_u8(&mut self, x: u8) {
self.buf.push(x);
}
pub fn add_u16(&mut self, x: u16) {
let buf = x.to_be_bytes();
self.buf.push(buf[0]);
self.buf.push(buf[1]);
}
pub fn as_bytes(&self) -> &[u8] {
&self.buf
}
}
pub struct ModbusTcpDevice {
stream: TcpStream,
transactions: u16,
}
impl Debug for ModbusTcpDevice {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.stream.peer_addr() {
Ok(addr) => write!(f, "ModbusTcpDevice({:?})", addr),
Err(_) => std::fmt::Result::Err(std::fmt::Error),
}
}
}
impl ModbusTcpDevice {
pub async fn new(addr: SocketAddr) -> Result<Self> {
let timeout = async {
Timer::after(Duration::from_millis(10)).await;
Err(io::Error::from(ErrorKind::TimedOut))
};
let stream = TcpStream::connect(addr)
.or(timeout)
.await
.context("Could not connect to modbus device!")?;
Ok(Self {
stream,
transactions: 0,
})
}
async fn read_and_check_responce_header(&mut self, func_code: u8) -> Result<()> {
if self.read_u16().await? != self.transactions {
bail!("Modbus device sent unexpected transaction id!");
}
if self.read_u16().await? != PROTOCOL_ID {
bail!("Modbus device sent unexpected protocol id!");
}
let length = self.read_u16().await?;
if length < 2 {
bail!("Modbus device will send too few bytes to understand the response!");
}
if self.read_u8().await? != UNIT_ID {
bail!("Modbus device sent unexpected unit id!");
}
let func_code_res = self.read_u8().await?;
if func_code != func_code_res {
let error_code = self.read_u8().await?;
bail!(
"Modbus device sent unexpected function code 0x{:x}! It probably encountered an error. Error code: 0x{:x}",
func_code_res,
error_code
);
}
Ok(())
}
async fn send_read_holding_request(&mut self, addr: u16, count: u16) -> Result<u16> {
let mut packet = Packet::new();
self.transactions += 1;
packet.add_u16(self.transactions);
packet.add_u16(PROTOCOL_ID);
packet.add_u16(READ_HOLDING_LENGTH);
packet.add_u8(UNIT_ID);
packet.add_u8(READ_HOLDING_FUNCTION_CODE);
packet.add_u16(addr);
packet.add_u16(count);
self.send(packet).await?;
self.read_and_check_responce_header(READ_HOLDING_FUNCTION_CODE)
.await?;
let num_data_bytes = self.read_u8().await? as u16;
if count * 2 != num_data_bytes {
bail!(
"Modbus device wants to send only a portion of the requested range - UNIMPLEMENTED!"
);
}
Ok(num_data_bytes)
}
pub async fn get_holding_registers(&mut self, addr: u16, count: u16) -> Result<Vec<u16>> {
self.send_read_holding_request(addr, count).await?;
let mut res: Vec<u16> = Vec::new();
while res.len() < count as usize {
res.push(self.read_u16().await?);
}
Ok(res)
}
pub async fn set_holding_registers(&mut self, addr: u16, values: &[u16]) -> Result<()> {
let count = values.len();
let num_bytes = count * 2;
assert!(
count <= 128,
"Cannot send that many bytes to modbus device in one go! Trying to send {} bytes of data.",
num_bytes
);
let mut packet = Packet::new();
self.transactions += 1;
packet.add_u16(self.transactions);
packet.add_u16(PROTOCOL_ID);
packet.add_u16(WRITE_HOLDING_LENGTH_WITHOUT_DATA + num_bytes as u16);
packet.add_u8(UNIT_ID);
packet.add_u8(WRITE_HOLDING_FUNCTION_CODE);
packet.add_u16(addr);
packet.add_u16(count as u16);
packet.add_u8(num_bytes as u8);
for value in values {
packet.add_u16(value.to_owned());
}
self.send(packet).await?;
self.read_and_check_responce_header(WRITE_HOLDING_FUNCTION_CODE)
.await?;
if self.read_u16().await? != addr {
bail!("Modbus device wrote to wrong register address!");
}
if self.read_u16().await? != count as u16 {
bail!("Modbus device wrote wrong number of registers!");
}
Ok(())
}
pub async fn get_string<const N: usize>(&mut self, addr: u16) -> Result<String> {
assert!(N % 2 == 0, "Strings are always of even length!");
assert!(N <= 256, "Cannot read long strings in a single swoop!");
let count = N / 2;
self.send_read_holding_request(addr, count as u16).await?;
let buf = self.read_bytes::<N>().await?;
let s = String::from_utf8_lossy(&buf);
let s = match s.split_once('\0') {
None => s.to_string(),
Some((l, _)) => l.to_string(),
};
Ok(s)
}
pub async fn set_string<const N: usize>(&mut self, _addr: u16, s: &str) -> Result<()> {
assert!(s.len() <= N, "String is too long to fit!");
todo!()
}
pub async fn get_u16(&mut self, addr: u16) -> Result<u16> {
let length = self.send_read_holding_request(addr, 1).await?;
assert_eq!(
length, 2,
"Expected modbus device to send exactly 2 bytes of data!"
);
self.read_u16().await
}
pub async fn get_u32(&mut self, addr: u16) -> Result<u32> {
let length = self.send_read_holding_request(addr, 2).await?;
assert_eq!(
length, 4,
"Expected modbus device to send exactly 4 bytes of data!"
);
let buf = self.read_bytes::<4>().await?;
Ok(u32::from_be_bytes(buf))
}
// We have to send the request in a single packet,
// otherwise the device may fail to respond correctly.
async fn send(&mut self, packet: Packet) -> Result<()> {
self.stream
.write_all(packet.as_bytes())
.await
.context("Could write to modbus device!")
}
async fn read_u8(&mut self) -> Result<u8> {
let buf = self.read_bytes::<1>().await?;
Ok(u8::from_be_bytes(buf))
}
async fn read_u16(&mut self) -> Result<u16> {
let buf = self.read_bytes::<2>().await?;
Ok(u16::from_be_bytes(buf))
}
async fn read_bytes<const N: usize>(&mut self) -> Result<[u8; N]> {
let mut buf = [0; N];
self.stream
.read_exact(&mut buf)
.await
.context("Could not read from modbus device!")?;
Ok(buf)
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 985 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

+139
View File
@@ -0,0 +1,139 @@
# XTREM Communication Protocol
**Version:** 3.007
**Device:** XTREM / XTREM-S Weighing Module
**Standard:** OIML R76:2006 / EN45501:2015 compliant
---
## Overview
The **XTREM communication protocol** allows a terminal or host controller to exchange data and commands with the XTREM weighing module.
The device transmits and receives ASCII-coded frames over a serial interface (RS232, RS485, Wi-Fi, or Ethernet, depending on configuration).
Since we are using the Ethernet interface we need to provide some kind of DHCP to assign an IP - Address to each XTREM Device.
They then may be addressed via a **broadcast** IP (e.g.: 192.168.4.255/24).
A **Unicast** communication is applied through the device ID inside the XTREM Frame.
---
## Frame Structure
Each message—both query and response—has the same format:
```
STX ID_O ID_D F D_ADDRESS D_L DATA LRC ETX
```
| Field | Description | Length (bytes) |
| :------------ | :------------------------------------------------ | :------------: |
| **STX** | Start of message (ASCII `02h`) | 1 |
| **ID_O** | Sender address (2 ASCII hex chars) | 2 |
| **ID_D** | Destination address (2 ASCII hex chars) | 2 |
| **F** | Function code (`R`, `W`, `E`, etc.) | 1 |
| **D_ADDRESS** | Data address (register, 4 ASCII hex chars) | 4 |
| **D_L** | Data length (2 ASCII hex chars) | 2 |
| **DATA** | Optional data field | Variable |
| **LRC** | Longitudinal Redundancy Check (2 ASCII hex chars) | 2 |
| **ETX** | End of message (ASCII `03h`) | 1 |
In the default configuration the Frame also requires a CRLF at the end.
**Example:**
```
STX ‘1’ ‘7’ ‘0’ ‘1’ ‘R’ ‘0’ ‘1’ ‘0’ ‘1’ ‘0’ ‘0’ LRC ETX CR LF
```
---
## Function Codes
| Function | ASCII | Hex | Description |
| :------- | :---- | :---| :-------------- |
| **R** | R | 52h | Read request |
| **r** | r | 72h | Read response |
| **W** | W | 57h | Write request |
| **w** | w | 77h | Write response |
| **E** | E | 45h | Execute command |
| **e** | e | 65h | Execute response|
### Write response codes
* `'0'` – OK
* `'1'` – Write denied (sealed)
* `'2'` – Read-only register
* `'3'` – Invalid value / out of range
### Execute response codes
* `'0'` – Executed successfully
* `'1'` – Blocked by seal switch
---
## LRC Calculation
The **Longitudinal Redundancy Check** is a one-byte XOR of all message bytes, excluding:
* `STX`
* `ETX`
* The LRC itself
Example C function:
```c
unsigned char LRC(unsigned char* s) {
unsigned char lrc = 0;
while (*s) {
lrc ^= *(s++);
}
return lrc;
}
```
Example Rust function:
```rust
pub fn compute_lrc(data: &[u8]) -> u8 {
data.iter().fold(0u8, |acc, &b| acc ^ b)
}
```
---
## Key Data Registers
| Address (hex) | Description | Type | Notes |
| :------------ | :------------------------- | :--: | :------------------------------------ |
| **0000h** | Serial number | R | Factory-set |
| **0001h** | Device ID | RW | Network address |
| **0007h** | Hardware version | R | Read-only |
| **0008h** | Software version | R | Read-only |
| **0009h** | Sealing switch status | R | `0=UNLOCK`, `1=LOCK` |
| **0010h** | Baud rate | RW | `0–4` → 9600–115200 baud |
| **0013h** | Stream mode output rate | RW | Time in ms |
| **0100h** | Device state | R | Status bits (power, Wi-Fi, ADC, etc.) |
| **0101h** | Weight value | R | Gross weight |
| **0102h** | Tare value | RE | Read/Execute |
| **0103h** | Net weight | R | Gross − Tare |
| **0110h** | ADC counts (instant) | R | Raw ADC data |
| **0111h** | ADC counts (filtered) | R | Post-filter data |
| **1010h** | Stop stream mode | E | Execute |
| **1011h** | Start stream mode (weight) | E | Continuous output |
| **9999h** | Device reset | E | Software reset |
| **EEEEh** | Factory reset | E | Restores defaults |
---
## Post-Factory Reset Configuration
After performing a factory reset, several registers must be configured to ensure proper operation of the device.
Note: The unit may remain set to 'g' after a factory reset, but the values received may be in 'kg' depending on the device configuration.
The following registers should be reviewed and set as required:
1. **Address: 0022 | Max (Max1) (scale maximum capacity)**
The default value for this register may vary between devices (e.g., 60000 or 6000). Set this value to 6000 to ensure correct operation.
2. **Address: 0023 | e (e1) (scale interval)**
3. **Address: 0026 | Decimal position**
+81
View File
@@ -89,5 +89,86 @@ To get started with actual hardware, check out these step-by-step tutorials:
- **[LED Control with EL2004](./minimal-example-el2004.md)** - Digital output control, the simplest possible hardware setup
- **[Analog Input with EL3021](./minimal-example-el3021.md)** - Reading analog current measurements
- **[Stepper Motor Control with EL7031-0030](./minimal-example-el7031-motor.md)** - Complete motor control integration with velocity control
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,236 @@
# Minimal Example — Beckhoff EL7031-0030 Stepper Motor Integration
A complete hardware + software walkthrough
---
## Table of Contents
1. [Introduction](#1-introduction)
2. [Requirements](#2-requirements)
3. [Hardware Setup](#3-hardware-setup)
- [3.2 EK1100 Wiring](#32-ek1100-wiring)
- [3.2.1 Safe Wiring Procedure](#321-safe-wiring-procedure-beckhoff-recommended)
- [3.2.2 Wiring (EL7031-0030)](#322-wiring-el7031-0030)
- [3.3 Safety Warning](#33--safety-warning)
4. [Software Setup](#4-software-setup)
- [4.1 Installing on Linux](#41-installing-on-linux-this-depends-on-your-distro)
- [4.2 Running the Backend](#42-running-the-backend)
- [4.3 Running the Frontend](#43-running-the-frontend)
5. [Demo](#5-demo)
- [5.1 Assigning Devices in the Dashboard](#51-assigning-devices-in-the-dashboard)
6. [Documentation](#6-documentation)
7. [Software Architecture](#7-software-architecture)
---
## 1. Introduction
This project documents the successful integration of a **Beckhoff EL7031-0030 Stepper Motor Terminal** into the QiTech control software stack.
The goal was to control a 24V stepper motor via EtherCAT using a custom Rust backend and a React/Electron frontend.
---
## 2. Requirements
### Software
- Rust toolchain
- Node.js + npm
- Git
- QiTech Control repository
- EtherCAT HAL (included inside repo)
### Hardware
- **EtherCAT Master:** Linux PC
- **Bus Coupler:** EtherCAT Beckhoff EK1100
- **Stepper Driver:** Beckhoff EL7031-0030 (THIS IS DIFFERENT FROM EL7031)
- **Motor:** Standard 4-wire Stepper Motor
- **Power Supply:** 24V DC
- **Ethernet Cable:** Standard Ethernet cable
- **Wiring Tools:** Screwdriver, wires
---
## 3. Hardware Setup
### 3.2 EK1100 Wiring
This wiring configuration powers the EL7031-0030.
It is not the only possible wiring but is the **simplest functional setup**.
#### ⚠️ Safety Warning
Always disconnect power before wiring.
Working on live EtherCAT terminals can cause serious damage or electrical shock.
---
#### 3.2.1 Safe Wiring Procedure (Beckhoff Recommended)
1. Insert a screwdriver **straight** into the square release hole.
2. Insert the stripped wire into the round opening.
3. Remove the screwdriver — the spring clamp locks the wire.
![](../assets/wiring.png)
---
We supply power using a **DC hollow-plug adapter**, like this one:
https://www.amazon.de/dp/B093FTFZ8Q
Perform the following wiring on the EK1100:
1. Red wire **(+24 V)** → Terminal **2**
2. Black wire **(0 V)** → Terminal **3**
3. Jumper wire from **Terminal 1 → Terminal 6**
4. Jumper wire from **Terminal 5 → Terminal 7**
After wiring, your module should look like **Figure 1**.
---
#### **Figure 1 — EK1100 Minimal Wiring**
<img src="../assets/ek1100.jpeg" width="400">
---
#### **Figure 2 — EL7031-0030 Terminal**
<img src="../assets/EL7031_0030.jpg" width="300">
Slide the EL7031-0030 onto the right side of the EK1100 until it locks.
The EtherCAT E-Bus and power contacts connect automatically — **no wiring required**.
---
#### **Figure 3 — Motor**
<img src="../assets/motor.jpg" width="300">
---
#### **Figure 4 — Motor Wiring**
<img src="../assets/motor_wiring.jpg" width="300">
Now the motor is wired via the pins 4, 5, 12, 13 on the EL7031-0030.
---
#### **Figure 5 — EL7031 Integration Connected**
<img src="../assets/EL7031_0030_connected.jpg" width="400">
That's what the pin should look like.
---
### 3.2.2 Wiring (EL7031-0030)
**Crucial:** The EL7031 requires two power sources: E-Bus (side contacts) for logic, and Front Terminal (6 & 14) for motor power.
| Terminal Point | Function | Cable Color (Example) |
| :------------- | :--------------- | :-------------------- |
| **4** | Motor Coil A1 | Red |
| **12** | Motor Coil A2 | Blue |
| **5** | Motor Coil B1 | Green |
| **13** | Motor Coil B2 | Black |
| **6** | **Power +24V** | PSU Red (+) |
| **14** | **Power 0V/GND** | PSU Black (-) |
---
### 3.3 ⚠️ Safety Warning
- **Mandatory Power Supply:** Without 24V connected to **Pin 6 (+24V)** and **Pin 14 (GND)**, the terminal will remain in `PREOP` state or show a "Warning" LED (No Power). The motor will **not** move without this external supply.
#### Risk of Destruction (Short Circuit):
- **Never** connect the 24V Power Supply to the Motor Output pins (**4, 5, 12, 13**). This will instantly destroy the terminal (causing pins 12 & 13 to glow red/burn).
- Ensure strict separation: **Pins 4/5/12/13 are for the MOTOR ONLY**.
- **Pins 6/14 are for POWER ONLY**.
Also, see the documentation of the EtherCAT terminal ([EL7031-0030](https://download.beckhoff.com/download/document/io/ethercat-terminals/el7031-0030de.pdf)) for the different power modes (page 46 ff.)
---
## 4. Software Setup
### 4.1 Installing on Linux (this depends on your distro)
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
### 4.3 Running the Frontend
```bash
# move to the control directory
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:
![](../assets/UI_start_motor.png)
Make sure that under "Assign" → "Machine Assignment"
the correct serial number is selected (each device should have the same one, here it is 1).
Under "Machine" select your new Machine however you named it (I named it "TestMotor V1"), else it will detect that there is something connected to it but the connection won't work.
![](../assets/machine_assigment_motor.png)
**NOW THE MOTOR SHOULD TURN!**
In the interface "TestMotor" on the left side, you can now control the motor (its state and its speed).
![](../assets/UI_motor.png)
---
## 6. Documentation
Use the official documentation of the EL7031-0030 for more information:
[Beckhoff EL7031-0030 Documentation](https://download.beckhoff.com/download/document/io/ethercat-terminals/el7031-0030de.pdf)
---
## 7. Software Architecture
### Backend (Rust)
Located in [machines/src/motor_test_machine/](/machines/src/motor_test_machine/).
1. **`mod.rs`**: Defines the `MotorTestMachine` struct and holds the state (driver wrapper, enabled state, target velocity).
2. **`api.rs`**: Handles incoming JSON commands from the frontend (Enable/Disable, Set Velocity) via WebSockets/SocketIO.
3. **`act.rs`**: The real-time control loop. It updates the `StepperVelocityEL70x1` driver wrapper in every cycle based on the current state.
4. **`new.rs`**: Initializes the hardware.
### Frontend (TypeScript/React)
Located in [electron/src/machines/motor_test_machine/](/electron/src/machines/motor_test_machine/) and [electron/src/routes/routes.tsx](/electron/src/routes/routes.tsx).
1. **`useTestMotor.ts`**: Custom hook managing the optimistic state and communication with the backend.
2. **`TestMotorControlPage.tsx`**: The UI using QiTech UI components (`ControlCard`, `EditValue`, `SelectionGroupBoolean`) to match the look and feel of the Winder2.
3. **Routing**: Integrated into `routes.tsx` using TanStack Router, ensuring the machine appears in the sidebar and navigation works correctly.
@@ -0,0 +1,196 @@
# Minimal Example — Analog Input Wago 750-455
## Table of Contents
1. [Introduction](#1-introduction)
2. [Requirements](#2-requirements)
3. [Hardware Setup](#3-hardware-setup)
4. [Software Setup](#4-software-setup)
5. [Demo](#5-demo)
6. [References](#6-references)
## 1. Introduction
The Wago 750-455 Analog Input example demonstrates how to measure analog current on a **Wago 750-455 EtherCAT module** using the QiTech machine framework. This module provides 4 independent analog input channels that can read 4-20mA signals.
## 2. Requirements
### Hardware
- **Wago 750-354 EtherCAT Coupler**
- **Wago 750-602 Power Supply** (24V VDC)
- **Wago 750-455 EtherCAT Module** (4-channel analog input, 4-20mA)
- **Wago 750-600 End Module**
- **24 V DC power supply** for the Wago system
- **Adjustable DC power supply** (or any other means of producing 4-20mA DC @ <10V)
- Jumper / bridge wires (0.5–1.5 mm² recommended)
- A **Linux PC** (Ubuntu/Debian recommended)
- Standard Ethernet cable
- Flat screwdriver
### Software
*(Installation steps in Section 4)*
- Rust toolchain
- Node.js + npm
- Git
- QiTech Control repository
- EtherCAT HAL (included inside repo)
## 3. Hardware Setup
### 3.1 Overview
The Wago 750-455 is a 4-channel analog input module that measures current signals in the 4-20mA range. Each channel has its own input terminals and can operate independently.
#### ⚠️ Safety Warning
Always disconnect power before wiring.
Working on live EtherCAT modules can cause serious damage or electrical shock.
### 3.2 Wiring Procedure
#### 3.2.1 Safe Wiring (Wago Spring Clamp)
1. Insert a screwdriver **straight** into the square release hole.
2. Insert the stripped wire into the round opening.
3. Remove the screwdriver — the spring clamp locks the wire.
![](../assets/wiring.png)
---
### 3.3 Wago 750-455 Module Wiring
The Wago 750-455 has 4 analog input channels. Each channel requires two terminals:
**Channel 1 (AI1):**
- Terminal 1: ⊕ (Positive input)
- Terminal 2: ⊖ (Negative input/Ground)
**Channel 2 (AI2):**
- Terminal 3: ⊕ (Positive input)
- Terminal 4: ⊖ (Negative input/Ground)
**Channel 3 (AI3):**
- Terminal 5: ⊕ (Positive input)
- Terminal 6: ⊖ (Negative input/Ground)
**Channel 4 (AI4):**
- Terminal 7: ⊕ (Positive input)
- Terminal 8: ⊖ (Negative input/Ground)
For this minimal example, we'll connect at least one channel to an adjustable power supply:
1. Wire adjustable power supply ⊕ to **Terminal 1** (Channel 1 positive)
2. Wire adjustable power supply ⊖ to **Terminal 2** (Channel 1 negative)
*Repeat for additional channels as needed.*
---
### 3.4 Wago I/O System Integration
The Wago 750-455 module plugs into a Wago fieldbus system. Consult your specific Wago controller/coupler documentation for:
- Power supply requirements
- Network configuration
- Module placement in the I/O rack
The module automatically connects to the backplane for EtherCAT communication and power.
---
### 3.5 Power & Ethernet
#### Power
Ensure your Wago system has adequate 24V DC power connected per manufacturer specifications.
#### Ethernet
Connect your PC to the Wago controller/coupler using a standard Ethernet cable.
---
## 4. Software Setup
### 4.1 Installing on Ubuntu/Debian
Paste this into your terminal:
```bash
# Press Enter when prompted
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
sudo apt update
sudo apt install -y npm nodejs git
git clone git@github.com:qitechgmbh/control.git
cd control/electron
npm install
```
### 4.2 Running the Backend
```bash
./cargo_run_linux.sh
```
This script:
- Builds the backend
- Grants required system capabilities (raw sockets)
- Starts EtherCAT communication
Ensure the Wago controller is connected and powered.
### 4.3 Running the Frontend
```bash
cd electron
npm run start
```
This launches the QiTech Control dashboard.
---
## 5. Demo
### 5.1 Assigning Devices in the Dashboard
Once both backend and frontend are running:
1. Navigate to **Setup → EtherCAT** page
2. You should see your Wago controller and the 750-455 module listed
3. Select the Device called 750-354 and assign the Wago AI Test V1 Machine, set the Serial to 1 and set the device role to 0 (Wago Bus Coupler)
4. Press write thn restart the backend to load the changes
### 5.2 Testing Analog Input
1. Set your adjustable power supply to output a current in the 4-20mA range
2. In the dashboard, navigate to the monitoring view
3. Observe the analog input value for the connected channel(s)
### 5.3 Channel Specifications
The Wago 750-455 provides:
- **Input Range:** 4-20mA
- **Wiring Error Detection:** Built-in detection for open/short circuit conditions
- **4 Independent Channels:** Each can be read separately
### 5.4 Error Detection
The module includes wiring error detection. If you see a wiring error flag:
- Check that your current source is within the 4-20mA range
- Verify all wire connections are secure
- Ensure the current loop is properly closed
---
## 6. References
### Wago Documentation
- [Wago 750-455 Product Page](https://www.wago.com/de-en/io-systems/4-channel-analog-input/p/750-455)
- Wago I/O System manual
- EtherCAT configuration guide for Wago systems
### QiTech Control
- [Architecture Overview](../architecture-overview.md)
- [EtherCAT Basics](../ethercat-basics.md)
- [Device Documentation](../devices.md)
- [Getting Started Guide](getting-started.md)
### Related Examples
- [Minimal Example — EL3021](minimal-example-el3021.md) (Single channel analog input)
- [Minimal Example — EL2004](minimal-example-el2004.md) (Digital output)
@@ -0,0 +1,240 @@
# Minimal Example — LED Control on the WAGO750-402
A complete hardware + software walkthrough
---
## Table of Contents
1. [Introduction](#1-introduction)
2. [Requirements](#2-requirements)
3. [Hardware Setup](#3-hardware-setup)
4. [Software Setup](#4-software-setup)
5. [Demo](#5-demo)
6. [References](#6-references)
---
## 1. Introduction
The WAGO 750-402 Display Example is a minimal demonstation showing how to display the digital inputs on a WAGO 750-402 EtherCAT terminal using the QiTech machine framework.
It represents the simplest possible hardware interaction in the system:
Displaying the Digital Inputs using the QiTech Control Dashboard.
## 2. Requirements
### Hardware
- WAGO **2787-2144 EtherCAT**
- WAGO **750-354 EtherCAT Coupler**
- WAGO **750-602 EtherCAT Power Terminal**
- WAGO **750-402 EtherCAT Terminal** (4.channle digital input)
- WAGO **750-600 EtherCAT Endmodule**
- WAGO **852-1322 Ethernet-Switch**
- **24V DC Lab power supply**(AC/DC adapter)
- A **Linux PC** (Ubuntu/Debian recommended)
- Standard Ethernet cable
- Flat screwdriver
---
### Software
*(Installation steps in Section 4)*
- Rust tollchain
- Node-js + npm
- Git
- QiTech Control repository
- EtherCAT HAL *(include inside repo)*
## 3. Hardware Setup
### 3.1 Schematic
![](../assets/WAGO750_402_schematic.png)
---
#### ⚠️ Safety Warning
Always disconnect power before wiring.
Working on live EtherCAT terminals can cause serious damage or electrical shock.
### 3.2.1 Safe Wiring Procedure (Beckhoff Recommended)
1. Insert a screwdriver **straight** into the square release hole.
2. Insert the stripped wire into the round opening.
3. Remove the screwdriver — the spring clamp locks the wire.
![](../assets/wiring.png)
---
### 3.2.2 Wiring Steps (Used in This Example)
We supply power to the other devices by using the **750-602 EtherCAT Power Terminal**
Perform the following wiring on the Ethernet-Switch:
1. Red wire (+24V) → PWR+
2. Black wire (0V) → RPS-
#### **Figure 1 —**
<img src="../assets/WAGO750_402_switch.JPG" width="400">
---
### 3.3 WAGO Wiring the Coupler
Wire the **750-354 EtherCAT Coupler** to the power supply
1. red wire (+24V)
2. yellow (0V)
#### **Figure 2 —**
<img src="../assets/WAGO750_402_coupler.JPG" width="400">
---
### 3.4 750-602 EtherCAT Power Terminal Integration
Slide the **750-602 EtherCAT Power Terminal** onto the **right side of the 750-354 Coupler** until it locks.
Next We need to wire the EtherCat Power Terminal
Perform the next wiring Steps:
1. Green wire (Ground) → **Terminal4**
2. Red wire (+24V) from **750-602 EtherCAT Power Terminal** → **Terminal 2**
3. Black wire(0V) from **750-602 EtherCAT Power Terminal** → **Terminal 3**
4. Red wire (+24V) from **750-354 EtherCAT Coupler** → **Terminal 6**
5. Yellow wire (0V) from **750-354 EtherCAT Coupler** → **Terminal 7**
#### **Figure 3 — Wiring**
<img src="../assets/WAGO750_402_power_terminal.JPG" width="400">
---
## 3.5 750-402 EtherCAT Terminal Integration
Slide the **750-402 EtherCAT Terminal** onto to **right side of the 750-602 EtherCAT Power Terminal** until it locks.
Wire from the **24V DC Lab Adapter** → **Terminal 1**
#### **Figure 4 — Wiring**
<img src="../assets/WAGO750_402_terminal.JPG" width="400">
## 3.6 750-600 Final Assemly
Slide the **750-600 EtherCAT Endmodule** onto to **right side of the 7750-402 EtherCAT Terminal** until it locks.
No wiring is required
#### **Figure 5 — 750-402 + 750-600 Connected**
<img src="../assets/WAGO750_402_end_modul.JPG" width="400">
## 3.7 Power & Etherneet
### Power
Connect the 2787-2144 power supply to the power outlet
### Ehternet
Use a standard LAN cable to connect to your PC → **852-1322 Ethernet-Switch**
And another from **852-1322 Ethernet-Switch** → **750-354 EtherCAT Coupler**
#### **Figure 6 — Ethernet**
<img src="../assets/WAGO750_402_ethernet.JPG" width="400">
## 4. Software Setup
### 4.1 Installing on Ubuntu/Debian
Paste this into your terminal:
```bash
# Press Enter when prompted
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
sudo apt update
sudo apt install -y npm nodejs git
git clone git@github.com:qitechgmbh/control.git
cd control/electron
npm install
```
### 4.2 Running the Backend
```bash
./cargo_run_linux.sh
```
This script:
- Builds the backend
- Grants required system capabilities (raw sockets)
- Starts EtherCAT communication
Ensure the WAGO750-402 is connected.
### 4.3 Running the Frontend
```bash
cd electron
npm run start
```
This launches the QiTech Control dashboard.
## 5. Demo
### 5.1 Assigning Devices in the Dashboard
Once the backend + frontend are running, you should see:
- WAGO750-354 (Coupler)
- wago module (Digital Input Terminal)
![](../assets/WAGO750_402_discovery.png)
Steps:
- Click Assign on the wago module
- Select Digital Input Machine V1
![](../assets/WAGO750_402_setmachine.png)
- Enter a serial number except for 0
![](../assets/WAGO750_402_serserial.png)
- Click Write
### 5.2 Diyplaying LED
Navigate to:
Machines → DigitalInputMachine
![](../assets/WAGO750_402_machinedetected.png)
You will see this interface:
![](../assets/WAGO750_402_machinecontrol1.png)
Now you can apply a Voltage of the Range of 15V-30V (+24V recommended) from the DC lab power supply to the Terminal.
The Terminal can read the digital input, which is signaled by the LED on the Terminal and should be displayed in the Control software.
In our example it lights up the first LED
![](../assets/WAGO750_402_machinecontrol2.png)
## 6. References
This guide incorporates information from official WAGO documentation.
All diagrams, product names, and figures belong to Beckhoff Automation GmbH & Co. KG and are used here solely for educational purposes.
Referenced Manuals
[WAGO 750-402 Documentation](https://www.wago.com/de/io-systeme/4-kanal-digitaleingang/p/750-402#downloads)
[WAGO 750-354 Documentation](https://www.wago.com/de/io-systeme/feldbuskoppler-ethercat/p/750-354#details)
[WAGO 750-602 Documentation](https://www.wago.com/de/io-systeme/potentialeinspeisung/p/750-602#details)
[WAGO 750-354 Documentation](https://www.wago.com/de/io-systeme/endmodul/p/750-600#details)
-40
View File
@@ -1,40 +0,0 @@
# Machine Documentation
This directory contains documentation for QiTech machines.
## Structure
```
docs/machines/
├── manuals/ # Machine operation manuals (markdown files)
│ ├── extruder.md
│ ├── winder.md
│ ├── laser.md
│ └── mock.md
├── extruder/ # Machine-specific documentation
├── winder/
├── laser/
└── mock/
```
## Images
All manual images are stored in: **`electron/public/images/manuals/[machine-name]/`**
For example:
- Winder images: `electron/public/images/manuals/winder/`
- Extruder images: `electron/public/images/manuals/extruder/`
- Laser images: `electron/public/images/manuals/laser/`
### Image References in Manuals
When referencing images in manual markdown files (located in `docs/machines/manuals/`), use relative paths:
```markdown
![Image description](../../../../electron/public/images/manuals/[machine-name]/image.png)
```
## Adding Images
1. Place images in: `electron/public/images/manuals/[machine-name]/`
2. Reference in markdown: `../../../../electron/public/images/manuals/[machine-name]/your-image.png`
+2 -2
View File
@@ -10,7 +10,7 @@ This is why we chose nixos
# How we do updates
1. The user selects a version (commit/branch/tag) of the software they want to install
2. The repo is cloned to the local machine
3. the `installInfo.nix` is created and commited locally. It contains infos like the commit hash, branch, tag, timestamp etc. These are later available as environment variables `QITECH_OS...`
3. Information like the commit hash, branch, tag, timestamp etc. is passed to the nix build process as environment variables `QITECH_OS...`
4. The `nixos-switch` command is run. After rebuilding & compiling the software the system reboots.
- The nixos-switch command does the following:
1. The system is built with the new configuration
@@ -21,4 +21,4 @@ This is why we chose nixos
# More Docs
- [Implementation Details](./details.md)
- [Fresh Installation Quick Start guide](./quick-start.md)
- [Fresh Installation Quick Start guide](./quick-start.md)
+241
View File
@@ -0,0 +1,241 @@
# REST API `/api/v2`
The Qitech Control Panel exposes a small HTTP interface for discovering machines and reading their current values. All examples below assume you are connected to the panel’s Ethernet subnet and talk directly to the panel at:
- **Base URL:** `http://10.10.10.1:3001`
> **Schema note:** This documentation intentionally stays light on field details beyond what is shown in the examples. For the exact datatypes and complete payload shapes, refer to the corresponding Rust types (linked below).
---
## Authentication
The panel does **not** perform HTTP authentication. The expected security model is **network-level isolation**: anything that can send packets to the panel’s Ethernet interface is treated as trusted. This matches common security assumptions in EtherCAT-style control networks. It is therefore suggested to run the entire production line in isolated operation.
The panel is configured to administer its own subnet `10.10.10.0/24` via DHCP, while Wi-Fi can be used for upstream internet connectivity. To let the outside world communicate with the production line, a router should be placed at the network bondary bridging the production line's subnet and the rest of the network. The router can run in **client/bridge mode** on the line's subnet and expose the panel's API via **port forwading** or through a **reverse proxy** where one can add authentication, logging, rate limiting, etc.
The following graphic depicts the suggested network topology for a single production line. Many lines could potentially share the same router.
<p align="center">
<img width="769" height="528" alt="Suggested network topology" src="./assets/subnet.png" />
</p>
If DNS is available on the subnet, you may be able to resolve `qitech.control`; otherwise use the static address `10.10.10.1`.
---
## List machines `GET /api/v2/machine`
Returns the set of machines currently known/connected to the panel.
Machines are identified by:
- `slug`: the machine type / model identifier (string)
- `serial`: the specific machine instance identifier (int)
Each machine also includes a `legacy_id` to support older **v1** workflows. If a machine reports an issue, the `error` field may be present and non-null (containing an error message).
### Example request
```bash
curl -X GET "http://10.10.10.1:3001/api/v2/machine"
```
### Example response
```json
{
"machines": [
{
"legacy_id": {
"machine_identification": {
"vendor": 1,
"machine": 7
},
"serial": 57922
},
"serial": 57922,
"vendor": "QiTech",
"slug": "mock",
"error": null
},
{
"legacy_id": {
"machine_identification": {
"vendor": 1,
"machine": 4
},
"serial": 57922
},
"serial": 57922,
"vendor": "QiTech",
"slug": "extruder_v1",
"error": null
},
{
"legacy_id": {
"machine_identification": {
"vendor": 1,
"machine": 2
},
"serial": 57922
},
"serial": 57922,
"vendor": "QiTech",
"slug": "winder_v1",
"error": null
},
{
"legacy_id": {
"machine_identification": {
"vendor": 1,
"machine": 10
},
"serial": 48879
},
"serial": 48879,
"vendor": "QiTech",
"slug": "wago_power_v1",
"error": null
}
]
}
```
---
## Get current values `GET /api/v2/machine/<slug>/<serial>`
Returns all currently known values for a single machine.
Values are categorized into two groups:
- **State**: requested/commanded values (these typically change only after a state-change request, or if another controller updates them)
- **Live Values**: measured/observed values coming from the machine and potentially changing quickly
This REST endpoint returns **only the current snapshot**, not a stream of live values.
To receive continuous updates (via WebSockets), subscribe to the machine namespace (see **WebSockets** below).
### Example request (mock machine)
```bash
curl -X GET "http://10.10.10.1:3001/api/v2/machine/mock/57922"
```
### Example response
```json
{
"machine": {
"legacy_id": {
"machine_identification": {
"vendor": 1,
"machine": 7
},
"serial": 57922
},
"serial": 57922,
"vendor": "QiTech",
"slug": "mock",
"error": null
},
"state": {
"frequency1": 100.0,
"frequency2": 200.0,
"frequency3": 500.0,
"is_default_state": false,
"mode_state": {
"mode": "Running"
}
},
"live_values": {
"amplitude1": -0.03438523433309566,
"amplitude2": -0.06872980145477608,
"amplitude3": -0.1711138370170743,
"amplitude_sum": -0.27422887280494607
}
}
```
---
## Change machine state `POST /api/v1/machine/<slug>/<serial>`
State changes are submitted as **mutations**. The mutation payload is defined per machine type in Rust. Conceptually, each item in the mutation list represents a setter-style operation that is applied by the real-time control loop.
The API does **not** return the newly-applied state in the POST response. The panel runs a real-time loop and generally won’t block waiting for the physical system to converge. Instead:
- Submit the mutation via `POST`
- Poll `GET /api/v2/machine/<slug>/<serial>` to observe the updated state and/or any reported errors
### Example request (mock machine)
```bash
curl -X POST \
-d \
-H "Content-Type: application/json" \
"http://10.10.10.1:3001/api/v1/machine/mock/57922"
```
### Example response
```json
null
```
---
## WebSockets
For continuous updates, subscribe to a machine-specific namespace derived from its `legacy_id`:
- Namespace: `/machine/<vendor>/<id>/<serial>`
The stream emits events for:
- state changes (`StateEvent`)
- live value updates (`LiveValuesEvent`)
Both event payloads use the same machine-specific schema as the `/api/v2` REST responses.
---
## List of all machines
Below is a template you can fill with links to the relevant Rust types (mutations + state/live structs).
For each machine, link to:
- **Mutations:** the request payload type used by `POST /api/v1/machine/<slug>/<serial>`
- **State / Live Values:** the response payload types returned by `GET /api/v2/machine/<slug>/<serial>`
### Machines
- **winder_v1**
- Mutations: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/winder2/api.rs#L87>
- State: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/winder2/api.rs#L163>
- Live Values: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/winder2/api.rs#L143>
- **extruder_v1**
- Mutations: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/extruder1/api.rs#L214>
- State: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/extruder1/api.rs#L89>
- Live Values: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/extruder1/api.rs#L55>
- **laser_v1**
- Mutations: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/laser/api.rs#L88>
- State: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/laser/api.rs#L32>
- Live Values: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/laser/api.rs#L17>
- **mock**
- Mutations: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/mock/api.rs#L84>
- State: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/mock/api.rs#L38>
- Live Values: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/mock/api.rs#L24>
- **extruder_v2**
- Mutations: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/extruder2/api.rs#L126>
- State: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/extruder2/api.rs#L93>
- Live Values: <https://github.com/qitechgmbh/control/blob/1ec20074e9030a0ed1739ca9d9a77e298a2652a3/machines/src/extruder2/api.rs#L59>
+1
View File
@@ -88,6 +88,7 @@ typings/
# Vite
.vite/
dist/
dist-electron/
# Electron-Forge
out/
-59
View File
@@ -1,59 +0,0 @@
import type { ForgeConfig } from "@electron-forge/shared-types";
import { MakerSquirrel } from "@electron-forge/maker-squirrel";
import { MakerZIP } from "@electron-forge/maker-zip";
import { MakerDeb } from "@electron-forge/maker-deb";
import { MakerRpm } from "@electron-forge/maker-rpm";
import { VitePlugin } from "@electron-forge/plugin-vite";
import { FusesPlugin } from "@electron-forge/plugin-fuses";
import { FuseV1Options, FuseVersion } from "@electron/fuses";
const config: ForgeConfig = {
packagerConfig: {
executableName: "electron-shadcn",
asar: true,
icon: "src/assets/icon",
},
rebuildConfig: {},
makers: [
new MakerSquirrel({}),
new MakerZIP({}, ["darwin"]),
new MakerRpm({}),
new MakerDeb({}),
],
plugins: [
new VitePlugin({
// `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.
// If you are familiar with Vite configuration, it will look really familiar.
build: [
{
// `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.
entry: "src/main.ts",
config: "vite.main.config.ts",
},
{
entry: "src/preload.ts",
config: "vite.preload.config.ts",
},
],
renderer: [
{
name: "main_window",
config: "vite.renderer.config.ts",
},
],
}),
// Fuses are used to enable/disable various Electron functionality
// at package time, before code signing the application
new FusesPlugin({
version: FuseVersion.V1,
[FuseV1Options.RunAsNode]: false,
[FuseV1Options.EnableCookieEncryption]: true,
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
[FuseV1Options.EnableNodeCliInspectArguments]: false,
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
[FuseV1Options.OnlyLoadAppFromAsar]: true,
}),
],
};
export default config;
-35
View File
@@ -1,35 +0,0 @@
export {}; // Make this a module
declare global {
// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Vite
// plugin that tells the Electron app where to look for the Vite-bundled app code (depending on
// whether you're running in development or production).
const MAIN_WINDOW_VITE_DEV_SERVER_URL: string;
const MAIN_WINDOW_VITE_NAME: string;
namespace NodeJS {
interface Process {
// Used for hot reload after preload scripts.
viteDevServers: Record<string, import("vite").ViteDevServer>;
}
}
type VitePluginConfig = ConstructorParameters<
typeof import("@electron-forge/plugin-vite").VitePlugin
>[0];
interface VitePluginRuntimeKeys {
VITE_DEV_SERVER_URL: `${string}_VITE_DEV_SERVER_URL`;
VITE_NAME: `${string}_VITE_NAME`;
}
}
declare module "vite" {
interface ConfigEnv<
K extends keyof VitePluginConfig = keyof VitePluginConfig,
> {
root: string;
forgeConfig: VitePluginConfig;
forgeConfigSelf: VitePluginConfig[K][number];
}
}
+292 -5700
View File
File diff suppressed because it is too large. Load diff
+14 -24
View File
@@ -1,15 +1,13 @@
{
"name": "qitech-control-electron",
"productName": "QiTech Control",
"version": "1.0.0",
"description": "",
"version": "2.4.1",
"description": "Next Generation Machine Interface ",
"main": ".vite/build/main.js",
"private": true,
"scripts": {
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make",
"publish": "electron-forge publish",
"start": "vite",
"build": "vite build",
"lint": "eslint .",
"lint:write": "eslint . --fix",
"format": "prettier --check .",
@@ -22,15 +20,6 @@
},
"author": "",
"devDependencies": {
"@electron-forge/cli": "^7.6.1",
"@electron-forge/maker-deb": "^7.8.0",
"@electron-forge/maker-rpm": "^7.8.3",
"@electron-forge/maker-squirrel": "^7.6.1",
"@electron-forge/maker-zip": "^7.6.1",
"@electron-forge/plugin-auto-unpack-natives": "^7.6.1",
"@electron-forge/plugin-fuses": "^7.10.2",
"@electron-forge/plugin-vite": "^7.6.1",
"@electron-forge/shared-types": "^7.4.0",
"@electron/fuses": "^1.8.0",
"@eslint/compat": "^1.2.6",
"@eslint/js": "^9.18.0",
@@ -47,7 +36,7 @@
"@types/react-virtualized": "^9.22.2",
"electron": "^37.3.1",
"electron-playwright-helpers": "^1.7.1",
"eslint": "^9.20.1",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.4",
@@ -62,7 +51,9 @@
"typescript": "^5.7.3",
"typescript-eslint": "^8.24.1",
"vite": "^6.4.1",
"vitest": "^3.0.6"
"vite-plugin-electron": "^0.29.0",
"vite-plugin-electron-renderer": "^0.14.6",
"vitest": "^4.0.18"
},
"dependencies": {
"@hookform/resolvers": "^5.2.2",
@@ -84,7 +75,6 @@
"@tanstack/react-router": "^1.130.17",
"@tanstack/react-table": "^8.21.2",
"@tanstack/router-devtools": "^1.105.0",
"@tanstack/zod-adapter": "^1.116.0",
"@vitejs/plugin-react": "^4.5.2",
"babel-plugin-react-compiler": "^19.0.0-beta-714736e-20250131",
"class-variance-authority": "^0.7.1",
@@ -98,20 +88,20 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.57.0",
"react-i18next": "^15.4.1",
"react-i18next": "^16.5.4",
"react-markdown": "^10.1.0",
"react-virtualized": "^9.22.6",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"socket.io-client": "^4.8.1",
"socket.io-client": "^4.8.3",
"socket.io-msgpack-parser": "^3.0.2",
"sonner": "^2.0.1",
"tailwind-merge": "^3.0.1",
"tailwindcss-animate": "^1.0.7",
"uplot": "^1.6.32",
"xlsx": "^0.18.5",
"zod": "^3.24.2",
"zod-validation-error": "^3.4.0",
"zustand": "^5.0.8"
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
"zod": "^4.3.5",
"zod-validation-error": "^5.0.0",
"zustand": "^5.0.11"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 KiB

@@ -1,65 +1,4 @@
![Titelseite,screenshot from Word doccument](/images/manuals/extruder/Titelseite.jpeg)
<!-- Datasheet: Added below Titelseite. Placeholder images to be replaced with final exports. -->
# QiTech Pro Extruder – Datasheet
![Datasheet Header](/images/manuals/extruder/datasheet/header.png)
## Included Assemblies
Electrical cabinet · Temperature controller · Frequency inverter · Cable routing + connection to QiTech Control (digital control)
## Motor
| Specification | Value |
| ------------- | ----- |
| Motor power | 1.5 kW |
| Output speed | 41 rpm |
| Output torque | 336 Nm |
| Transmission ratio | 34:2 |
| Service factor cG | 1.4 |
| Voltage / Frequency | 400 V / 50 Hz |
| Efficiency class | IE3 (Premium Efficiency) |
| Efficiency @ 100% rated load / % rated current (std. voltage) | 5.98 / 3.45 A |
| Protection class | IP55, Insulation class F |
| Construction design | Flange design, terminal box top |
| Shaft end (d×L) | 40 × 80 mm |
## Melt Pressure Sensor (DSI1-1/2-500-F-15)
| Specification | Value |
| ------------- | ----- |
| Output signal | 4–20 mA (2-conductor) |
| Sensor system | Mercury-free |
| Sensor | DMS Vollbrückentechnik |
| Measuring accuracy | 0.5 % |
| Calibration signal | 80 % |
| Supply voltage | 15–32 VDC |
| Measuring range | 0–500 bar |
| Temperature range (membrane) | max. 410 °C |
| Temperature range (sensor head) | max. 85 °C |
| Screw-in thread | 1/2"–20 UNF |
## Extruder Barrel & Screw
Nitrided extruder barrel (Ø 65 mm). Screw hardened from high‑alloyed steel (Ø 25 mm).
## Heating Elements & Temperature Sensors
3 heating zones (max. 350 °C), Heater 415 V, 3 × 750 W. 3× Type K temperature sensors, 1/8" NPT.
## Hopper
Volume: 4 L · Material: Aluminum · Easily detachable.
## Dimensions
![Dimensions](/images/manuals/extruder/datasheet/dimensions.png)
Overall length 1365 mm · Width 220 mm · Height 384 mm.
Fully wired and assembled (raised on 2 aluminum profiles).
Delivery time after completion: 2–5 working days.
Warranty: 24 months after delivery (excluding wearing parts).
<br>
![Titelseite,screenshot from Word doccument](images/manuals/extruder/Titelseite.jpeg)
<br>
@@ -116,7 +55,7 @@ Darmstadt, 10/06/2025
64392 Darmstadt,
<img src="/images/manuals/extruder/signature.jpeg" width="300" height="60"/>
<img src="images/manuals/extruder/signature.jpeg" width="300" height="60"/>
Milan von dem Bussche
@@ -162,7 +101,7 @@ Operating instructions
# 1 Machine description
![maschinenbeschreibung](/images/manuals/extruder/description.jpeg)
![maschinenbeschreibung](images/manuals/extruder/description.jpeg)
<br><br><br>
@@ -229,7 +168,7 @@ Accuracy class: 2
| Picture | Danger! |
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ![Warning](/images/manuals/extruder/w001.jpeg) | **Danger to life due to technically faulty machine.** <br> The machine may only be used when it is in perfect technical condition and in accordance with its intended use, in a safety-conscious and hazard-conscious manner and in compliance with these operating instructions. All faults, especially those that could impair safety, must be rectified immediately before starting the winder. Everyone who works on or with the Qitech Winder is responsible for observing these safety aspects! |
| ![Warning](images/manuals/extruder/w001.jpeg) | **Danger to life due to technically faulty machine.** <br> The machine may only be used when it is in perfect technical condition and in accordance with its intended use, in a safety-conscious and hazard-conscious manner and in compliance with these operating instructions. All faults, especially those that could impair safety, must be rectified immediately before starting the winder. Everyone who works on or with the Qitech Winder is responsible for observing these safety aspects! |
<br><br><br>
@@ -237,17 +176,17 @@ Accuracy class: 2
| Pictogram | Description of pictogram | Pictogram | Pictogram Description |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| ![w001](/images/manuals/extruder/w001.jpeg) | W001 General warning sign The combination of the symbol W001 and the signal word **"Danger"** indicates an imminently hazardous situation which, if not avoided death or serious injury if it is not avoided. The combination of the symbol W001 and the signal word **"Warning"** indicates an imminently hazardous situation which, if not avoided, will result in death or serious injury. death or serious injury if it is not avoided. The combination of the symbol W001 and the signal word **"Caution"** indicates an imminently hazardous situation which, if not avoided death or serious injury if not avoided. | ![m001](/images/manuals/extruder/m001.jpeg) | M001 General warning sign Mandatory sign This combination of symbol and signal word indicates a potentially hazardous situation which, if not avoided, may result in damage to property or the environment. not avoided. |
| ![w008](/images/manuals/extruder/w008.jpeg) | W008 Warning of risk of falling | ![m003](/images/manuals/extruder/m003.jpeg) | M003 Use hearing protection |
| ![w011](/images/manuals/extruder/w011.jpeg) | W011 Warning of danger of slipping | ![m004](/images/manuals/extruder/m004.jpeg) | M004 Use eye protection |
| ![w004](/images/manuals/extruder/w004.jpeg) | W004 Warning of laser beam | ![m008](/images/manuals/extruder/m008.jpeg) | M008 Use foot protection |
| ![w012](/images/manuals/extruder/w012.jpeg) | W012 Warning of electrical voltage | ![m009](/images/manuals/extruder/m009.jpeg) | M009 Use hand protection |
| ![w017](/images/manuals/extruder/w017.jpeg) | W017 Warning of hot surface | ![m010](/images/manuals/extruder/m010.jpeg) | M010 Use protective clothing |
| ![w018](/images/manuals/extruder/w018.jpeg) | W018 Warning against automatic start-up | ![m012](/images/manuals/extruder/m012.jpeg) | M012 Use handrail |
| ![w019](/images/manuals/extruder/w019.jpeg) | W019 Warning of crushing hazard | ![m021](/images/manuals/extruder/m021.jpeg) | M021 Switch off before maintenance or repair |
| ![w024](/images/manuals/extruder/w024.jpeg) | W024 Warning against hand injuries | ![m014](/images/manuals/extruder/m014.jpeg) | M014 Use head protection |
| ![w025](/images/manuals/extruder/w025.jpeg) | W025 Warning against counter-rotating rollers | | |
| ![w016](/images/manuals/extruder/w016.jpeg) | W016 Warning of toxic substances | | |
| ![w001](images/manuals/extruder/w001.jpeg) | W001 General warning sign The combination of the symbol W001 and the signal word **"Danger"** indicates an imminently hazardous situation which, if not avoided death or serious injury if it is not avoided. The combination of the symbol W001 and the signal word **"Warning"** indicates an imminently hazardous situation which, if not avoided, will result in death or serious injury. death or serious injury if it is not avoided. The combination of the symbol W001 and the signal word **"Caution"** indicates an imminently hazardous situation which, if not avoided death or serious injury if not avoided. | ![m001](images/manuals/extruder/m001.jpeg) | M001 General warning sign Mandatory sign This combination of symbol and signal word indicates a potentially hazardous situation which, if not avoided, may result in damage to property or the environment. not avoided. |
| ![w008](images/manuals/extruder/w008.jpeg) | W008 Warning of risk of falling | ![m003](images/manuals/extruder/m003.jpeg) | M003 Use hearing protection |
| ![w011](images/manuals/extruder/w011.jpeg) | W011 Warning of danger of slipping | ![m004](images/manuals/extruder/m004.jpeg) | M004 Use eye protection |
| ![w004](images/manuals/extruder/w004.jpeg) | W004 Warning of laser beam | ![m008](images/manuals/extruder/m008.jpeg) | M008 Use foot protection |
| ![w012](images/manuals/extruder/w012.jpeg) | W012 Warning of electrical voltage | ![m009](images/manuals/extruder/m009.jpeg) | M009 Use hand protection |
| ![w017](images/manuals/extruder/w017.jpeg) | W017 Warning of hot surface | ![m010](images/manuals/extruder/m010.jpeg) | M010 Use protective clothing |
| ![w018](images/manuals/extruder/w018.jpeg) | W018 Warning against automatic start-up | ![m012](images/manuals/extruder/m012.jpeg) | M012 Use handrail |
| ![w019](images/manuals/extruder/w019.jpeg) | W019 Warning of crushing hazard | ![m021](images/manuals/extruder/m021.jpeg) | M021 Switch off before maintenance or repair |
| ![w024](images/manuals/extruder/w024.jpeg) | W024 Warning against hand injuries | ![m014](images/manuals/extruder/m014.jpeg) | M014 Use head protection |
| ![w025](images/manuals/extruder/w025.jpeg) | W025 Warning against counter-rotating rollers | | |
| ![w016](images/manuals/extruder/w016.jpeg) | W016 Warning of toxic substances | | |
<br><br><br>
@@ -255,15 +194,15 @@ Accuracy class: 2
| Prohibition signs Pictogram | Pictogram description | International pictograms / other Pictogram | Pictogram Description |
| ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| <img src="/images/manuals/extruder/p001.jpeg" width="158" height="158"/> | P001 General prohibition sign | <img src="/images/manuals/extruder/f001.jpeg" width="158" height="158"> | F001 Fire extinguisher |
| <img src="/images/manuals/extruder/p002.jpeg" width="158" height="158"/> | P002 No smoking | <img src="/images/manuals/extruder/ANIS_danger.jpeg" width="232" height="60"> | ANSI Z535.3 - DANGER |
| <img src="/images/manuals/extruder/p003.jpeg" width="158" height="158"/> | P003 No open flames; fire, open source of ignition and smoking prohibited | <img src="/images/manuals/extruder/ANIS_warning.jpeg" width="232" height="60"> | ANSI Z535.3 - WARNING |
| <img src="/images/manuals/extruder/p006.jpeg" width="158" height="158"/> | P006 Prohibited for industrial trucks | <img src="/images/manuals/extruder/ANIS_caution.jpeg" width="232" height="60"> | ANSI Z535.3 - CAUTION |
| <img src="/images/manuals/extruder/p009.jpeg" width="158" height="158"/> | P009 Climbing prohibited - In the sense of climbing prohibited for unauthorized persons | <img src="/images/manuals/extruder/ANIS_notice.jpeg" width="232" height="60"> | ANSI Z535.3 - NOTICE |
| <img src="/images/manuals/extruder/p022.jpeg" width="158" height="158"/> | P022 Eating and drinking prohibited | <img src="/images/manuals/extruder/ANIS_safetyinstructions.jpeg" width="232" height="60"> | ANSI Z535.3 - SAFETY INSTRUCTIONS |
| <img src="/images/manuals/extruder/p024.jpeg" width="158" height="158"/> | P024 Entering the area prohibited | <img src="/images/manuals/extruder/trashcan.jpeg" width="158" height="158"> | Symbol for separate collection of waste electrical and electronic equipment |
| <img src="/images/manuals/extruder/p031.jpeg" width="158" height="158"/> | P031 Switching prohibited | | |
| <img src="/images/manuals/extruder/D-p006.jpeg" width="158" height="158"/> | D-P006 Access prohibited for unauthorized persons | | |
| <img src="images/manuals/extruder/p001.jpeg" width="158" height="158"/> | P001 General prohibition sign | <img src="images/manuals/extruder/f001.jpeg" width="158" height="158"> | F001 Fire extinguisher |
| <img src="images/manuals/extruder/p002.jpeg" width="158" height="158"/> | P002 No smoking | <img src="images/manuals/extruder/ANIS_danger.jpeg" width="232" height="60"> | ANSI Z535.3 - DANGER |
| <img src="images/manuals/extruder/p003.jpeg" width="158" height="158"/> | P003 No open flames; fire, open source of ignition and smoking prohibited | <img src="images/manuals/extruder/ANIS_warning.jpeg" width="232" height="60"> | ANSI Z535.3 - WARNING |
| <img src="images/manuals/extruder/p006.jpeg" width="158" height="158"/> | P006 Prohibited for industrial trucks | <img src="images/manuals/extruder/ANIS_caution.jpeg" width="232" height="60"> | ANSI Z535.3 - CAUTION |
| <img src="images/manuals/extruder/p009.jpeg" width="158" height="158"/> | P009 Climbing prohibited - In the sense of climbing prohibited for unauthorized persons | <img src="images/manuals/extruder/ANIS_notice.jpeg" width="232" height="60"> | ANSI Z535.3 - NOTICE |
| <img src="images/manuals/extruder/p022.jpeg" width="158" height="158"/> | P022 Eating and drinking prohibited | <img src="images/manuals/extruder/ANIS_safetyinstructions.jpeg" width="232" height="60"> | ANSI Z535.3 - SAFETY INSTRUCTIONS |
| <img src="images/manuals/extruder/p024.jpeg" width="158" height="158"/> | P024 Entering the area prohibited | <img src="images/manuals/extruder/trashcan.jpeg" width="158" height="158"> | Symbol for separate collection of waste electrical and electronic equipment |
| <img src="images/manuals/extruder/p031.jpeg" width="158" height="158"/> | P031 Switching prohibited | | |
| <img src="images/manuals/extruder/D-p006.jpeg" width="158" height="158"/> | D-P006 Access prohibited for unauthorized persons | | |
<br><br><br>
@@ -336,7 +275,7 @@ The machine has a main switch that disconnects/connects the mains supply. The ON
The emergency stop switch is used to quickly stop the machine in the event of danger. This is designed with redundant emergency stop contactors. An emergency stop switching device also prevents the machine from being switched on again after a power failure. By activating the emergency stop device, the entire machine is shut down and disconnected from the mains supply.
![emergency stop button / switch](/images/manuals/extruder/emergency_stop.jpeg)
![emergency stop button / switch](images/manuals/extruder/emergency_stop.jpeg)
Hazardous movements are brought to a controlled standstill as quickly as possible. The machine can only be restarted by physically resetting the emergency stop switch and then starting one of the operating modes in the software.
@@ -418,11 +357,11 @@ Depending on the activity or the duration of the activity, the operator must det
<br><br>
| Picture | Danger |
|---------|--------|
| ![Warning](/images/manuals/extruder/w001.jpeg)|**Risk of injury due to improper transport** <br> Be careful when loading the machine, do not place any limbs under the parts when setting them down, do not stand under suspended loads or between moving loads and walls or machine parts. Wear personal protective equipment: safety shoes, protective work clothing, protective gloves.
| ![Warning](images/manuals/extruder/w001.jpeg)|**Risk of injury due to improper transport** <br> Be careful when loading the machine, do not place any limbs under the parts when setting them down, do not stand under suspended loads or between moving loads and walls or machine parts. Wear personal protective equipment: safety shoes, protective work clothing, protective gloves.
| Picture | Caution! |
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ![Warning](/images/manuals/extruder/w001.jpeg) | **Danger to life due to incorrect installation and commissioning!** <brErrors during installation can lead to life-threatening situations or cause considerable damage to property. Installation and commissioning may only be carried out by the manufacturer's employees. |
| ![Warning](images/manuals/extruder/w001.jpeg) | **Danger to life due to incorrect installation and commissioning!** <brErrors during installation can lead to life-threatening situations or cause considerable damage to property. Installation and commissioning may only be carried out by the manufacturer's employees. |
<br>
<br>
@@ -451,7 +390,7 @@ The CEE plug of the extruder must be plugged into a suitable three-phase socket
The machines are delivered with the newest version of the software installed. Updates, as well as previous updates and the software version can be viewed under Setup-> Updates. If a new version of the software is available, it will be displayed here. To perform the update an internet connection is required. In the next step, click the red "Select" button next to the chosen update. The display will now shut down, and when it is turned back on, the latest version of the software will be installed.
![screenshot updates](/images/manuals/extruder/screenshot_updates.png)
![screenshot updates](images/manuals/extruder/screenshot_updates.png)
<br>
@@ -492,7 +431,7 @@ Then start the machine as follows:
- Set the “target temperature” of the individual heating zones according to the melting material in the 3 input fields
- Set the “target temperature” of the nozzle
- After reaching the target temperature, wait 15 minutes until the machine has heated through and the plastic in the screw barrel has completely melted.
![Screenshot: set traget temperature](/images/manuals/extruder/screenshot_adjust_heat.png)
![Screenshot: set traget temperature](images/manuals/extruder/screenshot_adjust_heat.png)
<br>
@@ -504,7 +443,7 @@ Then start the machine as follows:
- Press the "Extrude" button in the “mode” box
- Wait until old melt has been "flushed" out of the extruder, i.e. no more discolouration of the filament is no longer discoloured and a constant material flow has been achieved
![screenshot: adjust screw speed](/images/manuals/extruder/screenshot_extruder_adjustscrew_speed.png)
![screenshot: adjust screw speed](images/manuals/extruder/screenshot_extruder_adjustscrew_speed.png)
<br>
@@ -532,7 +471,7 @@ Then start the machine as follows:
- Go to "Config" in the funktion bar
- To set the screw direction click on "Forward" or "Backward" in the "Invert settings" box
![Changing screrw direction](/images/manuals/extruder/screenshot_config.png)
![Changing screrw direction](images/manuals/extruder/screenshot_config.png)
<br>
@@ -556,7 +495,7 @@ Then start the machine as follows:
| Picture | DANGER! |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| ![Warning](/images/manuals/extruder/w001.jpeg) | **Danger to life due to operation of the machine in a faulty condition.** <br> If safety-relevant changes or defects occur on the machine, shut it down immediately, secure it against being switched on again and report the incident to the responsible office/person! |
| ![Warning](images/manuals/extruder/w001.jpeg) | **Danger to life due to operation of the machine in a faulty condition.** <br> If safety-relevant changes or defects occur on the machine, shut it down immediately, secure it against being switched on again and report the incident to the responsible office/person! |
<br>
<br>
@@ -594,11 +533,11 @@ If further work is necessary, such as replacing the extruder screw, please conta
| Picture | Caution! |
| --------------------------------------------- | ----------------------------------------------------------------------------- |
| ![Warning](/images/manuals/extruder/w001.jpeg) | **Observe the instructions for maintenance work when servicing the machine!** |
| ![Warning](images/manuals/extruder/w001.jpeg) | **Observe the instructions for maintenance work when servicing the machine!** |
| Picture | Caution! |
| --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| ![Warning](/images/manuals/extruder/w001.jpeg) | **Risk of screw breakage if disassembled while hot!** <br> Screw connections may only be loosened when the system has cooled down. |
| ![Warning](images/manuals/extruder/w001.jpeg) | **Risk of screw breakage if disassembled while hot!** <br> Screw connections may only be loosened when the system has cooled down. |
<br>
<br>
@@ -681,15 +620,15 @@ Appliances with this logo on the packaging or on the appliance must be disposed
| Picture | Danger! |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <img src="/images/manuals/extruder/w001.jpeg" width="150" height="150"/> | **Danger of getting caught, pulled in or pulled along by moving parts of the machine** <br> Do not wear loose long hair, loose clothing or jewelry! There is always a risk of getting caught, pulled in or pulled along by moving parts! |
| <img src="/images/manuals/extruder/w001.jpeg" width="150" height="150"/> | **Operation only permitted with protective devices fitted!** <br> The extruder may only be operated with properly fitted and functioning safety guards. The operator is responsible for ensuring that all protective devices are properly fitted. |
| <img src="/images/manuals/extruder/w001.jpeg" width="150" height="150"/> | **Danger of cutting tensioned filament** <br> The stretched filament must not be touched between the puller and the spool. Danger of cutting! Do not wind any pointed, sharp-edged or particularly thin materials! |
| <img src="/images/manuals/extruder/w012.jpeg" width="150" height="150"/> | **Electric shock** <br> Before carrying out any repair, set-up or maintenance work, the extruder must be de-energised by disconnecting the mains plug! Pull out the mains plug! |
| <img src="/images/manuals/extruder/w012.jpeg" width="150" height="150"/> | **Electrostatic charges on the puller and spool** <br> Friction of the filament on the puller rollers and on the spool can lead to electrostatic charges. There is a risk of minor electric shocks! Do not touch the affected areas, but keep a sufficient distance! |
| <img src="/images/manuals/extruder/w018.jpeg" width="150" height="150"/> | **Automatic speed adjustment of puller, reel and traverse** <br> The bobbin automatically adjusts its speed to keep the winding pressure constant. The traverse automatically adjusts its speed to the reel in order to maintain the same travel distance per revolution. |
| <img src="/images/manuals/extruder/w025.jpeg" width="150" height="150"/> | **Feeding of body parts** <br> Extreme caution is required in the area of the puller and spool axle! There is an acute danger of getting caught or pulled in! Make sure you wear close-fitting clothing! Always keep the emergency stop switch within easy reach so that it can be activated immediately in an emergency. |
| <img src="/images/manuals/extruder/w024.jpeg" width="150" height="150"/> | **Risk of crushing when threading the filament** <br> Exercise extreme caution when threading the filament through the puller rollers! There is a risk of crushing between the upper and lower pair of rollers. |
| <img src="/images/manuals/extruder/w024.jpeg" width="150" height="150"/> | **Risk of crushing between the traversing unit and machine housing** <br> Exercise extreme caution when moving the traverser towards the housing! Do not place any limbs in the gap! |
| <img src="images/manuals/extruder/w001.jpeg" width="150" height="150"/> | **Danger of getting caught, pulled in or pulled along by moving parts of the machine** <br> Do not wear loose long hair, loose clothing or jewelry! There is always a risk of getting caught, pulled in or pulled along by moving parts! |
| <img src="images/manuals/extruder/w001.jpeg" width="150" height="150"/> | **Operation only permitted with protective devices fitted!** <br> The extruder may only be operated with properly fitted and functioning safety guards. The operator is responsible for ensuring that all protective devices are properly fitted. |
| <img src="images/manuals/extruder/w001.jpeg" width="150" height="150"/> | **Danger of cutting tensioned filament** <br> The stretched filament must not be touched between the puller and the spool. Danger of cutting! Do not wind any pointed, sharp-edged or particularly thin materials! |
| <img src="images/manuals/extruder/w012.jpeg" width="150" height="150"/> | **Electric shock** <br> Before carrying out any repair, set-up or maintenance work, the extruder must be de-energised by disconnecting the mains plug! Pull out the mains plug! |
| <img src="images/manuals/extruder/w012.jpeg" width="150" height="150"/> | **Electrostatic charges on the puller and spool** <br> Friction of the filament on the puller rollers and on the spool can lead to electrostatic charges. There is a risk of minor electric shocks! Do not touch the affected areas, but keep a sufficient distance! |
| <img src="images/manuals/extruder/w018.jpeg" width="150" height="150"/> | **Automatic speed adjustment of puller, reel and traverse** <br> The bobbin automatically adjusts its speed to keep the winding pressure constant. The traverse automatically adjusts its speed to the reel in order to maintain the same travel distance per revolution. |
| <img src="images/manuals/extruder/w025.jpeg" width="150" height="150"/> | **Feeding of body parts** <br> Extreme caution is required in the area of the puller and spool axle! There is an acute danger of getting caught or pulled in! Make sure you wear close-fitting clothing! Always keep the emergency stop switch within easy reach so that it can be activated immediately in an emergency. |
| <img src="images/manuals/extruder/w024.jpeg" width="150" height="150"/> | **Risk of crushing when threading the filament** <br> Exercise extreme caution when threading the filament through the puller rollers! There is a risk of crushing between the upper and lower pair of rollers. |
| <img src="images/manuals/extruder/w024.jpeg" width="150" height="150"/> | **Risk of crushing between the traversing unit and machine housing** <br> Exercise extreme caution when moving the traverser towards the housing! Do not place any limbs in the gap! |
<br>
<br>
@@ -735,7 +674,7 @@ The following persons must not work on the machine:
| Picture | DANGER! |
| ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| ![Warning](/images/manuals/extruder/w001.jpeg) | **Risk of injury due to insufficient qualification!** Improper handling can lead to considerable personal injury and damage to property. |
| ![Warning](images/manuals/extruder/w001.jpeg) | **Risk of injury due to insufficient qualification!** Improper handling can lead to considerable personal injury and damage to property. |
| - Special activities may only be carried out by the persons specified in the respective chapters of these instructions. |
<br>
@@ -764,24 +703,24 @@ The following persons must not work on the machine:
## Electrical Diagrams
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 1](/images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025-001.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 1](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025-001.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 2](/images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_2-002.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 2](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_2-002.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 3](/images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_3-003.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 3](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_3-003.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 4](/images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_4-004.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 4](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_4-004.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 5](/images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_5-005.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 5](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_5-005.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 6](/images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_6-006.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 6](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_6-006.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 7](/images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_7-007.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 7](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_7-007.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 8](/images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_8-008.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 8](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_8-008.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 9](/images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_9-009.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 9](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_9-009.png)
## Mechanical Diagrams
![QiTech Pro Extruder Nozzle 2025](/images/manuals/extruder/Nozzle.png)
![QiTech Pro Extruder Nozzle 2025](images/manuals/extruder/Nozzle.png)
File renamed without changes.
File renamed without changes.
@@ -1,88 +1,4 @@
![Titelseite,screenshot from Word doccument](/images/manuals/winder/Titelseite.jpeg)
<br>
# Data sheet: QiTech Pro Winder
![datasheet header illustration](/images/manuals/winder/datasheet/header.png)
Short description:
The QiTech Pro Winder is a machine for winding and rewinding 3D printing filaments and other strand materials. It is an improvement upon the proven QiTech Winder with support for multiple filament diameters, spools up to 5 kg, angular speeds up to 20 m/min and automatic adjustment of the winding speed depending on the filament tension.
It consists of a pulling unit, a tension arm, a filament guiding traverse and the spool axis. It is controlled by a computer using the QiTech Control software. The traverse movement ensures a clean wind of the filament on the spool. Automatic load adjustment allows for tight winding without having to manually readjust the winding speed each layer. A laser in the traverse allows for easy calibration of the spool width.
<br>
## Scope of delivery
| # | Item |
| - | ----------------------------------------- |
| 1 | QiTech Pro Winder |
| 2 | 2x Spool Carrier-Pins |
| 3 | 4x Weights for tension arm |
| 4 | 230V/110V to 24V Power Supply |
| 5 | USB C to USB A cable |
| 6 | USB Drive with QiTech Control software |
| 7 | Operating manual |
| 8 | optional: wheeled frame |
<br>
## Dimensions and Weight
![dimensions top view](/images/manuals/winder/datasheet/dimensions-top.png)
![dimensions front view](/images/manuals/winder/datasheet/gestell.png)
| | no frame | incl. frame |
| -------------- | -------- | ----------- |
| Weight | 20 kg | 35 kg |
| Length (1) | 91 cm | 91 cm |
| Width (2) | 50 cm | 50 cm |
| Height (3) | 42 cm | 134 cm |
<br>
## Requirements of the Spool
The QiTech Pro Winder is compatible with almost all spools. The spool is rotated by a Carrier-Pin and secured against lateral slipping by a quick release mechanism. The maximum spool weight is 5 kg.
![min/max spool dimensions](/images/manuals/winder/datasheet/spool-min-max.png)
<br>
## Winding Speed
How many meters of filament can be wound per minute at maximum speed depends on the inner diameter of the spool/how much filament has already been wound. The resulting path speed determines how much filament can be wound up in a given time. We take a usual spool with an inner diameter of 100 mm to calculate an expected maximum speed. If the inner diameter is larger, the maximum speed shifts upwards.
Winding Speed:
- Min.: 0.5 Meters/Minute
- Max.: 20 Meters/Minute
<br>
## Friction of the puller rolls
Since the diameter of the extruded filament depends primarily on the pulling speed of the Pro Winder, it is important that it remains constant. This is ensured on the one hand by the use of a stepper motor as drive. At the same time, a vulcanized PU coating of the rolls and a spring-increased contact pressure make the filament only slip through from approx. 30 N tension.
<br>
## Auto-Stop function
When extruding or rewinding, it is often the case that a specific amount of meters must be wound onto a spool. The QiTech Pro Winder has an integrated filament length measurement. For example, after 100 m, it can automatically switch from "wind" to "pull" mode. In addition, the operator is alerted acoustically that the spool has finished winding.
![auto stop ui placeholder](/images/manuals/winder/datasheet/autostop-ui.png)
<br>
## Electronics / Power consumption
| Specification | Value |
| --------------------- | -------------------------- |
| Mains connection | 230V/50Hz or 110V/60Hz |
| Ø Load in operation | 3A at 24V |
| Ø Consumption in use | 72 Wh |
![Titelseite,screenshot from Word doccument](images/manuals/winder/Titelseite.jpeg)
<br>
@@ -139,7 +55,7 @@ Darmstadt, 10/06/2025
64392 Darmstadt,
<img src="/images/manuals/winder/signature.jpeg" width="300" height="60"/>
<img src="images/manuals/winder/signature.jpeg" width="300" height="60"/>
Milan von dem Bussche
@@ -185,7 +101,7 @@ Operating instructions
# 1 Machine description
![maschinenbeschreibung](/images/manuals/winder/description.png)
![maschinenbeschreibung](images/manuals/winder/description.png)
| Item number | Item number Description |
| ----------- | ----------------------------------------- |
@@ -270,7 +186,7 @@ Accuracy class: 2
| Picture | Danger! |
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ![Warning](/images/manuals/winder/w001.jpeg) | **Danger to life due to technically faulty machine.** <br> The machine may only be used when it is in perfect technical condition and in accordance with its intended use, in a safety-conscious and hazard-conscious manner and in compliance with these operating instructions. All faults, especially those that could impair safety, must be rectified immediately before starting the winder. Everyone who works on or with the Qitech Winder is responsible for observing these safety aspects! |
| ![Warning](images/manuals/winder/w001.jpeg) | **Danger to life due to technically faulty machine.** <br> The machine may only be used when it is in perfect technical condition and in accordance with its intended use, in a safety-conscious and hazard-conscious manner and in compliance with these operating instructions. All faults, especially those that could impair safety, must be rectified immediately before starting the winder. Everyone who works on or with the Qitech Winder is responsible for observing these safety aspects! |
<br><br><br>
@@ -278,17 +194,17 @@ Accuracy class: 2
| Pictogram | Description of pictogram | Pictogram | Pictogram Description |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| ![w001](/images/manuals/winder/w001.jpeg) | W001 General warning sign The combination of the symbol W001 and the signal word **"Danger"** indicates an imminently hazardous situation which, if not avoided death or serious injury if it is not avoided. The combination of the symbol W001 and the signal word **"Warning"** indicates an imminently hazardous situation which, if not avoided, will result in death or serious injury. death or serious injury if it is not avoided. The combination of the symbol W001 and the signal word **"Caution"** indicates an imminently hazardous situation which, if not avoided death or serious injury if not avoided. | ![m001](/images/manuals/winder/m001.jpeg) | M001 General warning sign Mandatory sign This combination of symbol and signal word indicates a potentially hazardous situation which, if not avoided, may result in damage to property or the environment. not avoided. |
| ![w008](/images/manuals/winder/w008.jpeg) | W008 Warning of risk of falling | ![m003](/images/manuals/winder/m003.jpeg) | M003 Use hearing protection |
| ![w011](/images/manuals/winder/w011.jpeg) | W011 Warning of danger of slipping | ![m004](/images/manuals/winder/m004.jpeg) | M004 Use eye protection |
| ![w004](/images/manuals/winder/w004.jpeg) | W004 Warning of laser beam | ![m008](/images/manuals/winder/m008.jpeg) | M008 Use foot protection |
| ![w012](/images/manuals/winder/w012.jpeg) | W012 Warning of electrical voltage | ![m009](/images/manuals/winder/m009.jpeg) | M009 Use hand protection |
| ![w017](/images/manuals/winder/w017.jpeg) | W017 Warning of hot surface | ![m010](/images/manuals/winder/m010.jpeg) | M010 Use protective clothing |
| ![w018](/images/manuals/winder/w018.jpeg) | W018 Warning against automatic start-up | ![m012](/images/manuals/winder/m012.jpeg) | M012 Use handrail |
| ![w019](/images/manuals/winder/w019.jpeg) | W019 Warning of crushing hazard | ![m021](/images/manuals/winder/m021.jpeg) | M021 Switch off before maintenance or repair |
| ![w024](/images/manuals/winder/w024.jpeg) | W024 Warning against hand injuries | ![m014](/images/manuals/winder/m014.jpeg) | M014 Use head protection |
| ![w025](/images/manuals/winder/w025.jpeg) | W025 Warning against counter-rotating rollers | | |
| ![w016](/images/manuals/winder/w016.jpeg) | W016 Warning of toxic substances | | |
| ![w001](images/manuals/winder/w001.jpeg) | W001 General warning sign The combination of the symbol W001 and the signal word **"Danger"** indicates an imminently hazardous situation which, if not avoided death or serious injury if it is not avoided. The combination of the symbol W001 and the signal word **"Warning"** indicates an imminently hazardous situation which, if not avoided, will result in death or serious injury. death or serious injury if it is not avoided. The combination of the symbol W001 and the signal word **"Caution"** indicates an imminently hazardous situation which, if not avoided death or serious injury if not avoided. | ![m001](images/manuals/winder/m001.jpeg) | M001 General warning sign Mandatory sign This combination of symbol and signal word indicates a potentially hazardous situation which, if not avoided, may result in damage to property or the environment. not avoided. |
| ![w008](images/manuals/winder/w008.jpeg) | W008 Warning of risk of falling | ![m003](images/manuals/winder/m003.jpeg) | M003 Use hearing protection |
| ![w011](images/manuals/winder/w011.jpeg) | W011 Warning of danger of slipping | ![m004](images/manuals/winder/m004.jpeg) | M004 Use eye protection |
| ![w004](images/manuals/winder/w004.jpeg) | W004 Warning of laser beam | ![m008](images/manuals/winder/m008.jpeg) | M008 Use foot protection |
| ![w012](images/manuals/winder/w012.jpeg) | W012 Warning of electrical voltage | ![m009](images/manuals/winder/m009.jpeg) | M009 Use hand protection |
| ![w017](images/manuals/winder/w017.jpeg) | W017 Warning of hot surface | ![m010](images/manuals/winder/m010.jpeg) | M010 Use protective clothing |
| ![w018](images/manuals/winder/w018.jpeg) | W018 Warning against automatic start-up | ![m012](images/manuals/winder/m012.jpeg) | M012 Use handrail |
| ![w019](images/manuals/winder/w019.jpeg) | W019 Warning of crushing hazard | ![m021](images/manuals/winder/m021.jpeg) | M021 Switch off before maintenance or repair |
| ![w024](images/manuals/winder/w024.jpeg) | W024 Warning against hand injuries | ![m014](images/manuals/winder/m014.jpeg) | M014 Use head protection |
| ![w025](images/manuals/winder/w025.jpeg) | W025 Warning against counter-rotating rollers | | |
| ![w016](images/manuals/winder/w016.jpeg) | W016 Warning of toxic substances | | |
<br><br><br>
@@ -296,15 +212,15 @@ Accuracy class: 2
| Prohibition signs Pictogram | Pictogram description | International pictograms / other Pictogram | Pictogram Description |
| ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| <img src="/images/manuals/winder/p001.jpeg" width="158" height="158"/> | P001 General prohibition sign | <img src="/images/manuals/winder/f001.jpeg" width="158" height="158"> | F001 Fire extinguisher |
| <img src="/images/manuals/winder/p002.jpeg" width="158" height="158"/> | P002 No smoking | <img src="/images/manuals/winder/ANIS_danger.jpeg" width="232" height="60"> | ANSI Z535.3 - DANGER |
| <img src="/images/manuals/winder/p003.jpeg" width="158" height="158"/> | P003 No open flames; fire, open source of ignition and smoking prohibited | <img src="/images/manuals/winder/ANIS_warning.jpeg" width="232" height="60"> | ANSI Z535.3 - WARNING |
| <img src="/images/manuals/winder/p006.jpeg" width="158" height="158"/> | P006 Prohibited for industrial trucks | <img src="/images/manuals/winder/ANIS_caution.jpeg" width="232" height="60"> | ANSI Z535.3 - CAUTION |
| <img src="/images/manuals/winder/p009.jpeg" width="158" height="158"/> | P009 Climbing prohibited - In the sense of climbing prohibited for unauthorized persons | <img src="/images/manuals/winder/ANIS_notice.jpeg" width="232" height="60"> | ANSI Z535.3 - NOTICE |
| <img src="/images/manuals/winder/p022.jpeg" width="158" height="158"/> | P022 Eating and drinking prohibited | <img src="/images/manuals/winder/ANIS_safetyinstructions.jpeg" width="232" height="60"> | ANSI Z535.3 - SAFETY INSTRUCTIONS |
| <img src="/images/manuals/winder/p024.jpeg" width="158" height="158"/> | P024 Entering the area prohibited | <img src="/images/manuals/winder/trashcan.jpeg" width="158" height="158"> | Symbol for separate collection of waste electrical and electronic equipment |
| <img src="/images/manuals/winder/p031.jpeg" width="158" height="158"/> | P031 Switching prohibited | | |
| <img src="/images/manuals/winder/D-p006.jpeg" width="158" height="158"/> | D-P006 Access prohibited for unauthorized persons | | |
| <img src="images/manuals/winder/p001.jpeg" width="158" height="158"/> | P001 General prohibition sign | <img src="images/manuals/winder/f001.jpeg" width="158" height="158"> | F001 Fire extinguisher |
| <img src="images/manuals/winder/p002.jpeg" width="158" height="158"/> | P002 No smoking | <img src="images/manuals/winder/ANIS_danger.jpeg" width="232" height="60"> | ANSI Z535.3 - DANGER |
| <img src="images/manuals/winder/p003.jpeg" width="158" height="158"/> | P003 No open flames; fire, open source of ignition and smoking prohibited | <img src="images/manuals/winder/ANIS_warning.jpeg" width="232" height="60"> | ANSI Z535.3 - WARNING |
| <img src="images/manuals/winder/p006.jpeg" width="158" height="158"/> | P006 Prohibited for industrial trucks | <img src="images/manuals/winder/ANIS_caution.jpeg" width="232" height="60"> | ANSI Z535.3 - CAUTION |
| <img src="images/manuals/winder/p009.jpeg" width="158" height="158"/> | P009 Climbing prohibited - In the sense of climbing prohibited for unauthorized persons | <img src="images/manuals/winder/ANIS_notice.jpeg" width="232" height="60"> | ANSI Z535.3 - NOTICE |
| <img src="images/manuals/winder/p022.jpeg" width="158" height="158"/> | P022 Eating and drinking prohibited | <img src="images/manuals/winder/ANIS_safetyinstructions.jpeg" width="232" height="60"> | ANSI Z535.3 - SAFETY INSTRUCTIONS |
| <img src="images/manuals/winder/p024.jpeg" width="158" height="158"/> | P024 Entering the area prohibited | <img src="images/manuals/winder/trashcan.jpeg" width="158" height="158"> | Symbol for separate collection of waste electrical and electronic equipment |
| <img src="images/manuals/winder/p031.jpeg" width="158" height="158"/> | P031 Switching prohibited | | |
| <img src="images/manuals/winder/D-p006.jpeg" width="158" height="158"/> | D-P006 Access prohibited for unauthorized persons | | |
<br><br><br>
@@ -342,7 +258,7 @@ In addition, the following procedures must be regarded as misuse:
<br><br><br>
![spool sizes](/images/manuals/winder/spools.jpeg)
![spool sizes](images/manuals/winder/spools.jpeg)
<br><br><br>
@@ -370,7 +286,7 @@ Hazardous movements are brought to a controlled standstill as quickly as possibl
<br>
<img src="/images/manuals/winder/emergency_stop.jpeg" width="300" height="300"/>
<img src="images/manuals/winder/emergency_stop.jpeg" width="300" height="300"/>
<br>
@@ -427,8 +343,8 @@ Depending on the activity or the duration of the activity, the operator must det
<br><br>
| Picture | Danger |
|---------|--------|
| <img src="/images/manuals/winder/w001.jpeg" width="150" height="150"/> | **Risk of injury due to improper transport** <br> Be careful when loading the machine, do not place any limbs under the parts when setting them down, do not stand under suspended loads or between moving loads and walls or machine parts. Wear personal protective equipment: safety shoes, protective work clothing, protective gloves. |
| <img src="/images/manuals/winder/w001.jpeg" width="150" height="150"/> | **Danger to life due to incorrect installation and commissioning!** <br> Errors during installation can lead to life-threatening situations or cause considerable damage to property. Installation and commissioning may only be carried out by the manufacturer's employees. |
| <img src="images/manuals/winder/w001.jpeg" width="150" height="150"/> | **Risk of injury due to improper transport** <br> Be careful when loading the machine, do not place any limbs under the parts when setting them down, do not stand under suspended loads or between moving loads and walls or machine parts. Wear personal protective equipment: safety shoes, protective work clothing, protective gloves. |
| <img src="images/manuals/winder/w001.jpeg" width="150" height="150"/> | **Danger to life due to incorrect installation and commissioning!** <br> Errors during installation can lead to life-threatening situations or cause considerable damage to property. Installation and commissioning may only be carried out by the manufacturer's employees. |
<br><br>
@@ -448,7 +364,7 @@ The machines are delivered with the newest version of the software installed. Up
<br>
![screenshot of update tab](/images/manuals/winder/screenshot_updates.png)
![screenshot of update tab](images/manuals/winder/screenshot_updates.png)
<br>
@@ -496,7 +412,7 @@ Then start the machine as follows:
<br>
![screenshot outer limit](/images/manuals/winder/screenshot_traverse.png)
![screenshot outer limit](images/manuals/winder/screenshot_traverse.png)
<br>
@@ -519,7 +435,7 @@ The **padding** parameter creates a safety margin that prevents the filament fro
The default padding is 0.88mm, but you can adjust this value in the Control settings based on your filament diameter and winding requirements. Larger filaments may require more padding, while smaller filaments can use less padding for maximum spool utilization.
![Padding example](/images/manuals/winder/winder_padding_example_image.jpg)
![Padding example](images/manuals/winder/winder_padding_example_image.jpg)
<br>
@@ -532,7 +448,7 @@ The default padding is 0.88mm, but you can adjust this value in the Control sett
<br>
![screernshot target speed](/images/manuals/winder/screenshot_targetspeed.png)
![screernshot target speed](images/manuals/winder/screenshot_targetspeed.png)
<br>
@@ -598,7 +514,7 @@ All settings such as the position of the spool sides, the winding speed or the f
| Picture | Danger! |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <img src="/images/manuals/winder/w001.jpeg" width="150" height="150"/> | **Danger to life due to operation of the machine in faulty condition** <br> If safety-relevant changes or defects occur on the machine, shut down immediately, secure against restarting and report the process to the responsible office/person! |
| <img src="images/manuals/winder/w001.jpeg" width="150" height="150"/> | **Danger to life due to operation of the machine in faulty condition** <br> If safety-relevant changes or defects occur on the machine, shut down immediately, secure against restarting and report the process to the responsible office/person! |
<br><br>
@@ -642,14 +558,14 @@ The polyurethane coating of the puller rollers will inevitably wear out over tim
Now remove the black plastic caps and simply pull out the puller rollers. To install the new rollers back on, slide them on the axes, put the plastic cap back on and screw the screws.
<img src="/images/manuals/winder/swapping_out_rollers.jpeg" width="300" height="400"/>
<img src="images/manuals/winder/swapping_out_rollers.jpeg" width="300" height="400"/>
<br>
<br>
| Picture | Caution! |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| <img src="/images/manuals/winder/w001.jpeg" width="150" height="150"/> | Observe the instructions for maintenance work when servicing the machine! |
| <img src="images/manuals/winder/w001.jpeg" width="150" height="150"/> | Observe the instructions for maintenance work when servicing the machine! |
<br>
<br>
@@ -714,7 +630,7 @@ The machine consists largely of aluminum and plastic and can be easily separated
| Picture | Disposal Instructions |
| ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <img src="/images/manuals/winder/trashcan.jpeg" width="150" height="150"/> | Appliances with this logo on the packaging or on the appliance must be disposed of separately. These devices must not be disposed of with normal household waste. You are responsible for ensuring that any electrical or electronic waste is disposed of at the appropriate locations, e.g., recycling centers. |
| <img src="images/manuals/winder/trashcan.jpeg" width="150" height="150"/> | Appliances with this logo on the packaging or on the appliance must be disposed of separately. These devices must not be disposed of with normal household waste. You are responsible for ensuring that any electrical or electronic waste is disposed of at the appropriate locations, e.g., recycling centers. |
<br><br><br>
@@ -722,15 +638,15 @@ The machine consists largely of aluminum and plastic and can be easily separated
| Picture | Danger! |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <img src="/images/manuals/winder/w001.jpeg" width="150" height="150"/> | **Danger of getting caught, pulled in or pulled along by moving parts of the machine** <br> Do not wear loose long hair, loose clothing or jewelry! There is always a risk of getting caught, pulled in or pulled along by moving parts! |
| <img src="/images/manuals/winder/w001.jpeg" width="150" height="150"/> | **Operation only permitted with protective devices fitted!** <br> The winder may only be operated with properly fitted and functioning safety guards. The operator is responsible for ensuring that all protective devices are properly fitted. |
| <img src="/images/manuals/winder/w001.jpeg" width="150" height="150"/> | **Danger of cutting tensioned filament** <br> The stretched filament must not be touched between the puller and the spool. Danger of cutting! Do not wind any pointed, sharp-edged or particularly thin materials! |
| <img src="/images/manuals/winder/w012.jpeg" width="150" height="150"/> | **Electric shock** <br> Before carrying out any repair, set-up or maintenance work, the winder must be de-energised by disconnecting the mains plug! Pull out the mains plug! |
| <img src="/images/manuals/winder/w012.jpeg" width="150" height="150"/> | **Electrostatic charges on the puller and spool** <br> Friction of the filament on the puller rollers and on the spool can lead to electrostatic charges. There is a risk of minor electric shocks! Do not touch the affected areas, but keep a sufficient distance! |
| <img src="/images/manuals/winder/w018.jpeg" width="150" height="150"/> | **Automatic speed adjustment of puller, reel and traverse** <br> The bobbin automatically adjusts its speed to keep the winding pressure constant. The traverse automatically adjusts its speed to the reel in order to maintain the same travel distance per revolution. |
| <img src="/images/manuals/winder/w025.jpeg" width="150" height="150"/> | **Feeding of body parts** <br> Extreme caution is required in the area of the puller and spool axle! There is an acute danger of getting caught or pulled in! Make sure you wear close-fitting clothing! Always keep the emergency stop switch within easy reach so that it can be activated immediately in an emergency. |
| <img src="/images/manuals/winder/w024.jpeg" width="150" height="150"/> | **Risk of crushing when threading the filament** <br> Exercise extreme caution when threading the filament through the puller rollers! There is a risk of crushing between the upper and lower pair of rollers. |
| <img src="/images/manuals/winder/w024.jpeg" width="150" height="150"/> | **Risk of crushing between the traversing unit and machine housing** <br> Exercise extreme caution when moving the traverser towards the housing! Do not place any limbs in the gap! |
| <img src="images/manuals/winder/w001.jpeg" width="150" height="150"/> | **Danger of getting caught, pulled in or pulled along by moving parts of the machine** <br> Do not wear loose long hair, loose clothing or jewelry! There is always a risk of getting caught, pulled in or pulled along by moving parts! |
| <img src="images/manuals/winder/w001.jpeg" width="150" height="150"/> | **Operation only permitted with protective devices fitted!** <br> The winder may only be operated with properly fitted and functioning safety guards. The operator is responsible for ensuring that all protective devices are properly fitted. |
| <img src="images/manuals/winder/w001.jpeg" width="150" height="150"/> | **Danger of cutting tensioned filament** <br> The stretched filament must not be touched between the puller and the spool. Danger of cutting! Do not wind any pointed, sharp-edged or particularly thin materials! |
| <img src="images/manuals/winder/w012.jpeg" width="150" height="150"/> | **Electric shock** <br> Before carrying out any repair, set-up or maintenance work, the winder must be de-energised by disconnecting the mains plug! Pull out the mains plug! |
| <img src="images/manuals/winder/w012.jpeg" width="150" height="150"/> | **Electrostatic charges on the puller and spool** <br> Friction of the filament on the puller rollers and on the spool can lead to electrostatic charges. There is a risk of minor electric shocks! Do not touch the affected areas, but keep a sufficient distance! |
| <img src="images/manuals/winder/w018.jpeg" width="150" height="150"/> | **Automatic speed adjustment of puller, reel and traverse** <br> The bobbin automatically adjusts its speed to keep the winding pressure constant. The traverse automatically adjusts its speed to the reel in order to maintain the same travel distance per revolution. |
| <img src="images/manuals/winder/w025.jpeg" width="150" height="150"/> | **Feeding of body parts** <br> Extreme caution is required in the area of the puller and spool axle! There is an acute danger of getting caught or pulled in! Make sure you wear close-fitting clothing! Always keep the emergency stop switch within easy reach so that it can be activated immediately in an emergency. |
| <img src="images/manuals/winder/w024.jpeg" width="150" height="150"/> | **Risk of crushing when threading the filament** <br> Exercise extreme caution when threading the filament through the puller rollers! There is a risk of crushing between the upper and lower pair of rollers. |
| <img src="images/manuals/winder/w024.jpeg" width="150" height="150"/> | **Risk of crushing between the traversing unit and machine housing** <br> Exercise extreme caution when moving the traverser towards the housing! Do not place any limbs in the gap! |
# 4 Glossary and Definitions
@@ -771,7 +687,7 @@ The following persons may not work on the machine:
| Picture | Danger! |
| --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <img src="/images/manuals/winder/w001.jpeg" width="150" height="150"/> | **Risk of injury if insufficiently qualified!** <br> Improper handling can lead to considerable personal injury and damage to property. Special activities may only be carried out by the persons specified in the relevant sections of these instructions. |
| <img src="images/manuals/winder/w001.jpeg" width="150" height="150"/> | **Risk of injury if insufficiently qualified!** <br> Improper handling can lead to considerable personal injury and damage to property. Special activities may only be carried out by the persons specified in the relevant sections of these instructions. |
<br>
<br>
@@ -800,12 +716,12 @@ The following persons may not work on the machine:
## Electrical Diagrams
![QiTech Pro Winder Electrical Diagram - Sheet 1](/images/manuals/winder/QiTech_Pro_Winder_Electrical_Diagram-001.png)
![QiTech Pro Winder Electrical Diagram - Sheet 1](images/manuals/winder/QiTech_Pro_Winder_Electrical_Diagram-001.png)
![QiTech Pro Winder Electrical Diagram - Sheet 2](/images/manuals/winder/QiTech_Pro_Winder_Electrical_Diagram_2-002.png)
![QiTech Pro Winder Electrical Diagram - Sheet 2](images/manuals/winder/QiTech_Pro_Winder_Electrical_Diagram_2-002.png)
![QiTech Pro Winder Electrical Diagram - Sheet 3](/images/manuals/winder/QiTech_Pro_Winder_Electrical_Diagram_3-003.png)
![QiTech Pro Winder Electrical Diagram - Sheet 3](images/manuals/winder/QiTech_Pro_Winder_Electrical_Diagram_3-003.png)
![QiTech Pro Winder Electrical Diagram - Sheet 4](/images/manuals/winder/QiTech_Pro_Winder_Electrical_Diagram_4-004.png)
![QiTech Pro Winder Electrical Diagram - Sheet 4](images/manuals/winder/QiTech_Pro_Winder_Electrical_Diagram_4-004.png)
![QiTech Pro Winder Electrical Diagram - Sheet 5](/images/manuals/winder/QiTech_Pro_Winder_Electrical_Diagram_5-005.png)
![QiTech Pro Winder Electrical Diagram - Sheet 5](images/manuals/winder/QiTech_Pro_Winder_Electrical_Diagram_5-005.png)
+31 -27
View File
@@ -15,25 +15,31 @@ import {
machineIdentificationUnique,
} from "@/machines/types";
import { useRef } from "react";
import { rustEnumSchema } from "@/lib/types";
import { rustEnum } from "@/lib/types";
export type EthercatDevices = z.infer<typeof ethercatDevicesSchema>;
export const ethercatDevicesSchema = z.object({
devices: z.array(
z.object({
configured_address: z.number().int(),
name: z.string(),
vendor_id: z.number().int(),
product_id: z.number().int(),
revision: z.number().int(),
device_identification: deviceIdentification,
}),
),
});
// Update the EthercatDevicesEventData schema
export const ethercatDevicesEventDataSchema = rustEnumSchema({
Initializing: z.boolean(),
Done: z.object({
devices: z.array(
z.object({
configured_address: z.number().int(),
name: z.string(),
vendor_id: z.number().int(),
product_id: z.number().int(),
revision: z.number().int(),
device_identification: deviceIdentification,
}),
),
}),
Error: z.string(),
});
export const ethercatDevicesEventDataSchema = z
.object({
Initializing: z.boolean(),
Done: ethercatDevicesSchema,
Error: z.string(),
})
.check(rustEnum);
export type EthercatDevicesEventData = z.infer<
typeof ethercatDevicesEventDataSchema
@@ -62,10 +68,12 @@ export const machinesEventSchema = eventSchema(machinesEventDataSchema);
export type MachinesEvent = z.infer<typeof machinesEventSchema>;
// Keep the EthercatInterfaceDiscovery event
export const ethercatInterfaceDiscoveryEventDataSchema = rustEnumSchema({
Discovering: z.boolean(),
Done: z.string(),
});
export const ethercatInterfaceDiscoveryEventDataSchema = z
.object({
Discovering: z.boolean(),
Done: z.string(),
})
.check(rustEnum);
export type EthercatInterfaceDiscoveryEventData = z.infer<
typeof ethercatInterfaceDiscoveryEventDataSchema
@@ -110,23 +118,19 @@ export function mainMessageHandler(
try {
// Apply appropriate caching strategy based on event type
if (eventName === "EthercatDevicesEvent") {
const validatedEvent = ethercatDevicesEventSchema.parse(event);
console.log("EthercatDevicesEvent", validatedEvent);
const validatedEvent = event;
store.setState((state) => ({
...state,
ethercatDevices: validatedEvent,
}));
} else if (eventName === "MachinesEvent") {
const validatedEvent = machinesEventSchema.parse(event);
console.log("MachinesEvent", validatedEvent);
store.setState((state) => ({
...state,
machines: validatedEvent,
}));
} else if (eventName === "EthercatInterfaceDiscoveryEvent") {
const validatedEvent =
ethercatInterfaceDiscoveryEventSchema.parse(event);
console.log("EthercatInterfaceDiscoveryEvent", validatedEvent);
const validatedEvent = event;
store.setState((state) => ({
...state,
ethercatInterfaceDiscovery: validatedEvent,
+4 -4
View File
@@ -8,8 +8,7 @@ import { z } from "zod";
import { toastError, toastZodError } from "@/components/Toast";
import { MachineIdentificationUnique } from "@/machines/types";
import { FPS_30 } from "@/lib/constants";
import { mainNamespaceStore, createMainNamespaceStore } from "./mainNamespace";
import { softReloadMainNamespaceStore } from "./softReload";
import { mainNamespaceStore } from "./mainNamespace";
/**
* Simple buffer-based store updater to limit React re-renders to ~30 FPS
@@ -164,6 +163,7 @@ export const handleEventValidationError = (
error: z.ZodError,
eventName: string,
) => {
console.log(error);
toastZodError(error, `Event Validation Error for ${eventName}`);
throw new Error(`Event validation failed for ${eventName}`);
};
@@ -385,11 +385,11 @@ const useSocketioStore = create<SocketioStore>()((set, get) => ({
},
decrementNamespace: (namespaceId: NamespaceId) => {
const namespace_path = serializeNamespaceId(namespaceId);
/*const namespace_path = serializeNamespaceId(namespaceId);
// check if the namespace exists
const namespace = get().namespaces[namespace_path];
/*if (namespace) {
if (namespace) {
set(
produce((state: SocketioStore) => {
// decrement the count
@@ -1,67 +0,0 @@
import React from "react";
import { Markdown } from "./Markdown";
type ManualMarkdownProps = {
/**
* The markdown content to render
*/
markdownContent: string;
};
/**
* A specialized Markdown component for rendering machine manuals.
*
* This component transforms image paths in markdown to work correctly in the Electron app.
* Manual markdown files are stored in `docs/machines/manuals/` with relative image paths
* pointing to `../../../../electron/public/images/manuals/[machine-name]/...`
*
* This component transforms those paths to absolute paths `/images/manuals/[machine-name]/...`
* that work correctly in the Electron app's context.
*
* Path transformation examples:
* - `../../../../electron/public/images/manuals/winder/image.png` → `/images/manuals/winder/image.png`
* - `images/manuals/winder/image.png` → `/images/manuals/winder/image.png`
*/
export function ManualMarkdown({ markdownContent }: ManualMarkdownProps) {
const transformedContent = React.useMemo(() => {
// Transform image paths to work in Electron context
// Pattern 1: ../../../../electron/public/images/manuals/... → /images/manuals/...
let content = markdownContent.replace(
/!\[([^\]]*)\]\(\.\.\/\.\.\/\.\.\/\.\.\/electron\/public\/(images\/manuals\/[^)]+)\)/g,
"![$1](/$2)",
);
// Pattern 2: ../../../electron/public/images/manuals/... → /images/manuals/... (backward compatibility)
content = content.replace(
/!\[([^\]]*)\]\(\.\.\/\.\.\/\.\.\/electron\/public\/(images\/manuals\/[^)]+)\)/g,
"![$1](/$2)",
);
// Pattern 3: images/manuals/... → /images/manuals/... (for backward compatibility)
content = content.replace(
/!\[([^\]]*)\]\(images\/manuals\/([^)]+)\)/g,
"![$1](/images/manuals/$2)",
);
// Pattern 4: Handle <img> tags with src attribute (new path)
content = content.replace(
/<img\s+([^>]*)src="\.\.\/\.\.\/\.\.\/\.\.\/electron\/public\/(images\/manuals\/[^"]+)"/g,
'<img $1src="/$2"',
);
// Pattern 5: Handle <img> tags with src attribute (old path)
content = content.replace(
/<img\s+([^>]*)src="\.\.\/\.\.\/\.\.\/electron\/public\/(images\/manuals\/[^"]+)"/g,
'<img $1src="/$2"',
);
content = content.replace(
/<img\s+([^>]*)src="images\/manuals\/([^"]+)"/g,
'<img $1src="/images/manuals/$2"',
);
return content;
}, [markdownContent]);
return <Markdown text={transformedContent} />;
}
+1 -14
View File
@@ -1,6 +1,5 @@
import React from "react";
import { Markdown } from "./Markdown";
import { ManualMarkdown } from "./ManualMarkdown";
import { MarkdownToc } from "./MarkdownToc";
type MarkdownWithTocProps = {
@@ -27,11 +26,6 @@ type MarkdownWithTocProps = {
* @default "top-4"
*/
stickyTop?: string;
/**
* Whether this is a machine manual (enables image path transformation)
* @default false
*/
isManual?: boolean;
};
/**
@@ -50,19 +44,12 @@ export function MarkdownWithToc({
tocWidth = "w-64",
gap = "gap-6",
stickyTop = "top-6",
isManual = false,
}: MarkdownWithTocProps) {
const MarkdownComponent = isManual ? ManualMarkdown : Markdown;
return (
<div className={`flex ${gap}`}>
{/* Main Content */}
<div className="min-w-0 flex-1">
{isManual ? (
<ManualMarkdown markdownContent={markdownContent} />
) : (
<Markdown text={markdownContent} />
)}
<Markdown text={markdownContent} />
</div>
{/* Table of Contents Sidebar */}
+12
View File
@@ -84,6 +84,18 @@ export function toastError(title: string, error: string) {
toast(<ErrorToast title={title} error={error} />);
}
export function SuccessToast({ title, message }: Props & { message: string }) {
return (
<Toast title={title} icon="lu:Check">
<div className="text-zinc-500">{message}</div>
</Toast>
);
}
export function toastSuccess(title: string, message: string) {
toast(<SuccessToast title={title} message={message} />);
}
const friendlyHttpStatus = {
"200": "OK",
"201": "Created",
@@ -1,4 +1,3 @@
import { PresetData, PresetSchema } from "@/lib/preset/preset";
import {
Dialog,
DialogContent,
@@ -14,13 +13,13 @@ import { Separator } from "../ui/separator";
import { PresetPreviewEntries, PresetPreviewTable } from "./PresetPreviewTable";
import { Input } from "../ui/input";
export type NewPresetDialogProps<T extends PresetSchema> = {
currentState?: PresetData<T>;
export type NewPresetDialogProps<T> = {
currentState?: T;
previewEntries: PresetPreviewEntries<T>;
onSave: (name: string) => void;
};
export function NewPresetDialog<T extends PresetSchema>({
export function NewPresetDialog<T>({
currentState,
onSave,
previewEntries,
@@ -42,7 +41,7 @@ export function NewPresetDialog<T extends PresetSchema>({
</TouchButton>
</DialogTrigger>
<DialogContent>
<DialogContent className="flex max-h-[90vh] flex-col">
<DialogHeader>
<DialogTitle className="flex flex-row items-center gap-2">
<Icon name="lu:SquarePlus" />
@@ -59,7 +58,7 @@ export function NewPresetDialog<T extends PresetSchema>({
onChange={(e) => setName(e.target.value)}
className="w-full"
/>
<div className="flex flex-col gap-6 text-sm">
<div className="flex min-h-0 flex-1 flex-col gap-6 overflow-y-auto text-sm">
<span>Current Settings:</span>
<PresetPreviewTable entries={previewEntries} data={currentState} />
</div>
@@ -60,9 +60,9 @@ export function PresetCardMenu({
);
}
export type PresetCardProps<T extends PresetSchema> = {
export type PresetCardProps<T> = {
preset: Preset<T>;
onApply: (preset: Preset<T>) => void;
onApply: (preset: Preset<T>) => void | Promise<void>;
onOverwrite: (preset: Preset<T>) => void;
onDelete: (preset: Preset<T>) => void;
onExport?: (preset: Preset<T>) => void;
@@ -72,7 +72,7 @@ export type PresetCardProps<T extends PresetSchema> = {
isActive?: boolean;
};
export function PresetCard<T extends PresetSchema>({
export function PresetCard<T>({
preset,
onApply,
onOverwrite,
@@ -1,28 +1,27 @@
import React, { Fragment } from "react";
import { PresetData, PresetSchema } from "@/lib/preset/preset";
import { renderUnitSymbol, renderUnitSyntax, Unit } from "@/control/units";
export const previewSeparator = undefined;
type PreviewSeparator = typeof previewSeparator;
type PresetPreviewEntry<T extends PresetSchema> = {
type PresetPreviewEntry<T> = {
name: string;
unit?: Unit;
renderValue: (data: PresetData<T>) => string | undefined;
renderValue: (data: T) => string | undefined;
};
export type PresetPreviewEntries<T extends PresetSchema> = (
export type PresetPreviewEntries<T> = (
| PresetPreviewEntry<T>
| PreviewSeparator
)[];
export type PresetPreviewTableProps<T extends PresetSchema> = {
data: PresetData<T>;
export type PresetPreviewTableProps<T> = {
data?: T;
entries: PresetPreviewEntries<T>;
};
export function PresetPreviewTable<T extends PresetSchema>({
export function PresetPreviewTable<T>({
data,
entries,
}: PresetPreviewTableProps<T>) {
@@ -39,7 +38,7 @@ export function PresetPreviewTable<T extends PresetSchema>({
);
}
const value = entry.renderValue(data);
const value = data !== undefined ? entry.renderValue(data) : undefined;
return (
<Fragment key={entry.name}>
<div>{entry.name}</div>
@@ -1,4 +1,4 @@
import { Preset, PresetSchema } from "@/lib/preset/preset";
import { Preset } from "@/lib/preset/preset";
import {
Dialog,
DialogContent,
@@ -13,14 +13,14 @@ import { Icon } from "../Icon";
import { Separator } from "../ui/separator";
import { PresetPreviewEntries, PresetPreviewTable } from "./PresetPreviewTable";
export type PresetShowDialogProps<T extends PresetSchema> = {
export type PresetShowDialogProps<T> = {
preset: Preset<T>;
previewEntries: PresetPreviewEntries<T>;
onApply: (preset: Preset<T>) => void;
onApply: (preset: Preset<T>) => void | Promise<void>;
hideDate?: boolean;
};
export function PresetShowDialog<T extends PresetSchema>({
export function PresetShowDialog<T>({
preset,
onApply,
previewEntries,
@@ -41,7 +41,7 @@ export function PresetShowDialog<T extends PresetSchema>({
</TouchButton>
</DialogTrigger>
<DialogContent>
<DialogContent className="flex max-h-[90vh] flex-col">
<DialogHeader>
<DialogTitle className="flex flex-row items-center gap-2">
<Icon name="lu:Save" />
@@ -53,7 +53,7 @@ export function PresetShowDialog<T extends PresetSchema>({
</DialogHeader>
<Separator />
<div className="flex flex-col gap-6 text-sm">
<div className="flex min-h-0 flex-1 flex-col gap-6 overflow-y-auto text-sm">
{!hideDate && (
<div>
Latest modification at{" "}
@@ -2,19 +2,19 @@ import React from "react";
import { ControlGrid } from "@/control/ControlGrid";
import { Page } from "@/components/Page";
import { usePresets, UsePresetsParams } from "@/lib/preset/usePresets";
import { Preset, PresetSchema } from "@/lib/preset/preset";
import { Preset } from "@/lib/preset/preset";
import { PresetCard } from "./PresetCard";
import { PresetPreviewEntries } from "./PresetPreviewTable";
import { NewPresetDialog } from "./NewPresetDialog";
import { downloadJson } from "@/lib/download";
import { JsonFileInput } from "../FileInput";
type PresetsPageProps<T extends PresetSchema> = UsePresetsParams<T> & {
applyPreset: (preset: Preset<T>) => void;
type PresetsPageProps<T> = UsePresetsParams<T> & {
applyPreset: (preset: Preset<T>) => void | Promise<void>;
previewEntries: PresetPreviewEntries<T>;
};
export function PresetsPage<T extends PresetSchema>({
export function PresetsPage<T>({
applyPreset,
machine_identification,
currentState,
@@ -0,0 +1,427 @@
import React, { useState, useCallback, useEffect } from "react";
import { TouchButton } from "@/components/touch/TouchButton";
import { Icon } from "@/components/Icon";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import { useForm, useWatch } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Separator } from "@/components/ui/separator";
type RatioInputProps = {
master?: number;
slave?: number;
onRatioChange?: (master: number, slave: number) => void;
title?: string;
min?: number;
max?: number;
step?: number;
};
const ratioSchema = z.object({
master: z.number().min(0.1).max(1000),
slave: z.number().min(0.1).max(1000),
});
type RatioFormData = z.infer<typeof ratioSchema>;
export function RatioInput({
master = 1,
slave = 1,
onRatioChange,
title = "Motor Ratio",
min = 0.1,
max = 1000,
step = 0.1,
}: RatioInputProps) {
const [isOpen, setIsOpen] = useState(false);
const [activeField, setActiveField] = useState<"master" | "slave" | null>(
null,
);
const [inputValue, setInputValue] = useState<string>("");
const getInitialValues = useCallback(() => {
return {
master: master ?? 1,
slave: slave ?? 1,
};
}, [master, slave]);
const form = useForm<RatioFormData>({
resolver: zodResolver(ratioSchema),
defaultValues: getInitialValues(),
mode: "all",
});
// Initialize form with current values when dialog opens
useEffect(() => {
if (isOpen) {
const initialValues = getInitialValues();
form.reset(initialValues);
setActiveField(null);
setInputValue("");
}
}, [isOpen, getInitialValues, form]);
const formatRatioDisplay = useCallback(() => {
const m = master ?? 1;
const s = slave ?? 1;
return `${m.toFixed(1)} : ${s.toFixed(1)}`;
}, [master, slave]);
// Increment/decrement functions with bounds checking
const adjustRatioValue = useCallback(
(field: "master" | "slave", increment: boolean) => {
const currentValue = form.getValues(field);
let newValue = increment ? currentValue + step : currentValue - step;
// Round to avoid floating point precision issues
newValue = Math.round(newValue * 10) / 10;
// Clamp to min/max bounds
if (newValue < min) {
newValue = min;
} else if (newValue > max) {
newValue = max;
}
form.setValue(field, newValue, {
shouldValidate: true,
shouldDirty: true,
shouldTouch: true,
});
},
[form, step, min, max],
);
const handleNumpadClick = (digit: string) => {
if (activeField === null) return;
const newValue = inputValue + digit;
setInputValue(newValue);
};
const handleDecimal = () => {
if (activeField === null) return;
if (!inputValue.includes(".")) {
setInputValue(inputValue === "" ? "0." : inputValue + ".");
}
};
const handleBackspace = () => {
setInputValue(inputValue.slice(0, -1));
};
const handleClear = () => {
setInputValue("");
};
const handleNumpadConfirm = () => {
if (activeField === null || inputValue === "") return;
let numValue = parseFloat(inputValue);
// Clamp to min/max bounds
if (numValue < min) {
numValue = min;
} else if (numValue > max) {
numValue = max;
}
form.setValue(activeField, numValue, {
shouldValidate: true,
shouldDirty: true,
shouldTouch: true,
});
setInputValue("");
setActiveField(null);
};
const handleFieldClick = (field: "master" | "slave") => {
if (activeField === field) {
// If same field, submit and close
handleNumpadConfirm();
} else {
// Switch to new field
if (activeField !== null && inputValue !== "") {
handleNumpadConfirm();
}
setActiveField(field);
setInputValue("");
}
};
const handleSubmit = () => {
if (activeField !== null && inputValue !== "") {
handleNumpadConfirm();
return;
}
form.handleSubmit((data) => {
onRatioChange?.(data.master, data.slave);
setIsOpen(false);
})();
};
const handleAbort = () => {
const initialValues = getInitialValues();
form.reset(initialValues);
setActiveField(null);
setInputValue("");
setIsOpen(false);
};
const handleReset = () => {
form.setValue("master", 1, {
shouldValidate: true,
shouldDirty: true,
shouldTouch: true,
});
form.setValue("slave", 1, {
shouldValidate: true,
shouldDirty: true,
shouldTouch: true,
});
setActiveField(null);
setInputValue("");
};
// Watch individual form fields for real-time updates
const masterValue = useWatch({
control: form.control,
name: "master",
defaultValue: getInitialValues().master,
});
const slaveValue = useWatch({
control: form.control,
name: "slave",
defaultValue: getInitialValues().slave,
});
// Get current form values with fallback
const currentValues = {
master: masterValue ?? 1,
slave: slaveValue ?? 1,
};
// RatioValueInput component for individual ratio fields
const RatioValueInput = ({
field,
label,
value,
}: {
field: "master" | "slave";
label: string;
value: number;
}) => {
const isActive = activeField === field;
const displayValue =
isActive && inputValue !== "" ? inputValue : value.toFixed(1);
return (
<div className="flex flex-col items-center gap-3">
<label className="text-sm font-medium text-gray-700">{label}</label>
<div className="flex flex-col items-center gap-2">
<TouchButton
variant="outline"
size="sm"
className="h-10 w-20 p-0"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
adjustRatioValue(field, true);
}}
disabled={value >= max}
>
<Icon name="lu:ChevronUp" className="size-4" />
</TouchButton>
<div
onClick={() => handleFieldClick(field)}
className={`flex h-16 w-24 cursor-pointer items-center justify-center rounded border-2 font-mono text-3xl transition-colors ${
isActive
? "border-blue-500 bg-blue-50"
: "border-gray-300 bg-white"
}`}
>
{displayValue}
</div>
<TouchButton
variant="outline"
size="sm"
className="h-10 w-20 p-0"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
adjustRatioValue(field, false);
}}
disabled={value <= min}
>
<Icon name="lu:ChevronDown" className="size-4" />
</TouchButton>
</div>
</div>
);
};
// Numpad component
const Numpad = () => (
<div className="flex flex-col gap-3">
{/* Display showing input value */}
<div className="flex h-12 items-center justify-center rounded border-2 border-gray-300 bg-gray-50 font-mono text-xl">
{inputValue === "" ? "0" : inputValue}
</div>
{/* Numpad grid */}
<div className="grid grid-cols-3 gap-2">
{[
["1", "2", "3"],
["4", "5", "6"],
["7", "8", "9"],
[".", "0", "DEL"],
].map((row, i) => (
<div key={i} className="contents">
{row.map((key) => (
<TouchButton
key={key}
variant="outline"
className="h-12 p-0"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (key === "DEL") {
handleBackspace();
} else if (key === ".") {
handleDecimal();
} else {
handleNumpadClick(key);
}
}}
>
{key === "DEL" ? (
<Icon name="lu:Delete" className="size-4" />
) : (
key
)}
</TouchButton>
))}
</div>
))}
</div>
{/* Control buttons */}
<div className="flex gap-2">
<TouchButton
variant="outline"
className="h-10 flex-1"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleClear();
}}
>
C
</TouchButton>
<TouchButton
className="h-10 flex-1 bg-green-600 text-white hover:bg-green-700"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleNumpadConfirm();
}}
disabled={activeField === null || inputValue === ""}
>
<Icon name="lu:Check" className="size-4" />
</TouchButton>
</div>
</div>
);
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<TouchButton
variant="outline"
className="h-auto border-gray-300 bg-white px-3 py-3 text-base text-gray-900 hover:bg-gray-50"
>
<Icon name="lu:Settings" className="mr-2 size-4" />
{formatRatioDisplay()}
</TouchButton>
</DialogTrigger>
<DialogContent className="max-w-4xl p-8">
<div className="flex flex-col gap-8">
{/* Header */}
<div className="flex min-h-[2.5rem] items-center justify-between">
<div className="flex items-center gap-3">
<Icon name="lu:Settings" className="size-6" />
<h3 className="text-xl font-semibold">{title}</h3>
</div>
<TouchButton
variant="ghost"
size="sm"
onClick={handleReset}
className="text-blue-600 hover:text-blue-700"
>
<Icon name="lu:RotateCcw" className="mr-2 size-4" />
Reset to 1:1
</TouchButton>
</div>
{/* Description */}
<div className="-mt-4 text-base text-gray-600">
Set the gear ratio. For every {currentValues.master.toFixed(1)}{" "}
rotation(s) of the master puller, the motor will make{" "}
{currentValues.slave.toFixed(1)} rotation(s).
</div>
<Separator />
{/* Main content: Ratio inputs and Numpad */}
<div className="flex gap-8">
{/* Ratio inputs */}
<div className="flex flex-1 items-center justify-center gap-8">
<RatioValueInput
field="master"
label="Master"
value={currentValues.master}
/>
<div className="mt-8 text-4xl font-bold">:</div>
<RatioValueInput
field="slave"
label="Slave"
value={currentValues.slave}
/>
</div>
{/* Numpad */}
<div className="w-40">
<Numpad />
</div>
</div>
<Separator />
{/* Action buttons */}
<div className="flex gap-4">
<TouchButton
variant="outline"
className="h-14 flex-1"
onClick={handleAbort}
>
<Icon name="lu:X" className="mr-2 size-4" />
Cancel
</TouchButton>
<TouchButton
className="h-14 flex-1 bg-blue-600 text-white hover:bg-blue-700"
onClick={handleSubmit}
>
<Icon name="lu:Check" className="mr-2 size-4" />
Apply
</TouchButton>
</div>
</div>
</DialogContent>
</Dialog>
);
}
+2 -13
View File
@@ -36,7 +36,6 @@ type Props = {
maxSlider?: number; // Override the slider max value
maxLabel?: string;
step?: number;
valueSchema?: z.ZodType<number>;
inverted?: boolean;
confirmation?: string;
renderValue: (value: number) => string;
@@ -89,7 +88,6 @@ export function EditValue({
description,
defaultValue,
step = 1,
valueSchema: schema,
inverted,
min,
max,
@@ -104,7 +102,7 @@ export function EditValue({
// Form setup
const formSchema = z.object({
value: schema ?? z.number(),
value: z.number(),
});
type FormSchema = z.infer<typeof formSchema>;
@@ -191,7 +189,6 @@ export function EditValue({
const numericValue = parseFloat(normalizedInput);
const hasValidationError =
isNaN(numericValue) ||
(schema && !schema.safeParse(numericValue).success) ||
(max !== undefined && numericValue > max) ||
(min !== undefined && numericValue < min);
@@ -222,15 +219,7 @@ export function EditValue({
setValueStringError(false);
preventFormSyncRef.current = false;
}
}, [
valueString,
valueStringDirty,
schema,
max,
min,
form,
roundToStepDecimals,
]);
}, [valueString, valueStringDirty, max, min, form, roundToStepDecimals]);
// Reset input state to clean form value
const resetInput = React.useCallback(() => {
+8 -2
View File
@@ -4,7 +4,7 @@ import { cva } from "class-variance-authority";
import React from "react";
type Props = {
variant: "error" | "success";
variant: "error" | "success" | "info";
children: React.ReactNode;
};
@@ -14,10 +14,16 @@ export function StatusBadge({ variant, children }: Props) {
variant: {
error: "bg-red-500",
success: "bg-green-600",
info: "bg-blue-500",
},
},
});
const icon: IconName = variant === "error" ? "lu:TriangleAlert" : "lu:Check";
const icon: IconName =
variant === "error"
? "lu:TriangleAlert"
: variant === "success"
? "lu:Check"
: "lu:Info";
return (
<Badge
className={badgeStyle({
+17 -6
View File
@@ -28,10 +28,9 @@ export function getUnitIcon(unit: Unit): IconName {
case "Hz":
return "lu:AudioWaveform";
case "W":
return "lu:Zap";
case "V":
return "lu:Zap";
case "A":
case "mA":
return "lu:Zap";
case "kWh":
return "lu:BatteryFull";
@@ -43,6 +42,8 @@ export function getUnitIcon(unit: Unit): IconName {
return "lu:RefreshCcw";
case "s":
return "lu:Clock";
case "min":
return "lu:Clock";
case "MiB":
return "lu:MemoryStick";
case "Mbit/s":
@@ -82,6 +83,8 @@ export function renderUnitSymbol(unit: Unit | undefined): string {
return "W";
case "V":
return "V";
case "mA":
return "mA";
case "A":
return "A";
case "kWh":
@@ -94,6 +97,8 @@ export function renderUnitSymbol(unit: Unit | undefined): string {
return "µs";
case "s":
return "s";
case "min":
return "min";
case "MiB":
return "MiB";
case "Mbit/s":
@@ -105,11 +110,11 @@ export function renderUnitSymbol(unit: Unit | undefined): string {
}
}
// this function will add prefix/suffix to the valueString but without unit symbol.
// example
// value: 10.0
// -> valueString: "10" (custom input)
// this function will add prefix/suffix to the valueString but without unit symbol
// -> "10°"
// const f = 3.1415;
// renderUnitSyntax(f.toFixed(2), "deg")
// result: "3.14°"
export function renderUnitSyntax(
valueString: string | undefined,
unit: Unit | undefined,
@@ -155,6 +160,8 @@ export function renderUnitSymbolLong(unit: Unit): string {
return "watts";
case "V":
return "volts";
case "mA":
return "milliamperes";
case "A":
return "amperes";
case "kWh":
@@ -167,6 +174,8 @@ export function renderUnitSymbolLong(unit: Unit): string {
return "microseconds";
case "s":
return "seconds";
case "min":
return "minutes";
case "MiB":
return "mebibytes";
case "Mbit/s":
@@ -192,12 +201,14 @@ export const units = [
"Hz",
"W",
"V",
"mA",
"A",
"kWh",
"l/min",
"%",
"µs",
"s",
"min",
"MiB",
"Mbit/s",
"/s",
+68 -1
View File
@@ -1,4 +1,7 @@
export function deepEquals<T>(a: T, b: T): boolean {
export function deepEquals(
a: object | undefined,
b: object | undefined,
): boolean {
if (a === b) {
return true;
}
@@ -46,3 +49,67 @@ export function deepEquals<T>(a: T, b: T): boolean {
return true;
}
/**
* Checks if all keys in `subset` match corresponding values in `superset`.
* Extra keys in `superset` are ignored. Empty objects in `subset` are
* treated as matching anything.
*/
export function deepSubsetEquals(
superset: object | undefined,
subset: object | undefined,
): boolean {
if (superset === subset) {
return true;
}
if (
!subset ||
typeof subset !== "object" ||
!superset ||
typeof superset !== "object"
) {
return superset === subset;
}
if (Array.isArray(superset) && Array.isArray(subset)) {
if (superset.length !== subset.length) {
return false;
}
for (let i = 0; i < subset.length; i++) {
if (!deepSubsetEquals(superset[i], subset[i])) {
return false;
}
}
return true;
}
if (Array.isArray(superset) || Array.isArray(subset)) {
return false;
}
const subRec = subset as Record<string, any>;
const supRec = superset as Record<string, any>;
const subKeys = Object.keys(subRec);
// Empty object in subset matches anything
if (subKeys.length === 0) {
return true;
}
for (const key of subKeys) {
if (!(key in supRec)) {
// Key missing in superset — only ok if subset value is undefined
if (subRec[key] !== undefined) {
return false;
}
continue;
}
if (!deepSubsetEquals(supRec[key], subRec[key])) {
return false;
}
}
return true;
}
+4 -8
View File
@@ -1,20 +1,16 @@
import { machineIdentification } from "@/machines/types";
import { z } from "zod";
export type PresetSchema = z.ZodTypeAny;
export type PresetSchema<T> = z.ZodType<T>;
export const presetSchema = <S extends PresetSchema>(dataSchema: S) =>
export const presetSchema = <T>(dataSchema: PresetSchema<T>) =>
z.object({
id: z.number().int().nonnegative().optional(),
name: z.string().nonempty(),
lastModified: z.coerce.date(),
machineIdentification,
schemaVersion: z.number().int().positive(),
data: dataSchema,
data: dataSchema.optional(),
});
export type Preset<S extends PresetSchema> = z.infer<
ReturnType<typeof presetSchema<S>>
>;
export type PresetData<S extends PresetSchema> = z.infer<S>;
export type Preset<T> = z.infer<ReturnType<typeof presetSchema<T>>>;
+7 -7
View File
@@ -1,6 +1,6 @@
import { create } from "zustand";
import { persist, PersistStorage, StorageValue } from "zustand/middleware";
import { Preset, PresetSchema, presetSchema } from "./preset";
import { Preset, presetSchema } from "./preset";
import { MachineIdentification } from "@/machines/types";
import { z } from "zod";
import { toastError } from "@/components/Toast";
@@ -33,9 +33,9 @@ type PresetStoreData = {
};
export type PresetStore = PresetStoreData & {
insert: <T extends PresetSchema>(preset: Preset<T>) => PersistedPreset;
update: <T extends PresetSchema>(preset: Preset<T>) => void;
remove: <T extends PresetSchema>(preset: Preset<T>) => void;
insert: <T>(preset: Preset<T>) => PersistedPreset;
update: <T>(preset: Preset<T>) => void;
remove: <T>(preset: Preset<T>) => void;
getById: (id: number) => PersistedPreset | undefined;
setLatestPresetId: (
@@ -100,7 +100,7 @@ export const usePresetStore = create<PresetStore>()(
presets: [],
latestPresetIds: new Map(),
insert: <T extends PresetSchema>(preset: Preset<T>) => {
insert: <T>(preset: Preset<T>) => {
const state = get();
const { presets } = state;
const ids = presets.map((p: PersistedPreset) => p.id);
@@ -115,7 +115,7 @@ export const usePresetStore = create<PresetStore>()(
return persistedPreset;
},
update: <T extends PresetSchema>(preset: Preset<T>) => {
update: <T>(preset: Preset<T>) => {
const state = get();
const presets: PersistedPreset[] = state.presets.map(
(p: PersistedPreset) =>
@@ -124,7 +124,7 @@ export const usePresetStore = create<PresetStore>()(
set({ ...state, presets });
},
remove: <T extends PresetSchema>(preset: Preset<T>) => {
remove: <T>(preset: Preset<T>) => {
const state = get();
const presets = state.presets.filter(
(p: Preset<any>) => p.id !== preset.id,
+27 -13
View File
@@ -3,13 +3,13 @@ import {
machineIdentificationEquals,
} from "@/machines/types";
import { usePresetStore } from "./presetStore";
import { Preset, PresetData, presetSchema, PresetSchema } from "./preset";
import { deepEquals } from "@/lib/objects";
import { Preset, presetSchema, PresetSchema } from "./preset";
import { deepSubsetEquals } from "@/lib/objects";
import { useEffect } from "react";
import { toastError } from "@/components/Toast";
import { z } from "zod";
export type Presets<T extends PresetSchema> = {
export type Presets<T> = {
get: () => Preset<T>[];
createFromCurrentState: (name: string) => Preset<T>;
updateFromCurrentState: (preset: Preset<T>) => Preset<T>;
@@ -20,15 +20,15 @@ export type Presets<T extends PresetSchema> = {
import: (json: any) => void;
};
export type UsePresetsParams<T extends PresetSchema> = {
export type UsePresetsParams<T> = {
machine_identification: MachineIdentification;
schemas: Map<number, T>;
schemas: Map<number, PresetSchema<T>>;
schemaVersion: number;
currentState?: PresetData<T>;
defaultState?: PresetData<T>;
currentState?: T;
defaultState?: T;
};
export function usePresets<T extends PresetSchema>({
export function usePresets<T>({
machine_identification,
schemas,
schemaVersion,
@@ -49,7 +49,10 @@ export function usePresets<T extends PresetSchema>({
}
try {
const parsed = presetSchema(schema).parse(preset);
const parsed: Preset<T> = {
...preset,
data: schema.parse(preset.data),
};
if (parsed.schemaVersion < schemaVersion) {
parsed.schemaVersion = schemaVersion;
@@ -93,7 +96,11 @@ export function usePresets<T extends PresetSchema>({
return { ...preset, data: currentState };
};
const updateFromCurrentState = (preset: Preset<T>) => {
const updateFromCurrentState = (preset: Preset<T>): Preset<T> => {
if (!currentState) {
return preset;
}
const data = currentState;
const newPreset = {
@@ -125,7 +132,7 @@ export function usePresets<T extends PresetSchema>({
if (latestPresetId === undefined) {
const preset = store.insert({
id: undefined,
name: "Latest Machine Stettings",
name: "Latest Machine Settings",
machineIdentification: machine_identification,
lastModified: new Date(),
schemaVersion,
@@ -153,12 +160,19 @@ export function usePresets<T extends PresetSchema>({
};
const isActive = (preset: Preset<T>) => {
return deepEquals(currentState, preset.data);
if (!currentState) {
return false;
}
return deepSubsetEquals(
currentState as object,
(preset.data ?? {}) as object,
);
};
const importPreset = (json: any) => {
try {
const anyPreset = presetSchema(z.any()).parse(json);
const anyPreset = presetSchema(z.object()).parse(json);
if (
!machineIdentificationEquals(
+13 -34
View File
@@ -1,38 +1,17 @@
import { z } from "zod";
/**
* Creates a Zod schema for objects where exactly one property must be defined.
* This is useful for handling Rust enum JSON representations where only one variant exists at a time.
*
* @param schemas A record mapping property names to their Zod schemas
* @returns A Zod schema that validates that exactly one property is defined
*/
export function rustEnumSchema<T extends Record<string, z.ZodType>>(
schemas: T,
): z.ZodType<{ [K in keyof T]?: z.infer<T[K]> }> {
// Create an object schema where all properties are optional
const schemaEntries = Object.entries(schemas).map(([key, schema]) => [
key,
schema.optional(),
]);
const objectSchema = z.object(
Object.fromEntries(schemaEntries) as {
[K in keyof T]: z.ZodOptional<T[K]>;
},
export const rustEnum: z.core.CheckFn<object> = (ctx) => {
const definedKeys = Object.keys(ctx.value).filter(
(key) => ctx.value[key] !== undefined,
);
// Add refinement to ensure exactly one property is defined
return objectSchema.refine(
(data) => {
const definedKeys = Object.keys(data).filter(
(key) => data[key] !== undefined,
);
return definedKeys.length === 1;
},
{
message: "Exactly one property must be defined",
path: ["_exclusive"], // Add a path to help with error identification
},
);
}
if (definedKeys.length === 1) {
return;
}
ctx.issues.push({
code: "custom",
message: "Exactly one property must be defined",
input: definedKeys,
});
};
Loaded 100 of 313 files, more files were not shown because too many files have changed in this diff. Show more