231 Commits

Author SHA1 Message Date
Felix Weiß
1e2743603c Delete old LICENSE.md 2024-02-16 16:00:55 +01:00
Felix Weiß
6685258052 Changed license 2024-02-16 16:00:08 +01:00
Felix Weiß
99fa6a7eca Update README.md 2024-01-09 01:54:33 +01:00
Felix Weiß
19f6d44a43 Update LICENSE.md 2024-01-05 11:57:50 +01:00
Felix Weiß
18d5118b43 Update test-pipeline.yml 2023-11-13 12:27:15 +01:00
Felix Weiß
48b04dfb7f Update test-pipeline.yml 2023-11-13 11:49:29 +01:00
Felix Weiß
9ca31fe748 Update test-pipeline.yml 2023-11-13 10:27:12 +01:00
Felix Weiß
3bc790238e Update test-pipeline.yml 2023-11-13 10:26:11 +01:00
Felix Weiß
df0de8830e Update test-pipeline.yml 2023-11-13 10:23:52 +01:00
Felix Weiß
19db0a9daa Bugfixes
- fixes #11
- fixes #12
- fixes #13
- fixes #14
2023-11-13 10:08:06 +01:00
Felix Weiß
6dd2a1688a Fix cpu version display
- fix logger always printing to console if disabled
- add register adding at connected time and clearing
2023-08-25 16:35:52 +02:00
Felix Weiß
8bd1ce6b4d Disabled live plc tests for now 2023-08-24 09:39:28 +02:00
Felix Weiß
79d6f7fa54 Update publish-pipeline.yml 2023-08-24 09:35:36 +02:00
Felix Weiß
a58e60d4f9 Update csproj nuget references 2023-08-22 16:36:34 +02:00
Felix Weiß
c6bddc61fb Update test-pipeline.yml 2023-08-22 15:36:13 +02:00
Felix Weiß
300d84daf2 Update test-pipeline.yml 2023-08-22 15:20:23 +02:00
Felix Weiß
78b12afbc3 Update README.md 2023-08-22 15:15:43 +02:00
Felix Weiß
30fec39731 Update publish-pipeline.yml 2023-08-22 15:12:49 +02:00
Felix Weiß
372043f18e Update test-pipeline.yml 2023-08-22 15:12:08 +02:00
Felix Weiß
5e8baaddfd Update issue templates 2023-08-21 17:38:12 +02:00
Felix Weiß
97b1a665d7 (pipeline) Fix upload asset path 2023-08-21 17:07:52 +02:00
Felix Weiß
2afcea2767 (pipeline) fix pathing again 2023-08-21 17:03:12 +02:00
Felix Weiß
5d4ef322ee (pipeline) fix pathing 2023-08-21 16:57:58 +02:00
Felix Weiß
590a3a3723 (pipeline) Fix deploy mode to Release 2023-08-21 16:39:01 +02:00
Felix Weiß
19944f9fd9 (pipeline) Fix broken restore 2023-08-21 16:28:34 +02:00
Felix Weiß
993c3cba7e Move examples from root to /Examples 2023-08-21 16:21:55 +02:00
Felix Weiß
47e50078b0 Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2023-08-21 16:10:17 +02:00
Felix Weiß
42d6ee607d (pipeline) fix publish 2023-08-21 16:10:08 +02:00
Felix Weiß
bd907b80df Update README.md 2023-08-21 15:39:59 +02:00
Felix Weiß
b17fcfeff9 Update README.md 2023-08-21 15:38:36 +02:00
Felix Weiß
b3909e3f4b Update README.md 2023-08-21 15:36:20 +02:00
Felix Weiß
40016a5fa5 Update README.md 2023-08-21 15:24:33 +02:00
Felix Weiß
4551d24128 (pipeline) Fix wrong push name for auto_docs branches 2023-08-21 14:41:47 +02:00
Felix Weiß
aed1690a7c Fix docbuilder not knowing linux user paths 2023-08-21 14:36:52 +02:00
Felix Weiß
806b45beef (pipeline) docbuilder custom branch 2023-08-21 14:30:28 +02:00
Felix Weiß
5b4a9966b1 Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2023-08-21 14:13:58 +02:00
Felix Weiß
28752f4396 (pipeline) Add ammend auto docs 2023-08-21 14:13:33 +02:00
GitHub Action
8e5051ed83 (AUTO_DOC) add documentation for branch master 2023-08-21 12:11:20 +00:00
Felix Weiß
938918e4df Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2023-08-21 14:09:17 +02:00
Felix Weiß
642aed666c (householding) formatting fixes 2023-08-21 14:09:12 +02:00
GitHub Action
a8ee01dfd7 (AUTO_DOC) add documentation for branch master 2023-08-21 12:07:34 +00:00
Felix Weiß
a0e54bf8e1 Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2023-08-21 14:05:26 +02:00
Felix Weiß
eca2798859 (pipeline) Add merge last commits for auto doc 2023-08-21 14:05:13 +02:00
GitHub Action
7677ff5015 (AUTO_DOC) add documentation for branch master 2023-08-21 11:47:04 +00:00
Felix Weiß
aff5ca9fda Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2023-08-21 13:44:38 +02:00
Felix Weiß
5ff29e3ffc Fix not supported extended msg header on older plcs 2023-08-21 13:44:30 +02:00
GitHub Action
dba8de159b (AUTO_DOC) add documentation for branch master 2023-08-16 09:27:45 +00:00
Felix Weiß
5221664071 Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2023-08-16 11:25:27 +02:00
Felix Weiß
81676ec2e5 Fix removed nuget conf 2023-08-16 11:24:55 +02:00
GitHub Action
7f3db93f94 (AUTO_DOC) add documentation for branch master 2023-08-15 17:34:25 +00:00
Felix Weiß
0668ad2f86 Add reconnect task cancellation
- fix missing build methods for array registers
- add reconnect injection task
- add slim semaphore for async message single thread handling
2023-08-15 19:31:45 +02:00
Felix Weiß
354c4d6428 Removed polling message for area 2023-08-14 19:43:57 +02:00
Felix Weiß
a00f56074f Added intial connection result 2023-08-14 19:42:55 +02:00
Felix Weiß
4fb9910d54 Adjusted for new mewtocol ver 2023-08-13 23:31:34 +02:00
Felix Weiß
111eacb785 Fixed bool support and duplicate register refs 2023-08-09 18:08:47 +02:00
Felix Weiß
a06a69be3f Generized memory areas 2023-08-08 17:41:18 +02:00
Felix Weiß
5ed2580bb6 Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2023-08-07 18:05:10 +02:00
Felix Weiß
c41b0cef16 Fix queue and dc issues 2023-08-07 18:05:02 +02:00
GitHub Action
ec78fad881 (AUTO_DOC) add documentation for branch master 2023-08-02 15:37:31 +00:00
Felix Weiß
82d9aa8b16 Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2023-08-02 17:35:16 +02:00
Felix Weiß
74cb3bd59f Fixed canceling messages 2023-08-02 17:35:09 +02:00
GitHub Action
b62e0b06e6 (AUTO_DOC) add documentation for branch master 2023-07-31 16:01:24 +00:00
Felix Weiß
e5020253c9 Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2023-07-31 17:59:25 +02:00
Felix Weiß
aa8441fa33 Gone from async queue 2023-07-31 17:59:19 +02:00
GitHub Action
b64a82d5bb (AUTO_DOC) add documentation for branch master 2023-07-28 13:53:51 +00:00
Felix Weiß
3f3e3abe15 Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2023-07-28 15:51:41 +02:00
Felix Weiß
f4af3dd05e Register str data auto updates 2023-07-28 15:51:31 +02:00
GitHub Action
d695108fd4 (AUTO_DOC) add documentation for branch master 2023-07-27 17:06:01 +00:00
Felix Weiß
38b83affd7 Fix multiple poller situational problems 2023-07-27 19:03:02 +02:00
Felix Weiß
1f33ebb8d3 F func params parser 2023-07-26 22:47:35 +02:00
Felix Weiß
4d6eee5585 Added missing F functions set 2023-07-26 00:20:01 +02:00
Felix Weiß
d24201b5d9 Source gen F functions 2023-07-25 16:07:19 +02:00
Felix Weiß
bf78156a9d New functions 2023-07-23 15:29:29 +02:00
GitHub Action
eec1479406 (AUTO_DOC) add documentation for branch master 2023-07-21 16:30:21 +00:00
Felix Weiß
fa2762c082 Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2023-07-21 18:28:36 +02:00
Felix Weiß
9d3e895c3b Fix docbuilder csproj name 2023-07-21 18:28:30 +02:00
Felix Weiß
c1ce66c253 Update README.md 2023-07-21 18:25:28 +02:00
Felix Weiß
a399e0fa16 Fix typecodes 2023-07-21 18:23:20 +02:00
Felix Weiß
9bcffad77b Fix some of the old tests
- add new examples
- fix struct builder
- complete array interfaces
2023-07-20 23:28:58 +02:00
Felix Weiß
3a7b787949 Fix Rbuild layout 2023-07-18 17:33:10 +02:00
Felix Weiß
8fdff367f6 Restricted register generics 2023-07-18 00:51:33 +02:00
Felix Weiß
eb70dac5a8 Multiple fixes 2023-07-17 17:44:20 +02:00
Felix Weiß
d6c00097bc Simplify registers
- add array conversion
- fixed dt area merging
2023-07-17 00:41:27 +02:00
Felix Weiß
4666d3071b Array support first addition 2023-07-14 17:45:31 +02:00
Felix Weiß
32c20e7360 Add Dword / Word 2023-07-14 00:33:42 +02:00
Felix Weiß
daecd73a6d Add more xml doc
- fixed dynamically sized registers auto dt area resize
- add more anonymous register r/w
- add more register builder type casting methods
2023-07-13 00:34:06 +02:00
Felix Weiß
82821e773d Change register list source
- add poller levels
- add new register builder pattern
2023-07-12 18:54:07 +02:00
Felix Weiß
6c7e91f648 Add underlying byte data for registers
- change backend logic for register r/w
- remade interface builder pattern for better syntactic sugar
- refined tests
2023-07-12 00:32:56 +02:00
Felix Weiß
fbd53c850f Change datatypes to uint for registers
- add new tests / fix old tests
- change register builder pattern
- add register attribute from string name
- fix register update triggers
2023-07-11 01:37:44 +02:00
Felix Weiß
d8c18bbf34 Moved MewExplorer to a new repo 2023-07-10 11:00:34 +02:00
Felix Weiß
88cdc1a36c Merge PlcSeriesType into PlcType by using 16 bits 2023-07-07 17:59:22 +02:00
Felix Weiß
864389a4ad Fixed LinqHelpers namespace 2023-07-07 16:46:53 +02:00
Felix Weiß
5d6cef91ec Add plc series types to support FP7 and EcoLogiX
- add new checksum calculation for Mewtocol 7
2023-07-07 16:37:04 +02:00
Felix Weiß
2615f8c428 Update README.md 2023-07-07 00:23:22 +02:00
Felix Weiß
03341d3663 Update test-pipeline.yml 2023-07-07 00:18:41 +02:00
Felix Weiß
6c324bcdff Update test-pipeline.yml 2023-07-07 00:15:19 +02:00
Felix Weiß
befadb67a5 Dev (#9)
* Added autodoc builder step

* Added ignore badges branch for push events

* Fixed doc builder
2023-07-07 00:11:30 +02:00
Felix Weiß
6cb45ac27d Update README.md 2023-07-06 23:43:32 +02:00
Felix Weiß
fcb62960ec Removed MewExplorer from test pipeline 2023-07-06 23:33:18 +02:00
Felix Weiß
fd85a87f5d Added maui workloads 2023-07-06 23:08:28 +02:00
Felix Weiß
bd6a6cf177 Fixed missing dotnet 7 2023-07-06 23:04:40 +02:00
Felix Weiß
638b173847 Fixed env var in test pipeline 2023-07-06 23:01:33 +02:00
Felix Weiß
aa282fe156 Merge pull request #7 from WOmed/AutoStringParse
Auto string parse
2023-07-06 22:54:19 +02:00
Felix Weiß
fe3c849934 Fixed needs in pipeline 2023-07-06 22:49:04 +02:00
Felix Weiß
953e22503a Added new logic for test pipeline 2023-07-06 22:46:17 +02:00
Felix Weiß
d956525538 Removed old examples
- added instant register update if write was successful
2023-07-06 22:20:01 +02:00
Felix Weiß
616d102dea Added new console for all examples later on 2023-07-06 18:51:54 +02:00
Felix Weiß
6d3b5adf7d Remove wrong md 2023-07-06 11:46:51 +02:00
Felix Weiß
a0da9e77fe Added missing PLC types for FP2, FP10SH, FM-M, FP10 2023-07-06 11:39:34 +02:00
Felix Weiß
ab7f5ca302 Added new PLC types and auto documentation 2023-07-06 00:14:40 +02:00
Felix Weiß
f338acfe8a Fixes 2023-07-03 01:25:29 +02:00
Felix Weiß
8c2ba1f68f Added serial port support
- complete restructure of codebase
2023-06-30 18:39:19 +02:00
Felix Weiß
c332cd9f86 Added COM cassette features 2023-06-29 18:13:28 +02:00
Felix Weiß
15cc2e245d Added blazor MewExplorer project 2023-06-28 23:31:07 +02:00
Felix Weiß
bc765b870a Added new conversions
- added compile target for net6
2023-06-28 18:59:28 +02:00
Felix Weiß
a9bd2962b4 Added performance improvements for cyclic polling by using single frame building of multiple registers
- cleaned and refactored codebase
2023-06-27 20:44:11 +02:00
Felix Weiß
7be52efb7e Further implementation
- added new parsing method
2023-06-26 19:13:04 +02:00
Felix Weiß
b48f86d23d Added register builder for booleans and numerics 2023-06-25 22:45:07 +02:00
Felix Weiß
f5f1e3bea9 Namespaces fixed 2023-06-23 15:45:14 +02:00
Felix Weiß
88ad175145 Restructured files
- householding
2023-06-23 15:40:34 +02:00
Felix Weiß
ebee8a0623 Changed boolean register usage and attributes 2023-06-23 13:51:11 +02:00
Felix Weiß
50a11a3497 Update test-pipeline.yml 2023-06-18 23:21:52 +02:00
Felix Weiß
2f9c86d1a4 Update test-pipeline.yml 2023-06-18 23:14:16 +02:00
Felix Weiss
62bb1b422a Added more unit tests (#4) 2023-06-18 22:51:28 +02:00
Felix Weiß
431c5895f0 Update README.md 2023-06-18 22:26:35 +02:00
Felix Weiß
ebde5421ab Update README.md 2023-06-18 22:21:57 +02:00
Felix Weiß
c1678ae6b4 Update test-pipeline.yml 2023-06-18 22:16:19 +02:00
Felix Weiß
e2f53f9c4a Update README.md 2023-06-18 22:09:18 +02:00
Felix Weiß
844fcfc76d Update test-pipeline.yml 2023-06-18 21:55:03 +02:00
Felix Weiß
111482ec2d Update test-pipeline.yml 2023-06-18 21:52:16 +02:00
Felix Weiß
b72771c13e Update test-pipeline.yml 2023-06-18 21:48:09 +02:00
Felix Weiß
caa138c130 Update test-pipeline.yml 2023-06-18 21:37:43 +02:00
Felix Weiß
e3b6fdf780 Update test-pipeline.yml 2023-06-17 13:15:12 +02:00
Felix Weiß
5dfa48a89a Update test-pipeline.yml 2023-06-17 12:54:36 +02:00
Felix Weiß
128b9f9705 Update test-pipeline.yml 2023-06-17 12:43:32 +02:00
Felix Weiß
69d728f345 Update test-pipeline.yml 2023-06-17 11:41:06 +02:00
Felix Weiß
c01204ce57 Update test-pipeline.yml 2023-06-17 11:33:23 +02:00
Felix Weiß
da2cb6b58a Update test-pipeline.yml 2023-06-17 11:28:21 +02:00
Felix Weiß
96827f3baf Update test-pipeline.yml 2023-06-17 11:21:08 +02:00
Felix Weiß
2df944da8c Update test-pipeline.yml 2023-06-17 11:17:21 +02:00
Felix Weiß
f5e6d7d6af Added new tests 2023-06-16 16:29:01 +02:00
Felix Weiß
ff3cc41aea Update README.md 2023-06-16 15:05:34 +02:00
Felix Weiß
d5b701186e Fixed caching again 2023-06-16 14:36:49 +02:00
Felix Weiß
0702ab7d0a Fixed caching 2023-06-16 14:32:54 +02:00
Felix Weiß
5adadbfc46 fixed caching 2023-06-16 14:24:16 +02:00
Felix Weiß
739f83bf0c Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2023-06-16 14:19:05 +02:00
Felix Weiß
a9bd792af1 added test result saving 2023-06-16 14:18:51 +02:00
Felix Weiß
e6ffc92db6 Update README.md 2023-06-16 12:21:41 +02:00
Felix Weiß
7bd07bb520 Fixed env.VERSION for sed step 2023-06-16 12:10:57 +02:00
Felix Weiß
f99af9511b Fix wrong ver 2023-06-16 12:03:44 +02:00
Felix Weiß
b13b2d0199 Fixed sed escape 2023-06-16 11:49:17 +02:00
Felix Weiß
364e0637bb fixed versioning auto release 2023-06-16 11:42:04 +02:00
Felix Weiß
bba90106cb Made test-pipeline.yml callable 2023-06-16 11:22:48 +02:00
Felix Weiß
3dea18d285 Speperated test and publish pipeline 2023-06-16 11:18:08 +02:00
Sandoun
ad61361008 Use setup dotnet v2 2023-06-16 10:39:41 +02:00
Sandoun
0e0659000a Fixed ci no write permission 2023-06-16 10:34:57 +02:00
Sandoun
8cc47b496a Fixed new publish pipeline 2023-06-16 10:30:14 +02:00
Sandoun
9eb09fc7ec Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2023-06-16 10:24:25 +02:00
Sandoun
9b8fca6561 Changed package publisher to gh 2023-06-16 10:24:19 +02:00
Felix Weiß
6205f81931 Update README.md 2023-06-16 10:05:30 +02:00
Sandoun
a8960f12c8 Changed to opencover test format 2023-06-16 10:00:15 +02:00
Felix Weiß
0b670b1a27 Update build-pipeline.yml 2023-06-16 09:48:15 +02:00
Felix Weiß
63a1348704 Update README.md 2023-06-15 21:03:31 +02:00
Felix Weiß
61fe2aff65 Update build-pipeline.yml 2023-06-15 21:02:55 +02:00
Felix Weiß
314ce9c053 Merge pull request #5 from WOmed/PropAutoSetters
Prop auto setters
2023-06-15 20:51:34 +02:00
Sandoun
7864915967 Added auto prop sending
- updated readme
2023-06-15 20:49:14 +02:00
Sandoun
62f4a48bf9 Housekeeping 2023-06-15 20:23:48 +02:00
Sandoun
a78a405c25 update readme 2023-06-15 20:08:20 +02:00
Felix Weiß
09f4da54a9 Made registers use the IRegister interface
- cleanup and refactoring
- fully implemented auto prop register generator unit tests #4
- added plc test program c30 fpx-h
- fixed bitarray setback
- cleaned up examples and added new ones with addition of attributes for later additions
2023-06-15 20:04:38 +02:00
Felix Weiss
6ca8e9de96 Added mewtocol logger to unit test output 2023-06-13 23:11:56 +02:00
Felix Weiss
53a0856634 Change test result step name 2023-06-13 22:58:50 +02:00
Felix Weiß
2b173aeb95 Update build-pipeline.yml 2023-06-13 22:44:49 +02:00
Felix Weiß
2680bbb07b Update build-pipeline.yml 2023-06-13 22:41:46 +02:00
Felix Weiß
9fd178424f Update build-pipeline.yml 2023-06-13 22:35:38 +02:00
Felix Weiß
e96496cff8 Update README.md 2023-06-13 13:52:40 +02:00
Felix Weiß
39ff1a5c5b Made code ql only callable 2023-06-13 13:02:21 +02:00
Felix Weiß
6c50324696 Code ql test 2023-06-13 12:58:37 +02:00
Felix Weiß
affa2ea83f Merged CI into one workflow file 2023-06-13 12:49:33 +02:00
Felix Weiß
af101812a8 Reordered CI 2023-06-13 12:35:34 +02:00
Felix Weiß
35f2786129 Added test CI on push 2023-06-13 12:30:08 +02:00
Felix Weiß
062eb75876 CI use only net6 2023-06-13 12:09:29 +02:00
Felix Weiß
c771e747eb CI only .net 5 and 6 2023-06-13 12:04:30 +02:00
Felix Weiß
dc24b33297 CI fix 2023-06-13 12:01:28 +02:00
Felix Weiß
68aa2ad12a Unit test CI 2023-06-13 11:56:09 +02:00
Felix Weiß
bb1cc8c011 Merge pull request #3 from WOmed/dev
Added new unit tests
2023-06-13 11:45:38 +02:00
Felix Weiß
24525521af Added new unit tests 2023-06-13 11:42:49 +02:00
Felix Weiß
435624f8a2 Update test-on-hardware.yml 2023-06-13 10:41:11 +02:00
Felix Weiß
7c3f279576 Create test-on-hardware.yml 2023-06-13 10:36:33 +02:00
Felix Weiß
6001402991 Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2023-02-27 16:01:54 +01:00
Felix Weiß
fb2bd8d56d Added test structure 2023-02-27 16:01:47 +01:00
Felix Weiß
a71d545bf3 Create codeql.yml 2023-02-24 10:11:51 +01:00
Felix Weiß
43c7f72ac4 Added auto register reset to default typed value on disconnect 2022-10-21 11:56:58 +02:00
Felix Weiß
2e35ed87af Updated hyperlinks in readme 2022-10-20 10:16:11 +02:00
Felix Weiß
a7f97a72ea Package id change in readme 2022-10-20 10:11:13 +02:00
Felix Weiß
51870166e4 Update publish.yml 2022-10-20 10:04:49 +02:00
Felix Weiß
b43e9bd201 Package id 2022-10-20 10:02:59 +02:00
Felix Weiß
e313dbc3ec Changed package ID 2022-10-20 10:01:53 +02:00
Felix Weiß
bdf9f93f97 Update MewtocolNet.csproj 2022-10-20 09:50:58 +02:00
Felix Weiß
fe816ab78e Update publish.yml 2022-10-20 09:46:08 +02:00
Felix Weiß
48a5977185 Create publish.yml 2022-10-20 09:36:35 +02:00
Felix Weiß
c69f63c191 Update README.md 2022-09-27 10:21:37 +02:00
Felix Weiß
6a2f278dd1 Fixed negative queued msges
- version count up
2022-09-27 10:18:01 +02:00
Felix Weiß
19159ed183 Upcount version 2022-09-27 09:53:02 +02:00
Felix Weiß
635823a66f Made poller delay thread safe
- added property for currentlly queued messages
- added (optional) progress to ReadByteRange
2022-09-27 09:52:35 +02:00
Felix Weiß
c7a6559f97 Added methods to pause/resume the auto polling 2022-09-23 16:38:14 +02:00
Felix Weiß
88a453355c Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2022-09-21 16:24:08 +02:00
Felix Weiß
6f8f891760 Added poller delay
- added downstream and upstream speeds
- refactored SerialQueue
- counted version up to 0.5.5
2022-09-21 16:24:00 +02:00
Felix Weiß
8fb8d4989d Update version in package ref 2022-08-04 11:53:20 +02:00
Felix Weiß
45a9fa0520 Version upcount 2022-08-04 11:48:04 +02:00
Felix Weiß
772f8b89a4 Fixed auto value string enum conversion 2022-08-04 11:47:37 +02:00
Felix Weiß
6c7c368b55 Bugfixes in host connection params setup 2022-07-19 17:37:10 +02:00
Felix Weiß
38f0f9f523 Counted up version number 2022-07-19 09:31:06 +02:00
Felix Weiß
c2aecb387a Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2022-07-19 09:28:27 +02:00
Felix Weiß
0e1f5cd12b Added possiblity to connect over a certain host endpoint
- fixed missing docs and
- fixed accessing modifiers
2022-07-19 09:28:18 +02:00
Felix Weiß
f15d565029 Typos 2022-07-18 16:50:48 +02:00
Felix Weiß
0ac0198223 Update README.md 2022-07-18 16:49:53 +02:00
Felix Weiß
3c450eea97 Added method to change connection params 2022-07-18 16:47:43 +02:00
Felix Weiß
fe2d2b9fb9 Made the interface disposable and refactoring 2022-07-18 09:55:36 +02:00
Felix Weiß
6c411d7318 Heavily improved the overall performance and async handling
- added a queuing manager for all messages
- tcp now keeps the connection after the first handshake
- async tasks are now run by the time order they were added
- minor bugfixes
2022-07-15 15:53:26 +02:00
Felix Weiß
cdae9a60fb Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2022-07-13 18:28:40 +02:00
Felix Weiß
b1c2cdb70e Fixed exception on duplicate memory adresses when the adress area was different
- counted up version number
2022-07-13 18:28:28 +02:00
Felix Weiß
95bfcf94de Update README.md 2022-07-04 16:57:42 +02:00
Felix Weiß
0a93df287d Fixed more exception handling and added the ability to close a running interface 2022-07-04 16:53:55 +02:00
Felix Weiß
18384ff964 Fixed not updating timespan auto propertys and thread blocking Wait() call for await 2022-07-01 15:12:24 +02:00
Felix Weiß
e4ddad685a Fixed exception handling on receive 2022-06-30 15:47:05 +02:00
Felix Weiß
e953938a65 Added support for auto enum casting
- counted up version number
- cleaned up register constructors that are used inernally only
2022-06-24 11:25:26 +02:00
Felix Weiß
83f17a4eae Fixed bug a bug where the writeregister method always returned false
- fixed a bug where the adress ToString of a register with a special adress was always ending with 0
- counted up version
2022-06-23 12:15:42 +02:00
Felix Weiß
4c719843f2 Fixed issues with bitwise decoding not working correctly
- refactored bitwise register assign methods
- counted up version number
2022-06-22 15:58:24 +02:00
Felix Weiß
8f9e66d5d3 Added string value to register base for viewmodel support
- counted up version number
2022-06-22 10:13:15 +02:00
Felix Weiß
5cc222abcc Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2022-06-22 09:22:37 +02:00
Felix Weiß
14659ffaad Implemented INotifyPropertyChanged on MewtocolInterface
- added event for disconnect
- counted up version number
2022-06-22 09:21:50 +02:00
186 changed files with 22061 additions and 2782 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

70
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,70 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
workflow_call:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'csharp' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

View File

@@ -1,23 +0,0 @@
name: .NET Windows
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: windows-latest
strategy:
matrix:
dotnet-version: [ '3.0', '3.1.x', '5.0.x' ]
steps:
- uses: actions/checkout@v3
- name: Setup dotnet ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v2
with:
dotnet-version: ${{ matrix.dotnet-version }}
# You can test your matrix by printing the current dotnet version
- name: Display dotnet version
run: dotnet --version

178
.github/workflows/publish-pipeline.yml vendored Normal file
View File

@@ -0,0 +1,178 @@
name: Publish pipeline
on:
workflow_dispatch:
inputs:
build_pre_release:
description: 'Mark as pre-release'
required: true
default: false
type: boolean
version_tag:
description: 'The version number formatted as X.X.X'
default: '0.1.0'
required: true
type: string
permissions: write-all
jobs:
test-pipeline:
name: 'Invoke the test pipeline'
uses: ./.github/workflows/test-pipeline.yml
secrets: inherit
#Deploy package
publish-package:
name: 'Build and publish package'
needs: test-pipeline
runs-on: [self-hosted, linux, ARM64]
steps:
- uses: actions/checkout@v3
- name: 'RenameVersionTag'
shell: bash
run: |
PRE_REL_STR='-pre'
EMPTY_STR=
if ${{ github.event.inputs.build_pre_release }}
then
echo "prerelease_append=$PRE_REL_STR" >> $GITHUB_ENV
else
echo "prerelease_append=$EMPTY_STR" >> $GITHUB_ENV
fi
- name: Set .csproj version to ${{ github.event.inputs.version_tag }}${{ env.prerelease_append }}
run: |
sed -i 's/<Version>[0-9].[0-9].[0-9]<\/Version>/<Version>${{ github.event.inputs.version_tag }}${{ env.prerelease_append }}<\/Version>/g' MewtocolNet/MewtocolNet.csproj
less MewtocolNet/MewtocolNet.csproj
- name: 'Bump version and push tag v${{ github.event.inputs.version_tag }}${{ env.prerelease_append }}'
id: tag_version
uses: mathieudutour/github-tag-action@v6.1
with:
tag_prefix: v
custom_tag: ${{ github.event.inputs.version_tag }}${{ env.prerelease_append }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: 'Build Changelog'
uses: mikepenz/release-changelog-builder-action@v3
id: github_release_log
with:
commitMode: true
configurationJson: |
{
"template": "#{{CHANGELOG}}\n\n<details>\n<summary>Uncategorized</summary>\n\n#{{UNCATEGORIZED}}\n</details>",
"pr_template": "- #{{TITLE}} #{{MERGE_SHA}}",
"trim_values" : true,
"ignore_labels": [ "ignore" ],
"categories": [
{
"title": "## 🚀 Added Features",
"labels": ["feature", "features", "add", "added", "implemented", "impl", "new"]
},
{
"title": "## 🔃 Changed Features",
"labels": ["change", "changed"]
},
{
"title": "## ❌ Removed Features",
"labels": ["remove", "removed"]
},
{
"title": "## 🪲 Fixes",
"labels": ["fix", "fixed", "fixes"]
},
{
"title": "## 📖 Documentation",
"labels": ["doc", "docs", "documentation"]
},
{
"title": "## 🧪 Tests",
"labels": ["test", "tests", "unittests"]
},
{
"title": "## 📦 Dependencies",
"labels": ["dependencies", "package", "nuget"]
},
{
"title": "## 🏠 Householding",
"labels": ["upgraded", "upgrade", "update", "clear", "delete", "deleted", "version"]
},
{
"title": "## 🌎 Localization",
"labels": ["lang", "language", "localization", "locale", "translation", "translations"]
},
{
"title": "## 💬 Other",
"labels": ["other"]
}
],
"duplicate_filter": {
"pattern": ".*",
"on_property": "title",
"method": "match"
},
"transformers": [
{
"pattern": "^.*?\\s*\\(?(\\w+)\\)? (.*)",
"target": "- $1 $2"
}
],
"label_extractor": [
{
"pattern": "^.*?\\s*\\(?(\\w{3,})\\)? (.*)",
"target": "$1",
"flags": "gis"
}
]
}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Setup .NET
uses: actions/setup-dotnet@v2
with:
dotnet-version: '6.0.x'
- name: Restore dependencies
run: dotnet restore ./MewtocolNet/MewtocolNet.csproj
- name: Build as ${{ github.event.inputs.version_tag }}${{ env.prerelease_append }}
run: dotnet build "MewtocolNet" --no-incremental -c:Release
- name: Pack as ${{ github.event.inputs.version_tag }}${{ env.prerelease_append }}
run: dotnet pack "MewtocolNet" -c:Release
- name: Publish as ${{ github.event.inputs.version_tag }}${{ env.prerelease_append }}
run: |
cd '${{ github.workspace }}/Builds/MewtocolNet'
ls -l
dotnet nuget push "*.nupkg" --skip-duplicate --api-key ${{ secrets.GITHUB_TOKEN }} --source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json"
dotnet nuget push "*.nupkg" --skip-duplicate --api-key ${{ secrets.NUGET_TOKEN }} --source "https://api.nuget.org/v3/index.json"
- name: 'Create Release v${{ github.event.inputs.version_tag }}${{ env.prerelease_append }}'
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ github.event.inputs.version_tag }}${{ env.prerelease_append }}
release_name: v${{ github.event.inputs.version_tag }}${{ env.prerelease_append }}
body: |
## Changelog
${{ steps.github_release_log.outputs.changelog }}
**Total commits:** ${{ steps.github_release_log.outputs.commits }}
draft: false
prerelease: ${{ github.event.inputs.build_pre_release }}
- name: 'Upload package to latest release'
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ github.token }}
with:
upload_url: "${{ steps.create_release.outputs.upload_url }}"
asset_path: ${{ github.workspace }}/Builds/MewtocolNet/Mewtocol.NET.${{ github.event.inputs.version_tag }}${{ env.prerelease_append }}.nupkg
asset_name: Mewtocol.NET.${{ github.event.inputs.version_tag }}${{ env.prerelease_append }}.nupkg
asset_content_type: application/zip

111
.github/workflows/test-pipeline.yml vendored Normal file
View File

@@ -0,0 +1,111 @@
name: Test pipeline
on:
workflow_dispatch:
workflow_call:
inputs:
cache-id:
default: 'test-results'
required: false
type: string
push:
branches-ignore:
- badges
paths-ignore:
- '**.md'
permissions: write-all
jobs:
#Run unit tests on the test PLCs
run-unit-tests:
name: 'Run tests and documentation'
runs-on: self-hosted
steps:
- name: 'Setup dotnet'
uses: actions/setup-dotnet@v2
with:
dotnet-version: |
6.0.x
7.0.x
- name: 'Checkout'
uses: actions/checkout@v3
- name: 'Extract branch name'
shell: bash
run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT
id: extract_branch
- name: 'Run tests'
run: dotnet test "./MewtocolTests" /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:CoverletOutput=../Builds/TestResults/coverage.opencover.xml
- name: 'Run docbuilder'
continue-on-error: true
run: |
dotnet run --project "./AutoTools.DocBuilder/AutoTools.DocBuilder.csproj" "~/plctypes.md"
- name: 'Switch and Commit to docs branch'
continue-on-error: true
run: |
git fetch
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git checkout --orphan ${{ steps.extract_branch.outputs.branch }}_auto_docs
git rm -rf .
cp ~/plctypes.md ./plctypes.md &&
git add "./plctypes.md" -f &&
git commit -m "Update documentation"
- name: 'Push docs commit'
uses: ad-m/github-push-action@master
continue-on-error: true
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ steps.extract_branch.outputs.branch }}_auto_docs
- name: Report Generator
uses: danielpalme/ReportGenerator-GitHub-Action@5.1.22
with:
reports: './Builds/TestResults/coverage.opencover.xml'
targetdir: './Builds/TestResults'
reporttypes: HtmlSummary;MarkdownSummaryGithub;Badges
historydir: './Builds/Hist'
title: Report
- name: Markdown report and copy for badges branch
run: |
cat './Builds/TestResults/SummaryGithub.md' >> $GITHUB_STEP_SUMMARY
cp ./Builds/TestResults/badge_combined.svg ~/badge_combined.svg
cp ./Builds/TestResults/summary.html ~/summary.html
ls -l ~
- name: Cache test results
if: ${{ github.event_name == 'workflow_call' }}
uses: actions/cache/save@v3
with:
key: ${{ inputs.cache-id }}-${{ steps.extract_branch.outputs.branch }}
path: |
${{ github.workspace }}/Builds/TestResults
- name: Commit badge
continue-on-error: true
run: |
git fetch
git checkout badges
cp ~/summary.html ./Builds/TestResults/summary_${{ steps.extract_branch.outputs.branch }}.html
cp ~/badge_combined.svg ./Builds/TestResults/badge_combined_${{ steps.extract_branch.outputs.branch }}.svg
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add "./Builds/TestResults/badge_combined_${{ steps.extract_branch.outputs.branch }}.svg" -f
git add "./Builds/TestResults/summary_${{ steps.extract_branch.outputs.branch }}.html" -f
git commit -m "Add/Update badge for branch ${{ steps.extract_branch.outputs.branch }}"
- name: 'Push badge commit'
uses: ad-m/github-push-action@master
if: ${{ success() }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: badges

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPublishable>false</IsPublishable>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MewtocolNet\MewtocolNet.csproj"/>
<PackageReference Include="HtmlAgilityPack" Version="1.11.50" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,510 @@
using System.Collections.Specialized;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using HtmlAgilityPack;
using MewtocolNet;
using MewtocolNet.Helpers;
namespace AutoTools.ChmDataExtract;
internal class Program {
const string sysVarsLoc = @"Panasonic-ID SUNX Control\Control FPWIN Pro 7\Mak\Res_Eng\SysVars.chm";
const string funcNamesLoc = @"Panasonic-ID SUNX Control\Control FPWIN Pro 7\Mak\Res_Eng\FPWINPro.chm";
static Dictionary<string, List<PlcType>> plcGroups = new() {
{ "FP7 CPS41/31 E/ES", new List<PlcType> {
PlcType.FP7_120k__CPS31E,
PlcType.FP7_196k__CPS41E,
PlcType.FP7_120k__CPS31ES,
PlcType.FP7_196k__CPS41ES,
}},
{ "FP7 CPS31/31S", new List<PlcType> {
PlcType.FP7_120k__CPS31,
PlcType.FP7_120k__CPS31S,
}},
{ "FP7 CPS21", new List<PlcType> {
PlcType.FP7_64k__CPS21,
}},
{ "ELC500", new List<PlcType> {
PlcType.ECOLOGIX_0k__ELC500,
}},
{ "FP-SIGMA 12k", new List<PlcType> {
PlcType.FPdSIGMA_12k,
PlcType.FPdSIGMA_16k,
}},
{ "FP-SIGMA 32k", new List<PlcType> {
PlcType.FPdSIGMA_32k,
PlcType.FPdSIGMA_40k,
}},
{ "FP0R 16k/32k C types", new List<PlcType> {
PlcType.FP0R_16k__C10_C14_C16,
PlcType.FP0R_32k__C32,
}},
{ "FP0R 32k T32", new List<PlcType> {
PlcType.FP0R_32k__T32,
PlcType.FP0R_32k__F32,
}},
{ "FP2 16k", new List<PlcType> {
PlcType.FP2_16k,
}},
{ "FP2 32k", new List<PlcType> {
PlcType.FP2_32k,
}},
{ "FP2SH 32k/60k/120k", new List<PlcType> {
PlcType.FP2SH_60k,
PlcType.FP2SH_60k,
PlcType.FP2SH_120k,
}},
{ "FP-X 16k/32k R-types", new List<PlcType> {
PlcType.FPdX_16k__C14R,
PlcType.FPdX_32k__C30R_C60R,
}},
{ "FP-X 16k/32k T-types", new List<PlcType> {
PlcType.FPdX_16k__C14TsP,
PlcType.FPdX_32k__C30TsP_C60TsP_C38AT_C40T,
}},
{"FP0H C32T/P ET/EP", new List<PlcType> {
PlcType.FP0H_32k__C32TsP,
PlcType.FP0H_32k__C32ETsEP,
}},
{ "FP-X 16k/32k L-types", new List<PlcType> {
PlcType.FPdX_16k__L14,
PlcType.FPdX_32k__L30_L60,
}},
{ "FP-X 2.5k C40RT0A", new List<PlcType> {
PlcType.FPdX_2c5k__C40RT0A,
}},
{ "FP-X0 2.5k L14,L30", new List<PlcType> {
PlcType.FPdX0_2c5k__L14_L30,
}},
{ "FP-X0 8k L40,L60", new List<PlcType> {
PlcType.FPdX0_8k__L40_L60,
}},
{ "FP-e 2.7k", new List<PlcType> {
PlcType.FPde_2c7k,
}},
{ "FP-XH 16k/32k R-types", new List<PlcType> {
PlcType.FPdXH_16k__C14R,
PlcType.FPdXH_32k__C30R_C40R_C60R,
}},
{ "FP-XH 16k/32k T-types", new List<PlcType> {
PlcType.FPdXH_16k__C14TsP,
PlcType.FPdXH_32k__C30TsP_C40T_C60TsP,
PlcType.FPdXH_32k__C30TsP_C40T_C60TsP,
PlcType.FPdXH_32k__C38AT,
PlcType.FPdXH_32k__C40ET_C60ET,
PlcType.FPdXH_32k__C60ETF,
}},
{ "FP-XH M4/M8 types", new List<PlcType> {
PlcType.FPdXH_32k__M4TsL,
PlcType.FPdXH_32k__M8N16TsP,
PlcType.FPdXH_32k__M8N30T,
}},
};
class AddressException {
public string ExceptionTitle;
public string ForSysRegister;
}
internal class FPFunction {
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string RedundantName { get; set; } = null!;
public string Description { get; set; } = null!;
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Dictionary<string, string[]>? ParametersIn { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Dictionary<string, string[]>? ParametersOut { get; set; }
}
static void Main(string[] args) => Task.Run(AsyncMain).Wait();
static async Task AsyncMain () {
GetFunctionNames();
//await GetSystemRegisters();
}
static void GetFunctionNames () {
var functions = new Dictionary<string, FPFunction>();
var progLoc = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
var progFilesPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
var sysVarsPath = Path.Combine(progFilesPath, funcNamesLoc);
Directory.SetCurrentDirectory(progLoc);
File.Copy(sysVarsPath, "./FPWINPro.chm", true);
var startInfo = new ProcessStartInfo {
WorkingDirectory = progLoc,
FileName = "hh.exe",
Arguments = $"-decompile ./DecompFuncs ./FPWINPro.chm",
};
//call the hh.exe decompiler for chm
if (!File.Exists("./DecompFuncs/topics/availability.html")) {
var proc = Process.Start(startInfo)!;
proc.WaitForExit();
}
var doc = new HtmlDocument();
doc.Load("./DecompFuncs/topics/availability.html");
//[contains(@class, 'table mainbody')]
foreach (HtmlNode table in doc.DocumentNode.SelectNodes("//table[1]")) {
var rows = table?.SelectSingleNode("tbody")?.SelectNodes("tr");
if (rows == null) continue;
foreach (var row in rows) {
var columns = row.SelectNodes("td");
if (columns == null) continue;
var itemRow = columns?.FirstOrDefault()?.SelectSingleNode("p/a[contains(@class,'xref')]");
string rowName = itemRow?.InnerText ?? "Unnamed";
if (!Regex.IsMatch(rowName, @"^F[0-9]{1,3}_.*$")) continue;
FPFunction functionIns = new FPFunction();
Console.Write($"Var: {rowName, -20}");
var href = itemRow?.GetAttributeValue("href", null);
if (href != null) {
//Console.Write($" {href}");
var docSub = new HtmlDocument();
docSub.Load($"./DecompFuncs{href}");
var noteSection = docSub.DocumentNode.SelectSingleNode("//section/div[contains(@class,'note note')]");
var xrefRedundant = noteSection?.SelectSingleNode("p/a[contains(@class,'xref')]");
var xrefNodeContent = noteSection?.SelectSingleNode("p/span");
//get params in / out
var inOutDefinitionNodes = docSub.DocumentNode.SelectNodes("//p[contains(@class,'p inoutput')]");
if (inOutDefinitionNodes != null) {
foreach (var ioTypeNode in inOutDefinitionNodes) {
var nodeInOutType = ioTypeNode.InnerText.SanitizeLinebreakFormatting();
Console.Write($"{nodeInOutType}: ");
var currentSibling = ioTypeNode;
while (true) {
if (currentSibling.NextSibling == null) break;
currentSibling = currentSibling.NextSibling;
if (currentSibling.HasClass("inoutput")) {
break;
}
var paramNodes = currentSibling.SelectNodes("dt");
if (paramNodes == null) continue;
foreach (var paramNode in paramNodes) {
var paramName = paramNode.SelectSingleNode("span[1]")?.InnerText?.SanitizeBracketFormatting();
var paramTypes = paramNode.SelectSingleNode("span[2]")?.InnerText?.SanitizeBracketFormatting();
if (paramName != null && paramTypes != null) {
if (functionIns.ParametersIn == null)
functionIns.ParametersIn = new Dictionary<string, string[]>();
if (functionIns.ParametersOut == null)
functionIns.ParametersOut = new Dictionary<string, string[]>();
Console.Write($"{paramName} {paramTypes}");
var splitParamNames = paramName.Split(", ");
foreach (var splitName in splitParamNames) {
if (nodeInOutType == "Input") {
if (functionIns.ParametersIn.ContainsKey(splitName)) break;
functionIns.ParametersIn.Add(splitName, paramTypes.SanitizeBracketFormatting().Split(", "));
} else {
if (functionIns.ParametersOut.ContainsKey(splitName)) break;
functionIns.ParametersOut.Add(splitName, paramTypes.SanitizeBracketFormatting().Split(", "));
}
}
}
}
}
}
}
HtmlNode? descrSection = null;
if (xrefRedundant != null && xrefNodeContent != null && xrefNodeContent.InnerText.StartsWith("This is a redundant F instruction")) {
descrSection = docSub.DocumentNode.SelectSingleNode("//section[2]");
functionIns.RedundantName = xrefRedundant.InnerText;
//Console.Write($"{xrefRedundant.InnerText}");
} else {
descrSection = docSub.DocumentNode.SelectSingleNode("//section[1]");
}
if (descrSection != null) {
var descrText = descrSection?.InnerText;
if(descrText != null) {
descrText = descrText.SanitizeLinebreakFormatting().Replace("\"", "\\\"");
functionIns.Description = descrText;
//Console.Write($" {descrText}");
}
}
}
functions.Add(rowName, functionIns);
Console.WriteLine();
//compatibility matrix
//for (int i = 1; i < columns?.Count - 1; i++) {
// bool isChecked = columns?.ElementAtOrDefault(i)?.SelectSingleNode("p")?.InnerHtml != "";
// Console.Write($"{(isChecked ? "1" : "0")}, ");
//}
}
}
BuildFunctionNamesDictFile(functions);
}
static void BuildFunctionNamesDictFile (Dictionary<string, FPFunction> dict) {
var sb = new StringBuilder();
sb.AppendLine("using System.Collections.Generic;\n");
sb.AppendLine("namespace MewtocolNet.AutoGeneratedData {\n");
sb.AppendLine("\tpublic class FPFunction {\n");
sb.AppendLine("\t\tpublic string RedundantName { get; private set; }\n");
sb.AppendLine("\t\tpublic string Description { get; private set; }\n");
sb.AppendLine("\t\tpublic Dictionary<string, string[]> ParametersIn { get; private set; }\n");
sb.AppendLine("\t\tpublic Dictionary<string, string[]> ParametersOut { get; private set; }\n");
sb.AppendLine("\t\tpublic static readonly Dictionary<string, FPFunction> functions = new Dictionary<string, FPFunction> {\n");
foreach (var item in dict) {
sb.AppendLine($"\t\t\t{{ \"{item.Key}\", new FPFunction {{");
if(item.Value.RedundantName != null)
sb.AppendLine($"\t\t\t\tRedundantName = \"{item.Value.RedundantName}\",");
if(item.Value.Description != null)
sb.AppendLine($"\t\t\t\tDescription = \"{item.Value.Description}\",");
if (item.Value.ParametersIn != null) {
sb.AppendLine("\t\t\t\tParametersIn = new Dictionary<string, string[]> {");
foreach (var paramIn in item.Value.ParametersIn) {
sb.AppendLine($"\t\t\t\t\t{{ \"{paramIn.Key}\", new string[] {{");
foreach (var paramType in paramIn.Value) {
sb.AppendLine($"\t\t\t\t\t\t\"{paramType}\",");
}
sb.AppendLine("\t\t\t\t\t}},");
}
sb.AppendLine("\t\t\t\t},");
}
if (item.Value.ParametersOut != null) {
sb.AppendLine("\t\t\t\tParametersOut = new Dictionary<string, string[]> {");
foreach (var paramOut in item.Value.ParametersOut) {
sb.AppendLine($"\t\t\t\t\t{{ \"{paramOut.Key}\", new string[] {{");
foreach (var paramType in paramOut.Value) {
sb.AppendLine($"\t\t\t\t\t\t\"{paramType}\",");
}
sb.AppendLine("\t\t\t\t\t}},");
}
sb.AppendLine("\t\t\t\t},");
}
sb.AppendLine($"\t\t\t}}}},");
}
sb.AppendLine("\t\t};\n");
sb.AppendLine("\t}\n");
sb.AppendLine("}");
File.WriteAllText("../../../../MewtocolNet/AutoGeneratedData/FPFunction.cs", sb.ToString());
}
static async Task GetSystemRegisters () {
var addressExceptions = new List<AddressException>();
var progLoc = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
var progFilesPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
var sysVarsPath = Path.Combine(progFilesPath, sysVarsLoc);
Directory.SetCurrentDirectory(progLoc);
File.Copy(sysVarsPath, "./SysVars.chm", true);
var startInfo = new ProcessStartInfo {
WorkingDirectory = progLoc,
FileName = "hh.exe",
Arguments = $"-decompile ./Decomp ./SysVars.chm",
};
//call the hh.exe decompiler for chm
if (!File.Exists("./Decomp/topics/availability.html")) {
var proc = Process.Start(startInfo)!;
proc.WaitForExit();
}
var doc = new HtmlDocument();
doc.Load("./Decomp/topics/availability.html");
//[contains(@class, 'table mainbody')]
foreach (HtmlNode table in doc.DocumentNode.SelectNodes("//table[1]")) {
var rows = table?.SelectSingleNode("tbody")?.SelectNodes("tr");
if (rows == null) continue;
string lastRegisterName = "Name";
int iSystemRegister = 0;
foreach (var row in rows) {
var columns = row.SelectNodes("td");
if (columns == null) continue;
//get var name
var varNameNode = columns?.FirstOrDefault()?.SelectSingleNode("p/a[contains(@class,'xref')]");
string registerAddress;
int iterateStart;
if (varNameNode != null) {
lastRegisterName = varNameNode.InnerText;
//get second col
var regAddressNode = columns?.ElementAtOrDefault(1)?.SelectSingleNode("p");
registerAddress = regAddressNode?.InnerText ?? "Null";
iterateStart = 2;
} else {
//get first col
var regAddressNode = columns?.ElementAtOrDefault(0)?.SelectSingleNode("p");
registerAddress = regAddressNode?.InnerText ?? "Null";
iterateStart = 1;
}
//filter the address for annotations
var regexAnnotation = new Regex(@"\(.*\)");
var matchAnnotation = regexAnnotation.Match(registerAddress);
if (matchAnnotation.Success) {
registerAddress = regexAnnotation.Replace(registerAddress, "");
addressExceptions.Add(new AddressException {
ForSysRegister = lastRegisterName,
ExceptionTitle = matchAnnotation.Value,
});
}
Console.Write($"Var: {lastRegisterName} | {registerAddress} ".PadRight(100, ' '));
for (int i = iterateStart, j = 0; i < columns?.Count + 1; i++) {
if (j >= plcGroups.Count - 1) continue;
var group = plcGroups.Keys.ToList()[j];
bool isChecked = columns?.ElementAtOrDefault(i)?.SelectSingleNode("p")?.InnerHtml != "";
Console.Write($"{(isChecked ? "1" : "0")}, ");
j++;
}
Console.WriteLine();
}
}
}
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPublishable>false</IsPublishable>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MewtocolNet\MewtocolNet.csproj"/>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,2 @@
# Reading and writing programs

View File

@@ -0,0 +1,291 @@
# Open Questions
- What is the HW information byte for?
- What are the last bytes of the EXRT message for?
# Mewtocol Init
| PLC TYPE | CPU Code | Machine Code | HW Information|
|--------------|--------------|--------------|---------------|
| FPX C14 R | 20 | 70 | 02 |
| FPX C30 T | 20 | 77 | 02 |
| FPX-H C14 R | 20 | A0 | 01 |
| FPX-H C30 T | 20 | A5 | 01 |
## FPX 16k C14R Actual Response Examples
### %EE$RT
|Reponse Byte|Description|
|------------|-----------|
| 20 | Model code |
| 25 | Version |
| 16 | Prog capacity |
| 80 | Op mode |
| 00 | Link unit |
| E1 | Error flag |
| 2D00 | Self diag error |
### %EE$EX00RT Normal Operation
|Reponse Byte|Description|
|------------|-----------|
| 00 | Extended mode
| 32 | Data item count
| 70 | Machine type
| 00 | Version (Fixed to 00)
| 16 | Prog capacity in K
| 80 | Operation mode / status
| 00 | Link unit
| E1 | Error flag
| 2D00 | Self diag error
| 50 | Version
| 02 | Hardware information
| 0 | Number of programs
| 4100 | Program size BCD
| 1600 | Header size (no. of words) bcd
| 1604 | System register size
| 96230000001480004 | ?
What are the last bytes?
FP-X 16k C14R
96 23 00 00 00 14 80 00 4
FP-X 32k C30T/P
96 23 00 00 00 14 80 00 4
FP-XH 32k C30T/P
96 23 00 00 00 40 00 00 4
FP-XH 16k C14R
96 23 00 00 00 40 00 00 4
## FP-XH 16k C14R Actual Response Examples
### %EE$RT
|Reponse Byte|Description|
|------------|-----------|
| 20 | Model code |
| 16 | Version |
| 16 | Prog capacity |
| 81 | Op mode |
| 00 | Link unit |
| 60 | Error flag |
| 0000 | Self diag error |
## FP-X0 2.5k L14,L30
### %EE$RT
|Reponse Byte|Description|
|------------|-----------|
| 72 | Model code |
| 12 | Version |
| 02 | Prog capacity |
| 82 | Op mode |
| 00 | Link unit |
| 00 | Error flag |
| 0000 | Self diag error |
### %EE$EX00RT
|Reponse Byte|Description|
|------------|-----------|
| 00 | Extended mode
| 32 | Data item count
| 72 | Machine type
| 00 | Version (Fixed to 00)
| 02 | Prog capacity in K
| 82 | Operation mode / status
| 00 | Link unit
| 00 | Error flag
| 0000 | Self diag error
| 23 | Version
| 01 | Hardware information
| 0 | Number of programs
| 4100 | Program size BCD
| 0301 | Header size (no. of words) bcd
| 2819 | System register size
| 0000001480004 | ?
## FP0 2.7k C10,C14
### %EE$RT
|Reponse Byte|Description|
|------------|-----------|
| 05 | Model code |
| 12 | Version |
| 03 | Prog capacity |
| 82 | Op mode |
| 00 | Link unit |
| 00 | Error flag |
| 0000 | Self diag error |
### %EE$EX00RT
|Reponse Byte|Description|
|------------|-----------|
| 00 | Extended mode
| 32 | Data item count
| 40 | Machine type
| 00 | Version (Fixed to 00)
| 03 | Prog capacity in K
| 82 | Operation mode / status
| 00 | Link unit
| 00 | Error flag
| 0000 | Self diag error
| 23 | Version
| 01 | Hardware information
| 0 | Number of programs
| 4100 | Program size BCD
| 0301 | Header size (no. of words) bcd
| 2819 | System register size
| 20130000080070004 | ?
## FP0 5k C32,SL1
### %EE$RT
|Reponse Byte|Description|
|------------|-----------|
| 06 | Model code |
| 12 | Version |
| 05 | Prog capacity |
| 82 | Op mode |
| 00 | Link unit |
| 00 | Error flag |
| 0000 | Self diag error |
### %EE$EX00RT
|Reponse Byte|Description|
|------------|-----------|
| 00 | Extended mode
| 32 | Data item count
| 41 | Machine type
| 00 | Version (Fixed to 00)
| 03 | Prog capacity in K
| 82 | Operation mode / status
| 00 | Link unit
| 00 | Error flag
| 0000 | Self diag error
| 23 | Version
| 01 | Hardware information
| 0 | Number of programs
| 4100 | Program size BCD
| 0501 | Header size (no. of words) bcd
| 2819 | System register size
| 20130000080070004 | ?
## FP0 10k
### %EE$RT
|Reponse Byte|Description|
|------------|-----------|
| 42 | Model code |
| 12 | Version |
| 10 | Prog capacity |
| 82 | Op mode |
| 00 | Link unit |
| 00 | Error flag |
| 0000 | Self diag error |
### %EE$EX00RT
|Reponse Byte|Description|
|------------|-----------|
| 00 | Extended mode
| 32 | Data item count
| 42 | Machine type
| 00 | Version (Fixed to 00)
| 10 | Prog capacity in K
| 82 | Operation mode / status
| 00 | Link unit
| 00 | Error flag
| 0000 | Self diag error
| 23 | Version
| 01 | Hardware information
| 0 | Number of programs
| 4100 | Program size BCD
| 1001 | Header size (no. of words) bcd
| 2819 | System register size
| 20130000080070004 | ?
## FP2SH 60k
### %EE$RT
|Reponse Byte|Description|
|------------|-----------|
| 60 | Model code |
| 12 | Version |
| 00 | Prog capacity |
| 82 | Op mode |
| 00 | Link unit |
| 00 | Error flag |
| 0000 | Self diag error |
### %EE$EX00RT
|Reponse Byte|Description|
|------------|-----------|
| 00 | Extended mode
| 32 | Data item count
| 60 | Machine type
| 00 | Version (Fixed to 00)
| 00 | Prog capacity in K
| 82 | Operation mode / status
| 00 | Link unit
| 00 | Error flag
| 0000 | Self diag error
| 23 | Version
| 01 | Hardware information
| 0 | Number of programs
| 4100 | Program size BCD
| 6001 | Header size (no. of words) bcd
| 2819 | System register size
| 20130000000080004 | ?
# Mewtocol-7 Com
## Getting the status of the plc
`%EE#RT`
Normal plc would return the default, see above.
The FP7 series returns:
`%EE$RT0000000100000000`
> This indicates that the device uses the Mewtocol 7 format
When trying to use EXRT the FP7 returns not supported
[R] `%EE#EX00RT00`
[S] `%EE!42`
Then request the status from the PLC using Mewtocol 7
[R] `>@EEE00#30STRD00`
[O] `>@EEE00$30STRD`
|Reponse Byte|Description|
|-------------|-----------|
| 00 | Read status (always 0) |
| 07 | series code: 7 = FP7 |
| 10 | model code: 3 = CPS41E, 4 = CPS31E, 5 = CPS31, 6 = CPS41ES, 7 = CPS31ES, 8 = CPS31S, 9 = CPS21, 10 = ElC500 |
| 0000 | user special order code (Not important) |
| 0453 | latest cpu version = 4.53 |
| 0453 | communication cpu version = 4.53 |
| 0453 | operation cpu version = 4.53 |
| 01 | op mode = 4.53 |
| 00 | error flag |
| 0000 | self diag error |
| 00 | sd card information |

View File

@@ -0,0 +1,16 @@
3 byte system registers, read with RR
|RR Adress|Interpreting type|Description|
|-|-|-|
|RR000|uint16|Program size steps capacity|
|RR005|uint16|Start address counter|
|RR006|uint16|Start address timer/counter|
|RR007|uint16|Start WR area (self reliant)|
|RR008 - RR009|uint32|Start DT area (self reliant)|
4 byte / 1 word system registers read with R
|WR Adress|Interpreting type|Description|
|-|-|-|
|R900|uint16|Self diag error|
|R902|uint16|Mode info|

View File

@@ -0,0 +1,152 @@
//This program builds Markdown and docs for all kinds of data
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Reflection.PortableExecutable;
using System.Text;
using MewtocolNet;
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
Console.WriteLine("Building docs for PLC types...");
var entryLoc = Assembly.GetEntryAssembly();
ArgumentNullException.ThrowIfNull(entryLoc);
string filePath = null!;
if (args.Length == 0) {
filePath = Path.Combine(entryLoc.Location, @"..\..\..\..\Docs\plctypes.md");
} else {
var userPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
filePath = args[0].Replace("~/", $"{userPath}/");
}
Console.WriteLine($"{filePath}");
StringBuilder markdownBuilder = new StringBuilder();
var plcNames = Enum.GetNames<PlcType>().Where(x => x != PlcType.Unknown.ToString()).OrderBy(x => x).ToArray();
void WritePlcTypeTable(string[] names) {
var groups = names.Select(x => x.ToNameDecompose())
.GroupBy(x => x.Group)
.SelectMany(g => g.OrderBy(x => x.Size))
.GroupBy(x => x.Group);
markdownBuilder.AppendLine("<table>");
bool isFirstIt = true;
foreach (var group in groups) {
group.OrderBy(x => x.TypeCode);
bool isFirstGroup = true;
foreach (var enu in group) {
string cpuOrMachCode = null!;
cpuOrMachCode = enu.TypeCode.ToString("X6");
ArgumentNullException.ThrowIfNull(enu);
//first iteration
if (isFirstIt) {
markdownBuilder.AppendLine("<tr>");
markdownBuilder.AppendLine($"<th>Type</th>");
markdownBuilder.AppendLine($"<th>Capacity</th>");
markdownBuilder.AppendLine($"<th>Code</th>");
markdownBuilder.AppendLine($"<th>Enum</th>");
markdownBuilder.AppendLine($"<th>DCNT</th>");
markdownBuilder.AppendLine($"<th>EXRT</th>");
markdownBuilder.AppendLine($"<th>Tested</th>");
markdownBuilder.AppendLine("</tr>");
isFirstIt = false;
}
if (isFirstGroup) {
markdownBuilder.AppendLine("<tr>");
markdownBuilder.AppendLine($"<td colspan=\"7\" height=50>📟 <b>{group.Key}</b> </td>");
markdownBuilder.AppendLine("</tr>");
isFirstGroup = false;
}
markdownBuilder.AppendLine("<tr>");
markdownBuilder.AppendLine($"<td> {(enu.SubTypes.Length == 0 ? "-" : string.Join(", ", enu.SubTypes))} </td>");
markdownBuilder.AppendLine($"<td> {enu.Size}k </td>");
markdownBuilder.AppendLine($"<td><code>0x{cpuOrMachCode}</code></td>");
if (enu.IsDiscontinuedModel) {
markdownBuilder.AppendLine($"<td><i>{enu.EncodedName}</i></td>");
markdownBuilder.AppendLine($"<td align=center>⚠️</td>");
} else {
markdownBuilder.AppendLine($"<td colspan=\"2\"><i>{enu.EncodedName}</i></td>");
}
markdownBuilder.AppendLine($"<td align=center> {(enu.UsesEXRT ? "" : "")} </td>");
markdownBuilder.AppendLine($"<td align=center> {(enu.WasTestedLive ? "" : "")} </td>");
markdownBuilder.AppendLine("</tr>");
}
isFirstIt = false;
}
markdownBuilder.AppendLine("</table>");
markdownBuilder.AppendLine("\n");
}
markdownBuilder.AppendLine($"# PLC Type Table");
markdownBuilder.AppendLine($"Auto Generated @ **{DateTime.Now.ToUniversalTime().ToString("u", CultureInfo.InvariantCulture)}**\n");
markdownBuilder.AppendLine(
$"All supported PLC types for auto recognition are listed in this table. " +
$"Other ones might also be supported but are shown as unknown in the library. " +
$"Some models are never uniquely identifiable by their typecode and need extra hints like Prog Capacity in EXRT or RT. \n\n" +
$"Typecode explained:\n" +
$"```\n" +
$"From left to right\n" +
$"0x\n" +
$"07 <= extended code (00 for non Mewtocol 7 devices)\n" +
$"0120 <= for 120k (Prog capacity), with RT/EXRT/MEW7 override order\n" +
$"A5 <= Is the actual typecode, with RT/EXRT/MEW7 override order\n" +
$"```"
);
markdownBuilder.AppendLine($"> <b>Discontinued PLCs</b><br>");
markdownBuilder.AppendLine($"> These are PLCs that are no longer sold by Panasonic. Marked with ⚠️\n");
markdownBuilder.AppendLine($"> <b>EXRT PLCs</b><br>");
markdownBuilder.AppendLine($"> These are PLCs that utilize the basic `%EE#RT` and `%EE#EX00RT` command. All newer models do this. Old models only use the `%EE#RT` command.\n");
WritePlcTypeTable(plcNames);
File.WriteAllText(filePath, markdownBuilder.ToString());

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPublishable>false</IsPublishable>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\MewtocolNet\MewtocolNet.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,82 @@
using MewtocolNet;
using MewtocolNet.Logging;
namespace Examples.BasicUsage;
internal class Program {
const string IP = "192.168.115.210";
const int PORT = 9094;
static void Main(string[] args) => Task.Run(AsyncMain).Wait();
static async Task AsyncMain () {
//the library provides a logging tool, comment this out if needed
Logger.LogLevel = LogLevel.Critical;
Logger.OnNewLogMessage((t, l, m) => { Console.WriteLine(m); });
//create a new interface to the plc using ethernet / tcp ip
//the using keyword is optional, if you want to use your PLC instance
//globally leave it
//you can also specify the source ip with:
//Mewtocol.Ethernet(IP, PORT).FromSource("192.168.113.2", 46040).Build()
using (var plc = Mewtocol.Ethernet(IP, PORT).Build()) {
//connect async to the plc
await plc.ConnectAsync();
//check if the connection was established
if (!plc.IsConnected) {
Console.WriteLine("Failed to connect to the plc...");
Environment.Exit(1);
}
//print basic plc info
Console.WriteLine(plc.PlcInfo);
//check if the plc is not in RUN mode, change to run
if(!plc.PlcInfo.IsRunMode) await plc.SetOperationModeAsync(true);
//get information about the plc
Console.WriteLine($"PLC type: {plc.PlcInfo.TypeName}");
Console.WriteLine($"Capacity: {plc.PlcInfo.ProgramCapacity}k");
Console.WriteLine($"Error: {plc.PlcInfo.SelfDiagnosticError}k");
//set the plc to prog mode
//await plc.SetOperationModeAsync(false);
//do disconnect use
plc.Disconnect();
//or
//await plc.DisconnectAsync();
//you can then change the connection settings for example to another PLC
plc.ConfigureConnection("192.168.115.212", 9094);
await plc.ConnectAsync();
plc.Disconnect();
plc.ConfigureConnection("192.168.115.214", 9094);
await plc.ConnectAsync();
plc.Disconnect();
plc.ConfigureConnection("192.168.178.55", 9094);
await plc.ConnectAsync();
}
//you can also find any applicable source endpoints by using:
foreach (var endpoint in Mewtocol.GetSourceEndpoints()) {
Console.WriteLine($"Usable endpoint: {endpoint}");
}
}
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPublishable>false</IsPublishable>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\MewtocolNet\MewtocolNet.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,148 @@
using MewtocolNet;
using MewtocolNet.Logging;
using MewtocolNet.Registers;
namespace Examples.BasicRegisterReadWrite;
internal class Program {
//const string PLC_IP = "192.168.178.55";
const string PLC_IP = "192.168.115.210";
static void Main(string[] args) => Task.Run(AsyncMain).Wait();
static async Task AsyncMain() {
Console.Clear();
//the library provides a logging tool, comment this out if needed
Logger.LogLevel = LogLevel.Critical;
//here we collect our built registers
IRegister<short> simpleNumberRegister = null!;
IRegister<ushort> simpleNumberRegister2 = null!;
IRegister<Word> simpleWordRegister = null!;
IArrayRegister<Word> overlapWordRegister = null!;
IStringRegister stringRegister = null!;
IArrayRegister<string> stringArrayRegister = null!;
IArrayRegister2D<short> simpleNumberRegister2Dim = null!;
//create a new interface to the plc using ethernet / tcp ip
using var plc = Mewtocol.Ethernet(PLC_IP)
.WithRegisters(b => {
//a simple INT at register address DT1000
b.Struct<short>("DT1000").Build(out simpleNumberRegister);
b.Struct<ushort>("DT1001").Build(out simpleNumberRegister2);
//you can also read the same array as an other data type
//not how they are at the same address, that means their values are linked
b.Struct<Word>("DT1000").Build(out simpleWordRegister);
//same link feature is also true for arrays that overlap their addresses
//this will go from DT999 to DT1001 because its a 3 Word array
b.Struct<Word>("DT999").AsArray(3).Build(out overlapWordRegister);
//strings area not stacially sized, they use a different constructor
b.String("DT1024", 32).Build(out stringRegister);
//string can also be arrayed
b.String("DT2030", 5).AsArray(3).Build(out stringArrayRegister);
//a simple 2 dimensional ARRAY [0..2, 0..2] OF INT at DT2003
b.Struct<short>("DT2003").AsArray(3, 3).Build(out simpleNumberRegister2Dim);
b.Struct<bool>("R19A").Build();
})
.Build();
//you can explain the internal register layout and which ones are linked by
Console.WriteLine(plc.Explain());
//connect async to the plc
await plc.ConnectAsync();
//check if the connection was established
if (!plc.IsConnected) {
Console.WriteLine("Failed to connect to the plc...");
Environment.Exit(1);
}
//restarts the program, this will make sure the global registers of the plc get reset each run
await plc.RestartProgramAsync();
//from this point on we can modify our registers
//read the value of the the register
short readValue = await simpleNumberRegister.ReadAsync();
ushort readValue2 = await simpleNumberRegister2.ReadAsync();
//show the value
Console.WriteLine($"Read value for {nameof(simpleNumberRegister)}: {readValue}");
Console.WriteLine($"Read value for {nameof(simpleNumberRegister2)}: {readValue2}");
//write the value
await simpleNumberRegister.WriteAsync(30);
//show the value
Console.WriteLine($"Value of {nameof(simpleNumberRegister)}: {simpleNumberRegister.Value}");
//because the two registers at DT1000 are linked by their memory address in the plc,
//both of their values got updated
Console.WriteLine($"Value of {nameof(simpleWordRegister)}: {simpleWordRegister.Value}");
//also the overlapping word array register value got updated, but only at the DT positions that were read
int i = 0;
overlapWordRegister.Value.ToList().ForEach(x => {
Console.WriteLine($"Value of {nameof(overlapWordRegister)}[{i}]: {x}");
i++;
});
//you can even read out a word bitwise
Console.WriteLine($"Bits of {nameof(simpleWordRegister)}: {simpleWordRegister.Value?.ToStringBits()}");
//or as a single bit
Console.WriteLine($"Bit at index 3 of {nameof(simpleWordRegister)}: {simpleWordRegister.Value?[3]}");
//reading / writing the string register
//await stringRegister.WriteAsync("Lorem ipsum dolor sit amet, cons");
await stringRegister.ReadAsync();
Console.WriteLine($"Value of {nameof(stringRegister)}: {stringRegister.Value}");
//reading writing a string array register
await stringArrayRegister.ReadAsync();
i = 0;
stringArrayRegister.Value.ToList().ForEach(x => {
Console.WriteLine($"Value of {nameof(stringArrayRegister)}[{i}]: {x}");
i++;
});
//you can either set the whole array at once,
//this will be slow if you only want to update one item
await stringArrayRegister.WriteAsync(new string[] {
"Test1",
"Test2",
"Test3",
});
//or update just one
//COMING LATER
//same thing also works for 2 dim arrays
await simpleNumberRegister2Dim.ReadAsync();
//the array is multi dimensional but can also be iterated per element
foreach (var item in simpleNumberRegister2Dim)
Console.WriteLine($"Element of {nameof(simpleNumberRegister2Dim)}: {item}");
//you can also use the array indexer accessors
Console.WriteLine($"Element [1, 2] of {nameof(simpleNumberRegister2Dim)}: {simpleNumberRegister2Dim[1, 2]}");
}
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPublishable>false</IsPublishable>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\MewtocolNet\MewtocolNet.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,90 @@
using MewtocolNet.Logging;
using MewtocolNet.Registers;
using MewtocolNet;
namespace Examples.Polling;
internal class Program {
const string PLC_IP = "192.168.178.55";
static void Main(string[] args) => Task.Run(AsyncMain).Wait();
static async Task AsyncMain() {
Console.Clear();
//the library provides a logging tool, comment this out if needed
Logger.LogLevel = LogLevel.Change;
//create a new interface to the plc using ethernet / tcp ip
using var plc = Mewtocol.Ethernet(PLC_IP)
.WithPoller() // <= use this in the builder pattern to automatically poll registers
.WithInterfaceSettings(s => {
//this means registers at the same address
//or that are contained by overlapping arrays
//always get assigned the same poll level
s.PollLevelOverwriteMode = PollLevelOverwriteMode.Highest;
//this means combine all registers that are not farther
//than 8 words apart from another into one tcp message,
//this safes message frames but a to large number can block read write traffic
s.MaxOptimizationDistance = 8;
})
.WithCustomPollLevels(l => {
//this makes registers at polllevel 2 only run all 3 iterations
l.SetLevel(2, 3);
//this makes registers at polllevel 3 only run all 5 seconds
l.SetLevel(3, TimeSpan.FromSeconds(5));
})
.WithRegisters(b => {
b.Struct<short>("DT1000").Build();
b.Struct<Word>("DT1000").Build();
b.Struct<ushort>("DT1001").Build();
b.Struct<Word>("DT999").AsArray(3).Build();
//we dont want to poll the string register as fast as we can
//so we assign it to level 2 to run only all 3 iterations
b.String("DT1024", 32).PollLevel(2).Build();
//we want to poll this array only at the first iteration,
//and after this only if we need the data
b.Struct<Word>("DT2000").AsArray(3).PollLevel(PollLevel.FirstIteration).Build();
//we want to poll this string array all 5 seconds
b.String("DT2030", 5).AsArray(3).PollLevel(3).Build();
//this is a fairly large array, so we never poll it automatically
//only if we need the data
b.Struct<short>("DT2003").AsArray(3, 3).PollLevel(PollLevel.Never).Build();
})
.Build();
//this explains the underlying data structure for the poller
Console.WriteLine(plc.Explain());
await plc.ConnectAsync(async () => {
//we want to restart the program before the poller starts
await plc.RestartProgramAsync();
});
if (!plc.IsConnected) {
Console.WriteLine("Failed to connect to the plc...");
Environment.Exit(1);
}
Console.ReadLine();
}
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPublishable>false</IsPublishable>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\MewtocolNet\MewtocolNet.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,31 @@
using MewtocolNet;
using MewtocolNet.Logging;
namespace Examples.ProgramReadWrite;
internal class Program {
static void Main(string[] args) => Task.Run(AsyncMain).Wait();
//MewtocolNet.ProgramParsing.PlcBinaryProgram.ParseFromFile(@"C:\Users\fwe\Documents\sps\FPXH_C30_Test1.fp").AnalyzeProgram();
static async Task AsyncMain () {
Logger.LogLevel = LogLevel.Error;
using (var plc = Mewtocol.Ethernet("192.168.178.55").Build()) {
await plc.ConnectAsync();
var prog = await plc.ReadProgramAsync();
if (prog != null) {
prog.AnalyzeProgram();
}
}
}
}

View File

@@ -0,0 +1,11 @@
<Application x:Class="Examples.WPF.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Examples.WPF"
xmlns:conv="clr-namespace:Examples.WPF.Converters"
StartupUri="MainWindow.xaml">
<Application.Resources>
<conv:NegationConverter x:Key="bInv"/>
<conv:ColorHashConverter x:Key="hashColor"/>
</Application.Resources>
</Application>

View File

@@ -0,0 +1,85 @@
using Examples.WPF.ViewModels;
using MewtocolNet;
using MewtocolNet.Logging;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Navigation;
namespace Examples.WPF;
public partial class App : Application {
public static AppViewModel ViewModel { get; private set; } = null!;
public static new App Current = null!;
public static new MainWindow MainWindow = null!;
public static ObservableCollection<TextBlock> LoggerItems = null!;
internal static event Action? LogEventProcessed;
protected override void OnStartup(StartupEventArgs e) {
ViewModel = new AppViewModel();
Current = this;
LoggerItems = new();
Logger.LogLevel = LogLevel.Verbose;
Logger.DefaultTargets = LoggerTargets.Trace;
Logger.OnNewLogMessage((d, l, m) => {
Application.Current.Dispatcher.BeginInvoke(() => {
Brush msgColor = null!;
switch (l) {
case LogLevel.Error:
msgColor = Brushes.Red;
break;
case LogLevel.Change:
msgColor = Brushes.Blue;
break;
case LogLevel.Verbose:
msgColor = Brushes.Gold;
break;
case LogLevel.Critical:
msgColor = Brushes.Gray;
break;
}
if (LoggerItems.Count > 1000) LoggerItems.RemoveAt(0);
var contRun = msgColor == null ? new Run(m) : new Run(m) {
Foreground = msgColor,
};
LoggerItems.Add(new TextBlock {
Inlines = {
new Run($"[{d:hh:mm:ss:ff}] ") {
Foreground = Brushes.LimeGreen,
},
contRun
}
});
LogEventProcessed?.Invoke();
}, System.Windows.Threading.DispatcherPriority.Background);
});
}
}

View File

@@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@@ -0,0 +1,62 @@
using System;
using System.Drawing;
using System.Globalization;
using System.Windows.Data;
namespace Examples.WPF.Converters;
[ValueConversion(typeof(bool), typeof(bool))]
public class ColorHashConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var hashCode = value.GetHashCode();
var randColor = GenerateRandomVibrantColor(new Random(hashCode));
System.Windows.Media.Brush outBrush = new System.Windows.Media.SolidColorBrush(new System.Windows.Media.Color {
R = randColor.R,
G = randColor.G,
B = randColor.B,
A = 255,
});
return outBrush;
}
private Color GenerateRandomVibrantColor(Random random) {
byte red = (byte)random.Next(256);
byte green = (byte)random.Next(256);
byte blue = (byte)random.Next(256);
Color color = Color.FromArgb(255, red, green, blue);
// Ensure the color is vibrant and colorful
while (!IsVibrantColor(color)) {
red = (byte)random.Next(256);
green = (byte)random.Next(256);
blue = (byte)random.Next(256);
color = Color.FromArgb(255,red, green, blue);
}
return color;
}
private bool IsVibrantColor(Color color) {
int minBrightness = 100;
int maxBrightness = 200;
int minSaturation = 150;
int brightness = (int)(color.GetBrightness() * 255);
int saturation = (int)(color.GetSaturation() * 255);
return brightness >= minBrightness && brightness <= maxBrightness && saturation >= minSaturation;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace Examples.WPF.Converters;
[ValueConversion(typeof(bool), typeof(bool))]
public class NegationConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => value != null ? !(bool)value : false;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => value != null ? !(bool)value : false;
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPublishable>false</IsPublishable>
<IsPackable>false</IsPackable>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\MewtocolNet\MewtocolNet.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,169 @@
<Window x:Class="Examples.WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Examples.WPF"
d:DataContext="{d:DesignInstance local:MainWindow, IsDesignTimeCreatable=True}"
mc:Ignorable="d"
MinWidth="500"
MinHeight="400"
Height="850"
Width="1200"
Title="MewtocolNet WPF Demo">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
<RowDefinition MinHeight="30" Height="150"/>
</Grid.RowDefinitions>
<ContentControl x:Name="mainContent"/>
<GridSplitter Grid.Row="1"
HorizontalAlignment="Stretch"
Background="Gray"
ShowsPreview="true"
Height="5">
<GridSplitter.Template>
<ControlTemplate>
<Separator/>
</ControlTemplate>
</GridSplitter.Template>
</GridSplitter>
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Border Background="LightGray"/>
<StackPanel HorizontalAlignment="Left"
Orientation="Horizontal">
<TextBlock Text="Logger"
Margin="5"/>
<Border Width="1"
Margin="5"
Background="Gray"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Rx"
Margin="5"/>
<Ellipse IsEnabled="{Binding AppViewModel.Plc.IsReceiving, Mode=OneWay}"
Fill="Lime"
Width="10"
Height="10">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value=".1"/>
</Trigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<TextBlock Text="Tx"
Margin="5"/>
<Ellipse Fill="Orange"
IsEnabled="{Binding AppViewModel.Plc.IsSending, Mode=OneWay}"
Width="10"
Height="10">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value=".1"/>
</Trigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<Border Width="1"
Margin="5"
Background="Gray"/>
<TextBlock Text="{Binding AppViewModel.Plc.BytesPerSecondDownstream, Mode=OneWay}"
VerticalAlignment="Center"/>
<Border Width="1"
Margin="5"
Background="Gray"/>
<TextBlock Text="{Binding AppViewModel.Plc.BytesPerSecondUpstream, Mode=OneWay}"
VerticalAlignment="Center"/>
<Border Width="1"
Margin="5"
Background="Gray"/>
<TextBlock Text="{Binding AppViewModel.Plc.QueuedMessages, StringFormat='{}Q: {0}', Mode=OneWay}"
VerticalAlignment="Center"/>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding AppViewModel.PlcIsNull}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Content="Expand"
Margin="2"/>
<ToggleButton Content="Autoscroll"
IsChecked="True"
Margin="2"
x:Name="autoScrollBtn"/>
</StackPanel>
<ListBox Grid.Row="1"
Background="Black"
Foreground="White"
BorderThickness="0"
VirtualizingPanel.IsVirtualizing="true"
VirtualizingPanel.VirtualizationMode="Recycling"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ItemsSource="{Binding LoggerItems, Mode=OneWay}"
x:Name="loggerList">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<Border Background="Black"
Grid.Row="2">
<TextBlock Text=">"
Foreground="White"
Margin="5,0,0,0"/>
</Border>
</Grid>
</Grid>
</Window>

View File

@@ -0,0 +1,57 @@
using Examples.WPF.ViewModels;
using Examples.WPF.Views;
using MewtocolNet;
using MewtocolNet.Logging;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Examples.WPF;
public partial class MainWindow : Window {
public ObservableCollection<TextBlock> LoggerItems => App.LoggerItems;
public AppViewModel AppViewModel => App.ViewModel;
public MainWindow() {
InitializeComponent();
this.DataContext = this;
App.MainWindow = this;
mainContent.Content = new ConnectView();
loggerList.PreviewMouseWheel += (s, e) => {
autoScrollBtn.IsChecked = false;
};
App.LogEventProcessed += () => {
Application.Current.Dispatcher.BeginInvoke(() => {
if (autoScrollBtn?.IsChecked != null && autoScrollBtn.IsChecked.Value)
loggerList.ScrollIntoView(App.LoggerItems.Last());
}, System.Windows.Threading.DispatcherPriority.Send);
};
}
}

View File

@@ -0,0 +1,34 @@
using MewtocolNet;
using MewtocolNet.RegisterAttributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Examples.WPF.RegisterCollections;
public class TestRegisterCollection : RegisterCollection {
[Register("R11A")]
public bool? TestR11A { get; set; }
[Register("R11A")]
public bool TestR11A_Duplicate_NonNullable { get; set; }
[Register("R16B")]
public bool TestR16B { get; set; }
[Register("R902")]
public bool Test { get; set; }
[BitRegister("DT1000", 0), PollLevel(3)]
public bool? TestDT100_Word_Duplicate_SingleBit { get; set; }
[Register("DT1000")]
public Word TestDT100_Word_Duplicate { get; set; }
[BitRegister("DDT1010", 1)]
public bool? TestDDT1010_DWord_Duplicate_SingleBit { get; set; }
}

View File

@@ -0,0 +1,40 @@
using Examples.WPF.RegisterCollections;
using MewtocolNet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Examples.WPF.ViewModels {
public class AppViewModel : ViewModelBase {
private IPlc? plc;
private TestRegisterCollection testRegCollection = null!;
public bool PlcIsNull => plc == null;
public bool PlcIsNotNull => plc != null;
public IPlc? Plc {
get => plc;
set {
plc = value;
OnPropChange();
OnPropChange(nameof(PlcIsNull));
OnPropChange(nameof(PlcIsNotNull));
}
}
public TestRegisterCollection TestRegCollection {
get => testRegCollection;
set {
testRegCollection = value;
OnPropChange();
}
}
}
}

View File

@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Configuration.Internal;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
using MewtocolNet;
using MewtocolNet.ComCassette;
namespace Examples.WPF.ViewModels;
internal class ConnectViewViewModel : ViewModelBase {
private bool hasComports = false;
private IEnumerable<int> baudRates = null!;
private IEnumerable<string> comPorts = null!;
private IEnumerable<CassetteInformation> foundCassettes = null!;
private string selectedIP = "192.168.115.210";
private string selectedPort = "9094";
private bool isConnecting;
public IEnumerable<int> BaudRates {
get => baudRates;
set {
baudRates = value;
OnPropChange();
}
}
public IEnumerable<string> ComPorts {
get => comPorts;
set {
comPorts = value;
OnPropChange();
}
}
public IEnumerable<CassetteInformation> FoundCassettes {
get => foundCassettes;
set {
foundCassettes = value;
OnPropChange();
}
}
public bool HasComports {
get => hasComports;
set {
hasComports = value;
OnPropChange();
}
}
public string SelectedIP {
get { return selectedIP; }
set {
selectedIP = value;
OnPropChange();
}
}
public string SelectedPort {
get { return selectedPort; }
set {
selectedPort = value;
OnPropChange();
}
}
public bool IsConnecting {
get { return isConnecting; }
set {
isConnecting = value;
OnPropChange();
}
}
private DispatcherTimer tm;
public ConnectViewViewModel() {
BaudRates = Mewtocol.GetUseableBaudRates();
ScanTimerTick(null, null!);
tm = new DispatcherTimer {
Interval = TimeSpan.FromSeconds(3),
};
tm.Tick += ScanTimerTick;
tm.Start();
}
private async void ScanTimerTick(object? sender, EventArgs e) {
ComPorts = Mewtocol.GetSerialPortNames();
HasComports = ComPorts != null && ComPorts.Count() > 0;
var found = await CassetteFinder.FindClientsAsync(timeoutMs: 1000);
if (FoundCassettes == null || !Enumerable.SequenceEqual(found, FoundCassettes))
FoundCassettes = found;
}
internal void SelectedCassette (CassetteInformation cassette) {
SelectedIP = cassette.IPAddress.ToString();
SelectedPort = cassette.Port.ToString();
}
internal void EndTimer() => tm.Stop();
}

View File

@@ -0,0 +1,36 @@
using Examples.WPF.RegisterCollections;
using MewtocolNet;
using MewtocolNet.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Examples.WPF.ViewModels;
public class PlcDataViewViewModel : ViewModelBase {
private ReconnectArgs plcCurrentReconnectArgs = null!;
public IPlc Plc => App.ViewModel.Plc!;
public TestRegisterCollection RegCollection => App.ViewModel.TestRegCollection;
public ReconnectArgs PlcCurrentReconnectArgs {
get => plcCurrentReconnectArgs;
set {
plcCurrentReconnectArgs = value;
OnPropChange();
}
}
public PlcDataViewViewModel () {
Plc.ReconnectTryStarted += (s, e) => PlcCurrentReconnectArgs = e;
Plc.Reconnected += (s, e) => PlcCurrentReconnectArgs = null!;
Plc.Disconnected += (s, e) => PlcCurrentReconnectArgs = null!;
}
}

View File

@@ -0,0 +1,18 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Examples.WPF.ViewModels;
public abstract class ViewModelBase : INotifyPropertyChanged {
public event PropertyChangedEventHandler? PropertyChanged;
public void PropChange(string _name) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(_name));
}
protected void OnPropChange([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

View File

@@ -0,0 +1,129 @@
<UserControl x:Class="Examples.WPF.Views.ConnectView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Examples.WPF.Views"
xmlns:vm="clr-namespace:Examples.WPF.ViewModels"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance vm:ConnectViewViewModel, IsDesignTimeCreatable=True}"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Margin="10">
<StackPanel>
<StackPanel.Resources>
<Style TargetType="TextBox">
<Setter Property="Padding" Value="5"/>
</Style>
</StackPanel.Resources>
<StackPanel>
<TextBlock Text="Connect to a PLC"
FontSize="24"/>
<Label Content="Set your connection type"/>
<ComboBox SelectedIndex="0" x:Name="conTypeCombo">
<ComboBoxItem>Ethernet</ComboBoxItem>
<ComboBoxItem IsEnabled="{Binding HasComports}">Serial</ComboBoxItem>
</ComboBox>
</StackPanel>
<Separator/>
<StackPanel MinWidth="200">
<TextBlock Text="Cassettes"/>
<DataGrid ItemsSource="{Binding FoundCassettes}"
SelectionChanged="SelectedCassette"
AutoGenerateColumns="False"
MinHeight="150"
MaxHeight="200"
IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" Width=".5*"/>
<DataGridTextColumn Header="IP" Binding="{Binding IPAddress}" Width=".3*"/>
<DataGridTextColumn Header="Port" Binding="{Binding Port}" Width=".3*"/>
<DataGridCheckBoxColumn Header="DHCP" Binding="{Binding UsesDHCP}" Width="auto"/>
<DataGridTextColumn Header="MAC" Binding="{Binding MacAddressStr, Mode=OneWay}" Width="auto"/>
<DataGridTextColumn Header="Status" Binding="{Binding Status}" Width="auto"/>
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding Port}" Value="0">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
<TextBlock Text="Connection"/>
<StackPanel Orientation="Horizontal">
<Label Content="IP Address"
VerticalAlignment="Center"/>
<TextBox Text="{Binding SelectedIP}"
VerticalAlignment="Center"/>
<Label Content="Port"
VerticalAlignment="Center"/>
<TextBox Text="{Binding SelectedPort}"
VerticalAlignment="Center"/>
<Button Content="Connect"
Click="ClickedConnectEth"
VerticalAlignment="Center"
Padding="5"
Margin="10,0,0,0">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnecting}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=conTypeCombo}" Value="0">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel>
<StackPanel MinWidth="200"
HorizontalAlignment="Left">
<StackPanel Orientation="Vertical">
<Label Content="COM Port"/>
<ComboBox ItemsSource="{Binding ComPorts}"
SelectedIndex="0"/>
<Label Content="BaudRate"/>
<ComboBox ItemsSource="{Binding BaudRates}"
SelectedIndex="0"/>
</StackPanel>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=conTypeCombo}" Value="1">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,161 @@
using Examples.WPF.RegisterCollections;
using Examples.WPF.ViewModels;
using MewtocolNet;
using MewtocolNet.ComCassette;
using MewtocolNet.Logging;
using MewtocolNet.Registers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Examples.WPF.Views;
/// <summary>
/// Interaktionslogik für ConnectView.xaml
/// </summary>
public partial class ConnectView : UserControl {
private ConnectViewViewModel viewModel;
public ConnectView() {
InitializeComponent();
viewModel = new ConnectViewViewModel();
this.DataContext = viewModel;
Unloaded += (s, e) => viewModel.EndTimer();
}
private void SelectedCassette(object sender, SelectionChangedEventArgs e) {
var cassette = (CassetteInformation)((DataGrid)sender).SelectedItem;
if (cassette == null) return;
viewModel.SelectedCassette(cassette);
}
private void ClickedConnectEth(object sender, RoutedEventArgs e) {
Application.Current.Dispatcher.BeginInvoke(async () => {
viewModel.IsConnecting = true;
var parsedInt = int.Parse(viewModel.SelectedPort);
IRegister<short> heartbeatSetter = null!;
IRegister<bool> outputContactReference = null!;
IRegister<bool> testBoolReference = null!;
IRegister<Word> wordRefTest = null!;
//build a new interface
App.ViewModel.Plc = Mewtocol.Ethernet(viewModel.SelectedIP, parsedInt)
.WithPoller()
.WithInterfaceSettings(setting => {
setting.TryReconnectAttempts = 10;
setting.TryReconnectDelayMs = 2000;
setting.SendReceiveTimeoutMs = 1000;
setting.HeartbeatIntervalMs = 3000;
setting.MaxDataBlocksPerWrite = 128;
setting.MaxOptimizationDistance = 20;
})
.WithCustomPollLevels(lvl => {
lvl.SetLevel(2, 3);
lvl.SetLevel(3, TimeSpan.FromSeconds(5));
lvl.SetLevel(4, TimeSpan.FromSeconds(10));
})
.WithRegisterCollections(collector => {
App.ViewModel.TestRegCollection = collector.AddCollection<TestRegisterCollection>();
})
.WithRegisters(b => {
b.Bool("X4").Build();
b.Bool("Y4").Build(out outputContactReference);
b.Bool("R10A").PollLevel(PollLevel.FirstIteration).Build(out testBoolReference);
b.Struct<short>("DT1000").Build(out heartbeatSetter);
//these will be merged into one
b.Struct<Word>("DT1000").Build(out wordRefTest);
b.Struct<Word>("DT1000").Build(out wordRefTest);
b.Struct<ushort>("DT1001").PollLevel(2).Build();
b.Struct<Word>("DT1002").PollLevel(2).Build();
b.Struct<int>("DDT1010").PollLevel(2).Build();
b.Struct<uint>("DDT1012").PollLevel(2).Build();
b.Struct<DWord>("DDT1014").PollLevel(2).Build();
b.Struct<float>("DDT1016").PollLevel(2).Build();
b.Struct<TimeSpan>("DDT1018").PollLevel(2).Build();
b.Struct<DateAndTime>("DDT1020").PollLevel(2).Build();
b.Struct<DateAndTime>("DDT1022").PollLevel(2).Build();
b.String("DT1028", 32).PollLevel(3).Build();
b.String("DT1046", 5).PollLevel(4).Build();
b.Struct<Word>("DT1000").AsArray(5).PollLevel(1).Build();
})
.WithHeartbeatTask(async (plc) => {
var randShort = (short)new Random().Next(short.MinValue, short.MaxValue);
//write direct
//await heartbeatSetter.WriteAsync(randShort);
//or by anonymous
await plc.Register.Struct<short>("DT1000").WriteAsync(randShort);
//write a register without a reference
bool randBool = new Random().Next(0, 2) == 1;
await plc.Register.Bool("Y4").WriteAsync(randBool);
if (testBoolReference.Value != null)
await testBoolReference.WriteAsync(!testBoolReference.Value.Value);
await plc.Register.Struct<DateAndTime>("DDT1022").WriteAsync(DateAndTime.FromDateTime(DateTime.UtcNow));
})
.Build();
//connect to it
await App.ViewModel.Plc.ConnectAsync(async () => {
await App.ViewModel.Plc.RestartProgramAsync();
});
await App.ViewModel.Plc.AwaitFirstDataCycleAsync();
if (App.ViewModel.Plc.IsConnected) {
App.MainWindow.mainContent.Content = new PlcDataView();
}
viewModel.IsConnecting = false;
}, System.Windows.Threading.DispatcherPriority.Send);
}
}

View File

@@ -0,0 +1,350 @@
<UserControl x:Class="Examples.WPF.Views.PlcDataView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:Examples.WPF.ViewModels"
xmlns:local="clr-namespace:Examples.WPF.Views"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance vm:PlcDataViewViewModel, IsDesignTimeCreatable=True}"
d:DesignHeight="450"
d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Menu>
<MenuItem Header="PLC">
<MenuItem Header="Disconnect" IsEnabled="{Binding Plc.IsConnected}"
Click="ClickedDisconnect"/>
<MenuItem Header="Connect" IsEnabled="{Binding Plc.IsConnected, Converter={StaticResource bInv}}"
Click="ClickedConnect"/>
<MenuItem Header="Set Random DT1000" IsEnabled="{Binding Plc.IsConnected}"
Click="ClickedSetRandom"/>
<MenuItem Header="Queue test" IsEnabled="{Binding Plc.IsConnected}"
Click="ClickedAddQueueTest"/>
<MenuItem Header="Toggle OP mode" IsEnabled="{Binding Plc.IsConnected}"
Click="ClickedToggleRunMode"/>
</MenuItem>
</Menu>
<StackPanel Margin="10"
Grid.Row="1">
<TextBlock IsEnabled="{Binding Plc.IsConnected}">
<Run Text="{Binding Plc.PlcInfo.TypeName, Mode=OneWay}"
FontSize="24"
BaselineAlignment="Center"
FontWeight="SemiBold"/>
<Run Text="{Binding Plc.PlcInfo.CpuVersion, StringFormat='v{0}', Mode=OneWay}"
FontSize="24"
FontWeight="Light"/>
<Ellipse Width="10"
Height="10"
Fill="Lime"
IsEnabled="{Binding Plc.IsConnected}"/>
<Run>
<Run.Style>
<Style TargetType="Run">
<Style.Triggers>
<DataTrigger Binding="{Binding Plc.IsRunMode, Mode=OneWay}" Value="True">
<Setter Property="Text" Value="RUN MODE"/>
</DataTrigger>
<DataTrigger Binding="{Binding Plc.IsRunMode, Mode=OneWay}" Value="False">
<Setter Property="Text" Value="NO RUN MODE"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Run.Style>
</Run>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock IsEnabled="{Binding Plc.IsConnected, Converter={StaticResource bInv}}">
<Run Text="Disconnected"
FontSize="24"
BaselineAlignment="Center"
FontWeight="SemiBold"/>
<Ellipse Width="10"
Height="10"
Fill="Red"
IsEnabled="{Binding Plc.IsConnected}"/>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="{Binding Plc.PlcInfo.TypeCode, StringFormat='#{0:X}', Mode=OneWay}"
FontSize="18"/>
<ItemsControl ItemsSource="{Binding Plc.PlcInfo.OperationModeTags, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="LightGray"
CornerRadius="5"
Margin="2"
Padding="5">
<TextBlock Text="{Binding}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding Plc.PlcInfo.HardwareInformationTags, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="LightGray"
CornerRadius="5"
Margin="2"
Padding="5">
<TextBlock Text="{Binding}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock>
<Run Text="Reconnecting..."/>
<Run Text="{Binding PlcCurrentReconnectArgs.ReconnectTry, Mode=OneWay, StringFormat='{}{0}/'}"/>
<Run Text="{Binding PlcCurrentReconnectArgs.MaxAttempts, Mode=OneWay}"/> in
<Run Text="{Binding PlcCurrentReconnectArgs.RetryCountDownRemaining, Mode=OneWay}"/>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding PlcCurrentReconnectArgs, Mode=OneWay}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1.5*"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<GridSplitter Grid.Column="1"
Grid.Row="1"
Grid.RowSpan="3"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Background="Gray"
ShowsPreview="true"
Width="10">
<GridSplitter.Template>
<ControlTemplate>
<Rectangle Fill="Gray"
Width="2"
Margin="2"/>
</ControlTemplate>
</GridSplitter.Template>
</GridSplitter>
<TextBlock Text="Underlying Registers"
FontSize="18"
Margin="10"/>
<DataGrid Grid.Row="1"
Grid.RowSpan="3"
AutoGenerateColumns="False"
IsReadOnly="True"
ItemsSource="{Binding Plc.Registers, Mode=OneWay}">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding IsAutoGenerated}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="FP Address" Binding="{Binding PLCAddressName}"/>
<DataGridTextColumn Header="Type" Binding="{Binding UnderlyingSystemType.Name}"/>
<DataGridTemplateColumn Header="Value" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border HorizontalAlignment="Left"
Padding="2,0">
<TextBlock Text="{Binding ValueStr}"/>
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding ValueStr}" Value="True">
<Setter Property="Background" Value="Blue"/>
<Setter Property="TextElement.Foreground" Value="White"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Poll Level" Binding="{Binding PollLevel, Mode=OneWay}"/>
<DataGridTextColumn Header="Update Frequency" Binding="{Binding UpdateFreqHz, StringFormat='{}{0} Hz',Mode=OneWay}"/>
<DataGridTemplateColumn Width="15">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border Background="{Binding MemoryArea, Mode=OneWay, Converter={StaticResource hashColor}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<TextBlock Text="Memory Areas"
Grid.Column="2"
FontSize="18"
Margin="10"/>
<Border Grid.Column="2"
Grid.Row="1"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
BorderBrush="LightBlue"
BorderThickness="1.5">
<DataGrid IsReadOnly="True"
AutoGenerateColumns="False"
ItemsSource="{Binding Plc.MemoryAreas, Mode=OneWay}">
<DataGrid.Columns>
<DataGridTextColumn Header="Address Range" Binding="{Binding AddressRange, Mode=OneWay}"/>
<DataGridTemplateColumn Width="15">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border Background="{Binding Converter={StaticResource hashColor}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Words" Binding="{Binding UnderlyingWordsString, Mode=OneWay}"/>
</DataGrid.Columns>
</DataGrid>
</Border>
<TextBlock Text="Property Bindings"
Grid.Column="2"
Grid.Row="2"
FontSize="18"
Margin="10"/>
<Border Grid.Column="2"
Grid.Row="3"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
BorderBrush="LightBlue"
BorderThickness="1.5">
<ScrollViewer>
<StackPanel>
<TextBlock Text="Boolean internal relays"/>
<TextBlock>
<Run Text="R11A Nullable property: "/>
<Run Text="{Binding RegCollection.TestR11A}"
FontWeight="Bold"/>
</TextBlock>
<TextBlock>
<Run Text="R11A Duplicate non nullable property: "/>
<Run Text="{Binding RegCollection.TestR11A_Duplicate_NonNullable}"
FontWeight="Bold"/>
</TextBlock>
<TextBlock>
<Run Text="R16B"/>
<Run Text="{Binding RegCollection.TestR16B}"
FontWeight="Bold"/>
</TextBlock>
<TextBlock>
<Run Text="DT1000 Word duplicate: "/>
<Run Text="{Binding RegCollection.TestDT100_Word_Duplicate, Mode=OneWay}"
FontWeight="Bold"/>
</TextBlock>
<TextBlock>
<Run Text="DT1000 Word direct bit read (Idx 0): "/>
<Run Text="{Binding RegCollection.TestDT100_Word_Duplicate_SingleBit, Mode=OneWay}"
FontWeight="Bold"/>
</TextBlock>
<TextBlock>
<Run Text="DDT1010 DWord direct bit read (Idx 1): "/>
<Run Text="{Binding RegCollection.TestDDT1010_DWord_Duplicate_SingleBit, Mode=OneWay}"
FontWeight="Bold"/>
</TextBlock>
</StackPanel>
</ScrollViewer>
</Border>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,81 @@
using Examples.WPF.ViewModels;
using MewtocolNet.Registers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Examples.WPF.Views;
public partial class PlcDataView : UserControl {
private PlcDataViewViewModel viewModel;
public PlcDataView() {
InitializeComponent();
viewModel = new PlcDataViewViewModel();
this.DataContext = viewModel;
}
private void ClickedDisconnect(object sender, RoutedEventArgs e) {
viewModel.Plc.Disconnect();
}
private async void ClickedConnect(object sender, RoutedEventArgs e) {
await viewModel.Plc.ConnectAsync();
}
private async void ClickedSetRandom(object sender, RoutedEventArgs e) {
var reg = (IRegister<ushort>?)viewModel.Plc.GetAllRegisters()?.FirstOrDefault(x => x.PLCAddressName == "DT1001");
if(reg != null) {
await reg.WriteAsync((ushort)new Random().Next(ushort.MinValue, ushort.MaxValue));
}
}
private async void ClickedAddQueueTest(object sender, RoutedEventArgs e) {
var tasks = new List<Task<short>>();
for (int i = 0; i < 100; i++) {
var t = viewModel.Plc.Register.Struct<short>("DT1000").ReadAsync();
tasks.Add(t);
}
var list = await Task.WhenAll(tasks);
Console.WriteLine();
}
private async void ClickedToggleRunMode(object sender, RoutedEventArgs e) {
await viewModel.Plc.ToggleOperationModeAsync();
}
}

View File

@@ -1,12 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\MewtocolNet\MewtocolNet.csproj" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@@ -1,67 +0,0 @@
using System;
using System.Threading.Tasks;
using MewtocolNet;
using MewtocolNet.Logging;
namespace Examples {
class Program {
static void Main(string[] args) {
Task.Factory.StartNew(async () => {
//attaching the logger
Logger.LogLevel = LogLevel.Verbose;
Logger.OnNewLogMessage((date, msg) => {
Console.WriteLine($"{date.ToString("HH:mm:ss")} {msg}");
});
//setting up a new PLC interface and register collection
MewtocolInterface interf = new MewtocolInterface("10.237.191.3");
TestRegisters registers = new TestRegisters();
//attaching the register collection and an automatic poller
interf.WithRegisterCollection(registers).WithPoller();
await interf.ConnectAsync(
(plcinf) => {
//reading a value from the register collection
Console.WriteLine($"BitValue is: {registers.BitValue}");
//writing a value to the registers
Task.Factory.StartNew(async () => {
//set plc to run mode if not already
await interf.SetOperationMode(OPMode.Run);
await Task.Delay(2000);
//inverts the boolean register
await interf.SetRegisterAsync(nameof(registers.TestBool1), !registers.TestBool1);
Console.WriteLine("Testregister was toggled");
//adds 10 each time the plc connects to the PLCs INT regíster
interf.SetRegister(nameof(registers.TestInt16), (short)(registers.TestInt16 + 10));
//adds 1 each time the plc connects to the PLCs DINT regíster
interf.SetRegister(nameof(registers.TestInt32), (registers.TestInt32 + 1));
//adds 11.11 each time the plc connects to the PLCs REAL regíster
interf.SetRegister(nameof(registers.TestFloat32), (float)(registers.TestFloat32 + 11.11));
//writes 'Hello' to the PLCs string register
interf.SetRegister(nameof(registers.TestString2), "Hello");
//set the current second to the PLCs TIME register
interf.SetRegister(nameof(registers.TestTime), TimeSpan.FromSeconds(DateTime.Now.Second));
});
}
);
});
Console.ReadLine();
}
}
}

View File

@@ -1,51 +0,0 @@
using MewtocolNet;
using MewtocolNet.RegisterAttributes;
using System;
using System.Collections;
namespace Examples {
public class TestRegisters : RegisterCollectionBase {
//corresponds to a R100 boolean register in the PLC
[Register(100, RegisterType.R)]
public bool TestBool1 { get; private set; }
//corresponds to a XD input of the PLC
[Register(RegisterType.X, SpecialAddress.D)]
public bool TestBoolInputXD { get; private set; }
//corresponds to a DT1101 - DT1104 string register in the PLC with (STRING[4])
[Register(1101, 4)]
public string TestString1 { get; private set; }
//corresponds to a DT7000 16 bit int register in the PLC
[Register(7000)]
public short TestInt16 { get; private set; }
//corresponds to a DTD7001 - DTD7002 32 bit int register in the PLC
[Register(7001)]
public int TestInt32 { get; private set; }
//corresponds to a DTD7001 - DTD7002 32 bit float register in the PLC (REAL)
[Register(7003)]
public float TestFloat32 { get; private set; }
//corresponds to a DT7005 - DT7009 string register in the PLC with (STRING[5])
[Register(7005, 5)]
public string TestString2 { get; private set; }
//corresponds to a DT7010 as a 16bit word/int and parses the word as single bits
[Register(7010)]
public BitArray TestBitRegister { get; private set; }
//corresponds to a DT1204 as a 16bit word/int takes the bit at index 9 and writes it back as a boolean
[Register(1204, 9, BitCount.B16)]
public bool BitValue { get; private set; }
//corresponds to a DT7012 - DT7013 as a 32bit time value that gets parsed as a timespan (TIME)
//the smallest value to communicate to the PLC is 10ms
[Register(7012)]
public TimeSpan TestTime { get; private set; }
}
}

674
LICENSE Normal file
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>.

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 WOLF Medizintechnik GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using CommandLine;
using MewtocolNet;
using MewtocolNet.ComCassette;
using Spectre.Console;
namespace MewTerminal.Commands;
[Verb("clear", HelpText = "Clears console", Hidden = true)]
internal class ClearCommand : CommandLineExcecuteable {
public override void Run() {
Console.Clear();
}
}

View File

@@ -0,0 +1,20 @@
using CommandLine.Text;
using CommandLine;
using MewtocolNet.Logging;
namespace MewTerminal.Commands;
public abstract class CommandLineExcecuteable {
static UnParserSettings UnparserSet = new UnParserSettings {
PreferShortName = true,
};
[Option('v', "verbosity", HelpText = "Sets the Loglevel verbosity", Default = LogLevel.None)]
public LogLevel LogLevel { get; set; } = LogLevel.None;
public virtual void Run() { }
public virtual Task RunAsync () => Task.CompletedTask;
}

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using CommandLine;
using MewtocolNet;
using MewtocolNet.ComCassette;
using MewtocolNet.Logging;
using Spectre.Console;
namespace MewTerminal.Commands;
[Verb("support-list", HelpText = "Lists all supported PLC types")]
internal class ListSupportCommand : CommandLineExcecuteable {
public override void Run () {
var plcs = Enum.GetValues<PlcType>().Cast<PlcType>();
var lst = new List<ParsedPlcName>();
foreach (var plcT in plcs) {
var decomp = plcT.ToNameDecompose();
if (decomp == null) continue;
lst.Add(decomp);
}
AnsiConsole.Write(lst.ToTable());
}
}

View File

@@ -0,0 +1,46 @@
using CommandLine;
using MewtocolNet;
using Spectre.Console;
namespace MewTerminal.Commands.OnlineCommands;
internal class OnlineCommand : CommandLineExcecuteable {
[Option('e', "ethernet", Default = "127.0.0.1:9094", HelpText = "Ethernet config")]
public string? EthernetStr { get; set; }
[Option('s', "serial", Default = null, HelpText = "Serial port config")]
public string? SerialStr { get; set; }
public override async Task RunAsync() {
try {
if (!string.IsNullOrEmpty(SerialStr)) {
} else {
var split = EthernetStr.Split(":");
string ip = split[0];
int port = int.Parse(split[1]);
using (var plc = Mewtocol.Ethernet(ip, port).Build()) {
await AfterSetup(plc);
}
}
} catch (Exception ex) {
AnsiConsole.WriteLine($"[red]{ex.Message.ToString()}[/]");
}
}
internal virtual async Task AfterSetup(IPlc plc) => throw new NotImplementedException();
}

View File

@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using CommandLine;
using MewtocolNet;
using MewtocolNet.ComCassette;
using MewtocolNet.Logging;
using Spectre.Console;
namespace MewTerminal.Commands;
[Verb("scan", HelpText = "Scans all network PLCs")]
internal class ScanCommand : CommandLineExcecuteable {
[Option("ip", HelpText = "IP of the source adapter" )]
public string? IPSource { get; set; }
[Option("timeout", Default = 100)]
public int? TimeoutMS { get; set; }
[Option("plc", Required = false, HelpText = "Gets the PLC types")]
public bool GetPLCTypes { get; set; }
private class PLCCassetteTypeInfo {
public CassetteInformation Cassette { get; set; }
public PLCInfo PLCInf { get; set; }
}
public override async Task RunAsync () {
await AnsiConsole.Status()
.Spinner(Spinner.Known.Dots)
.StartAsync("Scanning...", async ctx => {
var query = await CassetteFinder.FindClientsAsync(IPSource, TimeoutMS ?? 100);
var found = query.Select(x => new PLCCassetteTypeInfo { Cassette = x }).ToList();
if (found.Count > 0 && GetPLCTypes) {
foreach (var item in found) {
ctx.Status($"Getting cassette PLC {item.Cassette.IPAddress}:{item.Cassette.Port}")
.Spinner(Spinner.Known.Dots);
var dev = Mewtocol.Ethernet(item.Cassette.IPAddress, item.Cassette.Port).Build();
dev.ConnectTimeout = 1000;
await dev.ConnectAsync();
item.PLCInf = dev.PlcInfo;
dev.Disconnect();
}
}
if (found.Count() > 0) {
AnsiConsole.MarkupLineInterpolated($"✅ Found {found.Count()} devices...");
} else {
AnsiConsole.MarkupLineInterpolated($"❌ Found no devices");
return;
}
if (found.Any(x => x.PLCInf != PLCInfo.None)) {
AnsiConsole.Write(found.Select(x => new {
x.Cassette.Name,
PLC = x.PLCInf.TypeCode.ToName(),
IsRun = x.PLCInf.OperationMode.HasFlag(OPMode.RunMode),
IP = x.Cassette.IPAddress,
x.Cassette.Port,
DHCP = x.Cassette.UsesDHCP,
MAC = x.Cassette.MacAddress,
Ver = x.Cassette.FirmwareVersion,
x.Cassette.Status,
}).ToTable());
} else {
AnsiConsole.Write(found.Select(x => new {
x.Cassette.Name,
IP = x.Cassette.IPAddress,
x.Cassette.Port,
DHCP = x.Cassette.UsesDHCP,
MAC = x.Cassette.MacAddress,
Ver = x.Cassette.FirmwareVersion,
x.Cassette.Status,
}).ToTable());
}
});
}
}

View File

@@ -0,0 +1,65 @@
using Spectre.Console;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace MewTerminal;
internal static class Helpers {
internal static Table ToTable<T> (this IEnumerable<T> data, params string[] markups) {
// Create a table
var table = new Table();
var type = typeof(T);
var props = type.GetProperties();
bool isFirst = true;
foreach (var item in data) {
var rowVals = new List<string>();
foreach (var prop in props) {
if(isFirst) table.AddColumn(prop.Name.SplitCamelCase());
var propVal = prop.GetValue(item);
string strVal = propVal?.ToString() ?? "null";
if (propVal is byte[] bArr) {
strVal = string.Join(" ", bArr.Select(x => x.ToString("X2")));
}
if (propVal is string[] sArr) {
strVal = string.Join(", ", sArr);
}
strVal = strVal.Replace("[", "");
strVal = strVal.Replace("]", "");
rowVals.Add(strVal);
}
isFirst = false;
table.AddRow(rowVals.ToArray());
}
return table;
}
private static string SplitCamelCase (this string str) {
return Regex.Replace(Regex.Replace(str, @"(\P{Ll})(\P{Ll}\p{Ll})", "$1 $2"), @"(\p{Ll})(\P{Ll})", "$1 $2");
}
}

View File

@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<ApplicationManifest>app.manifest</ApplicationManifest>
<NoWarn>IL2026,IL2027,IL2090</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Spectre.Console" Version="0.47.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MewtocolNet\MewtocolNet.csproj" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<AssemblyName>mewcmd</AssemblyName>
<PlatformTarget>x64</PlatformTarget>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<TrimmerRemoveSymbols>true</TrimmerRemoveSymbols>
<TrimMode>partial</TrimMode>
<SelfContained>True</SelfContained>
<DebugType>None</DebugType>
<DebugSymbols>False</DebugSymbols>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<AllowedReferenceRelatedFileExtensions>none</AllowedReferenceRelatedFileExtensions>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<OutputPath>..\Builds\MewTerminal</OutputPath>
</PropertyGroup>
</Project>

93
MewTerminal/Program.cs Normal file
View File

@@ -0,0 +1,93 @@
using CommandLine;
using CommandLine.Text;
using MewTerminal.Commands;
using MewTerminal.Commands.OnlineCommands;
using MewtocolNet.Logging;
using Spectre.Console;
using System.Globalization;
using System.Reflection;
namespace MewTerminal;
internal class Program {
static void Main(string[] args) {
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
Console.OutputEncoding = System.Text.Encoding.UTF8;
Logger.OnNewLogMessage((dt, lv, msg) => {
AnsiConsole.WriteLine($"{msg}");
});
#if DEBUG
Console.Clear();
var firstArg = new string[] { "help" };
start:
if(firstArg == null) {
Console.WriteLine("Enter arguments [DEBUG MODE]");
args = Console.ReadLine().SplitArgs();
}
//print help first time
InitParser(firstArg ?? args);
firstArg = null;
goto start;
#else
InitParser(args);
#endif
}
private static Type[] LoadVerbs() {
var lst = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(t => t.GetCustomAttribute<VerbAttribute>() != null)
.ToArray();
return lst;
}
static void InitParser (string[] args) {
var types = LoadVerbs();
var parseRes = Parser.Default.ParseArguments(args, types);
var helpText = HelpText.AutoBuild(parseRes, h => {
h.AddEnumValuesToHelpText = true;
return h;
}, e => e);
parseRes.WithNotParsed(err => {
});
if(parseRes?.Value != null && parseRes.Value is CommandLineExcecuteable exc) {
Logger.LogLevel = exc.LogLevel;
exc.Run();
var task = Task.Run(exc.RunAsync);
task.Wait();
}
}
}

18
MewTerminal/app.manifest Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="mewterminal.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
</application>
</compatibility>
</assembly>

View File

@@ -1,11 +1,31 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 17
VisualStudioVersion = 16.0.30114.105 VisualStudioVersion = 17.5.33103.201
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MewtocolNet", "MewtocolNet\MewtocolNet.csproj", "{8B7863E7-5E82-4990-9138-2C0C24629982}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MewtocolNet", "MewtocolNet\MewtocolNet.csproj", "{8B7863E7-5E82-4990-9138-2C0C24629982}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Examples", "Examples\Examples.csproj", "{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MewtocolTests", "MewtocolTests\MewtocolTests.csproj", "{C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MewTerminal", "MewTerminal\MewTerminal.csproj", "{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AutoTools", "AutoTools", "{BAEF983A-EFF2-48DF-A74E-57084166BB4D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoTools.ChmDataExtract", "AutoTools.ChmDataExtract\AutoTools.ChmDataExtract.csproj", "{5A9DE453-AD64-4F8D-8215-3BB26674D164}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoTools.DocBuilder", "AutoTools.DocBuilder\AutoTools.DocBuilder.csproj", "{00ACA0AB-3988-4EF7-98A6-B39A36B136DA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.BasicEthernet", "Examples\Examples.BasicEthernet\Examples.BasicEthernet.csproj", "{9FAC7072-1AD7-45D7-B257-E46F9CC8BE8E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{FDBC39C6-5C06-4249-BDBF-068562D02AD6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.BasicRegisterReadWrite", "Examples\Examples.BasicRegisterReadWrite\Examples.BasicRegisterReadWrite.csproj", "{C630B87F-0DFD-4B3B-9845-B5627E9612D5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.Polling", "Examples\Examples.Polling\Examples.Polling.csproj", "{6615556F-EF18-442F-8FF7-A7943A503C92}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.ProgramReadWrite", "Examples\Examples.ProgramReadWrite\Examples.ProgramReadWrite.csproj", "{8311E099-F164-4700-BA1A-F5214B38E0FA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.WPF", "Examples\Examples.WPF\Examples.WPF.csproj", "{339CC94E-68AB-4D2E-BCC1-84D81082EC93}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -16,9 +36,6 @@ Global
Release|x64 = Release|x64 Release|x64 = Release|x64
Release|x86 = Release|x86 Release|x86 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8B7863E7-5E82-4990-9138-2C0C24629982}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8B7863E7-5E82-4990-9138-2C0C24629982}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8B7863E7-5E82-4990-9138-2C0C24629982}.Debug|Any CPU.Build.0 = Debug|Any CPU {8B7863E7-5E82-4990-9138-2C0C24629982}.Debug|Any CPU.Build.0 = Debug|Any CPU
@@ -32,17 +49,128 @@ Global
{8B7863E7-5E82-4990-9138-2C0C24629982}.Release|x64.Build.0 = Release|Any CPU {8B7863E7-5E82-4990-9138-2C0C24629982}.Release|x64.Build.0 = Release|Any CPU
{8B7863E7-5E82-4990-9138-2C0C24629982}.Release|x86.ActiveCfg = Release|Any CPU {8B7863E7-5E82-4990-9138-2C0C24629982}.Release|x86.ActiveCfg = Release|Any CPU
{8B7863E7-5E82-4990-9138-2C0C24629982}.Release|x86.Build.0 = Release|Any CPU {8B7863E7-5E82-4990-9138-2C0C24629982}.Release|x86.Build.0 = Release|Any CPU
{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Debug|Any CPU.Build.0 = Debug|Any CPU {C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Debug|x64.ActiveCfg = Debug|Any CPU {C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Debug|x64.ActiveCfg = Debug|Any CPU
{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Debug|x64.Build.0 = Debug|Any CPU {C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Debug|x64.Build.0 = Debug|Any CPU
{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Debug|x86.ActiveCfg = Debug|Any CPU {C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Debug|x86.ActiveCfg = Debug|Any CPU
{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Debug|x86.Build.0 = Debug|Any CPU {C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Debug|x86.Build.0 = Debug|Any CPU
{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Release|Any CPU.ActiveCfg = Release|Any CPU {C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Release|Any CPU.Build.0 = Release|Any CPU {C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Release|Any CPU.Build.0 = Release|Any CPU
{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Release|x64.ActiveCfg = Release|Any CPU {C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Release|x64.ActiveCfg = Release|Any CPU
{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Release|x64.Build.0 = Release|Any CPU {C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Release|x64.Build.0 = Release|Any CPU
{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Release|x86.ActiveCfg = Release|Any CPU {C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Release|x86.ActiveCfg = Release|Any CPU
{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Release|x86.Build.0 = Release|Any CPU {C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Release|x86.Build.0 = Release|Any CPU
{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Debug|x64.ActiveCfg = Debug|Any CPU
{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Debug|x64.Build.0 = Debug|Any CPU
{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Debug|x86.ActiveCfg = Debug|Any CPU
{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Debug|x86.Build.0 = Debug|Any CPU
{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|Any CPU.Build.0 = Release|Any CPU
{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|x64.ActiveCfg = Release|Any CPU
{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|x64.Build.0 = Release|Any CPU
{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|x86.ActiveCfg = Release|Any CPU
{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|x86.Build.0 = Release|Any CPU
{5A9DE453-AD64-4F8D-8215-3BB26674D164}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5A9DE453-AD64-4F8D-8215-3BB26674D164}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A9DE453-AD64-4F8D-8215-3BB26674D164}.Debug|x64.ActiveCfg = Debug|Any CPU
{5A9DE453-AD64-4F8D-8215-3BB26674D164}.Debug|x64.Build.0 = Debug|Any CPU
{5A9DE453-AD64-4F8D-8215-3BB26674D164}.Debug|x86.ActiveCfg = Debug|Any CPU
{5A9DE453-AD64-4F8D-8215-3BB26674D164}.Debug|x86.Build.0 = Debug|Any CPU
{5A9DE453-AD64-4F8D-8215-3BB26674D164}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A9DE453-AD64-4F8D-8215-3BB26674D164}.Release|Any CPU.Build.0 = Release|Any CPU
{5A9DE453-AD64-4F8D-8215-3BB26674D164}.Release|x64.ActiveCfg = Release|Any CPU
{5A9DE453-AD64-4F8D-8215-3BB26674D164}.Release|x64.Build.0 = Release|Any CPU
{5A9DE453-AD64-4F8D-8215-3BB26674D164}.Release|x86.ActiveCfg = Release|Any CPU
{5A9DE453-AD64-4F8D-8215-3BB26674D164}.Release|x86.Build.0 = Release|Any CPU
{00ACA0AB-3988-4EF7-98A6-B39A36B136DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{00ACA0AB-3988-4EF7-98A6-B39A36B136DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{00ACA0AB-3988-4EF7-98A6-B39A36B136DA}.Debug|x64.ActiveCfg = Debug|Any CPU
{00ACA0AB-3988-4EF7-98A6-B39A36B136DA}.Debug|x64.Build.0 = Debug|Any CPU
{00ACA0AB-3988-4EF7-98A6-B39A36B136DA}.Debug|x86.ActiveCfg = Debug|Any CPU
{00ACA0AB-3988-4EF7-98A6-B39A36B136DA}.Debug|x86.Build.0 = Debug|Any CPU
{00ACA0AB-3988-4EF7-98A6-B39A36B136DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{00ACA0AB-3988-4EF7-98A6-B39A36B136DA}.Release|Any CPU.Build.0 = Release|Any CPU
{00ACA0AB-3988-4EF7-98A6-B39A36B136DA}.Release|x64.ActiveCfg = Release|Any CPU
{00ACA0AB-3988-4EF7-98A6-B39A36B136DA}.Release|x64.Build.0 = Release|Any CPU
{00ACA0AB-3988-4EF7-98A6-B39A36B136DA}.Release|x86.ActiveCfg = Release|Any CPU
{00ACA0AB-3988-4EF7-98A6-B39A36B136DA}.Release|x86.Build.0 = Release|Any CPU
{9FAC7072-1AD7-45D7-B257-E46F9CC8BE8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9FAC7072-1AD7-45D7-B257-E46F9CC8BE8E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9FAC7072-1AD7-45D7-B257-E46F9CC8BE8E}.Debug|x64.ActiveCfg = Debug|Any CPU
{9FAC7072-1AD7-45D7-B257-E46F9CC8BE8E}.Debug|x64.Build.0 = Debug|Any CPU
{9FAC7072-1AD7-45D7-B257-E46F9CC8BE8E}.Debug|x86.ActiveCfg = Debug|Any CPU
{9FAC7072-1AD7-45D7-B257-E46F9CC8BE8E}.Debug|x86.Build.0 = Debug|Any CPU
{9FAC7072-1AD7-45D7-B257-E46F9CC8BE8E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9FAC7072-1AD7-45D7-B257-E46F9CC8BE8E}.Release|Any CPU.Build.0 = Release|Any CPU
{9FAC7072-1AD7-45D7-B257-E46F9CC8BE8E}.Release|x64.ActiveCfg = Release|Any CPU
{9FAC7072-1AD7-45D7-B257-E46F9CC8BE8E}.Release|x64.Build.0 = Release|Any CPU
{9FAC7072-1AD7-45D7-B257-E46F9CC8BE8E}.Release|x86.ActiveCfg = Release|Any CPU
{9FAC7072-1AD7-45D7-B257-E46F9CC8BE8E}.Release|x86.Build.0 = Release|Any CPU
{C630B87F-0DFD-4B3B-9845-B5627E9612D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C630B87F-0DFD-4B3B-9845-B5627E9612D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C630B87F-0DFD-4B3B-9845-B5627E9612D5}.Debug|x64.ActiveCfg = Debug|Any CPU
{C630B87F-0DFD-4B3B-9845-B5627E9612D5}.Debug|x64.Build.0 = Debug|Any CPU
{C630B87F-0DFD-4B3B-9845-B5627E9612D5}.Debug|x86.ActiveCfg = Debug|Any CPU
{C630B87F-0DFD-4B3B-9845-B5627E9612D5}.Debug|x86.Build.0 = Debug|Any CPU
{C630B87F-0DFD-4B3B-9845-B5627E9612D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C630B87F-0DFD-4B3B-9845-B5627E9612D5}.Release|Any CPU.Build.0 = Release|Any CPU
{C630B87F-0DFD-4B3B-9845-B5627E9612D5}.Release|x64.ActiveCfg = Release|Any CPU
{C630B87F-0DFD-4B3B-9845-B5627E9612D5}.Release|x64.Build.0 = Release|Any CPU
{C630B87F-0DFD-4B3B-9845-B5627E9612D5}.Release|x86.ActiveCfg = Release|Any CPU
{C630B87F-0DFD-4B3B-9845-B5627E9612D5}.Release|x86.Build.0 = Release|Any CPU
{6615556F-EF18-442F-8FF7-A7943A503C92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6615556F-EF18-442F-8FF7-A7943A503C92}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6615556F-EF18-442F-8FF7-A7943A503C92}.Debug|x64.ActiveCfg = Debug|Any CPU
{6615556F-EF18-442F-8FF7-A7943A503C92}.Debug|x64.Build.0 = Debug|Any CPU
{6615556F-EF18-442F-8FF7-A7943A503C92}.Debug|x86.ActiveCfg = Debug|Any CPU
{6615556F-EF18-442F-8FF7-A7943A503C92}.Debug|x86.Build.0 = Debug|Any CPU
{6615556F-EF18-442F-8FF7-A7943A503C92}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6615556F-EF18-442F-8FF7-A7943A503C92}.Release|Any CPU.Build.0 = Release|Any CPU
{6615556F-EF18-442F-8FF7-A7943A503C92}.Release|x64.ActiveCfg = Release|Any CPU
{6615556F-EF18-442F-8FF7-A7943A503C92}.Release|x64.Build.0 = Release|Any CPU
{6615556F-EF18-442F-8FF7-A7943A503C92}.Release|x86.ActiveCfg = Release|Any CPU
{6615556F-EF18-442F-8FF7-A7943A503C92}.Release|x86.Build.0 = Release|Any CPU
{8311E099-F164-4700-BA1A-F5214B38E0FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8311E099-F164-4700-BA1A-F5214B38E0FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8311E099-F164-4700-BA1A-F5214B38E0FA}.Debug|x64.ActiveCfg = Debug|Any CPU
{8311E099-F164-4700-BA1A-F5214B38E0FA}.Debug|x64.Build.0 = Debug|Any CPU
{8311E099-F164-4700-BA1A-F5214B38E0FA}.Debug|x86.ActiveCfg = Debug|Any CPU
{8311E099-F164-4700-BA1A-F5214B38E0FA}.Debug|x86.Build.0 = Debug|Any CPU
{8311E099-F164-4700-BA1A-F5214B38E0FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8311E099-F164-4700-BA1A-F5214B38E0FA}.Release|Any CPU.Build.0 = Release|Any CPU
{8311E099-F164-4700-BA1A-F5214B38E0FA}.Release|x64.ActiveCfg = Release|Any CPU
{8311E099-F164-4700-BA1A-F5214B38E0FA}.Release|x64.Build.0 = Release|Any CPU
{8311E099-F164-4700-BA1A-F5214B38E0FA}.Release|x86.ActiveCfg = Release|Any CPU
{8311E099-F164-4700-BA1A-F5214B38E0FA}.Release|x86.Build.0 = Release|Any CPU
{339CC94E-68AB-4D2E-BCC1-84D81082EC93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{339CC94E-68AB-4D2E-BCC1-84D81082EC93}.Debug|Any CPU.Build.0 = Debug|Any CPU
{339CC94E-68AB-4D2E-BCC1-84D81082EC93}.Debug|x64.ActiveCfg = Debug|Any CPU
{339CC94E-68AB-4D2E-BCC1-84D81082EC93}.Debug|x64.Build.0 = Debug|Any CPU
{339CC94E-68AB-4D2E-BCC1-84D81082EC93}.Debug|x86.ActiveCfg = Debug|Any CPU
{339CC94E-68AB-4D2E-BCC1-84D81082EC93}.Debug|x86.Build.0 = Debug|Any CPU
{339CC94E-68AB-4D2E-BCC1-84D81082EC93}.Release|Any CPU.ActiveCfg = Release|Any CPU
{339CC94E-68AB-4D2E-BCC1-84D81082EC93}.Release|Any CPU.Build.0 = Release|Any CPU
{339CC94E-68AB-4D2E-BCC1-84D81082EC93}.Release|x64.ActiveCfg = Release|Any CPU
{339CC94E-68AB-4D2E-BCC1-84D81082EC93}.Release|x64.Build.0 = Release|Any CPU
{339CC94E-68AB-4D2E-BCC1-84D81082EC93}.Release|x86.ActiveCfg = Release|Any CPU
{339CC94E-68AB-4D2E-BCC1-84D81082EC93}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5A9DE453-AD64-4F8D-8215-3BB26674D164} = {BAEF983A-EFF2-48DF-A74E-57084166BB4D}
{00ACA0AB-3988-4EF7-98A6-B39A36B136DA} = {BAEF983A-EFF2-48DF-A74E-57084166BB4D}
{9FAC7072-1AD7-45D7-B257-E46F9CC8BE8E} = {FDBC39C6-5C06-4249-BDBF-068562D02AD6}
{C630B87F-0DFD-4B3B-9845-B5627E9612D5} = {FDBC39C6-5C06-4249-BDBF-068562D02AD6}
{6615556F-EF18-442F-8FF7-A7943A503C92} = {FDBC39C6-5C06-4249-BDBF-068562D02AD6}
{8311E099-F164-4700-BA1A-F5214B38E0FA} = {FDBC39C6-5C06-4249-BDBF-068562D02AD6}
{339CC94E-68AB-4D2E-BCC1-84D81082EC93} = {FDBC39C6-5C06-4249-BDBF-068562D02AD6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4ABB8137-CD8F-4691-9802-9ED371012F47}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,148 @@
using MewtocolNet.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace MewtocolNet.ComCassette {
/// <summary>
/// Provides a interface to modify and find PLC network cassettes also known as COM5
/// </summary>
public class CassetteFinder {
public static async Task<IEnumerable<CassetteInformation>> FindClientsAsync(string ipSource = null, int timeoutMs = 100) {
Logger.Log("Scanning for cassettes over UDP");
var from = new IPEndPoint(IPAddress.Any, 0);
var interfacesTasks = new List<Task<List<CassetteInformation>>>();
var usableInterfaces = Mewtocol.GetUseableNetInterfaces();
if (ipSource == null) {
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (NetworkInterface netInterface in usableInterfaces) {
IPInterfaceProperties ipProps = netInterface.GetIPProperties();
var unicastInfo = ipProps.UnicastAddresses
.FirstOrDefault(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
var ep = new IPEndPoint(unicastInfo.Address, 0);
interfacesTasks.Add(FindClientsForEndpoint(ep, timeoutMs, netInterface.Name));
}
} else {
from = new IPEndPoint(IPAddress.Parse(ipSource), 0);
var netInterface = usableInterfaces.FirstOrDefault(x => x.GetIPProperties().UnicastAddresses.Any(y => y.Address.ToString() == ipSource));
if (netInterface == null)
throw new NotSupportedException($"The host endpoint {ipSource}, is not available");
interfacesTasks.Add(FindClientsForEndpoint(from, timeoutMs, netInterface.Name));
}
//run the interface querys
var grouped = await Task.WhenAll(interfacesTasks);
var decomposed = new List<CassetteInformation>();
foreach (var grp in grouped) {
foreach (var cassette in grp) {
if (decomposed.Any(x => x.MacAddress.SequenceEqual(cassette.MacAddress))) continue;
decomposed.Add(cassette);
}
}
Logger.Log($"Found {decomposed.Count} cassettes");
return decomposed.OrderBy(x => x.IPAddress.ToString());
}
private static async Task<List<CassetteInformation>> FindClientsForEndpoint(IPEndPoint from, int timeoutMs, string ipEndpointName) {
var cassettesFound = new List<CassetteInformation>();
int plcPort = 9090;
// Byte msg to request the status transmission of all plcs
byte[] requestCode = new byte[] { 0x88, 0x40, 0x00 };
// The start code of the status transmission response
byte[] startCode = new byte[] { 0x88, 0xC0, 0x00 };
using (var udpClient = new UdpClient()) {
udpClient.EnableBroadcast = true;
udpClient.Client.Bind(from);
//broadcast packet to all devices (plc specific package)
udpClient.Send(requestCode, requestCode.Length, "255.255.255.255", plcPort);
//canceling after no new data was read
CancellationTokenSource tSource = new CancellationTokenSource();
var tm = new System.Timers.Timer(timeoutMs);
tm.Elapsed += (s, e) => {
tSource.Cancel();
tm.Stop();
};
tm.Start();
//wait for devices to send response
try {
byte[] recvBuffer = null;
while (!tSource.Token.IsCancellationRequested) {
if (tSource.Token.IsCancellationRequested) break;
var res = await udpClient.ReceiveAsync().WithCancellation(tSource.Token);
if (res.Buffer == null) break;
recvBuffer = res.Buffer;
if (recvBuffer.SearchBytePattern(startCode) == 0) {
tm.Stop();
tm.Start();
var parsed = CassetteInformation.FromBytes(recvBuffer, from, ipEndpointName);
if (parsed != null) cassettesFound.Add(parsed);
}
}
}
catch (OperationCanceledException) { }
catch (SocketException) { }
}
return cassettesFound;
}
}
}

View File

@@ -0,0 +1,219 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
//WARNING! The whole UDP protocol was reverse engineered and is not fully implemented..
namespace MewtocolNet.ComCassette {
/// <summary>
/// Information about the COM cassette
/// </summary>
public class CassetteInformation {
/// <summary>
/// Indicates if the cassette is currently configurating
/// </summary>
public bool IsConfigurating { get; private set; }
/// <summary>
/// Name of the COM cassette
/// </summary>
public string Name { get; set; }
/// <summary>
/// Indicates the usage of DHCP
/// </summary>
public bool UsesDHCP { get; set; }
/// <summary>
/// IP Address of the COM cassette
/// </summary>
public IPAddress IPAddress { get; set; }
/// <summary>
/// Subnet mask of the cassette
/// </summary>
public IPAddress SubnetMask { get; set; }
/// <summary>
/// Default gateway of the cassette
/// </summary>
public IPAddress GatewayAddress { get; set; }
/// <summary>
/// Mac address of the cassette
/// </summary>
public byte[] MacAddress { get; private set; }
/// <summary>
/// Mac address of the cassette formatted as a MAC string (XX:XX:XX:XX:XX)
/// </summary>
public string MacAddressStr => MacAddress.ToHexString(":");
/// <summary>
/// The source endpoint the cassette is reachable from
/// </summary>
public IPEndPoint Endpoint { get; private set; }
/// <summary>
/// The name of the endpoint the device is reachable from, or null if not specifically defined
/// </summary>
public string EndpointName { get; private set; }
/// <summary>
/// Firmware version as string
/// </summary>
public string FirmwareVersion { get; private set; }
/// <summary>
/// The tcp port of the cassette
/// </summary>
public int Port { get; private set; }
/// <summary>
/// Status of the cassette
/// </summary>
public CassetteStatus Status { get; private set; }
internal static CassetteInformation FromBytes(byte[] bytes, IPEndPoint endpoint, string endpointName) {
// Receive data package explained:
// 0 3 4 8 12 17 22 24 27 29 31 32
// 88 C0 00 | 00 | C0 A8 73 D4 | FF FF FF 00 | C0 A8 73 3C | 00 | C0 8F 60 53 1C | 01 10 | 23 86 | 00 | 25 | 00 | 00 | 00 | 0D | (byte) * (n) NAME LEN
// Header |DHCP| IPv4 addr. | Subnet Mask | IPv4 Gatwy | | Mac Addr. | Ver. | Port | | | |STAT| | Name LEN | Name
// 1 or 0 Procuct Type? StatusCode Length of Name
//get ips / mac
var dhcpOn = bytes.Skip(3).First() != 0x00;
var ipAdd = new IPAddress(bytes.Skip(4).Take(4).ToArray());
var subnetMask = new IPAddress(bytes.Skip(8).Take(4).ToArray());
var gateWaysAdd = new IPAddress(bytes.Skip(12).Take(4).ToArray());
var macAdd = bytes.Skip(17).Take(5).ToArray();
var firmwareV = string.Join(".", bytes.Skip(22).Take(2).Select(x => x.ToString("X1")).ToArray());
var port = BitConverter.ToUInt16(bytes.Skip(24).Take(2).Reverse().ToArray(), 0);
var status = (CassetteStatus)bytes.Skip(29).First();
//missing blocks, later
//get name
var name = Encoding.ASCII.GetString(bytes.Skip(32).ToArray());
return new CassetteInformation {
Name = name,
UsesDHCP = dhcpOn,
IPAddress = ipAdd,
SubnetMask = subnetMask,
GatewayAddress = gateWaysAdd,
MacAddress = macAdd,
Endpoint = endpoint,
EndpointName = endpointName,
FirmwareVersion = firmwareV,
Port = port,
Status = status,
};
}
public async Task SendNewConfigAsync() {
if (IsConfigurating) return;
// this command gets sent to a specific plc ip address to overwrite the cassette config
// If dhcp is set to 1 the ip is ignored but still must be valid
// 88 41 00 | 00 | C0 8F 61 07 1B | 05 | 54 65 73 74 31 | 05 | 46 50 58 45 54 | 00 | C0 A8 01 07 | FF FF FF 00 | C0 A8 73 3C
// Header | | | 5 | T e s t 1 | 05 | F P X E T |0||1| 192.168.1.7 | 255.255... | 192.168.115.60
// Header | | Mac Address |LEN>| ASCII Name |LEN>| Static |DHCP| Target IP | Subnet Mask | Gateway
IsConfigurating = true;
List<byte> sendBytes = new List<byte>();
//add cmd header
sendBytes.AddRange(new byte[] { 0x88, 0x41, 0x00, 0x00 });
//add mac
sendBytes.AddRange(MacAddress);
//add name length
sendBytes.Add((byte)Name.Length);
//add name
sendBytes.AddRange(Encoding.ASCII.GetBytes(Name));
//FPXET
var subname = Encoding.ASCII.GetBytes("TESTFP");
//add sub name length
sendBytes.Add((byte)subname.Length);
//add subname
sendBytes.AddRange(subname);
//add dhcp 0 | 1
sendBytes.Add((byte)(UsesDHCP ? 0x01 : 0x00));
//add ip address
sendBytes.AddRange(IPAddress.GetAddressBytes());
//add subnet mask ip address
sendBytes.AddRange(SubnetMask.GetAddressBytes());
//add gateway ip
sendBytes.AddRange(GatewayAddress.GetAddressBytes());
var sendBytesArr = sendBytes.ToArray();
using (var udpClient = new UdpClient()) {
udpClient.Client.Bind(Endpoint);
//broadcast packet to all devices (plc specific package)
await udpClient.SendAsync(sendBytesArr, sendBytesArr.Length, "255.255.255.255", 9090);
}
}
public static bool operator ==(CassetteInformation a, CassetteInformation b) => EqualProps(a, b);
public static bool operator !=(CassetteInformation a, CassetteInformation b) => !EqualProps(a, b);
private static bool EqualProps (CassetteInformation a, CassetteInformation b) {
if (a is null && b is null) return true;
if (!(a is null) && b is null) return false;
if (!(b is null) && a is null) return false;
return a.Name == b.Name &&
a.UsesDHCP == b.UsesDHCP &&
a.IPAddress.ToString() == b.IPAddress.ToString() &&
a.SubnetMask.ToString() == b.SubnetMask.ToString() &&
a.GatewayAddress.ToString() == b.GatewayAddress.ToString() &&
a.MacAddressStr == b.MacAddressStr &&
a.FirmwareVersion == b.FirmwareVersion &&
a.Port == b.Port &&
a.Status == b.Status;
}
public override bool Equals(object obj) {
if ((obj == null) || !this.GetType().Equals(obj.GetType())) {
return false;
} else {
return (CassetteInformation)obj == this;
}
}
}
}

View File

@@ -0,0 +1,19 @@
namespace MewtocolNet.ComCassette {
/// <summary>
/// Needs a list of all status codes.. hard to reverse engineer
/// </summary>
public enum CassetteStatus {
/// <summary>
/// Cassette is running as intended
/// </summary>
Normal = 0,
/// <summary>
/// Cassette DHCP resolution error
/// </summary>
DHCPError = 2,
}
}

View File

@@ -0,0 +1,103 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
namespace MewtocolNet {
/// <summary>
/// A DWord is a 16 bit value of 2 bytes
/// </summary>
public struct DWord : MewtocolExtTypeInit2Word {
private int bitLength;
internal uint value;
public uint Value => value;
public DWord(uint bytes) {
value = bytes;
bitLength = Marshal.SizeOf(value) * 8;
}
public DWord(byte[] bytes) {
bytes = bytes.Take(4).ToArray();
value = BitConverter.ToUInt32(bytes, 0);
bitLength = Marshal.SizeOf(value) * 8;
}
//operations
public static DWord operator -(DWord a, DWord b) => new DWord() {
value = (ushort)(a.value - b.value)
};
public static DWord operator +(DWord a, DWord b) => new DWord() {
value = (ushort)(a.value + b.value)
};
public static DWord operator *(DWord a, DWord b) => new DWord() {
value = (ushort)(a.value * b.value)
};
public static DWord operator /(DWord a, DWord b) => new DWord() {
value = (ushort)(a.value / b.value)
};
public static bool operator ==(DWord a, DWord b) => a.value == b.value;
public static bool operator !=(DWord a, DWord b) => a.value != b.value;
/// <summary>
/// Gets or sets the bit value at the given position
/// </summary>
public bool this[int bitIndex] {
get {
if (bitIndex > bitLength - 1 && bitLength != 0)
throw new IndexOutOfRangeException($"The word bit index was out of range ({bitIndex}/{bitLength - 1})");
if (bitLength == 0) return false;
return (value & (1 << bitIndex)) != 0;
}
}
public override bool Equals(object obj) {
if ((obj == null) || !this.GetType().Equals(obj.GetType())) {
return false;
} else {
return (DWord)obj == this;
}
}
public override int GetHashCode() => (int)value;
public byte[] ToByteArray() => BitConverter.GetBytes(value);
//string ops
public override string ToString() => $"0x{value.ToString("X8")}";
public string ToStringBits() {
return Convert.ToString(value, 2).PadLeft(bitLength, '0');
}
public string ToStringBitsPlc() {
var parts = Convert.ToString(value, 2)
.PadLeft(Marshal.SizeOf(value) * 8, '0')
.SplitInParts(4);
return string.Join("_", parts);
}
}
}

View File

@@ -0,0 +1,92 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
namespace MewtocolNet {
/// <summary>
/// A DateAndTime struct of 4 bytes represented as seconds from 2001-01-01 in the PLC<br/>
/// This also works for the PLC type TIME_OF_DAY and DATE
/// </summary>
public struct DateAndTime : MewtocolExtTypeInit2Word {
internal DateTime value;
public DateAndTime(int year = 2001, int month = 1, int day = 1, int hour = 0, int minute = 0, int second = 0) {
var minDate = MinDate;
var maxDate = MaxDate;
if (year < 2001 || year > 2099)
throw new NotSupportedException("Year must be between 2001 and 2099");
if (month < 1 || month > 12)
throw new NotSupportedException("Month must be between 1 and 12");
if (day < 1 || day > 32)
throw new NotSupportedException("Day must be between 1 and 32");
if (day < 1 || day > 32)
throw new NotSupportedException("Month must be between 1 and 32");
var dt = new DateTime(year, month, day, hour, minute, second);
if (dt < minDate)
throw new Exception($"The minimal DATE_AND_TIME repesentation is {minDate}");
if (dt > maxDate)
throw new Exception($"The maximal DATE_AND_TIME repesentation is {maxDate}");
value = dt;
}
public static DateAndTime FromBytes(byte[] bytes) {
var secondsFrom = BitConverter.ToUInt32(bytes, 0);
return FromDateTime(MinDate + TimeSpan.FromSeconds(secondsFrom));
}
public static DateAndTime FromDateTime(DateTime time) => new DateAndTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, time.Second);
//operations
public static TimeSpan operator -(DateAndTime a, DateAndTime b) => a.value - b.value;
public static DateAndTime operator +(DateAndTime a, TimeSpan b) => FromDateTime(a.value + b);
public static bool operator ==(DateAndTime a, DateAndTime b) => a.value == b.value;
public static bool operator !=(DateAndTime a, DateAndTime b) => a.value != b.value;
public override bool Equals(object obj) {
if ((obj == null) || !this.GetType().Equals(obj.GetType())) {
return false;
} else {
return (DateAndTime)obj == this;
}
}
public override int GetHashCode() => value.GetHashCode();
public byte[] ToByteArray() => BitConverter.GetBytes(SecondsSinceStart());
public DateTime ToDateTime() => value;
private uint SecondsSinceStart() => (uint)(value - MinDate).TotalSeconds;
private static DateTime MinDate => new DateTime(2001, 01, 01, 0, 0, 0);
private static DateTime MaxDate => new DateTime(2099, 12, 31, 23, 59, 59);
//string ops
public override string ToString() => $"DT#{value:yyyy-MM-dd-HH:mm:ss}";
}
}

View File

@@ -0,0 +1,7 @@
namespace MewtocolNet {
internal interface MewtocolExtTypeInit1Word { }
internal interface MewtocolExtTypeInit2Word { }
}

View File

@@ -0,0 +1,103 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
namespace MewtocolNet {
/// <summary>
/// A word is a 16 bit value of 2 bytes
/// </summary>
public struct Word : MewtocolExtTypeInit1Word {
private int bitLength;
internal ushort value;
public ushort Value => value;
public Word(ushort bytes) {
value = bytes;
bitLength = Marshal.SizeOf(value) * 8;
}
public Word(byte[] bytes) {
bytes = bytes.Take(2).ToArray();
value = BitConverter.ToUInt16(bytes, 0);
bitLength = Marshal.SizeOf(value) * 8;
}
//operations
public static Word operator -(Word a, Word b) => new Word() {
value = (ushort)(a.value - b.value)
};
public static Word operator +(Word a, Word b) => new Word() {
value = (ushort)(a.value + b.value)
};
public static Word operator *(Word a, Word b) => new Word() {
value = (ushort)(a.value * b.value)
};
public static Word operator /(Word a, Word b) => new Word() {
value = (ushort)(a.value / b.value)
};
public static bool operator ==(Word a, Word b) => a.value == b.value;
public static bool operator !=(Word a, Word b) => a.value != b.value;
/// <summary>
/// Gets the bit value at the given position
/// </summary>
public bool this[int bitIndex] {
get {
if (bitIndex > bitLength - 1 && bitLength != 0)
throw new IndexOutOfRangeException($"The word bit index was out of range ({bitIndex}/{bitLength - 1})");
if (bitLength == 0) return false;
return (value & (1 << bitIndex)) != 0;
}
}
public override bool Equals(object obj) {
if ((obj == null) || !this.GetType().Equals(obj.GetType())) {
return false;
} else {
return (Word)obj == this;
}
}
public override int GetHashCode() => (int)value;
public byte[] ToByteArray() => BitConverter.GetBytes(value);
//string ops
public override string ToString() => $"0x{value.ToString("X4")}";
public string ToStringBits() {
return Convert.ToString(value, 2).PadLeft(bitLength, '0');
}
public string ToStringBitsPlc() {
var parts = Convert.ToString(value, 2)
.PadLeft(Marshal.SizeOf(value) * 8, '0')
.SplitInParts(4);
return string.Join("_", parts);
}
}
}

View File

@@ -1,11 +1,10 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace MewtocolNet.Links { namespace MewtocolNet.DataLists {
internal class LinkedData { internal class CodeDescriptions {
internal static Dictionary<int, string> ErrorCodes = new System.Collections.Generic.Dictionary<int, string> { internal static Dictionary<int, string> Error = new Dictionary<int, string> {
{21, "NACK error"}, {21, "NACK error"},
{22, "WACK error"}, {22, "WACK error"},
@@ -40,7 +39,6 @@ namespace MewtocolNet.Links {
}; };
} }
} }

View File

@@ -0,0 +1,12 @@
using System;
namespace MewtocolNet.Documentation {
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
internal class PlcCodeTestedAttribute : Attribute {
public PlcCodeTestedAttribute() { }
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace MewtocolNet.Documentation {
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
internal class PlcEXRTAttribute : Attribute {
public PlcEXRTAttribute() { }
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace MewtocolNet.Documentation {
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
internal class PlcLegacyAttribute : Attribute {
public PlcLegacyAttribute() { }
}
}

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8" ?>
<extradoc>
<class name="support-conv-types">
<list type="bullet">
<item>
<term>
<see cref="bool"/>
</term>
<description>Boolean R/X/Y registers</description>
</item>
<item>
<term>
<see cref="short"/>
</term>
<description>16 bit signed integer</description>
</item>
<item>
<term>
<see cref="ushort"/>
</term>
<description>16 bit un-signed integer</description>
</item>
<item>
<term>
<see cref="Word"/>
</term>
<description>16 bit word (2 bytes)</description>
</item>
<item>
<term>
<see cref="int"/>
</term>
<description>32 bit signed integer</description>
</item>
<item>
<term>
<see cref="uint"/>
</term>
<description>32 bit un-signed integer</description>
</item>
<item>
<term>
<see cref="DWord"/>
</term>
<description>32 bit word (4 bytes)</description>
</item>
<item>
<term>
<see cref="float"/>
</term>
<description>32 bit floating point</description>
</item>
<item>
<term>
<see cref="TimeSpan"/>
</term>
<description>
32 bit time from <see cref="PlcVarType.TIME"/> interpreted as <see cref="TimeSpan"/>
</description>
</item>
<item>
<term>
<see cref="Enum"/>
</term>
<description>16 or 32 bit enums, also supports flags</description>
</item>
<item>
<term>
<see cref="string"/>
</term>
<description>String of chars, the interface will automatically get the length</description>
</item>
</list>
</class>
</extradoc>

View File

@@ -0,0 +1,9 @@
using System;
namespace MewtocolNet.Events {
public delegate void PlcConnectionEventHandler(object sender, PlcConnectionArgs e);
public class PlcConnectionArgs : EventArgs { }
}

View File

@@ -0,0 +1,19 @@
using System;
namespace MewtocolNet.Events {
public delegate void PlcModeChangedEventHandler(object sender, PlcModeArgs e);
public class PlcModeArgs : EventArgs {
public OPMode LastMode { get; internal set; }
public OPMode NowMode { get; internal set; }
public bool ProgToRun { get; internal set; }
public bool RunToProg { get; internal set; }
}
}

View File

@@ -0,0 +1,98 @@
using MewtocolNet.Registers;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
namespace MewtocolNet.Events {
public delegate void PlcReconnectEventHandler(object sender, ReconnectArgs e);
public class ReconnectArgs : EventArgs, INotifyPropertyChanged {
private TimeSpan retryCountDownRemaining;
public event PropertyChangedEventHandler PropertyChanged;
public event Action Reconnected;
public int ReconnectTry { get; internal set; }
public int MaxAttempts { get; internal set; }
public TimeSpan RetryCountDownTime { get; internal set; }
public TimeSpan RetryCountDownRemaining {
get => retryCountDownRemaining;
private set {
retryCountDownRemaining = value;
OnPropChange();
}
}
private bool isReconnected;
public bool IsReconnected {
get { return isReconnected; }
set {
isReconnected = value;
OnPropChange();
}
}
private System.Timers.Timer countDownTimer;
internal ReconnectArgs(int currentAttempt, int totalAttempts, TimeSpan delayBetween) {
ReconnectTry = currentAttempt;
MaxAttempts = totalAttempts;
RetryCountDownTime = delayBetween;
//start countdown timer
RetryCountDownRemaining = RetryCountDownTime;
var interval = 100;
var intervalTS = TimeSpan.FromMilliseconds(interval);
countDownTimer = new System.Timers.Timer(100);
countDownTimer.Elapsed += (s, e) => {
if (RetryCountDownRemaining <= TimeSpan.Zero) {
StopTimer();
return;
}
RetryCountDownRemaining -= intervalTS;
};
countDownTimer.Start();
}
internal void ConnectionSuccess () {
IsReconnected = true;
StopTimer();
Reconnected?.Invoke();
}
private void StopTimer () {
countDownTimer?.Stop();
RetryCountDownRemaining = TimeSpan.Zero;
}
private void OnPropChange([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@@ -0,0 +1,22 @@
using MewtocolNet.Registers;
using System;
using System.Collections.Generic;
using System.Text;
namespace MewtocolNet.Events {
public delegate void RegisterChangedEventHandler(object sender, RegisterChangedArgs e);
public class RegisterChangedArgs : EventArgs {
public IRegister Register { get; internal set; }
public object Value { get; internal set; }
public object PreviousValue { get; internal set; }
public string PreviousValueString { get; internal set; }
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MewtocolNet {
internal static class AsyncExtensions {
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken) {
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs)) {
if (task != await Task.WhenAny(task, tcs.Task)) {
return default(T);
}
}
if (task.IsCanceled) return default(T);
return task.Result;
}
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.IO.Ports;
using System.Threading.Tasks;
namespace MewtocolNet {
internal static class SerialPortExtensions {
public async static Task WriteAsync(this SerialPort serialPort, byte[] buffer, int offset, int count) {
await serialPort.BaseStream.WriteAsync(buffer, 0, buffer.Length);
}
public async static Task ReadAsync(this SerialPort serialPort, byte[] buffer, int offset, int count) {
var bytesToRead = count;
var temp = new byte[count];
while (bytesToRead > 0) {
var readBytes = await serialPort.BaseStream.ReadAsync(temp, 0, bytesToRead);
Array.Copy(temp, 0, buffer, offset + count - bytesToRead, readBytes);
bytesToRead -= readBytes;
}
}
public async static Task<byte[]> ReadAsync(this SerialPort serialPort, int count) {
var buffer = new byte[count];
await serialPort.ReadAsync(buffer, 0, count);
return buffer;
}
}
}

View File

@@ -0,0 +1,88 @@
using System;
using System.Text;
namespace MewtocolNet {
internal static class CRCCalculator {
private static readonly ushort[] crcTable_CRC16_MCRF4XX = {
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78,
};
internal static string BCC_Mew(this string msg) {
byte[] bytes = Encoding.ASCII.GetBytes(msg);
byte crc = CRC8(bytes);
return msg.Insert(msg.Length, crc.ToString("X2"));
}
internal static string BCC_Mew7(this string msg) {
byte[] bytes = Encoding.ASCII.GetBytes(msg);
ushort crc = CRC16_MCRF4XX(bytes);
return msg.Insert(msg.Length, crc.ToString("X4"));
}
private static ushort CRC16_MCRF4XX(byte[] bytes) {
int icrc = 0xFFFF;
for (int i = 0; i < bytes.Length; i++) {
icrc = (icrc >> 8) ^ crcTable_CRC16_MCRF4XX[(icrc ^ bytes[i]) & 0xff];
}
byte[] ret = BitConverter.GetBytes(icrc);
return BitConverter.ToUInt16(ret, 0);
}
private static byte CRC8(byte[] bytes) {
byte xorTotalByte = 0;
for (int i = 0; i < bytes.Length; i++)
xorTotalByte ^= bytes[i];
return xorTotalByte;
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
namespace MewtocolNet.Helpers {
internal static class LinqHelpers {
internal static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) {
return DistinctBy(source, keySelector, null);
}
internal static IEnumerable<TSource> DistinctBy<TSource, TKey>
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer) {
if (source == null) throw new ArgumentNullException(nameof(source));
if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));
return _(); IEnumerable<TSource> _() {
var knownKeys = new HashSet<TKey>(comparer);
foreach (var element in source) {
if (knownKeys.Add(keySelector(element)))
yield return element;
}
}
}
}
}

View File

@@ -0,0 +1,514 @@
using MewtocolNet.Documentation;
using MewtocolNet.Registers;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Threading;
namespace MewtocolNet {
/// <summary>
/// Contains helper methods
/// </summary>
public static class MewtocolHelpers {
#region Async extensions
internal static Task WhenCanceled(this CancellationToken cancellationToken) {
var tcs = new TaskCompletionSource<bool>();
cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs);
return tcs.Task;
}
#endregion
#region Byte and string operation helpers
public static T SetFlag<T>(this Enum value, T flag, bool set) {
Type underlyingType = Enum.GetUnderlyingType(value.GetType());
dynamic valueAsInt = Convert.ChangeType(value, underlyingType);
dynamic flagAsInt = Convert.ChangeType(flag, underlyingType);
if (set) {
valueAsInt |= flagAsInt;
} else {
valueAsInt &= ~flagAsInt;
}
return (T)valueAsInt;
}
public static int DetermineTypeByteIntialSize(this Type type) {
//enums can only be of numeric types
if (type.IsEnum) return Marshal.SizeOf(Enum.GetUnderlyingType(type));
//strings get always set with 4 bytes because the first 4 bytes contain the length
if (type == typeof(string)) return 4;
if (type == typeof(TimeSpan)) return 4;
if (type == typeof(DateTime)) return 4;
if (type.Namespace.StartsWith("System")) return Marshal.SizeOf(type);
if (typeof(MewtocolExtTypeInit1Word).IsAssignableFrom(type)) return 2;
if (typeof(MewtocolExtTypeInit2Word).IsAssignableFrom(type)) return 4;
throw new Exception("Type not supported");
}
/// <summary>
/// Searches a byte array for a pattern
/// </summary>
/// <param name="src"></param>
/// <param name="pattern"></param>
/// <returns>The start index of the found pattern or -1</returns>
public static int SearchBytePattern(this byte[] src, byte[] pattern) {
int maxFirstCharSlot = src.Length - pattern.Length + 1;
for (int i = 0; i < maxFirstCharSlot; i++) {
if (src[i] != pattern[0]) // compare only first byte
continue;
// found a match on first byte, now try to match rest of the pattern
for (int j = pattern.Length - 1; j >= 1; j--) {
if (src[i + j] != pattern[j]) break;
if (j == 1) return i;
}
}
return -1;
}
/// <summary>
/// Converts a string (after converting to upper case) to ascii bytes
/// </summary>
internal static byte[] BytesFromHexASCIIString(this string _str) {
ASCIIEncoding ascii = new ASCIIEncoding();
byte[] bytes = ascii.GetBytes(_str.ToUpper());
return bytes;
}
/// <summary>
/// Parses a return message as RCS single bit
/// </summary>
internal static bool? ParseRCSingleBit(this string _onString) {
_onString = _onString.Replace("\r", "");
var res = new Regex(@"\%([0-9a-fA-F]{2})\$RC(.)").Match(_onString);
if (res.Success) {
string val = res.Groups[2].Value;
return val == "1";
}
return null;
}
/// <summary>
/// Parses a return message as RCS multiple bits
/// </summary>
internal static BitArray ParseRCMultiBit(this string _onString) {
_onString = _onString.Replace("\r", "");
var res = new Regex(@"\%([0-9a-fA-F]{2})\$RC(?<bits>(?:0|1){0,8})(..)").Match(_onString);
if (res.Success) {
string val = res.Groups["bits"].Value;
return new BitArray(val.Select(c => c == '1').ToArray());
}
return null;
}
/// <summary>
/// Parses a return string from the PLC as a raw byte array <br/>
/// Example:
/// <code>
/// ↓Start ↓end
/// %01$RD0100020010\r
/// </code>
/// This will return the byte array:
/// <code>
/// [0x01, 0x00, 0x02, 0x00]
/// </code>
/// </summary>
/// <param name="_onString"></param>
/// <returns>A <see cref="T:byte[]"/> or null of failed</returns>
internal static byte[] ParseResponseStringAsBytes(this string _onString) {
_onString = _onString.Replace("\r", "");
var res = new Regex(@"(?:\%|\<)([0-9a-fA-F]{2})\$(?:RD|RP|RC)(?<data>.*)(?<csum>..)").Match(_onString);
if (res.Success) {
string val = res.Groups["data"].Value;
var parts = val.SplitInParts(2).ToArray();
var bytes = new byte[parts.Length];
for (int i = 0; i < bytes.Length; i++) {
bytes[i] = byte.Parse(parts[i], NumberStyles.HexNumber);
}
return bytes;
}
return null;
}
/// <summary>
/// Splits a string into even parts
/// </summary>
internal static IEnumerable<string> SplitInParts(this string s, int partLength) {
if (s == null)
throw new ArgumentNullException(nameof(s));
if (partLength <= 0)
throw new ArgumentException("Part length has to be positive.", nameof(partLength));
for (var i = 0; i < s.Length; i += partLength)
yield return s.Substring(i, Math.Min(partLength, s.Length - i));
}
/// <summary>
/// Splits a string by uppercase words
/// </summary>
internal static IEnumerable<string> SplitByAlternatingCase(this string str) {
var words = new List<string>();
var result = new StringBuilder();
for (int i = 0; i < str.Length; i++) {
char lastCh = str[Math.Max(0, i - 1)];
char ch = str[i];
if (char.IsUpper(ch) && char.IsLower(lastCh) && result.Length > 0) {
words.Add(result.ToString().Trim());
result.Clear();
}
result.Append(ch);
}
if (!string.IsNullOrEmpty(result.ToString()))
words.Add(result.ToString().Trim());
return words;
}
/// <summary>
/// Splits a string by uppercase words and joins them with the given seperator
/// </summary>
internal static string JoinSplitByUpperCase(this string str, string seperator = " ") => string.Join(seperator, str.SplitByAlternatingCase());
internal static string Ellipsis(this string str, int maxLength) {
if (string.IsNullOrEmpty(str) || str.Length <= maxLength)
return str;
return $"{str.Substring(0, maxLength - 3)}...";
}
internal static string SanitizeLinebreakFormatting (this string str) {
str = str.Replace("\r", "").Replace("\n", "").Trim();
return Regex.Replace(str, @"\s+", " ");
}
internal static string SanitizeBracketFormatting(this string str) {
return str.Replace("(", "").Replace(")", "").Trim();
}
/// <summary>
/// Converts a hex string (AB01C1) to a byte array
/// </summary>
internal static byte[] HexStringToByteArray(this string hex) {
if (hex == null) return null;
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
/// <summary>
/// Converts a byte array to a hexadecimal string
/// </summary>
/// <param name="seperator">Seperator between the hex numbers</param>
/// <param name="arr">The byte array</param>
public static string ToHexString(this byte[] arr, string seperator = "") {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < arr.Length; i++) {
byte b = arr[i];
sb.Append(b.ToString("X2"));
if (i < arr.Length - 1) sb.Append(seperator);
}
return sb.ToString();
}
/// <summary>
/// Switches byte order from mixed to big endian
/// </summary>
internal static byte[] BigToMixedEndian(this byte[] arr) {
List<byte> oldBL = new List<byte>(arr);
List<byte> tempL = new List<byte>();
//make the input list even
if (arr.Length % 2 != 0)
oldBL.Add((byte)0);
for (int i = 0; i < oldBL.Count; i += 2) {
byte firstByte = oldBL[i];
byte lastByte = oldBL[i + 1];
tempL.Add(lastByte);
tempL.Add(firstByte);
}
return tempL.ToArray();
}
#endregion
#region Comparerers
/// <summary>
/// Checks if the register type is boolean
/// </summary>
internal static bool IsBoolean(this RegisterPrefix type) {
return type == RegisterPrefix.X || type == RegisterPrefix.Y || type == RegisterPrefix.R;
}
/// <summary>
/// Checks if the register type numeric
/// </summary>
internal static bool IsNumericDTDDT(this RegisterPrefix type) {
return type == RegisterPrefix.DT || type == RegisterPrefix.DDT;
}
/// <summary>
/// Checks if the register type is an physical in or output of the plc
/// </summary>
internal static bool IsPhysicalInOutType(this RegisterPrefix type) {
return type == RegisterPrefix.X || type == RegisterPrefix.Y;
}
internal static bool CompareIsDuplicate(this Register reg1, Register compare) {
bool valCompare = reg1.RegisterType == compare.RegisterType &&
reg1.MemoryAddress == compare.MemoryAddress &&
reg1.GetRegisterAddressLen() == compare.GetRegisterAddressLen() &&
reg1.GetSpecialAddress() == compare.GetSpecialAddress();
return valCompare;
}
internal static bool CompareIsDuplicateNonCast(this Register toInsert, Register compare, List<Type> allowOverlappingTypes) {
foreach (var type in allowOverlappingTypes) {
if (toInsert.GetType() == type) return false;
}
bool valCompare = toInsert.GetType() != compare.GetType() &&
toInsert.MemoryAddress == compare.MemoryAddress &&
toInsert.GetRegisterAddressLen() == compare.GetRegisterAddressLen() &&
toInsert.GetSpecialAddress() == compare.GetSpecialAddress();
return valCompare;
}
internal static bool CompareIsNameDuplicate(this Register reg1, Register compare) {
return (reg1.Name != null || compare.Name != null) && reg1.Name == compare.Name;
}
#endregion
#region PLC Type Enum Parsing
/// <summary>
/// Gets synonim names for a plc type enum
/// </summary>
/// <returns>All or just one of there are no synonims for the same <see cref="PlcType"/></returns>
public static string[] GetSynonims (this PlcType plcType) {
return Enum.GetNames(typeof(PlcType)).Where(n => Enum.Parse(typeof(PlcType), n).Equals(plcType)).ToArray();
}
/// <summary>
/// Converts the enum to a plc name string
/// </summary>
public static string ToName(this PlcType plcT) {
if (plcT == 0) return "Unknown";
if(!Enum.IsDefined(typeof(PlcType), plcT)) return "Unknown";
return ParsedPlcName.PlcDeconstruct(plcT.ToString()).ToString();
}
/// <summary>
/// Converts the enum to a decomposed <see cref="ParsedPlcName"/> struct
/// </summary>
public static ParsedPlcName ToNameDecompose(this PlcType plcT) {
if ((int)plcT == 0)
throw new NotSupportedException("No plc type found");
if (!Enum.IsDefined(typeof(PlcType), plcT)) return null;
return ParsedPlcName.PlcDeconstruct(plcT.ToString());
}
internal static ParsedPlcName ToNameDecompose(this string plcEnumString) {
return ParsedPlcName.PlcDeconstruct(plcEnumString);
}
/// <summary>
/// Checks if the PLC type is discontinued
/// </summary>
public static bool IsDiscontinued(this PlcType plcT) {
var memberInfos = plcT.GetType().GetMember(plcT.ToString());
var enumValueMemberInfo = memberInfos.FirstOrDefault(m => m.DeclaringType == plcT.GetType());
var valueAttributes = enumValueMemberInfo?.GetCustomAttributes(typeof(PlcLegacyAttribute), false);
if (valueAttributes != null) {
var found = valueAttributes.FirstOrDefault(x => x.GetType() == typeof(PlcLegacyAttribute));
if (found != null) return true;
}
return false;
}
#if DEBUG
internal static bool WasTestedLive(this PlcType plcT) {
var memberInfos = plcT.GetType().GetMember(plcT.ToString());
var enumValueMemberInfo = memberInfos.FirstOrDefault(m => m.DeclaringType == plcT.GetType());
var valueAttributes = enumValueMemberInfo?.GetCustomAttributes(typeof(PlcCodeTestedAttribute), false);
if (valueAttributes != null) {
var found = valueAttributes.FirstOrDefault(x => x.GetType() == typeof(PlcCodeTestedAttribute));
if (found != null) return true;
}
return false;
}
internal static bool IsEXRTPLC(this PlcType plcT) {
var memberInfos = plcT.GetType().GetMember(plcT.ToString());
var enumValueMemberInfo = memberInfos.FirstOrDefault(m => m.DeclaringType == plcT.GetType());
var valueAttributes = enumValueMemberInfo?.GetCustomAttributes(typeof(PlcEXRTAttribute), false);
if (valueAttributes != null) {
var found = valueAttributes.FirstOrDefault(x => x.GetType() == typeof(PlcEXRTAttribute));
if (found != null) return true;
}
return false;
}
#endif
#endregion
#region Mapping
/// <summary>
/// Maps the source object to target object.
/// </summary>
/// <typeparam name="T">Type of target object.</typeparam>
/// <typeparam name="TU">Type of source object.</typeparam>
/// <param name="target">Target object.</param>
/// <param name="source">Source object.</param>
/// <returns>Updated target object.</returns>
internal static T Map<T, TU>(this T target, TU source) {
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var tprops = target.GetType().GetProperties();
tprops.Where(x => x.CanWrite == true).ToList().ForEach(prop => {
// check whether source object has the the property
var sp = source.GetType().GetProperty(prop.Name);
if (sp != null) {
// if yes, copy the value to the matching property
var value = sp.GetValue(source, null);
target.GetType().GetProperty(prop.Name).SetValue(target, value, null);
}
});
var tfields = target.GetType().GetFields(flags);
tfields.ToList().ForEach(field => {
var sp = source.GetType().GetField(field.Name, flags);
if (sp != null) {
// if yes, copy the value to the matching property
var value = sp.GetValue(source);
target.GetType().GetField(field.Name, flags).SetValue(target, value);
}
});
return target;
}
#endregion
}
}

View File

@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
namespace MewtocolNet {
/// <summary>
/// A structure containing the PLC name parsed
/// </summary>
public class ParsedPlcName {
/// <summary>
/// Whole name of the PLC
/// </summary>
public string WholeName { get; internal set; } = "Unknown PLC";
/// <summary>
/// The family group of the PLC
/// </summary>
public string Group { get; internal set; } = "Unknown Group";
/// <summary>
/// The Memory size of the PLC
/// </summary>
public float Size { get; internal set; }
/// <summary>
/// The subtype strings of the plc
/// </summary>
public string[] SubTypes { get; internal set; } = new string[] { "N/A" };
/// <summary>
/// Typecode of the parsed string
/// </summary>
public int TypeCode { get; internal set; }
/// <summary>
/// The encoded name, same as enum name
/// </summary>
public string EncodedName { get; internal set; } = "Unknown";
/// <summary>
/// True if the model is discontinued
/// </summary>
public bool IsDiscontinuedModel { get; internal set; }
internal bool WasTestedLive { get; set; }
internal bool UsesEXRT { get; set; }
/// <inheritdoc/>
public override string ToString() => WholeName;
internal static ParsedPlcName PlcDeconstruct(string wholeStr) {
if(wholeStr == "Unknown") {
return new ParsedPlcName();
}
var reg = new Regex(@"(?<group>[A-Za-z0-9]*)_(?<size>[A-Za-z0-9]*)(?:__)?(?<additional>.*)");
var match = reg.Match(wholeStr);
if (match.Success) {
string groupStr = SanitizePlcEncodedString(match.Groups["group"].Value);
string sizeStr = SanitizePlcEncodedString(match.Groups["size"].Value);
float sizeFl = float.Parse(sizeStr.Replace("k", ""), NumberStyles.Float, CultureInfo.InvariantCulture);
string additionalStr = match.Groups["additional"].Value;
string[] subTypes = additionalStr.Split('_').Select(x => SanitizePlcEncodedString(x)).ToArray();
string wholeName = $"{groupStr} {sizeFl:0.##}k{(subTypes.Length > 0 ? " " : "")}{string.Join(",", subTypes)}";
if (string.IsNullOrEmpty(subTypes[0]))
subTypes = Array.Empty<string>();
int typeCode = 999;
bool discontinued = false, exrt = false, tested = false;
if(Enum.TryParse(wholeStr, out PlcType t)) {
typeCode = (int)t;
discontinued = t.IsDiscontinued();
#if Debug
exrt = t.IsEXRTPLC();
tested = t.WasTestedLive();
#endif
}
return new ParsedPlcName {
Group = groupStr,
Size = sizeFl,
SubTypes = subTypes,
WholeName = wholeName,
EncodedName = wholeStr,
TypeCode = typeCode,
IsDiscontinuedModel = discontinued,
UsesEXRT = exrt,
WasTestedLive = tested,
};
} else {
throw new FormatException($"The plc enum was not formatted correctly: {wholeStr}");
}
}
private static string SanitizePlcEncodedString(string input) {
input = input.Replace("d", "-");
input = input.Replace("c", ".");
input = input.Replace("s", "/");
return input;
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MewtocolNet.Helpers {
internal static class PlcBitConverter {
internal static string ToVersionNumber(IEnumerable<byte> inBytes, int startIndex = 0) {
return string.Join(".", inBytes.Skip(startIndex).Take(4).Reverse().Select(x => x.ToString()));
}
internal static DateTime ToDateTime(IEnumerable<byte> inBytes, int startIndex = 0) {
var offDate = new DateTime(2001, 01, 01);
var secondsOff = BitConverter.ToUInt32(inBytes.ToArray(), startIndex);
return offDate + TimeSpan.FromSeconds(secondsOff);
}
}
}

View File

@@ -0,0 +1,87 @@
using System;
using System.Collections;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace MewtocolNet {
public static class PlcFormat {
/// <summary>
/// Gets the TimeSpan as a PLC representation string fe.
/// <code>
/// T#1h10m30s20ms
/// </code>
/// </summary>
/// <param name="timespan"></param>
/// <returns></returns>
public static string ToPlcTime(this TimeSpan timespan) {
if (timespan == null || timespan == TimeSpan.Zero)
return $"T#0s";
StringBuilder sb = new StringBuilder("T#");
int millis = timespan.Milliseconds;
int seconds = timespan.Seconds;
int minutes = timespan.Minutes;
int hours = timespan.Hours;
int days = timespan.Days;
if (days > 0) sb.Append($"{days}d");
if (hours > 0) sb.Append($"{hours}h");
if (minutes > 0) sb.Append($"{minutes}m");
if (seconds > 0) sb.Append($"{seconds}s");
if (millis > 0) sb.Append($"{millis}ms");
return sb.ToString();
}
public static TimeSpan ParsePlcTime(string plcTimeFormat) {
var reg = new Regex(@"(?:T|t)#(?:(?<d>[0-9]{1,2})d)?(?:(?<h>[0-9]{1,2})h)?(?:(?<m>[0-9]{1,2})m)?(?:(?<s>[0-9]{1,2})s)?(?:(?<ms>[0-9]{1,3})ms)?");
var match = reg.Match(plcTimeFormat);
if (match.Success) {
var days = match.Groups["d"].Value;
var hours = match.Groups["h"].Value;
var minutes = match.Groups["m"].Value;
var seconds = match.Groups["s"].Value;
var milliseconds = match.Groups["ms"].Value;
TimeSpan retTime = TimeSpan.Zero;
if (!string.IsNullOrEmpty(days)) retTime += TimeSpan.FromDays(int.Parse(days));
if (!string.IsNullOrEmpty(hours)) retTime += TimeSpan.FromHours(int.Parse(hours));
if (!string.IsNullOrEmpty(minutes)) retTime += TimeSpan.FromMinutes(int.Parse(minutes));
if (!string.IsNullOrEmpty(seconds)) retTime += TimeSpan.FromSeconds(int.Parse(seconds));
if (!string.IsNullOrEmpty(milliseconds)) retTime += TimeSpan.FromMilliseconds(int.Parse(milliseconds));
if ((retTime.TotalMilliseconds % 10) != 0)
throw new NotSupportedException("Plc times can't have a millisecond component lower than 10ms");
return retTime;
}
return TimeSpan.Zero;
}
/// <summary>
/// Turns a bit array into a 0 and 1 string
/// </summary>
public static string ToBitString(this BitArray arr) {
var bits = new bool[arr.Length];
arr.CopyTo(bits, 0);
return string.Join("", bits.Select(x => x ? "1" : "0"));
}
}
}

224
MewtocolNet/IPlc.cs Normal file
View File

@@ -0,0 +1,224 @@
using MewtocolNet.Events;
using MewtocolNet.ProgramParsing;
using MewtocolNet.RegisterBuilding;
using MewtocolNet.RegisterBuilding.BuilderPatterns;
using MewtocolNet.Registers;
using MewtocolNet.UnderlyingRegisters;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
namespace MewtocolNet {
/// <summary>
/// Provides a interface for Panasonic PLCs
/// </summary>
public interface IPlc : IDisposable, INotifyPropertyChanged {
/// <summary>
/// Fires when the interface is fully connected to a PLC
/// </summary>
event PlcConnectionEventHandler Connected;
/// <summary>
/// Fires when a reconnect attempt was successfull
/// </summary>
event PlcConnectionEventHandler Reconnected;
/// <summary>
/// Fires when the interfaces makes a reconnect try to the PLC
/// </summary>
event PlcReconnectEventHandler ReconnectTryStarted;
/// <summary>
/// Fires when the plc/interface connection was fully closed
/// </summary>
event PlcConnectionEventHandler Disconnected;
/// <summary>
/// Fires when the value of a register changes
/// </summary>
event RegisterChangedEventHandler RegisterChanged;
/// <summary>
/// Plc mode was changed
/// </summary>
event PlcModeChangedEventHandler ModeChanged;
/// <summary>
/// The current connection state of the interface
/// </summary>
bool IsConnected { get; }
/// <summary>
/// This device is sending a message to the plc
/// </summary>
bool IsSending { get; }
/// <summary>
/// The current transmission speed in bytes per second
/// </summary>
int BytesPerSecondUpstream { get; }
/// <summary>
/// This device is receiving a message from the plc
/// </summary>
bool IsReceiving { get; }
/// <summary>
/// The current transmission speed in bytes per second
/// </summary>
int BytesPerSecondDownstream { get; }
/// <summary>
/// Current poller cycle duration
/// </summary>
int PollerCycleDurationMs { get; }
/// <summary>
/// Shorthand indicator if the plc is in RUN mode
/// </summary>
bool IsRunMode { get; }
/// <summary>
/// Currently queued message count
/// </summary>
int QueuedMessages { get; }
/// <summary>
/// Generic information about the connected PLC
/// </summary>
PLCInfo PlcInfo { get; }
/// <summary>
/// The station number of the PLC
/// </summary>
int StationNumber { get; }
/// <summary>
/// A connection info string
/// </summary>
string ConnectionInfo { get; }
/// <summary>
/// The initial connection timeout in milliseconds
/// </summary>
int ConnectTimeout { get; set; }
IEnumerable<IRegister> Registers { get; }
RBuildAnon Register { get; }
/// <summary>
/// Tries to establish a connection with the device asynchronously
/// </summary>
/// <param name="onConnected">A callback for excecuting something inside the plc connetion process</param>
/// <returns></returns>
Task<ConnectResult> ConnectAsync(Func<Task> onConnected = null);
/// <summary>
/// Disconnects the device from its current plc connection
/// and awaits the end of all asociated tasks
/// </summary>
Task DisconnectAsync();
/// <summary>
/// Disconnects the device from its current plc connection
/// </summary>
void Disconnect();
/// <summary>
/// Stops a running reconnect task
/// </summary>
void StopReconnecting();
/// <summary>
/// Adds a task to each reconnect cycle that is run before each individual try
/// </summary>
void WithReconnectTask(Func<int, Task> callback);
/// <summary>
/// Changes the PLCs operation mode to the given one
/// </summary>
/// <param name="setRun">True for RUN mode, false for PROG mode</param>
/// <returns>The success state of the write operation</returns>
Task<bool> SetOperationModeAsync(bool setRun);
/// <summary>
/// Toggles between RUN and PROG mode
/// </summary>
/// <returns>The success state of the write operation</returns>
Task<bool> ToggleOperationModeAsync();
/// <summary>
/// Restarts the plc program
/// </summary>
/// <returns>The success state of the write operation</returns>
Task<bool> RestartProgramAsync();
/// <summary>
/// Reads the program from the connected plc
/// </summary>
/// <returns></returns>
Task<PlcBinaryProgram> ReadProgramAsync();
/// <summary>
/// Factory resets the PLC, this includes the current program
/// and data in the EEPROM
/// </summary>
/// <returns></returns>
Task FactoryResetAsync();
/// <summary>
/// Use this to await the first poll iteration after connecting,
/// This also completes if the initial connection fails
/// </summary>
Task AwaitFirstDataCycleAsync();
/// <summary>
/// Runs a single poller cycle manually,
/// useful if you want to use a custom update frequency
/// </summary>
/// <returns>The number of inidvidual mewtocol commands sent</returns>
Task<int> UpdateAsync();
/// <summary>
/// Gets the connection info string
/// </summary>
string GetConnectionInfo();
/// <summary>
/// Gets a register from the plc by name
/// </summary>
IRegister GetRegister(string name);
/// <summary>
/// Gets all registers from the plc
/// </summary>
IEnumerable<IRegister> GetAllRegisters();
/// <summary>
/// Builds and adds registers to the device
/// </summary>
/// <param name="builder"></param>
void BuildRegisters(Action<RBuildMulti> builder);
/// <summary>
/// Clears all registers atached to the interface
/// </summary>
void ClearAllRegisters();
/// <summary>
/// Explains the register internal layout at this moment in time
/// </summary>
string Explain();
/// <summary>
/// A readonly list of the underlying memory areas
/// </summary>
IReadOnlyList<IMemoryArea> MemoryAreas { get; }
}
}

View File

@@ -0,0 +1,37 @@
using System;
using System.Net;
using System.Threading.Tasks;
namespace MewtocolNet {
/// <summary>
/// Provides a interface for Panasonic PLCs over a ethernet connection
/// </summary>
public interface IPlcEthernet : IPlc {
/// <summary>
/// The current IP of the PLC connection
/// </summary>
string IpAddress { get; }
/// <summary>
/// The current port of the PLC connection
/// </summary>
int Port { get; }
/// <summary>
/// The host ip endpoint, leave it null to use an automatic interface
/// </summary>
IPEndPoint HostEndpoint { get; }
/// <summary>
/// Configures the serial interface
/// </summary>
/// <param name="_ip">IP adress of the PLC</param>
/// <param name="_port">Port of the PLC</param>
/// <param name="_station">Station Number of the PLC</param>
void ConfigureConnection(string _ip, int _port = 9094, int _station = 1);
}
}

61
MewtocolNet/IPlcSerial.cs Normal file
View File

@@ -0,0 +1,61 @@
using System;
using System.IO.Ports;
using System.Threading.Tasks;
namespace MewtocolNet {
/// <summary>
/// Provides a interface for Panasonic PLCs over a serial port connection
/// </summary>
public interface IPlcSerial : IPlc {
/// <summary>
/// Port name of the serial port that this device is configured for
/// </summary>
string PortName { get; }
/// <summary>
/// The serial connection baud rate that this device is configured for
/// </summary>
int SerialBaudRate { get; }
/// <summary>
/// The serial connection data bits
/// </summary>
int SerialDataBits { get; }
/// <summary>
/// The serial connection parity
/// </summary>
Parity SerialParity { get; }
/// <summary>
/// The serial connection stop bits
/// </summary>
StopBits SerialStopBits { get; }
/// <summary>
/// Is RTS (Request to send) enabled?
/// </summary>
bool RtsEnabled { get; }
/// <summary>
/// Sets up the connection settings for the device
/// </summary>
/// <param name="_portName">Port name of COM port</param>
/// <param name="_baudRate">The serial connection baud rate</param>
/// <param name="_dataBits">The serial connection data bits</param>
/// <param name="_parity">The serial connection parity</param>
/// <param name="_stopBits">The serial connection stop bits</param>
/// <param name="_rtsEnable">Is RTS (Request to send) enabled?</param>
/// <param name="_station">The station number of the PLC</param>
void ConfigureConnection(string _portName, int _baudRate = 19200, int _dataBits = 8, Parity _parity = Parity.Odd, StopBits _stopBits = StopBits.One, bool _rtsEnable = true, int _station = 1);
/// <summary>
/// Tries to establish a connection with the device asynchronously
/// </summary>
Task<ConnectResult> ConnectAsync(Func<Task> callBack, Action onTryingConfig);
}
}

View File

@@ -0,0 +1,12 @@
namespace MewtocolNet {
internal enum CommandState {
Initial,
LineFeed,
RequestedNextFrame,
Complete
}
}

View File

@@ -0,0 +1,112 @@
using System;
using System.Diagnostics;
namespace MewtocolNet.Logging {
/// <summary>
/// Logging module for all PLCs
/// </summary>
public static class Logger {
/// <summary>
/// Sets the loglevel for the global logging module
/// </summary>
public static LogLevel LogLevel { get; set; }
/// <summary>
/// Defines the default output logger targets
/// </summary>
public static LoggerTargets DefaultTargets { get; set; } = LoggerTargets.Console;
internal static Action<DateTime, LogLevel, string> LogInvoked;
static Logger () {
var isConsoleApplication = Console.LargestWindowWidth != 0;
OnNewLogMessage((d, l, m) => {
if(isConsoleApplication && DefaultTargets.HasFlag(LoggerTargets.Console)) {
switch (l) {
case LogLevel.Error:
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"{d:hh:mm:ss:ff} {m}");
break;
case LogLevel.Info:
Console.WriteLine($"{d:hh:mm:ss:ff} {m}");
break;
case LogLevel.Change:
Console.ForegroundColor = ConsoleColor.DarkBlue;
Console.WriteLine($"{d:hh:mm:ss:ff} {m}");
break;
case LogLevel.Verbose:
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine($"{d:hh:mm:ss:ff} {m}");
break;
case LogLevel.Critical:
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine($"{d:hh:mm:ss:ff} {m}");
break;
}
Console.ResetColor();
}
});
LogInvoked += (d, l, m) => {
if (DefaultTargets.HasFlag(LoggerTargets.Trace)) {
Trace.WriteLine($"{d:hh:mm:ss:ff} {m}");
}
};
}
//for static calling purposes only
internal static void Start() { }
/// <summary>
/// Gets invoked whenever a new log message is ready
/// </summary>
public static void OnNewLogMessage(Action<DateTime, LogLevel, string> onMsg, LogLevel? maxLevel = null) {
if (maxLevel == null) maxLevel = LogLevel;
LogInvoked += (t, l, m) => {
if ((int)l <= (int)maxLevel) {
onMsg(t, l, m);
}
};
}
internal static void Log(string message, LogLevel loglevel, MewtocolInterface sender = null) {
if (sender == null) {
LogInvoked?.Invoke(DateTime.Now, loglevel, message);
} else {
LogInvoked?.Invoke(DateTime.Now, loglevel, $"[{sender.GetConnectionInfo()}] {message}");
}
}
internal static void LogError (string message, MewtocolInterface sender = null) => Log(message, LogLevel.Error, sender);
internal static void Log (string message, MewtocolInterface sender = null) => Log(message, LogLevel.Info, sender);
internal static void LogChange (string message, MewtocolInterface sender = null) => Log(message, LogLevel.Change, sender);
internal static void LogVerbose (string message, MewtocolInterface sender = null) => Log(message, LogLevel.Verbose, sender);
internal static void LogCritical (string message, MewtocolInterface sender = null) => Log(message, LogLevel.Critical, sender);
}
}

View File

@@ -1,14 +1,14 @@
using System; namespace MewtocolNet.Logging {
using System.Collections.Generic;
using System.Text;
namespace MewtocolNet.Logging {
/// <summary> /// <summary>
/// The loglevel of the logging module /// The loglevel of the logging module
/// </summary> /// </summary>
public enum LogLevel { public enum LogLevel {
/// <summary>
/// Logs nothing
/// </summary>
None = -1,
/// <summary> /// <summary>
/// Logs only errors /// Logs only errors
/// </summary> /// </summary>

View File

@@ -0,0 +1,12 @@
using System;
namespace MewtocolNet.Logging {
[Flags]
public enum LoggerTargets {
None = 0,
Console = 1,
Trace = 2,
}
}

498
MewtocolNet/Mewtocol.cs Normal file
View File

@@ -0,0 +1,498 @@
using MewtocolNet.RegisterAttributes;
using MewtocolNet.RegisterBuilding;
using MewtocolNet.RegisterBuilding.BuilderPatterns;
using MewtocolNet.SetupClasses;
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading.Tasks;
using static MewtocolNet.Mewtocol;
namespace MewtocolNet {
/// <summary>
/// Builder helper for mewtocol interfaces
/// </summary>
public static class Mewtocol {
#region Data Access
/// <summary>
/// Lists all usable COM port names
/// </summary>
/// <returns></returns>
public static IEnumerable<string> GetSerialPortNames () => SerialPort.GetPortNames();
/// <summary>
/// Lists all usable serial baud rates
/// </summary>
/// <returns></returns>
public static IEnumerable<int> GetUseableBaudRates() => Enum.GetValues(typeof(BaudRate)).Cast<BaudRate>().Select(x => (int)x);
/// <summary>
/// Lists all useable source endpoints of the device this is running on for usage with PLCs
/// </summary>
public static IEnumerable<IPEndPoint> GetSourceEndpoints() {
foreach (var netIf in GetUseableNetInterfaces()) {
var addressInfo = netIf.GetIPProperties().UnicastAddresses
.FirstOrDefault(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
yield return new IPEndPoint(addressInfo.Address, 9094);
}
}
/// <summary>
/// Lists all useable network interfaces of the device this is running on for usage with PLCs
/// </summary>
public static IEnumerable<NetworkInterface> GetUseableNetInterfaces() {
foreach (NetworkInterface netInterface in NetworkInterface.GetAllNetworkInterfaces()) {
bool isEthernet =
netInterface.NetworkInterfaceType == NetworkInterfaceType.Ethernet ||
netInterface.NetworkInterfaceType == NetworkInterfaceType.Ethernet3Megabit ||
netInterface.NetworkInterfaceType == NetworkInterfaceType.FastEthernetFx ||
netInterface.NetworkInterfaceType == NetworkInterfaceType.FastEthernetT ||
netInterface.NetworkInterfaceType == NetworkInterfaceType.GigabitEthernet;
bool isWlan = netInterface.NetworkInterfaceType == NetworkInterfaceType.Wireless80211;
bool isUsable = netInterface.OperationalStatus == OperationalStatus.Up;
if (!isUsable) continue;
if (!(isWlan || isEthernet)) continue;
IPInterfaceProperties ipProps = netInterface.GetIPProperties();
var hasUnicastInfo = ipProps.UnicastAddresses
.Any(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
if (!hasUnicastInfo) continue;
yield return netInterface;
}
}
#endregion
#region Interface building step 1
/// <summary>
/// Builds a ethernet based Mewtocol Interface
/// </summary>
/// <param name="ip"></param>
/// <param name="port"></param>
/// <param name="station">Plc station number 0xEE for direct communication</param>
/// <returns></returns>
public static PostInitEth<IPlcEthernet> Ethernet(string ip, int port = 9094, int station = 0xEE) {
var instance = new MewtocolInterfaceTcp();
instance.ConfigureConnection(ip, port, station);
return new PostInitEth<IPlcEthernet> {
intf = instance
};
}
/// <summary>
/// Builds a ethernet based Mewtocol Interface
/// </summary>
/// <param name="ip"></param>
/// <param name="port"></param>
/// <param name="station">Plc station number 0xEE for direct communication</param>
/// <returns></returns>
public static PostInitEth<IPlcEthernet> Ethernet(IPAddress ip, int port = 9094, int station = 0xEE) {
var instance = new MewtocolInterfaceTcp();
instance.ConfigureConnection(ip, port, station);
return new PostInitEth<IPlcEthernet> {
intf = instance
};
}
/// <summary>
/// Builds a serial port based Mewtocol Interface
/// </summary>
/// <param name="portName">System port name</param>
/// <param name="baudRate">Baud rate of the plc toolport</param>
/// <param name="dataBits">DataBits of the plc toolport</param>
/// <param name="parity">Parity rate of the plc toolport</param>
/// <param name="stopBits">Stop bits of the plc toolport</param>
/// <param name="rtsEnabled">Is RTS (Request to send) enabled?</param>
/// <param name="station">Plc station number 0xEE for direct communication</param>
/// <returns></returns>
public static PostInit<IPlcSerial> Serial(string portName, BaudRate baudRate = BaudRate._19200, DataBits dataBits = DataBits.Eight, Parity parity = Parity.Odd, StopBits stopBits = StopBits.One, bool rtsEnabled = true, int station = 0xEE) {
var instance = new MewtocolInterfaceSerial();
instance.ConfigureConnection(portName, (int)baudRate, (int)dataBits, parity, stopBits, rtsEnabled, station);
return new PostInit<IPlcSerial> {
intf = instance
};
}
/// <summary>
/// Builds a serial mewtocol interface that finds the correct settings for the given port name automatically
/// </summary>
/// <param name="portName"></param>
/// <param name="rtsEnabled">Is RTS (Request to send) enabled?</param>
/// <param name="station">Plc station number 0xEE for direct communication</param>
/// <returns></returns>
public static PostInit<IPlcSerial> SerialAuto(string portName, bool rtsEnabled = true, int station = 0xEE) {
var instance = new MewtocolInterfaceSerial();
instance.ConfigureConnection(portName, station, rtsEnable: rtsEnabled);
instance.ConfigureConnectionAuto();
return new PostInit<IPlcSerial> {
intf = instance
};
}
#endregion
#region Interface building step 2
public class PollLevelConfigurator {
internal Dictionary<int, PollLevelConfig> levelConfigs = new Dictionary<int, PollLevelConfig>();
/// <summary>
/// Sets the poll level for the given key
/// </summary>
/// <param name="level">The level to reference</param>
/// <param name="interval">Delay between poll requests</param>
public PollLevelConfigurator SetLevel(int level, TimeSpan interval) {
if (level == PollLevel.Always || level == PollLevel.Never || level == PollLevel.FirstIteration)
throw new NotSupportedException("The poll level is reserved for the library");
if (!levelConfigs.ContainsKey(level)) {
levelConfigs.Add(level, new PollLevelConfig {
delay = interval,
});
} else {
throw new NotSupportedException("Can't set poll levels multiple times");
}
return this;
}
public PollLevelConfigurator SetLevel(int level, int skipNth) {
if (level == PollLevel.Always || level == PollLevel.Never || level == PollLevel.FirstIteration)
throw new NotSupportedException("The poll level is reserved for the library");
if (!levelConfigs.ContainsKey(level)) {
levelConfigs.Add(level, new PollLevelConfig {
skipNth = skipNth,
});
} else {
throw new NotSupportedException("Can't set poll levels multiple times");
}
return this;
}
}
public class RegCollector {
internal List<RegisterCollection> collections = new List<RegisterCollection>();
public T AddCollection<T>(T collection) where T : RegisterCollection {
collections.Add(collection);
return collection;
}
public T AddCollection<T>() where T : RegisterCollection {
var instance = (RegisterCollection)Activator.CreateInstance(typeof(T));
collections.Add(instance);
return (T)instance;
}
}
public class PostInitEth<T> : PostInit<T> {
/// <summary>
/// Sets the source of the outgoing ethernet connection
/// </summary>
public PostInit<T> FromSource (IPEndPoint endpoint) {
if(endpoint == null)
throw new ArgumentNullException("Endpoint can't be null", nameof(endpoint));
if(intf is MewtocolInterfaceTcp imew) {
imew.HostEndpoint = endpoint;
}
return this;
}
/// <summary>
/// Sets the source of the outgoing ethernet connection
/// </summary>
/// <param name="ip">IP address of the source interface (Format: 127.0.0.1)</param>
/// <param name="port">Port of the source interface</param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public PostInit<T> FromSource(string ip, int port) {
if (intf is MewtocolInterfaceTcp imew) {
if(port < IPEndPoint.MinPort)
throw new ArgumentException($"Source port cant be smaller than {IPEndPoint.MinPort}", nameof(port));
if (port > IPEndPoint.MaxPort)
throw new ArgumentException($"Source port cant be larger than {IPEndPoint.MaxPort}", nameof(port));
if (!IPAddress.TryParse(ip, out var ipParsed))
throw new ArgumentException("Failed to parse the source IP", nameof(ip));
imew.HostEndpoint = new IPEndPoint(ipParsed, port);
}
return this;
}
}
public class PostInit<T> {
internal T intf;
/// <summary>
/// Attaches a auto poller to the interface that reads all registers
/// cyclic
/// </summary>
/// <returns></returns>
public PostInit<T> WithPoller() {
if (intf is MewtocolInterface imew) {
imew.usePoller = true;
}
return this;
}
/// <summary>
/// General setting for the memory manager
/// </summary>
public PostInit<T> WithInterfaceSettings(Action<InterfaceSettings> settings) {
var res = new InterfaceSettings();
settings.Invoke(res);
if (res.MaxOptimizationDistance < 0)
throw new NotSupportedException($"A value lower than 0 is not allowed for " +
$"{nameof(InterfaceSettings.MaxOptimizationDistance)}");
if (res.MaxDataBlocksPerWrite < 1)
throw new NotSupportedException($"A value lower than 1 is not allowed for " +
$"{nameof(InterfaceSettings.MaxDataBlocksPerWrite)}");
if (intf is MewtocolInterface imew) {
imew.memoryManager.maxOptimizationDistance = res.MaxOptimizationDistance;
imew.memoryManager.pollLevelOrMode = res.PollLevelOverwriteMode;
imew.maxDataBlocksPerWrite = res.MaxDataBlocksPerWrite;
imew.heartbeatIntervalMs = res.HeartbeatIntervalMs;
imew.tryReconnectAttempts = res.TryReconnectAttempts;
imew.tryReconnectDelayMs = res.TryReconnectDelayMs;
imew.alwaysGetMetadata = res.AlwaysGetMetadata;
}
return this;
}
/// <summary>
/// A builder for poll custom levels
/// </summary>
public PostInit<T> WithCustomPollLevels(Action<PollLevelConfigurator> levels) {
var res = new PollLevelConfigurator();
levels.Invoke(res);
if (intf is MewtocolInterface imew) {
imew.memoryManager.pollLevelConfigs = res.levelConfigs;
}
return this;
}
/// <summary>
/// A builder for attaching register collections
/// </summary>
public PostInit<T> WithRegisterCollections(Action<RegCollector> collector) {
try {
var res = new RegCollector();
collector.Invoke(res);
if (intf is MewtocolInterface imew) {
imew.WithRegisterCollections(res.collections);
}
return this;
} catch {
throw;
}
}
/// <summary>
/// A builder for attaching register collections
/// </summary>
public PostInit<T> WithRegisters(Action<RBuildMulti> builder) {
try {
var plc = (MewtocolInterface)(object)intf;
var regBuilder = new RBuildMulti(plc);
builder.Invoke(regBuilder);
plc.AddRegisters(regBuilder.assembler.assembled.ToArray());
return this;
} catch {
throw;
}
}
/// <summary>
/// Adds a task to each reconnect cycle that is run before each individual try
/// </summary>
public PostInit<T> WithReconnectTask(Func<int, Task> callback) {
var plc = (MewtocolInterface)(object)intf;
plc.onBeforeReconnectTryTask = callback;
return this;
}
/// <summary>
/// Adds a task to each heartbeat cycle that is run before each individual cycle request
/// </summary>
public EndInitSetup<T> WithHeartbeatTask(Func<IPlc,Task> heartBeatAsync, bool executeInProg = false) {
try {
var plc = (MewtocolInterface)(object)this.intf;
plc.heartbeatCallbackTask = heartBeatAsync;
plc.execHeartBeatCallbackTaskInProg = executeInProg;
return new EndInitSetup<T> {
postInit = this,
};
} catch {
throw;
}
}
/// <summary>
/// Repeats the passed method each time the hearbeat is triggered,
/// use
/// </summary>
/// <param name="heartBeatAsync"></param>
/// <returns></returns>
public EndInitSetup<T> WithHeartbeatTask(Func<Task> heartBeatAsync, bool executeInProg = false) => WithHeartbeatTask(heartBeatAsync, executeInProg);
/// <summary>
/// Disables all heartbeat tasks
/// </summary>
/// <returns></returns>
public EndInitSetup<T> DisableHeartBeat() {
try {
var plc = (MewtocolInterface)(object)this.intf;
plc.disableHeartbeat = true;
return new EndInitSetup<T> {
postInit = this,
};
} catch {
throw;
}
}
/// <summary>
/// Builds and returns the final plc interface
/// </summary>
public T Build() => (T)(object)((MewtocolInterface)(object)intf).Build();
}
#endregion
#region Interface building step 4
public class EndInitSetup<T> {
internal PostInit<T> postInit;
/// <summary>
/// Builds and returns the final plc interface
/// </summary>
public T Build() => (T)(object)((MewtocolInterface)(object)postInit.intf).Build();
}
#endregion
}
}

View File

@@ -1,54 +0,0 @@
using System;
namespace MewtocolNet.Registers {
public partial class CpuInfo {
public CpuType Cputype { get; set; }
public int ProgramCapacity { get; set; }
public string CpuVersion { get; set; }
public static CpuInfo BuildFromHexString (string _cpuType, string _cpuVersion, string _progCapacity) {
CpuInfo retInf = new CpuInfo();
switch (_cpuType) {
case "02":
retInf.Cputype = CpuType.FP5_16K;
break;
case "03":
retInf.Cputype = CpuType.FP3_C_10K;
break;
case "04":
retInf.Cputype = CpuType.FP1_M_0_9K;
break;
case "05":
retInf.Cputype = CpuType.FP0_FP1_2_7K;
break;
case "06":
retInf.Cputype = CpuType.FP0_FP1_5K_10K;
break;
case "12":
retInf.Cputype = CpuType.FP5_24K;
break;
case "13":
retInf.Cputype = CpuType.FP3_C_16K;
break;
case "20":
retInf.Cputype = CpuType.FP_Sigma_X_H_30K_60K_120K;
break;
case "50":
retInf.Cputype = CpuType.FP2_16K_32K;
break;
}
retInf.ProgramCapacity = Convert.ToInt32(_progCapacity);
retInf.CpuVersion = _cpuVersion.Insert(1, ".");
return retInf;
}
}
}

View File

@@ -1,366 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MewtocolNet.Logging;
using MewtocolNet.Registers;
namespace MewtocolNet {
/// <summary>
/// The PLC com interface class
/// </summary>
public partial class MewtocolInterface {
internal event Action PolledCycle;
internal CancellationTokenSource cTokenAutoUpdater;
internal bool ContinousReaderRunning;
internal bool usePoller = false;
#region Register Polling
internal void KillPoller () {
ContinousReaderRunning = false;
cTokenAutoUpdater?.Cancel();
}
/// <summary>
/// Attaches a continous reader that reads back the Registers and Contacts
/// </summary>
internal void AttachPoller () {
if (ContinousReaderRunning) return;
cTokenAutoUpdater = new CancellationTokenSource();
Logger.Log("Poller is attaching", LogLevel.Info, this);
try {
Task.Factory.StartNew(async () => {
var plcinf = await GetPLCInfoAsync();
if (plcinf == null) {
Logger.Log("PLC not reachable, stopping logger", LogLevel.Info, this);
return;
}
PolledCycle += MewtocolInterface_PolledCycle;
void MewtocolInterface_PolledCycle () {
StringBuilder stringBuilder = new StringBuilder();
foreach (var reg in GetAllRegisters()) {
string address = $"{reg.GetRegisterString()}{reg.GetStartingMemoryArea()}".PadRight(8, (char)32);
stringBuilder.AppendLine($"{address}{(reg.Name != null ? $" ({reg.Name})" : "")}: {reg.GetValueString()}");
}
Logger.Log($"Registers loaded are: \n" +
$"--------------------\n" +
$"{stringBuilder.ToString()}" +
$"--------------------",
LogLevel.Verbose, this);
Logger.Log("Logger did its first cycle successfully", LogLevel.Info, this);
PolledCycle -= MewtocolInterface_PolledCycle;
}
ContinousReaderRunning = true;
int getPLCinfoCycleCount = 0;
while (ContinousReaderRunning) {
//do priority tasks first
if (PriorityTasks.Count > 0) {
await PriorityTasks.FirstOrDefault(x => !x.IsCompleted);
} else if (getPLCinfoCycleCount > 25) {
await GetPLCInfoAsync();
getPLCinfoCycleCount = 0;
}
foreach (var registerPair in Registers) {
var reg = registerPair.Value;
if (reg is NRegister<short> shortReg) {
var lastVal = shortReg.Value;
var readout = (await ReadNumRegister(shortReg)).Register.Value;
if (lastVal != readout) {
shortReg.LastValue = readout;
InvokeRegisterChanged(shortReg);
shortReg.TriggerNotifyChange();
}
}
if (reg is NRegister<ushort> ushortReg) {
var lastVal = ushortReg.Value;
var readout = (await ReadNumRegister(ushortReg)).Register.Value;
if (lastVal != readout) {
ushortReg.LastValue = readout;
InvokeRegisterChanged(ushortReg);
ushortReg.TriggerNotifyChange();
}
}
if (reg is NRegister<int> intReg) {
var lastVal = intReg.Value;
var readout = (await ReadNumRegister(intReg)).Register.Value;
if (lastVal != readout) {
intReg.LastValue = readout;
InvokeRegisterChanged(intReg);
intReg.TriggerNotifyChange();
}
}
if (reg is NRegister<uint> uintReg) {
var lastVal = uintReg.Value;
var readout = (await ReadNumRegister(uintReg)).Register.Value;
if (lastVal != readout) {
uintReg.LastValue = readout;
InvokeRegisterChanged(uintReg);
uintReg.TriggerNotifyChange();
}
}
if (reg is NRegister<float> floatReg) {
var lastVal = floatReg.Value;
var readout = (await ReadNumRegister(floatReg)).Register.Value;
if (lastVal != readout) {
floatReg.LastValue = readout;
InvokeRegisterChanged(floatReg);
floatReg.TriggerNotifyChange();
}
}
if (reg is NRegister<TimeSpan> tsReg) {
var lastVal = tsReg.Value;
var readout = (await ReadNumRegister(tsReg)).Register.Value;
if (lastVal != readout) {
tsReg.LastValue = readout;
InvokeRegisterChanged(tsReg);
tsReg.TriggerNotifyChange();
}
}
if (reg is BRegister boolReg) {
var lastVal = boolReg.Value;
var readout = (await ReadBoolRegister(boolReg)).Register.Value;
if (lastVal != readout) {
boolReg.LastValue = readout;
InvokeRegisterChanged(boolReg);
boolReg.TriggerNotifyChange();
}
}
if (reg is SRegister stringReg) {
var lastVal = stringReg.Value;
var readout = (await ReadStringRegister(stringReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(stringReg);
stringReg.TriggerNotifyChange();
}
}
}
getPLCinfoCycleCount++;
//invoke cycle polled event
InvokePolledCycleDone();
}
}, cTokenAutoUpdater.Token);
} catch (TaskCanceledException) { }
}
#endregion
#region Register Adding
/// <summary>
/// Adds a PLC memory register to the watchlist <para/>
/// The registers can be read back by attaching <see cref="WithPoller"/>
/// </summary>
/// <param name="_address">The address of the register in the PLCs memory</param>
/// <param name="_type">
/// The memory area type
/// <para>X = Physical input area (bool)</para>
/// <para>Y = Physical input area (bool)</para>
/// <para>R = Internal relay area (bool)</para>
/// <para>DT = Internal data area (short/ushort)</para>
/// <para>DDT = Internal relay area (int/uint)</para>
/// </param>
/// <param name="_name">A naming definition for QOL, doesn't effect PLC and is optional</param>
public void AddRegister (int _address, RegisterType _type, string _name = null) {
//as number registers
if (_type == RegisterType.DT_short) {
Registers.Add(_address, new NRegister<short>(_address, _name));
return;
}
if (_type == RegisterType.DT_ushort) {
Registers.Add(_address, new NRegister<ushort>(_address, _name));
return;
}
if (_type == RegisterType.DDT_int) {
Registers.Add(_address, new NRegister<int>(_address, _name));
return;
}
if (_type == RegisterType.DDT_uint) {
Registers.Add(_address, new NRegister<uint>(_address, _name));
return;
}
if (_type == RegisterType.DDT_float) {
Registers.Add(_address, new NRegister<float>(_address, _name));
return;
}
//as bool registers
Registers.Add(_address, new BRegister(_address, _type, _name));
}
/// <summary>
/// Adds a PLC memory register to the watchlist <para/>
/// The registers can be read back by attaching <see cref="WithPoller"/>
/// </summary>
/// <param name="_spAddress">The special address of the register in the PLCs memory</param>
/// <param name="_type">
/// The memory area type
/// <para>X = Physical input area (bool)</para>
/// <para>Y = Physical input area (bool)</para>
/// <para>R = Internal relay area (bool)</para>
/// <para>DT = Internal data area (short/ushort)</para>
/// <para>DDT = Internal relay area (int/uint)</para>
/// </param>
/// <param name="_name">A naming definition for QOL, doesn't effect PLC and is optional</param>
public void AddRegister (SpecialAddress _spAddress, RegisterType _type, string _name = null) {
//as bool registers
Registers.Add((int)_spAddress, new BRegister(_spAddress, _type, _name));
}
/// <summary>
/// Adds a PLC memory register to the watchlist <para/>
/// The registers can be read back by attaching <see cref="WithPoller"/>
/// </summary>
/// <typeparam name="T">
/// The type of the register translated from C# to IEC 61131-3 types
/// <para>C# ------ IEC</para>
/// <para>short => INT/WORD</para>
/// <para>ushort => UINT</para>
/// <para>int => DOUBLE</para>
/// <para>uint => UDOUBLE</para>
/// <para>float => REAL</para>
/// <para>string => STRING</para>
/// </typeparam>
/// <param name="_name">A naming definition for QOL, doesn't effect PLC and is optional</param>
/// <param name="_address">The address of the register in the PLCs memory</param>
/// <param name="_length">The length of the string (Can be ignored for other types)</param>
public void AddRegister<T>(int _address, int _length = 1, string _name = null, bool _isBitwise = false) {
Type regType = typeof(T);
if (regType != typeof(string) && _length != 1) {
throw new NotSupportedException($"_lenght parameter only allowed for register of type string");
}
if (Registers.Any(x => x.Key == _address)) {
throw new NotSupportedException($"Cannot add a register multiple times, " +
$"make sure that all register attributes or AddRegister assignments have different adresses.");
}
if (regType == typeof(short)) {
Registers.Add(_address, new NRegister<short>(_address, _name, _isBitwise));
} else if (regType == typeof(ushort)) {
Registers.Add(_address, new NRegister<ushort>(_address, _name));
} else if (regType == typeof(int)) {
Registers.Add(_address, new NRegister<int>(_address, _name, _isBitwise));
} else if (regType == typeof(uint)) {
Registers.Add(_address, new NRegister<uint>(_address, _name));
} else if (regType == typeof(float)) {
Registers.Add(_address, new NRegister<float>(_address, _name));
} else if (regType == typeof(string)) {
Registers.Add(_address, new SRegister(_address, _length, _name));
} else if (regType == typeof(TimeSpan)) {
Registers.Add(_address, new NRegister<TimeSpan>(_address, _name));
} else if (regType == typeof(bool)) {
Registers.Add(_address, new BRegister(_address, RegisterType.R, _name));
} else {
throw new NotSupportedException($"The type {regType} is not allowed for Registers \n" +
$"Allowed are: short, ushort, int, uint, float and string");
}
}
#endregion
#region Register accessing
/// <summary>
/// Gets a register that was added by its name
/// </summary>
/// <returns></returns>
public Register GetRegister (string name) {
return Registers.FirstOrDefault(x => x.Value.Name == name).Value;
}
/// <summary>
/// Gets a register that was added by its name
/// </summary>
/// <typeparam name="T">The type of register</typeparam>
/// <returns>A casted register or the <code>default</code> value</returns>
public T GetRegister<T> (string name) where T : Register {
try {
var reg = Registers.FirstOrDefault(x => x.Value.Name == name);
return reg.Value as T;
} catch (InvalidCastException) {
return default(T);
}
}
#endregion
#region Register Reading
/// <summary>
/// Gets a list of all added registers
/// </summary>
public List<Register> GetAllRegisters () {
return Registers.Values.ToList();
}
#endregion
#region Event Invoking
internal void InvokeRegisterChanged (Register reg) {
RegisterChanged?.Invoke(reg);
}
internal void InvokePolledCycleDone () {
PolledCycle?.Invoke();
}
#endregion
}
}

View File

@@ -1,43 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MewtocolNet.Logging {
/// <summary>
/// Logging module for all PLCs
/// </summary>
public static class Logger {
/// <summary>
/// Sets the loglevel for the logger module
/// </summary>
public static LogLevel LogLevel { get; set; }
internal static Action<DateTime, string> LogInvoked;
/// <summary>
/// Gets invoked whenever a new log message is ready
/// </summary>
public static void OnNewLogMessage (Action<DateTime, string> onMsg) {
LogInvoked += (t, m) => {
onMsg(t, m);
};
}
internal static void Log (string message, LogLevel loglevel, MewtocolInterface sender = null) {
if ((int)loglevel <= (int)LogLevel) {
if (sender == null) {
LogInvoked?.Invoke(DateTime.Now, message);
} else {
LogInvoked?.Invoke(DateTime.Now, $"[{sender.GetConnectionPortInfo()}] {message}");
}
}
}
}
}

View File

@@ -1,200 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using MewtocolNet.Registers;
using System.Collections;
namespace MewtocolNet {
/// <summary>
/// Contains helper methods
/// </summary>
public static class MewtocolHelpers {
/// <summary>
/// Turns a bit array into a 0 and 1 string
/// </summary>
public static string ToBitString (this BitArray arr) {
var bits = new bool[arr.Length];
arr.CopyTo(bits, 0);
return string.Join("", bits.Select(x => x ? "1" : "0"));
}
internal static byte[] ToHexASCIIBytes (this string _str) {
ASCIIEncoding ascii = new ASCIIEncoding();
byte[] bytes = ascii.GetBytes(_str.ToUpper());
return bytes;
}
internal static string BuildBCCFrame (this string asciiArr) {
Encoding ae = Encoding.ASCII;
byte[] b = ae.GetBytes(asciiArr);
byte xorTotalByte = 0;
for(int i = 0; i < b.Length; i++)
xorTotalByte ^= b[i];
return asciiArr.Insert(asciiArr.Length, xorTotalByte.ToString("X2"));
}
internal static byte[] ParseDTBytes (this string _onString ,int _blockSize = 4) {
var res = new Regex(@"\%([0-9]{2})\$RD(.{"+_blockSize+"})").Match(_onString);
if(res.Success) {
string val = res.Groups[2].Value;
return val.HexStringToByteArray();
}
return null;
}
internal static string ParseDTByteString (this string _onString, int _blockSize = 4) {
if (_onString == null)
return null;
var res = new Regex(@"\%([0-9]{2})\$RD(.{" + _blockSize + "})").Match(_onString);
if (res.Success) {
string val = res.Groups[2].Value;
return val;
}
return null;
}
internal static bool? ParseRCSingleBit (this string _onString, int _blockSize = 4) {
var res = new Regex(@"\%([0-9]{2})\$RC(.)").Match(_onString);
if (res.Success) {
string val = res.Groups[2].Value;
return val == "1";
}
return null;
}
internal static string ParseDTString (this string _onString) {
var res = new Regex(@"\%([0-9]{2})\$RD.{8}(.*)...").Match(_onString);
if(res.Success) {
string val = res.Groups[2].Value;
return val.GetStringFromAsciiHex().Trim();
}
return null;
}
internal static string ReverseByteOrder (this string _onString) {
if(_onString == null) return null;
//split into 2 chars
var stringBytes = _onString.SplitInParts(2).ToList();
stringBytes.Reverse();
return string.Join("", stringBytes);
}
internal static IEnumerable<String> SplitInParts (this string s, int partLength) {
if (s == null)
throw new ArgumentNullException(nameof(s));
if (partLength <= 0)
throw new ArgumentException("Part length has to be positive.", nameof(partLength));
for (var i = 0; i < s.Length; i += partLength)
yield return s.Substring(i, Math.Min(partLength, s.Length - i));
}
internal static string BuildDTString (this string _inString, short _stringReservedSize) {
StringBuilder sb = new StringBuilder();
//clamp string lenght
if (_inString.Length > _stringReservedSize) {
_inString = _inString.Substring(0, _stringReservedSize);
}
//actual string content
var hexstring = _inString.GetAsciiHexFromString();
var sizeBytes = BitConverter.GetBytes((short)(hexstring.Length / 2)).ToHexString();
if (hexstring.Length >= 2) {
var remainderBytes = (hexstring.Length / 2) % 2;
if (remainderBytes != 0) {
hexstring += "20";
}
}
var reservedSizeBytes = BitConverter.GetBytes(_stringReservedSize).ToHexString();
//reserved string count bytes
sb.Append(reservedSizeBytes);
//string count actual bytes
sb.Append(sizeBytes);
sb.Append(hexstring);
return sb.ToString();
}
internal static string GetStringFromAsciiHex (this string input) {
if (input.Length % 2 != 0)
throw new ArgumentException("input not a hex string");
byte[] bytes = new byte[input.Length / 2];
for (int i = 0; i < input.Length; i += 2) {
String hex = input.Substring(i, 2);
bytes[i/2] = Convert.ToByte(hex, 16);
}
return Encoding.ASCII.GetString(bytes);
}
internal static string GetAsciiHexFromString (this string input) {
var bytes = new ASCIIEncoding().GetBytes(input);
return bytes.ToHexString();
}
internal static byte[] HexStringToByteArray(this string hex) {
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
internal static string ToHexString (this byte[] arr) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < arr.Length; i++) {
byte b = arr[i];
sb.Append(b.ToString("X2"));
}
return sb.ToString();
}
internal static byte[] BigToMixedEndian (this byte[] arr) {
List<byte> oldBL = new List<byte>(arr);
List<byte> tempL = new List<byte>();
//make the input list even
if(arr.Length % 2 != 0)
oldBL.Add((byte)0);
for (int i = 0; i < oldBL.Count; i+=2) {
byte firstByte = oldBL[i];
byte lastByte = oldBL[i + 1];
tempL.Add(lastByte);
tempL.Add(firstByte);
}
return tempL.ToArray();
}
}
}

View File

@@ -1,593 +0,0 @@
using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using MewtocolNet.Registers;
using MewtocolNet.RegisterAttributes;
using MewtocolNet.Logging;
using System.Collections;
using System.Diagnostics;
namespace MewtocolNet {
/// <summary>
/// The PLC com interface class
/// </summary>
public partial class MewtocolInterface {
/// <summary>
/// Gets triggered when the PLC connection was established
/// </summary>
public event Action<PLCInfo> Connected;
/// <summary>
/// Gets triggered when a registered data register changes its value
/// </summary>
public event Action<Register> RegisterChanged;
/// <summary>
/// The current connection state of the interface
/// </summary>
public bool IsConnected { get; private set; }
/// <summary>
/// Generic information about the connected PLC
/// </summary>
public PLCInfo PlcInfo { get; private set; }
/// <summary>
/// The registered data registers of the PLC
/// </summary>
public Dictionary<int, Register> Registers { get; set; } = new Dictionary<int, Register>();
private string ip;
private int port;
private int stationNumber;
/// <summary>
/// The current IP of the PLC connection
/// </summary>
public string IpAddress => ip;
/// <summary>
/// The current port of the PLC connection
/// </summary>
public int Port => port;
/// <summary>
/// The station number of the PLC
/// </summary>
public int StationNumber => stationNumber;
internal List<Task> PriorityTasks { get; set; } = new List<Task>();
#region Initialization
/// <summary>
/// Builds a new Interfacer for a PLC
/// </summary>
/// <param name="_ip">IP adress of the PLC</param>
/// <param name="_port">Port of the PLC</param>
/// <param name="_station">Station Number of the PLC</param>
public MewtocolInterface (string _ip, int _port = 9094, int _station = 1) {
ip = _ip;
port = _port;
stationNumber = _station;
Connected += MewtocolInterface_Connected;
void MewtocolInterface_Connected (PLCInfo obj) {
if (usePoller)
AttachPoller();
IsConnected = true;
}
RegisterChanged += (o) => {
string address = $"{o.GetRegisterString()}{o.MemoryAdress}".PadRight(5, (char)32); ;
Logger.Log($"{address} " +
$"{(o.Name != null ? $"({o.Name}) " : "")}" +
$"changed to \"{o.GetValueString()}\"", LogLevel.Change, this);
};
}
#endregion
#region Setup
/// <summary>
/// Trys to connect to the PLC by the IP given in the constructor
/// </summary>
/// <param name="OnConnected">
/// Gets called when a connection with a PLC was established
/// <para/>
/// If <see cref="WithPoller"/> is used it waits for the first data receive cycle to complete
/// </param>
/// <param name="OnFailed">Gets called when an error or timeout during connection occurs</param>
/// <returns></returns>
public async Task<MewtocolInterface> ConnectAsync (Action<PLCInfo> OnConnected = null, Action OnFailed = null) {
Logger.Log("Connecting to PLC...", LogLevel.Info, this);
var plcinf = await GetPLCInfoAsync();
if (plcinf != null) {
Logger.Log("Connected", LogLevel.Info, this);
Logger.Log($"\n\n{plcinf.ToString()}\n\n", LogLevel.Verbose, this);
Connected?.Invoke(plcinf);
if (OnConnected != null) {
if (!usePoller) {
OnConnected(plcinf);
return this;
}
PolledCycle += OnPollCycleDone;
void OnPollCycleDone () {
OnConnected(plcinf);
PolledCycle -= OnPollCycleDone;
}
}
} else {
if (OnFailed != null) {
OnFailed();
Logger.Log("Initial connection failed", LogLevel.Info, this);
}
}
return this;
}
/// <summary>
/// Attaches a poller to the interface that continously
/// polls the registered data registers and writes the values to them
/// </summary>
public MewtocolInterface WithPoller () {
usePoller = true;
return this;
}
#endregion
#region Register Collection
/// <summary>
/// Attaches a register collection object to
/// the interface that can be updated automatically.
/// <para/>
/// Just create a class inheriting from <see cref="RegisterCollectionBase"/>
/// and assert some propertys with the custom <see cref="RegisterAttribute"/>.
/// </summary>
/// <param name="collection">A collection inherting the <see cref="RegisterCollectionBase"/> class</param>
public MewtocolInterface WithRegisterCollection (RegisterCollectionBase collection) {
collection.PLCInterface = this;
var props = collection.GetType().GetProperties();
foreach (var prop in props) {
var attributes = prop.GetCustomAttributes(true);
string propName = prop.Name;
foreach (var attr in attributes) {
if(attr is RegisterAttribute cAttribute) {
if (prop.PropertyType == typeof(bool) && cAttribute.AssignedBitIndex == -1) {
if (cAttribute.SpecialAddress == SpecialAddress.None) {
AddRegister(cAttribute.MemoryArea, cAttribute.RegisterType, _name: propName);
} else {
AddRegister(cAttribute.SpecialAddress, cAttribute.RegisterType, _name: propName);
}
}
if (prop.PropertyType == typeof(short)) {
AddRegister<short>(cAttribute.MemoryArea, _name: propName);
}
if (prop.PropertyType == typeof(ushort)) {
AddRegister<ushort>(cAttribute.MemoryArea, _name: propName);
}
if (prop.PropertyType == typeof(int)) {
AddRegister<int>(cAttribute.MemoryArea, _name: propName);
}
if (prop.PropertyType == typeof(uint)) {
AddRegister<uint>(cAttribute.MemoryArea, _name: propName);
}
if (prop.PropertyType == typeof(float)) {
AddRegister<float>(cAttribute.MemoryArea, _name: propName);
}
if (prop.PropertyType == typeof(string)) {
AddRegister<string>(cAttribute.MemoryArea, cAttribute.StringLength, _name: propName);
}
//read number as bit array
if (prop.PropertyType == typeof(BitArray)) {
if(cAttribute.BitCount == BitCount.B16) {
AddRegister<short>(cAttribute.MemoryArea, _name: propName, _isBitwise: true);
} else {
AddRegister<int>(cAttribute.MemoryArea, _name: propName, _isBitwise: true);
}
}
//read number as bit array by invdividual properties
if (prop.PropertyType == typeof(bool) && cAttribute.AssignedBitIndex != -1) {
if (cAttribute.BitCount == BitCount.B16) {
AddRegister<short>(cAttribute.MemoryArea, _name: propName, _isBitwise: true);
} else {
AddRegister<int>(cAttribute.MemoryArea, _name: propName, _isBitwise: true);
}
}
if (prop.PropertyType == typeof(TimeSpan)) {
AddRegister<TimeSpan>(cAttribute.MemoryArea, _name: propName);
}
}
}
}
RegisterChanged += (reg) => {
var foundToUpdate = props.FirstOrDefault(x => x.Name == reg.Name);
if (foundToUpdate != null) {
var foundAttributes = foundToUpdate.GetCustomAttributes(true);
var foundAttr = foundAttributes.FirstOrDefault(x => x.GetType() == typeof(RegisterAttribute));
if (foundAttr == null)
return;
var registerAttr = (RegisterAttribute)foundAttr;
//check if bit parse mode
if (registerAttr.AssignedBitIndex == -1) {
//setting back booleans
if (foundToUpdate.PropertyType == typeof(bool)) {
foundToUpdate.SetValue(collection, ((BRegister)reg).Value);
}
//setting back numbers
if (foundToUpdate.PropertyType == typeof(short)) {
foundToUpdate.SetValue(collection, ((NRegister<short>)reg).Value);
}
if (foundToUpdate.PropertyType == typeof(ushort)) {
foundToUpdate.SetValue(collection, ((NRegister<ushort>)reg).Value);
}
if (foundToUpdate.PropertyType == typeof(int)) {
foundToUpdate.SetValue(collection, ((NRegister<int>)reg).Value);
}
if (foundToUpdate.PropertyType == typeof(uint)) {
foundToUpdate.SetValue(collection, ((NRegister<uint>)reg).Value);
}
if (foundToUpdate.PropertyType == typeof(float)) {
foundToUpdate.SetValue(collection, ((NRegister<float>)reg).Value);
}
//setting back strings
if (foundToUpdate.PropertyType == typeof(string)) {
foundToUpdate.SetValue(collection, ((SRegister)reg).Value);
}
}
if (foundToUpdate.PropertyType == typeof(bool) && registerAttr.AssignedBitIndex >= 0) {
//setting back bit registers to individual properties
if (reg is NRegister<short> shortReg) {
var bytes = BitConverter.GetBytes(shortReg.Value);
BitArray bitAr = new BitArray(bytes);
foundToUpdate.SetValue(collection, bitAr[registerAttr.AssignedBitIndex]);
}
if (reg is NRegister<int> intReg) {
var bytes = BitConverter.GetBytes(intReg.Value);
BitArray bitAr = new BitArray(bytes);
foundToUpdate.SetValue(collection, bitAr[registerAttr.AssignedBitIndex]);
}
} else if(foundToUpdate.PropertyType == typeof(BitArray)) {
//setting back bit registers
if (reg is NRegister<short> shortReg) {
var bytes = BitConverter.GetBytes(shortReg.Value);
BitArray bitAr = new BitArray(bytes);
foundToUpdate.SetValue(collection, bitAr);
}
if (reg is NRegister<int> intReg) {
var bytes = BitConverter.GetBytes(intReg.Value);
BitArray bitAr = new BitArray(bytes);
foundToUpdate.SetValue(collection, bitAr);
}
}
collection.TriggerPropertyChanged(foundToUpdate.Name);
}
};
if (collection != null)
collection.OnInterfaceLinked(this);
Connected += (i) => {
if(collection != null)
collection.OnInterfaceLinkedAndOnline(this);
};
return this;
}
#endregion
#region Register Writing
/// <summary>
/// Sets a register in the PLCs memory
/// </summary>
/// <param name="registerName">The name the register was given to or a property name from the RegisterCollection class</param>
/// <param name="value">The value to write to the register</param>
public void SetRegister (string registerName, object value) {
var foundRegister = GetAllRegisters().FirstOrDefault(x => x.Name == registerName);
if (foundRegister == null) {
throw new Exception($"Register with the name {registerName} was not found");
}
_ = SetRegisterAsync(registerName, value);
}
/// <summary>
/// Sets a register in the PLCs memory asynchronously, returns the result status from the PLC
/// </summary>
/// <param name="registerName">The name the register was given to or a property name from the RegisterCollection class</param>
/// <param name="value">The value to write to the register</param>
public async Task<bool> SetRegisterAsync (string registerName, object value) {
var foundRegister = GetAllRegisters().FirstOrDefault(x => x.Name == registerName);
if (foundRegister == null) {
throw new Exception($"Register with the name {registerName} was not found");
}
if (foundRegister.GetType() == typeof(BRegister)) {
return await WriteBoolRegister((BRegister)foundRegister, (bool)value);
}
if (foundRegister.GetType() == typeof(NRegister<short>)) {
return await WriteNumRegister((NRegister<short>)foundRegister, (short)value);
}
if (foundRegister.GetType() == typeof(NRegister<ushort>)) {
return await WriteNumRegister((NRegister<ushort>)foundRegister, (ushort)value);
}
if (foundRegister.GetType() == typeof(NRegister<int>)) {
return await WriteNumRegister((NRegister<int>)foundRegister, (int)value);
}
if (foundRegister.GetType() == typeof(NRegister<uint>)) {
return await WriteNumRegister((NRegister<uint>)foundRegister, (uint)value);
}
if (foundRegister.GetType() == typeof(NRegister<float>)) {
return await WriteNumRegister((NRegister<float>)foundRegister, (float)value);
}
if (foundRegister.GetType() == typeof(NRegister<TimeSpan>)) {
return await WriteNumRegister((NRegister<TimeSpan>)foundRegister, (TimeSpan)value);
}
if (foundRegister.GetType() == typeof(SRegister)) {
return await WriteStringRegister((SRegister)foundRegister, (string)value);
}
return false;
}
#endregion
#region Low level command handling
/// <summary>
/// Calculates checksum and sends a command to the PLC then awaits results
/// </summary>
/// <param name="_msg">MEWTOCOL Formatted request string ex: %01#RT</param>
/// <param name="_close">Auto close of frame [true]%01#RT01\r [false]%01#RT</param>
/// <returns>Returns the result</returns>
public async Task<CommandResult> SendCommandAsync (string _msg) {
_msg = _msg.BuildBCCFrame();
_msg += "\r";
//send request
string response = null;
if(ContinousReaderRunning) {
//if the poller is active then add all messages to a qeueue
var awaittask = SendSingleBlock(_msg);
PriorityTasks.Add(awaittask);
awaittask.Wait();
PriorityTasks.Remove(awaittask);
response = awaittask.Result;
} else {
//poller not active let the user manage message timing
response = await SendSingleBlock(_msg);
}
if(response == null) {
return new CommandResult {
Success = false,
Error = "0000",
ErrorDescription = "null result"
};
}
//error catching
Regex errorcheck = new Regex(@"\%[0-9]{2}\!([0-9]{2})", RegexOptions.IgnoreCase);
Match m = errorcheck.Match(response.ToString());
if (m.Success) {
string eCode = m.Groups[1].Value;
string eDes = Links.LinkedData.ErrorCodes[Convert.ToInt32(eCode)];
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Response is: {response}");
Console.WriteLine($"Error on command {_msg.Replace("\r", "")} the PLC returned error code: {eCode}, {eDes}");
Console.ResetColor();
return new CommandResult {
Success = false,
Error = eCode,
ErrorDescription = eDes
};
}
return new CommandResult {
Success = true,
Error = "0000",
Response = response.ToString()
};
}
private async Task<string> SendSingleBlock (string _blockString) {
Stopwatch sw = Stopwatch.StartNew();
using (TcpClient client = new TcpClient() { ReceiveBufferSize = 64, NoDelay = true, ExclusiveAddressUse = true }) {
try {
await client.ConnectAsync(ip, port);
using (NetworkStream stream = client.GetStream()) {
var message = _blockString.ToHexASCIIBytes();
var messageAscii = BitConverter.ToString(message).Replace("-", " ");
//send request
using (var sendStream = new MemoryStream(message)) {
await sendStream.CopyToAsync(stream);
Logger.Log($"OUT MSG: {_blockString}", LogLevel.Critical, this);
//log message sent
ASCIIEncoding enc = new ASCIIEncoding();
string characters = enc.GetString(message);
}
//await result
StringBuilder response = new StringBuilder();
byte[] responseBuffer = new byte[256];
do {
int bytes = stream.Read(responseBuffer, 0, responseBuffer.Length);
response.Append(Encoding.UTF8.GetString(responseBuffer, 0, bytes));
}
while (stream.DataAvailable);
sw.Stop();
Logger.Log($"IN MSG ({(int)sw.Elapsed.TotalMilliseconds}ms): {_blockString}", LogLevel.Critical, this);
return response.ToString();
}
} catch(Exception) {
IsConnected = false;
KillPoller();
Logger.Log("The PLC connection was closed", LogLevel.Error, this);
return null;
}
}
}
#endregion
#region Accessing Info
/// <summary>
/// Gets the connection info string
/// </summary>
public string GetConnectionPortInfo () {
return $"{IpAddress}:{Port}";
}
#endregion
}
}

View File

@@ -1,390 +0,0 @@
using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using MewtocolNet.Registers;
using System.Linq;
using System.Globalization;
using MewtocolNet.Logging;
namespace MewtocolNet {
public partial class MewtocolInterface {
#region PLC info getters
/// <summary>
/// Gets generic information about the PLC
/// </summary>
/// <returns>A PLCInfo class</returns>
public async Task<PLCInfo> GetPLCInfoAsync () {
var resu = await SendCommandAsync("%01#RT");
if(!resu.Success) return null;
var reg = new Regex(@"\%([0-9]{2})\$RT([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{4})..", RegexOptions.IgnoreCase);
Match m = reg.Match(resu.Response);
if(m.Success) {
string station = m.Groups[1].Value;
string cpu = m.Groups[2].Value;
string version = m.Groups[3].Value;
string capacity = m.Groups[4].Value;
string operation = m.Groups[5].Value;
string errorflag = m.Groups[7].Value;
string error = m.Groups[8].Value;
PLCInfo retInfo = new PLCInfo {
CpuInformation = CpuInfo.BuildFromHexString(cpu, version, capacity),
OperationMode = PLCMode.BuildFromHex(operation),
ErrorCode = error,
StationNumber = int.Parse(station ?? "0"),
};
PlcInfo = retInfo;
return retInfo;
}
return null;
}
#endregion
#region Operation mode changing
/// <summary>
/// Changes the PLCs operation mode to the given one
/// </summary>
/// <param name="mode">The mode to change to</param>
/// <returns>The success state of the write operation</returns>
public async Task<bool> SetOperationMode (OPMode mode) {
string modeChar = mode == OPMode.Prog ? "P" : "R";
string requeststring = $"%{GetStationNumber()}#RM{modeChar}";
var result = await SendCommandAsync(requeststring);
if (result.Success) {
Logger.Log($"operation mode was changed to {mode}", LogLevel.Info, this);
} else {
Logger.Log("Operation mode change failed", LogLevel.Error, this);
}
return result.Success;
}
#endregion
#region Byte range writingv / reading to registers
/// <summary>
/// Writes a byte array to a span over multiple registers at once,
/// Rembember the plc can only store word so in order to write to a word array
/// your byte array should be double the size
/// </summary>
/// /// <param name="start">start address of the array</param>
/// <param name="byteArr"></param>
/// <returns></returns>
public async Task<bool> WriteByteRange (int start, byte[] byteArr) {
string byteString = byteArr.BigToMixedEndian().ToHexString();
var wordLength = byteArr.Length / 2;
if (byteArr.Length % 2 != 0)
wordLength++;
string startStr = start.ToString().PadLeft(5, '0');
string endStr = (start + wordLength - 1).ToString().PadLeft(5, '0');
string requeststring = $"%{GetStationNumber()}#WDD{startStr}{endStr}{byteString}";
var result = await SendCommandAsync(requeststring);
return result.Success;
}
/// <summary>
/// Reads the bytes from the start adress for counts byte length
/// </summary>
/// <param name="start">Start adress</param>
/// <param name="count">Number of bytes to get</param>
/// <returns>A byte array or null of there was an error</returns>
public async Task<byte[]> ReadByteRange (int start, int count) {
string startStr = start.ToString().PadLeft(5, '0');
var wordLength = count / 2;
bool wasOdd = false;
if (count % 2 != 0)
wordLength++;
string endStr = (start + wordLength - 1).ToString().PadLeft(5, '0');
string requeststring = $"%{GetStationNumber()}#RDD{startStr}{endStr}";
var result = await SendCommandAsync(requeststring);
if(result.Success && !string.IsNullOrEmpty(result.Response)) {
var bytes = result.Response.ParseDTByteString(wordLength * 4).HexStringToByteArray();
return bytes.BigToMixedEndian().Take(count).ToArray();
}
return null;
}
#endregion
#region Bool register reading / writing
/// <summary>
/// Reads the given boolean register from the PLC
/// </summary>
/// <param name="_toRead">The register to read</param>
public async Task<BRegisterResult> ReadBoolRegister (BRegister _toRead) {
string requeststring = $"%{GetStationNumber()}#RCS{_toRead.BuildMewtocolIdent()}";
var result = await SendCommandAsync(requeststring);
if(!result.Success) {
return new BRegisterResult {
Result = result,
Register = _toRead
};
}
var resultBool = result.Response.ParseRCSingleBit();
if(resultBool != null) {
_toRead.LastValue = resultBool.Value;
}
var finalRes = new BRegisterResult {
Result = result,
Register = _toRead
};
return finalRes;
}
/// <summary>
/// Writes to the given bool register on the PLC
/// </summary>
/// <param name="_toWrite">The register to write to</param>
/// <returns>The success state of the write operation</returns>
public async Task<bool> WriteBoolRegister (BRegister _toWrite, bool value) {
string requeststring = $"%{GetStationNumber()}#WCS{_toWrite.BuildMewtocolIdent()}{(value ? "1" : "0")}";
var result = await SendCommandAsync(requeststring);
return result.Success && result.Response.StartsWith($"%{ GetStationNumber()}#WC");
}
#endregion
#region Number register reading / writing
/// <summary>
/// Reads the given numeric register from the PLC
/// </summary>
/// <typeparam name="T">Type of number (short, ushort, int, uint, float)</typeparam>
/// <param name="_toRead">The register to read</param>
/// <param name="_stationNumber">Station number to access</param>
/// <returns>A result with the given NumberRegister containing the readback value and a result struct</returns>
public async Task<NRegisterResult<T>> ReadNumRegister<T> (NRegister<T> _toRead) {
Type numType = typeof(T);
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolIdent()}";
var result = await SendCommandAsync(requeststring);
if(!result.Success || string.IsNullOrEmpty(result.Response)) {
return new NRegisterResult<T> {
Result = result,
Register = _toRead
};
}
if (numType == typeof(short)) {
var resultBytes = result.Response.ParseDTByteString(4).ReverseByteOrder();
var val = short.Parse(resultBytes, NumberStyles.HexNumber);
(_toRead as NRegister<short>).LastValue = val;
} else if (numType == typeof(ushort)) {
var resultBytes = result.Response.ParseDTByteString(4).ReverseByteOrder();
var val = ushort.Parse(resultBytes, NumberStyles.HexNumber);
(_toRead as NRegister<ushort>).LastValue = val;
} else if (numType == typeof(int)) {
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
var val = int.Parse(resultBytes, NumberStyles.HexNumber);
(_toRead as NRegister<int>).LastValue = val;
} else if (numType == typeof(uint)) {
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
var val = uint.Parse(resultBytes, NumberStyles.HexNumber);
(_toRead as NRegister<uint>).LastValue = val;
} else if (numType == typeof(float)) {
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
//convert to unsigned int first
var val = uint.Parse(resultBytes, NumberStyles.HexNumber);
byte[] floatVals = BitConverter.GetBytes(val);
float finalFloat = BitConverter.ToSingle(floatVals, 0);
(_toRead as NRegister<float>).LastValue = finalFloat;
} else if (numType == typeof(TimeSpan)) {
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
//convert to unsigned int first
var vallong = long.Parse(resultBytes, NumberStyles.HexNumber);
var valMillis = vallong * 10;
var ts = TimeSpan.FromMilliseconds(valMillis);
//minmax writable / readable value is 10ms
(_toRead as NRegister<TimeSpan>).LastValue = ts;
}
var finalRes = new NRegisterResult<T> {
Result = result,
Register = _toRead
};
return finalRes;
}
/// <summary>
/// Reads the given numeric register from the PLC
/// </summary>
/// <typeparam name="T">Type of number (short, ushort, int, uint, float)</typeparam>
/// <param name="_toWrite">The register to write</param>
/// <param name="_stationNumber">Station number to access</param>
/// <returns>The success state of the write operation</returns>
public async Task<bool> WriteNumRegister<T> (NRegister<T> _toWrite, T _value) {
byte[] toWriteVal;
Type numType = typeof(T);
if (numType == typeof(short)) {
toWriteVal = BitConverter.GetBytes(Convert.ToInt16(_value));
} else if (numType == typeof(ushort)) {
toWriteVal = BitConverter.GetBytes(Convert.ToUInt16(_value));
} else if (numType == typeof(int)) {
toWriteVal = BitConverter.GetBytes(Convert.ToInt32(_value));
} else if (numType == typeof(uint)) {
toWriteVal = BitConverter.GetBytes(Convert.ToUInt32(_value));
} else if (numType == typeof(float)) {
var fl = _value as float?;
if (fl == null)
throw new NullReferenceException("Float cannot be null");
toWriteVal = BitConverter.GetBytes(fl.Value);
} else if (numType == typeof(TimeSpan)) {
var fl = _value as TimeSpan?;
if (fl == null)
throw new NullReferenceException("Timespan cannot be null");
var tLong = (uint)(fl.Value.TotalMilliseconds / 10);
toWriteVal = BitConverter.GetBytes(tLong);
} else {
toWriteVal = null;
}
string requeststring = $"%{GetStationNumber()}#WD{_toWrite.BuildMewtocolIdent()}{toWriteVal.ToHexString()}";
var result = await SendCommandAsync(requeststring);
return result.Success && result.Response.StartsWith($"%{ GetStationNumber()}#WD");
}
#endregion
#region String register reading / writing
//string is build up like this
//04 00 04 00 53 50 33 35 13
//0, 1 = reserved size
//1, 2 = current size
//3,4,5,6 = ASCII encoded chars (SP35)
//7,8 = checksum
/// <summary>
/// Reads back the value of a string register
/// </summary>
/// <param name="_toRead">The register to read</param>
/// <param name="_stationNumber">The station number of the PLC</param>
/// <returns></returns>
public async Task<SRegisterResult> ReadStringRegister (SRegister _toRead, int _stationNumber = 1) {
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolIdent()}";
var result = await SendCommandAsync(requeststring);
if (result.Success)
_toRead.SetValueFromPLC(result.Response.ParseDTString());
return new SRegisterResult {
Result = result,
Register = _toRead
};
}
/// <summary>
/// Writes a string to a string register
/// </summary>
/// <param name="_toWrite">The register to write</param>
/// <param name="_value">The value to write, if the strings length is longer than the cap size it gets trimmed to the max char length</param>
/// <param name="_stationNumber">The station number of the PLC</param>
/// <returns>The success state of the write operation</returns>
public async Task<bool> WriteStringRegister(SRegister _toWrite, string _value, int _stationNumber = 1) {
if (_value == null) _value = "";
if(_value.Length > _toWrite.ReservedSize) {
throw new ArgumentException("Write string size cannot be longer than reserved string size");
}
string stationNum = GetStationNumber();
string dataString = _value.BuildDTString(_toWrite.ReservedSize);
string dataArea = _toWrite.BuildCustomIdent(dataString.Length / 4);
string requeststring = $"%{stationNum}#WD{dataArea}{dataString}";
var result = await SendCommandAsync(requeststring);
return result.Success && result.Response.StartsWith($"%{ GetStationNumber()}#WD");
}
#endregion
#region Helpers
internal string GetStationNumber () {
return StationNumber.ToString().PadLeft(2, '0');
}
#endregion
}
}

View File

@@ -1,46 +0,0 @@
namespace MewtocolNet {
/// <summary>
/// CPU type of the PLC
/// </summary>
public enum CpuType {
/// <summary>
/// FP 0 / FP 2.7K
/// </summary>
FP0_FP1_2_7K,
/// <summary>
/// FP0 / FP1, 5K / 10K
/// </summary>
FP0_FP1_5K_10K,
/// <summary>
/// FP1 M 0.9K
/// </summary>
FP1_M_0_9K,
/// <summary>
/// FP2 16k / 32k
/// </summary>
FP2_16K_32K,
/// <summary>
/// FP3 C 10K
/// </summary>
FP3_C_10K,
/// <summary>
/// FP3 C 16K
/// </summary>
FP3_C_16K,
/// <summary>
/// FP5 16K
/// </summary>
FP5_16K,
/// <summary>
/// FP 5 24K
/// </summary>
FP5_24K,
/// <summary>
/// Includes panasonic FPX, FPX-H, Sigma
/// </summary>
FP_Sigma_X_H_30K_60K_120K
}
}

View File

@@ -1,21 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MewtocolNet {
/// <summary>
/// CPU type of the PLC
/// </summary>
public enum OPMode {
/// <summary>
/// PLC run mode
/// </summary>
Run,
/// <summary>
/// PLC programming mode
/// </summary>
Prog,
}
}

View File

@@ -1,43 +0,0 @@
namespace MewtocolNet.Registers {
/// <summary>
/// Contains generic information about the plc
/// </summary>
public class PLCInfo {
/// <summary>
/// Contains information about the PLCs cpu
/// </summary>
public CpuInfo CpuInformation {get;set;}
/// <summary>
/// Contains information about the PLCs operation modes
/// </summary>
public PLCMode OperationMode {get;set;}
/// <summary>
/// Current error code of the PLC
/// </summary>
public string ErrorCode {get;set;}
/// <summary>
/// Current station number of the PLC
/// </summary>
public int StationNumber { get;set;}
/// <summary>
/// Generates a string containing some of the most important informations
/// </summary>
/// <returns></returns>
public override string ToString () {
return $"Type: {CpuInformation.Cputype},\n" +
$"Capacity: {CpuInformation.ProgramCapacity}k\n" +
$"CPU v: {CpuInformation.CpuVersion}\n" +
$"Station Num: {StationNumber}\n" +
$"--------------------------------\n" +
$"OP Mode: {(OperationMode.RunMode ? "Run" : "Prog")}\n" +
$"Error Code: {ErrorCode}";
}
}
}

View File

@@ -1,67 +0,0 @@
using System;
namespace MewtocolNet.Registers {
/// <summary>
/// All modes
/// </summary>
public class PLCMode {
public bool RunMode { get; set; }
public bool TestRunMode { get; set; }
public bool BreakExcecuting { get; set; }
public bool BreakValid { get; set; }
public bool OutputEnabled { get; set; }
public bool StepRunMode { get; set; }
public bool MessageExecuting { get; set; }
public bool RemoteMode { get; set; }
/// <summary>
/// Gets operation mode from 2 digit hex number
/// </summary>
internal static PLCMode BuildFromHex (string _hexString) {
string lower = Convert.ToString(Convert.ToInt32(_hexString.Substring(0, 1)), 2).PadLeft(4, '0');
string higher = Convert.ToString(Convert.ToInt32(_hexString.Substring(1, 1)), 2).PadLeft(4, '0');
string combined = lower + higher;
var retMode = new PLCMode();
for (int i = 0; i < 8; i++) {
char digit = combined[i];
bool state = false;
if (digit.ToString() == "1")
state = true;
switch (i) {
case 0:
retMode.RunMode = state;
break;
case 1:
retMode.TestRunMode = state;
break;
case 2:
retMode.BreakExcecuting = state;
break;
case 3:
retMode.BreakValid = state;
break;
case 4:
retMode.OutputEnabled = state;
break;
case 5:
retMode.StepRunMode = state;
break;
case 6:
retMode.MessageExecuting = state;
break;
case 7:
retMode.RemoteMode = state;
break;
}
}
return retMode;
}
}
}

View File

@@ -1,81 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MewtocolNet.RegisterAttributes {
public enum BitCount {
B16,
B32
}
[AttributeUsage(AttributeTargets.Property)]
public class RegisterAttribute : Attribute {
public int MemoryArea;
public int StringLength;
public RegisterType RegisterType;
public SpecialAddress SpecialAddress = SpecialAddress.None;
public BitCount BitCount;
public int AssignedBitIndex = -1;
public RegisterAttribute (int memoryArea, int stringLength = 1) {
MemoryArea = memoryArea;
StringLength = stringLength;
}
public RegisterAttribute (int memoryArea, RegisterType type) {
if (type.ToString().StartsWith("DT"))
throw new NotSupportedException("DT types are not supported for attribute register setups because the number type is casted automatically");
MemoryArea = memoryArea;
RegisterType = type;
SpecialAddress = SpecialAddress.None;
}
public RegisterAttribute (RegisterType type, SpecialAddress spAdress) {
if (type.ToString().StartsWith("DT"))
throw new NotSupportedException("DT types are not supported for attribute register setups because the number type is casted automatically");
RegisterType = type;
SpecialAddress = spAdress;
}
public RegisterAttribute (int memoryArea, BitCount bitcount) {
MemoryArea = memoryArea;
StringLength = 0;
BitCount = bitcount;
}
public RegisterAttribute (int memoryArea, uint assignBit, BitCount bitcount) {
if(assignBit > 15 && bitcount == BitCount.B16) {
throw new NotSupportedException("The assignBit parameter cannot be greater than 15 in a 16 bit var");
}
if (assignBit > 31 && bitcount == BitCount.B32) {
throw new NotSupportedException("The assignBit parameter cannot be greater than 31 in a 32 bit var");
}
MemoryArea = memoryArea;
StringLength = 0;
BitCount = bitcount;
AssignedBitIndex = (int)assignBit;
}
}
}

View File

@@ -1,43 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace MewtocolNet.RegisterAttributes {
/// <summary>
/// A register collection base with full auto read and notification support built in
/// </summary>
public class RegisterCollectionBase : INotifyPropertyChanged {
/// <summary>
/// Reference to its bound interface
/// </summary>
public MewtocolInterface PLCInterface { get; set; }
/// <summary>
/// Whenever one of its props changes
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Triggers a property changed event
/// </summary>
/// <param name="propertyName">Name of the property to trigger for</param>
public void TriggerPropertyChanged (string propertyName = null) {
var handler = PropertyChanged;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public virtual void OnInterfaceLinked (MewtocolInterface plc) { }
public virtual void OnInterfaceLinkedAndOnline (MewtocolInterface plc) { }
}
}

View File

@@ -1,71 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MewtocolNet {
/// <summary>
/// The special register type
/// </summary>
public enum RegisterType {
/// <summary>
/// Physical input as a bool (Relay)
/// </summary>
X,
/// <summary>
/// Physical output as a bool (Relay)
/// </summary>
Y,
/// <summary>
/// Internal as a bool (Relay)
/// </summary>
R,
/// <summary>
/// Data area as a short (Register)
/// </summary>
DT_short,
/// <summary>
/// Data area as an unsigned short (Register)
/// </summary>
DT_ushort,
/// <summary>
/// Double data area as an integer (Register)
/// </summary>
DDT_int,
/// <summary>
/// Double data area as an unsigned integer (Register)
/// </summary>
DDT_uint,
/// <summary>
/// Double data area as an floating point number (Register)
/// </summary>
DDT_float,
}
/// <summary>
/// The special input / output channel address
/// </summary>
public enum SpecialAddress {
#pragma warning disable CS1591
/// <summary>
/// No defined
/// </summary>
None,
A = -10,
B = -11,
C = -12,
D = -13,
E = -14,
F = -15,
#pragma warning restore
}
}

View File

@@ -1,33 +0,0 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace MewtocolNet.Registers {
/// <summary>
/// The formatted result of a ascii command
/// </summary>
public struct CommandResult {
/// <summary>
/// Success state of the message
/// </summary>
public bool Success {get;set;}
/// <summary>
/// Response text of the message
/// </summary>
public string Response {get;set;}
/// <summary>
/// Error code of the message
/// </summary>
public string Error {get;set;}
/// <summary>
/// Error text of the message
/// </summary>
public string ErrorDescription {get;set;}
}
}

Some files were not shown because too many files have changed in this diff Show More