56 Commits
Author SHA1 Message Date
lorenz.stechauner 16f697c017 Fix/Remove error message boxes
Test / Run tests (push) Successful in 16s
2026-11-06 14:26:48 +01:00
lorenz.stechauner f784f7871a Fix hovering with IsMouseOver
Test / Run tests (push) Successful in 16s
2026-11-06 14:17:34 +01:00
lorenz.stechauner eaafc47804 Fix crash in ManualControlWindow 2026-11-06 14:17:00 +01:00
lorenz.stechauner e626ae1910 Add stirrers to graph
Test / Run tests (push) Successful in 14s
2026-11-06 14:02:56 +01:00
lorenz.stechauner 1e31dafce5 Xbe24Keyboard: Update colors
Test / Run tests (push) Successful in 15s
2026-11-06 10:19:24 +01:00
lorenz.stechauner 50f6174af1 Implement remaining XBE-24 and stirrer keys
Test / Run tests (push) Successful in 35s
2026-11-06 09:49:23 +01:00
lorenz.stechauner ed4bbd09bd ManualControlWindow: Small fixes 2026-11-06 09:38:28 +01:00
lorenz.stechauner d0c328d97e Add sensor delay for opening and closing valves
Test / Run tests (push) Successful in 43s
2026-10-16 10:26:17 +02:00
lorenz.stechauner d8c2292b24 Move path logic into Path class
Test / Run tests (push) Successful in 43s
2026-10-15 23:45:32 +02:00
lorenz.stechauner c005378368 Allow configuration of XBE-24 keyboard layout
Test / Run tests (push) Successful in 37s
2026-10-14 16:38:23 +02:00
lorenz.stechauner 4b9bcc6ad3 Add ReconnectThread to reconnect to PLC and Keyboard on the fly
Test / Run tests (push) Successful in 26s
2026-10-13 17:26:35 +02:00
lorenz.stechauner c3f4acaf07 Indicate Valve status with top/bottom lines
Test / Run tests (push) Successful in 16s
2026-10-13 15:31:40 +02:00
lorenz.stechauner 49bdd5d4c0 Refactor PamhagenPlc
Test / Run tests (push) Successful in 36s
2026-10-13 12:45:33 +02:00
lorenz.stechauner 6f076919c7 Add XBE-24 key icons 2026-10-10 22:19:58 +02:00
lorenz.stechauner 947d581c11 Add PLC simulator 2026-10-10 22:19:40 +02:00
lorenz.stechauner 334f201a32 Add support for XBE-24 keyboard
Test / Run tests (push) Successful in 36s
2026-10-10 22:16:50 +02:00
lorenz.stechauner 24beafd9e5 Add status bar 2026-10-10 22:16:14 +02:00
lorenz.stechauner 3d24c65758 Pipeline: Change Valve symbol and add abstract Valve
Test / Run tests (push) Successful in 15s
2026-09-12 19:54:49 +02:00
lorenz.stechauner f4ef8d2f16 Pipeline/Pump: Change triangle to ISO symbol 2026-09-12 19:50:51 +02:00
lorenz.stechauner 3c97d974b5 Bump version to 0.0.4
Test / Run tests (push) Successful in 14s
Deploy / Build and Deploy (push) Successful in 2m40s
2026-08-19 10:11:04 +02:00
lorenz.stechauner 2bad9b8a0b Update dependencies 2026-08-19 10:10:07 +02:00
lorenz.stechauner 0b6b05551b Show only one UpdateDialog at a time
Test / Run tests (push) Successful in 14s
2026-08-19 10:09:06 +02:00
lorenz.stechauner d303f0767a Switch start/stop buttons for entry trough
Test / Run tests (push) Successful in 35s
2026-08-17 01:08:18 +02:00
lorenz.stechauner a18f253fe4 Switch start/stop buttons for MP1/2
Test / Run tests (push) Successful in 32s
2026-08-16 22:45:49 +02:00
lorenz.stechauner 7542b70595 Add T1Error names and descriptions 2026-08-14 13:51:18 +02:00
lorenz.stechauner f2a3ad3423 Reconstruct selected paths
Test / Run tests (push) Successful in 13s
2026-08-14 10:53:42 +02:00
lorenz.stechauner f251994c25 Add scaling to PlantSchemeWindow
Test / Run tests (push) Successful in 13s
2026-08-14 09:59:16 +02:00
lorenz.stechauner 2dee3a9ae9 Rework updating process a bit
Test / Run tests (push) Successful in 13s
2026-08-12 15:01:59 +02:00
lorenz.stechauner 4e82f164c9 Update README and AboutWindow 2026-08-12 14:45:58 +02:00
thomas.hilscher 77f9ae167c Update Über page 2026-08-12 11:38:09 +02:00
thomas.hilscher eb0ec60ed2 Add auto updater 2026-08-12 11:37:07 +02:00
lorenz.stechauner 9d4ae11de8 Ignore empty sensors in tanks
Test / Run tests (push) Successful in 33s
2026-08-12 11:24:14 +02:00
lorenz.stechauner d507a7486e Bump version to 0.0.3
Test / Run tests (push) Successful in 14s
Deploy / Build and Deploy (push) Successful in 2m34s
2026-08-11 14:50:41 +02:00
lorenz.stechauner ece46846b4 Add buttons for controlling pumps
Test / Run tests (push) Successful in 34s
2026-08-11 12:59:08 +02:00
lorenz.stechauner 03c91fb55a Enhance path finding using path cost 2026-08-11 02:01:08 +02:00
lorenz.stechauner f0accc8df8 Add fast select buttons
Test / Run tests (push) Successful in 14s
2026-08-11 00:26:27 +02:00
lorenz.stechauner 3983393e10 Bump version to 0.0.2
Test / Run tests (push) Successful in 32s
Deploy / Build and Deploy (push) Successful in 2m35s
2026-08-10 19:37:59 +02:00
lorenz.stechauner c82adcc054 Handle windows for manual control correctly
Test / Run tests (push) Successful in 13s
2026-08-07 16:15:00 +02:00
lorenz.stechauner 28aaa6b1f3 Use ManualControlMode to disable automatic behaviour
Test / Run tests (push) Successful in 13s
2026-08-07 16:03:56 +02:00
lorenz.stechauner 0ceab368ce Add Handbetrieb support in scheme window
Test / Run tests (push) Successful in 33s
2026-08-07 15:55:22 +02:00
lorenz.stechauner 7c3233361f Add automatic stirrers
Test / Run tests (push) Successful in 14s
2026-08-07 13:08:45 +02:00
lorenz.stechauner e45c398c68 Update manual control invocation
Test / Run tests (push) Successful in 13s
2026-08-07 12:58:55 +02:00
lorenz.stechauner d3dea3e9a0 Add ability to override clearances 2026-08-07 12:48:51 +02:00
lorenz.stechauner dbcf807154 Add pipe labels
Test / Run tests (push) Successful in 13s
2026-08-07 12:30:11 +02:00
lorenz.stechauner 96bb51807d Rearrange graph
Test / Run tests (push) Successful in 15s
2026-08-07 11:49:38 +02:00
lorenz.stechauner 4f8c401cae Adjust clearances to new pipe schema
Test / Run tests (push) Successful in 32s
2026-08-07 10:30:37 +02:00
lorenz.stechauner 06dcdd8569 Add CenterValves to presses
Test / Run tests (push) Successful in 14s
2026-08-07 01:10:29 +02:00
lorenz.stechauner e6b97ccf33 Fix belts/augers
Test / Run tests (push) Successful in 32s
2026-08-06 22:08:44 +02:00
lorenz.stechauner b85d1d8afd Update press and tank capacity 2026-08-06 21:47:16 +02:00
lorenz.stechauner b485bcfb19 deploy.yaml: Shorten Gitea release name 2026-08-06 10:04:51 +02:00
thomas.hilscher 122ac8efd7 Change assembly title to show correct name in task manager
Test / Run tests (push) Successful in 33s
2026-08-05 20:07:37 +02:00
lorenz.stechauner cbf33cbd17 Small bugfixes
Deploy / Build and Deploy (push) Successful in 2m8s
2026-08-05 16:59:29 +02:00
lorenz.stechauner fd8bd7215a Add GPLv3 license 2026-08-05 16:38:47 +02:00
lorenz.stechauner 35a25320ae Remove BOM
Test / Run tests (push) Successful in 13s
2026-08-05 16:37:18 +02:00
lorenz.stechauner 9c3cc8d4be Enhance Installer
Test / Run tests (push) Failing after 13s
2026-08-05 16:31:32 +02:00
thomas.hilscher bf8533c857 Add msi installer 2026-08-05 15:39:00 +02:00
72 changed files with 5522 additions and 1571 deletions

No files matched your search

+56
View File
@@ -0,0 +1,56 @@
name: Deploy
on:
push:
tags: ["v[0-9]+.[0-9]+.[0-9]+"]
jobs:
deploy:
name: Build and Deploy
runs-on: windows-latest
permissions:
contents: write
steps:
- name: Set APP_VERSION variable from tag
shell: powershell
run: |
$APP_VERSION = $env:GITHUB_REF -replace '^refs/tags/v', ''
Add-Content -Path $env:GITHUB_ENV -Value "APP_VERSION=$APP_VERSION"
- name: Checkout repository
uses: actions/checkout@v4
- name: Check version in project
shell: powershell
run: |
Select-String PamhagenSysCtrl/PamhagenSysCtrl.csproj -Pattern "<Version>"
$res = Select-String PamhagenSysCtrl/PamhagenSysCtrl.csproj -Pattern "<Version>${{ env.APP_VERSION }}</Version>"
if ($res -eq $null) {
exit 1
}
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.1
- name: Setup NuGet
uses: nuget/setup-nuget@v1
- name: Restore NuGet packages
shell: powershell
run: $(& nuget restore PamhagenSysCtrl.slnx; $a=$lastexitcode) | findstr x*; exit $a
- name: Build Installer
shell: powershell
run: $(& msbuild -verbosity:quiet Installer/Installer.wixproj -property:Configuration=Release -property:Platform=x64; $a=$lastexitcode) | findstr x*; exit $a
- name: Rename artifact
shell: powershell
run: Move-Item Installer/bin/Release/de-AT/PamhagenSysCtrl.msi Installer/bin/Release/de-AT/PamhagenSysCtrl-${{ env.APP_VERSION }}.msi
- name: Create release
uses: akkuman/gitea-release-action@v1
with:
name: Anl.-Strg. Pamhagen ${{ env.APP_VERSION }}
body: "**[Changelog](src/branch/main/CHANGELOG.md#v${{ env.APP_VERSION }})**"
files: |-
Installer/bin/Release/de-AT/PamhagenSysCtrl-${{ env.APP_VERSION }}.msi
- name: Upload to website
shell: powershell
run: |
$content = [System.IO.File]::ReadAllBytes("Installer/bin/Release/de-AT/PamhagenSysCtrl-${{ env.APP_VERSION }}.msi")
Invoke-WebRequest `
-Uri "https://elwig.at/files/PamhagenSysCtrl-${{ env.APP_VERSION }}.msi" `
-Method PUT `
-Body $content `
-Headers @{ Authorization = "${{ secrets.API_AUTHORIZATION }}" } `
-ContentType "application/octet-stream"
+25
View File
@@ -0,0 +1,25 @@
name: Test
on:
push:
branches: ["**"]
paths: ["PamhagenSysCtrl/**", ".gitea/workflows/test.yaml"]
jobs:
test:
name: Run tests
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Check for Byte order marks
shell: powershell
run: |
$pattern = [char]::ConvertFromUtf32(0xFEFF)
$files = git grep -IEl "^$pattern"
if ( $lastexitcode -ne 1 ) {
echo "Files with BOM found:"
echo $files
exit 1
} else {
echo "No files with BOM found"
exit 0
}
+1
View File
@@ -4,3 +4,4 @@ bin/
.vs .vs
.idea .idea
*.exe *.exe
*.zip
+14
View File
@@ -0,0 +1,14 @@
Pamhagen
========
Source code
C:\Program Files\Anlagensteuerung Pamhagen\src\pamhagen-sysctrl.zip
https://git.necronda.net/winzer/pamhagen-sysctrl
Installation folder
C:\Program Files\Anlagensteuerung Pamhagen\
Data and configuration folder
C:\ProgramData\Anlagensteuerung Pamhagen\
- config.ini : main configuration file
+39
View File
@@ -0,0 +1,39 @@
[plc]
port = COM1
; sensor delay in seconds
sensor_delay_open = 0
sensor_delay_close = 2
sensor_delay_mw = 10
; from which filling level on does the stirrer start (or stop when below) automatically when pumping
auto_stirrer = Half
[update]
url = https://elwig.at/files/pamhagen-sysctrl/latest
auto = true
[xbe24]
a1 = PATH_MW2_PRESS1
a2 = PATH_MW2_PRESS2
a3 = PATH_MW2_T1
a4 = PATH_MW2_T2
a5 = PATH_MW2_T3
a6 = PATH_MW2_T4
b1 = START_REBLER
b2 = STOP_REBLER
b3 = SELECT_MW2
b4 = TOGGLE_STIRRER_MW2
b5 = START_MP2
b6 = STOP_MP2
c1 = START_TROUGH_AUGER
c2 = STOP_TROUGH_AUGER
c3 = SELECT_MW1
c4 = TOGGLE_STIRRER_MW1
c5 = START_MP1
c6 = STOP_MP1
d1 = PATH_MW1_PRESS1
d2 = PATH_MW1_PRESS2
d3 = PATH_MW1_T1
d4 = PATH_MW1_T2
d5 = PATH_MW1_T3
d6 = PATH_MW1_T4
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Fragment>
<!-- C:\Program Files (x86)\Anlagensteuerung Pamhagen or C:\Program Files\Anlagensteuerung Pamhagen -->
<StandardDirectory Id="ProgramFiles64Folder">
<Directory Id="InstallFolder" Name="!(bind.Property.ProductName)">
<Directory Id="SourceFolder" Name="src" />
</Directory>
</StandardDirectory>
<!-- C:\ProgramData\Anlagensteuerung Pamhagen -->
<StandardDirectory Id="CommonAppDataFolder">
<Directory Id="ConfigFolder" Name="!(bind.Property.ProductName)" />
</StandardDirectory>
<!-- C:\ProgramData\Microsoft\Windows\Start Menu\Programs -->
<StandardDirectory Id="ProgramMenuFolder">
<Directory Id="StartMenuProgramsFolder" Name="!(bind.Property.ProductName)" />
</StandardDirectory>
<!-- C:\Users\{USERNAME}\Desktop -->
<StandardDirectory Id="DesktopFolder" />
</Fragment>
</Wix>
+43
View File
@@ -0,0 +1,43 @@
<Project Sdk="WixToolset.Sdk/7.0.0">
<PropertyGroup>
<InstallerPlatform>x64</InstallerPlatform>
<OutputName>PamhagenSysCtrl</OutputName>
<OutputPath>bin\$(Configuration)\</OutputPath>
<Cultures>de-AT</Cultures>
<AcceptEula>wix7</AcceptEula>
<SuppressValidation>false</SuppressValidation>
<PublishDirectory>$(MSBuildProjectDirectory)\..\PamhagenSysCtrl\bin\Publish</PublishDirectory>
</PropertyGroup>
<UsingTask TaskName="GetFileVersion" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<AssemblyPath ParameterType="System.String" Required="true" />
<Version ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System.Diagnostics" />
<Code Type="Fragment" Language="cs"><![CDATA[
Version = FileVersionInfo.GetVersionInfo(AssemblyPath).ProductVersion.Split('+')[0];
]]></Code>
</Task>
</UsingTask>
<Target Name="PublishApplication" BeforeTargets="BeforeBuild">
<RemoveDir Directories="$(PublishDirectory)" Condition="Exists('$(PublishDirectory)')" />
<Exec Command="dotnet publish &quot;$(MSBuildProjectDirectory)\..\PamhagenSysCtrl\PamhagenSysCtrl.csproj&quot; --configuration &quot;$(Configuration)&quot; --runtime win-x64 --self-contained true --output &quot;$(PublishDirectory)&quot; --nologo" />
<GetFileVersion AssemblyPath="$(PublishDirectory)\PamhagenSysCtrl.exe">
<Output TaskParameter="Version" PropertyName="ProductVersion" />
</GetFileVersion>
<PropertyGroup>
<DefineConstants>ProductVersion=$(ProductVersion);BuildPath=$(PublishDirectory)</DefineConstants>
</PropertyGroup>
</Target>
<Target Name="CreateSourceArchive" BeforeTargets="BeforeBuild">
<Exec Command="git -C &quot;$(MSBuildProjectDirectory)\..&quot; archive --format=zip --output &quot;$(MSBuildProjectDirectory)\Files\pamhagen-sysctrl.zip&quot; HEAD" />
</Target>
<ItemGroup>
<ProjectReference Include="..\PamhagenSysCtrl\PamhagenSysCtrl.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>
<None Include="Files\config.ini" />
<None Include="Files\README.txt" />
</ItemGroup>
</Project>
+22
View File
@@ -0,0 +1,22 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Fragment>
<ComponentGroup Id="MainComponents">
<Component Directory="InstallFolder">
<File Source="$(var.BuildPath)\PamhagenSysCtrl.exe" Id="PamhagenSysCtrl.exe"/>
</Component>
<Files Directory="InstallFolder" Include="$(var.BuildPath)\**">
<Exclude Files="$(var.BuildPath)\PamhagenSysCtrl.exe"/>
<Exclude Files="$(var.BuildPath)\**.pdb"/>
</Files>
<Component Directory="SourceFolder">
<File Source="$(ProjectDir)\Files\pamhagen-sysctrl.zip" Id="pamhagen_sysctrl.zip"/>
</Component>
<Component Directory="ConfigFolder" Permanent="true" NeverOverwrite="true">
<File Source="$(ProjectDir)\Files\config.ini" Id="config.ini"/>
</Component>
<Component Directory="ConfigFolder">
<File Source="$(ProjectDir)\Files\README.txt" Id="README.txt"/>
</Component>
</ComponentGroup>
</Fragment>
</Wix>
+5
View File
@@ -0,0 +1,5 @@
<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl"
Culture="de-AT">
<String Id="DowngradeError"
Value="Eine neuere Version von [ProductName] ist bereits installiert." />
</WixLocalization>
+17
View File
@@ -0,0 +1,17 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Package Name="Anlagensteuerung Pamhagen"
Manufacturer="Anlagensteuerung Pamhagen"
Version="$(var.ProductVersion)"
Language="3079"
UpgradeCode="f214bfc6-1cd0-4971-807e-a4830e3b82a3"
Scope="perMachine"
Compressed="true">
<MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" AllowSameVersionUpgrades="no"/>
<MediaTemplate EmbedCab="true" CompressionLevel="high"/>
<Feature Id="Main" Title="Anlagensteuerung Pamhagen" Level="1">
<ComponentGroupRef Id="MainComponents"/>
<ComponentGroupRef Id="ShortcutComponents"/>
</Feature>
</Package>
</Wix>
+39
View File
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Fragment>
<ComponentGroup Id="ShortcutComponents">
<Component Id="StartMenuFolderShortcut"
Directory="StartMenuProgramsFolder"
Guid="6101b33f-9bc8-4d44-aad0-0f3eddc840c6">
<Shortcut Id="ApplicationStartMenuShortcut"
Name="!(bind.Property.ProductName)"
Description="!(bind.Property.ProductName)"
Target="[InstallFolder]PamhagenSysCtrl.exe"
WorkingDirectory="InstallFolder" />
<RemoveFolder Id="StartMenuProgramsFolder" On="uninstall" />
<RegistryValue Root="HKCU"
Key="Software\!(bind.Property.Manufacturer)\!(bind.Property.ProductName)"
Name="start_menu_installed"
Type="integer"
Value="1"
KeyPath="yes" />
</Component>
<Component Id="DesktopFolderShortcut"
Directory="DesktopFolder"
Guid="58b61147-4e5c-4581-8593-fc7bcb591048">
<Shortcut Id="DesktopShortcut"
Name="!(bind.Property.ProductName)"
Description="!(bind.Property.ProductName)"
Target="[InstallFolder]PamhagenSysCtrl.exe"
WorkingDirectory="InstallFolder" />
<RegistryValue Root="HKCU"
Key="Software\!(bind.Property.Manufacturer)\!(bind.Property.ProductName)"
Name="desktop_installed"
Type="integer"
Value="1"
KeyPath="yes" />
</Component>
</ComponentGroup>
</Fragment>
</Wix>
+674
View File
@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
+7
View File
@@ -1,3 +1,10 @@
<Solution> <Solution>
<Configurations>
<Platform Name="Any CPU" />
</Configurations>
<Project Path="Installer/Installer.wixproj">
<Platform Solution="*|Any CPU" Project="x64" />
<Build Solution="*|Any CPU" Project="true" />
</Project>
<Project Path="PamhagenSysCtrl/PamhagenSysCtrl.csproj" /> <Project Path="PamhagenSysCtrl/PamhagenSysCtrl.csproj" />
</Solution> </Solution>
+1 -1
View File
@@ -1,4 +1,4 @@
<Application x:Class="PamhagenSysCtrl.App" <Application x:Class="PamhagenSysCtrl.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PamhagenSysCtrl" xmlns:local="clr-namespace:PamhagenSysCtrl"
+55 -11
View File
@@ -1,4 +1,5 @@
using PamhagenSysCtrl.Helpers; using PamhagenSysCtrl.Dialogs;
using PamhagenSysCtrl.Helpers;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Windows; using System.Windows;
@@ -10,6 +11,8 @@ namespace PamhagenSysCtrl {
public static PamhagenPlant? Plant; public static PamhagenPlant? Plant;
public static Dispatcher? MainDispatcher; public static Dispatcher? MainDispatcher;
private readonly DispatcherTimer AutoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) };
public static readonly string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Anlagensteuerung Pamhagen"); public static readonly string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Anlagensteuerung Pamhagen");
public static readonly string ConfigPath = Path.Combine(DataPath, "config.ini"); public static readonly string ConfigPath = Path.Combine(DataPath, "config.ini");
public static readonly string InstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Anlagensteuerung Pamhagen"); public static readonly string InstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Anlagensteuerung Pamhagen");
@@ -31,20 +34,61 @@ namespace PamhagenSysCtrl {
} }
protected override async void OnStartup(StartupEventArgs evt) { protected override async void OnStartup(StartupEventArgs evt) {
if (Config == null)
return;
Version = new Version(typeof(App).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Split('+')[0] ?? "0.0.0"); Version = new Version(typeof(App).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Split('+')[0] ?? "0.0.0");
try { Plant = new(Config.PlcPort);
Plant = new(Config.PlcPort); base.OnStartup(evt);
await Plant.StartThread();
} catch (Exception exc) { if (Config.UpdateUrl != null && Config.UpdateAuto) {
Plant?.Dispose(); AutoUpdateTimer.Tick += async (_, _) => {
Plant = null; try {
var str = $"Beim Aufbauen einer Verbindung zur SPS ist ein Fehler aufgetreten:\n\n{exc.GetType().Name}: {exc.Message}"; await CheckForUpdates();
if (exc.InnerException != null) str += $"\n\n{exc.InnerException.GetType().Name}: {exc.InnerException.Message}"; } catch { }
MessageBox.Show(str, "SPS-Fehler", MessageBoxButton.OK, MessageBoxImage.Error); };
AutoUpdateTimer.Start();
await Dispatcher.BeginInvoke(async () => {
await Task.Delay(1500);
try {
await CheckForUpdates();
} catch { }
});
}
}
public static async Task CheckForUpdates(bool showResult = false) {
if (Config.UpdateUrl == null) return;
foreach (Window w in Current.Windows) {
if (w is UpdateDialog) return;
} }
base.OnStartup(evt); try {
var latest = await UpdateService.GetLatestInstallerUrl(Config.UpdateUrl);
if (latest.HasValue && new Version(latest.Value.Version) > Version) {
var dialog = new UpdateDialog(latest.Value.Version, latest.Value.Url, latest.Value.Size) {
Owner = Current.MainWindow,
};
if (dialog.ShowDialog() == true) {
Current.Shutdown();
}
} else if (showResult) {
MessageBox.Show($"Die Anlagensteuerung ist auf dem aktuellen Stand.\n(Version {Version})", "Nach Updates suchen", MessageBoxButton.OK, MessageBoxImage.Information);
}
} catch (OperationCanceledException) {
if (showResult) {
MessageBox.Show("Zeitüberschreitung beim Abrufen der Update-Informationen.", "Nach Updates suchen", MessageBoxButton.OK, MessageBoxImage.Error);
} else {
throw;
}
} catch (Exception exc) {
if (showResult) {
MessageBox.Show($"Die Update-Informationen konnten nicht abgerufen werden:\n\n{exc.Message}", "Nach Updates suchen", MessageBoxButton.OK, MessageBoxImage.Error);
} else {
throw;
}
}
} }
protected async void Application_Exit(object sender, ExitEventArgs evt) { protected async void Application_Exit(object sender, ExitEventArgs evt) {
+37
View File
@@ -0,0 +1,37 @@
<Window x:Class="PamhagenSysCtrl.Dialogs.UpdateDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True"
WindowStartupLocation="CenterOwner"
Title="Update verfügbar - Anlagensteuerung Pamhagen"
Height="220" Width="480"
Closed="OnClosed">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock x:Name="Description" FontSize="14" TextAlignment="Center"
HorizontalAlignment="Center" VerticalAlignment="Center">
Version <Run x:Name="VersionText" FontWeight="Bold">0.0.0</Run> ist verfügbar.<LineBreak/>
Soll das Update heruntergeladen und installiert werden?<LineBreak/>
(ca. <Run x:Name="SizeText">0</Run> MB)<LineBreak/><LineBreak/>
<Run FontWeight="Bold">Hinweis:</Run> Die Anlagensteuerung wird für die Installation geschlossen.
</TextBlock>
<StackPanel x:Name="DownloadPanel" Grid.Row="0" VerticalAlignment="Center" Visibility="Collapsed">
<TextBlock x:Name="StatusText" Text="Update wird heruntergeladen..."
FontSize="14" TextAlignment="Center" Margin="0,0,0,10"/>
<ProgressBar x:Name="DownloadProgress" Height="24" Minimum="0" Maximum="100"/>
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,18,0,0">
<Button x:Name="InstallButton" Content="Installieren" IsDefault="True"
Width="110" Height="30" Margin="0,0,12,0" Click="InstallButton_Click"/>
<Button x:Name="CancelButton" Content="Abbrechen" IsCancel="True"
Width="110" Height="30"/>
</StackPanel>
</Grid>
</Window>
@@ -0,0 +1,45 @@
using PamhagenSysCtrl.Helpers;
using System.IO;
using System.Windows;
namespace PamhagenSysCtrl.Dialogs {
public partial class UpdateDialog : Window {
private readonly string Url;
private readonly CancellationTokenSource Cancellation = new();
public UpdateDialog(string version, string url, long size) {
Url = url;
InitializeComponent();
VersionText.Text = version;
SizeText.Text = Math.Ceiling(size / 1024d / 1024d).ToString("N0");
}
private void OnClosed(object? sender, EventArgs evt) {
Cancellation.Cancel();
Cancellation.Dispose();
}
private async void InstallButton_Click(object sender, RoutedEventArgs evt) {
Description.Visibility = Visibility.Collapsed;
DownloadPanel.Visibility = Visibility.Visible;
InstallButton.IsEnabled = false;
try {
var progress = new Progress<double>(value => DownloadProgress.Value = value * 100);
var filename = Path.Combine(App.TempPath, $"PamhagenSysCtrl-{VersionText.Text}.msi");
await UpdateService.DownloadInstaller(Url, filename, progress, Cancellation.Token);
StatusText.Text = "Installer wird gestartet...";
UpdateService.StartInstaller(filename);
DialogResult = true;
} catch (OperationCanceledException) {
// Closing the dialog cancels an active download.
} catch (Exception exc) {
MessageBox.Show($"Das Update konnte nicht installiert werden:\n\n{exc.Message}", "Update installieren", MessageBoxButton.OK, MessageBoxImage.Error);
Description.Visibility = Visibility.Visible;
DownloadPanel.Visibility = Visibility.Collapsed;
InstallButton.IsEnabled = true;
}
}
}
}
+33 -2
View File
@@ -1,4 +1,4 @@
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using System.Windows; using System.Windows;
namespace PamhagenSysCtrl.Helpers { namespace PamhagenSysCtrl.Helpers {
@@ -9,9 +9,18 @@ namespace PamhagenSysCtrl.Helpers {
private readonly string FileName; private readonly string FileName;
public string PlcPort = "COM1"; public string PlcPort = "COM1";
public int PlcSensorDelayOpen = 0;
public int PlcSensorDelayClose = 0;
public int PlcSensorDelayMW = 0;
public FillState PlcAutoStirrer = FillState.Half;
public string? UpdateUrl;
public bool UpdateAuto;
public PamhagenPlant.ShortcutKey?[] Xbe24KeyMap;
public Config(string filename) { public Config(string filename) {
FileName = filename; FileName = filename;
Xbe24KeyMap = new PamhagenPlant.ShortcutKey?[24];
Read(); Read();
} }
@@ -19,10 +28,32 @@ namespace PamhagenSysCtrl.Helpers {
try { try {
var config = new ConfigurationBuilder().AddIniFile(FileName).Build(); var config = new ConfigurationBuilder().AddIniFile(FileName).Build();
PlcPort = config["plc:port"] ?? "COM1"; PlcPort = config["plc:port"] ?? "COM1";
PlcSensorDelayOpen = int.Parse(config["plc:sensor_delay_open"] ?? "0");
PlcSensorDelayClose = int.Parse(config["plc:sensor_delay_close"] ?? "0");
PlcSensorDelayMW = int.Parse(config["plc:sensor_delay_mw"] ?? "0");
PlcAutoStirrer = Enum.Parse<FillState>(config["plc:auto_stirrer"] ?? "Half");
UpdateUrl = config["update:url"];
UpdateAuto = TrueValues.Contains(config["update:auto"]?.ToLower());
var xbe24 = config.GetSection("xbe24");
for (char c = 'a'; c <= 'd'; c++) {
for (int r = 1; r <= 6; r++) {
int n = (c - 'a') * 6 + r - 1;
var s = xbe24[$"{c}{r}"]?.ToUpper();
Xbe24KeyMap[n] = s == null ? null : Enum.Parse<PamhagenPlant.ShortcutKey>(s);
}
}
} catch (Exception exc) { } catch (Exception exc) {
MessageBox.Show($"Die Konfigurationsdatei konnte nicht gelesen werden:\n\n{exc.Message}", "Konfigurationsdatei lesen", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"Die Konfigurationsdatei konnte nicht gelesen werden:\n\n{exc.Message}", "Konfigurationsdatei lesen", MessageBoxButton.OK, MessageBoxImage.Error);
App.Current.Shutdown(); Application.Current.Shutdown();
} }
} }
public PamhagenPlant.ShortcutKey? MapKey(int n) {
return n < 0 || Xbe24KeyMap.Length < n ? null : Xbe24KeyMap[n];
}
public int? MapKey(PamhagenPlant.ShortcutKey key) {
return Xbe24KeyMap.IndexOf(key) is int n ? n == -1 ? null : n : null;
}
} }
} }
+19
View File
@@ -0,0 +1,19 @@
using System.Drawing;
namespace PamhagenSysCtrl.Helpers {
public static class Extensions {
public static void SetBacklight(this Xbe24Keyboard k, PamhagenPlant.ShortcutKey key, (Color Color, bool Flashing) state) {
k.SetBacklight(key, state.Color, state.Flashing);
}
public static void SetBacklight(this Xbe24Keyboard k, PamhagenPlant.ShortcutKey key, Color color, bool flashing = false) {
if (App.Config.MapKey(key) is int n)
k.SetBacklight(n, color, flashing);
}
public static bool IsKeyPressed(this Xbe24Keyboard k, PamhagenPlant.ShortcutKey key) {
return App.Config.MapKey(key) is int n && k.IsKeyPressed(n);
}
}
}
+2 -2
View File
@@ -1,3 +1,3 @@
namespace PamhagenSysCtrl.Helpers { namespace PamhagenSysCtrl.Helpers {
public enum FillState { Empty = 0, Filled = 1, Half = 2, Full = 3, Invalid = -1, Unknown = -2 } public enum FillState { Unknown = 0, Empty = 1, Filled = 2, Quarter = 3, Half = 4, ThreeQuarter = 5, AlmostFull = 6, Full = 7, Invalid = -1 }
} }
+1 -1
View File
@@ -1,3 +1,3 @@
namespace PamhagenSysCtrl.Helpers { namespace PamhagenSysCtrl.Helpers {
public enum MotorState { Halt = 0, Forward = 1, Backward = 2, Invalid = -1 } public enum MotorState { Halt = 0, Forward = 1, Backward = 2, Invalid = -1 }
} }
+22 -5
View File
@@ -1,20 +1,25 @@
using System.Windows.Media; using System.Windows.Media;
namespace PamhagenSysCtrl.Helpers { namespace PamhagenSysCtrl.Helpers {
public class PamhagenBrushes { public class PamhagenBrushes {
public const double TRANSITION = 1.0 / 4096; public const double TRANSITION = 1.0 / 4096;
public static readonly SolidColorBrush Green = new SolidColorBrush(Color.FromRgb(0x20, 0xE0, 0x20)); public static readonly SolidColorBrush Green = new(Color.FromRgb(0x20, 0xE0, 0x20));
public static readonly SolidColorBrush DimOrange = new SolidColorBrush(Color.FromRgb(0xFF, 0xC0, 0x80)); public static readonly SolidColorBrush DimGreen = new(Color.FromRgb(0xC0, 0xE0, 0xC0));
public static readonly SolidColorBrush Yellow = new SolidColorBrush(Color.FromRgb(0xFF, 0xE0, 0x10)); public static readonly SolidColorBrush DimOrange = new(Color.FromRgb(0xFF, 0xC0, 0x80));
public static readonly SolidColorBrush Red = new SolidColorBrush(Color.FromRgb(0xFF, 0x20, 0x20)); public static readonly SolidColorBrush Yellow = new(Color.FromRgb(0xFF, 0xE0, 0x10));
public static readonly SolidColorBrush Red = new(Color.FromRgb(0xFF, 0x20, 0x20));
public static readonly SolidColorBrush DimRed = new(Color.FromRgb(0xFF, 0xC0, 0xC0));
public static Brush ForFillState(FillState state, Color background) { public static Brush ForFillState(FillState state, Color background) {
return state switch { return state switch {
FillState.Empty => GetEmpty(background), FillState.Empty => GetEmpty(background),
FillState.Filled => GetFilled(background), FillState.Filled => GetFilled(background),
FillState.Quarter => GetQuarter(background),
FillState.Half => GetHalf(background), FillState.Half => GetHalf(background),
FillState.ThreeQuarter => GetThreeQuarter(background),
FillState.AlmostFull => GetAlmostFull(background),
FillState.Full => GetFull(background), FillState.Full => GetFull(background),
_ => GetEmpty(background), _ => GetEmpty(background),
}; };
@@ -32,10 +37,22 @@ namespace PamhagenSysCtrl.Helpers {
return new LinearGradientBrush(background, Green.Color, new(0.5, 0.875 - TRANSITION), new(0.5, 0.875 + TRANSITION)); return new LinearGradientBrush(background, Green.Color, new(0.5, 0.875 - TRANSITION), new(0.5, 0.875 + TRANSITION));
} }
public static Brush GetQuarter(Color background) {
return new LinearGradientBrush(background, Green.Color, new(0.5, 0.75 - TRANSITION), new(0.5, 0.75 + TRANSITION));
}
public static Brush GetHalf(Color background) { public static Brush GetHalf(Color background) {
return new LinearGradientBrush(background, Green.Color, new(0.5, 0.5 - TRANSITION), new(0.5, 0.5 + TRANSITION)); return new LinearGradientBrush(background, Green.Color, new(0.5, 0.5 - TRANSITION), new(0.5, 0.5 + TRANSITION));
} }
public static Brush GetThreeQuarter(Color background) {
return new LinearGradientBrush(background, Green.Color, new(0.5, 0.25 - TRANSITION), new(0.5, 0.25 + TRANSITION));
}
public static Brush GetAlmostFull(Color background) {
return new LinearGradientBrush(background, Green.Color, new(0.5, 0.125 - TRANSITION), new(0.5, 0.125 + TRANSITION));
}
public static Brush GetFull(Color background) { public static Brush GetFull(Color background) {
return new LinearGradientBrush(background, Red.Color, new(0.5, 0.0625 - TRANSITION), new(0.5, 0.0625 + TRANSITION)); return new LinearGradientBrush(background, Red.Color, new(0.5, 0.0625 - TRANSITION), new(0.5, 0.0625 + TRANSITION));
} }
+393 -266
View File
@@ -1,4 +1,4 @@
using PamhagenSysCtrl.Helpers.Pipeline; using PamhagenSysCtrl.Helpers.Pipeline;
using System.Windows.Media; using System.Windows.Media;
namespace PamhagenSysCtrl.Helpers { namespace PamhagenSysCtrl.Helpers {
@@ -17,78 +17,103 @@ namespace PamhagenSysCtrl.Helpers {
public readonly Pump P5; public readonly Pump P5;
public readonly Pump P6; public readonly Pump P6;
public readonly EncasedAuger Auger1; public readonly EncasedAuger Auger1;
public readonly EncasedAuger Auger2; public readonly EncasedAuger Auger3;
public readonly Press Press1;
public readonly Press Press2;
public readonly Tank Tank1;
public readonly Tank Tank2;
public readonly Tank Tank3;
public readonly Tank Tank4;
public readonly Tank Tank5;
public readonly Tank Tank6;
public readonly Tank Tank7;
public readonly Tank Tank8;
public readonly Tank Tank9;
public readonly Valve V5;
public readonly Valve V6;
public readonly Valve V7;
public readonly Valve V8;
public readonly Dictionary<PamhagenPlant.ShortcutKey, (ISource Source, Pump Pump, INode? Intermediate, ISink Sink)> PathShortcuts;
public PamhagenGraph() { public PamhagenGraph() {
double mw2X = 180; double mw2X = -118;
double mw1X = 300; double mw1X = -94;
double mpY = 380; double rebX = (mw1X + mw2X) / 2;
double ltg1Y = 510; double rebY = -46;
double ltg2Y = ltg1Y + 30; double mpY = -10;
double ltg1X = 600; double tanksY = -30;
double ltg2X = ltg1X + 30; double ltg1Y = 16;
double ltg6Y = 160; double ltg2Y = ltg1Y + 6;
double ltg7Y = ltg6Y - 30; double ltg1X = -34;
double tanksY = 280; double ltg2X = ltg1X + 6;
double tValveY = ((tanksY - 50) + ltg6Y) / 2 + 5; double ltg3Y = 30;
double wtX = ltg1X - 180; double ltg4Y = ltg3Y + 6;
double wt2Y = 225; double ltg5Y = tanksY + 36;
double wt1Y = wt2Y + 135; double ltg6Y = -54;
double t1X = 735; double ltg7Y = ltg6Y - 6;
double t2X = t1X + 80; double ltg8Y = ltg3Y - 6;
double t3X = t2X + 80; double ltg9Y = ltg4Y + 6;
double t4X = t3X + 80 + 30; double tValveY = ((tanksY - 10) + ltg6Y) / 2 + 1;
double t5X = t4X + 80; double wtX = ltg1X - 36;
double t6X = t5X + 80 + 30; double wt2Y = -41;
double t7X = t6X + 80; double wt1Y = wt2Y + 27;
double t8X = t7X + 80; double x1X = wtX + 8;
double t9X = t8X + 80; double t1X = -7;
var prY = 780; double t2X = t1X + 16;
var pr1X = 750; double t3X = t2X + 16;
var pr2X = 900; double t4X = t3X + 16 + 6;
double t5X = t4X + 16;
double t6X = t5X + 16 + 6;
double t7X = t6X + 16;
double t8X = t7X + 16;
double t9X = t8X + 16;
var prY = 58;
var pr1X = -15;
var pr2X = 23;
var trough = new EntryTrough("Auffangwanne", mw1X + 70, 100, () => App.Plant?.IsTroughAugerActive ?? false); var trough = new EntryTrough("Auffangwanne", mw1X + 14, rebY - 20, () => App.Plant?.State.TroughAuger_IsActive ?? false);
Nodes.Add(trough); Nodes.Add(trough);
Auger1 = new EncasedAuger("", (mw1X + mw2X) / 2 - 100, 200, () => App.Plant?.IsAuger1Active ?? false); Auger1 = new EncasedAuger("", rebX - 19, rebY, () => App.Plant?.State.Auger1_IsActive ?? false);
var auger3 = new EncasedAuger("Pressen-\nQuerschnecke", 40, 200, () => App.Plant?.IsPresseQuerSchneckeActive ?? false, 180, clickable: false); var auger0 = new EncasedAuger("Pressen-\nQuerschnecke", rebX - 39, rebY, () => App.Plant?.State.PressCrossAuger_IsActive ?? false, 180, clickable: false);
Auger2 = new EncasedAuger("", 120, 80, () => App.Plant?.IsAuger2Active ?? false, 135); Auger3 = new EncasedAuger("", rebX - 23, rebY - 24, () => App.Plant?.State.Auger3_IsActive ?? false, 135);
var belt = new ConveyorBelt("", 90, 160, () => App.Plant?.IsBeltActive ?? false); var belt = new ConveyorBelt("", rebX - 29, rebY - 8, () => App.Plant?.State.Auger2_IsActive ?? false);
Nodes.Add(belt); Nodes.Add(belt);
Nodes.Add(Auger1); Nodes.Add(Auger1);
Nodes.Add(auger3); Nodes.Add(auger0);
Nodes.Add(Auger2); Nodes.Add(Auger3);
var rebler = new Rebler("Rebler", (mw1X + mw2X) / 2, 200, () => App.Plant?.IsReblerActive ?? false); var rebler = new Rebler("Rebler", rebX, rebY, () => App.Plant?.State.Rebler_IsActive ?? false);
Nodes.Add(rebler); Nodes.Add(rebler);
var switcher = new Switcher("", (mw1X + mw2X) / 2, 260, () => App.Plant?.TroughSelector ?? 0, () => App.Plant == null ? 0 : App.Plant.WantMW1Selected ? 1 : 2); var switcher = new Switcher("", rebX, rebY + 12, () => App.Plant?.State.MW1_IsSelected == true ? 1 : App.Plant?.State.MW2_IsSelected == true ? 2 : 0, () => App.Plant?.State.MW1_WantSelected == true ? 1 : App.Plant?.State.MW2_WantSelected == true ? 2 : 0);
Nodes.Add(switcher); Nodes.Add(switcher);
MW1 = new Trough("MW1", mw1X, mpY - 70, () => App.Plant?.FillLevelMW1 ?? FillState.Unknown); MW1 = new Trough("MW1", mw1X, mpY - 14, true, () => App.Plant?.State.MW1_FillLevel ?? FillState.Unknown, () => App.Plant?.State.MW1_Stirrer_IsActive ?? false);
MW2 = new Trough("MW2", mw2X, mpY - 70, () => App.Plant?.FillLevelMW2 ?? FillState.Unknown); MW2 = new Trough("MW2", mw2X, mpY - 14, false, () => App.Plant?.State.MW2_FillLevel ?? FillState.Unknown, () => App.Plant?.State.MW2_Stirrer_IsActive ?? false);
MP1 = new Pump("MP1", mw1X, mpY, () => App.Plant?.IsMP1Active ?? false, () => App.Plant?.MP1HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); MP1 = new Pump("MP1", mw1X, mpY, () => App.Plant?.State.MP1_State ?? default, () => App.Plant?.State.MP1_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x)));
MP2 = new Pump("MP2", mw2X, mpY, () => App.Plant?.IsMP2Active ?? false, () => App.Plant?.MP2HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); MP2 = new Pump("MP2", mw2X, mpY, () => App.Plant?.State.MP2_State ?? default, () => App.Plant?.State.MP2_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x)));
var x1 = new PipeJoin(mw1X, mpY + 40); var x1 = new PipeJoin(mw1X, mpY + 8);
var x2 = new PipeJoin(mw2X, mpY + 40); var x2 = new PipeJoin(mw2X, mpY + 8);
CreateOutlet(MW1, MP1); CreateOutlet(MW1, MP1);
CreateOutlet(MW2, MP2); CreateOutlet(MW2, MP2);
CreatePipe(MP1, x1); CreatePipe(MP1, x1);
CreatePipe(MP2, x2); CreatePipe(MP2, x2);
var v1 = CreateValve(1, mw1X, mpY + 70); var v1 = CreateValve(1, mw1X, mpY + 16, 2);
var v2 = CreateValve(2, mw2X, mpY + 70); var v2 = CreateValve(2, mw2X, mpY + 16, 0);
var v3 = CreateValve(3, mw2X + 30, mpY + 70); var v3 = CreateValve(3, mw2X + 6, mpY + 16, 2);
var v4 = CreateValve(4, mw1X - 30, mpY + 70); var v4 = CreateValve(4, mw1X - 6, mpY + 16, 0);
CreatePipe(x1, v1); CreatePipe(x1, v1);
CreatePipe(x1, v4); CreatePipe(x1, v4);
CreatePipe(x2, v2); CreatePipe(x2, v2);
CreatePipe(x2, v3); CreatePipe(x2, v3);
var x3 = new PipeJoin(mw1X, mpY + 100); var x3 = new PipeJoin(mw1X, ltg1Y);
var x4 = new PipeJoin(mw1X, ltg1Y); var x4 = new PipeJoin(x1X, ltg1Y);
var x5 = new PipeJoin(mw2X, mpY + 130); var x5 = new PipeJoin(mw1X - 6, ltg2Y);
var x6 = new PipeJoin(mw2X, ltg2Y); var x6 = new PipeJoin(x1X - 6, ltg2Y);
CreatePipe(v1, x3, true); CreatePipe(v1, x3, true);
CreatePipe(v3, x3, true); CreatePipe(v3, x3, true);
@@ -97,50 +122,50 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(v4, x5, true); CreatePipe(v4, x5, true);
CreatePipe(x5, x6, true); CreatePipe(x5, x6, true);
var v5 = CreateValve(5, mw1X + 50, ltg1Y); V5 = CreateValve(5, x1X + 6, ltg1Y, 1);
var v6 = CreateValve(6, mw1X, ltg2Y + 40); V6 = CreateValve(6, x1X + 6, ltg2Y + 8, 3);
var v7 = CreateValve(7, (mw2X + mw1X) / 2, ltg2Y); V7 = CreateValve(7, x1X + 6, ltg2Y, 3);
var v8 = CreateValve(8, mw2X, ltg2Y + 40); V8 = CreateValve(8, x1X - 6, ltg2Y + 8, 0);
var v9 = CreateValve(9, ltg2X, 500); var v9 = CreateValve(9, ltg2X, ltg5Y + 8, 2);
CreatePipe(x4, v5); CreatePipe(x4, V5);
CreatePipe(x4, v6); CreatePipe(x4, V6, true);
CreatePipe(x6, v7); CreatePipe(x6, V7);
CreatePipe(x6, v8); CreatePipe(x6, V8);
CreatePipe(v7, v9); CreatePipe(V7, v9, label: "Ltg. 2", labelOffsetX: 6);
var xv15 = new PipeJoin(ltg1X, wt1Y + 40); var xv15 = new PipeJoin(ltg1X, wt1Y + 8);
var xv11 = new PipeJoin(ltg2X, wt1Y + 70); var xv11 = new PipeJoin(ltg2X, wt1Y + 14);
var x7 = new PipeJoin(ltg2X, 460); var x7 = new PipeJoin(ltg2X, ltg5Y);
CreatePipe(v5, xv15); CreatePipe(V5, xv15, label: "Ltg. 1", labelOffsetX: 6);
CreatePipe(v9, x7); CreatePipe(v9, x7);
CreatePipe(x7, xv11); CreatePipe(x7, xv11);
var v10 = CreateValve(10, ltg2X + 40, 460); var v10 = CreateValve(10, ltg2X + 8, ltg5Y, 3);
var v11 = CreateValve(11, ltg1X - 30, wt1Y + 70); var v11 = CreateValve(11, ltg1X - 6, wt1Y + 14, 3);
var v12 = CreateValve(12, ltg1X, wt1Y); var v12 = CreateValve(12, ltg1X, wt1Y, 0);
var v13 = CreateValve(13, ltg2X, wt1Y); var v13 = CreateValve(13, ltg2X, wt1Y, 2);
var v14 = CreateValve(14, ltg1X - 30, wt1Y - 40); var v14 = CreateValve(14, ltg1X - 6, wt1Y - 8, 3);
var v15 = CreateValve(15, ltg1X - 30, wt1Y + 40); var v15 = CreateValve(15, ltg1X - 6, wt1Y + 8, 1);
var v16 = CreateValve(16, ltg1X - 30, wt1Y - 70); var v16 = CreateValve(16, ltg1X - 6, wt1Y - 14, 1);
CreatePipe(v10, x7); CreatePipe(v10, x7);
CreatePipe(xv11, v11); CreatePipe(xv11, v11);
CreatePipe(xv15, v15); CreatePipe(xv15, v15);
CreatePipe(xv15, v12); CreatePipe(xv15, v12);
CreatePipe(xv11, v13); CreatePipe(xv11, v13);
var xv16 = new PipeJoin(ltg1X, wt1Y - 70); var xv16 = new PipeJoin(ltg1X, wt1Y - 14);
var xv14 = new PipeJoin(ltg2X, wt1Y - 40); var xv14 = new PipeJoin(ltg2X, wt1Y - 8);
CreatePipe(v12, xv16); CreatePipe(v12, xv16);
CreatePipe(v13, xv14); CreatePipe(v13, xv14);
CreatePipe(v16, xv16); CreatePipe(v16, xv16);
CreatePipe(v14, xv14); CreatePipe(v14, xv14);
var v20 = CreateValve(20, ltg1X - 30, wt2Y - 70); var v20 = CreateValve(20, ltg1X - 6, wt2Y - 14, 1);
var v21 = CreateValve(21, ltg1X - 30, wt2Y - 40); var v21 = CreateValve(21, ltg1X - 6, wt2Y - 8, 3);
var xv20 = new PipeJoin(ltg1X, wt2Y - 70); var xv20 = new PipeJoin(ltg1X, wt2Y - 14);
var xv21 = new PipeJoin(ltg2X, wt2Y - 40); var xv21 = new PipeJoin(ltg2X, wt2Y - 8);
CreatePipe(xv16, xv20); CreatePipe(xv16, xv20);
CreatePipe(xv14, xv21); CreatePipe(xv14, xv21);
CreatePipe(v20, xv20); CreatePipe(v20, xv20);
@@ -148,15 +173,15 @@ namespace PamhagenSysCtrl.Helpers {
var wt1 = new HeatExchanger("WT Most", wtX, wt1Y); var wt1 = new HeatExchanger("WT Most", wtX, wt1Y);
var wt2 = new HeatExchanger("WT Warmwasser", wtX, wt2Y); var wt2 = new HeatExchanger("WT Warmwasser", wtX, wt2Y);
var v17 = CreateValve(17, wtX + 70, wt1Y); var v17 = CreateValve(17, wtX + 14, wt1Y, 2);
var v18 = CreateValve(18, wtX + 40, wt1Y + 40); var v18 = CreateValve(18, wtX + 8, wt1Y + 8, 3);
var v19 = CreateValve(19, wtX + 40, (wt1Y + wt2Y) / 2); var v19 = CreateValve(19, wtX + 8, (wt1Y + wt2Y) / 2, 0);
var xwt1 = new PipeJoin(wtX + 120, wt1Y + 40); var xwt1 = new PipeJoin(wtX + 24, wt1Y + 8);
var xwt2 = new PipeJoin(wtX + 70, wt1Y + 40); var xwt2 = new PipeJoin(wtX + 14, wt1Y + 8);
var xwt3 = new PipeJoin(wtX + 40, wt1Y - 40); var xwt3 = new PipeJoin(wtX + 8, wt1Y - 8);
var xwt4 = new PipeJoin(wtX + 120, wt1Y - 40); var xwt4 = new PipeJoin(wtX + 24, wt1Y - 8);
var xwt5 = new PipeJoin(wtX + 40, wt2Y + 40); var xwt5 = new PipeJoin(wtX + 8, wt2Y + 8);
var xwt6 = new PipeJoin(wtX + 120, wt2Y - 40); var xwt6 = new PipeJoin(wtX + 24, wt2Y - 8);
CreatePipe(v11, xwt1); CreatePipe(v11, xwt1);
CreatePipe(v15, xwt1); CreatePipe(v15, xwt1);
CreatePipe(xwt1, xwt2); CreatePipe(xwt1, xwt2);
@@ -175,141 +200,141 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(v19, xwt5); CreatePipe(v19, xwt5);
CreatePipe(xwt5, wt2); CreatePipe(xwt5, wt2);
var xv22 = new PipeJoin(t1X - 15, ltg7Y); var xv22 = new PipeJoin(t1X - 3, ltg7Y);
var xv23 = new PipeJoin(t1X + 15, ltg6Y); var xv23 = new PipeJoin(t1X + 3, ltg6Y);
var v22 = CreateValve(22, t1X - 15, tValveY); var v22 = CreateValve(22, t1X - 3, tValveY, 0);
var v23 = CreateValve(23, t1X + 15, tValveY); var v23 = CreateValve(23, t1X + 3, tValveY, 2);
var t1 = CreateTank(1, t1X, tanksY, 100000); Tank1 = CreateTank(1, t1X, tanksY, 35000);
CreatePipe(xv20, xv22, true); CreatePipe(xv20, xv22, true, label: "Ltg. 7", labelOffsetX: 10);
CreatePipe(xv21, xv23, true); CreatePipe(xv21, xv23, true, label: "Ltg. 6", labelOffsetX: 4);
CreatePipe(xv22, v22); CreatePipe(xv22, v22);
CreatePipe(xv23, v23); CreatePipe(xv23, v23);
CreateInlet(v22, t1); CreateInlet(v22, Tank1);
CreateInlet(v23, t1); CreateInlet(v23, Tank1);
var xv24 = new PipeJoin(t2X - 15, ltg7Y); var xv24 = new PipeJoin(t2X - 3, ltg7Y);
var xv25 = new PipeJoin(t2X + 15, ltg6Y); var xv25 = new PipeJoin(t2X + 3, ltg6Y);
var v24 = CreateValve(24, t2X - 15, tValveY); var v24 = CreateValve(24, t2X - 3, tValveY, 0);
var v25 = CreateValve(25, t2X + 15, tValveY); var v25 = CreateValve(25, t2X + 3, tValveY, 2);
var t2 = CreateTank(2, t2X, tanksY, 100000); Tank2 = CreateTank(2, t2X, tanksY, 35000);
CreatePipe(xv22, xv24); CreatePipe(xv22, xv24);
CreatePipe(xv23, xv25); CreatePipe(xv23, xv25);
CreatePipe(xv24, v24); CreatePipe(xv24, v24);
CreatePipe(xv25, v25); CreatePipe(xv25, v25);
CreateInlet(v24, t2); CreateInlet(v24, Tank2);
CreateInlet(v25, t2); CreateInlet(v25, Tank2);
var xv26 = new PipeJoin(t3X - 15, ltg7Y); var xv26 = new PipeJoin(t3X - 3, ltg7Y);
var xv27 = new PipeJoin(t3X + 15, ltg6Y); var xv27 = new PipeJoin(t3X + 3, ltg6Y);
var v26 = CreateValve(26, t3X - 15, tValveY); var v26 = CreateValve(26, t3X - 3, tValveY, 0);
var v27 = CreateValve(27, t3X + 15, tValveY); var v27 = CreateValve(27, t3X + 3, tValveY, 2);
var t3 = CreateTank(3, t3X, tanksY, 100000); Tank3 = CreateTank(3, t3X, tanksY, 35000);
CreatePipe(xv24, xv26); CreatePipe(xv24, xv26);
CreatePipe(xv25, xv27); CreatePipe(xv25, xv27);
CreatePipe(xv26, v26); CreatePipe(xv26, v26);
CreatePipe(xv27, v27); CreatePipe(xv27, v27);
CreateInlet(v26, t3); CreateInlet(v26, Tank3);
CreateInlet(v27, t3); CreateInlet(v27, Tank3);
var v28 = CreateValve(28, (t3X + t4X) / 2, ltg7Y); var v28 = CreateValve(28, (t3X + t4X) / 2, ltg7Y, 1);
var v29 = CreateValve(29, (t3X + t4X) / 2, ltg6Y); var v29 = CreateValve(29, (t3X + t4X) / 2, ltg6Y, 3);
CreatePipe(xv26, v28); CreatePipe(xv26, v28);
CreatePipe(xv27, v29); CreatePipe(xv27, v29);
var xv30 = new PipeJoin(t4X - 15, ltg7Y); var xv30 = new PipeJoin(t4X - 3, ltg7Y);
var xv31 = new PipeJoin(t4X + 15, ltg6Y); var xv31 = new PipeJoin(t4X + 3, ltg6Y);
var v30 = CreateValve(30, t4X - 15, tValveY); var v30 = CreateValve(30, t4X - 3, tValveY, 0);
var v31 = CreateValve(31, t4X + 15, tValveY); var v31 = CreateValve(31, t4X + 3, tValveY, 2);
var t4 = CreateTank(4, t4X, tanksY, 100000); Tank4 = CreateTank(4, t4X, tanksY, 35000);
CreatePipe(v28, xv30); CreatePipe(v28, xv30);
CreatePipe(v29, xv31); CreatePipe(v29, xv31);
CreatePipe(xv30, v30); CreatePipe(xv30, v30);
CreatePipe(xv31, v31); CreatePipe(xv31, v31);
CreateInlet(v30, t4); CreateInlet(v30, Tank4);
CreateInlet(v31, t4); CreateInlet(v31, Tank4);
var xv32 = new PipeJoin(t5X - 15, ltg7Y); var xv32 = new PipeJoin(t5X - 3, ltg7Y);
var xv33 = new PipeJoin(t5X + 15, ltg6Y); var xv33 = new PipeJoin(t5X + 3, ltg6Y);
var v32 = CreateValve(32, t5X - 15, tValveY); var v32 = CreateValve(32, t5X - 3, tValveY, 0);
var v33 = CreateValve(33, t5X + 15, tValveY); var v33 = CreateValve(33, t5X + 3, tValveY, 2);
var t5 = CreateTank(5, t5X, tanksY, 100000); Tank5 = CreateTank(5, t5X, tanksY, 35000);
CreatePipe(xv30, xv32); CreatePipe(xv30, xv32, label: "Ltg. 7", labelOffsetX: 6);
CreatePipe(xv31, xv33); CreatePipe(xv31, xv33, label: "Ltg. 6", labelOffsetX: 0);
CreatePipe(xv32, v32); CreatePipe(xv32, v32);
CreatePipe(xv33, v33); CreatePipe(xv33, v33);
CreateInlet(v32, t5); CreateInlet(v32, Tank5);
CreateInlet(v33, t5); CreateInlet(v33, Tank5);
var v34 = CreateValve(34, (t5X + t6X) / 2, ltg7Y); var v34 = CreateValve(34, (t5X + t6X) / 2, ltg7Y, 1);
var v35 = CreateValve(35, (t5X + t6X) / 2, ltg6Y); var v35 = CreateValve(35, (t5X + t6X) / 2, ltg6Y, 3);
CreatePipe(xv32, v34); CreatePipe(xv32, v34);
CreatePipe(xv33, v35); CreatePipe(xv33, v35);
var xv36 = new PipeJoin(t6X + 15, ltg6Y); var xv36 = new PipeJoin(t6X + 3, ltg6Y);
var xv37 = new PipeJoin(t6X - 15, ltg7Y); var xv37 = new PipeJoin(t6X - 3, ltg7Y);
var v36 = CreateValve(36, t6X + 15, tValveY); var v36 = CreateValve(36, t6X + 3, tValveY, 2);
var v37 = CreateValve(37, t6X - 15, tValveY); var v37 = CreateValve(37, t6X - 3, tValveY, 0);
var t6 = CreateTank(6, t6X, tanksY, 100000); Tank6 = CreateTank(6, t6X, tanksY, 35000);
CreatePipe(v34, xv37); CreatePipe(v34, xv37);
CreatePipe(v35, xv36); CreatePipe(v35, xv36);
CreatePipe(xv37, v37); CreatePipe(xv37, v37);
CreatePipe(xv36, v36); CreatePipe(xv36, v36);
CreateInlet(v36, t6); CreateInlet(v36, Tank6);
CreateInlet(v37, t6); CreateInlet(v37, Tank6);
var xv38 = new PipeJoin(t7X + 15, ltg6Y); var xv38 = new PipeJoin(t7X + 3, ltg6Y);
var xv39 = new PipeJoin(t7X - 15, ltg7Y); var xv39 = new PipeJoin(t7X - 3, ltg7Y);
var v38 = CreateValve(38, t7X + 15, tValveY); var v38 = CreateValve(38, t7X + 3, tValveY, 2);
var v39 = CreateValve(39, t7X - 15, tValveY); var v39 = CreateValve(39, t7X - 3, tValveY, 0);
var t7 = CreateTank(7, t7X, tanksY, 100000); Tank7 = CreateTank(7, t7X, tanksY, 35000);
CreatePipe(xv37, xv39); CreatePipe(xv37, xv39);
CreatePipe(xv36, xv38); CreatePipe(xv36, xv38);
CreatePipe(xv38, v38); CreatePipe(xv38, v38);
CreatePipe(xv39, v39); CreatePipe(xv39, v39);
CreateInlet(v38, t7); CreateInlet(v38, Tank7);
CreateInlet(v39, t7); CreateInlet(v39, Tank7);
var xv40 = new PipeJoin(t8X + 15, ltg6Y); var xv40 = new PipeJoin(t8X + 3, ltg6Y);
var xv41 = new PipeJoin(t8X - 15, ltg7Y); var xv41 = new PipeJoin(t8X - 3, ltg7Y);
var v40 = CreateValve(40, t8X + 15, tValveY); var v40 = CreateValve(40, t8X + 3, tValveY, 2);
var v41 = CreateValve(41, t8X - 15, tValveY); var v41 = CreateValve(41, t8X - 3, tValveY, 0);
var t8 = CreateTank(8, t8X, tanksY, 100000); Tank8 = CreateTank(8, t8X, tanksY, 35000);
CreatePipe(xv38, xv40); CreatePipe(xv38, xv40, label: "Ltg. 6", labelOffsetX: 0);
CreatePipe(xv39, xv41); CreatePipe(xv39, xv41, label: "Ltg. 7", labelOffsetX: 6);
CreatePipe(xv40, v40); CreatePipe(xv40, v40);
CreatePipe(xv41, v41); CreatePipe(xv41, v41);
CreateInlet(v40, t8); CreateInlet(v40, Tank8);
CreateInlet(v41, t8); CreateInlet(v41, Tank8);
var v42 = CreateValve(42, t9X + 15, tValveY); var v42 = CreateValve(42, t9X + 3, tValveY, 2);
var v43 = CreateValve(43, t9X - 15, tValveY); var v43 = CreateValve(43, t9X - 3, tValveY, 0);
var t9 = CreateTank(9, t9X, tanksY, 100000); Tank9 = CreateTank(9, t9X, tanksY, 35000);
CreatePipe(xv40, v42); CreatePipe(xv40, v42);
CreatePipe(xv41, v43); CreatePipe(xv41, v43);
CreateInlet(v42, t9); CreateInlet(v42, Tank9);
CreateInlet(v43, t9); CreateInlet(v43, Tank9);
var xp12 = new PipeJoin((t1X + t2X) / 2, tanksY + 80); var xp12 = new PipeJoin((t1X + t2X) / 2, tanksY + 16);
P12 = new Pump("P1/2", (t1X + t2X) / 2, tanksY + 110, () => App.Plant?.IsP12Active ?? false, () => App.Plant?.P12HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); P12 = new Pump("P1/2", (t1X + t2X) / 2, tanksY + 22, () => App.Plant?.State.P12_State ?? default, () => App.Plant?.State.P12_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x)));
CreateOutlet(t1, xp12); CreateOutlet(Tank1, xp12);
CreateOutlet(t2, xp12); CreateOutlet(Tank2, xp12);
CreatePipe(xp12, P12); CreatePipe(xp12, P12);
P3 = new Pump("P3", t3X, tanksY + 110, () => App.Plant?.IsP3Active ?? false, () => App.Plant?.P3HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); P3 = new Pump("P3", t3X, tanksY + 22, () => App.Plant?.State.P3_State ?? default, () => App.Plant?.State.P3_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x)));
P4 = new Pump("P4", t4X, tanksY + 110, () => App.Plant?.IsP4Active ?? false, () => App.Plant?.P4HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); P4 = new Pump("P4", t4X, tanksY + 22, () => App.Plant?.State.P4_State ?? default, () => App.Plant?.State.P4_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x)));
P5 = new Pump("P5", t5X, tanksY + 110, () => App.Plant?.IsP5Active ?? false, () => App.Plant?.P5HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); P5 = new Pump("P5", t5X, tanksY + 22, () => App.Plant?.State.P5_State ?? default, () => App.Plant?.State.P5_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x)));
CreateOutlet(t3, P3); CreateOutlet(Tank3, P3);
CreateOutlet(t4, P4); CreateOutlet(Tank4, P4);
CreateOutlet(t5, P5); CreateOutlet(Tank5, P5);
var v44 = CreateValve(44, (t1X + t2X) / 2, tanksY + 150); var v44 = CreateValve(44, (t1X + t2X) / 2, tanksY + 30, 2);
var v45 = CreateValve(45, t3X, tanksY + 150); var v45 = CreateValve(45, t3X, tanksY + 30, 2);
var v46 = CreateValve(46, (t3X + t4X) / 2, tanksY + 180); var v46 = CreateValve(46, (t3X + t4X) / 2, ltg5Y, 3);
var v47 = CreateValve(47, t4X, tanksY + 150); var v47 = CreateValve(47, t4X, tanksY + 30, 2);
var v48 = CreateValve(48, t5X, tanksY + 150); var v48 = CreateValve(48, t5X, tanksY + 30, 2);
var xv44 = new PipeJoin((t1X + t2X) / 2, tanksY + 180); var xv44 = new PipeJoin((t1X + t2X) / 2, tanksY + 36);
var xv45 = new PipeJoin(t3X, tanksY + 180); var xv45 = new PipeJoin(t3X, tanksY + 36);
var xv47 = new PipeJoin(t4X, tanksY + 180); var xv47 = new PipeJoin(t4X, tanksY + 36);
CreatePipe(P12, v44); CreatePipe(P12, v44);
CreatePipe(P3, v45); CreatePipe(P3, v45);
CreatePipe(P4, v47); CreatePipe(P4, v47);
@@ -318,148 +343,250 @@ namespace PamhagenSysCtrl.Helpers {
CreatePipe(v45, xv45); CreatePipe(v45, xv45);
CreatePipe(v47, xv47); CreatePipe(v47, xv47);
CreatePipe(xv44, v10); CreatePipe(xv44, v10, label: "Ltg. 5");
CreatePipe(v48, xv47, true); CreatePipe(v48, xv47, true);
CreateTwoWayPipe(xv47, v46); CreateTwoWayPipe(xv47, v46);
CreateTwoWayPipe(v46, xv45); CreateTwoWayPipe(v46, xv45);
CreateTwoWayPipe(xv45, xv44); CreateTwoWayPipe(xv45, xv44);
var xt6 = new PipeJoin(t6X, tanksY + 110); var xt6 = new PipeJoin(t6X, tanksY + 22);
var xt7 = new PipeJoin(t7X, tanksY + 110); var xt7 = new PipeJoin(t7X, tanksY + 22);
var xt8 = new PipeJoin(t8X, tanksY + 110); var xt8 = new PipeJoin(t8X, tanksY + 22);
P6 = new Pump("P6", t6X, tanksY + 150, () => App.Plant?.IsP6Active ?? false, () => App.Plant?.P6HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); P6 = new Pump("P6", t6X, tanksY + 30, () => App.Plant?.State.P6_State ?? default, () => App.Plant?.State.P6_HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Contains(x)));
CreateOutlet(t6, xt6); CreateOutlet(Tank6, xt6);
CreateOutlet(t7, xt7); CreateOutlet(Tank7, xt7);
CreateOutlet(t8, xt8); CreateOutlet(Tank8, xt8);
CreateOutlet(t9, xt8); CreateOutlet(Tank9, xt8);
CreatePipe(xt8, xt7); CreatePipe(xt8, xt7);
CreatePipe(xt7, xt6); CreatePipe(xt7, xt6);
CreatePipe(xt6, P6); CreatePipe(xt6, P6);
var v49 = CreateValve(49, t4X, tanksY + 220); var v49 = CreateValve(49, t4X, tanksY + 44, 2);
CreatePipe(xv47, v49); CreatePipe(xv47, v49);
var press1 = new Press("Presse 1", pr1X, prY, 200000, () => App.Plant?.FillLevelPress1 ?? FillState.Unknown); var prX = (pr1X + pr2X) / 2;
var press2 = new Press("Presse 2", pr2X, prY, 150000, () => App.Plant?.FillLevelPress2 ?? FillState.Unknown); var pr1cX = prX - 3;
var v50 = CreateValve(50, pr1X - 45, prY - 100); var pr2cX = prX + 3;
var v51 = CreateValve(51, pr1X - 15, prY - 100); Press1 = new Press("Presse 1", pr1X, prY, 20000, () => FillState.Unknown, () => App.Plant?.State.Press1_HasClearance ?? false);
var v52 = CreateValve(52, pr1X + 15, prY - 100); Press2 = new Press("Presse 2", pr2X, prY, 15000, () => FillState.Unknown, () => App.Plant?.State.Press2_HasClearance ?? false);
var v53 = CreateValve(53, pr1X + 45, prY - 100); var v50 = CreateValve(50, pr2cX + 6, ltg3Y, 1);
var v54 = CreateValve(54, pr2X - 45, prY - 100); var v51 = CreateValve(51, pr1cX - 6, ltg3Y, 1);
var v55 = CreateValve(55, pr2X - 15, prY - 100); var v52 = CreateValve(52, pr1cX - 6, ltg4Y, 3);
var v56 = CreateValve(56, pr2X + 15, prY - 100); var v53 = CreateValve(53, pr2cX + 6, ltg4Y, 3);
var v57 = CreateValve(57, pr2X + 45, prY - 100); var v54 = CreateValve(54, pr2cX + 6, ltg8Y, 1);
var xp1 = new PipeJoin(pr1X - 15, 610); var v55 = CreateValve(55, pr1cX - 6, ltg8Y, 1);
var xp2 = new PipeJoin(pr1X + 15, 640); var v56 = CreateValve(56, pr1cX - 6, ltg9Y, 3);
var xp3 = new PipeJoin(pr2X - 45, 550); var v57 = CreateValve(57, pr2cX + 6, ltg9Y, 3);
var xp4 = new PipeJoin(pr2X + 45, 580); var xp1 = new PipeJoin(pr1cX - 14, ltg3Y);
var xp2 = new PipeJoin(pr1cX - 14, ltg4Y);
var xp3 = new PipeJoin(pr2cX + 14, ltg8Y);
var xp4 = new PipeJoin(pr2cX + 14, ltg9Y);
var xp5 = new PipeJoin(pr1cX, ltg3Y);
var xp6 = new PipeJoin(pr1cX, ltg4Y);
var xp7 = new PipeJoin(pr2cX, ltg8Y);
var xp8 = new PipeJoin(pr2cX, ltg9Y);
CreatePipe(v6, xp1, true); CreatePipe(V6, xp1, true, label: "Ltg. 3", labelOffsetX: 6);
CreatePipe(v8, xp2, true); CreatePipe(V8, xp2, true, label: "Ltg. 4", labelOffsetX: 18);
CreatePipe(v49, xp3, true); CreatePipe(v49, xp3, true, label: "Ltg. 8");
CreatePipe(P6, xp4, true); CreatePipe(P6, xp4, true, label: "Ltg. 9");
CreatePipe(xp1, v51); CreatePipe(xp1, v51, true);
CreatePipe(xp1, v55); CreatePipe(xp1, v55, true);
CreatePipe(xp2, v52); CreatePipe(xp2, v52, true);
CreatePipe(xp2, v56); CreatePipe(xp2, v56, true);
CreatePipe(xp3, v50); CreatePipe(xp3, v50, true);
CreatePipe(xp3, v54); CreatePipe(xp3, v54, true);
CreatePipe(xp4, v53); CreatePipe(xp4, v53, true);
CreatePipe(xp4, v57); CreatePipe(xp4, v57, true);
CreateInlet(v50, press1); CreatePipe(v50, xp5);
CreateInlet(v51, press1); CreatePipe(v51, xp5);
CreateInlet(v52, press1); CreatePipe(v52, xp6);
CreateInlet(v53, press1); CreatePipe(v53, xp6);
CreateInlet(v54, press2); CreatePipe(v54, xp7);
CreateInlet(v55, press2); CreatePipe(v55, xp7);
CreateInlet(v56, press2); CreatePipe(v56, xp8);
CreateInlet(v57, press2); CreatePipe(v57, xp8);
CreatePipe(xp5, xp6);
CreatePipe(xp7, xp8);
var zv1 = new ExternalValve("ZV1", pr1cX, prY - 8, 0, () => App.Plant?.State.Press1_HasClearance ?? false);
var zv2 = new ExternalValve("ZV2", pr2cX, prY - 8, 2, () => App.Plant?.State.Press2_HasClearance ?? false);
CreatePipe(xp6, zv1, true);
CreatePipe(xp8, zv2, true);
CreateInlet(zv1, Press1, true);
CreateInlet(zv2, Press2, true);
PathShortcuts = [];
foreach (var key in Enum.GetValues<PamhagenPlant.ShortcutKey>()) {
var n = Enum.GetName(key);
if (n?.StartsWith("PATH_") != true) continue;
ISource src;
Pump p;
INode? inter = null;
ISink sink;
if (n.StartsWith("PATH_MW1_")) {
src = MW1;
p = MP1;
} else if (n.StartsWith("PATH_MW2_")) {
src = MW2;
p = MP2;
} else if (n.StartsWith("PATH_T12_")) {
src = Tank1;
p = P12;
} else if (n.StartsWith("PATH_T3_")) {
src = Tank3;
p = P3;
} else if (n.StartsWith("PATH_T4_")) {
src = Tank4;
p = P4;
} else if (n.StartsWith("PATH_T5_")) {
src = Tank5;
p = P5;
} else if (n.StartsWith("PATH_P6_")) {
src = Tank6;
p = P6;
} else {
continue;
}
if (n.Contains("_LTG1_")) {
inter = V5;
} else if (n.Contains("_LTG2_")) {
inter = V7;
} else if (n.Contains("_LTG3_")) {
inter = V6;
} else if (n.Contains("_LTG4_")) {
inter = V8;
}
if (n.EndsWith("_PRESS1")) {
sink = Press1;
} else if (n.EndsWith("_PRESS2")) {
sink = Press2;
} else if (n.EndsWith("_T1")) {
sink = Tank1;
} else if (n.EndsWith("_T2")) {
sink = Tank2;
} else if (n.EndsWith("_T3")) {
sink = Tank3;
} else if (n.EndsWith("_T4")) {
sink = Tank4;
} else if (n.EndsWith("_T5")) {
sink = Tank5;
} else if (n.EndsWith("_T6")) {
sink = Tank6;
} else if (n.EndsWith("_T7")) {
sink = Tank7;
} else if (n.EndsWith("_T8")) {
sink = Tank8;
} else if (n.EndsWith("_T9")) {
sink = Tank9;
} else {
continue;
}
PathShortcuts[key] = (src, p, inter, sink);
}
} }
protected static Valve CreateValve(int n, double x, double y) { protected ControlledValve CreateValve(int n, double x, double y, int o) {
return new(n, x, y, () => App.Plant?.IsValveOpen(n) ?? false, () => App.Plant?.WantValveOpen(n) ?? false); return new(n, x, y, o, () => App.Plant?.State.Valve_OpenState(n), (v) => App.Plant?.ManualControlMode ?? false ? App.Plant.State.Valve_WantOpen(n) : SelectedPaths.Any(p => p.Contains(v)));
} }
protected static Tank CreateTank(int n, double x, double y, int capacity) { protected static Tank CreateTank(int n, double x, double y, int capacity) {
return new($"Tank {n}", x, y, capacity, () => App.Plant?.TankFillLevels[n - 1] ?? FillState.Unknown); return new($"Tank {n}", x, y, capacity, () => App.Plant?.State.Tanks_FillLevel[n - 1] ?? FillState.Unknown, () => true);
} }
public void TraverseSubgraph(INode start, HashSet<IEdge>? visited = null, Action<IEdge>? edgeAction = null, Action<Valve>? valveAction = null) { private int PathCost(Path p) {
visited ??= []; return
if (start is Valve v) { 1000 * p.Valves.Count() +
valveAction?.Invoke(v); (p.InnerValves.Any(v => v.Nr == 15) && !p.InnerValves.Any(v => v.Nr == 16 || v.Nr == 20) ? 10 : 0) +
return; (p.InnerValves.Any(v => v.Nr == 11) && !p.InnerValves.Any(v => v.Nr == 14 || v.Nr == 21) ? 10 : 0) +
} else if (start is ISink || start is Pump) { (p.InnerValves.Any(v => v.Nr == 6) ? 10 : 0) +
return; p.Bends;
}
public Path? GetFreePath(IEnumerable<INode> points, bool overrideStart = false) {
if (overrideStart) {
return GetPath(points, (n) => n is not ISink && (n is not ControlledValve v || !v.LockedForPaths.Any(p => p.Start != points.First())), PathCost);
} else {
return GetPath(points, (n) => n is not ISink && (n is not ControlledValve v || !v.IsLocked), PathCost);
} }
}
public static IEnumerable<(INode Start, IEdge Edge, bool EdgeReverse, INode End)> TraverseSubgraph(INode start, Func<INode, bool> isBorder, HashSet<IEdge>? _visited = null, bool reverse = false) {
if (isBorder(start))
yield break;
var visited = _visited ?? [];
foreach (var edge in start.Inputs) { foreach (var edge in start.Inputs) {
if (visited.Contains(edge)) continue; if (visited.Contains(edge)) continue;
var o = edge.IsTwoWay && edge.Start == start ? edge.End : edge.Start; var o = edge.IsTwoWay && edge.Start == start ? edge.End : edge.Start;
edgeAction?.Invoke(edge); yield return (start, edge, reverse || _visited == null, o);
visited.Add(edge); visited.Add(edge);
TraverseSubgraph(o, visited, edgeAction, valveAction); foreach (var e in TraverseSubgraph(o, isBorder, visited, reverse || _visited == null))
yield return e;
} }
foreach (var edge in start.Outputs) { foreach (var edge in start.Outputs) {
if (visited.Contains(edge)) continue; if (visited.Contains(edge)) continue;
var o = edge.IsTwoWay && edge.End == start ? edge.Start : edge.End; var o = edge.IsTwoWay && edge.End == start ? edge.Start : edge.End;
edgeAction?.Invoke(edge); yield return (start, edge, reverse, o);
visited.Add(edge); visited.Add(edge);
TraverseSubgraph(o, visited, edgeAction, valveAction); foreach (var e in TraverseSubgraph(o, isBorder, visited, reverse))
yield return e;
} }
} }
public void AddPath(Path path) { public void AddPath(Path path) {
_selectedPaths.Add(path); _selectedPaths.Add(path);
UpdateValvesAdjacientToPath(path, true, false); path.LockValves();
if (App.Plant is PamhagenPlant plant) { if (App.Plant is PamhagenPlant plant) {
foreach (var (_, node) in path.Hops) { foreach (var v in path.InnerValves) plant.OpenV(v.Nr);
if (node is Valve v) { foreach (var v in path.BorderValves) plant.CloseV(v.Nr);
plant.OpenV(v.Nr);
}
}
} }
} }
public void RemovePath(Path path) { public void RemovePath(Path path) {
_selectedPaths.Remove(path); _selectedPaths.Remove(path);
UpdateValvesAdjacientToPath(path, false); path.UnlockValves();
foreach (var p in _selectedPaths) UpdateValvesAdjacientToPath(p, true);
if (App.Plant is PamhagenPlant plant) { if (App.Plant is PamhagenPlant plant) {
foreach (var (_, node) in path.Hops) { foreach (var v in path.InnerValves) plant.CloseV(v.Nr);
if (node is Valve v) {
plant.CloseV(v.Nr);
}
}
} }
} }
private void UpdateValvesAdjacientToPath(Path path, bool locked, bool? wantOpen = null) { public static void ColorPipesAdjacientToPath(Path path, Brush? color1, MotorState flowArrows, Brush? color2) {
foreach (var (edge, node) in path.Hops) {
TraverseSubgraph(node, valveAction: (v) => {
v.IsLocked = locked;
if (wantOpen.HasValue) {
if (wantOpen.Value) {
App.Plant?.OpenV(v.Nr);
} else {
App.Plant?.CloseV(v.Nr);
}
}
});
}
}
public void ColorPipesAdjacientToPath(Path path, Brush? color1, bool flowArrows, Brush? color2) {
var color = color1; var color = color1;
var flowPathOffset = 0.0; var flowPathOffset = 0.0;
HashSet<IEdge> visited = []; HashSet<IEdge> visited = [];
foreach (var (edge, node) in path.Hops) { foreach (var (edge, node) in path.Hops) {
edge.Highlight = color1; edge.Highlight = color1;
edge.Flow = flowArrows ? (edge.End == node, flowPathOffset) : null; if (flowArrows > MotorState.Halt) {
if (edge.Flow != null) flowPathOffset += edge.FlowLength; var dir = edge.End == node;
if (flowArrows == MotorState.Backward) dir = !dir;
edge.Flow = (dir, flowPathOffset);
flowPathOffset += edge.FlowLength;
}
visited.Add(edge); visited.Add(edge);
if (node is Pump) color = color2; if (node is Pump) color = color2;
TraverseSubgraph(node, visited, edgeAction: (e) => e.Highlight = color); foreach (var (_, e, _, _) in TraverseSubgraph(node, (n) => n is Valve || n is ISink || n is Pump, visited)) {
e.Highlight = color;
}
}
}
public static void ColorPipesByValves(INode start, Brush? color1, MotorState flowArrows, Brush? color2) {
var flowPathOffset = 0.0;
foreach (var (s, e, r, _) in TraverseSubgraph(start, (n) => n is ISink || n is Outlet || !((n as Valve)?.IsOpen ?? (n as ExternalValve)?.IsOpen ?? true))) {
if (((e.Start as Valve)?.IsOpen ?? true) && ((e.End as Valve)?.IsOpen ?? true)) {
e.Highlight = color1;
if (flowArrows == MotorState.Forward || flowArrows == MotorState.Backward) {
var dir = e.Start == s;
if (r) dir = !dir;
if (flowArrows == MotorState.Backward) dir = !dir;
e.Flow = (dir, flowPathOffset);
flowPathOffset += e.FlowLength;
}
} else {
e.Highlight = color2;
}
} }
} }
} }
File diff suppressed because it is too large. Load diff
+39 -25
View File
@@ -1,4 +1,4 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Animation; using System.Windows.Media.Animation;
@@ -14,17 +14,21 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public double Angle { get; set; } public double Angle { get; set; }
public bool IsAnimationActive { public bool IsAnimationActive {
get => !(_storyboard?.GetIsPaused() ?? true);
set { set {
if (value) { if (_storyboard == null) return;
_storyboard?.Resume(); if (value && _storyboard.GetIsPaused()) {
} else { _storyboard.Resume();
_storyboard?.Pause(); } else if (!value && !_storyboard.GetIsPaused()) {
_storyboard.Pause();
} }
} }
} }
private Polyline? _polyline; private Polyline? _polyline;
private Storyboard? _storyboard; private Storyboard? _storyboard;
private DoubleAnimation? _leftSlide;
private Canvas? _clipCanvas;
public Auger(double x, double y, double width, double height, double angle = 0) { public Auger(double x, double y, double width, double height, double angle = 0) {
CenterX = x; CenterX = x;
@@ -35,41 +39,51 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
var d = Height / 4;
int n = (int)(Width / d / 4) + 2;
_polyline = new Polyline() { _polyline = new Polyline() {
Points = [.. Enumerable.Range(0, n).SelectMany(i => new List<Point>() {
new(i * 4 * d, Height / 2), new((i * 4 + 1) * d, 0),
new((i * 4 + 2) * d, Height / 2), new((i * 4 + 3) * d, Height)
}), new(n * 4 * d, Height / 2), new(0, Height / 2)],
Fill = Brushes.Transparent, Fill = Brushes.Transparent,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
}; };
_polyline.SetValue(Canvas.LeftProperty, 0.0); _polyline.SetValue(Canvas.LeftProperty, 0.0);
var slideLeft = new DoubleAnimation { _leftSlide = new DoubleAnimation {
From = 0, From = 0,
To = -d * 4,
Duration = new Duration(TimeSpan.FromSeconds(1)), Duration = new Duration(TimeSpan.FromSeconds(1)),
RepeatBehavior = RepeatBehavior.Forever, RepeatBehavior = RepeatBehavior.Forever,
}; };
Storyboard.SetTarget(_leftSlide, _polyline);
_storyboard = new Storyboard(); _storyboard = new Storyboard();
_storyboard.Children.Add(slideLeft); _storyboard.Children.Add(_leftSlide);
Storyboard.SetTarget(slideLeft, _polyline); Storyboard.SetTargetProperty(_leftSlide, new PropertyPath(Canvas.LeftProperty));
Storyboard.SetTargetProperty(slideLeft, new PropertyPath(Canvas.LeftProperty)); _clipCanvas = new Canvas() {
var clipCanvas = new Canvas() {
Width = Width,
Height = Height,
ClipToBounds = true, ClipToBounds = true,
RenderTransform = new RotateTransform(Angle, Width / 2, Height / 2),
}; };
clipCanvas.SetValue(Canvas.TopProperty, CenterY - Height / 2); _clipCanvas.Children.Add(_polyline);
clipCanvas.SetValue(Canvas.LeftProperty, CenterX - Width / 2); canvas.Children.Add(_clipCanvas);
clipCanvas.Children.Add(_polyline);
canvas.Children.Add(clipCanvas);
_storyboard.Begin(); _storyboard.Begin();
_storyboard.Pause(); _storyboard.Pause();
} }
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
var d = Height / 4 * scale;
int n = (int)(Width * scale / d / 4) + 2;
_polyline?.Points = [.. Enumerable.Range(0, n).SelectMany(i => new List<Point>() {
new(i * 4 * d, Height / 2 * scale), new((i * 4 + 1) * d, 0),
new((i * 4 + 2) * d, Height / 2 * scale), new((i * 4 + 3) * d, Height * scale)
}), new(n * 4 * d, Height / 2 * scale), new(0, Height / 2 * scale)];
_polyline?.StrokeThickness = border;
var active = IsAnimationActive;
_leftSlide?.To = -d * 4;
_storyboard?.Stop();
_storyboard?.Begin();
if (!active) _storyboard?.Pause();
_clipCanvas?.Width = Width * scale;
_clipCanvas?.Height = Height * scale;
_clipCanvas?.RenderTransform = new RotateTransform(Angle, Width / 2 * scale, Height / 2 * scale);
_clipCanvas?.SetValue(Canvas.TopProperty, (CenterY - Height / 2) * scale);
_clipCanvas?.SetValue(Canvas.LeftProperty,( CenterX - Width / 2) * scale);
}
} }
} }
@@ -0,0 +1,51 @@
using System.Windows;
using System.Windows.Media;
namespace PamhagenSysCtrl.Helpers.Pipeline {
public class ControlledValve : Valve {
public int Nr { get; set; }
public override string Label => $"V{Nr}";
public readonly ISet<Path> LockedForPaths = new HashSet<Path>();
public bool IsLocked => LockedForPaths.Any();
public bool WantOpen => CallbackWantOpen(this);
protected Func<Valve, bool> CallbackWantOpen;
public ControlledValve(int nr, double x, double y, int orientation, Func<bool?> cbOpen, Func<Valve, bool> cbWant) :
base(x, y, orientation, cbOpen) {
Nr = nr;
CallbackWantOpen = cbWant;
}
public override void Update(bool isHovering, bool isPressed) {
if (App.Plant?.ManualControlMode ?? false) {
_text?.Visibility = Visibility.Visible;
_triangles?.Fill =
!WantOpen && IsClosed && !isHovering ? PamhagenBrushes.Red :
!WantOpen && IsClosed ? PamhagenBrushes.Green :
!WantOpen && !IsClosed ? PamhagenBrushes.DimRed :
WantOpen && !IsOpen ? PamhagenBrushes.DimGreen :
WantOpen && IsOpen && !isHovering ? PamhagenBrushes.Green :
WantOpen && IsOpen ? PamhagenBrushes.Red :
Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray);
} else {
_text?.Visibility = isHovering ? Visibility.Visible : Visibility.Hidden;
_triangles?.Fill =
IsLocked && !WantOpen && IsClosed ? PamhagenBrushes.DimOrange :
IsLocked && !WantOpen && !IsClosed ? PamhagenBrushes.Red :
IsLocked && WantOpen && !IsOpen ? PamhagenBrushes.Yellow :
IsLocked && WantOpen && IsOpen ? PamhagenBrushes.Green :
Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray);
}
if (!IsClosed) {
_topLine?.Points[1] = _middlePoints.Open.Top;
_bottomLine?.Points[1] = _middlePoints.Open.Bottom;
} else {
_topLine?.Points[1] = _middlePoints.Closed.Top;
_bottomLine?.Points[1] = _middlePoints.Closed.Bottom;
}
}
}
}
@@ -1,4 +1,4 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Animation; using System.Windows.Media.Animation;
@@ -7,8 +7,8 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class ConveyorBelt : INode { public class ConveyorBelt : INode {
public const double WIDTH = 30; public const double WIDTH = 6;
public const double HEIGHT = 120; public const double HEIGHT = 24;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -18,22 +18,23 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public bool IsActive => CallbackActive(); public bool IsActive => CallbackActive();
public bool IsAnimationActive { public bool IsAnimationActive {
get => _animationActive; get => !(_storyboard?.GetIsPaused() ?? true);
set { set {
_animationActive = value; if (_storyboard == null) return;
if (value) { if (value && _storyboard.GetIsPaused()) {
_storyboard?.Resume(); _storyboard.Resume();
} else { } else if (!value && !_storyboard.GetIsPaused()) {
_storyboard?.Pause(); _storyboard.Pause();
} }
} }
} }
protected Func<bool> CallbackActive; protected Func<bool> CallbackActive;
private bool _animationActive; private Rectangle? _rect;
private Shape? _rect;
private Polyline? _polyline; private Polyline? _polyline;
private DoubleAnimation? _slideUp;
private Canvas? _clipCanvas;
private Storyboard? _storyboard; private Storyboard? _storyboard;
public ConveyorBelt(string label, double x, double y, Func<bool> cbActive) { public ConveyorBelt(string label, double x, double y, Func<bool> cbActive) {
@@ -47,52 +48,63 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_rect = new Rectangle() { _rect = new Rectangle() {
Width = WIDTH,
Height = HEIGHT,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
StrokeLineJoin = PenLineJoin.Round, StrokeLineJoin = PenLineJoin.Round,
}; };
_rect.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_rect.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2);
var d = 15;
int n = (int)(HEIGHT / d) + 2;
_polyline = new Polyline() { _polyline = new Polyline() {
Points = [.. Enumerable.Range(0, n).SelectMany(i => new List<Point>() {
new(-WIDTH, i * d), new(WIDTH, i * d), new(-WIDTH, i * d)
})],
Fill = Brushes.Transparent, Fill = Brushes.Transparent,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2, StrokeThickness = 2,
}; };
_polyline.SetValue(Canvas.TopProperty, 0.0); _polyline.SetValue(Canvas.TopProperty, 0.0);
var slideUp = new DoubleAnimation { _slideUp = new DoubleAnimation {
From = 0, From = 0,
To = -d,
Duration = new Duration(TimeSpan.FromSeconds(1)), Duration = new Duration(TimeSpan.FromSeconds(1)),
RepeatBehavior = RepeatBehavior.Forever, RepeatBehavior = RepeatBehavior.Forever,
}; };
Storyboard.SetTargetProperty(_slideUp, new PropertyPath(Canvas.TopProperty));
_storyboard = new Storyboard(); _storyboard = new Storyboard();
_storyboard.Children.Add(slideUp); _storyboard.Children.Add(_slideUp);
Storyboard.SetTarget(slideUp, _polyline); Storyboard.SetTarget(_slideUp, _polyline);
Storyboard.SetTargetProperty(slideUp, new PropertyPath(Canvas.TopProperty)); _clipCanvas = new Canvas() {
var clipCanvas = new Canvas() {
Width = WIDTH - 15,
Height = HEIGHT - 8,
ClipToBounds = true, ClipToBounds = true,
}; };
clipCanvas.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2 + 4); _clipCanvas.Children.Add(_polyline);
clipCanvas.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2 + 7.5);
clipCanvas.Children.Add(_polyline);
canvas.Children.Add(_rect); canvas.Children.Add(_rect);
canvas.Children.Add(clipCanvas); canvas.Children.Add(_clipCanvas);
_storyboard.Begin(); _storyboard.Begin();
_storyboard.Pause(); _storyboard.Pause();
}
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_rect?.Width = WIDTH * scale;
_rect?.Height = HEIGHT * scale;
_rect?.StrokeThickness = border;
_rect?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale);
_rect?.SetValue(Canvas.TopProperty, (CenterY - HEIGHT / 2) * scale);
var d = Math.Round(3 * scale);
int n = (int)(HEIGHT * scale / d) + 2;
_polyline?.Points = [.. Enumerable.Range(0, n).SelectMany(i => new List<Point>() {
new(-WIDTH * scale, i * d), new(WIDTH * scale, i * d), new(-WIDTH * scale, i * d)
})];
_polyline?.StrokeThickness = border;
var active = IsAnimationActive;
_slideUp?.To = -d;
_storyboard?.Stop();
_storyboard?.Begin();
if (!active) _storyboard?.Pause();
_clipCanvas?.Width = (WIDTH - 2.2) * scale - border * 2;
_clipCanvas?.Height = (HEIGHT - 0.8) * scale - border * 2;
_clipCanvas?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2 + 1.1) * scale + border);
_clipCanvas?.SetValue(Canvas.TopProperty, (CenterY - HEIGHT / 2 + 0.4) * scale + border);
} }
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
@@ -102,11 +114,11 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public void Update(bool isHovering, bool isPressed) { public void Update(bool isHovering, bool isPressed) {
IsAnimationActive = IsActive; IsAnimationActive = IsActive;
_rect?.Fill = _rect?.Fill =
IsAnimationActive && isHovering && isPressed ? PamhagenBrushes.Red : IsActive && isHovering && isPressed ? PamhagenBrushes.Red :
IsAnimationActive && isHovering ? PamhagenBrushes.Red : IsActive && isHovering ? PamhagenBrushes.Red :
!IsAnimationActive && isHovering && isPressed ? PamhagenBrushes.Green : !IsActive && isHovering && isPressed ? PamhagenBrushes.Green :
!IsAnimationActive && isHovering ? PamhagenBrushes.Green : !IsActive && isHovering ? PamhagenBrushes.Green :
IsAnimationActive ? PamhagenBrushes.Green : IsActive ? PamhagenBrushes.Green :
Brushes.WhiteSmoke; Brushes.WhiteSmoke;
} }
} }
@@ -1,4 +1,4 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Shapes; using System.Windows.Shapes;
@@ -6,8 +6,8 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class EncasedAuger : INode { public class EncasedAuger : INode {
public const double WIDTH = 80; public const double WIDTH = 16;
public const double HEIGHT = 20; public const double HEIGHT = 4;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -29,8 +29,9 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
protected Func<bool> CallbackActive; protected Func<bool> CallbackActive;
private bool _animationActive; private bool _animationActive;
private Shape? _inner; private Canvas? _canvas;
private Shape? _outer; private Rectangle? _inner;
private Rectangle? _outer;
private Auger? _auger; private Auger? _auger;
private TextBlock? _text; private TextBlock? _text;
@@ -46,41 +47,49 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
var c = new Canvas() { _canvas = new Canvas() {
RenderTransform = new RotateTransform(Angle, WIDTH / 2, HEIGHT / 2), RenderTransform = new RotateTransform(Angle),
}; };
_outer = new Rectangle() { _outer = new Rectangle() {
Width = WIDTH,
Height = HEIGHT,
Fill = Brushes.Black, Fill = Brushes.Black,
}; };
_inner = new Rectangle() { _inner = new Rectangle() {
Width = WIDTH,
Height = HEIGHT - 4,
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
}; };
c.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
c.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2); _canvas.Children.Add(_outer);
_inner.SetValue(Canvas.TopProperty, 2.0); _canvas.Children.Add(_inner);
c.Children.Add(_outer); canvas.Children.Add(_canvas);
c.Children.Add(_inner);
canvas.Children.Add(c);
_text = new() { _text = new() {
Text = Label, Text = Label,
Width = WIDTH,
FontSize = 12,
TextAlignment = TextAlignment.Center, TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
}; };
_text.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_text.SetValue(Canvas.TopProperty, CenterY + HEIGHT / 2);
canvas.Children.Add(_text); canvas.Children.Add(_text);
_auger = new Auger(CenterX, CenterY, WIDTH, 12, Angle); _auger = new Auger(CenterX, CenterY, WIDTH, 2.4, Angle);
_auger.Draw(canvas); _auger.Draw(canvas);
} }
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_canvas?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale);
_canvas?.SetValue(Canvas.TopProperty, (CenterY - HEIGHT / 2) * scale);
_canvas?.RenderTransform = new RotateTransform(Angle, WIDTH / 2 * scale, HEIGHT / 2 * scale);
_outer?.Width = WIDTH * scale;
_outer?.Height = HEIGHT * scale;
_inner?.Width = WIDTH * scale;
_inner?.Height = HEIGHT * scale - border * 2;
_inner?.SetValue(Canvas.TopProperty, border);
_text?.FontSize = 12;
_text?.Width = WIDTH * scale;
_text?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale);
_text?.SetValue(Canvas.TopProperty, (CenterY + HEIGHT / 2) * scale);
_auger?.Scale(scale);
}
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
return Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= WIDTH / 2; return Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= WIDTH / 2;
} }
+48 -23
View File
@@ -1,4 +1,4 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Animation; using System.Windows.Media.Animation;
@@ -7,8 +7,8 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
class EntryTrough : INode { class EntryTrough : INode {
public const double WIDTH = 150; public const double WIDTH = 30;
public const double HEIGHT = 60; public const double HEIGHT = 12;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -20,9 +20,8 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public bool IsAnimationActive { public bool IsAnimationActive {
get => _animationActive; get => _animationActive;
set { set {
_animationActive = value;
_auger?.IsAnimationActive = value; _auger?.IsAnimationActive = value;
if (value) { if (value && !_animationActive) {
_doorTransform?.BeginAnimation( _doorTransform?.BeginAnimation(
RotateTransform.AngleProperty, RotateTransform.AngleProperty,
new DoubleAnimation { new DoubleAnimation {
@@ -30,7 +29,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
To = 60, To = 60,
Duration = TimeSpan.FromSeconds(Math.Abs(_doorTransform.Angle - 60) / 60.0) Duration = TimeSpan.FromSeconds(Math.Abs(_doorTransform.Angle - 60) / 60.0)
}); });
} else { } else if (!value && _animationActive) {
_doorTransform?.BeginAnimation( _doorTransform?.BeginAnimation(
RotateTransform.AngleProperty, RotateTransform.AngleProperty,
new DoubleAnimation { new DoubleAnimation {
@@ -39,17 +38,23 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
Duration = TimeSpan.FromSeconds(Math.Abs(_doorTransform.Angle - 0) / 60.0) Duration = TimeSpan.FromSeconds(Math.Abs(_doorTransform.Angle - 0) / 60.0)
}); });
} }
_animationActive = value;
} }
} }
public double Top => CenterY - HEIGHT / 2;
public double Right => CenterX + WIDTH / 2;
public double Bottom => CenterY + HEIGHT / 2;
public double Left => CenterX - WIDTH / 2;
protected Func<bool> CallbackActive; protected Func<bool> CallbackActive;
private bool _animationActive; private bool _animationActive;
private Shape? _inner; private Polygon? _inner;
private Shape? _outer; private Polygon? _outer;
private TextBlock? _text; private TextBlock? _text;
private Auger? _auger; private Auger? _auger;
private Shape? _door; private Line? _door;
private RotateTransform? _doorTransform; private RotateTransform? _doorTransform;
public EntryTrough(string label, double x, double y, Func<bool> cbActive) { public EntryTrough(string label, double x, double y, Func<bool> cbActive) {
@@ -62,42 +67,62 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
var top = CenterY - HEIGHT / 2;
var right = CenterX + WIDTH / 2;
var bottom = CenterY + HEIGHT / 2;
var left = CenterX - WIDTH / 2;
_outer = new Polygon() { _outer = new Polygon() {
Points = [new(left, top), new(right, top), new(right - 20, bottom), new(left, bottom), new(left, bottom - 20), new(left + 12.5, bottom - 20)],
Fill = Brushes.Black, Fill = Brushes.Black,
}; };
_inner = new Polygon() { _inner = new Polygon() {
Points = [new(left + 2, top), new(right - 2, top), new(right - 20 - 2, bottom - 2), new(left, bottom - 2), new(left, bottom - 18), new(left + 15.5, bottom - 18)],
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
}; };
_doorTransform = new RotateTransform(0, left, bottom - 24); _doorTransform = new RotateTransform(0);
_door = new Line() { _door = new Line() {
X1 = left, Y1 = bottom - 24, X2 = left, Y2 = bottom + 4,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 4,
RenderTransform = _doorTransform, RenderTransform = _doorTransform,
}; };
_text = new() { _text = new() {
Text = Label, Text = Label,
FontSize = 12,
Width = WIDTH,
TextAlignment = TextAlignment.Center, TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
}; };
_text.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_text.SetValue(Canvas.TopProperty, CenterY - 7.5 - 8);
canvas.Children.Add(_outer); canvas.Children.Add(_outer);
canvas.Children.Add(_inner); canvas.Children.Add(_inner);
canvas.Children.Add(_text); canvas.Children.Add(_text);
canvas.Children.Add(_door); canvas.Children.Add(_door);
_auger = new(CenterX - 11, CenterY + HEIGHT / 2 - 9, WIDTH - 25, 12); _auger = new(CenterX - 2.2, CenterY + HEIGHT / 2 - 1.8, WIDTH - 5, 2.4);
_auger.Draw(canvas); _auger.Draw(canvas);
} }
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_outer?.Points = [
new(Left * scale, Top * scale),
new(Right * scale, Top * scale),
new((Right - 4) * scale, Bottom * scale),
new(Left * scale, Bottom * scale),
new(Left * scale, (Bottom - 4) * scale),
new((Left + 2.5) * scale, (Bottom - 4) * scale),
];
_inner?.Points = [
new(Left * scale + border, Top * scale),
new(Right * scale - border, Top * scale),
new((Right - 4) * scale - border, Bottom * scale - border),
new(Left * scale, Bottom * scale - border),
new(Left * scale, (Bottom - 3.6) * scale),
new((Left + 3.1) * scale, (Bottom - 3.6) * scale),
];
_door?.X1 = Left * scale;
_door?.Y1 = (Bottom - 4.8) * scale;
_door?.X2 = Left * scale;
_door?.Y2 = (Bottom + 0.8) * scale;
_door?.StrokeThickness = border * 2;
_doorTransform?.CenterX = Left * scale;
_doorTransform?.CenterY = (Bottom - 4.8) * scale;
_text?.FontSize = 12;
_text?.Width = WIDTH * scale;
_text?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale);
_text?.SetValue(Canvas.TopProperty, (CenterY - 1.5) * scale - 8);
_auger?.Scale(scale);
}
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2; return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2;
} }
@@ -0,0 +1,29 @@
using System.Windows;
using System.Windows.Media;
namespace PamhagenSysCtrl.Helpers.Pipeline {
public class ExternalValve : Valve {
public override string Label { get; }
public ExternalValve(string label, double x, double y, int orientation, Func<bool?> cbOpen) :
base(x, y, orientation, cbOpen) {
Label = label;
}
public override void Update(bool isHovering, bool isPressed) {
_text?.Visibility = isHovering || (App.Plant?.ManualControlMode ?? false) ? Visibility.Visible : Visibility.Hidden;
_triangles?.Fill =
!IsOpen ? PamhagenBrushes.Red :
IsOpen ? PamhagenBrushes.Green :
Highlight ?? Brushes.DarkGray;
if (!IsClosed) {
_topLine?.Points[1] = _middlePoints.Open.Top;
_bottomLine?.Points[1] = _middlePoints.Open.Bottom;
} else {
_topLine?.Points[1] = _middlePoints.Closed.Top;
_bottomLine?.Points[1] = _middlePoints.Closed.Bottom;
}
}
}
}
+18 -21
View File
@@ -7,42 +7,40 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
internal sealed class FlowArrows { internal sealed class FlowArrows {
public const double SPEED = 20; public const double SPEED = 4;
public const double SPACING = 15; public const double SPACING = 3;
public const double LENGTH = 7; public const double LENGTH = 1.4;
public const double WIDTH = 7; public const double WIDTH = 1.4;
private static readonly Stopwatch Clock = Stopwatch.StartNew(); private static readonly Stopwatch Clock = Stopwatch.StartNew();
private double _scale = 5;
private readonly Canvas _layer; private readonly Canvas _layer;
private readonly Point[] _path; private readonly Point[] _path;
private Point[] _points = []; private Point[] _points = [];
private double _length;
private double _pathOffset; private double _pathOffset;
private bool _isActive; private bool _isActive;
public double Length => GetLength(_path[0], _path[1]) + GetLength(_path[1], _path[2]); public double Length { get; init; }
private FlowArrows(Canvas canvas, Point[] path) { private FlowArrows(Point[] path) {
_path = path; _path = path;
Length = _path.Length <= 1 ? 0 : Enumerable.Range(0, _path.Length - 1).Sum(i => GetLength(_path[i], _path[i + 1]));
_layer = new Canvas() { _layer = new Canvas() {
IsHitTestVisible = false, IsHitTestVisible = false,
Visibility = Visibility.Collapsed, Visibility = Visibility.Collapsed,
}; };
_layer.Unloaded += (_, _) => Stop(); _layer.Unloaded += (_, _) => Stop();
canvas.Children.Add(_layer);
} }
public static FlowArrows Create(Canvas canvas, Line first, Line second) { public static FlowArrows Create(Canvas canvas, IEnumerable<Point> points) {
return new FlowArrows(canvas, [ var arrows = new FlowArrows([.. points]);
new Point(first.X1, first.Y1), canvas.Children.Add(arrows._layer);
new Point(first.X2, first.Y2), return arrows;
new Point(second.X2, second.Y2),
]);
} }
public static FlowArrows Create(Canvas canvas, Polyline path) { public void Scale(double scale) {
return new FlowArrows(canvas, [.. path.Points]); _scale = scale;
} }
public void Update((bool TowardEnd, double PathOffset)? flow) { public void Update((bool TowardEnd, double PathOffset)? flow) {
@@ -52,7 +50,6 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
_points = flow.Value.TowardEnd ? _path : [.. _path.Reverse()]; _points = flow.Value.TowardEnd ? _path : [.. _path.Reverse()];
_length = GetLength(_points[0], _points[1]) + GetLength(_points[1], _points[2]);
_pathOffset = flow.Value.PathOffset; _pathOffset = flow.Value.PathOffset;
if (!_isActive) { if (!_isActive) {
CompositionTarget.Rendering += OnRendering; CompositionTarget.Rendering += OnRendering;
@@ -69,7 +66,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
private void Render() { private void Render() {
var distance = ((Clock.Elapsed.TotalSeconds * SPEED - _pathOffset) % SPACING + SPACING) % SPACING; var distance = ((Clock.Elapsed.TotalSeconds * SPEED - _pathOffset) % SPACING + SPACING) % SPACING;
var arrowIndex = 0; var arrowIndex = 0;
while (distance <= _length) { while (distance <= Length) {
var arrow = GetArrow(arrowIndex++); var arrow = GetArrow(arrowIndex++);
SetPosition(arrow, distance); SetPosition(arrow, distance);
arrow.Visibility = Visibility.Visible; arrow.Visibility = Visibility.Visible;
@@ -145,9 +142,9 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
direction = v2; direction = v2;
} }
double ux = direction.X; double ux = direction.X * _scale;
double uy = direction.Y; double uy = direction.Y * _scale;
(arrow.RenderTransform as MatrixTransform)?.Matrix = new Matrix(ux, uy, -uy, ux, position.X, position.Y); (arrow.RenderTransform as MatrixTransform)?.Matrix = new Matrix(ux, uy, -uy, ux, position.X * _scale, position.Y * _scale);
} }
private void Stop() { private void Stop() {
+89 -32
View File
@@ -1,5 +1,7 @@
using System.Windows.Controls; using System.Windows;
using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Shapes; using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
@@ -12,10 +14,10 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public Pipe CreatePipe(INode start, INode end, bool verticalFirst = false) { public Pipe CreatePipe(INode start, INode end, bool verticalFirst = false, string? label = null, double? labelOffsetX = null) {
Nodes.Add(start); Nodes.Add(start);
Nodes.Add(end); Nodes.Add(end);
var p = new Pipe(start, end, verticalFirst); var p = new Pipe(start, end, label, verticalFirst, default, labelOffsetX);
start.Outputs.Add(p); start.Outputs.Add(p);
end.Inputs.Add(p); end.Inputs.Add(p);
Edges.Add(p); Edges.Add(p);
@@ -25,7 +27,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public Pipe CreateTwoWayPipe(INode n1, INode n2, bool verticalFirst = false) { public Pipe CreateTwoWayPipe(INode n1, INode n2, bool verticalFirst = false) {
Nodes.Add(n1); Nodes.Add(n1);
Nodes.Add(n2); Nodes.Add(n2);
var p = new Pipe(n1, n2, verticalFirst, twoWay: true); var p = new Pipe(n1, n2, default, verticalFirst, twoWay: true);
n1.Inputs.Add(p); n1.Inputs.Add(p);
n1.Outputs.Add(p); n1.Outputs.Add(p);
n2.Inputs.Add(p); n2.Inputs.Add(p);
@@ -34,10 +36,10 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
return p; return p;
} }
public Inlet CreateInlet(INode start, ISink end) { public Inlet CreateInlet(INode start, ISink end, bool sideways = false) {
Nodes.Add(start); Nodes.Add(start);
Nodes.Add(end); Nodes.Add(end);
var p = new Inlet(start, end, start.CenterX > end.CenterX); var p = new Inlet(start, end, sideways);
start.Outputs.Add(p); start.Outputs.Add(p);
end.Inputs.Add(p); end.Inputs.Add(p);
Edges.Add(p); Edges.Add(p);
@@ -54,64 +56,119 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
return p; return p;
} }
public Path? GetPath(IEnumerable<INode> points, Func<INode, bool>? valid = null) { public Path? GetPath(IEnumerable<INode> points, Func<INode, bool>? valid = null, Func<Path, int>? cost = null) {
if (points.Count() < 2) return null; if (points.Count() < 2) return null;
var start = points.First(); var start = points.First();
var path = new Path(start, []); List<Path> paths = [new(start, [])];
foreach (var end in points.Skip(1)) { foreach (var end in points.Skip(1)) {
if (valid != null && !valid(start)) { if (valid != null && start != points.First() && !valid(start))
return null; return null;
} else if (GetPath(start, end, valid, visited: [.. path.Hops.Select(h => h.Node)]) is not Path p) {
return null; var newPaths = new List<Path>();
} else { foreach (var path in paths) {
path.Hops = [.. path.Hops, .. p.Hops]; foreach (var subPath in GetPaths(start, end, visited: [.. path.Hops.Select(h => h.Node)], valid)) {
start = end; newPaths.Add(new(path.Start, [.. path.Hops, .. subPath.Hops]));
}
} }
if (newPaths.Count == 0)
return null;
paths = newPaths;
start = end;
}
if (paths.Count == 0) {
return null;
} else {
cost ??= p => 0;
return paths.OrderBy(cost).First();
} }
return path;
} }
public Path? GetPath(INode start, INode end, Func<INode, bool>? valid = null, HashSet<INode>? visited = null) { public IEnumerable<Path> GetPaths(INode start, INode end, HashSet<INode>? visited = null, Func<INode, bool>? valid = null) {
if (!Nodes.Contains(start) || !Nodes.Contains(end)) throw new ArgumentException("Start/end node not contained in graph"); if (!Nodes.Contains(start) || !Nodes.Contains(end)) throw new ArgumentException("Start/end node not contained in graph");
valid ??= a => true; valid ??= a => true;
visited = [.. visited ?? [], start]; visited = [.. visited ?? [], start];
List<(IEdge, INode)> hops = []; List<(IEdge, INode)> hops = [];
Path? path = null;
foreach (var o in start.Outputs) { foreach (var o in start.Outputs) {
var e = o.IsTwoWay && o.End == start ? o.Start : o.End; var e = o.IsTwoWay && o.End == start ? o.Start : o.End;
if (e == end) { if (e == end) {
return new(start, [(o, e)]); yield return new(start, [(o, e)]);
continue;
} else if (!valid(e) || visited.Contains(e)) { } else if (!valid(e) || visited.Contains(e)) {
continue; continue;
} }
var p = GetPath(e, end, valid, visited); foreach (var p in GetPaths(e, end, visited, valid)) {
if (p != null) { yield return new(start, [(o, e), .. p.Hops]);
var p2 = new Path(start, [(o, e), .. p.Value.Hops]);
if (path == null || p2.Bends < path.Value.Bends || (p2.Bends == path.Value.Bends && p2.Length < path.Value.Length)) {
path = p2;
}
} }
} }
return path != null ? new(start, path.Value.Hops) : null;
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas, double scale) {
canvas.Children.Add(new Rectangle() { var bg = new Rectangle() {
Fill = Brushes.White, Fill = Brushes.White,
Width = 5000, Width = 8000,
Height = 5000, Height = 8000,
}); };
SetPos(bg, -4000, -4000, 1);
canvas.Children.Add(bg);
foreach (var e in Edges) { foreach (var e in Edges) {
e.Draw(canvas); e.Draw(canvas);
e.Scale(scale);
} }
foreach (var n in Nodes) { foreach (var n in Nodes) {
n.Draw(canvas); n.Draw(canvas);
n.Scale(scale);
} }
} }
public ISet<INode> GetHover(double x, double y) { public void Scale(double scale) {
return Nodes.Where(n => n.IsInside(x, y)).ToHashSet(); foreach (var e in Edges) {
e.Scale(scale);
}
foreach (var n in Nodes) {
n.Scale(scale);
}
}
public static double ToPx(double v, double scale) {
return Math.Round(v * scale);
}
public static Point ToPx(double x, double y, double scale, double dxpx = 0, double dypx = 0, bool round = true) {
var p = new Point(x * scale + dxpx, y * scale + dypx);
return round ? new(Math.Round(p.X), Math.Round(p.Y)) : p;
}
public static Point ToPx(Point p, double scale) {
return ToPx(p.X, p.Y, scale);
}
public static double ToBorder(double scale) {
return scale > 10 ? 4 : scale > 7.5 ? 3 : scale > 4 ? 2 : 1;
}
public static void SetPos(FrameworkElement? s, double x, double y, double scale, double dxpx = 0, double dypx = 0) {
s?.SetValue(Canvas.LeftProperty, ToPx(x, scale) + dxpx);
s?.SetValue(Canvas.TopProperty, ToPx(y, scale) + dypx);
}
public static void SetWH(Shape? s, double w, double h, double scale) {
s?.Width = ToPx(w, scale);
s?.Height = ToPx(h, scale);
}
public static void SetCenter(Shape? s, double cx, double cy, double w, double h, double scale) {
Graph.SetWH(s, w, h, scale);
Graph.SetPos(s, cx - w / 2, cy - h / 2, scale);
}
public ISet<INode> GetHover(Point? p, double scale) {
return p is Point v ? GetHover(v.X, v.Y, scale) : new HashSet<INode>();
}
public ISet<INode> GetHover(double x, double y, double scale) {
return Nodes.Where(n => n.IsInside(x / scale, y / scale)).ToHashSet();
} }
public void Update(ISet<INode> hovering, bool isPressed) { public void Update(ISet<INode> hovering, bool isPressed) {
@@ -1,4 +1,4 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Shapes; using System.Windows.Shapes;
@@ -6,8 +6,8 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class HeatExchanger : INode { public class HeatExchanger : INode {
public const double WIDTH = 30; public const double WIDTH = 6;
public const double HEIGHT = 120; public const double HEIGHT = 24;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -28,28 +28,30 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_rect = new Rectangle() { _rect = new Rectangle() {
Width = WIDTH,
Height = HEIGHT,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
}; };
_text = new() { _text = new() {
Text = Label, Text = Label,
FontSize = 12,
Width = HEIGHT,
TextAlignment = TextAlignment.Center, TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
RenderTransform = new RotateTransform(270), RenderTransform = new RotateTransform(270),
SnapsToDevicePixels = true,
}; };
_rect.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_rect.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2);
_text.SetValue(Canvas.LeftProperty, CenterX - 9);
_text.SetValue(Canvas.TopProperty, CenterY + HEIGHT / 2);
canvas.Children.Add(_rect); canvas.Children.Add(_rect);
canvas.Children.Add(_text); canvas.Children.Add(_text);
} }
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
Graph.SetWH(_rect, WIDTH, HEIGHT, scale);
Graph.SetPos(_rect, CenterX - WIDTH / 2, CenterY - HEIGHT / 2, scale);
_rect?.StrokeThickness = border;
_text?.FontSize = 12;
_text?.Width = Math.Round(HEIGHT * scale);
Graph.SetPos(_text, CenterX, CenterY + HEIGHT / 2, scale, dxpx: -9);
}
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2; return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2;
} }
+3 -1
View File
@@ -1,4 +1,4 @@
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
@@ -6,6 +6,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public INode Start { get; } public INode Start { get; }
public INode End { get; } public INode End { get; }
public string? Label { get; }
public bool IsTwoWay { get; } public bool IsTwoWay { get; }
public bool Orientation { get; } public bool Orientation { get; }
public Brush? Highlight { get; set; } public Brush? Highlight { get; set; }
@@ -13,6 +14,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public double FlowLength { get; } public double FlowLength { get; }
public void Draw(Canvas canvas); public void Draw(Canvas canvas);
public void Scale(double scale);
public void Update(); public void Update();
} }
} }
+2 -2
View File
@@ -1,4 +1,4 @@
using System.Windows.Controls; using System.Windows.Controls;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public interface INode { public interface INode {
@@ -10,8 +10,8 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public ISet<IEdge> Outputs { get; } public ISet<IEdge> Outputs { get; }
public void Draw(Canvas canvas); public void Draw(Canvas canvas);
public void Scale(double scale);
public void Update(bool isHovering, bool isPressed); public void Update(bool isHovering, bool isPressed);
public bool IsInside(double x, double y); public bool IsInside(double x, double y);
} }
} }
+2 -1
View File
@@ -1,7 +1,8 @@
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public interface ISink : INode { public interface ISink : INode {
public double TopY { get; } public double TopY { get; }
public bool IsFull { get; } public bool IsFull { get; }
public bool HasFillingClearance { get; }
public int? CapacityLiters { get; } public int? CapacityLiters { get; }
} }
} }
+1 -1
View File
@@ -1,4 +1,4 @@
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public interface ISource : INode { public interface ISource : INode {
public double BottomY { get; } public double BottomY { get; }
public bool IsFilled { get; } public bool IsFilled { get; }
+28 -50
View File
@@ -1,4 +1,4 @@
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Shapes; using System.Windows.Shapes;
@@ -8,16 +8,22 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public INode Start { get; set; } public INode Start { get; set; }
public INode End => Sink; public INode End => Sink;
public ISink Sink { get; set; } public ISink Sink { get; set; }
public string? Label => null;
public bool IsTwoWay => false; public bool IsTwoWay => false;
public bool Orientation { get; set; } public bool Orientation { get; set; }
public Brush? Highlight { get; set; } public Brush? Highlight { get; set; }
public (bool TowardEnd, double PathOffset)? Flow { get; set; } public (bool TowardEnd, double PathOffset)? Flow { get; set; }
public double FlowLength => _flowArrows?.Length ?? 0; public double FlowLength => _flowArrows?.Length ?? 0;
private Line? _outer1; public double X1 => Start.CenterX;
private Line? _outer2; public double Y1 => Start.CenterY;
private Line? _inner1; public double X2 => Start.CenterX;
private Line? _inner2; public double Y2 => Orientation ? Sink.CenterY - 2 : Sink.TopY - 2;
public double X3 => Orientation ? Sink.CenterX : Start.CenterX + (Start.CenterX == End.CenterX ? 0 : Start.CenterX > End.CenterX ? -4 : 4);
public double Y3 => Orientation ? Sink.CenterY - 2 : Sink.TopY + 4;
private Polyline? _outer;
private Polyline? _inner;
private FlowArrows? _flowArrows; private FlowArrows? _flowArrows;
public Inlet(INode start, ISink end, bool orientation = false) { public Inlet(INode start, ISink end, bool orientation = false) {
@@ -27,62 +33,34 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
var x1 = Start.CenterX; _outer = new Polyline() {
var y1 = Start.CenterY;
var x2 = Start.CenterX;
var y2 = Sink.TopY - 10;
var x3 = Start.CenterX + (Orientation ? -20 : 20);
var y3 = Sink.TopY + 20;
_outer1 = new Line() {
X1 = x1,
Y1 = y1,
X2 = x2,
Y2 = y2,
StrokeThickness = 10,
Stroke = Brushes.Black,
StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round,
};
_inner1 = new Line() {
X1 = x1,
Y1 = y1,
X2 = x2,
Y2 = y2,
StrokeThickness = 6,
Stroke = Brushes.DarkGray,
StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round,
};
_outer2 = new Line() {
X1 = x2,
Y1 = y2,
X2 = x3,
Y2 = y3,
StrokeThickness = 10,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeStartLineCap = PenLineCap.Round, StrokeStartLineCap = PenLineCap.Round,
StrokeLineJoin = PenLineJoin.Round,
StrokeEndLineCap = PenLineCap.Flat, StrokeEndLineCap = PenLineCap.Flat,
}; };
_inner2 = new Line() { _inner = new Polyline() {
X1 = x2,
Y1 = y2,
X2 = x3,
Y2 = y3,
StrokeThickness = 6,
Stroke = Brushes.DarkGray, Stroke = Brushes.DarkGray,
StrokeStartLineCap = PenLineCap.Round, StrokeStartLineCap = PenLineCap.Round,
StrokeLineJoin = PenLineJoin.Round,
StrokeEndLineCap = PenLineCap.Flat, StrokeEndLineCap = PenLineCap.Flat,
}; };
canvas.Children.Add(_outer1); canvas.Children.Add(_outer);
canvas.Children.Add(_outer2); canvas.Children.Add(_inner);
canvas.Children.Add(_inner1); _flowArrows = FlowArrows.Create(canvas, [new(X1, Y1), new(X2, Y2), new(X3, Y3)]);
canvas.Children.Add(_inner2); }
_flowArrows = FlowArrows.Create(canvas, _inner1!, _inner2!);
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_outer?.Points = [Graph.ToPx(X1, Y1, scale), Graph.ToPx(X2, Y2, scale), Graph.ToPx(X3, Y3, scale)];
_inner?.Points = [Graph.ToPx(X1, Y1, scale), Graph.ToPx(X2, Y2, scale), Graph.ToPx(X3, Y3, scale)];
_inner?.StrokeThickness = Math.Round(1.2 * scale / 2) * 2;
_outer?.StrokeThickness = Math.Round(1.2 * scale / 2) * 2 + border * 2;
_flowArrows?.Scale(scale);
} }
public void Update() { public void Update() {
_inner1?.Stroke = Highlight ?? Brushes.DarkGray; _inner?.Stroke = Highlight ?? Brushes.DarkGray;
_inner2?.Stroke = Highlight ?? Brushes.DarkGray;
_flowArrows?.Update(Flow); _flowArrows?.Update(Flow);
} }
} }
+31 -53
View File
@@ -1,27 +1,33 @@
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Shapes; using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Outlet : IEdge { public class Outlet : IEdge {
public const double POINT = 16; public const double POINT = 3.2;
public INode Start => Source; public INode Start => Source;
public INode End { get; set; } public INode End { get; set; }
public ISource Source { get; set; } public ISource Source { get; set; }
public string? Label => null;
public bool IsTwoWay => false; public bool IsTwoWay => false;
public bool Orientation { get; set; } public bool Orientation { get; set; }
public Brush? Highlight { get; set; } public Brush? Highlight { get; set; }
public (bool TowardEnd, double PathOffset)? Flow { get; set; } public (bool TowardEnd, double PathOffset)? Flow { get; set; }
public double FlowLength => _flowArrows?.Length ?? 0; public double FlowLength => _flowArrows?.Length ?? 0;
public double X1 => Start.CenterX;
public double Y1 => Source.BottomY;
public double X2 => Orientation ? Start.CenterX : End.CenterX;
public double Y2 => Orientation ? End.CenterY : Start.CenterY;
public double X3 => End.CenterX;
public double Y3 => End.CenterY;
private Polygon? _hopper; private Polygon? _hopper;
private FlowArrows? _flowArrows; private FlowArrows? _flowArrows;
private Line? _outer1; private Polyline? _outer;
private Line? _outer2; private Polyline? _inner;
private Line? _inner1;
private Line? _inner2;
public Outlet(ISource start, INode end) { public Outlet(ISource start, INode end) {
Source = start; Source = start;
@@ -30,69 +36,41 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
var x1 = Start.CenterX ;
var y1 = Source.BottomY;
var x2 = Orientation ? Start.CenterX : End.CenterX;
var y2 = Orientation ? End.CenterY : Start.CenterY;
var x3 = End.CenterX;
var y3 = End.CenterY;
_hopper = new Polygon() { _hopper = new Polygon() {
Points = [new(x1 - POINT, y1 - 2), new(x1 + POINT, y1 - 2), new(x1, y1 + POINT)],
Fill = Brushes.DarkGray, Fill = Brushes.DarkGray,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
}; };
_outer1 = new Line() { _outer = new Polyline() {
X1 = x1,
Y1 = y1,
X2 = x2,
Y2 = y2,
StrokeThickness = 10,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeStartLineCap = PenLineCap.Round, StrokeStartLineCap = PenLineCap.Round,
StrokeLineJoin = PenLineJoin.Round,
StrokeEndLineCap = PenLineCap.Round, StrokeEndLineCap = PenLineCap.Round,
}; };
_inner1 = new Line() { _inner = new Polyline() {
X1 = x1,
Y1 = y1,
X2 = x2,
Y2 = y2,
StrokeThickness = 6,
Stroke = Brushes.DarkGray, Stroke = Brushes.DarkGray,
StrokeStartLineCap = PenLineCap.Round, StrokeStartLineCap = PenLineCap.Round,
StrokeLineJoin = PenLineJoin.Round,
StrokeEndLineCap = PenLineCap.Round, StrokeEndLineCap = PenLineCap.Round,
}; };
_outer2 = new Line() { canvas.Children.Add(_outer);
X1 = x2,
Y1 = y2,
X2 = x3,
Y2 = y3,
StrokeThickness = 10,
Stroke = Brushes.Black,
StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round,
};
_inner2 = new Line() {
X1 = x2,
Y1 = y2,
X2 = x3,
Y2 = y3,
StrokeThickness = 6,
Stroke = Brushes.DarkGray,
StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round,
};
canvas.Children.Add(_outer1);
canvas.Children.Add(_outer2);
canvas.Children.Add(_hopper); canvas.Children.Add(_hopper);
canvas.Children.Add(_inner1); canvas.Children.Add(_inner);
canvas.Children.Add(_inner2); _flowArrows = FlowArrows.Create(canvas, [new(X1, Y1), new(X2, Y2), new(X3, Y3)]);
_flowArrows = FlowArrows.Create(canvas, _inner1!, _inner2!); }
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_hopper?.Points = [Graph.ToPx(X1 - POINT, Y1, scale, dypx: -border * 2), Graph.ToPx(X1 + POINT, Y1, scale, dypx: -border * 2), Graph.ToPx(X1, Y1 + POINT, scale)];
_hopper?.StrokeThickness = border;
_outer?.Points = [Graph.ToPx(X1, Y1, scale), Graph.ToPx(X2, Y2, scale), Graph.ToPx(X3, Y3, scale)];
_inner?.Points = [Graph.ToPx(X1, Y1, scale), Graph.ToPx(X2, Y2, scale), Graph.ToPx(X3, Y3, scale)];
_inner?.StrokeThickness = Math.Round(1.2 * scale / 2) * 2;
_outer?.StrokeThickness = Math.Round(1.2 * scale / 2) * 2 + border * 2;
_flowArrows?.Scale(scale);
} }
public void Update() { public void Update() {
_inner1?.Stroke = Highlight ?? Brushes.DarkGray; _inner?.Stroke = Highlight ?? Brushes.DarkGray;
_inner2?.Stroke = Highlight ?? Brushes.DarkGray;
_hopper?.Fill = Highlight ?? Brushes.DarkGray; _hopper?.Fill = Highlight ?? Brushes.DarkGray;
_flowArrows?.Update(Flow); _flowArrows?.Update(Flow);
} }
+39 -5
View File
@@ -1,9 +1,43 @@
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public record struct Path( public record struct Path(
INode Start, INode Start,
IEnumerable<(IEdge Edge, INode Node)> Hops) (IEdge Edge, INode Node)[] Hops
{ ) {
public readonly double Bends => Hops.Sum(h => (h.Node is not Valve && h.Node is not PipeJoin ? 1 : 0) + (h.Edge.Start.CenterX != h.Edge.End.CenterX && h.Edge.Start.CenterY != h.Edge.End.CenterY ? 1 : 0)); public readonly int Bends => Hops.Sum(h => (h.Node is not Valve && h.Node is not PipeJoin ? 1 : 0) + (h.Edge.Start.CenterX != h.Edge.End.CenterX && h.Edge.Start.CenterY != h.Edge.End.CenterY ? 1 : 0));
public readonly double Length => Hops.Sum(h => Math.Abs(h.Edge.Start.CenterX - h.Edge.End.CenterX) + Math.Abs(h.Edge.Start.CenterY - h.Edge.End.CenterY)); public readonly double Length => Hops.Sum(h => Math.Abs(h.Edge.Start.CenterX - h.Edge.End.CenterX) + Math.Abs(h.Edge.Start.CenterY - h.Edge.End.CenterY));
public readonly INode End => Hops.Last().Node;
public readonly IEnumerable<INode> Nodes => [Start, .. Hops.Select(h => h.Node)];
public readonly bool Contains(INode node) => Nodes.Any(h => h == node);
public readonly bool Matches(INode start, INode end) => Matches(start, null, end);
public readonly bool Matches(INode start, INode? intermediate, INode end) => Start == start && End == end && (intermediate == null || Nodes.Any(n => n == intermediate));
public readonly IEnumerable<Pump> Pumps => Nodes.Select(n => n as Pump).Where(p => p != null).Cast<Pump>();
public readonly IEnumerable<Valve> Valves => Nodes.Select(n => n as Valve).Where(v => v != null).Cast<Valve>();
private readonly IEnumerable<ControlledValve> InnerAndBorderValves => Nodes
.SelectMany(node => PamhagenGraph.TraverseSubgraph(node, (n) => n is ControlledValve || n is ISink || n is Pump))
.Select(e => e.End as ControlledValve)
.Where(v => v != null)
.Cast<ControlledValve>()
.Distinct();
public readonly IEnumerable<ControlledValve> InnerValves => Nodes.Select(n => n as ControlledValve).Where(v => v != null).Cast<ControlledValve>();
public readonly IEnumerable<ControlledValve> BorderValves => InnerAndBorderValves.Except(InnerValves);
public readonly bool HasActivePump => Nodes.Any(n => n is Pump p && p.IsActive);
public readonly bool IsReady => InnerValves.All(v => v.IsOpen && v.WantOpen && v.IsLocked) && BorderValves.All(v => v.IsClosed && !v.WantOpen && v.IsLocked);
public readonly void LockValves() {
foreach (var v in InnerAndBorderValves) {
v.LockedForPaths.Add(this);
}
}
public readonly void UnlockValves() {
foreach (var v in InnerAndBorderValves) {
v.LockedForPaths.Remove(this);
}
}
} }
} }
+38 -13
View File
@@ -1,4 +1,5 @@
using System.Windows.Controls; using System.Windows;
using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Shapes; using System.Windows.Shapes;
@@ -7,41 +8,43 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public INode Start { get; set; } public INode Start { get; set; }
public INode End { get; set; } public INode End { get; set; }
public string? Label { get; set; }
public bool IsTwoWay { get; set; } public bool IsTwoWay { get; set; }
public bool Orientation { get; set; } public bool Orientation { get; set; }
public Brush? Highlight { get; set; } public Brush? Highlight { get; set; }
public (bool TowardEnd, double PathOffset)? Flow { get; set; } public (bool TowardEnd, double PathOffset)? Flow { get; set; }
public double FlowLength => _flowArrows?.Length ?? 0; public double FlowLength => _flowArrows?.Length ?? 0;
public double X1 => Start.CenterX;
public double Y1 => Start.CenterY;
public double X2 => Orientation ? Start.CenterX : End.CenterX;
public double Y2 => Orientation ? End.CenterY : Start.CenterY;
public double X3 => End.CenterX;
public double Y3 => End.CenterY;
private Polyline? _outer; private Polyline? _outer;
private Polyline? _inner; private Polyline? _inner;
private TextBlock? _text;
private FlowArrows? _flowArrows; private FlowArrows? _flowArrows;
private double? _textOffsetX;
public Pipe(INode start, INode end, bool orientation = false, bool twoWay = false) { public Pipe(INode start, INode end, string? label = null, bool orientation = false, bool twoWay = false, double? textOffsetX = null) {
Start = start; Start = start;
End = end; End = end;
Label = label;
_textOffsetX = textOffsetX;
Orientation = orientation; Orientation = orientation;
IsTwoWay = twoWay; IsTwoWay = twoWay;
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
var x1 = Start.CenterX;
var y1 = Start.CenterY;
var x2 = Orientation ? Start.CenterX : End.CenterX;
var y2 = Orientation ? End.CenterY : Start.CenterY;
var x3 = End.CenterX;
var y3 = End.CenterY;
_outer = new Polyline() { _outer = new Polyline() {
Points = [new(x1, y1), new(x2, y2), new(x3, y3)],
StrokeThickness = 10,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeStartLineCap = PenLineCap.Round, StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round, StrokeEndLineCap = PenLineCap.Round,
StrokeLineJoin = PenLineJoin.Round, StrokeLineJoin = PenLineJoin.Round,
}; };
_inner = new Polyline() { _inner = new Polyline() {
Points = [new(x1, y1), new(x2, y2), new(x3, y3)],
StrokeThickness = 6,
Stroke = Brushes.DarkGray, Stroke = Brushes.DarkGray,
StrokeStartLineCap = PenLineCap.Round, StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round, StrokeEndLineCap = PenLineCap.Round,
@@ -49,7 +52,29 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
}; };
canvas.Children.Add(_outer); canvas.Children.Add(_outer);
canvas.Children.Add(_inner); canvas.Children.Add(_inner);
_flowArrows = FlowArrows.Create(canvas, _inner); if (Label != null) {
_text = new() {
Text = Label,
TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Bottom,
HorizontalAlignment = HorizontalAlignment.Center,
SnapsToDevicePixels = true,
};
canvas.Children.Add(_text);
}
_flowArrows = FlowArrows.Create(canvas, [new(X1, Y1), new(X2, Y2), new(X3, Y3)]);
}
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_outer?.Points = [Graph.ToPx(X1, Y1, scale), Graph.ToPx(X2, Y2, scale), Graph.ToPx(X3, Y3, scale)];
_inner?.Points = [Graph.ToPx(X1, Y1, scale), Graph.ToPx(X2, Y2, scale), Graph.ToPx(X3, Y3, scale)];
_inner?.StrokeThickness = Math.Round(1.2 * scale / 2) * 2;
_outer?.StrokeThickness = Math.Round(1.2 * scale / 2) * 2 + border * 2;
_text?.FontSize = 12;
_text?.Width = 10 * scale;
Graph.SetPos(_text, _textOffsetX != null ? (Orientation ? X2 : X1) + _textOffsetX.Value : (Orientation ? (X2 + X3) / 2 : (X1 + X2) / 2) - 5, Y2 - 1.4, scale, dypx: -16);
_flowArrows?.Scale(scale);
} }
public void Update() { public void Update() {
+24 -21
View File
@@ -1,4 +1,4 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Shapes; using System.Windows.Shapes;
@@ -6,8 +6,8 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class PipeJoin : INode { public class PipeJoin : INode {
public const double SIZE = 18; public const double SIZE = 3.6;
public const double POINT = 4; public const double POINT = 0.8;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -16,7 +16,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public ISet<IEdge> Inputs { get; init; } public ISet<IEdge> Inputs { get; init; }
public ISet<IEdge> Outputs { get; init; } public ISet<IEdge> Outputs { get; init; }
private Shape? _rect; private Polygon? _rect;
public PipeJoin(double x, double y) { public PipeJoin(double x, double y) {
CenterX = x; CenterX = x;
@@ -26,14 +26,22 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
var it = Inputs.Any(e => (e.Start.CenterY < CenterY && (!e.Orientation || e.Start.CenterX == CenterX)) || (e.IsTwoWay && e.End.CenterY < CenterY && (!e.Orientation || e.End.CenterX == CenterX))); _rect = new Polygon() {
var ot = Outputs.Any(e => (e.End.CenterY < CenterY && ( e.Orientation || e.End.CenterX == CenterX)) || (e.IsTwoWay && e.Start.CenterY < CenterY && ( e.Orientation || e.Start.CenterX == CenterX))); Fill = Brushes.Black,
var ir = Inputs.Any(e => (e.Start.CenterX > CenterX && ( e.Orientation || e.Start.CenterY == CenterY)) || (e.IsTwoWay && e.End.CenterX > CenterX && ( e.Orientation || e.End.CenterY == CenterY))); Stroke = Brushes.Black,
var or = Outputs.Any(e => (e.End.CenterX > CenterX && (!e.Orientation || e.End.CenterY == CenterY)) || (e.IsTwoWay && e.Start.CenterX > CenterX && (!e.Orientation || e.Start.CenterY == CenterY))); };
var ib = Inputs.Any(e => (e.Start.CenterY > CenterY && (!e.Orientation || e.Start.CenterX == CenterX)) || (e.IsTwoWay && e.End.CenterY > CenterY && (!e.Orientation || e.End.CenterX == CenterX))); canvas.Children.Add(_rect);
var ob = Outputs.Any(e => (e.End.CenterY > CenterY && ( e.Orientation || e.End.CenterX == CenterX)) || (e.IsTwoWay && e.Start.CenterY > CenterY && ( e.Orientation || e.Start.CenterX == CenterX))); }
var il = Inputs.Any(e => (e.Start.CenterX < CenterX && ( e.Orientation || e.Start.CenterY == CenterY)) || (e.IsTwoWay && e.End.CenterX < CenterX && ( e.Orientation || e.End.CenterY == CenterY)));
var ol = Outputs.Any(e => (e.End.CenterX < CenterX && (!e.Orientation || e.End.CenterY == CenterY)) || (e.IsTwoWay && e.Start.CenterX < CenterX && (!e.Orientation || e.Start.CenterY == CenterY))); public void Scale(double scale) {
var it = Inputs.Any(e => (e.Start.CenterY < CenterY && (!e.Orientation || e.Start.CenterX == CenterX)) || (e.IsTwoWay && e.End.CenterY < CenterY && (!e.Orientation || e.End.CenterX == CenterX)));
var ot = Outputs.Any(e => (e.End.CenterY < CenterY && (e.Orientation || e.End.CenterX == CenterX)) || (e.IsTwoWay && e.Start.CenterY < CenterY && (e.Orientation || e.Start.CenterX == CenterX)));
var ir = Inputs.Any(e => (e.Start.CenterX > CenterX && (e.Orientation || e.Start.CenterY == CenterY)) || (e.IsTwoWay && e.End.CenterX > CenterX && (e.Orientation || e.End.CenterY == CenterY)));
var or = Outputs.Any(e => (e.End.CenterX > CenterX && (!e.Orientation || e.End.CenterY == CenterY)) || (e.IsTwoWay && e.Start.CenterX > CenterX && (!e.Orientation || e.Start.CenterY == CenterY)));
var ib = Inputs.Any(e => (e.Start.CenterY > CenterY && (!e.Orientation || e.Start.CenterX == CenterX)) || (e.IsTwoWay && e.End.CenterY > CenterY && (!e.Orientation || e.End.CenterX == CenterX)));
var ob = Outputs.Any(e => (e.End.CenterY > CenterY && (e.Orientation || e.End.CenterX == CenterX)) || (e.IsTwoWay && e.Start.CenterY > CenterY && (e.Orientation || e.Start.CenterX == CenterX)));
var il = Inputs.Any(e => (e.Start.CenterX < CenterX && (e.Orientation || e.Start.CenterY == CenterY)) || (e.IsTwoWay && e.End.CenterX < CenterX && (e.Orientation || e.End.CenterY == CenterY)));
var ol = Outputs.Any(e => (e.End.CenterX < CenterX && (!e.Orientation || e.End.CenterY == CenterY)) || (e.IsTwoWay && e.Start.CenterX < CenterX && (!e.Orientation || e.Start.CenterY == CenterY)));
var points = new List<Point> { var points = new List<Point> {
new(0, 0) new(0, 0)
@@ -70,15 +78,10 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
points.AddRange([new(0, SIZE / 2 + POINT), new(POINT, SIZE / 2), new(0, SIZE / 2 - POINT)]); points.AddRange([new(0, SIZE / 2 + POINT), new(POINT, SIZE / 2), new(0, SIZE / 2 - POINT)]);
} }
_rect = new Polygon() { _rect?.Points = [.. points.Select(p => new Point(p.X * scale, p.Y * scale))];
Points = [..points], _rect?.StrokeThickness = 2;
Fill = Brushes.Black, _rect?.SetValue(Canvas.LeftProperty, (CenterX - SIZE / 2) * scale);
Stroke = Brushes.Black, _rect?.SetValue(Canvas.TopProperty, (CenterY - SIZE / 2) * scale);
StrokeThickness = 2,
};
_rect.SetValue(Canvas.LeftProperty, CenterX - SIZE / 2);
_rect.SetValue(Canvas.TopProperty, CenterY - SIZE / 2);
canvas.Children.Add(_rect);
} }
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
+25 -22
View File
@@ -1,13 +1,12 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Shapes; using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Press : INode, ISink { public class Press : INode, ISink {
public const double DIAMETER = 120; public const double DIAMETER = 24;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -19,60 +18,64 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public double TopY => CenterY - DIAMETER / 2; public double TopY => CenterY - DIAMETER / 2;
public FillState FillState => CallbackFillState(); public FillState FillState => CallbackFillState();
public bool IsFull => FillState == FillState.Full; public bool IsFull => FillState == FillState.Full;
public bool HasFillingClearance => CallbackFillingClearance();
public int? CapacityLiters { get; set; } public int? CapacityLiters { get; set; }
protected Func<FillState> CallbackFillState; protected Func<FillState> CallbackFillState;
protected Func<bool> CallbackFillingClearance;
private Shape? _frame; private Polygon? _frame;
private Shape? _circle; private Ellipse? _circle;
private TextBlock? _text; private TextBlock? _text;
public Press(string label, double x, double y, int capacityLiters, Func<FillState> cbFillState) { public Press(string label, double x, double y, int capacityLiters, Func<FillState> cbFillState, Func<bool> cbFillingClearance) {
CenterX = x; CenterX = x;
CenterY = y; CenterY = y;
Label = label; Label = label;
CapacityLiters = capacityLiters; CapacityLiters = capacityLiters;
CallbackFillState = cbFillState; CallbackFillState = cbFillState;
CallbackFillingClearance = cbFillingClearance;
Inputs = new HashSet<IEdge>(); Inputs = new HashSet<IEdge>();
Outputs = new HashSet<IEdge>(); Outputs = new HashSet<IEdge>();
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_circle = new Ellipse() { _circle = new Ellipse() {
Width = DIAMETER,
Height = DIAMETER,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
}; };
_frame = new Polygon() { _frame = new Polygon() {
Points = [
new(CenterX - DIAMETER / 2 - 10, CenterY), new(CenterX + DIAMETER / 2 + 10, CenterY),
new(CenterX + DIAMETER / 2 + 10, CenterY + DIAMETER / 2 + 30), new(CenterX + DIAMETER / 2 - 20, CenterY + DIAMETER / 2 + 30),
new(CenterX + DIAMETER / 2 - 20, CenterY + DIAMETER / 2 + 10), new(CenterX - DIAMETER / 2 + 20, CenterY + DIAMETER / 2 + 10),
new(CenterX - DIAMETER / 2 + 20, CenterY + DIAMETER / 2 + 30), new(CenterX - DIAMETER / 2 - 10, CenterY + DIAMETER / 2 + 30)
],
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
}; };
_text = new() { _text = new() {
Text = CapacityLiters.HasValue ? $"{Label}\n{CapacityLiters:N0}" : Label, Text = CapacityLiters.HasValue ? $"{Label}\n{CapacityLiters:N0}" : Label,
FontSize = 14,
Width = DIAMETER,
TextAlignment = TextAlignment.Center, TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
}; };
_circle.SetValue(Canvas.LeftProperty, CenterX - DIAMETER / 2);
_circle.SetValue(Canvas.TopProperty, CenterY - DIAMETER / 2);
_text.SetValue(Canvas.LeftProperty, CenterX - DIAMETER / 2);
_text.SetValue(Canvas.TopProperty, CenterY - (CapacityLiters.HasValue ? 20 : 10));
canvas.Children.Add(_frame); canvas.Children.Add(_frame);
canvas.Children.Add(_circle); canvas.Children.Add(_circle);
canvas.Children.Add(_text); canvas.Children.Add(_text);
} }
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_circle?.StrokeThickness = border;
Graph.SetCenter(_circle, CenterX, CenterY, DIAMETER, DIAMETER, scale);
_frame?.Points = [
Graph.ToPx(CenterX - DIAMETER / 2 - 2, CenterY, scale), Graph.ToPx(CenterX + DIAMETER / 2 + 2, CenterY, scale),
Graph.ToPx(CenterX + DIAMETER / 2 + 2, CenterY + DIAMETER / 2 + 6, scale), Graph.ToPx(CenterX + DIAMETER / 2 - 4, CenterY + DIAMETER / 2 + 6, scale),
Graph.ToPx(CenterX + DIAMETER / 2 - 4, CenterY + DIAMETER / 2 + 2, scale), Graph.ToPx(CenterX - DIAMETER / 2 + 4, CenterY + DIAMETER / 2 + 2, scale),
Graph.ToPx(CenterX - DIAMETER / 2 + 4, CenterY + DIAMETER / 2 + 6, scale), Graph.ToPx(CenterX - DIAMETER / 2 - 2, CenterY + DIAMETER / 2 + 6, scale),
];
_frame?.StrokeThickness = border;
_text?.FontSize = 14;
_text?.Width = DIAMETER * scale;
_text?.SetValue(Canvas.LeftProperty, (CenterX - DIAMETER / 2) * scale);
_text?.SetValue(Canvas.TopProperty, (CenterY * scale) - (CapacityLiters.HasValue ? 20 : 10));
}
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
return Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= DIAMETER / 2 || (Math.Abs(x - CenterX) <= DIAMETER / 2 + 10 && y >= CenterY && y <= CenterY + DIAMETER / 2 + 30); return Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= DIAMETER / 2 || (Math.Abs(x - CenterX) <= DIAMETER / 2 + 10 && y >= CenterY && y <= CenterY + DIAMETER / 2 + 30);
} }
+46 -29
View File
@@ -1,4 +1,4 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Shapes; using System.Windows.Shapes;
@@ -6,7 +6,7 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Pump : INode { public class Pump : INode {
public const double DIAMETER = 24; public const double DIAMETER = 4.8;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -16,23 +16,24 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public ISet<IEdge> Outputs { get; init; } public ISet<IEdge> Outputs { get; init; }
public Brush? Highlight { get; set; } public Brush? Highlight { get; set; }
public bool IsActive => CallbackActive(); public bool IsActive => State == MotorState.Forward || State == MotorState.Backward;
public MotorState State => CallbackState();
public bool HasClearance => CallbackClearance(); public bool HasClearance => CallbackClearance();
public bool IsSelected => CallbackSelected(this); public bool IsSelected => CallbackSelected(this);
protected Func<bool> CallbackActive; protected Func<MotorState> CallbackState;
protected Func<bool> CallbackClearance; protected Func<bool> CallbackClearance;
protected Func<Pump, bool> CallbackSelected; protected Func<Pump, bool> CallbackSelected;
private Shape? _circle; private Ellipse? _circle;
private Shape? _triangle; private Polyline? _triangle;
private TextBlock? _text; private TextBlock? _text;
public Pump(string label, double x, double y, Func<bool> cbActive, Func<bool> cbClearance, Func<Pump, bool> cbSelected) { public Pump(string label, double x, double y, Func<MotorState> cbState, Func<bool> cbClearance, Func<Pump, bool> cbSelected) {
CenterX = x; CenterX = x;
CenterY = y; CenterY = y;
Label = label; Label = label;
CallbackActive = cbActive; CallbackState = cbState;
CallbackClearance = cbClearance; CallbackClearance = cbClearance;
CallbackSelected = cbSelected; CallbackSelected = cbSelected;
Inputs = new HashSet<IEdge>(); Inputs = new HashSet<IEdge>();
@@ -41,47 +42,63 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_circle = new Ellipse() { _circle = new Ellipse() {
Height = DIAMETER,
Width = DIAMETER,
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
}; };
var dx = Math.Cos(Math.PI / 6) * (DIAMETER / 2 - 2); _triangle = new Polyline() {
var dy = Math.Sin(Math.PI / 6) * (DIAMETER / 2 - 2);
_triangle = new Polygon() {
Points = [new(CenterX - dx, CenterY - dy), new(CenterX + dx, CenterY - dy), new(CenterX, CenterY + DIAMETER / 2 - 2)],
Fill = Brushes.Transparent,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2, StrokeStartLineCap = PenLineCap.Square,
StrokeLineJoin = PenLineJoin.Round,
StrokeEndLineCap = PenLineCap.Square,
}; };
_text = new() { _text = new() {
Text = Label, Text = Label,
FontSize = 14,
TextAlignment = TextAlignment.Left, TextAlignment = TextAlignment.Left,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
}; };
_circle.SetValue(Canvas.LeftProperty, CenterX - DIAMETER / 2);
_circle.SetValue(Canvas.TopProperty, CenterY - DIAMETER / 2);
_text.SetValue(Canvas.LeftProperty, CenterX + DIAMETER / 2 + 2);
_text.SetValue(Canvas.TopProperty, CenterY - 10);
canvas.Children.Add(_circle); canvas.Children.Add(_circle);
canvas.Children.Add(_triangle); canvas.Children.Add(_triangle);
canvas.Children.Add(_text); canvas.Children.Add(_text);
} }
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_circle?.StrokeThickness = border;
Graph.SetCenter(_circle, CenterX, CenterY, DIAMETER, DIAMETER, scale);
var dx = Math.Cos(Math.PI / 9.5) * (DIAMETER / 2);
var dy = Math.Sin(Math.PI / 9.5) * (DIAMETER / 2);
_triangle?.Points = [
Graph.ToPx(CenterX - dx, CenterY - dy, scale, dxpx: border, dypx: border / 2, round: false),
Graph.ToPx(CenterX, CenterY + DIAMETER / 2, scale, dypx: -border, round: false),
Graph.ToPx(CenterX + dx, CenterY - dy, scale, dxpx: -border, dypx: border / 2, round: false),
];
_triangle?.StrokeThickness = border;
_text?.FontSize = 14;
_text?.SetValue(Canvas.LeftProperty, (CenterX + DIAMETER / 2) * scale + 2);
_text?.SetValue(Canvas.TopProperty, CenterY * scale - 10);
}
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
return Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= DIAMETER / 2; return Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= DIAMETER / 2;
} }
public void Update(bool isHovering, bool isPresed) { public void Update(bool isHovering, bool isPresed) {
_circle?.Fill = Highlight ?? ( if (App.Plant?.ManualControlMode ?? false) {
IsActive && isHovering ? PamhagenBrushes.Red : _circle?.Fill = Highlight ?? (
IsActive ? PamhagenBrushes.Green : IsActive && isHovering ? PamhagenBrushes.Red :
HasClearance && IsSelected && isHovering ? PamhagenBrushes.Green : IsActive ? PamhagenBrushes.Green :
HasClearance && IsSelected ? PamhagenBrushes.Yellow : HasClearance && isHovering ? PamhagenBrushes.Green :
IsSelected ? PamhagenBrushes.Red : HasClearance ? PamhagenBrushes.Yellow :
isHovering ? Brushes.LightGray : Brushes.WhiteSmoke); PamhagenBrushes.Red);
} else {
_circle?.Fill = Highlight ?? (
IsActive && isHovering ? PamhagenBrushes.Red :
IsActive ? PamhagenBrushes.Green :
HasClearance && IsSelected && isHovering ? PamhagenBrushes.Green :
HasClearance && IsSelected ? PamhagenBrushes.Yellow :
IsSelected ? PamhagenBrushes.Red :
isHovering ? Brushes.LightGray : Brushes.WhiteSmoke);
}
} }
} }
} }
+32 -28
View File
@@ -1,4 +1,4 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Shapes; using System.Windows.Shapes;
@@ -6,8 +6,8 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Rebler : INode { public class Rebler : INode {
public const double WIDTH = 120; public const double WIDTH = 24;
public const double HEIGHT = 60; public const double HEIGHT = 12;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -20,11 +20,11 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
protected Func<bool> CallbackActive; protected Func<bool> CallbackActive;
private TextBlock? _text; private TextBlock? _text;
private Shape? _rect; private Rectangle? _rect;
private Shape? _hopperOuter; private Polygon? _hopperOuter;
private Shape? _hopperInner; private Polygon? _hopperInner;
private Shape? _bottomInner; private Polygon? _bottomInner;
private Shape? _bottomOuter; private Polygon? _bottomOuter;
public Rebler(string label, double x, double y, Func<bool> cbActive) { public Rebler(string label, double x, double y, Func<bool> cbActive) {
CenterX = x; CenterX = x;
@@ -37,54 +37,58 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_rect = new Rectangle() { _rect = new Rectangle() {
Width = WIDTH,
Height = HEIGHT,
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2, StrokeThickness = 2,
}; };
var left = CenterX - WIDTH / 2;
var right = CenterX + WIDTH / 2;
var top = CenterY - HEIGHT / 2;
var bottom = CenterY + HEIGHT / 2;
var hx = right - 20;
var h = 15;
var w = 25;
_hopperOuter = new Polygon() { _hopperOuter = new Polygon() {
Points = [new(hx - w, top - h), new(hx - w + 5, top + 2), new(hx + w - 5, top + 2), new(hx + w, top - h)],
Fill = Brushes.Black, Fill = Brushes.Black,
}; };
_hopperInner = new Polygon() { _hopperInner = new Polygon() {
Points = [new(hx - w + 2, top - h), new(hx - w + 5 + 2, top + 2), new(hx + w - 5 - 2, top + 2), new(hx + w - 2, top - h)],
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
}; };
_bottomOuter = new Polygon() { _bottomOuter = new Polygon() {
Points = [new(left + 20, bottom), new(right - 20, bottom), new(right - 15, bottom + 10), new(left + 15, bottom + 10)],
Fill = Brushes.Black, Fill = Brushes.Black,
}; };
_bottomInner = new Polygon() { _bottomInner = new Polygon() {
Points = [new(left + 20 + 2, bottom), new(right - 20 - 2, bottom), new(right - 15 - 2, bottom + 10), new(left + 15 + 2, bottom + 10)],
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
}; };
_text = new() { _text = new() {
Text = Label, Text = Label,
FontSize = 12,
Width = WIDTH,
TextAlignment = TextAlignment.Center, TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
}; };
_rect.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_rect.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2);
_text.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_text.SetValue(Canvas.TopProperty, CenterY - 8);
canvas.Children.Add(_hopperOuter); canvas.Children.Add(_hopperOuter);
canvas.Children.Add(_bottomOuter); canvas.Children.Add(_bottomOuter);
canvas.Children.Add(_bottomInner);
canvas.Children.Add(_rect); canvas.Children.Add(_rect);
canvas.Children.Add(_hopperInner); canvas.Children.Add(_hopperInner);
canvas.Children.Add(_bottomInner);
canvas.Children.Add(_text); canvas.Children.Add(_text);
} }
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
Graph.SetCenter(_rect, CenterX, CenterY, WIDTH, HEIGHT, scale);
_rect?.StrokeThickness = border;
var left = CenterX - WIDTH / 2;
var right = CenterX + WIDTH / 2;
var top = CenterY - HEIGHT / 2;
var bottom = CenterY + HEIGHT / 2;
var hx = right - 4;
var h = 3;
var w = 5;
_hopperOuter?.Points = [Graph.ToPx(hx - w, top - h, scale), Graph.ToPx(hx - w + 1, top, scale, dypx: border), Graph.ToPx(hx + w - 1, top, scale, dypx: border), Graph.ToPx(hx + w, top - h, scale)];
_hopperInner?.Points = [Graph.ToPx(hx - w, top - h, scale, dxpx: border), Graph.ToPx(hx - w + 1, top, scale, dxpx: border, dypx: border), Graph.ToPx(hx + w - 1, top, scale, dxpx: -border, dypx: border), Graph.ToPx(hx + w, top - h, scale, dxpx: -border)];
_bottomOuter?.Points = [Graph.ToPx(left + 5, bottom - 1, scale), Graph.ToPx(right - 5, bottom - 1, scale), Graph.ToPx(right - 3, bottom + 2, scale), Graph.ToPx(left + 3, bottom + 2, scale)];
_bottomInner?.Points = [Graph.ToPx(left + 5, bottom - 1, scale, dxpx: border), Graph.ToPx(right - 5, bottom - 1, scale, dxpx: -border), Graph.ToPx(right - 3, bottom + 2, scale, dxpx: -border), Graph.ToPx(left + 3, bottom + 2, scale, dxpx: border)];
_text?.FontSize = 12;
_text?.Width = WIDTH * scale;
_text?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale);
_text?.SetValue(Canvas.TopProperty, (CenterY * scale) - 8);
}
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2; return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2;
} }
@@ -0,0 +1,76 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Stirrer {
public double CenterX { get; set; }
public double CenterY { get; set; }
public double Radius { get; set; }
public bool IsRotatingClockwise { get; set; }
public bool IsAnimationActive {
get => !(_storyboard?.GetIsPaused() ?? true);
set {
if (_storyboard == null) return;
if (value && _storyboard.GetIsPaused()) {
_storyboard.Resume();
} else if (!value && !_storyboard.GetIsPaused()) {
_storyboard.Pause();
}
}
}
private Polyline? _polyline;
private Storyboard? _storyboard;
private RotateTransform? _transform;
private DoubleAnimation? _rotate;
public Stirrer(double x, double y, double radius, bool clockwise = true) {
CenterX = x;
CenterY = y;
Radius = radius;
IsRotatingClockwise = clockwise;
}
public void Draw(Canvas canvas) {
_transform = new RotateTransform(0);
_polyline = new Polyline() {
Stroke = Brushes.Black,
RenderTransform = _transform,
StrokeLineJoin = PenLineJoin.Round,
};
_rotate = new DoubleAnimation {
From = 0,
To = IsRotatingClockwise ? 360 : -360,
Duration = new Duration(TimeSpan.FromSeconds(4.5)),
RepeatBehavior = RepeatBehavior.Forever,
};
Storyboard.SetTarget(_rotate, _polyline);
_storyboard = new Storyboard();
_storyboard.Children.Add(_rotate);
Storyboard.SetTargetProperty(_rotate, new PropertyPath("RenderTransform.Angle"));
canvas.Children.Add(_polyline);
_storyboard.Begin();
_storyboard.Pause();
}
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
var dx = Radius * Math.Cos(double.DegreesToRadians(30));
var dy = Radius * Math.Sin(double.DegreesToRadians(30));
_polyline?.Points = [
Graph.ToPx(CenterX, CenterY, scale, round: false),
Graph.ToPx(CenterX, CenterY - Radius, scale, round: false), Graph.ToPx(CenterX, CenterY, scale, round: false),
Graph.ToPx(CenterX + dx, CenterY + dy, scale, round: false), Graph.ToPx(CenterX, CenterY, scale, round: false),
Graph.ToPx(CenterX - dx, CenterY + dy, scale, round: false), Graph.ToPx(CenterX, CenterY, scale, round: false)];
_polyline?.StrokeThickness = border * 1.5;
_transform?.CenterX = Graph.ToPx(CenterX, scale);
_transform?.CenterY = Graph.ToPx(CenterY, scale);
}
}
}
+23 -10
View File
@@ -6,8 +6,8 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Switcher : INode { public class Switcher : INode {
public const double WIDTH = 100; public const double WIDTH = 20;
public const double HEIGHT = 50; public const double HEIGHT = 10;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -34,21 +34,15 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_mainTransform = new RotateTransform(0, CenterX, CenterY); _mainTransform = new RotateTransform(0);
_shadowTransform = new RotateTransform(0, CenterX, CenterY); _shadowTransform = new RotateTransform(0);
_main = new Line() { _main = new Line() {
X1 = CenterX - WIDTH / 2, Y1 = CenterY,
X2 = CenterX + WIDTH / 2, Y2 = CenterY,
StrokeThickness = 5,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeStartLineCap = PenLineCap.Square, StrokeStartLineCap = PenLineCap.Square,
StrokeEndLineCap = PenLineCap.Square, StrokeEndLineCap = PenLineCap.Square,
RenderTransform = _mainTransform, RenderTransform = _mainTransform,
}; };
_shadow = new Line() { _shadow = new Line() {
X1 = CenterX - WIDTH / 2, Y1 = CenterY,
X2 = CenterX + WIDTH / 2, Y2 = CenterY,
StrokeThickness = 5,
Stroke = Brushes.DarkGray, Stroke = Brushes.DarkGray,
StrokeStartLineCap = PenLineCap.Square, StrokeStartLineCap = PenLineCap.Square,
StrokeEndLineCap = PenLineCap.Square, StrokeEndLineCap = PenLineCap.Square,
@@ -58,6 +52,25 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
canvas.Children.Add(_main); canvas.Children.Add(_main);
} }
public void Scale(double scale) {
_mainTransform?.CenterX = CenterX * scale;
_mainTransform?.CenterY = CenterY * scale;
_shadowTransform?.CenterX = CenterX * scale;
_shadowTransform?.CenterY = CenterY * scale;
_main?.X1 = (CenterX - WIDTH / 2) * scale;
_main?.Y1 = CenterY * scale;
_main?.X2 = (CenterX + WIDTH / 2) * scale;
_main?.Y2 = CenterY * scale;
_main?.StrokeThickness = 1 * scale;
_shadow?.X1 = (CenterX - WIDTH / 2) * scale;
_shadow?.Y1 = CenterY * scale;
_shadow?.X2 = (CenterX + WIDTH / 2) * scale;
_shadow?.Y2 = CenterY * scale;
_shadow?.StrokeThickness = 1 * scale;
}
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2; return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2;
} }
+20 -13
View File
@@ -1,4 +1,4 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Shapes; using System.Windows.Shapes;
@@ -6,8 +6,8 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Tank : INode, ISink, ISource { public class Tank : INode, ISink, ISource {
public const double WIDTH = 60; public const double WIDTH = 12;
public const double HEIGHT = 100; public const double HEIGHT = 20;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -17,6 +17,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public FillState FillState => CallbackFillState(); public FillState FillState => CallbackFillState();
public bool IsFull => FillState == FillState.Full; public bool IsFull => FillState == FillState.Full;
public bool IsFilled => FillState != FillState.Empty; public bool IsFilled => FillState != FillState.Empty;
public bool HasFillingClearance => CallbackFillingClearance();
public ISet<IEdge> Inputs { get; init; } public ISet<IEdge> Inputs { get; init; }
public ISet<IEdge> Outputs { get; init; } public ISet<IEdge> Outputs { get; init; }
@@ -24,43 +25,49 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public int? CapacityLiters { get; set; } public int? CapacityLiters { get; set; }
protected Func<FillState> CallbackFillState; protected Func<FillState> CallbackFillState;
protected Func<bool> CallbackFillingClearance;
private Shape? _rect; private Shape? _rect;
private TextBlock? _text; private TextBlock? _text;
public Tank(string label, double x, double y, int capacityLiters, Func<FillState> cbFillState) { public Tank(string label, double x, double y, int capacityLiters, Func<FillState> cbFillState, Func<bool> cbFillingClearance) {
CenterX = x; CenterX = x;
CenterY = y; CenterY = y;
Label = label; Label = label;
CapacityLiters = capacityLiters; CapacityLiters = capacityLiters;
CallbackFillState = cbFillState; CallbackFillState = cbFillState;
CallbackFillingClearance = cbFillingClearance;
Inputs = new HashSet<IEdge>(); Inputs = new HashSet<IEdge>();
Outputs = new HashSet<IEdge>(); Outputs = new HashSet<IEdge>();
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_rect = new Rectangle() { _rect = new Rectangle() {
Width = WIDTH,
Height = HEIGHT,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
}; };
_text = new() { _text = new() {
Text = CapacityLiters.HasValue ? $"{Label}\n{CapacityLiters:N0}" : Label, Text = CapacityLiters.HasValue ? $"{Label}\n{CapacityLiters:N0}" : Label,
FontSize = 12,
Width = WIDTH,
TextAlignment = TextAlignment.Center, TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
}; };
_rect.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_rect.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2);
_text.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_text.SetValue(Canvas.TopProperty, CenterY - (CapacityLiters.HasValue ? 16 : 8));
canvas.Children.Add(_rect); canvas.Children.Add(_rect);
canvas.Children.Add(_text); canvas.Children.Add(_text);
} }
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_rect?.Height = Graph.ToPx(HEIGHT, scale);
_rect?.Width = Graph.ToPx(WIDTH, scale);
_rect?.StrokeThickness = border;
_rect?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale);
_rect?.SetValue(Canvas.TopProperty, (CenterY - HEIGHT / 2) * scale);
_text?.Width = Graph.ToPx(WIDTH, scale);
_text?.FontSize = 12;
_text?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale);
_text?.SetValue(Canvas.TopProperty, (CenterY * scale) - (CapacityLiters.HasValue ? 16 : 8));
}
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2; return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2;
} }
+30 -16
View File
@@ -1,4 +1,4 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Shapes; using System.Windows.Shapes;
@@ -6,8 +6,8 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Trough : INode, ISource { public class Trough : INode, ISource {
public const double WIDTH = 100; public const double WIDTH = 20;
public const double HEIGHT = 60; public const double HEIGHT = 12;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
@@ -15,51 +15,64 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public string Label { get; set; } public string Label { get; set; }
public FillState FillState => CallbackFillState(); public FillState FillState => CallbackFillState();
public bool IsFilled => FillState != FillState.Empty; public bool IsFilled => FillState != FillState.Empty;
public bool IsStirrerActive => CallbackStirrerActive();
public ISet<IEdge> Inputs { get; init; } public ISet<IEdge> Inputs { get; init; }
public ISet<IEdge> Outputs { get; init; } public ISet<IEdge> Outputs { get; init; }
protected Func<FillState> CallbackFillState; protected Func<FillState> CallbackFillState;
protected Func<bool> CallbackStirrerActive;
private Shape? _inner; private Shape? _inner;
private Shape? _outer; private Shape? _outer;
private TextBlock? _text; private TextBlock? _text;
private Stirrer _stirrer;
private readonly bool _stirrerLeft;
public Trough(string label, double x, double y, Func<FillState> cbFillState) { public Trough(string label, double x, double y, bool stirrerLeft, Func<FillState> cbFillState, Func<bool> cbStirrerActive) {
CenterX = x; CenterX = x;
CenterY = y; CenterY = y;
Label = label; Label = label;
CallbackFillState = cbFillState; CallbackFillState = cbFillState;
CallbackStirrerActive = cbStirrerActive;
Inputs = new HashSet<IEdge>(); Inputs = new HashSet<IEdge>();
Outputs = new HashSet<IEdge>(); Outputs = new HashSet<IEdge>();
_stirrer = new(x + (stirrerLeft ? -4 : 4), y, 4, stirrerLeft);
_stirrerLeft = stirrerLeft;
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_outer = new Rectangle() { _outer = new Rectangle() {
Width = WIDTH,
Height = HEIGHT,
Fill = Brushes.Black, Fill = Brushes.Black,
}; };
_inner = new Rectangle() { _inner = new Rectangle() {
Width = WIDTH - 4,
Height = HEIGHT - 2,
Fill = Brushes.WhiteSmoke, Fill = Brushes.WhiteSmoke,
}; };
_text = new() { _text = new() {
Text = Label, Text = Label,
FontSize = 14,
Width = WIDTH,
TextAlignment = TextAlignment.Center, TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
}; };
_outer.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_outer.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2);
_inner.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2 + 2);
_inner.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2);
_text.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2);
_text.SetValue(Canvas.TopProperty, CenterY - 10);
canvas.Children.Add(_outer); canvas.Children.Add(_outer);
canvas.Children.Add(_inner); canvas.Children.Add(_inner);
canvas.Children.Add(_text); canvas.Children.Add(_text);
_stirrer.Draw(canvas);
}
public void Scale(double scale) {
var border = Graph.ToBorder(scale);
_outer?.Width = WIDTH * scale;
_outer?.Height = HEIGHT * scale;
_inner?.Width = WIDTH * scale - border * 2;
_inner?.Height = HEIGHT * scale - border;
_text?.Width = WIDTH * scale;
_text?.FontSize = 14;
_outer?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale);
_outer?.SetValue(Canvas.TopProperty, (CenterY - HEIGHT / 2) * scale);
_inner?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2) * scale + border);
_inner?.SetValue(Canvas.TopProperty, (CenterY - HEIGHT / 2) * scale);
_text?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 2 + (_stirrerLeft ? 5 : -5)) * scale);
_text?.SetValue(Canvas.TopProperty, (CenterY * scale) - 10);
_stirrer.Scale(scale);
} }
public bool IsInside(double x, double y) { public bool IsInside(double x, double y) {
@@ -72,6 +85,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
isHovering ? Brushes.LightGray : isHovering ? Brushes.LightGray :
Brushes.WhiteSmoke Brushes.WhiteSmoke
); );
_stirrer.IsAnimationActive = IsStirrerActive;
} }
} }
} }
+107 -37
View File
@@ -1,76 +1,146 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Shapes; using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
public class Valve : INode { public abstract class Valve : INode {
public const double DIAMETER = 24; public const double LENGTH = 4.8;
public const double WIDTH = 2.6;
public double CenterX { get; set; } public double CenterX { get; set; }
public double CenterY { get; set; } public double CenterY { get; set; }
public int Nr { get; set; } public abstract string Label { get; }
public string Label => $"V{Nr}";
public Brush? Highlight { get; set; } public Brush? Highlight { get; set; }
public bool IsLocked { get; set; } public int Orientation { get; set; }
public bool IsOpen => CallbackIsOpen();
public bool WantOpen => CallbackWantOpen(); public bool IsOpen => CallbackIsOpen() == true;
public bool IsClosed => CallbackIsOpen() == false;
public bool IsTransitioning => CallbackIsOpen() == null;
public ISet<IEdge> Inputs { get; init; } public ISet<IEdge> Inputs { get; init; }
public ISet<IEdge> Outputs { get; init; } public ISet<IEdge> Outputs { get; init; }
protected Func<bool> CallbackIsOpen; protected Func<bool?> CallbackIsOpen;
protected Func<bool> CallbackWantOpen;
private Shape? _circle; protected Rectangle? _background;
private TextBlock? _text; protected Polygon? _triangles;
protected Polyline? _topLine;
protected Polyline? _bottomLine;
protected ((Point Top, Point Bottom) Closed, (Point Top, Point Bottom) Open) _middlePoints;
protected TextBlock? _text;
public Valve(int nr, double x, double y, Func<bool> cbOpen, Func<bool> cbWant) { public Valve(double x, double y, int orientation, Func<bool?> cbOpen) {
CenterX = x; CenterX = x;
CenterY = y; CenterY = y;
Nr = nr;
CallbackIsOpen = cbOpen; CallbackIsOpen = cbOpen;
CallbackWantOpen = cbWant;
Inputs = new HashSet<IEdge>(); Inputs = new HashSet<IEdge>();
Outputs = new HashSet<IEdge>(); Outputs = new HashSet<IEdge>();
Orientation = orientation;
} }
public void Draw(Canvas canvas) { public void Draw(Canvas canvas) {
_circle = new Ellipse() { _background = new Rectangle() {
Height = DIAMETER, Fill = Brushes.White,
Width = DIAMETER, };
_triangles = new Polygon() {
Fill = Brushes.DarkGray, Fill = Brushes.DarkGray,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeThickness = 2,
}; };
_circle.SetValue(Canvas.LeftProperty, CenterX - DIAMETER / 2); _topLine = new Polyline() {
_circle.SetValue(Canvas.TopProperty, CenterY - DIAMETER / 2); Stroke = Brushes.Black,
};
_bottomLine = new Polyline() {
Stroke = Brushes.Black,
};
_text = new() { _text = new() {
Text = Label, Text = Label,
FontSize = 10,
Width = DIAMETER,
Height = DIAMETER,
TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
SnapsToDevicePixels = true,
}; };
_text.SetValue(Canvas.LeftProperty, CenterX - DIAMETER / 2); canvas.Children.Add(_background);
_text.SetValue(Canvas.TopProperty, CenterY - DIAMETER / 2 + 5.5); canvas.Children.Add(_triangles);
canvas.Children.Add(_circle); canvas.Children.Add(_topLine);
canvas.Children.Add(_bottomLine);
canvas.Children.Add(_text); canvas.Children.Add(_text);
} }
public bool IsInside(double x, double y) { public void Scale(double scale) {
return Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= DIAMETER / 2; var border = Graph.ToBorder(scale);
if (Orientation == 0 || Orientation == 2) {
Graph.SetCenter(_background, CenterX, CenterY, LENGTH / 2, LENGTH, scale);
_triangles?.Points = [
Graph.ToPx(CenterX - WIDTH / 2, CenterY - LENGTH / 2, scale),
Graph.ToPx(CenterX + WIDTH / 2, CenterY + LENGTH / 2, scale),
Graph.ToPx(CenterX - WIDTH / 2, CenterY + LENGTH / 2, scale),
Graph.ToPx(CenterX + WIDTH / 2, CenterY - LENGTH / 2, scale),
];
_topLine?.Points = [
Graph.ToPx(CenterX - WIDTH / 2, CenterY - LENGTH / 4, scale),
Graph.ToPx(CenterX - WIDTH / 4, CenterY, scale),
Graph.ToPx(CenterX - WIDTH / 2, CenterY + LENGTH / 4, scale),
];
_bottomLine?.Points = [
Graph.ToPx(CenterX + WIDTH / 2, CenterY - LENGTH / 4, scale),
Graph.ToPx(CenterX + WIDTH / 4, CenterY, scale),
Graph.ToPx(CenterX + WIDTH / 2, CenterY + LENGTH / 4, scale),
];
if (_topLine != null && _bottomLine != null)
_middlePoints = ((_topLine.Points[1], _bottomLine.Points[1]),
(Graph.ToPx(CenterX - WIDTH / 3, CenterY, scale), Graph.ToPx(CenterX + WIDTH / 3, CenterY, scale)));
} else {
Graph.SetCenter(_background, CenterX, CenterY, LENGTH, LENGTH / 2, scale);
_triangles?.Points = [
Graph.ToPx(CenterX - LENGTH / 2, CenterY - WIDTH / 2, scale),
Graph.ToPx(CenterX + LENGTH / 2, CenterY + WIDTH / 2, scale),
Graph.ToPx(CenterX + LENGTH / 2, CenterY - WIDTH / 2, scale),
Graph.ToPx(CenterX - LENGTH / 2, CenterY + WIDTH / 2, scale),
];
_topLine?.Points = [
Graph.ToPx(CenterX - LENGTH / 4, CenterY - WIDTH / 2, scale),
Graph.ToPx(CenterX, CenterY - WIDTH / 4, scale),
Graph.ToPx(CenterX + LENGTH / 4, CenterY - WIDTH / 2, scale),
];
_bottomLine?.Points = [
Graph.ToPx(CenterX - LENGTH / 4, CenterY + WIDTH / 2, scale),
Graph.ToPx(CenterX, CenterY + WIDTH / 4, scale),
Graph.ToPx(CenterX + LENGTH / 4, CenterY + WIDTH / 2, scale),
];
if (_topLine != null && _bottomLine != null)
_middlePoints = ((_topLine.Points[1], _bottomLine.Points[1]),
(Graph.ToPx(CenterX, CenterY - WIDTH / 3, scale), Graph.ToPx(CenterX, CenterY + WIDTH / 3, scale)));
}
_triangles?.StrokeThickness = border;
_topLine?.StrokeThickness = border;
_bottomLine?.StrokeThickness = border;
_text?.Width = 40;
_text?.Height = 16;
_text?.FontSize = 12;
_text?.TextAlignment = Orientation == 0 ? TextAlignment.Right : Orientation == 2 ? TextAlignment.Left : TextAlignment.Center;
if (Orientation == 0) {
_text?.SetValue(Canvas.LeftProperty, (CenterX - WIDTH / 1.5) * scale - 40);
_text?.SetValue(Canvas.TopProperty, CenterY * scale - 8);
} else if (Orientation == 2) {
_text?.SetValue(Canvas.LeftProperty, (CenterX + WIDTH / 1.5) * scale);
_text?.SetValue(Canvas.TopProperty, CenterY * scale - 8 );
} else if (Orientation == 1) {
_text?.SetValue(Canvas.LeftProperty, CenterX * scale - 20);
_text?.SetValue(Canvas.TopProperty, (CenterY - WIDTH / 2) * scale - 16);
} else {
_text?.SetValue(Canvas.LeftProperty, CenterX * scale - 20);
_text?.SetValue(Canvas.TopProperty, (CenterY + WIDTH / 2) * scale);
}
} }
public void Update(bool isHovering, bool isPressed) { public bool IsInside(double x, double y) {
_circle?.Fill = if (Orientation == 0 || Orientation == 2) {
IsLocked && !WantOpen && !IsOpen ? PamhagenBrushes.DimOrange : return Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= LENGTH / 2;
IsLocked && !WantOpen && IsOpen ? PamhagenBrushes.Red : } else {
IsLocked && WantOpen && !IsOpen ? PamhagenBrushes.Yellow : return Math.Abs(x - CenterX) <= LENGTH / 2 && Math.Abs(y - CenterY) <= WIDTH / 2;
IsLocked && WantOpen && IsOpen ? PamhagenBrushes.Green : }
Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray);
} }
public abstract void Update(bool isHovering, bool isPressed);
} }
} }
+1 -1
View File
@@ -1,3 +1,3 @@
namespace PamhagenSysCtrl.Helpers { namespace PamhagenSysCtrl.Helpers {
public class PlantEventArgs : EventArgs { } public class PlantEventArgs : EventArgs { }
} }
@@ -1,15 +1,18 @@
using System.IO.Ports; using System.IO.Ports;
namespace PamhagenSysCtrl.Helpers { namespace PamhagenSysCtrl.Helpers.Plc {
public class PamhagenPlc : IDisposable { public class LegacyPamhagenPlc : IDisposable {
public record struct SensorStates( public record struct SensorStates(
long PressureSensors, long PressureSensors,
(FillState MW1, FillState MW2, FillState Press1, FillState Press2) FillingLevels, FillState FillLevelMW1,
FillState FillLevelMW2,
bool ClearancePress1,
bool ClearancePress2,
int TroughSelector, int TroughSelector,
double GradationOe, double GradationOe,
FillState[] FillingLevelTanks, FillState[] FillLevelTanks,
MotorState PresseQuerSchnecke MotorState PressCrossAuger
) { ) {
public readonly bool GetDS(int n) => (PressureSensors & (1L << (n - 1))) != 0; public readonly bool GetDS(int n) => (PressureSensors & (1L << (n - 1))) != 0;
} }
@@ -17,7 +20,7 @@ namespace PamhagenSysCtrl.Helpers {
public record struct ActuatorStates( public record struct ActuatorStates(
long Valves, long Valves,
MotorState MP1, MotorState MP2, MotorState P12, MotorState P3, MotorState P4, MotorState P5, MotorState P6, MotorState MP1, MotorState MP2, MotorState P12, MotorState P3, MotorState P4, MotorState P5, MotorState P6,
bool TroughAuger, bool Rebler, bool StirrerMW1, bool StirrerMW2, bool Belt, bool Auger1, bool Auger2, bool GradationPump bool TroughAuger, bool Rebler, bool StirrerMW1, bool StirrerMW2, bool Auger2, bool Auger3, bool Auger1, bool GradationPump
) { ) {
public readonly bool GetV(int n) => (Valves & (1L << (n - 1))) != 0; public readonly bool GetV(int n) => (Valves & (1L << (n - 1))) != 0;
public void SetV(int n, bool val) { public void SetV(int n, bool val) {
@@ -31,8 +34,12 @@ namespace PamhagenSysCtrl.Helpers {
protected readonly T1 Plc; protected readonly T1 Plc;
public PamhagenPlc(string portName, Parity parity = Parity.Odd) { public string ComPort => Plc.PortName;
Plc = new T1(portName, parity); public T1.Status LastStatus => Plc.LastStatus;
public T1Error LastError => Plc.LastError;
public LegacyPamhagenPlc(string comPort, Parity parity = Parity.Odd) {
Plc = new T1(comPort, parity);
} }
public void Dispose() { public void Dispose() {
@@ -40,31 +47,28 @@ namespace PamhagenSysCtrl.Helpers {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
protected static FillState Int3ToFillState(int n) { protected static FillState Int3ToFillState(int n, bool bigEndian = true) {
return n switch { return
7 => FillState.Full, (bigEndian && (n & 0x4) != 0) || (!bigEndian && (n & 0x1) != 0) ? FillState.AlmostFull :
6 => FillState.Half, (bigEndian && (n & 0x2) != 0) || (!bigEndian && (n & 0x2) != 0) ? FillState.Half :
4 => FillState.Filled, (bigEndian && (n & 0x1) != 0) || (!bigEndian && (n & 0x4) != 0) ? FillState.Filled :
0 => FillState.Empty, n == 0 ? FillState.Empty :
_ => FillState.Invalid, FillState.Invalid;
};
} }
protected static FillState Int2ToFillState(int n) { protected static FillState Int2ToFillState(int n, bool bigEndian = true) {
return n switch { return
2 => FillState.Full, (bigEndian && (n & 0x2) != 0) || (!bigEndian && (n & 0x1) != 0) ? FillState.Full :
0 => FillState.Filled, (bigEndian && (n & 0x1) != 0) || (!bigEndian && (n & 0x2) != 0) ? FillState.Filled :
1 => FillState.Empty, n == 0 ? FillState.Empty :
_ => FillState.Invalid, FillState.Invalid;
};
} }
protected static FillState Int1ToFillState(int n) { protected static FillState Int1ToFillState(int n) {
return n switch { return
1 => FillState.Full, n == 1 ? FillState.Full :
0 => FillState.Unknown, n == 0 ? FillState.Unknown :
_ => FillState.Invalid, FillState.Invalid;
};
} }
protected static MotorState Int2ToPumpState(int n, bool inverse) { protected static MotorState Int2ToPumpState(int n, bool inverse) {
@@ -91,7 +95,7 @@ namespace PamhagenSysCtrl.Helpers {
throw new T1Exception($"Interner Fehler: {error} ({(int)error:0000})"); throw new T1Exception($"Interner Fehler: {error} ({(int)error:0000})");
} }
protected async Task<(ushort RW010, ushort RW011, ushort RW012, ushort RW013, ushort RW014, ushort RW015, ushort RW016)> ReadInputRegisters() { public async Task<(ushort RW010, ushort RW011, ushort RW012, ushort RW013, ushort RW014, ushort RW015, ushort RW016)> ReadInputRegisters() {
var d = await Plc.DataRead("RW010", 7); var d = await Plc.DataRead("RW010", 7);
// RW010, RW011, RW012, RW013 Drucksensoren // RW010, RW011, RW012, RW013 Drucksensoren
// RW014 Füllstände A1, A2, Presse1, Presse2, Wannenselekt // RW014 Füllstände A1, A2, Presse1, Presse2, Wannenselekt
@@ -107,10 +111,10 @@ namespace PamhagenSysCtrl.Helpers {
long pressure = (long)r1 | ((long)r2 << 16) | ((long)r3 << 32) | ((long)r4 << 48); long pressure = (long)r1 | ((long)r2 << 16) | ((long)r3 << 32) | ((long)r4 << 48);
pressure = (pressure & 0x01FFFFFFFFFFFFFF) | ((pressure & 0x0600000000000000) << 1); pressure = (pressure & 0x01FFFFFFFFFFFFFF) | ((pressure & 0x0600000000000000) << 1);
var mw1 = Int3ToFillState((f1 ) & 0x7); // 111 011 001 000 var mw1 = Int3ToFillState((f1 ) & 0x7, false); // 111 011 001 000
var mw2 = Int3ToFillState((f1 >> 3) & 0x7); // 111 011 001 000 var mw2 = Int3ToFillState((f1 >> 3) & 0x7, false); // 111 011 001 000
var p1 = Int1ToFillState((f1 >> 6) & 0x1); // 1 0 var p1 = ((f1 >> 6) & 0x1) == 0; // 1 0
var p2 = Int1ToFillState((f1 >> 7) & 0x1); // 1 0 var p2 = ((f1 >> 7) & 0x1) == 0; // 1 0
var wp = ((f1 >> 8) & 0x3); // 10 01 var wp = ((f1 >> 8) & 0x3); // 10 01
// Masseligerät : 4mA => 50 Oe // Masseligerät : 4mA => 50 Oe
@@ -126,18 +130,18 @@ namespace PamhagenSysCtrl.Helpers {
// -> factor: 0,03125 + (offset) 25 Oe // -> factor: 0,03125 + (offset) 25 Oe
double oe = v * 0.03125 + 25; double oe = v * 0.03125 + 25;
var t1 = Int2ToFillState((f2 ) & 0x3); // 10 01 00 var t1 = Int1ToFillState(((f2 ) & 0x3) >> 1); // 1X 0X
var t2 = Int2ToFillState((f2 >> 2) & 0x3); // 10 01 00 var t2 = Int1ToFillState(((f2 >> 2) & 0x3) >> 1); // 1X 0X
var t3 = Int2ToFillState((f2 >> 4) & 0x3); // 10 01 00 var t3 = Int1ToFillState(((f2 >> 4) & 0x3) >> 1); // 1X 0X
var t4 = Int2ToFillState((f2 >> 6) & 0x3); // 10 01 00 var t4 = Int1ToFillState(((f2 >> 6) & 0x3) >> 1); // 1X 0X
var t5 = Int2ToFillState((f2 >> 8) & 0x3); // 10 01 00 var t5 = Int1ToFillState(((f2 >> 8) & 0x3) >> 1); // 1X 0X
var t6 = Int1ToFillState((f2 >> 10) & 0x1); // 1 0 var t6 = Int1ToFillState((f2 >> 10) & 0x1); // 1 0
var t7 = Int1ToFillState((f2 >> 11) & 0x1); // 1 0 var t7 = Int1ToFillState((f2 >> 11) & 0x1); // 1 0
var t8 = Int1ToFillState((f2 >> 12) & 0x1); // 1 0 var t8 = Int1ToFillState((f2 >> 12) & 0x1); // 1 0
var t9 = Int1ToFillState((f2 >> 13) & 0x1); // 1 0 var t9 = Int1ToFillState((f2 >> 13) & 0x1); // 1 0
var q1 = Int1ToPumpState((f2 >> 15) & 0x1); // 1 0 var q1 = Int1ToPumpState((f2 >> 15) & 0x1); // 1 0
return new SensorStates(pressure, (mw1, mw2, p1, p2), wp == 3 ? -1 : wp == 1 ? 2 : wp == 2 ? 1 : 0, oe, [t1, t2, t3, t4, t5, t6, t7, t8, t9], q1); return new SensorStates(pressure, mw1, mw2, p1, p2, wp == 3 ? -1 : wp == 1 ? 2 : wp == 2 ? 1 : 0, oe, [t1, t2, t3, t4, t5, t6, t7, t8, t9], q1);
} }
protected async Task<(ushort RW000, ushort RW001, ushort RW002, ushort RW003, ushort RW004, ushort RW005, ushort RW006)> ReadOutputRegisters() { protected async Task<(ushort RW000, ushort RW001, ushort RW002, ushort RW003, ushort RW004, ushort RW005, ushort RW006)> ReadOutputRegisters() {
@@ -212,9 +216,9 @@ namespace PamhagenSysCtrl.Helpers {
(state.GetV(58) ? 0x04 : 0x00) | (state.GetV(58) ? 0x04 : 0x00) |
(state.StirrerMW1 ? 0x08 : 0x00) | (state.StirrerMW1 ? 0x08 : 0x00) |
(state.StirrerMW2 ? 0x10 : 0x00) | (state.StirrerMW2 ? 0x10 : 0x00) |
(state.Belt ? 0x20 : 0x00) | (state.Auger2 ? 0x20 : 0x00) |
(state.Auger1 ? 0x40 : 0x00) | (state.Auger3 ? 0x40 : 0x00) |
(state.Auger2 ? 0x80 : 0x00); (state.Auger1 ? 0x80 : 0x00);
int measuring = int measuring =
(state.GetV(59) ? 0x01 : 0x00) | (state.GetV(59) ? 0x01 : 0x00) |
+365
View File
@@ -0,0 +1,365 @@
using System.IO.Ports;
namespace PamhagenSysCtrl.Helpers.Plc {
public class PamhagenPlc : IDisposable {
private bool _manualControlMode = false;
private bool _overridePathClearnaces = false;
private bool _overrideDryRunClearances = false;
public bool ManualControlMode { get => _manualControlMode; set { _manualControlMode = value; UpdateInterlocks(); } }
public bool OverridePathClearances { get => _overridePathClearnaces; set { _overridePathClearnaces = value; UpdateInterlocks(); } }
public bool OverrideDryRunClearances { get => _overrideDryRunClearances; set { _overrideDryRunClearances = value; UpdateInterlocks(); } }
internal bool MP1_WantActive { get; private set; }
internal bool MP2_WantActive { get; private set; }
internal bool P12_WantActive { get; private set; }
internal bool P3_WantActive { get; private set; }
internal bool P4_WantActive { get; private set; }
internal bool P5_WantActive { get; private set; }
internal bool P6_WantActive { get; private set; }
protected LegacyPamhagenPlc Plc;
public PamhagenState State { get; private set; }
public PamhagenState LastState { get; private set; }
public string? ComPort => Plc?.ComPort;
public T1.Status? LastStatus => Plc?.LastStatus;
public T1Error? LastError => Plc?.LastError;
internal LegacyPamhagenPlc.ActuatorStates Actuators;
internal LegacyPamhagenPlc.ActuatorStates LastActuators;
internal LegacyPamhagenPlc.SensorStates[] LastSensors = [];
internal readonly int ConsiderLastSensorsOpen;
internal readonly int ConsiderLastSensorsClose;
internal readonly int ConsiderLastSensorsMW;
internal readonly FillState AutoStirrer;
public PamhagenPlc(string comPort, int sensorDelayOpenMs, int sensorDelayCloseMs, int sensorDelayMwMs, FillState autoStirrer = FillState.Half, Parity parity = Parity.Odd) {
Plc = new(comPort, parity);
ConsiderLastSensorsOpen = sensorDelayOpenMs / 125 + 1;
ConsiderLastSensorsClose = sensorDelayCloseMs / 125 + 1;
ConsiderLastSensorsMW = sensorDelayMwMs / 125 + 1;
AutoStirrer = autoStirrer;
LastSensors = new LegacyPamhagenPlc.SensorStates[Math.Max(Math.Max(ConsiderLastSensorsOpen, ConsiderLastSensorsClose), ConsiderLastSensorsMW)];
}
public void Dispose() {
GC.SuppressFinalize(this);
Plc.Dispose();
}
public async Task Initialize() {
await Plc.TestConnection();
Actuators = await Plc.ReadOutputs();
LastActuators = Actuators;
var sensors = await Plc.ReadInputs();
LastSensors[0] = sensors;
State = new(LastSensors, this);
LastState = State;
}
public async Task Shutdown() {
await Plc.WriteOutputs(new() { Valves = Actuators.Valves });
}
public async Task Update() {
UpdateInterlocks();
if (Actuators != LastActuators) {
LastActuators = Actuators;
await Plc.WriteOutputs(LastActuators);
}
var sensors = await Plc.ReadInputs();
Array.Copy(LastSensors, 0, LastSensors, 1, LastSensors.Length - 1);
LastSensors[0] = sensors;
var current = new PamhagenState(LastSensors, this);
LastState = State;
State = current;
UpdateInterlocks();
}
protected void UpdateInterlocks() {
if (!ManualControlMode) {
if (State.PressCrossAuger_IsActive && !State.Auger2_IsActive) StartAuger2();
if (State.Rebler_IsActive && !State.Auger1_IsActive) StartAuger1();
if (State.Auger1_IsActive && !State.Auger2_IsActive) StartAuger2();
if (State.Auger2_IsActive && !State.Auger3_IsActive) StartAuger3();
if (State.TroughAuger_IsActive && !State.TroughAuger_HasClearance)
StopTroughAuger();
if (!State.PressCrossAuger_IsActive && !State.Auger1_IsActive) {
StopAuger2();
StopAuger3();
}
}
if (State.MP1_IsActive && !State.MP1_HasClearance) StopMP1();
if (State.MP2_IsActive && !State.MP2_HasClearance) StopMP2();
if (State.P12_IsActive && !State.P12_HasClearance) DisableP12();
if (State.P3_IsActive && !State.P3_HasClearance) DisableP3();
if (State.P4_IsActive && !State.P4_HasClearance) DisableP4();
if (State.P5_IsActive && !State.P5_HasClearance) DisableP5();
if (State.P6_IsActive && !State.P6_HasClearance) DisableP6();
if (MP1_WantActive && State.MP1_HasClearance) StartMP1Forward();
if (MP2_WantActive && State.MP2_HasClearance) StartMP2Forward();
if (P12_WantActive && State.P12_HasClearance) EnableP12();
if (P3_WantActive && State.P3_HasClearance) EnableP3();
if (P4_WantActive && State.P4_HasClearance) EnableP4();
if (P5_WantActive && State.P5_HasClearance) EnableP5();
if (P6_WantActive && State.P6_HasClearance) EnableP6();
if (!ManualControlMode) {
if (State.MW1_Stirrer_IsActive && (State.MW1_FillLevel == FillState.Empty || (LastState.MW1_FillLevel >= AutoStirrer && State.MW1_FillLevel < AutoStirrer))) {
StopStirrerMW1();
}
if (State.MW2_Stirrer_IsActive && (State.MW2_FillLevel == FillState.Empty || (LastState.MW2_FillLevel >= AutoStirrer && State.MW2_FillLevel < AutoStirrer))) {
StopStirrerMW2();
}
}
}
public void OpenV(int n) => SetV(n, true);
public void CloseV(int n) => SetV(n, false);
protected void SetV(int n, bool val) {
if (n < 1 || n > 57) throw new ArgumentException("Invalid value for n: 1 <= #V <= 57");
Actuators.SetV(n, val);
}
public void SelectMW1() {
Actuators.SetV(58, true);
}
public void SelectMW2() {
Actuators.SetV(58, false);
}
public void StartTroughAuger() {
if (!ManualControlMode && !State.TroughAuger_HasClearance)
throw new NoClearanceException("Schnecke Auffangwanne hat keine Freigabe");
Actuators.TroughAuger = true;
}
public void StopTroughAuger() {
Actuators.TroughAuger = false;
}
public void StartRebler() {
Actuators.Rebler = true;
if (!ManualControlMode) StartAuger1();
}
public void StopRebler() {
if (!ManualControlMode) StopTroughAuger();
Actuators.Rebler = false;
if (!ManualControlMode) StopAuger1();
}
public void StartStirrerMW1() {
if (!ManualControlMode && State.MW1_FillLevel == FillState.Empty) throw new NoClearanceException("MW1 ist leer");
Actuators.StirrerMW1 = true;
}
public void StopStirrerMW1() {
Actuators.StirrerMW1 = false;
}
public void StartStirrerMW2() {
if (!ManualControlMode && State.MW2_FillLevel == FillState.Empty) throw new NoClearanceException("MW2 ist leer");
Actuators.StirrerMW2 = true;
}
public void StopStirrerMW2() {
Actuators.StirrerMW2 = false;
}
public void StartAuger1() {
Actuators.Auger1 = true;
if (!ManualControlMode) StartAuger2();
}
public void StopAuger1() {
if (!ManualControlMode && State.Rebler_IsActive) throw new NoClearanceException("Rebler aktiv");
Actuators.Auger1 = false;
if (!ManualControlMode && !State.PressCrossAuger_IsActive)
StopAuger2();
}
public void StartAuger2() {
Actuators.Auger2 = true;
if (!ManualControlMode) StartAuger3();
}
public void StopAuger2() {
if (!ManualControlMode && State.PressCrossAuger_IsActive) throw new NoClearanceException("Presse-Querschnecke aktiv");
if (!ManualControlMode && State.Auger1_IsActive) throw new NoClearanceException("Förderschnecke 1 aktiv");
Actuators.Auger2 = false;
if (!ManualControlMode)
StopAuger3();
}
public void StartAuger3() {
Actuators.Auger3 = true;
}
public void StopAuger3() {
if (!ManualControlMode && State.Auger2_IsActive) throw new NoClearanceException("Förderschnecke 2 aktiv");
Actuators.Auger3 = false;
}
public void StartWasteDisposal() {
StartAuger1();
StartAuger2();
StartAuger3();
}
public void StopWasteDisposal() {
StopAuger1();
StopAuger2();
StopAuger3();
}
public void StartGradationPump() {
Actuators.GradationPump = true;
}
public void StopGradationPump() {
Actuators.GradationPump = false;
}
public void StartMP1Forward() {
if (!State.MP1_HasClearance) {
if (!State.MP1_HasDryRunClearance || !State.MP1_WantsPathClearance)
throw new NoClearanceException("MP1 hat keine Freigabe");
MP1_WantActive = true;
return;
}
MP1_WantActive = false;
if (Actuators.MP1 == MotorState.Backward) throw new NoClearanceException("MP1 läuft bereits (zurück)");
Actuators.MP1 = MotorState.Forward;
if (!ManualControlMode && State.MW1_FillLevel >= AutoStirrer) StartStirrerMW1();
}
public void StartMP1Backward() {
MP1_WantActive = false;
if (!State.MP1_HasClearance) throw new NoClearanceException("MP1 hat keine Freigabe");
if (Actuators.MP1 == MotorState.Forward) throw new NoClearanceException("MP1 läuft bereits (vor)");
Actuators.MP1 = MotorState.Backward;
}
public void StopMP1() {
MP1_WantActive = false;
Actuators.MP1 = MotorState.Halt;
if (!ManualControlMode) StopStirrerMW1();
}
public void StartMP2Forward() {
if (!State.MP2_HasClearance) {
if (!State.MP2_HasDryRunClearance || !State.MP2_WantsPathClearance)
throw new NoClearanceException("MP2 hat keine Freigabe");
MP2_WantActive = true;
return;
}
MP2_WantActive = false;
if (Actuators.MP2 == MotorState.Backward) throw new NoClearanceException("MP2 läuft bereits (zurück)");
Actuators.MP2 = MotorState.Forward;
if (!ManualControlMode && State.MW2_FillLevel >= AutoStirrer) StartStirrerMW2();
}
public void StartMP2Backward() {
MP2_WantActive = false;
if (!State.MP2_HasClearance) throw new NoClearanceException("MP2 hat keine Freigabe");
if (Actuators.MP2 == MotorState.Forward) throw new NoClearanceException("MP2 läuft bereits (vor)");
Actuators.MP2 = MotorState.Backward;
}
public void StopMP2() {
MP2_WantActive = false;
Actuators.MP2 = MotorState.Halt;
if (!ManualControlMode) StopStirrerMW2();
}
public void EnableP12() {
if (!State.P12_HasClearance) {
if (!State.P12_HasDryRunClearance || !State.P12_WantsPathClearance)
throw new NoClearanceException("P1/2 hat keine Freigabe");
P12_WantActive = true;
return;
}
P12_WantActive = false;
if (!State.P12_HasClearance) throw new NoClearanceException("P1/2 hat keine Freigabe");
Actuators.P12 = MotorState.Forward;
}
public void DisableP12() {
P12_WantActive = false;
Actuators.P12 = MotorState.Halt;
}
public void EnableP3() {
if (!State.P3_HasClearance) {
if (!State.P3_HasDryRunClearance || !State.P3_WantsPathClearance)
throw new NoClearanceException("P3 hat keine Freigabe");
P3_WantActive = true;
return;
}
P3_WantActive = false;
Actuators.P3 = MotorState.Forward;
}
public void DisableP3() {
P3_WantActive = false;
Actuators.P3 = MotorState.Halt;
}
public void EnableP4() {
if (!State.P4_HasClearance) {
if (!State.P4_HasDryRunClearance || !State.P4_WantsPathClearance)
throw new NoClearanceException("P4 hat keine Freigabe");
P4_WantActive = true;
return;
}
P4_WantActive = false;
Actuators.P4 = MotorState.Forward;
}
public void DisableP4() {
P4_WantActive = false;
Actuators.P4 = MotorState.Halt;
}
public void EnableP5() {
if (!State.P5_HasClearance) {
if (!State.P5_HasDryRunClearance || !State.P5_WantsPathClearance)
throw new NoClearanceException("P5 hat keine Freigabe");
P5_WantActive = true;
return;
}
P5_WantActive = false;
Actuators.P5 = MotorState.Forward;
}
public void DisableP5() {
P5_WantActive = false;
Actuators.P5 = MotorState.Halt;
}
public void EnableP6() {
if (!State.P6_HasClearance) {
if (!State.P6_HasDryRunClearance || !State.P6_WantsPathClearance)
throw new NoClearanceException("P6 hat keine Freigabe");
P6_WantActive = true;
return;
}
P6_WantActive = false;
Actuators.P6 = MotorState.Forward;
}
public void DisableP6() {
P6_WantActive = false;
Actuators.P6 = MotorState.Halt;
}
}
}
@@ -0,0 +1,372 @@
namespace PamhagenSysCtrl.Helpers.Plc {
public readonly record struct PamhagenState {
public readonly record struct PumpState(
MotorState State, bool IsActive,
bool HasClearance, bool HasPathClearance, bool HasDryRunClearance,
bool WantsPathClearance, bool WantActive);
private readonly LegacyPamhagenPlc.SensorStates Sensors;
private readonly PamhagenPlc Plc;
private LegacyPamhagenPlc.ActuatorStates Actuators => Plc == null ? default : Plc.Actuators;
public bool ManualControlMode => Plc != null && Plc.ManualControlMode;
public bool OverridePathClearances => Plc != null && Plc.OverridePathClearances;
public bool OverrideDryRunClearances => Plc != null && Plc.OverrideDryRunClearances;
public long Valves => Actuators.Valves;
public readonly long OpenValves { get; }
public readonly long ClosedValves { get; }
public bool MW1_IsSelected => Sensors.TroughSelector == 1;
public bool MW2_IsSelected => Sensors.TroughSelector == 2;
public bool MW1_WantSelected => Actuators.GetV(58);
public bool MW2_WantSelected => !Actuators.GetV(58);
public readonly FillState MW1_FillLevel { get; }
public readonly FillState MW2_FillLevel { get; }
public FillState[] Tanks_FillLevel => Sensors.FillLevelTanks ?? [FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown, FillState.Unknown];
public FillState T1_FillLevel => Tanks_FillLevel[0];
public FillState T2_FillLevel => Tanks_FillLevel[1];
public FillState T3_FillLevel => Tanks_FillLevel[2];
public FillState T4_FillLevel => Tanks_FillLevel[3];
public FillState T5_FillLevel => Tanks_FillLevel[4];
public FillState T6_FillLevel => Tanks_FillLevel[5];
public FillState T7_FillLevel => Tanks_FillLevel[6];
public FillState T8_FillLevel => Tanks_FillLevel[7];
public FillState T9_FillLevel => Tanks_FillLevel[8];
public MotorState PressCrossAuger_State => Sensors.PressCrossAuger;
public bool PressCrossAuger_IsActive => PressCrossAuger_State == MotorState.Forward;
public bool TroughAuger_IsActive => Actuators.TroughAuger;
public bool Rebler_IsActive => Actuators.Rebler;
public bool MW1_Stirrer_IsActive => Actuators.StirrerMW1;
public bool MW2_Stirrer_IsActive => Actuators.StirrerMW2;
public bool Auger1_IsActive => Actuators.Auger1;
public bool Auger2_IsActive => Actuators.Auger2;
public bool Auger3_IsActive => Actuators.Auger3;
public bool WasteDisposal_IsActive => Auger1_IsActive || Auger2_IsActive || Auger3_IsActive;
public bool WasteDisposal_IsConsistent => (Auger1_IsActive && Auger2_IsActive && Auger3_IsActive) || (!Auger1_IsActive && !Auger2_IsActive && !Auger3_IsActive);
public MotorState MP1_State => Actuators.MP1;
public MotorState MP2_State => Actuators.MP2;
public MotorState P12_State => Actuators.P12;
public MotorState P3_State => Actuators.P3;
public MotorState P4_State => Actuators.P4;
public MotorState P5_State => Actuators.P5;
public MotorState P6_State => Actuators.P6;
public bool MP1_IsActive => Actuators.MP1 == MotorState.Forward || Actuators.MP1 == MotorState.Backward;
public bool MP2_IsActive => Actuators.MP2 == MotorState.Forward || Actuators.MP2 == MotorState.Backward;
public bool P12_IsActive => Actuators.P12 == MotorState.Forward;
public bool P3_IsActive => Actuators.P3 == MotorState.Forward;
public bool P4_IsActive => Actuators.P4 == MotorState.Forward;
public bool P5_IsActive => Actuators.P5 == MotorState.Forward;
public bool P6_IsActive => Actuators.P6 == MotorState.Forward;
public bool Press1_HasClearance => Sensors.ClearancePress1;
public bool Press2_HasClearance => Sensors.ClearancePress2;
public bool TroughAuger_HasClearance => Rebler_IsActive && !(MW1_IsSelected && MW1_FillLevel == FillState.Full) && !(MW2_IsSelected && MW2_FillLevel == FillState.Full);
private readonly bool MP1_PathClearance;
private readonly bool MP2_PathClearance;
private readonly bool P12_PathClearance;
private readonly bool P3_PathClearance;
private readonly bool P4_PathClearance;
private readonly bool P5_PathClearance;
private readonly bool P6_PathClearance;
private readonly bool MP1_DryRunClearance;
private readonly bool MP2_DryRunClearance;
private readonly bool P12_DryRunClearance;
private readonly bool P3_DryRunClearance;
private readonly bool P4_DryRunClearance;
private readonly bool P5_DryRunClearance;
private readonly bool P6_DryRunClearance;
public readonly bool MP1_WantsPathClearance { get; }
public readonly bool MP2_WantsPathClearance { get; }
public readonly bool P12_WantsPathClearance { get; }
public readonly bool P3_WantsPathClearance { get; }
public readonly bool P4_WantsPathClearance { get; }
public readonly bool P5_WantsPathClearance { get; }
public readonly bool P6_WantsPathClearance { get; }
public bool MP1_WantActive => Plc != null && Plc.MP1_WantActive;
public bool MP2_WantActive => Plc != null && Plc.MP2_WantActive;
public bool P12_WantActive => Plc != null && Plc.P12_WantActive;
public bool P3_WantActive => Plc != null && Plc.P3_WantActive;
public bool P4_WantActive => Plc != null && Plc.P4_WantActive;
public bool P5_WantActive => Plc != null && Plc.P5_WantActive;
public bool P6_WantActive => Plc != null && Plc.P6_WantActive;
public bool MP1_HasPathClearance => OverridePathClearances || MP1_PathClearance;
public bool MP2_HasPathClearance => OverridePathClearances || MP2_PathClearance;
public bool P12_HasPathClearance => OverridePathClearances || P12_PathClearance;
public bool P3_HasPathClearance => OverridePathClearances || P3_PathClearance;
public bool P4_HasPathClearance => OverridePathClearances || P4_PathClearance;
public bool P5_HasPathClearance => OverridePathClearances || P5_PathClearance;
public bool P6_HasPathClearance => OverridePathClearances || P6_PathClearance;
public bool MP1_HasDryRunClearance => OverrideDryRunClearances || MP1_DryRunClearance;
public bool MP2_HasDryRunClearance => OverrideDryRunClearances || MP2_DryRunClearance;
public bool P12_HasDryRunClearance => OverrideDryRunClearances || P12_DryRunClearance;
public bool P3_HasDryRunClearance => OverrideDryRunClearances || P3_DryRunClearance;
public bool P4_HasDryRunClearance => OverrideDryRunClearances || P4_DryRunClearance;
public bool P5_HasDryRunClearance => OverrideDryRunClearances || P5_DryRunClearance;
public bool P6_HasDryRunClearance => OverrideDryRunClearances || P6_DryRunClearance;
public bool MP1_HasClearance => MP1_HasDryRunClearance && MP1_HasPathClearance;
public bool MP2_HasClearance => MP2_HasDryRunClearance && MP2_HasPathClearance;
public bool P12_HasClearance => P12_HasDryRunClearance && P12_HasPathClearance;
public bool P3_HasClearance => P3_HasDryRunClearance && P3_HasPathClearance;
public bool P4_HasClearance => P4_HasDryRunClearance && P4_HasPathClearance;
public bool P5_HasClearance => P4_HasDryRunClearance && P5_HasPathClearance;
public bool P6_HasClearance => P6_HasDryRunClearance && P6_HasPathClearance;
public PumpState MP1 => new(MP1_State, MP1_IsActive, MP1_HasClearance, MP1_HasPathClearance, MP1_HasDryRunClearance, MP1_WantsPathClearance, MP1_WantActive);
public PumpState MP2 => new(MP2_State, MP2_IsActive, MP2_HasClearance, MP2_HasPathClearance, MP2_HasDryRunClearance, MP2_WantsPathClearance, MP2_WantActive);
public PumpState P12 => new(P12_State, P12_IsActive, P12_HasClearance, P12_HasPathClearance, P12_HasDryRunClearance, P12_WantsPathClearance, P12_WantActive);
public PumpState P3 => new(P3_State, P3_IsActive, P3_HasClearance, P3_HasPathClearance, P3_HasDryRunClearance, P3_WantsPathClearance, P3_WantActive);
public PumpState P4 => new(P4_State, P4_IsActive, P4_HasClearance, P4_HasPathClearance, P4_HasDryRunClearance, P4_WantsPathClearance, P4_WantActive);
public PumpState P5 => new(P5_State, P5_IsActive, P5_HasClearance, P5_HasPathClearance, P5_HasDryRunClearance, P5_WantsPathClearance, P5_WantActive);
public PumpState P6 => new(P6_State, P6_IsActive, P6_HasClearance, P6_HasPathClearance, P6_HasDryRunClearance, P6_WantsPathClearance, P6_WantActive);
public PamhagenState(IEnumerable<LegacyPamhagenPlc.SensorStates> sensors, PamhagenPlc plc) {
Sensors = sensors.First();
Plc = plc;
OpenValves = sensors.Take(Plc.ConsiderLastSensorsOpen).Aggregate(-1L, (m, s) => m & ~s.PressureSensors);
ClosedValves = sensors.Take(Plc.ConsiderLastSensorsClose).Aggregate(-1L, (m, s) => m & s.PressureSensors);
var mw1 = sensors.Take(Plc.ConsiderLastSensorsMW).Select(s => s.FillLevelMW1).Where(s => s != FillState.Unknown).Distinct().ToArray();
var mw2 = sensors.Take(Plc.ConsiderLastSensorsMW).Select(s => s.FillLevelMW2).Where(s => s != FillState.Unknown).Distinct().ToArray();
MW1_FillLevel =
mw1.Contains(FillState.Invalid) ? FillState.Invalid :
mw1.Length == 1 && mw1[0] == FillState.Empty ? mw1[0] :
mw1.Length == 1 ? mw1[0] + 1 :
mw1.Length >= 2 ? mw1[0] > mw1[1] ? mw1[0] : mw1[1] :
FillState.Unknown;
MW2_FillLevel =
mw2.Contains(FillState.Invalid) ? FillState.Invalid :
mw2.Length == 1 && mw2[0] == FillState.Empty ? mw2[0] :
mw2.Length == 1 ? mw2[0] + 1 :
mw2.Length >= 2 ? mw2[0] > mw2[1] ? mw2[0] : mw2[1] :
FillState.Unknown;
MP1_WantsPathClearance = WantsPathClearnace(this, Paths.MP1);
MP2_WantsPathClearance = WantsPathClearnace(this, Paths.MP2);
P12_WantsPathClearance = WantsPathClearnace(this, Paths.P12);
P3_WantsPathClearance = WantsPathClearnace(this, Paths.P3);
P4_WantsPathClearance = WantsPathClearnace(this, Paths.P4);
P5_WantsPathClearance = WantsPathClearnace(this, Paths.P5);
P6_WantsPathClearance = WantsPathClearnace(this, Paths.P6);
MP1_PathClearance = HasPathClearance(this, Paths.MP1);
MP2_PathClearance = HasPathClearance(this, Paths.MP2);
P12_PathClearance = HasPathClearance(this, Paths.P12);
P3_PathClearance = HasPathClearance(this, Paths.P3);
P4_PathClearance = HasPathClearance(this, Paths.P4);
P5_PathClearance = HasPathClearance(this, Paths.P5);
P6_PathClearance = HasPathClearance(this, Paths.P6);
MP1_DryRunClearance = MW1_FillLevel != FillState.Empty;
MP2_DryRunClearance = MW2_FillLevel != FillState.Empty;
P12_DryRunClearance = T1_FillLevel != FillState.Empty || T2_FillLevel != FillState.Empty;
P3_DryRunClearance = T3_FillLevel != FillState.Empty;
P4_DryRunClearance = T4_FillLevel != FillState.Empty;
P5_DryRunClearance = T5_FillLevel != FillState.Empty;
P6_DryRunClearance = true;
}
public bool Valve_IsClosed(int n) => (ClosedValves & (1L << (n - 1))) != 0;
public bool Valve_IsTransitioning(int n) => !Valve_IsClosed(n) && !Valve_IsOpen(n);
public bool Valve_IsOpen(int n) => (OpenValves & (1L << (n - 1))) != 0;
public bool? Valve_OpenState(int n) => Valve_IsOpen(n) ? true : Valve_IsClosed(n) ? false : null;
public bool Valve_WantOpen(int n) => Actuators.GetV(n);
public bool Valve_WantClosed(int n) => !Valve_WantOpen(n);
private record struct ValveStates(int[] Open, int[] Closed) {
public readonly ValveStateMask ToMask() => new(Open.Aggregate(0L, (m, n) => m | (1L << (n - 1))), Open.Concat(Closed).Aggregate(0L, (m, n) => m | (1L << (n - 1))));
public static ValveStates operator +(ValveStates a, ValveStates b) {
return new([.. a.Open, .. b.Open], [.. a.Closed, .. b.Closed]);
}
}
private record struct ValveStateMask(long States, long Mask) {
public static ValveStateMask[] From(IEnumerable<ValveStates> states) => [.. states.Select(s => s.ToMask())];
public static bool operator ==(long a, ValveStateMask b) => (a & b.Mask) == b.States;
public static bool operator >=(long a, ValveStateMask b) => (a & b.States) == b.States;
public static bool operator !=(long a, ValveStateMask b) => !(a == b);
public static bool operator <=(long a, ValveStateMask b) => !(a >= b);
}
private static bool HasSinglePathClearance(PamhagenState state, IEnumerable<ValveStateMask> masks) {
return masks.Any(m => state.Valves == m && state.OpenValves == m && ~state.ClosedValves == m);
}
private static bool HasAnyPathClearance(PamhagenState state, IEnumerable<ValveStateMask> masks) {
return masks.Any(m => state.Valves >= m && state.OpenValves >= m && ~state.ClosedValves >= m);
}
private static bool WantsSinglePathClearance(PamhagenState state, IEnumerable<ValveStateMask> masks) {
return masks.Any(m => state.Valves == m);
}
private static bool WantsAnyPathClearance(PamhagenState state, IEnumerable<ValveStateMask> masks) {
return masks.Any(m => state.Valves >= m);
}
private static bool HasPathClearance(PamhagenState state, (ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) masks) {
return
(HasSinglePathClearance(state, masks.Press1) && state.Press1_HasClearance) ||
(HasSinglePathClearance(state, masks.Press2) && state.Press2_HasClearance) ||
(HasSinglePathClearance(state, masks.Tanks[0]) && state.T1_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[1]) && state.T2_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[2]) && state.T3_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[3]) && state.T4_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[4]) && state.T5_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[5]) && state.T6_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[6]) && state.T7_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[7]) && state.T8_FillLevel != FillState.Full) ||
(HasSinglePathClearance(state, masks.Tanks[8]) && state.T9_FillLevel != FillState.Full) ||
false;
}
private static bool WantsPathClearnace(PamhagenState state, (ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) masks) {
return
(WantsSinglePathClearance(state, masks.Press1) && state.Press1_HasClearance) ||
(WantsSinglePathClearance(state, masks.Press2) && state.Press2_HasClearance) ||
(WantsSinglePathClearance(state, masks.Tanks[0]) && state.T1_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[1]) && state.T2_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[2]) && state.T3_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[3]) && state.T4_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[4]) && state.T5_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[5]) && state.T6_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[6]) && state.T7_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[7]) && state.T8_FillLevel != FillState.Full) ||
(WantsSinglePathClearance(state, masks.Tanks[8]) && state.T9_FillLevel != FillState.Full) ||
false;
}
private static bool HasPathClearance(PamhagenState state, (ValveStateMask[] Press1, ValveStateMask[] Press2) masks) {
return
(HasSinglePathClearance(state, masks.Press1) && state.Press1_HasClearance) ||
(HasSinglePathClearance(state, masks.Press2) && state.Press2_HasClearance) ||
false;
}
private static bool WantsPathClearnace(PamhagenState state, (ValveStateMask[] Press1, ValveStateMask[] Press2) masks) {
return
(WantsSinglePathClearance(state, masks.Press1) && state.Press1_HasClearance) ||
(WantsSinglePathClearance(state, masks.Press2) && state.Press2_HasClearance) ||
false;
}
private static readonly (
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) MP1,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) MP2,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) P12,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) P3,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) P4,
(ValveStateMask[] Press1, ValveStateMask[] Press2, ValveStateMask[][] Tanks) P5,
(ValveStateMask[] Press1, ValveStateMask[] Press2) P6
) Paths;
static PamhagenState() {
ValveStates[] wt_aa = [
new([12], [15, 16, 20]), // bypass
new([15, 18, 16], [11, 12, 14, 17, 19, 20]), // WT1
new([15, 17, 20], [11, 12, 18, 19, 21]), // WT2
new([15, 18, 19, 20], [11, 12, 14, 16, 17, 21]), // WT1+WT2
];
ValveStates[] wt_ab = [
new([15, 18, 14], [11, 12, 13, 16, 17, 19, 21]), // WT1
new([15, 17, 21], [11, 12, 13, 18, 19, 20]), // WT2
new([15, 18, 19, 21], [11, 12, 13, 14, 16, 17, 20]), // WT1+WT2
];
ValveStates[] wt_ba = [
new([11, 18, 16], [15, 12, 13, 14, 17, 19, 20]), // WT1
new([11, 17, 20], [15, 12, 13, 18, 19, 21]), // WT2
new([11, 18, 19, 20], [15, 12, 13, 14, 16, 17, 21]), // WT1+WT2
];
ValveStates[] wt_bb = [
new([13], [11, 14, 21]), // bypass
new([11, 18, 14], [15, 13, 16, 17, 19, 21]), // WT1
new([11, 17, 21], [15, 13, 18, 19, 20]), // WT2
new([11, 18, 19, 21], [15, 13, 14, 16, 17, 20]), // WT1+WT2
];
ValveStates[][] tanks = [
[new([22], [24, 26, 28]), new([23], [25, 27, 29])],
[new([24], [22, 26, 28]), new([25], [23, 27, 29])],
[new([26], [22, 24, 28]), new([27], [23, 25, 29])],
[new([28, 30], [22, 24, 26, 32, 34]), new([29, 31], [23, 25, 27, 33, 35])],
[new([28, 32], [22, 24, 26, 30, 34]), new([29, 33], [23, 25, 27, 31, 35])],
[new([28, 34, 37], [22, 24, 26, 30, 32, 39, 41, 43]), new([29, 35, 36], [23, 25, 27, 31, 33, 38, 40, 42])],
[new([28, 34, 39], [22, 24, 26, 30, 32, 37, 41, 43]), new([29, 35, 38], [23, 25, 27, 31, 33, 36, 40, 42])],
[new([28, 34, 41], [22, 24, 26, 30, 32, 37, 39, 43]), new([29, 35, 40], [23, 25, 27, 31, 33, 36, 38, 42])],
[new([28, 34, 43], [22, 24, 26, 30, 32, 37, 39, 41]), new([29, 35, 42], [23, 25, 27, 31, 33, 36, 38, 40])],
];
ValveStates[] mp1_pr1 = [
new([1, 6, 51], [3, 4, 5, 50, 52, 53, 55]),
new([4, 8, 52], [1, 2, 7, 50, 51, 53, 56]),
];
ValveStates[] mp1_pr2 = [
new([1, 6, 55], [3, 4, 5, 51, 54, 56, 57]),
new([4, 8, 56], [1, 2, 7, 52, 54, 55, 57]),
];
ValveStates mp1_wt_a = new([1, 5], [3, 4, 6]);
ValveStates mp1_wt_b = new([4, 7, 9], [1, 2, 8, 10]);
ValveStates[] mp1_a = [.. wt_aa.Select(m => mp1_wt_a + m), .. wt_ba.Select(m => mp1_wt_b + m)];
ValveStates[] mp1_b = [.. wt_ab.Select(m => mp1_wt_a + m), .. wt_bb.Select(m => mp1_wt_b + m)];
ValveStates[][] mp1_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. mp1_a.Select(m => m + t[0]), .. mp1_b.Select(m => m + t[1])])];
ValveStates[] mp2_pr1 = [
new([2, 8, 52], [3, 4, 7, 50, 51, 53, 56]),
new([3, 6, 51], [1, 2, 5, 50, 52, 53, 55]),
];
ValveStates[] mp2_pr2 = [
new([2, 8, 56], [3, 4, 7, 52, 54, 55, 57]),
new([3, 6, 55], [2, 1, 5, 51, 54, 56, 57]),
];
ValveStates mp2_wt_a = new([3, 5], [1, 2, 6]);
ValveStates mp2_wt_b = new([2, 7, 9], [3, 4, 8, 10]);
ValveStates[] mp2_a = [.. wt_aa.Select(m => mp2_wt_a + m), .. wt_ba.Select(m => mp2_wt_b + m)];
ValveStates[] mp2_b = [.. wt_ab.Select(m => mp2_wt_a + m), .. wt_bb.Select(m => mp2_wt_b + m)];
ValveStates[][] mp2_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. mp2_a.Select(m => m + t[0]), .. mp2_b.Select(m => m + t[1])])];
ValveStates[] p12_pr1 = [new([44, 46, 49, 50], [10, 45, 47, 48, 54, 51, 52, 53])];
ValveStates[] p12_pr2 = [new([44, 46, 49, 54], [10, 45, 47, 48, 50, 55, 56, 57])];
ValveStates[] p12_b = [new([44, 10], [9, 45, 46])];
ValveStates[][] p12_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. p12_b.Select(m => m + t[1])])];
ValveStates[] p3_pr1 = [new([45, 46, 49, 50], [10, 44, 47, 48, 51, 52, 53, 54])];
ValveStates[] p3_pr2 = [new([45, 46, 49, 54], [10, 44, 47, 48, 50, 55, 56, 57])];
ValveStates[] p3_b = [new([45, 10], [9, 44, 46])];
ValveStates[][] p3_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. p3_b.Select(m => m + t[1])])];
ValveStates[] p4_pr1 = [new([47, 49, 50], [46, 48, 51, 52, 53, 54])];
ValveStates[] p4_pr2 = [new([47, 49, 54], [46, 48, 50, 55, 56, 57])];
ValveStates[] p4_b = [new([47, 46, 10], [9, 44, 45, 48, 49])];
ValveStates[][] p4_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. p4_b.Select(m => m + t[1])])];
ValveStates[] p5_pr1 = [new([48, 49, 50], [46, 47, 51, 52, 53, 54])];
ValveStates[] p5_pr2 = [new([48, 49, 54], [46, 47, 50, 55, 56, 57])];
ValveStates[] p5_b = [new([48, 46, 10], [9, 44, 45, 47, 49])];
ValveStates[][] p5_t = [.. tanks.Select<ValveStates[], ValveStates[]>(t => [.. p5_b.Select(m => m + t[1])])];
ValveStates[] p6_pr1 = [new([53], [50, 51, 52, 57])];
ValveStates[] p6_pr2 = [new([57], [53, 54, 55, 56])];
Paths = (
(ValveStateMask.From(mp1_pr1), ValveStateMask.From(mp1_pr2), [.. mp1_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(mp2_pr1), ValveStateMask.From(mp2_pr2), [.. mp2_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p12_pr1), ValveStateMask.From(p12_pr2), [.. p12_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p3_pr1), ValveStateMask.From(p3_pr2), [.. p3_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p4_pr1), ValveStateMask.From(p4_pr2), [.. p4_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p5_pr1), ValveStateMask.From(p5_pr2), [.. p5_t.Select(t => ValveStateMask.From(t))]),
(ValveStateMask.From(p6_pr1), ValveStateMask.From(p6_pr2))
);
}
}
}
@@ -1,8 +1,8 @@
using System.IO; using System.IO;
using System.IO.Ports; using System.IO.Ports;
using System.Text; using System.Text;
namespace PamhagenSysCtrl.Helpers { namespace PamhagenSysCtrl.Helpers.Plc {
public class T1 : IDisposable { public class T1 : IDisposable {
public enum Mode { HALT = 1, RUN = 2, RUN_F = 3, HOLD = 4, ERROR = 6 } public enum Mode { HALT = 1, RUN = 2, RUN_F = 3, HOLD = 4, ERROR = 6 }
@@ -10,12 +10,16 @@ namespace PamhagenSysCtrl.Helpers {
protected SerialPort Serial; protected SerialPort Serial;
public string PortName => Serial.PortName;
public Status LastStatus { get; private set; }
public T1Error LastError { get; private set; }
public T1(string portName, Parity parity = Parity.Odd) { public T1(string portName, Parity parity = Parity.Odd) {
Serial = new SerialPort(portName, 9600, parity, 8, StopBits.One) { Serial = new SerialPort(portName, 9600, parity, 8, StopBits.One) {
Handshake = Handshake.None, Handshake = Handshake.None,
NewLine = "\r", NewLine = "\r",
ReadTimeout = 1000, ReadTimeout = 500,
WriteTimeout = 1000, WriteTimeout = 500,
DtrEnable = true, DtrEnable = true,
RtsEnable = true, RtsEnable = true,
Encoding = Encoding.ASCII, Encoding = Encoding.ASCII,
@@ -47,7 +51,7 @@ namespace PamhagenSysCtrl.Helpers {
Serial.WriteLine(line.ToString()); Serial.WriteLine(line.ToString());
} }
protected async Task<(string, string)> ReceiveResponse(string? expectedCmd = null) { protected async Task<(string Command, string Data)> ReceiveResponse(string? expectedCmd = null) {
var line = Serial.ReadLine(); var line = Serial.ReadLine();
if (line.Length < 8 || !line.StartsWith("(A") || !line.EndsWith(')') || line[^4] != '&') { if (line.Length < 8 || !line.StartsWith("(A") || !line.EndsWith(')') || line[^4] != '&') {
throw new FormatException("Invalid response from PLC (T1)"); throw new FormatException("Invalid response from PLC (T1)");
@@ -97,14 +101,16 @@ namespace PamhagenSysCtrl.Helpers {
public async Task<Status> StatusRead() { public async Task<Status> StatusRead() {
await SendCommand("ST", checksum: false); await SendCommand("ST", checksum: false);
var (_, res) = await ReceiveResponse("ST"); var (_, res) = await ReceiveResponse("ST");
return new Status((Mode)(res[3] - '0'), ((res[0] - '0') << 4) | (res[1] - '0')); LastStatus = new Status((Mode)(res[3] - '0'), ((res[0] - '0') << 4) | (res[1] - '0'));
return LastStatus;
} }
public async Task<T1Error> ErrorStatusRead() { public async Task<T1Error> ErrorStatusRead() {
await SendCommand("ER", checksum: false); await SendCommand("ER", checksum: false);
var (_, res) = await ReceiveResponse("ER"); var (_, res) = await ReceiveResponse("ER");
var code = Convert.ToInt32(res, 10); var code = Convert.ToInt32(res, 10);
return Enum.IsDefined(typeof(T1Error), code) ? (T1Error)code : T1Error.UNKNOWN_ERROR; LastError = Enum.IsDefined(typeof(T1Error), code) ? (T1Error)code : T1Error.UNKNOWN_ERROR;
return LastError;
} }
public Task<ushort[]> DataRead(string register, int length) { public Task<ushort[]> DataRead(string register, int length) {
@@ -123,7 +129,7 @@ namespace PamhagenSysCtrl.Helpers {
} }
public async Task<Status> DataWrite(params (string Register, ushort[] Values)[] registers) { public async Task<Status> DataWrite(params (string Register, ushort[] Values)[] registers) {
await SendCommand("DW", string.Join(',', registers.Select(r => $"{r.Register},{r.Values.Length}," + string.Join(',', r.Values.Select(v => $"{v:X}"))))); await SendCommand("DW", string.Join(',', registers.Select(r => $"{r.Register},{r.Values.Length}," + string.Join(',', r.Values.Select(v => $"{v:X4}")))));
var (_, res) = await ReceiveResponse("ST"); var (_, res) = await ReceiveResponse("ST");
return new Status((Mode)(res[3] - '0'), ((res[0] - '0') << 4) | (res[1] - '0')); return new Status((Mode)(res[3] - '0'), ((res[0] - '0') << 4) | (res[1] - '0'));
} }
+144
View File
@@ -0,0 +1,144 @@
namespace PamhagenSysCtrl.Helpers.Plc {
public enum T1Error {
UNKNOWN_ERROR = -1,
NO_ERROR = 0,
SYSTEM_POWER_ON = 10,
SYSTEM_POWER_OFF = 11,
RAM_CHECK_ERROR = 20,
PROGRAM_BCC_ERROR = 21,
BATTERY_VOLTAGE_DROP = 22,
EEPROM_BCC_ERROR = 23,
EEPROM_WARNING = 26,
SYSTEM_RAM_CHECK_ERROR = 30,
SYSTEM_ROM_BCC_ERROR = 31,
PERIPHERAL_LSI_ERROR = 32,
CLOCK_CALENDAR_CHECK_ERROR = 33,
ILLEGAL_SYSTEM_INTERRUPT = 34,
WD_TIMER_ERROR = 35,
IO_BUS_ERROR = 40,
IO_MISMATCH = 41,
IO_NO_ANSWER = 42,
IO_PARITY_ERROR = 43,
ILLEGAL_IO_REGISTER = 46,
COMMUNICATION_BUSY = 51,
FORMAT_ERROR = 52,
SCAN_TIME_OVER = 64,
NO_END_IRET_ERROR = 80,
PAIR_INSTRUCTION_ERROR = 81,
OPERAND_ERROR = 82,
INVALID_PROGRAM = 83,
NO_SUBROUTINE_ENTRY = 86,
NO_RET_ERROR = 87,
SUBROUTINE_NESTING_ERROR = 88,
LOOP_NESTING_ERROR = 89,
INVALID_FUNCTION_INSTRUCTION = 98,
PASSWORD_PROTECT = 106,
ILLEGAL_INSTRUCTION = 110,
REGISTER_ADDRESS_ERROR = 111,
BOUNDARY_ERROR = 112,
MEMORY_FULL = 113,
MODE_MISMATCH = 114,
REGISTER_ADDRESS_SIZE_ERROR = 115,
DUPLICATE_ENTRY_NR = 121,
}
public static class T1ErrorExtension {
public static bool IsError(this T1Error error) {
return error switch {
T1Error.NO_ERROR => false,
T1Error.SYSTEM_POWER_ON => false,
T1Error.SYSTEM_POWER_OFF => false,
_ => true,
};
}
public static string GetName(this T1Error error) {
return error switch {
T1Error.UNKNOWN_ERROR => "Unknown error",
T1Error.NO_ERROR => "No error",
T1Error.SYSTEM_POWER_ON => "System power on",
T1Error.SYSTEM_POWER_OFF => "System power off",
T1Error.RAM_CHECK_ERROR => "RAM check error",
T1Error.PROGRAM_BCC_ERROR => "Program BCC error",
T1Error.BATTERY_VOLTAGE_DROP => "Battery voltage drop",
T1Error.EEPROM_BCC_ERROR => "EEPROM BCC error",
T1Error.EEPROM_WARNING => "EEPROM warning",
T1Error.SYSTEM_RAM_CHECK_ERROR => "System RAM check error",
T1Error.SYSTEM_ROM_BCC_ERROR => "System ROM BCC error",
T1Error.PERIPHERAL_LSI_ERROR => "Peripheral LSI error",
T1Error.CLOCK_CALENDAR_CHECK_ERROR => "Clock-calendar check error",
T1Error.ILLEGAL_SYSTEM_INTERRUPT => "Illegal system interrupt",
T1Error.WD_TIMER_ERROR => "WD timer error",
T1Error.IO_BUS_ERROR => "I/O bus error",
T1Error.IO_MISMATCH => "I/O mismatch",
T1Error.IO_NO_ANSWER => "I/O no answer",
T1Error.IO_PARITY_ERROR => "I/O parity error",
T1Error.ILLEGAL_IO_REGISTER => "Illegal I/O register",
T1Error.COMMUNICATION_BUSY => "Communication busy",
T1Error.FORMAT_ERROR => "Format error",
T1Error.SCAN_TIME_OVER => "Scan time over",
T1Error.NO_END_IRET_ERROR => "No END/IRET error",
T1Error.PAIR_INSTRUCTION_ERROR => "Pair instruction error",
T1Error.OPERAND_ERROR => "Operand error",
T1Error.INVALID_PROGRAM => "Invalid program",
T1Error.NO_SUBROUTINE_ENTRY => "No subroutine entry",
T1Error.NO_RET_ERROR => "No RET error",
T1Error.SUBROUTINE_NESTING_ERROR => "Subroutine nesting error",
T1Error.LOOP_NESTING_ERROR => "Loop nesting error",
T1Error.INVALID_FUNCTION_INSTRUCTION => "Invalid function instruction",
T1Error.PASSWORD_PROTECT => "Password protect",
T1Error.ILLEGAL_INSTRUCTION => "Illegal instruction",
T1Error.REGISTER_ADDRESS_ERROR => "Register address error",
T1Error.BOUNDARY_ERROR => "Boundary error",
T1Error.MEMORY_FULL => "Memory full",
T1Error.MODE_MISMATCH => "Mode mismatch",
T1Error.REGISTER_ADDRESS_SIZE_ERROR => "Register address/size error",
T1Error.DUPLICATE_ENTRY_NR => "Duplicate entry No.",
_ => error.ToString(),
};
}
public static string? GetDescription(this T1Error error) {
return error switch {
T1Error.NO_ERROR => "No error recorded",
T1Error.SYSTEM_POWER_ON => "Power on (no error)",
T1Error.SYSTEM_POWER_OFF => "Power off (no error)",
T1Error.RAM_CHECK_ERROR => "RAM read/write error has been detected",
T1Error.PROGRAM_BCC_ERROR => "Program BCC (memory check code) error has been detected",
T1Error.BATTERY_VOLTAGE_DROP => "Data invalidity of RAM (back-up area) has been detected",
T1Error.EEPROM_BCC_ERROR => "BCC error of built-in EEPROM has been detected",
T1Error.EEPROM_WARNING => "EEPROM write operation has exceeded 100,000 times",
T1Error.SYSTEM_RAM_CHECK_ERROR => "System RAM read/write error has been detected",
T1Error.SYSTEM_ROM_BCC_ERROR => "BCC error of system ROM has been detected",
T1Error.PERIPHERAL_LSI_ERROR => "CPU hardware error has been detected",
T1Error.CLOCK_CALENDAR_CHECK_ERROR => "Invalid clock-calendar data has been detected",
T1Error.ILLEGAL_SYSTEM_INTERRUPT => "Unregistered interrupt has occurred",
T1Error.WD_TIMER_ERROR => "Watchdog timer error has occurred",
T1Error.IO_BUS_ERROR => "I/O bus error has been detected",
T1Error.IO_MISMATCH => "Registered I/O allocation table and actual I/O configuration are not identical",
T1Error.IO_NO_ANSWER => "No response from I/O module has been received",
T1Error.IO_PARITY_ERROR => "I/O bus parity error has been detected",
T1Error.ILLEGAL_IO_REGISTER => "Excess I/O register allocation has been detected",
T1Error.COMMUNICATION_BUSY => "The T1/T1S is busy in processing for other peripheral communications",
T1Error.FORMAT_ERROR => "Received request is invalid (detected by the T1/T1S)",
T1Error.SCAN_TIME_OVER => "Scan time has exceeded 200 ms",
T1Error.NO_END_IRET_ERROR => "END or IRET instruction has not been programmed",
T1Error.PAIR_INSTRUCTION_ERROR => "Illegal combination of pair instructions has been programmed",
T1Error.OPERAND_ERROR => "Illegal operand has been detected",
T1Error.INVALID_PROGRAM => "Program abnormality has been detected",
T1Error.NO_SUBROUTINE_ENTRY => "Subroutine corresponding to CALL instruction has not been programmed",
T1Error.NO_RET_ERROR => "RET (subroutine return) instruction has not been in a subroutine",
T1Error.SUBROUTINE_NESTING_ERROR => "CALL instruction has been programmed in a subroutine (subroutine nesting)",
T1Error.LOOP_NESTING_ERROR => "Nesting of FOR-NEXT loop has been programmed",
T1Error.INVALID_FUNCTION_INSTRUCTION => "Function instruction which is not supported by T1/T1S has been programmed",
T1Error.PASSWORD_PROTECT => "Requested operation is protected by password",
T1Error.ILLEGAL_INSTRUCTION => "Illegal instruction has been detected",
T1Error.REGISTER_ADDRESS_ERROR => "Excess register address range has been programmed",
T1Error.BOUNDARY_ERROR => "Illegal register address is designated by index modification",
T1Error.MEMORY_FULL => "Program memory is insufficient for the requested command",
T1Error.MODE_MISMATCH => "Received command is invalid in the current T1/T1S operation mode",
T1Error.REGISTER_ADDRESS_SIZE_ERROR => "Specified register range exceeds the limit",
T1Error.DUPLICATE_ENTRY_NR => "Multiple subroutines which has same subroutine number have been programmed",
_ => null,
};
}
}
}
@@ -1,4 +1,4 @@
namespace PamhagenSysCtrl.Helpers { namespace PamhagenSysCtrl.Helpers.Plc {
public class T1Exception(string msg) : Exception(msg) { public class T1Exception(string msg) : Exception(msg) {
} }
} }
-73
View File
@@ -1,73 +0,0 @@
namespace PamhagenSysCtrl.Helpers {
public enum T1Error {
UNKNOWN_ERROR = -1,
NO_ERROR = 0,
SYSTEM_POWER_ON = 10,
SYSTEM_POWER_OFF = 11,
RAM_CHECK_ERROR = 20,
PROGRAM_BCC_ERROR = 21,
BATTERY_VOLTAGE_DROP = 22,
EEPROM_BCC_ERROR = 23,
EEPROM_WARNING = 26,
SYSTEM_RAM_CHECK_ERROR = 30,
SYSTEM_ROM_BCC_ERROR = 31,
PERIPHERAL_LSI_ERROR = 32,
CLOCK_CALENDAR_CHECK_ERROR = 33,
ILLEGAL_SYSTEM_INTERRUPT = 34,
WD_TIMER_ERROR = 35,
IO_BUS_ERROR = 40,
IO_MISMATCH = 41,
IO_NO_ANSWER = 42,
IO_PARITY_ERROR = 43,
ILLEGAL_IO_REGISTER = 46,
COMMUNICATION_BUSY = 51,
FORMAT_ERROR = 52,
SCAN_TIME_OVER = 64,
NO_END_IRET_ERROR = 80,
PAIR_INSTRUCTION_ERROR = 81,
OPERAND_ERROR = 82,
INVALID_PROGRAM = 83,
NO_SUBROUTINE_ENTRY = 86,
NO_RET_ERROR = 87,
SUBROUTINE_NESTING_ERROR = 88,
LOOP_NESTING_ERROR = 89,
INVALID_FUNCTION_INSTRUCTION = 98,
PASSWORD_PROTECT = 106,
ILLEGAL_INSTRUCTION = 110,
REGISTER_ADDRESS_ERROR = 111,
BOUNDARY_ERROR = 112,
MEMORY_FULL = 113,
MODE_MISMATCH = 114,
REGISTER_ADDRESS_SIZE_ERROR = 115,
DUPLICATE_ENTRY_NR = 121,
}
public static class T1ErrorExtension {
public static bool IsError(this T1Error error) {
return error switch {
T1Error.NO_ERROR => false,
T1Error.SYSTEM_POWER_ON => false,
T1Error.SYSTEM_POWER_OFF => false,
_ => true,
};
}
public static string GetName(this T1Error error) {
return error switch {
T1Error.NO_ERROR => "No error",
T1Error.SYSTEM_POWER_ON => "System power on",
T1Error.SYSTEM_POWER_OFF => "System power off",
// TODO
_ => "Unknown error",
};
}
public static string? GetDescription(this T1Error error) {
return error switch {
T1Error.NO_ERROR => "No error recorded",
T1Error.SYSTEM_POWER_ON => "Power on (no error)",
T1Error.SYSTEM_POWER_OFF => "Power off (no error)",
// TODO
_ => null,
};
}
}
}
+77
View File
@@ -0,0 +1,77 @@
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Text.Json.Nodes;
namespace PamhagenSysCtrl.Helpers {
public static class UpdateService {
public static async Task<(string Version, string Url, long Size)?> GetLatestInstallerUrl(string feedUrl) {
try {
using var client = new HttpClient() {
Timeout = TimeSpan.FromSeconds(5),
};
client.DefaultRequestHeaders.UserAgent.Clear();
client.DefaultRequestHeaders.UserAgent.ParseAdd($"PamhagenSysCtrl/{App.Version} ({Environment.MachineName}, {Environment.OSVersion})");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new("application/json"));
using var res = await client.GetAsync(feedUrl);
if (!res.IsSuccessStatusCode)
return null;
var json = JsonNode.Parse(await res.Content.ReadAsStringAsync());
var latest = json!["data"]!.AsArray()[^1]!;
return ((string)latest["version"]!, (string)latest["url"]!, (long)latest["size"]!);
} catch {
return null;
}
}
public static async Task DownloadInstaller(string url, string filename, IProgress<double>? progress = null, CancellationToken cancellationToken = default) {
try {
using var client = new HttpClient() {
Timeout = TimeSpan.FromSeconds(5),
};
client.DefaultRequestHeaders.UserAgent.Clear();
client.DefaultRequestHeaders.UserAgent.ParseAdd($"PamhagenSysCtrl/{App.Version} ({Environment.MachineName}, {Environment.OSVersion})");
client.DefaultRequestHeaders.Accept.Clear();
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();
var contentLength = response.Content.Headers.ContentLength;
await using (var destination = new FileStream(filename, FileMode.Create)) {
await using var source = await response.Content.ReadAsStreamAsync(cancellationToken);
var buffer = new byte[81920];
long downloaded = 0;
int read;
while ((read = await source.ReadAsync(buffer, cancellationToken)) != 0) {
await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken);
downloaded += read;
if (contentLength.HasValue) {
progress?.Report((double)downloaded / contentLength.Value);
}
}
}
progress?.Report(1);
} catch {
File.Delete(filename);
throw;
}
}
public static void StartInstaller(string fileName) {
var startInfo = new ProcessStartInfo {
FileName = "msiexec.exe",
UseShellExecute = true,
Verb = "runas",
};
startInfo.ArgumentList.Add("/i");
startInfo.ArgumentList.Add(Path.GetFullPath(fileName));
if (Process.Start(startInfo) == null) {
throw new InvalidOperationException("Der Installer konnte nicht gestartet werden.");
}
}
}
}
+135
View File
@@ -0,0 +1,135 @@
using PIEHidNetCore;
using System.Drawing;
using System.IO;
namespace PamhagenSysCtrl.Helpers {
public class Xbe24Keyboard : IDisposable, PIEDataHandler, PIEErrorHandler {
public enum LedBank { UPPER = 0, LOWER = 1 }
public record struct KeyState(byte[] Rows) {
public readonly bool GetKey(int n) => (Rows[n / 6] & (1 << (n % 6))) != 0;
}
public static readonly Color Off = Color.FromArgb(0, 0, 0);
public static readonly Color DefaltBacklight = Color.FromArgb(3, 3, 3);
public static readonly Color Neutral = Color.FromArgb(4, 4, 4);
public static readonly Color Red = Color.FromArgb(255, 0, 0);
public static readonly Color Green = Color.FromArgb(0, 255, 0);
public static readonly Color Blue = Color.FromArgb(0, 0, 255);
public static readonly Color Yellow = Color.FromArgb(128, 128, 0);
public static readonly Color Magenta = Color.FromArgb(128, 0, 128);
public static readonly Color Cyan = Color.FromArgb(0, 128, 128);
public static readonly Color Orange = Color.FromArgb(255, 32, 0);
public static readonly Color Purple = Color.FromArgb(192, 32, 192);
public static readonly Color Lime = Color.FromArgb(64, 255, 4);
public static readonly Color LightRed = Color.FromArgb(32, 4, 4);
public static readonly Color LightGreen = Color.FromArgb(4, 32, 4);
public static readonly Color LightBlue = Color.FromArgb(4, 4, 32);
protected readonly PIEDevice XbeDevice;
private KeyState LastKeyState = new(new byte[4]);
private readonly (Color Color, bool Flashing)[] LastStates = new (Color, bool)[48];
public bool IsKeyPressed(int keyIndex) => LastKeyState.GetKey(keyIndex);
public event EventHandler<int>? KeyDown;
public event EventHandler<int>? KeyUp;
public event EventHandler<KeyState>? KeysChanged;
public event EventHandler<int>? Error;
public Xbe24Keyboard(int pid = 0x0555) {
foreach (var device in PIEDevice.EnumeratePIE()) {
if (device.Pid == pid && device.HidUsagePage == 0x0C && device.HidUsage == 1 && device.WriteLength > 1) {
XbeDevice = device;
break;
}
}
if (XbeDevice == null)
throw new ArgumentException($"No XBE keyboard with id 05f3:{pid:x4}");
int res;
if ((res = XbeDevice.SetupInterface()) != 0)
throw new IOException($"Unable to SetupInterface for XBE keyboard: {res}");
if ((res = XbeDevice.SetDataCallback(this)) != 0)
throw new IOException($"Unable to SetDataCallback for XBE keyboard: {res}");
if ((res = XbeDevice.SetErrorCallback(this)) != 0)
throw new IOException($"Unable to SetErrorCallback for XBE keyboard: {res}");
SetBacklight(DefaltBacklight);
}
public void Dispose() {
GC.SuppressFinalize(this);
SetBacklight(DefaltBacklight);
XbeDevice?.CloseInterface();
}
public void HandlePIEHidData(byte[] data, PIEDevice sourceDevice, int error) {
if (sourceDevice != XbeDevice)
return;
var current = new KeyState(data[3..8]);
var last = LastKeyState;
LastKeyState = current;
HandleKeyStateChange(current, last);
}
public void HandlePIEHidError(PIEDevice sourceDevice, int error) {
if (sourceDevice != XbeDevice)
return;
Error?.Invoke(this, error);
}
protected void HandleKeyStateChange(KeyState current, KeyState last) {
for (int i = 0; i < 24; i++) {
if (!last.GetKey(i) && current.GetKey(i)) {
KeyDown?.Invoke(this, i);
} else if (last.GetKey(i) && !current.GetKey(i)) {
KeyUp?.Invoke(this, i);
}
}
KeysChanged?.Invoke(this, current);
}
protected void SendCommand(byte command, byte[] payload) {
byte[] data = new byte[XbeDevice.WriteLength];
data[1] = command;
Array.Copy(payload, 0, data, 2, Math.Min(payload.Length, data.Length - 2));
int res;
do {
res = XbeDevice.WriteData(data);
} while (res == 404);
if (res != 0)
throw new IOException($"Unable to WriteData for XBE keyboard: {res}");
}
public void SetBacklight(int keyIndex, Color color, bool flashing = false) {
SetBacklight(keyIndex, LedBank.UPPER, color, flashing);
SetBacklight(keyIndex, LedBank.LOWER, color, flashing);
}
public void SetBacklight(int keyIndex, LedBank bank, Color color, bool flashing = false) {
if (LastStates[keyIndex * 2 + (int)bank] == (color, flashing))
return;
SendCommand(165, [(byte)keyIndex, (byte)bank, color.R, color.G, color.B, (byte)(flashing ? 1 : 0)]);
LastStates[keyIndex * 2 + (int)bank] = (color, flashing);
}
public void SetBacklight(Color color) {
SetBacklight(LedBank.UPPER, color);
SetBacklight(LedBank.LOWER, color);
}
public void SetBacklight(LedBank bank, Color color) {
SendCommand(166, [(byte)bank, color.R, color.G, color.B]);
for (int i = 0; i < LastStates.Length; i++)
LastStates[i] = (color, false);
}
public void SetFashFrequency(int ms) {
SendCommand(180, [(byte)(ms / 16)]);
}
}
}
Binary file not shown.
+11 -3
View File
@@ -1,8 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework> <TargetFramework>net10.0-windows</TargetFramework>
<Version>0.0.4</Version>
<Product>Anlagensteuerung Pamhagen</Product>
<AssemblyTitle>Anlagensteuerung Pamhagen</AssemblyTitle>
<AssemblyName>PamhagenSysCtrl</AssemblyName>
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
@@ -11,8 +16,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.10" /> <PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.11" />
<PackageReference Include="System.IO.Ports" Version="10.0.10" /> <PackageReference Include="System.IO.Ports" Version="10.0.11" />
<Reference Include="PIEHidNetCore">
<HintPath>PIEHidNetCore.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
</Project> </Project>
+28
View File
@@ -0,0 +1,28 @@
<Window x:Class="PamhagenSysCtrl.Windows.AboutWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Über - Anlagensteuerung Pamhagen"
Height="340" Width="560" ResizeMode="NoResize"
WindowStartupLocation="CenterOwner">
<Grid>
<TextBlock Margin="20,10" FontSize="12">
<Bold>Produkt:</Bold> Anlagensteuerung Pamhagen<LineBreak/>
<Bold>Beschreibung:</Bold> Schnittstelle zu einer SPS und grafische Benutzeroberfläche für die<LineBreak/>
<Bold Foreground="Transparent">Beschreibung:</Bold> Steuerung der Anlage<LineBreak/>
<Bold>Typ:</Bold> HMI- und SPS-Steuerungssoftware<LineBreak/>
<Bold>Version:</Bold> <Run x:Name="VersionText">0.0.0</Run><LineBreak/>
<Bold>Lizenz:</Bold> <Hyperlink NavigateUri="https://www.gnu.org/licenses/gpl-3.0.html" RequestNavigate="Hyperlink_RequestNavigate">GNU General Public License 3.0 (GPLv3)</Hyperlink><LineBreak/>
<Bold>Entwickler:</Bold> Lorenz Stechauner, Thomas Hilscher<LineBreak/>
<Bold>Kontakt:</Bold> <Hyperlink NavigateUri="mailto:lorenz.stechauner@necronda.net" RequestNavigate="Hyperlink_RequestNavigate">lorenz.stechauner@necronda.net</Hyperlink>, <Hyperlink NavigateUri="mailto:thomas.hilscher@gmail.com" RequestNavigate="Hyperlink_RequestNavigate">thomas.hilscher@gmail.com</Hyperlink><LineBreak/>
<Bold>Quellcode:</Bold> <Hyperlink NavigateUri="C:\Program Files\Anlagensteuerung Pamhagen\src" RequestNavigate="Hyperlink_RequestNavigate_Explorer">C:\Program Files\Anlagensteuerung Pamhagen\src</Hyperlink>,<LineBreak/>
<Bold Foreground="Transparent">Quellcode:</Bold> <Hyperlink NavigateUri="https://git.necronda.net/winzer/pamhagen-sysctrl" RequestNavigate="Hyperlink_RequestNavigate">https://git.necronda.net/winzer/pamhagen-sysctrl</Hyperlink><LineBreak/>
<Bold>Entwicklungszeitraum:</Bold> 2026<LineBreak/>
<LineBreak/>
<Bold>Verwendete Technologien:</Bold><LineBreak/>
Programmiersprache: C#<LineBreak/>
Framework: Windows Presentation Framework (WPF)<LineBreak/>
SPS-Schnittstelle: Serielle Verbindung<LineBreak/>
Paketierung: <Hyperlink NavigateUri="https://www.firegiant.com/wixtoolset/" RequestNavigate="Hyperlink_RequestNavigate">WiX Toolset</Hyperlink>
</TextBlock>
</Grid>
</Window>
@@ -0,0 +1,25 @@
using System.Diagnostics;
using System.Windows;
using System.Windows.Navigation;
namespace PamhagenSysCtrl.Windows {
public partial class AboutWindow : Window {
public AboutWindow() {
InitializeComponent();
VersionText.Text = App.Version.ToString();
}
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs evt) {
try {
Process.Start(new ProcessStartInfo(evt.Uri.AbsoluteUri) { UseShellExecute = true });
} catch { }
}
private void Hyperlink_RequestNavigate_Explorer(object sender, RequestNavigateEventArgs evt) {
try {
Process.Start("explorer.exe", evt.Uri.AbsoluteUri);
} catch { }
}
}
}
@@ -1,17 +1,18 @@
<Window x:Class="PamhagenSysCtrl.ManualControlWindow" <Window x:Class="PamhagenSysCtrl.ManualControlWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PamhagenSysCtrl" xmlns:local="clr-namespace:PamhagenSysCtrl"
mc:Ignorable="d" mc:Ignorable="d"
Title="Manuelle Steuerung - Anlagensteuerung Pamhagen" Height="550" Width="860"> Closed="OnClosed"
Title="Handbetrieb - Anlagensteuerung Pamhagen" Height="550" Width="860">
<Grid> <Grid>
<Grid x:Name="ValvePanel" Margin="5,5,5,5"> <Grid x:Name="ValvePanel" Margin="5,5,5,5">
</Grid> </Grid>
<TextBlock x:Name="Status" FontSize="14" <TextBlock x:Name="Status" FontSize="14"
Margin="350,50,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"/> Margin="350,10,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<Grid x:Name="ActuatorPanel" Margin="350,350,5,5"> <Grid x:Name="ActuatorPanel" Margin="350,350,5,5">
@@ -39,14 +40,14 @@
FontSize="14" BorderThickness="2" FontWeight="Bold" FontSize="14" BorderThickness="2" FontWeight="Bold"
Margin="135,10,10,10" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="135,10,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Click="Auger1Button_Click"/> Click="Auger1Button_Click"/>
<Button x:Name="BeltButton" Content="Förderband" Padding="2" Height="30" Width="150"
FontSize="14" BorderThickness="2" FontWeight="Bold"
Margin="135,45,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Click="BeltButton_Click"/>
<Button x:Name="Auger2Button" Content="Förderschnecke 2" Padding="2" Height="30" Width="150" <Button x:Name="Auger2Button" Content="Förderschnecke 2" Padding="2" Height="30" Width="150"
FontSize="14" BorderThickness="2" FontWeight="Bold" FontSize="14" BorderThickness="2" FontWeight="Bold"
Margin="135,80,10,10" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="135,45,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Click="Auger2Button_Click"/> Click="Auger2Button_Click"/>
<Button x:Name="Auger3Button" Content="Förderschnecke 3" Padding="2" Height="30" Width="150"
FontSize="14" BorderThickness="2" FontWeight="Bold"
Margin="135,80,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Click="Auger3Button_Click"/>
<Button x:Name="P6Button" Content="Pumpe 6" Padding="2" Height="30" Width="150" <Button x:Name="P6Button" Content="Pumpe 6" Padding="2" Height="30" Width="150"
FontSize="14" BorderThickness="2" FontWeight="Bold" FontSize="14" BorderThickness="2" FontWeight="Bold"
@@ -1,4 +1,5 @@
using PamhagenSysCtrl.Helpers; using PamhagenSysCtrl.Helpers;
using PamhagenSysCtrl.Windows;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
@@ -28,14 +29,14 @@ namespace PamhagenSysCtrl {
if (App.Plant == null || sender is not Button b) return; if (App.Plant == null || sender is not Button b) return;
var i = int.Parse($"{b.Content}"[1..]); var i = int.Parse($"{b.Content}"[1..]);
if (i == 58) { if (i == 58) {
if (App.Plant.WantMW1Selected) { if (App.Plant.State.MW1_WantSelected) {
App.Plant.SelectMW2(); App.Plant.SelectMW2();
} else { } else {
App.Plant.SelectMW1(); App.Plant.SelectMW1();
} }
} else if (i > 57) { } else if (i > 57) {
return; return;
} else if (App.Plant.WantValveOpen(i)) { } else if (App.Plant.State.Valve_WantOpen(i)) {
App.Plant.CloseV(i); App.Plant.CloseV(i);
} else { } else {
App.Plant.OpenV(i); App.Plant.OpenV(i);
@@ -47,12 +48,20 @@ namespace PamhagenSysCtrl {
App.Plant?.Update += OnUpdate; App.Plant?.Update += OnUpdate;
} }
private void OnClosed(object sender, EventArgs evt) {
foreach (var win in Application.Current.Windows) {
if (win is PlantSchemeWindow w) {
w.Menu_Settings_ManualControl.IsChecked = false;
}
}
}
public void OnUpdate(object? sender, PlantEventArgs evt) { public void OnUpdate(object? sender, PlantEventArgs evt) {
if (sender is not PamhagenPlant plant) return; if (sender is not PamhagenPlant plant) return;
for (int i = 1; i <= 58; i++) { for (int i = 1; i <= 58; i++) {
var target = plant.WantValveClosed(i); var target = plant.State.Valve_WantClosed(i);
var actual = plant.IsValveClosed(i); var actual = plant.State.Valve_IsClosed(i);
if (ValvePanel.Children[i - 1] is not Button b) if (ValvePanel.Children[i - 1] is not Button b)
continue; continue;
b.BorderBrush = target ? Brushes.DarkRed : Brushes.DarkGreen; b.BorderBrush = target ? Brushes.DarkRed : Brushes.DarkGreen;
@@ -60,98 +69,98 @@ namespace PamhagenSysCtrl {
b.Background = actual ? Brushes.MistyRose : Brushes.MintCream; b.Background = actual ? Brushes.MistyRose : Brushes.MintCream;
} }
ReblerButton.BorderBrush = plant.IsReblerActive ? Brushes.DarkGreen : Brushes.DarkRed; ReblerButton.BorderBrush = plant.State.Rebler_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
ReblerButton.Foreground = plant.IsReblerActive ? Brushes.DarkGreen : Brushes.DarkRed; ReblerButton.Foreground = plant.State.Rebler_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
ReblerButton.Background = plant.IsReblerActive ? Brushes.MintCream: Brushes.MistyRose; ReblerButton.Background = plant.State.Rebler_IsActive ? Brushes.MintCream: Brushes.MistyRose;
TroughAugerButton.BorderBrush = plant.IsTroughAugerActive ? Brushes.DarkGreen : Brushes.DarkRed; TroughAugerButton.BorderBrush = plant.State.TroughAuger_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
TroughAugerButton.Foreground = plant.IsTroughAugerActive ? Brushes.DarkGreen : Brushes.DarkRed; TroughAugerButton.Foreground = plant.State.TroughAuger_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
TroughAugerButton.Background = plant.IsTroughAugerActive ? Brushes.MintCream : Brushes.MistyRose; TroughAugerButton.Background = plant.State.TroughAuger_IsActive ? Brushes.MintCream : Brushes.MistyRose;
StirrerMW1Button.BorderBrush = plant.IsStirrerMW1Active ? Brushes.DarkGreen : Brushes.DarkRed; StirrerMW1Button.BorderBrush = plant.State.MW1_Stirrer_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
StirrerMW1Button.Foreground = plant.IsStirrerMW1Active ? Brushes.DarkGreen : Brushes.DarkRed; StirrerMW1Button.Foreground = plant.State.MW1_Stirrer_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
StirrerMW1Button.Background = plant.IsStirrerMW1Active ? Brushes.MintCream : Brushes.MistyRose; StirrerMW1Button.Background = plant.State.MW1_Stirrer_IsActive ? Brushes.MintCream : Brushes.MistyRose;
StirrerMW2Button.BorderBrush = plant.IsStirrerMW2Active ? Brushes.DarkGreen : Brushes.DarkRed; StirrerMW2Button.BorderBrush = plant.State.MW2_Stirrer_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
StirrerMW2Button.Foreground = plant.IsStirrerMW2Active ? Brushes.DarkGreen : Brushes.DarkRed; StirrerMW2Button.Foreground = plant.State.MW2_Stirrer_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
StirrerMW2Button.Background = plant.IsStirrerMW2Active ? Brushes.MintCream : Brushes.MistyRose; StirrerMW2Button.Background = plant.State.MW2_Stirrer_IsActive ? Brushes.MintCream : Brushes.MistyRose;
BeltButton.BorderBrush = plant.IsBeltActive ? Brushes.DarkGreen : Brushes.DarkRed; Auger1Button.BorderBrush = plant.State.Auger1_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
BeltButton.Foreground = plant.IsBeltActive ? Brushes.DarkGreen : Brushes.DarkRed; Auger1Button.Foreground = plant.State.Auger1_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
BeltButton.Background = plant.IsBeltActive ? Brushes.MintCream : Brushes.MistyRose; Auger1Button.Background = plant.State.Auger1_IsActive ? Brushes.MintCream : Brushes.MistyRose;
Auger1Button.BorderBrush = plant.IsAuger1Active ? Brushes.DarkGreen : Brushes.DarkRed; Auger2Button.BorderBrush = plant.State.Auger2_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
Auger1Button.Foreground = plant.IsAuger1Active ? Brushes.DarkGreen : Brushes.DarkRed; Auger2Button.Foreground = plant.State.Auger2_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
Auger1Button.Background = plant.IsAuger1Active ? Brushes.MintCream : Brushes.MistyRose; Auger2Button.Background = plant.State.Auger2_IsActive ? Brushes.MintCream : Brushes.MistyRose;
Auger2Button.BorderBrush = plant.IsAuger2Active ? Brushes.DarkGreen : Brushes.DarkRed; Auger3Button.BorderBrush = plant.State.Auger3_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
Auger2Button.Foreground = plant.IsAuger2Active ? Brushes.DarkGreen : Brushes.DarkRed; Auger3Button.Foreground = plant.State.Auger3_IsActive ? Brushes.DarkGreen : Brushes.DarkRed;
Auger2Button.Background = plant.IsAuger2Active ? Brushes.MintCream : Brushes.MistyRose; Auger3Button.Background = plant.State.Auger3_IsActive ? Brushes.MintCream : Brushes.MistyRose;
MP1BackwardButton.BorderBrush = plant.MP1 == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray; MP1BackwardButton.BorderBrush = plant.State.MP1_State == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray;
MP1BackwardButton.Foreground = plant.MP1 == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray; MP1BackwardButton.Foreground = plant.State.MP1_State == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray;
MP1BackwardButton.Background = plant.MP1 == MotorState.Backward ? Brushes.MintCream : Brushes.WhiteSmoke; MP1BackwardButton.Background = plant.State.MP1_State == MotorState.Backward ? Brushes.MintCream : Brushes.WhiteSmoke;
MP1HaltButton.BorderBrush = plant.MP1 == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray; MP1HaltButton.BorderBrush = plant.State.MP1_State == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray;
MP1HaltButton.Foreground = plant.MP1 == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray; MP1HaltButton.Foreground = plant.State.MP1_State == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray;
MP1HaltButton.Background = plant.MP1 == MotorState.Halt ? Brushes.MistyRose : Brushes.WhiteSmoke; MP1HaltButton.Background = plant.State.MP1_State == MotorState.Halt ? Brushes.MistyRose : Brushes.WhiteSmoke;
MP1ForwardButton.BorderBrush = plant.MP1 == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray; MP1ForwardButton.BorderBrush = plant.State.MP1_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray;
MP1ForwardButton.Foreground = plant.MP1 == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray; MP1ForwardButton.Foreground = plant.State.MP1_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray;
MP1ForwardButton.Background = plant.MP1 == MotorState.Forward ? Brushes.MintCream: Brushes.WhiteSmoke; MP1ForwardButton.Background = plant.State.MP1_State == MotorState.Forward ? Brushes.MintCream: Brushes.WhiteSmoke;
MP2BackwardButton.BorderBrush = plant.MP2 == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray; MP2BackwardButton.BorderBrush = plant.State.MP2_State == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray;
MP2BackwardButton.Foreground = plant.MP2 == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray; MP2BackwardButton.Foreground = plant.State.MP2_State == MotorState.Backward ? Brushes.DarkGreen : Brushes.SlateGray;
MP2BackwardButton.Background = plant.MP2 == MotorState.Backward ? Brushes.MintCream : Brushes.WhiteSmoke; MP2BackwardButton.Background = plant.State.MP2_State == MotorState.Backward ? Brushes.MintCream : Brushes.WhiteSmoke;
MP2HaltButton.BorderBrush = plant.MP2 == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray; MP2HaltButton.BorderBrush = plant.State.MP2_State == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray;
MP2HaltButton.Foreground = plant.MP2 == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray; MP2HaltButton.Foreground = plant.State.MP2_State == MotorState.Halt ? Brushes.DarkRed : Brushes.SlateGray;
MP2HaltButton.Background = plant.MP2 == MotorState.Halt ? Brushes.MistyRose : Brushes.WhiteSmoke; MP2HaltButton.Background = plant.State.MP2_State == MotorState.Halt ? Brushes.MistyRose : Brushes.WhiteSmoke;
MP2ForwardButton.BorderBrush = plant.MP2 == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray; MP2ForwardButton.BorderBrush = plant.State.MP2_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray;
MP2ForwardButton.Foreground = plant.MP2 == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray; MP2ForwardButton.Foreground = plant.State.MP2_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.SlateGray;
MP2ForwardButton.Background = plant.MP2 == MotorState.Forward ? Brushes.MintCream : Brushes.WhiteSmoke; MP2ForwardButton.Background = plant.State.MP2_State == MotorState.Forward ? Brushes.MintCream : Brushes.WhiteSmoke;
P12Button.BorderBrush = plant.P12 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P12Button.BorderBrush = plant.State.P12_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P12Button.Foreground = plant.P12 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P12Button.Foreground = plant.State.P12_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P12Button.Background = plant.P12 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P12Button.Background = plant.State.P12_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
P3Button.BorderBrush = plant.P3 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P3Button.BorderBrush = plant.State.P3_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P3Button.Foreground = plant.P3 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P3Button.Foreground = plant.State.P3_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P3Button.Background = plant.P3 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P3Button.Background = plant.State.P3_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
P4Button.BorderBrush = plant.P4 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P4Button.BorderBrush = plant.State.P4_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P4Button.Foreground = plant.P4 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P4Button.Foreground = plant.State.P4_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P4Button.Background = plant.P4 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P4Button.Background = plant.State.P4_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
P5Button.BorderBrush = plant.P5 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P5Button.BorderBrush = plant.State.P5_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P5Button.Foreground = plant.P5 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P5Button.Foreground = plant.State.P5_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P5Button.Background = plant.P5 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P5Button.Background = plant.State.P5_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
P6Button.BorderBrush = plant.P6 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P6Button.BorderBrush = plant.State.P6_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P6Button.Foreground = plant.P6 == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed; P6Button.Foreground = plant.State.P6_State == MotorState.Forward ? Brushes.DarkGreen : Brushes.DarkRed;
P6Button.Background = plant.P6 == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose; P6Button.Background = plant.State.P6_State == MotorState.Forward ? Brushes.MintCream : Brushes.MistyRose;
Status.Text = $""" Status.Text = $"""
Gradation °Oe: {plant.GradationOe:N2} Presse Querschnecke: {plant.State.PressCrossAuger_State}
Presse Querschnecke: {plant.PresseQuerSchnecke} Freigaben:
Füllstände: Presse 1: {plant.State.Press1_HasClearance}
Maischewanne 1: {plant.FillLevelMW1} (ausgewählt: {plant.WantMW1Selected}, Status: {plant.IsMWSelected}) Presse 2: {plant.State.Press2_HasClearance}
Maischewanne 2: {plant.FillLevelMW2} (ausgewählt: {plant.WantMW2Selected}, Status: {plant.IsMW2Selected}) Füllstände:
Presse 1: {plant.FillLevelPress1} Maischewanne 1: {plant.State.MW1_FillLevel} (ausgewählt-soll: {plant.State.MW1_WantSelected}, ausgewählt-ist: {plant.State.MW1_IsSelected})
Presse 2: {plant.FillLevelPress2} Maischewanne 2: {plant.State.MW2_FillLevel} (ausgewählt-soll: {plant.State.MW2_WantSelected}, ausgewählt-ist: {plant.State.MW2_IsSelected})
Tank 1: {plant.FillLevelT1} Tank 1: {plant.State.T1_FillLevel}
Tank 2: {plant.FillLevelT2} Tank 2: {plant.State.T2_FillLevel}
Tank 3: {plant.FillLevelT3} Tank 3: {plant.State.T3_FillLevel}
Tank 4: {plant.FillLevelT4} Tank 4: {plant.State.T4_FillLevel}
Tank 5: {plant.FillLevelT5} Tank 5: {plant.State.T5_FillLevel}
Tank 6: {plant.FillLevelT6} Tank 6: {plant.State.T6_FillLevel}
Tank 7: {plant.FillLevelT7} Tank 7: {plant.State.T7_FillLevel}
Tank 8: {plant.FillLevelT8} Tank 8: {plant.State.T8_FillLevel}
Tank 9: {plant.FillLevelT9} Tank 9: {plant.State.T9_FillLevel}
"""; """;
} }
private void TroughAugerButton_Click(object? sender, RoutedEventArgs? evt) { private void TroughAugerButton_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.IsTroughAugerActive) { if (App.Plant.State.TroughAuger_IsActive) {
App.Plant.StopTroughAuger(); App.Plant.StopTroughAuger();
} else { } else {
App.Plant.StartTroughAuger(); App.Plant.StartTroughAuger();
@@ -164,7 +173,7 @@ namespace PamhagenSysCtrl {
private void ReblerButton_Click(object? sender, RoutedEventArgs? evt) { private void ReblerButton_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.IsReblerActive) { if (App.Plant.State.Rebler_IsActive) {
App.Plant.StopRebler(); App.Plant.StopRebler();
} else { } else {
App.Plant.StartRebler(); App.Plant.StartRebler();
@@ -177,7 +186,7 @@ namespace PamhagenSysCtrl {
private void StirrerMW1Button_Click(object? sender, RoutedEventArgs? evt) { private void StirrerMW1Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.IsStirrerMW1Active) { if (App.Plant.State.MW1_Stirrer_IsActive) {
App.Plant.StopStirrerMW1(); App.Plant.StopStirrerMW1();
} else { } else {
App.Plant.StartStirrerMW1(); App.Plant.StartStirrerMW1();
@@ -190,7 +199,7 @@ namespace PamhagenSysCtrl {
private void StirrerMW2Button_Click(object? sender, RoutedEventArgs? evt) { private void StirrerMW2Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.IsStirrerMW2Active) { if (App.Plant.State.MW2_Stirrer_IsActive) {
App.Plant.StopStirrerMW2(); App.Plant.StopStirrerMW2();
} else { } else {
App.Plant.StartStirrerMW2(); App.Plant.StartStirrerMW2();
@@ -200,23 +209,10 @@ namespace PamhagenSysCtrl {
} }
} }
private void BeltButton_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return;
try {
if (App.Plant.IsBeltActive) {
App.Plant.StopBelt();
} else {
App.Plant.StartBelt();
}
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void Auger1Button_Click(object? sender, RoutedEventArgs? evt) { private void Auger1Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.IsAuger1Active) { if (App.Plant.State.Auger1_IsActive) {
App.Plant.StopAuger1(); App.Plant.StopAuger1();
} else { } else {
App.Plant.StartAuger1(); App.Plant.StartAuger1();
@@ -229,7 +225,7 @@ namespace PamhagenSysCtrl {
private void Auger2Button_Click(object? sender, RoutedEventArgs? evt) { private void Auger2Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.IsAuger2Active) { if (App.Plant.State.Auger2_IsActive) {
App.Plant.StopAuger2(); App.Plant.StopAuger2();
} else { } else {
App.Plant.StartAuger2(); App.Plant.StartAuger2();
@@ -239,6 +235,19 @@ namespace PamhagenSysCtrl {
} }
} }
private void Auger3Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return;
try {
if (App.Plant.State.Auger3_IsActive) {
App.Plant.StopAuger3();
} else {
App.Plant.StartAuger3();
}
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void MP1BackwardButton_Click(object? sender, RoutedEventArgs? evt) { private void MP1BackwardButton_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
@@ -297,10 +306,10 @@ namespace PamhagenSysCtrl {
private void P12Button_Click(object? sender, RoutedEventArgs? evt) { private void P12Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.P12 == MotorState.Forward) { if (App.Plant.State.P12_IsActive) {
App.Plant.StopP12(); App.Plant.DisableP12();
} else { } else {
App.Plant.StartP12(); App.Plant.EnableP12();
} }
} catch (NoClearanceException exc) { } catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
@@ -310,10 +319,10 @@ namespace PamhagenSysCtrl {
private void P3Button_Click(object? sender, RoutedEventArgs? evt) { private void P3Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.P3 == MotorState.Forward) { if (App.Plant.State.P3_IsActive) {
App.Plant.StopP3(); App.Plant.DisableP3();
} else { } else {
App.Plant.StartP3(); App.Plant.EnableP3();
} }
} catch (NoClearanceException exc) { } catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
@@ -323,10 +332,10 @@ namespace PamhagenSysCtrl {
private void P4Button_Click(object? sender, RoutedEventArgs? evt) { private void P4Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.P4 == MotorState.Forward) { if (App.Plant.State.P4_IsActive) {
App.Plant.StopP4(); App.Plant.DisableP4();
} else { } else {
App.Plant.StartP4(); App.Plant.EnableP4();
} }
} catch (NoClearanceException exc) { } catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
@@ -336,10 +345,10 @@ namespace PamhagenSysCtrl {
private void P5Button_Click(object? sender, RoutedEventArgs? evt) { private void P5Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.P5 == MotorState.Forward) { if (App.Plant.State.P5_IsActive) {
App.Plant.StopP5(); App.Plant.DisableP5();
} else { } else {
App.Plant.StartP5(); App.Plant.EnableP5();
} }
} catch (NoClearanceException exc) { } catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
@@ -349,10 +358,10 @@ namespace PamhagenSysCtrl {
private void P6Button_Click(object? sender, RoutedEventArgs? evt) { private void P6Button_Click(object? sender, RoutedEventArgs? evt) {
if (App.Plant == null) return; if (App.Plant == null) return;
try { try {
if (App.Plant.P6 == MotorState.Forward) { if (App.Plant.State.P6_IsActive) {
App.Plant.StopP6(); App.Plant.DisableP6();
} else { } else {
App.Plant.StartP6(); App.Plant.EnableP6();
} }
} catch (NoClearanceException exc) { } catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
+224 -8
View File
@@ -1,10 +1,13 @@
<Window x:Class="PamhagenSysCtrl.Windows.PlantSchemeWindow" <Window x:Class="PamhagenSysCtrl.Windows.PlantSchemeWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PamhagenSysCtrl.Windows" xmlns:local="clr-namespace:PamhagenSysCtrl.Windows"
Title="Anlagensteuerung Pamhagen" Height="950" Width="1600" MinWidth="1500" MinHeight="950"> Closed="OnClosed"
SizeChanged="OnSizeChanged"
Title="Anlagensteuerung Pamhagen"
Height="800" Width="1400" MinWidth="1000" MinHeight="500">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="19"/> <RowDefinition Height="19"/>
@@ -14,11 +17,23 @@
<Menu Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="LightGray" Background="White"> <Menu Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="LightGray" Background="White">
<MenuItem Header="Einstellungen"> <MenuItem Header="Einstellungen">
<MenuItem x:Name="Menu_Settings_ManualControl" Header="Manuelle Steuerung" Click="Menu_Settings_ManualControl_Click"> <MenuItem x:Name="Menu_Settings_ManualControl" Header="Handbetrieb" IsCheckable="True"
Checked="Menu_Settings_ManualControl_Changed"
Unchecked="Menu_Settings_ManualControl_Changed">
<MenuItem.Icon> <MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xF271;"/> <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xF271;"/>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem x:Name="Menu_Settings_OverridePathClearances" Header="Pfad-Freigaben überbrücken" IsCheckable="True"
Checked="Menu_Settings_OverridePathClearances_Changed"
Unchecked="Menu_Settings_OverridePathClearances_Changed">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE785;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_Settings_OverrideDryRunClearances" Header="Trockenlauf-Freigaben überbrücken" IsCheckable="True"
Checked="Menu_Settings_OverrideDryRunClearances_Changed"
Unchecked="Menu_Settings_OverrideDryRunClearances_Changed"/>
<Separator/> <Separator/>
<MenuItem x:Name="Menu_Settings_Config" Header="Konfigurationsdatei öffnen..." Click="Menu_Settings_Config_Click"> <MenuItem x:Name="Menu_Settings_Config" Header="Konfigurationsdatei öffnen..." Click="Menu_Settings_Config_Click">
<MenuItem.Icon> <MenuItem.Icon>
@@ -32,23 +47,224 @@
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
<MenuItem Header="Hilfe"> <MenuItem Header="Hilfe">
<MenuItem Header="Über"> <MenuItem Header="Über" Click="Menu_Help_About_Click">
<MenuItem.Icon> <MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE946;"/> <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE946;"/>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem x:Name="Menu_Help_CheckForUpdates" Header="Nach Updates suchen..." Click="Menu_Help_CheckForUpdates_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE895;"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem> </MenuItem>
</Menu> </Menu>
<Grid Grid.Row="1" ClipToBounds="True"> <Grid Grid.Row="1" ClipToBounds="True">
<Canvas x:Name="SchemeCanvas" Width="1550" Height="900" VerticalAlignment="Center" HorizontalAlignment="Center" SnapsToDevicePixels="True" <Canvas x:Name="SchemeCanvas" VerticalAlignment="Center" HorizontalAlignment="Center" SnapsToDevicePixels="True" Grid.ColumnSpan="2" Margin="25,0,0,0"
MouseMove="SchemeCanvas_MouseMove" MouseLeftButtonDown="SchemeCanvas_MouseLeftButtonDown" MouseLeftButtonUp="SchemeCanvas_MouseLeftButtonUp" Grid.ColumnSpan="2" Margin="25,0,0,0"> MouseMove="SchemeCanvas_MouseMove" MouseWheel="SchemeCanvas_MouseWheel"
MouseLeftButtonDown="SchemeCanvas_MouseLeftButtonDown" MouseLeftButtonUp="SchemeCanvas_MouseLeftButtonUp">
</Canvas> </Canvas>
<Border Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Bottom" Padding="10,10,20,20" Margin="0,0,-11,-11"
Background="WhiteSmoke"
BorderBrush="Gray" BorderThickness="1" CornerRadius="10">
<Grid x:Name="FastSelectButtonsRight">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="160"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="5"/>
<RowDefinition Height="50"/>
<RowDefinition Height="5"/>
<RowDefinition Height="50"/>
<RowDefinition Height="5"/>
<RowDefinition Height="50"/>
<RowDefinition Height="5"/>
<RowDefinition Height="50"/>
<RowDefinition Height="5"/>
<RowDefinition Height="50"/>
<RowDefinition Height="5"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Button x:Name="FastSelectButton_MW1_Press1" Grid.Column="0" Grid.Row="0"
Content="MW1 &#x2B9E; Presse 1" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW2_Press1" Grid.Column="2" Grid.Row="0"
Content="MW2 &#x2B9E; Presse 1" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW1_Press2" Grid.Column="0" Grid.Row="2"
Content="MW1 &#x2B9E; Presse 2" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW2_Press2" Grid.Column="2" Grid.Row="2"
Content="MW2 &#x2B9E; Presse 2" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW1_Tank1" Grid.Column="0" Grid.Row="4"
Content="MW1 &#x2B9E; Tank 1" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW2_Tank1" Grid.Column="2" Grid.Row="4"
Content="MW2 &#x2B9E; Tank 1" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW1_Tank2" Grid.Column="0" Grid.Row="6"
Content="MW1 &#x2B9E; Tank 2" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW2_Tank2" Grid.Column="2" Grid.Row="6"
Content="MW2 &#x2B9E; Tank 2" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW1_Tank3" Grid.Column="0" Grid.Row="8"
Content="MW1 &#x2B9E; Tank 3" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW2_Tank3" Grid.Column="2" Grid.Row="8"
Content="MW2 &#x2B9E; Tank 3" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW1_Tank4" Grid.Column="0" Grid.Row="10"
Content="MW1 &#x2B9E; Tank 4" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW2_Tank4" Grid.Column="2" Grid.Row="10"
Content="MW2 &#x2B9E; Tank 4" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW1_Tank5" Grid.Column="0" Grid.Row="12"
Content="MW1 &#x2B9E; Tank 5" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW2_Tank5" Grid.Column="2" Grid.Row="12"
Content="MW2 &#x2B9E; Tank 5" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
</Grid>
</Border>
<Border HorizontalAlignment="Left" VerticalAlignment="Bottom" Padding="60,15,15,30" Margin="-11,0,0,-11"
Background="WhiteSmoke"
BorderBrush="Gray" BorderThickness="1" CornerRadius="10">
<Grid x:Name="FastSelectButtonsLeft">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="2.5"/>
<ColumnDefinition Width="2.5"/>
<ColumnDefinition Width="160"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition Height="5"/>
<RowDefinition Height="50"/>
<RowDefinition Height="5"/>
<RowDefinition Height="80"/>
<RowDefinition Height="80"/>
</Grid.RowDefinitions>
<Button x:Name="FastSelectButton_EntryTrough_Stop" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2"
FontSize="14" FontWeight="Bold"
Click="FastSelectButton_EntryTroughStop_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2">
<TextBlock TextAlignment="Center">Auffangwanne<LineBreak/><Run x:Name="FastSelectButton_EntryTrough_Stop_Run" FontSize="18">Schnecke Stop</Run></TextBlock>
</Button>
<Button x:Name="FastSelectButton_EntryTrough_Start" Grid.Column="2" Grid.Row="0" Grid.ColumnSpan="2"
FontSize="14" FontWeight="Bold"
Click="FastSelectButton_EntryTroughStart_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2">
<TextBlock TextAlignment="Center">Auffangwanne<LineBreak/><Run x:Name="FastSelectButton_EntryTrough_Start_Run" FontSize="18">Schnecke Start</Run></TextBlock>
</Button>
<Button x:Name="FastSelectButton_MW2" Grid.Column="0" Grid.Row="2"
Content="MW2 &#x2B9C;" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_MW2_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MW1" Grid.Column="3" Grid.Row="2"
Content="&#x2B9E; MW1" FontSize="16" FontWeight="Bold"
Click="FastSelectButton_MW1_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2"/>
<Button x:Name="FastSelectButton_MP2_Forward" Grid.Column="0" Grid.Row="4"
FontSize="16" FontWeight="Bold"
Click="FastSelectButton_MP2_Forward_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2">
<TextBlock TextAlignment="Center"><Run x:Name="FastSelectButton_MP2_Forward_Run" FontSize="24">MP2</Run><LineBreak/><Run x:Name="MP2_Target">Vor</Run></TextBlock>
</Button>
<Button x:Name="FastSelectButton_MP2_Stop" Grid.Column="0" Grid.Row="5"
FontSize="16" FontWeight="Bold"
Click="FastSelectButton_MP2_Stop_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2">
<TextBlock TextAlignment="Center"><Run x:Name="FastSelectButton_MP2_Stop_Run" FontSize="24">MP2</Run><LineBreak/>Stop</TextBlock>
</Button>
<Button x:Name="FastSelectButton_MP1_Forward" Grid.Column="3" Grid.Row="4"
FontSize="16" FontWeight="Bold"
Click="FastSelectButton_MP1_Forward_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2">
<TextBlock TextAlignment="Center"><Run x:Name="FastSelectButton_MP1_Forward_Run" FontSize="24">MP1</Run><LineBreak/><Run x:Name="MP1_Target">Vor</Run></TextBlock>
</Button>
<Button x:Name="FastSelectButton_MP1_Stop" Grid.Column="3" Grid.Row="5"
FontSize="16" FontWeight="Bold"
Click="FastSelectButton_MP1_Stop_Click" MouseEnter="FastSelectButton_Enter" MouseLeave="FastSelectButton_Leave"
BorderThickness="2">
<TextBlock TextAlignment="Center"><Run x:Name="FastSelectButton_MP1_Stop_Run" FontSize="24">MP1</Run><LineBreak/>Stop</TextBlock>
</Button>
</Grid>
</Border>
</Grid> </Grid>
<StatusBar Grid.Row="2" BorderThickness="0,1,0,0" BorderBrush="Gray"> <StatusBar Grid.Row="2" BorderThickness="0,1,0,0" BorderBrush="Gray">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem>
<TextBlock>
SPS-Verbindung: <Run x:Name="Status_Connection" Text="-"/>
</TextBlock>
</StatusBarItem>
<Separator Grid.Column="1"/>
<StatusBarItem Grid.Column="2">
<TextBlock>
SPS-Status: <Run x:Name="Status_Plc" Text="-"/>
</TextBlock>
</StatusBarItem>
<Separator Grid.Column="3"/>
<StatusBarItem Grid.Column="4">
<TextBlock>
Druckluft: <Run x:Name="Status_CompressedAir" Text="-"/>
</TextBlock>
</StatusBarItem>
<Separator Grid.Column="5"/>
<StatusBarItem Grid.Column="6">
<TextBlock>
Ventile offen: <Run x:Name="Status_ValvesOpen" Text="-"/>
</TextBlock>
</StatusBarItem>
</StatusBar> </StatusBar>
</Grid> </Grid>
</Window> </Window>
+446 -74
View File
@@ -1,29 +1,116 @@
using PamhagenSysCtrl.Helpers; using PamhagenSysCtrl.Helpers;
using PamhagenSysCtrl.Helpers.Pipeline; using PamhagenSysCtrl.Helpers.Pipeline;
using PamhagenSysCtrl.Helpers.Plc;
using System.Diagnostics; using System.Diagnostics;
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media;
namespace PamhagenSysCtrl.Windows { namespace PamhagenSysCtrl.Windows {
public partial class PlantSchemeWindow : Window { public partial class PlantSchemeWindow : Window {
private readonly PamhagenGraph Graph; private const double _minMove = 50;
private static PamhagenGraph Graph => App.Plant!.Graph;
private Point? _lastMousePos;
private bool _mouseDragging;
private ISource? _lastSource; private ISource? _lastSource;
private List<INode> _path = []; private List<INode> _path = [];
private ISet<INode>? _lastSelected; private ISet<INode>? _lastSelected;
private ISet<INode>? _hover; private ISet<INode>? _hover;
public PlantSchemeWindow() { public double Scale { get; set; } = 5;
InitializeComponent(); public double RoundedScale => Math.Round(Scale * 2) / 2;
App.Plant?.Update += OnUpdate;
Graph = new(); public Path? HoveringPath {
Graph.Draw(SchemeCanvas); get {
foreach (var b in FastSelectPaths.Where(b => b.Key.IsMouseOver)) {
if (Graph.SelectedPaths.Any(p => p.Matches(b.Value.Source, b.Value.Sink)))
continue;
return Graph.GetFreePath([b.Value.Source, b.Value.Sink], true);
}
if (_lastSource == null || _hover == null || !(_hover.Count > 0 || _path.Count > 0))
return null;
List<INode> points = [_lastSource, .. _path];
if (_hover.Count > 0 && !points.Contains(_hover.First())) {
points.Add(_hover.First());
}
if (points.Count >= 2) {
return Graph.GetFreePath(points);
} else {
return null;
}
}
} }
private void Menu_Settings_ManualControl_Click(object sender, RoutedEventArgs evt) { protected Dictionary<Button, (ISource Source, Pump Pump, ISink Sink)> FastSelectPaths;
var w = new ManualControlWindow();
w.Show(); public PlantSchemeWindow() {
InitializeComponent();
Menu_Help_CheckForUpdates.IsEnabled = App.Config.UpdateUrl != null;
Graph.Draw(SchemeCanvas, RoundedScale);
FastSelectPaths = new Dictionary<Button, (ISource, Pump, ISink)> {
[FastSelectButton_MW1_Press1] = (Graph.MW1, Graph.MP1, Graph.Press1),
[FastSelectButton_MW2_Press1] = (Graph.MW2, Graph.MP2, Graph.Press1),
[FastSelectButton_MW1_Press2] = (Graph.MW1, Graph.MP1, Graph.Press2),
[FastSelectButton_MW2_Press2] = (Graph.MW2, Graph.MP2, Graph.Press2),
[FastSelectButton_MW1_Tank1] = (Graph.MW1, Graph.MP1, Graph.Tank1),
[FastSelectButton_MW2_Tank1] = (Graph.MW2, Graph.MP2, Graph.Tank1),
[FastSelectButton_MW1_Tank2] = (Graph.MW1, Graph.MP1, Graph.Tank2),
[FastSelectButton_MW2_Tank2] = (Graph.MW2, Graph.MP2, Graph.Tank2),
[FastSelectButton_MW1_Tank3] = (Graph.MW1, Graph.MP1, Graph.Tank3),
[FastSelectButton_MW2_Tank3] = (Graph.MW2, Graph.MP2, Graph.Tank3),
[FastSelectButton_MW1_Tank4] = (Graph.MW1, Graph.MP1, Graph.Tank4),
[FastSelectButton_MW2_Tank4] = (Graph.MW2, Graph.MP2, Graph.Tank4),
[FastSelectButton_MW1_Tank5] = (Graph.MW1, Graph.MP1, Graph.Tank5),
[FastSelectButton_MW2_Tank5] = (Graph.MW2, Graph.MP2, Graph.Tank5),
};
if (App.Plant != null) {
Status_Connection.Text = $"Nein ({App.Plant.PlcComPort})";
App.Plant.Update += OnUpdate;
}
}
private void OnClosed(object sender, EventArgs evt) {
Application.Current.Shutdown();
}
private void Menu_Settings_ManualControl_Changed(object sender, RoutedEventArgs evt) {
App.Plant?.ManualControlMode = Menu_Settings_ManualControl.IsChecked;
if (Menu_Settings_ManualControl.IsChecked) {
foreach (var win in Application.Current.Windows) {
if (win is ManualControlWindow w) {
w.Focus();
return;
}
}
var ctrl = new ManualControlWindow();
ctrl.Show();
} else {
foreach (var win in Application.Current.Windows) {
if (win is ManualControlWindow w) {
w.Close();
}
}
Menu_Settings_OverridePathClearances.IsChecked = false;
Menu_Settings_OverrideDryRunClearances.IsChecked = false;
App.Plant?.InitializePaths();
}
OnUpdate(null, null);
}
private void Menu_Settings_OverridePathClearances_Changed(object sender, RoutedEventArgs evt) {
App.Plant?.OverridePathClearances = Menu_Settings_OverridePathClearances.IsChecked;
OnUpdate(null, null);
}
private void Menu_Settings_OverrideDryRunClearances_Changed(object sender, RoutedEventArgs evt) {
App.Plant?.OverrideDryRunClearances = Menu_Settings_OverrideDryRunClearances.IsChecked;
OnUpdate(null, null);
} }
private void Menu_Settings_Directory_Click(object sender, RoutedEventArgs evt) { private void Menu_Settings_Directory_Click(object sender, RoutedEventArgs evt) {
@@ -43,45 +130,249 @@ namespace PamhagenSysCtrl.Windows {
} catch { } } catch { }
} }
private void SchemeCanvas_MouseMove(object sender, MouseEventArgs evt) { private async void Menu_Help_CheckForUpdates_Click(object sender, RoutedEventArgs evt) {
var p = evt.GetPosition(SchemeCanvas); await App.CheckForUpdates(true);
}
private void Menu_Help_About_Click(object sender, RoutedEventArgs evt) {
var window = new AboutWindow {
Owner = this,
};
window.Show();
}
private void FastSelectButton_EntryTroughStart_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.StartTroughAuger();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_EntryTroughStop_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.StopTroughAuger();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_MW1_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.SelectMW1();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_MW2_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.SelectMW2();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_MP1_Forward_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.StartMP1Forward();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_MP1_Stop_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.StopMP1();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_MP2_Forward_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.StartMP2Forward();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_MP2_Stop_Click(object sender, RoutedEventArgs evt) {
try {
App.Plant?.StopMP2();
} catch (NoClearanceException exc) {
MessageBox.Show($"{exc.Message}", "Keine Freigabe", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void FastSelectButton_Enter(object sender, MouseEventArgs evt) {
UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed); UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed);
} }
private void FastSelectButton_Leave(object sender, MouseEventArgs evt) {
UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed);
}
private void FastSelectButton_Click(object sender, RoutedEventArgs? evt) {
if (sender is not Button b || !FastSelectPaths.TryGetValue(b, out var val))
return;
App.Plant?.FastSelect(val.Source, null, val.Sink);
OnUpdate(null, null);
_lastSource = null;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs evt) {
var rx = evt.NewSize.Width / 1550.0;
var ry = evt.NewSize.Height / 900.0;
var r = Math.Min(rx, ry);
Scale = r * 5;
SchemeCanvas.Margin = new();
Graph.Scale(RoundedScale);
if (RoundedScale < 4) {
(FastSelectButtonsLeft.Parent as Border)?.Visibility = Visibility.Hidden;
(FastSelectButtonsRight.Parent as Border)?.Visibility = Visibility.Hidden;
} else if (RoundedScale <= 5) {
(FastSelectButtonsLeft.Parent as Border)?.Visibility = Visibility.Visible;
FastSelectButtonsLeft.ColumnDefinitions[0].Width = new(120);
FastSelectButtonsLeft.ColumnDefinitions[3].Width = new(120);
FastSelectButtonsLeft.RowDefinitions[0].Height = new(45);
FastSelectButtonsLeft.RowDefinitions[2].Height = new(40);
FastSelectButtonsLeft.RowDefinitions[4].Height = new(55);
FastSelectButtonsLeft.RowDefinitions[5].Height = new(55);
(FastSelectButtonsRight.Parent as Border)?.Visibility = Visibility.Visible;
foreach (var col in FastSelectButtonsRight.ColumnDefinitions) {
if (col.Width.Value <= 5) continue;
col.Width = new(120);
}
foreach (var row in FastSelectButtonsRight.RowDefinitions) {
if (row.Height.Value <= 5) continue;
row.Height = new(40);
}
foreach (var b in FastSelectPaths.Keys) {
b.FontSize = 14;
}
FastSelectButton_EntryTrough_Start.FontSize = 12;
FastSelectButton_EntryTrough_Start_Run.FontSize = 14;
FastSelectButton_EntryTrough_Stop.FontSize = 12;
FastSelectButton_EntryTrough_Stop_Run.FontSize = 14;
FastSelectButton_MW1.FontSize = 14;
FastSelectButton_MW2.FontSize = 14;
FastSelectButton_MP1_Stop.FontSize = 14;
FastSelectButton_MP1_Stop_Run.FontSize = 18;
FastSelectButton_MP1_Forward.FontSize = 14;
FastSelectButton_MP1_Forward_Run.FontSize = 18;
FastSelectButton_MP2_Stop.FontSize = 14;
FastSelectButton_MP2_Stop_Run.FontSize = 18;
FastSelectButton_MP2_Forward.FontSize = 14;
FastSelectButton_MP2_Forward_Run.FontSize = 18;
} else {
(FastSelectButtonsLeft.Parent as Border)?.Visibility = Visibility.Visible;
FastSelectButtonsLeft.ColumnDefinitions[0].Width = new(160);
FastSelectButtonsLeft.ColumnDefinitions[3].Width = new(160);
FastSelectButtonsLeft.RowDefinitions[0].Height = new(60);
FastSelectButtonsLeft.RowDefinitions[2].Height = new(50);
FastSelectButtonsLeft.RowDefinitions[4].Height = new(80);
FastSelectButtonsLeft.RowDefinitions[5].Height = new(80);
(FastSelectButtonsRight.Parent as Border)?.Visibility = Visibility.Visible;
foreach (var col in FastSelectButtonsRight.ColumnDefinitions) {
if (col.Width.Value <= 5) continue;
col.Width = new(160);
}
foreach (var row in FastSelectButtonsRight.RowDefinitions) {
if (row.Height.Value <= 5) continue;
row.Height = new(50);
}
foreach (var b in FastSelectPaths.Keys) {
b.FontSize = 16;
}
FastSelectButton_EntryTrough_Start.FontSize = 14;
FastSelectButton_EntryTrough_Start_Run.FontSize = 18;
FastSelectButton_EntryTrough_Stop.FontSize = 14;
FastSelectButton_EntryTrough_Stop_Run.FontSize = 18;
FastSelectButton_MW1.FontSize = 16;
FastSelectButton_MW2.FontSize = 16;
FastSelectButton_MP1_Stop.FontSize = 16;
FastSelectButton_MP1_Stop_Run.FontSize = 24;
FastSelectButton_MP1_Forward.FontSize = 16;
FastSelectButton_MP1_Forward_Run.FontSize = 24;
FastSelectButton_MP2_Stop.FontSize = 16;
FastSelectButton_MP2_Stop_Run.FontSize = 24;
FastSelectButton_MP2_Forward.FontSize = 16;
FastSelectButton_MP2_Forward_Run.FontSize = 24;
}
UpdateScheme(Mouse.GetPosition(SchemeCanvas), Mouse.LeftButton == MouseButtonState.Pressed);
}
private void SchemeCanvas_MouseWheel(object sender, MouseWheelEventArgs evt) {
Scale = Math.Max(1, Math.Min(20, Scale + evt.Delta / 500.0));
Graph.Scale(RoundedScale);
UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed);
}
private void SchemeCanvas_MouseMove(object sender, MouseEventArgs evt) {
var pos = evt.GetPosition(SchemeCanvas);
if (_lastMousePos is Point p) {
if (!_mouseDragging) {
var dx = p.X - pos.X;
var dy = p.Y - pos.Y;
_mouseDragging = Math.Sqrt(dx * dx + dy * dy) >= _minMove;
}
if (_mouseDragging) {
var dx = SchemeCanvas.Margin.Right - SchemeCanvas.Margin.Left + p.X - pos.X;
var dy = SchemeCanvas.Margin.Bottom - SchemeCanvas.Margin.Top + p.Y - pos.Y;
SchemeCanvas.Margin = new(dx > 0 ? 0 : -dx, dy > 0 ? 0 : -dy, dx < 0 ? 0 : dx, dy < 0 ? 0 : dy);
}
}
UpdateScheme(pos, evt.LeftButton == MouseButtonState.Pressed);
}
private void SchemeCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs evt) { private void SchemeCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs evt) {
var p = evt.GetPosition(SchemeCanvas); var pos = evt.GetPosition(SchemeCanvas);
_lastSelected = UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed); _lastMousePos = pos;
_mouseDragging = false;
_lastSelected = UpdateScheme(pos, evt.LeftButton == MouseButtonState.Pressed);
} }
private void SchemeCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs evt) { private void SchemeCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs evt) {
var pos = evt.GetPosition(SchemeCanvas); var pos = evt.GetPosition(SchemeCanvas);
_lastMousePos = null;
_mouseDragging = false;
var curSelected = UpdateScheme(pos, evt.LeftButton == MouseButtonState.Pressed); var curSelected = UpdateScheme(pos, evt.LeftButton == MouseButtonState.Pressed);
var clicked = curSelected.Intersect(_lastSelected ?? new HashSet<INode>()).ToList(); var clicked = curSelected.Intersect(_lastSelected ?? new HashSet<INode>()).ToList();
if (clicked.Count == 0) { if (clicked.Count == 0) {
_lastSource = null; _lastSource = null;
} }
foreach (var c in clicked) { foreach (var c in clicked) {
if (_lastSource == null && Graph.SelectedPaths.Any(p => p.Start == c)) { if (App.Plant?.ManualControlMode ?? false) {
var path = Graph.SelectedPaths.First(p => p.Start == c); if (c is ControlledValve v) {
if (!path.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false)) { if (v.WantOpen) {
Graph.RemovePath(path); App.Plant.CloseV(v.Nr);
} else {
App.Plant.OpenV(v.Nr);
}
} }
} else if (_lastSource != null && c is ISink sink) { } else {
var res = Graph.GetPath([_lastSource, .._path, sink], (n) => n is not ISink && (n is not Valve v || !v.IsLocked)); if (_lastSource == null && Graph.SelectedPaths.Any(p => p.Start == c)) {
if (res is Path path) { var path = Graph.SelectedPaths.First(p => p.Start == c);
Graph.AddPath(path); if (!path.HasActivePump) {
Graph.RemovePath(path);
}
} else if (_lastSource != null && c is ISink sink) {
if (Graph.GetFreePath([_lastSource, .. _path, sink]) is Path path) {
Graph.AddPath(path);
_lastSource = null;
}
} else if (_lastSource != null) {
var path = Graph.GetFreePath([_lastSource, .. _path, c]);
if (path != null) {
_path.Add(c);
}
} else if (c is ISource source) {
_lastSource = source;
_path = [];
} else {
_lastSource = null; _lastSource = null;
} }
} else if (_lastSource != null) {
var path = Graph.GetPath([_lastSource, .. _path, c], (n) => n is not ISink && (n is not Valve v || !v.IsLocked));
if (path != null) {
_path.Add(c);
}
} else if (c is ISource source) {
_lastSource = source;
_path = [];
} else {
_lastSource = null;
} }
try { try {
if (c is EntryTrough trough) { if (c is EntryTrough trough) {
@@ -97,7 +388,7 @@ namespace PamhagenSysCtrl.Windows {
App.Plant?.StartRebler(); App.Plant?.StartRebler();
} }
} else if (c is Switcher switcher) { } else if (c is Switcher switcher) {
if (App.Plant?.WantMW1Selected ?? false) { if (App.Plant?.State.MW1_WantSelected ?? false) {
App.Plant?.SelectMW2(); App.Plant?.SelectMW2();
} else { } else {
App.Plant?.SelectMW1(); App.Plant?.SelectMW1();
@@ -109,18 +400,18 @@ namespace PamhagenSysCtrl.Windows {
} else { } else {
App.Plant?.StartAuger1(); App.Plant?.StartAuger1();
} }
} else if (auger == Graph.Auger2) { } else if (auger == Graph.Auger3) {
if (auger.IsActive) { if (auger.IsActive) {
App.Plant?.StopAuger2(); App.Plant?.StopAuger3();
} else { } else {
App.Plant?.StartAuger2(); App.Plant?.StartAuger3();
} }
} }
} else if (c is ConveyorBelt belt) { } else if (c is ConveyorBelt belt) {
if (belt.IsActive) { if (belt.IsActive) {
App.Plant?.StopBelt(); App.Plant?.StopAuger2();
} else { } else {
App.Plant?.StartBelt(); App.Plant?.StartAuger2();
} }
} else if (c is Pump pump) { } else if (c is Pump pump) {
if (pump == Graph.MP1) { if (pump == Graph.MP1) {
@@ -137,33 +428,33 @@ namespace PamhagenSysCtrl.Windows {
} }
} else if (pump == Graph.P12) { } else if (pump == Graph.P12) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.StopP12(); App.Plant?.DisableP12();
} else { } else {
App.Plant?.StartP12(); App.Plant?.EnableP12();
} }
} else if (pump == Graph.P3) { } else if (pump == Graph.P3) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.StopP3(); App.Plant?.DisableP3();
} else { } else {
App.Plant?.StartP3(); App.Plant?.EnableP3();
} }
} else if (pump == Graph.P4) { } else if (pump == Graph.P4) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.StopP4(); App.Plant?.DisableP4();
} else { } else {
App.Plant?.StartP4(); App.Plant?.EnableP4();
} }
} else if (pump == Graph.P5) { } else if (pump == Graph.P5) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.StopP5(); App.Plant?.DisableP5();
} else { } else {
App.Plant?.StartP5(); App.Plant?.EnableP5();
} }
} else if (pump == Graph.P6) { } else if (pump == Graph.P6) {
if (pump.IsActive) { if (pump.IsActive) {
App.Plant?.StopP6(); App.Plant?.DisableP6();
} else { } else {
App.Plant?.StartP6(); App.Plant?.EnableP6();
} }
} }
} }
@@ -174,7 +465,32 @@ namespace PamhagenSysCtrl.Windows {
UpdateScheme(pos, evt.LeftButton == MouseButtonState.Pressed); UpdateScheme(pos, evt.LeftButton == MouseButtonState.Pressed);
} }
private ISet<INode> UpdateScheme(Point pos, bool down) { private static void SetFastSelectButton(Button b, int m, bool disabled = false) {
b.IsEnabled = !disabled;
if (m == 1) {
b.Background = Brushes.MintCream;
b.BorderBrush = Brushes.DarkGreen;
b.Foreground = Brushes.DarkGreen;
} else if (m == 2) {
b.Background = Brushes.MistyRose;
b.BorderBrush = Brushes.DarkRed;
b.Foreground = Brushes.DarkRed;
} else {
b.Background = Brushes.LightGray;
b.BorderBrush = Brushes.DimGray;
b.Foreground = Brushes.Black;
}
}
private void UpdateFastSelectPathButton(IEnumerable<Path> paths, Path? hoverPathDeactivate, Button b) {
var e = FastSelectPaths[b];
SetFastSelectButton(b,
paths.Any(p => p.Matches(e.Source, e.Sink)) ? (hoverPathDeactivate?.Matches(e.Source, e.Sink) ?? false) ? 2 : 1 : 0,
e.Pump.IsActive || Graph.GetFreePath([e.Source, e.Sink], overrideStart: true) == null);
}
private ISet<INode> UpdateScheme(Point? pos, bool down) {
if (!SchemeCanvas.IsMouseOver) pos = null;
foreach (var e in Graph.Edges) { foreach (var e in Graph.Edges) {
e.Highlight = null; e.Highlight = null;
e.Flow = null; e.Flow = null;
@@ -184,43 +500,99 @@ namespace PamhagenSysCtrl.Windows {
p.Highlight = null; p.Highlight = null;
} }
} }
foreach (var p in Graph.SelectedPaths) {
Graph.ColorPipesAdjacientToPath(p, PamhagenBrushes.Green, p.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false), PamhagenBrushes.DimOrange); SetFastSelectButton(FastSelectButton_EntryTrough_Start, App.Plant?.State.TroughAuger_IsActive ?? false ? 1 : 0, !(App.Plant?.State.TroughAuger_HasClearance ?? false));
} SetFastSelectButton(FastSelectButton_EntryTrough_Stop, App.Plant?.State.TroughAuger_IsActive ?? false ? 0 : 2);
_hover = Graph.GetHover(pos.X, pos.Y); SetFastSelectButton(FastSelectButton_MW1, App.Plant?.State.MW1_IsSelected?? false ? 1 : 0);
if (_lastSource != null && (_hover.Count > 0 || _path.Count > 0)) { SetFastSelectButton(FastSelectButton_MW2, App.Plant?.State.MW2_IsSelected ?? false ? 1 : 0);
List<INode> points = [_lastSource, .. _path]; SetFastSelectButton(FastSelectButton_MP1_Forward, App.Plant?.State.MP1_State == MotorState.Forward ? 1 : 0, !(App.Plant?.State.MP1_HasClearance ?? false));
if (_hover.Count > 0 && !points.Contains(_hover.First())) { SetFastSelectButton(FastSelectButton_MP1_Stop, App.Plant?.State.MP1_State == MotorState.Halt ? 2 : 0);
points.Add(_hover.First()); SetFastSelectButton(FastSelectButton_MP2_Forward, App.Plant?.State.MP2_State == MotorState.Forward ? 1 : 0, !(App.Plant?.State.MP2_HasClearance ?? false));
SetFastSelectButton(FastSelectButton_MP2_Stop, App.Plant?.State.MP2_State == MotorState.Halt ? 2 : 0);
_hover = Graph.GetHover(pos, RoundedScale);
var hoverButtons = FastSelectPaths.Where(b => b.Key.IsMouseOver).ToDictionary();
Path? hoverPathDeactivate = null;
if (App.Plant?.ManualControlMode ?? false) {
foreach (var src in Graph.Nodes.Where(n => n is Pump)) {
PamhagenGraph.ColorPipesByValves(src, PamhagenBrushes.Green, (src as Pump)?.State ?? default, PamhagenBrushes.DimOrange);
} }
if (points.Count >= 2) { MP1_Target.Text = "Vor";
var res = Graph.GetPath(points, (n) => n is not ISink && (n is not Valve v || !v.IsLocked)); MP2_Target.Text = "Vor";
if (res is Path path) { MP1_Target.FontSize = 16;
Graph.ColorPipesAdjacientToPath(path, PamhagenBrushes.Green, false, PamhagenBrushes.DimOrange); MP2_Target.FontSize = 16;
} else {
foreach (var p in Graph.SelectedPaths) {
PamhagenGraph.ColorPipesAdjacientToPath(p, PamhagenBrushes.Green, p.Pumps.Select(p => p.State).FirstOrDefault(n => n != MotorState.Halt), PamhagenBrushes.DimOrange);
}
if (_lastSource == null && _hover.Count > 0 && Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == _hover.First()) is Path path1) {
if (path1.HasActivePump) {
foreach (var p in path1.Pumps) p.Highlight = PamhagenBrushes.Red;
} else {
hoverPathDeactivate = path1;
PamhagenGraph.ColorPipesAdjacientToPath(path1, PamhagenBrushes.Red, default, null);
}
} else if (hoverButtons.Count != 0 && Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == hoverButtons.First().Value.Source) is Path path2) {
if (path2.HasActivePump) {
foreach (var p in path2.Pumps) p.Highlight = PamhagenBrushes.Red;
} else {
hoverPathDeactivate = path2;
PamhagenGraph.ColorPipesAdjacientToPath(path2, PamhagenBrushes.Red, default, null);
} }
} }
} else if (_lastSource == null && _hover.Count > 0 && Graph.SelectedPaths.Any(p => p.Start == _hover.First())) { if (_lastSource != null) {
var path = Graph.SelectedPaths.First(p => p.Start == _hover.First()); foreach (var edge in _lastSource.Outputs) {
var pumpActive = path.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false); edge.Highlight = PamhagenBrushes.Green;
if (pumpActive) {
foreach (var pump in path.Hops.Select(h => h.Node as Pump).Where(p => p != null)) {
pump?.Highlight = PamhagenBrushes.Red;
} }
}
if (HoveringPath is Path hoveringPath) {
PamhagenGraph.ColorPipesAdjacientToPath(hoveringPath, PamhagenBrushes.Green, default, PamhagenBrushes.DimOrange);
}
if (Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == Graph.MW1) is Path pmw1) {
MP1_Target.Text = "\u2b9e " + string.Join(" \u2b9e ", pmw1.Hops
.Select(h => h.Node is Valve || h.Node is Pump ? null : h.Node.Label)
.Where(l => !string.IsNullOrWhiteSpace(l)));
} else { } else {
Graph.ColorPipesAdjacientToPath(path, PamhagenBrushes.Red, false, null); MP1_Target.Text = "Vor";
} }
} if (Graph.SelectedPaths.Select(p => (Path?)p).FirstOrDefault(p => p?.Start == Graph.MW2) is Path pmw2) {
if (_lastSource != null) { MP2_Target.Text = "\u2b9e " + string.Join(" \u2b9e ", pmw2.Hops
foreach (var edge in _lastSource.Outputs) { .Select(h => h.Node is Valve || h.Node is Pump ? null : h.Node.Label)
edge.Highlight = PamhagenBrushes.Green; .Where(l => !string.IsNullOrWhiteSpace(l)));
} else {
MP2_Target.Text = "Vor";
} }
var len1 = MP1_Target.Text.Length;
var len2 = MP2_Target.Text.Length;
MP1_Target.FontSize = len1 > 24 ? 8 : len1 > 20 ? 10 : len1 > 16 ? 12 : len1 > 12 ? 14 : 16;
MP2_Target.FontSize = len2 > 24 ? 8 : len2 > 20 ? 10 : len2 > 16 ? 12 : len2 > 12 ? 14 : 16;
} }
var paths = Graph.SelectedPaths;
if (HoveringPath is Path hp) paths = paths.Append(hp);
foreach (var b in FastSelectPaths) {
UpdateFastSelectPathButton(paths, hoverPathDeactivate, b.Key);
}
Graph.Update(_hover, down); Graph.Update(_hover, down);
return _hover; return _hover;
} }
public void OnUpdate(object? sender, PlantEventArgs evt) { public void OnUpdate(object? sender, PlantEventArgs? evt) {
if (sender is not PamhagenPlant plant) return; if (App.Plant is PamhagenPlant p) {
if (p.IsPlcConnected) {
Status_Connection.Text = $"Ja ({p.PlcComPort}" + (p.LastRttNs != null ? $", {p.LastRttNs / 1000000.0:N0} ms" : "") + ")";
Status_Plc.Text = $"{p.LastPlcStatus?.Mode} ({p.LastPlcStatus?.Flags:X3}) / {p.LastPlcError?.GetName()} ({(int?)p.LastPlcError:0000})";
Status_CompressedAir.Text = Enumerable.Range(1, 57).Any(p.State.Valve_IsClosed) ? "Ein" : Enumerable.Range(1, 57).Any(p.State.Valve_WantClosed) ? "Aus" : "?";
Status_ValvesOpen.Text = Enumerable.Range(1, 57).Count(p.State.Valve_IsOpen) + " / " + Enumerable.Range(1, 57).Count(p.State.Valve_WantOpen) + " / 57";
} else {
Status_Connection.Text = p.PlcConnectionError is string err ? $"Nein ({p.PlcComPort}, {err})" : $"Nein ({p.PlcComPort})";
Status_Plc.Text = "-";
Status_CompressedAir.Text = "-";
Status_ValvesOpen.Text = "-";
}
}
UpdateScheme(Mouse.GetPosition(SchemeCanvas), Mouse.LeftButton == MouseButtonState.Pressed); UpdateScheme(Mouse.GetPosition(SchemeCanvas), Mouse.LeftButton == MouseButtonState.Pressed);
} }
} }
+41 -1
View File
@@ -1,2 +1,42 @@
# Anlagensteuerung Pamhangen Anlagensteuerung Pamhagen
=========================
Schnittstelle zur SPS und grafische Benutzeroberfläche.
Winzerkeller Seewinkel, Pamhagen.
About
=====
**Product:** Anlagensteuerung Pamhagen
**Description:** Interface to a LPC and graphical user interface for controlling the plant/system
**Type:** HMI and PLC Control Software
**Version:** 0.0.4 ([Changelog](./CHANGELOG.md))
**License:** [GNU General Public License 3.0 (GPLv3)](./LICENSE)
**Source code:** https://git.necronda.net/winzer/pamhagen-sysctrl
**Developement period:** 2026
**Technology Stack:**
Language: C#
Framework: Windows Presentation Framework (WPF)
LPC-Interface: Serial connection
Packaging: [WiX Toolset](https://www.firegiant.com/wixtoolset/)
Über
====
**Produkt:** Anlagensteuerung Pamhagen
**Beschreibung:** Schnittstelle zu einer SPS und grafische Benutzeroberfläche für die Steuerung der Anlage
**Typ:** HMI- und SPS-Steuerungssoftware
**Version:** 0.0.4 ([Änderungsprotokoll](./CHANGELOG.md))
**Lizenz:** [GNU General Public License 3.0 (GPLv3)](./LICENSE)
**Quellcode:** https://git.necronda.net/winzer/pamhagen-sysctrl
**Entwicklungszeitraum:** 2026
**Verwendete Technologien:**
Programmiersprache: C#
Framework: Windows Presentation Framework (WPF)
SPS-Schnittstelle: Serielle Verbindung
Paketierung: [WiX Toolset](https://www.firegiant.com/wixtoolset/)
+233
View File
@@ -0,0 +1,233 @@
#!/bin/env python3
import socket
import time
import random
import argparse
mw1 = 0
mw2 = 0
t1 = 0
t2 = 0
t3 = 0
t4 = 0
t5 = 0
t6 = 0
t7 = 0
t8 = 0
t9 = 0
pr1 = 0
pr2 = 0
mw1_max = 5000
mw2_max = 5000
t_max = 35000
pr1_max = 20000
pr2_max = 15000
ENTRY_RATE = 20 / 20
PUMP_RATE = 5 / 20
rw = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
def are_valves_open(valves, *ns):
mask = 0
for n in ns:
mask |= 1 << (n - 1)
return (~valves & mask) == mask
def send_cmd(cmd: str, data: str) -> None:
msg = f'(A01{cmd}{data}&'
chk = sum(c for c in msg.encode('ascii')) & 0xFF
msg += f'{chk:02X})\r'
time.sleep(len(msg) * 8 / 9600 * 2)
print(f'-> {msg}')
client.send(msg.encode('ascii'))
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('socket')
args = parser.parse_args()
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client.connect(args.socket)
while True:
line = b''
while b'\r' not in line:
line += client.recv(256)
cmds = line.split(b'\r')
line = cmds[-1]
cmds = [c.decode('ascii') for c in cmds[:-1]]
for cmd in cmds:
print(f'<- {cmd}')
time.sleep(len(cmd) * 8 / 9600 * 2)
if not cmd.startswith('(A01') or not cmd.endswith(')') or len(cmd) < 7:
send_cmd('CE', '02')
continue
c, d = cmd[4:6], cmd[6:-1]
if cmd[-4] == '&':
chk1 = int(cmd[-3:-1], 16)
chk2 = sum(c for c in cmd[:-3].encode('ascii')) & 0xFF
if chk1 != chk2:
send_cmd('CE', '03')
continue
d = d[:-3]
if c == 'TS':
send_cmd('TS', d)
elif c == 'ST':
send_cmd('ST', '0002')
elif c == 'DR':
# FIXME
d = d.split(',')
s = int(d[0][2:])
l = int(d[1])
send_cmd('DR', ''.join(f'{r:04X}' for r in rw[s:s+l]))
elif c == 'DW':
# FIXME
d = d.split(',')
s = int(d[0][2:])
l = int(d[1])
for i in range(l):
rw[s + i] = int(d[2 + i], 16)
send_cmd('ST', '0002')
else:
send_cmd('CE', '01')
# tick - valves
for i in range(0, 60):
want_open = rw[i // 16] & (1 << (i % 16)) == 0
is_open = rw[10 + i // 16] & (1 << (i % 16)) != 0
if want_open and not is_open:
if random.random() > 0.875:
rw[10 + i // 16] |= 1 << (i % 16)
elif not want_open and is_open:
if random.random() > 0.875:
rw[10 + i // 16] &= ~(1 << (i % 16))
# tick - wannen
want_a1_selected = (rw[5] & 0x0004) != 0
want_a2_selected = not want_a1_selected
is_a1_selected = (rw[14] & 0x0200) != 0
is_a2_selected = (rw[14] & 0x0100) != 0
if is_a1_selected and not want_a1_selected:
rw[14] &= 0xFDFF
elif not is_a1_selected and want_a1_selected:
if random.random() > 0.875:
rw[14] |= 0x0200
if is_a2_selected and not want_a2_selected:
rw[14] &= 0xFEFF
elif not is_a2_selected and want_a2_selected:
if random.random() > 0.875:
rw[14] |= 0x0100
# tick - pipeline
valves = (rw[13] << 48) | (rw[12] << 32) | (rw[11] << 16) | rw[10]
wannen_schnecke = (rw[5] & 0x1) != 0
mp1 = (rw[4] & 0x002) != 0
mp2 = (rw[4] & 0x004) != 0
p12 = (rw[4] & 0x010) != 0
p3 = (rw[4] & 0x020) != 0
p4 = (rw[4] & 0x040) != 0
p5 = (rw[4] & 0x080) != 0
p6 = (rw[4] & 0x100) != 0
if wannen_schnecke and is_a1_selected:
mw1 += ENTRY_RATE
elif wannen_schnecke and is_a2_selected:
mw2 += ENTRY_RATE
if mp1 and mw1 > 0:
mw1 -= PUMP_RATE
if are_valves_open(valves, 1, 6, 51) or are_valves_open(valves, 4, 8, 52):
pr1 += PUMP_RATE
elif are_valves_open(valves, 1, 6, 55) or are_valves_open(valves, 4, 8, 56):
pr2 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 22) or are_valves_open(valves, 4, 7, 9, 13, 23):
t1 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 24) or are_valves_open(valves, 4, 7, 9, 13, 25):
t2 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 26) or are_valves_open(valves, 4, 7, 9, 13, 27):
t3 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 28, 30) or are_valves_open(valves, 4, 7, 9, 13, 29, 31):
t4 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 28, 32) or are_valves_open(valves, 4, 7, 9, 13, 29, 33):
t5 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 28, 34, 37) or are_valves_open(valves, 4, 7, 9, 13, 29, 35, 36):
t6 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 28, 34, 39) or are_valves_open(valves, 4, 7, 9, 13, 29, 35, 38):
t7 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 28, 34, 41) or are_valves_open(valves, 4, 7, 9, 13, 29, 35, 40):
t8 += PUMP_RATE
elif are_valves_open(valves, 1, 5, 12, 28, 34, 43) or are_valves_open(valves, 4, 7, 9, 13, 29, 35, 42):
t9 += PUMP_RATE
if mp2 and mw2 > 0:
mw2 -= PUMP_RATE
if are_valves_open(valves, 2, 8, 52) or are_valves_open(valves, 3, 6, 51):
pr1 += PUMP_RATE
elif are_valves_open(valves, 2, 8, 56) or are_valves_open(valves, 3, 6, 55):
pr2 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 22) or are_valves_open(valves, 2, 7, 9, 13, 23):
t1 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 24) or are_valves_open(valves, 2, 7, 9, 13, 25):
t2 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 26) or are_valves_open(valves, 2, 7, 9, 13, 27):
t3 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 28, 30) or are_valves_open(valves, 2, 7, 9, 13, 29, 31):
t4 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 28, 32) or are_valves_open(valves, 2, 7, 9, 13, 29, 33):
t5 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 28, 34, 37) or are_valves_open(valves, 2, 7, 9, 13, 29, 35, 36):
t6 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 28, 34, 39) or are_valves_open(valves, 2, 7, 9, 13, 29, 35, 38):
t7 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 28, 34, 41) or are_valves_open(valves, 2, 7, 9, 13, 29, 35, 40):
t8 += PUMP_RATE
elif are_valves_open(valves, 3, 5, 12, 28, 34, 43) or are_valves_open(valves, 2, 7, 9, 13, 29, 35, 42):
t9 += PUMP_RATE
if p6 and (t6 > 0 or t7 > 0 or t8 > 0 or t9 > 0):
if t6 > 0:
t6 -= PUMP_RATE
if t7 > 0:
t7 -= PUMP_RATE
if t8 > 0:
t8 -= PUMP_RATE
if t9 > 0:
t9 -= PUMP_RATE
if are_valves_open(valves, 53):
pr1 += PUMP_RATE
elif are_valves_open(valves, 57):
pr2 += PUMP_RATE
# tick - fill levels
rw[14] &= 0xFF00
rw[14] |= 0x0004 if mw1 > 50 else 0
rw[14] |= 0x0002 if mw1 > mw1_max / 2 else 0
rw[14] |= 0x0001 if mw1 > mw1_max * 0.875 else 0
rw[14] |= 0x0020 if mw2 > 50 else 0
rw[14] |= 0x0010 if mw2 > mw2_max / 2 else 0
rw[14] |= 0x0008 if mw2 > mw2_max * 0.875 else 0
rw[14] |= 0x0040 if pr1 > pr1_max * 0.875 else 0
rw[14] |= 0x0080 if pr2 > pr2_max * 0.875 else 0
rw[16] &= 0xC000
rw[16] |= 0x0001 if t1 > 50 else 0
rw[16] |= 0x0002 if t1 > t_max * 0.875 else 0
rw[16] |= 0x0004 if t2 > 50 else 0
rw[16] |= 0x0008 if t2 > t_max * 0.875 else 0
rw[16] |= 0x0010 if t3 > 50 else 0
rw[16] |= 0x0020 if t3 > t_max * 0.875 else 0
rw[16] |= 0x0040 if t4 > 50 else 0
rw[16] |= 0x0080 if t4 > t_max * 0.875 else 0
rw[16] |= 0x0100 if t5 > 50 else 0
rw[16] |= 0x0200 if t5 > t_max * 0.875 else 0
rw[16] |= 0x0400 if t6 > t_max * 0.875 else 0
rw[16] |= 0x0800 if t7 > t_max * 0.875 else 0
rw[16] |= 0x1000 if t8 > t_max * 0.875 else 0
rw[16] |= 0x2000 if t9 > t_max * 0.875 else 0
print(mw1, mw2, t1, t2, t3, t4, t5, t6, t7, t8, t9, pr1, pr2)
BIN
View File
Binary file not shown.
+216
View File
@@ -0,0 +1,216 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="78mm" height="117mm" viewBox="0 0 780 1170">
<title>XBE-24 Key Legend Pamhagen</title>
<defs>
<path id="key" d="M 5,5 l 150,0 l 0,130 q -75,35 -150,0 Z"
stroke-width="10" stroke="#808080" paint-order="stroke"/>
<path id="start" d="M 0,-40 l 40,40 l -40,40 l -40,-40 Z M 0,-20 l 0,40 Z"
stroke-width="7" stroke="#004000" fill-opacity="0"
stroke-linejoin="round"/>
<path id="stop" d="M 0,40 a 40,40 180 0,0 0,-80 a 40,40 180 1,0 0,80 m 0,-5 l -30.310889,-52.5 l 60.621778,0 Z"
stroke-width="7" stroke="#400000" fill-opacity="0"/>
<path id="on-off" d="M 0,40 a 40,40 180 0,0 0,-80 a 40,40 180 1,0 0,80 Z M 0,-20 l 0,40 Z"
stroke-width="7" stroke="#000000" fill-opacity="0"
stroke-linejoin="round"/>
<path id="arrow-down" d="M 0,-35 l 15,0 l 0,50 l 10,0 l -25,25 l -25,-25 l 10,0 l 0,-50 Z"
fill="#000040"/>
<use id="arrow-left-down" href="#arrow-down" transform="rotate(30)"/>
<path id="arrow-from-mw1" d="M -70,-20 l 80,0 l 0,30 l 10,0 l -20,20 l -20,-20 l 10,0 l 0,-10 l -60,0 Z"
fill="#000040"/>
<use id="arrow-from-mw2" href="#arrow-from-mw1" transform="scale(-1,1)"/>
<path id="press" d="M 35,0 l 10,0 l 0,55 l -20,0 l 0,-10 l -50,0 l 0,10 l -20,0 l 0,-55 l 10,0 a 35,35 180 0,1 70,0 a 35,35 180 0,1 -70,0"
stroke-width="5" stroke="#000000" fill-opacity="0"/>
<!--path id="tank" d="M 35,-32 l 0,90 l -10,0 l 0,-23 l 10,-9 l -30,25 l -10,0 l -30,-25 l 10,9 l 0,23 l -10,0 l 0,-90 Z"
stroke-width="5" stroke="#000000" fill-opacity="0"/-->
<path id="tank" d="M 35,-32 l 0,90 l -10,0 l 0,-23 l -20,16 l -10,0 l -20,-16 l 0,23 l -10,0 l 0,-90 Z"
stroke-width="5" stroke="#000000" fill-opacity="0"/>
</defs>
<rect x="0" y="0" width="780" height="1170" stroke="#000000" fill="#FFFFFF" stroke-width="5"/>
<g id="k0">
<use href="#key" x="25" y="30" fill="#E8E8FF"/>
<use href="#press" x="105" y="110"/>
<text x="105" y="110" font-size="35" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">P1</text>
<use href="#arrow-from-mw2" x="105" y="60"/>
<text x="148" y="50" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k1">
<use href="#key" x="25" y="220" fill="#E8E8FF"/>
<use href="#press" x="105" y="300"/>
<text x="105" y="300" font-size="35" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">P2</text>
<use href="#arrow-from-mw2" x="105" y="250"/>
<text x="148" y="240" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k2">
<use href="#key" x="25" y="410" fill="#E8E8FF"/>
<use href="#tank" x="105" y="490"/>
<text x="105" y="490" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT1</text>
<use href="#arrow-from-mw2" x="105" y="440"/>
<text x="148" y="430" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k3">
<use href="#key" x="25" y="600" fill="#E8E8FF"/>
<use href="#tank" x="105" y="680"/>
<text x="105" y="680" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT2</text>
<use href="#arrow-from-mw2" x="105" y="630"/>
<text x="148" y="620" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k4">
<use href="#key" x="25" y="790" fill="#E8E8FF"/>
<use href="#tank" x="105" y="870"/>
<text x="105" y="870" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT3</text>
<use href="#arrow-from-mw2" x="105" y="820"/>
<text x="148" y="810" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k5">
<use href="#key" x="25" y="980" fill="#E8E8FF"/>
<use href="#tank" x="105" y="1060"/>
<text x="105" y="1060" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT4</text>
<use href="#arrow-from-mw2" x="105" y="1010"/>
<text x="148" y="1000" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k6">
<use href="#key" x="215" y="30" fill="#E8FFE8"/>
<use href="#start" x="295" y="85"/>
<text x="295" y="150" font-size="40" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">Rebler</text>
</g>
<g id="k7">
<use href="#key" x="215" y="220" fill="#FFE8E8"/>
<use href="#stop" x="295" y="275"/>
<text x="295" y="340" font-size="40" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">Rebler</text>
</g>
<g id="k8">
<use href="#key" x="215" y="410" fill="#E8E8FF"/>
<use href="#arrow-left-down" x="300" y="465"/>
<text x="295" y="530" font-size="45" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">MW2</text>
</g>
<g id="k9">
<use href="#key" x="215" y="600" fill="#FFFFFF"/>
<use href="#on-off" x="295" y="655"/>
<text x="295" y="710" font-size="25" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;"><tspan>Rührer</tspan><tspan font-size="30" dx="-2.5em" dy="0.875em">MW2</tspan></text>
</g>
<g id="k10">
<use href="#key" x="215" y="790" fill="#E8FFE8"/>
<use href="#start" x="295" y="845"/>
<text x="295" y="913" font-size="45" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">MP2</text>
</g>
<g id="k11">
<use href="#key" x="215" y="980" fill="#FFE8E8"/>
<use href="#stop" x="295" y="1035"/>
<text x="295" y="1103" font-size="45" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">MP2</text>
</g>
<g id="k12">
<use href="#key" x="405" y="30" fill="#E8FFE8"/>
<use href="#start" x="485" y="85"/>
<text x="485" y="140" font-size="25" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;"><tspan>Trog-</tspan><tspan dx="-3.5em" dy="0.875em">schnecke</tspan></text>
</g>
<g id="k13">
<use href="#key" x="405" y="220" fill="#FFE8E8"/>
<use href="#stop" x="485" y="275"/>
<text x="485" y="330" font-size="25" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;"><tspan>Trog-</tspan><tspan dx="-3.5em" dy="0.875em">schnecke</tspan></text>
</g>
<g id="k14">
<use href="#key" x="405" y="410" fill="#E8E8FF"/>
<use href="#arrow-down" x="485" y="465"/>
<text x="485" y="530" font-size="45" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
<g id="k15">
<use href="#key" x="405" y="600" fill="#FFFFFF"/>
<use href="#on-off" x="485" y="655"/>
<text x="485" y="710" font-size="25" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;"><tspan>Rührer</tspan><tspan font-size="30" dx="-2.5em" dy="0.875em">MW1</tspan></text>
</g>
<g id="k16">
<use href="#key" x="405" y="790" fill="#E8FFE8"/>
<use href="#start" x="485" y="845"/>
<text x="485" y="913" font-size="45" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">MP1</text>
</g>
<g id="k17">
<use href="#key" x="405" y="980" fill="#FFE8E8"/>
<use href="#stop" x="485" y="1035"/>
<text x="485" y="1103" font-size="45" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">MP1</text>
</g>
<g id="k18">
<use href="#key" x="595" y="30" fill="#E8E8FF"/>
<use href="#press" x="675" y="110"/>
<text x="675" y="110" font-size="35" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">P1</text>
<use href="#arrow-from-mw1" x="675" y="60"/>
<text x="632" y="50" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
<g id="k19">
<use href="#key" x="595" y="220" fill="#E8E8FF"/>
<use href="#press" x="675" y="300"/>
<text x="675" y="300" font-size="35" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">P2</text>
<use href="#arrow-from-mw1" x="675" y="250"/>
<text x="632" y="240" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
<g id="k20">
<use href="#key" x="595" y="410" fill="#E8E8FF"/>
<use href="#tank" x="675" y="490"/>
<text x="675" y="490" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT1</text>
<use href="#arrow-from-mw1" x="675" y="440"/>
<text x="632" y="430" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
<g id="k21">
<use href="#key" x="595" y="600" fill="#E8E8FF"/>
<use href="#tank" x="675" y="680"/>
<text x="675" y="680" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT2</text>
<use href="#arrow-from-mw1" x="675" y="630"/>
<text x="632" y="620" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
<g id="k22">
<use href="#key" x="595" y="790" fill="#E8E8FF"/>
<use href="#tank" x="675" y="870"/>
<text x="675" y="870" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT3</text>
<use href="#arrow-from-mw1" x="675" y="820"/>
<text x="632" y="810" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
<g id="k23">
<use href="#key" x="595" y="980" fill="#E8E8FF"/>
<use href="#tank" x="675" y="1060"/>
<text x="675" y="1060" font-size="30" text-anchor="middle" dominant-baseline="central"
style="font-family:Arial,sans-serif; font-weight:bold;">RT4</text>
<use href="#arrow-from-mw1" x="675" y="1010"/>
<text x="632" y="1000" font-size="20" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF"
style="font-family:Arial,sans-serif; font-weight:bold;">MW1</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB