uphy-controller 0.1.0.dev2809__tar.gz → 0.1.0.dev3125__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {uphy_controller-0.1.0.dev2809 → uphy_controller-0.1.0.dev3125}/LICENSE.txt +5 -5
- {uphy_controller-0.1.0.dev2809/src/uphy_controller.egg-info → uphy_controller-0.1.0.dev3125}/PKG-INFO +51 -43
- {uphy_controller-0.1.0.dev2809 → uphy_controller-0.1.0.dev3125}/README.md +27 -20
- {uphy_controller-0.1.0.dev2809 → uphy_controller-0.1.0.dev3125}/pyproject.toml +31 -27
- {uphy_controller-0.1.0.dev2809 → uphy_controller-0.1.0.dev3125}/setup.cfg +4 -4
- {uphy_controller-0.1.0.dev2809 → uphy_controller-0.1.0.dev3125}/src/uphy/__init__.py +2 -2
- {uphy_controller-0.1.0.dev2809 → uphy_controller-0.1.0.dev3125}/src/uphy/controller/__init__.py +7 -8
- {uphy_controller-0.1.0.dev2809 → uphy_controller-0.1.0.dev3125}/src/uphy/controller/__main__.py +190 -187
- {uphy_controller-0.1.0.dev2809 → uphy_controller-0.1.0.dev3125}/src/uphy/controller/gui.py +175 -157
- {uphy_controller-0.1.0.dev2809 → uphy_controller-0.1.0.dev3125}/src/uphy/controller/modbus.py +108 -103
- {uphy_controller-0.1.0.dev2809 → uphy_controller-0.1.0.dev3125/src/uphy_controller.egg-info}/PKG-INFO +51 -43
- {uphy_controller-0.1.0.dev2809 → uphy_controller-0.1.0.dev3125}/src/uphy_controller.egg-info/requires.txt +2 -1
- {uphy_controller-0.1.0.dev2809 → uphy_controller-0.1.0.dev3125}/src/uphy_controller.egg-info/SOURCES.txt +0 -0
- {uphy_controller-0.1.0.dev2809 → uphy_controller-0.1.0.dev3125}/src/uphy_controller.egg-info/dependency_links.txt +0 -0
- {uphy_controller-0.1.0.dev2809 → uphy_controller-0.1.0.dev3125}/src/uphy_controller.egg-info/entry_points.txt +0 -0
- {uphy_controller-0.1.0.dev2809 → uphy_controller-0.1.0.dev3125}/src/uphy_controller.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
Copyright rt-labs AB, Sweden.
|
|
2
|
-
All rights reserved.
|
|
3
|
-
|
|
4
|
-
You may not use this software in a commercial product without
|
|
5
|
-
purchasing a license. Contact sales@rt-labs.com for more information.
|
|
1
|
+
Copyright rt-labs AB, Sweden.
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
You may not use this software in a commercial product without
|
|
5
|
+
purchasing a license. Contact sales@rt-labs.com for more information.
|
|
@@ -1,43 +1,51 @@
|
|
|
1
|
-
Metadata-Version: 2.2
|
|
2
|
-
Name: uphy-controller
|
|
3
|
-
Version: 0.1.0.
|
|
4
|
-
Summary: Add your description here
|
|
5
|
-
License: Copyright rt-labs AB, Sweden.
|
|
6
|
-
All rights reserved.
|
|
7
|
-
|
|
8
|
-
You may not use this software in a commercial product without
|
|
9
|
-
purchasing a license. Contact sales@rt-labs.com for more information.
|
|
10
|
-
|
|
11
|
-
Project-URL: RT-Labs, https://rt-labs.com
|
|
12
|
-
Project-URL: U-Phy, https://rt-labs.com/u-phy
|
|
13
|
-
Project-URL: Documentation, https://docs.rt-labs.com/u-phy
|
|
14
|
-
Requires-Python: >=3.10
|
|
15
|
-
Description-Content-Type: text/markdown
|
|
16
|
-
License-File: LICENSE.txt
|
|
17
|
-
Requires-Dist: dearpygui>=2.0.0
|
|
18
|
-
Requires-Dist: pymodbus
|
|
19
|
-
Requires-Dist: requests>=2.32.3
|
|
20
|
-
Requires-Dist: rich>=13.9.2
|
|
21
|
-
Requires-Dist: typer>=0.12.5
|
|
22
|
-
Requires-Dist:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: uphy-controller
|
|
3
|
+
Version: 0.1.0.dev3125
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
License: Copyright rt-labs AB, Sweden.
|
|
6
|
+
All rights reserved.
|
|
7
|
+
|
|
8
|
+
You may not use this software in a commercial product without
|
|
9
|
+
purchasing a license. Contact sales@rt-labs.com for more information.
|
|
10
|
+
|
|
11
|
+
Project-URL: RT-Labs, https://rt-labs.com
|
|
12
|
+
Project-URL: U-Phy, https://rt-labs.com/u-phy
|
|
13
|
+
Project-URL: Documentation, https://docs.rt-labs.com/u-phy
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE.txt
|
|
17
|
+
Requires-Dist: dearpygui>=2.0.0
|
|
18
|
+
Requires-Dist: pymodbus<3.9.0,>=3.8.3
|
|
19
|
+
Requires-Dist: requests>=2.32.3
|
|
20
|
+
Requires-Dist: rich>=13.9.2
|
|
21
|
+
Requires-Dist: typer>=0.12.5
|
|
22
|
+
Requires-Dist: uphy
|
|
23
|
+
Requires-Dist: zeroconf>=0.135.0
|
|
24
|
+
|
|
25
|
+
# U-Phy Controller
|
|
26
|
+
|
|
27
|
+
This contain a python based U-Phy device controller that act as a fieldbus master/controller.
|
|
28
|
+
|
|
29
|
+
Supported fieldbuses:
|
|
30
|
+
|
|
31
|
+
- modbus
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
uv pip install uphy-controller
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Workflow
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
# Start up controller against an existing device
|
|
43
|
+
uphy-controller modbus --model [PATH_TO_MODEL] --target [UUID]:[HOST]:[PORT]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
# Start up controller against discovered devices that expose their model via
|
|
49
|
+
# mdns.
|
|
50
|
+
uphy-controller mdns
|
|
51
|
+
```
|
|
@@ -1,20 +1,27 @@
|
|
|
1
|
-
# U-Phy Controller
|
|
2
|
-
|
|
3
|
-
This contain a python based U-Phy device controller that act as a fieldbus master/controller.
|
|
4
|
-
|
|
5
|
-
Supported fieldbuses:
|
|
6
|
-
|
|
7
|
-
- modbus
|
|
8
|
-
|
|
9
|
-
## Install
|
|
10
|
-
|
|
11
|
-
```sh
|
|
12
|
-
uv pip install uphy-controller
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Workflow
|
|
16
|
-
|
|
17
|
-
```sh
|
|
18
|
-
# Start up controller against an existing device
|
|
19
|
-
uphy-controller modbus
|
|
20
|
-
```
|
|
1
|
+
# U-Phy Controller
|
|
2
|
+
|
|
3
|
+
This contain a python based U-Phy device controller that act as a fieldbus master/controller.
|
|
4
|
+
|
|
5
|
+
Supported fieldbuses:
|
|
6
|
+
|
|
7
|
+
- modbus
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
uv pip install uphy-controller
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Workflow
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
# Start up controller against an existing device
|
|
19
|
+
uphy-controller modbus --model [PATH_TO_MODEL] --target [UUID]:[HOST]:[PORT]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
# Start up controller against discovered devices that expose their model via
|
|
25
|
+
# mdns.
|
|
26
|
+
uphy-controller mdns
|
|
27
|
+
```
|
|
@@ -1,27 +1,31 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "uphy-controller"
|
|
3
|
-
version = "0.1.0.
|
|
4
|
-
description = "Add your description here"
|
|
5
|
-
readme = "README.md"
|
|
6
|
-
requires-python = ">=3.10"
|
|
7
|
-
dependencies = [
|
|
8
|
-
"dearpygui>=2.0.0",
|
|
9
|
-
"pymodbus>=3.
|
|
10
|
-
"requests>=2.32.3",
|
|
11
|
-
"rich>=13.9.2",
|
|
12
|
-
"typer>=0.12.5",
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
1
|
+
[project]
|
|
2
|
+
name = "uphy-controller"
|
|
3
|
+
version = "0.1.0.dev3125"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"dearpygui>=2.0.0",
|
|
9
|
+
"pymodbus (>=3.8.3,<3.9.0)",
|
|
10
|
+
"requests>=2.32.3",
|
|
11
|
+
"rich>=13.9.2",
|
|
12
|
+
"typer>=0.12.5",
|
|
13
|
+
"uphy",
|
|
14
|
+
"zeroconf>=0.135.0",
|
|
15
|
+
]
|
|
16
|
+
license = {file = "LICENSE.txt"}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
RT-Labs = "https://rt-labs.com"
|
|
21
|
+
U-Phy = "https://rt-labs.com/u-phy"
|
|
22
|
+
Documentation = "https://docs.rt-labs.com/u-phy"
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
uphy-controller = "uphy.controller.__main__:app"
|
|
26
|
+
|
|
27
|
+
[project.entry-points.'uphy.cli']
|
|
28
|
+
controller = 'uphy.controller.__main__:app'
|
|
29
|
+
|
|
30
|
+
[tool.uv.sources]
|
|
31
|
+
uphy = { workspace = true }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[egg_info]
|
|
2
|
-
tag_build =
|
|
3
|
-
tag_date = 0
|
|
4
|
-
|
|
1
|
+
[egg_info]
|
|
2
|
+
tag_build =
|
|
3
|
+
tag_date = 0
|
|
4
|
+
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
# This is a namespace package to hold on to uphy namespace
|
|
2
|
-
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
|
|
1
|
+
# This is a namespace package to hold on to uphy namespace
|
|
2
|
+
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
|
{uphy_controller-0.1.0.dev2809 → uphy_controller-0.1.0.dev3125}/src/uphy/controller/__init__.py
RENAMED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
@dataclass
|
|
5
|
-
class Target:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
port: str = 502
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class Target:
|
|
6
|
+
host: str
|
|
7
|
+
port: str = 502
|
{uphy_controller-0.1.0.dev2809 → uphy_controller-0.1.0.dev3125}/src/uphy/controller/__main__.py
RENAMED
|
@@ -1,187 +1,190 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from functools import partial
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from threading import Event, Thread
|
|
5
|
-
from typing import Annotated
|
|
6
|
-
from rich.table import Table
|
|
7
|
-
from rich import print
|
|
8
|
-
|
|
9
|
-
from typer import Option, Typer, BadParameter
|
|
10
|
-
from upgen.model.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
device.
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
)
|
|
68
|
-
print()
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
thread
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
1
|
+
import logging
|
|
2
|
+
from functools import partial
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from threading import Event, Thread
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
from rich import print
|
|
8
|
+
|
|
9
|
+
from typer import Option, Typer, BadParameter
|
|
10
|
+
from upgen.model.runtime import Root as RootModel
|
|
11
|
+
from uphy.cli import VERBOSE_OPTION, configure_logging
|
|
12
|
+
|
|
13
|
+
from . import Target, gui, modbus
|
|
14
|
+
|
|
15
|
+
LOG = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
app = Typer(
|
|
18
|
+
pretty_exceptions_enable=False,
|
|
19
|
+
no_args_is_help=True,
|
|
20
|
+
name="controller",
|
|
21
|
+
help="Run a demo modbus controller based on a model",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
configure_logging(level=logging.INFO)
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def parse_target(value: str):
|
|
28
|
+
values = value.split(":")
|
|
29
|
+
if len(values) != 3:
|
|
30
|
+
raise BadParameter(f"Expected uuid:host:port, got {value}")
|
|
31
|
+
|
|
32
|
+
return Target(*value.split(":"))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@app.command(name="modbus")
|
|
36
|
+
def modbus_tcp(
|
|
37
|
+
model: Annotated[list[Path], Option()],
|
|
38
|
+
target: Annotated[
|
|
39
|
+
list[Target] | None,
|
|
40
|
+
Option(
|
|
41
|
+
parser=parse_target,
|
|
42
|
+
metavar="ID:HOST:PORT",
|
|
43
|
+
help="Targets to connect controller to, where ID is a device uuid or name, HOST is hostname to connect to and PORT is the port the modbus-tcp server is listening on.",
|
|
44
|
+
),
|
|
45
|
+
] = None,
|
|
46
|
+
verbose: Annotated[bool, VERBOSE_OPTION] = False,
|
|
47
|
+
):
|
|
48
|
+
"""Run controller against a set of fixed uphy devices."""
|
|
49
|
+
models = [RootModel.parse_file(file) for file in model]
|
|
50
|
+
|
|
51
|
+
devices_by_id = {
|
|
52
|
+
str(device.id): (root, device) for root in models for device in root.devices
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
devices_by_name = {
|
|
56
|
+
device.name: (root, device) for root in models for device in root.devices
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if target is None:
|
|
60
|
+
table = Table(title="Available devices")
|
|
61
|
+
table.add_column("ID")
|
|
62
|
+
table.add_column("Name")
|
|
63
|
+
|
|
64
|
+
for id, (_, device) in devices_by_id.items():
|
|
65
|
+
table.add_row(id, device.name)
|
|
66
|
+
|
|
67
|
+
print(table)
|
|
68
|
+
print()
|
|
69
|
+
print(
|
|
70
|
+
"Please add targets to connect to using the --target ID:HOST:PORT argument."
|
|
71
|
+
)
|
|
72
|
+
print()
|
|
73
|
+
exit(-1)
|
|
74
|
+
|
|
75
|
+
gui.setup()
|
|
76
|
+
|
|
77
|
+
stop = Event()
|
|
78
|
+
try:
|
|
79
|
+
for target_obj in target:
|
|
80
|
+
root, device = devices_by_id.get(target_obj.id, (None, None))
|
|
81
|
+
if not device:
|
|
82
|
+
root, device = devices_by_name.get(target_obj.id, (None, None))
|
|
83
|
+
if not device:
|
|
84
|
+
LOG.warning("Unable to find device %s", target_obj.id)
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
device_gui = gui.add_device(
|
|
88
|
+
root, device, f"{target_obj.host}:{target_obj.port}"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
thread = Thread(
|
|
92
|
+
target=partial(modbus.target_runner, stop, target_obj, device_gui),
|
|
93
|
+
name=device.name,
|
|
94
|
+
)
|
|
95
|
+
thread.start()
|
|
96
|
+
|
|
97
|
+
gui.run()
|
|
98
|
+
finally:
|
|
99
|
+
stop.set()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@app.command()
|
|
103
|
+
def mdns(
|
|
104
|
+
verbose: Annotated[bool, VERBOSE_OPTION] = False,
|
|
105
|
+
):
|
|
106
|
+
"""Run controller against devices automatically discovered via mdns."""
|
|
107
|
+
from zeroconf import (
|
|
108
|
+
Zeroconf,
|
|
109
|
+
ServiceBrowser,
|
|
110
|
+
ServiceStateChange,
|
|
111
|
+
IPVersion,
|
|
112
|
+
ServiceInfo,
|
|
113
|
+
)
|
|
114
|
+
import requests
|
|
115
|
+
|
|
116
|
+
zeroconf = Zeroconf(ip_version=IPVersion.V4Only)
|
|
117
|
+
services = ["_modbus._tcp.local."]
|
|
118
|
+
|
|
119
|
+
device_runners: dict[tuple, Event] = {}
|
|
120
|
+
|
|
121
|
+
def on_device_added(device_key: tuple, info: ServiceInfo):
|
|
122
|
+
properties = info.decoded_properties
|
|
123
|
+
model_port = properties.get("model-port")
|
|
124
|
+
model_path = properties.get("model-path")
|
|
125
|
+
if not model_port or not model_path:
|
|
126
|
+
LOG.error("Missing model info for %s", info.name)
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
device_stop = Event()
|
|
130
|
+
device_runners[device_key] = device_stop
|
|
131
|
+
|
|
132
|
+
def _run():
|
|
133
|
+
for address in info.parsed_addresses(IPVersion.V4Only):
|
|
134
|
+
model_url = f"http://{address}:{model_port}{model_path}"
|
|
135
|
+
target_obj = Target(address, info.port)
|
|
136
|
+
try:
|
|
137
|
+
response = requests.get(model_url)
|
|
138
|
+
response.raise_for_status()
|
|
139
|
+
model_raw = response.content
|
|
140
|
+
break
|
|
141
|
+
except Exception as exception:
|
|
142
|
+
LOG.warning("Failed to connect to %s:%r", model_url, exception)
|
|
143
|
+
continue
|
|
144
|
+
else:
|
|
145
|
+
LOG.error("Unable to get model for %s", info.name)
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
model = RootModel.parse_raw(model_raw)
|
|
150
|
+
device = model.device
|
|
151
|
+
|
|
152
|
+
LOG.info("Adding device %s", device.name)
|
|
153
|
+
device_gui = gui.add_device(model, device, address)
|
|
154
|
+
modbus.target_runner(device_stop, target_obj, device_gui)
|
|
155
|
+
device_gui.close()
|
|
156
|
+
|
|
157
|
+
thread = Thread(
|
|
158
|
+
target=_run,
|
|
159
|
+
)
|
|
160
|
+
thread.start()
|
|
161
|
+
|
|
162
|
+
def on_service_state_change(
|
|
163
|
+
zeroconf: Zeroconf,
|
|
164
|
+
service_type: str,
|
|
165
|
+
name: str,
|
|
166
|
+
state_change: ServiceStateChange,
|
|
167
|
+
) -> None:
|
|
168
|
+
print(f"Service {name}: {state_change}")
|
|
169
|
+
|
|
170
|
+
device_key = (service_type, name)
|
|
171
|
+
|
|
172
|
+
if state_change is ServiceStateChange.Added:
|
|
173
|
+
info = zeroconf.get_service_info(service_type, name)
|
|
174
|
+
if info and info.port:
|
|
175
|
+
on_device_added(device_key, info)
|
|
176
|
+
|
|
177
|
+
if state_change is ServiceStateChange.Removed:
|
|
178
|
+
device_stop = device_runners.pop(device_key, None)
|
|
179
|
+
if device_stop:
|
|
180
|
+
device_stop.set()
|
|
181
|
+
|
|
182
|
+
gui.setup()
|
|
183
|
+
|
|
184
|
+
ServiceBrowser(zeroconf, services, handlers=[on_service_state_change])
|
|
185
|
+
|
|
186
|
+
gui.run()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
if __name__ == "__main__":
|
|
190
|
+
app()
|
|
@@ -1,157 +1,175 @@
|
|
|
1
|
-
import math
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
|
|
4
|
-
import dearpygui.dearpygui as dpg
|
|
5
|
-
from upgen.model.uphy import DataType
|
|
6
|
-
from upgen.model.
|
|
7
|
-
|
|
8
|
-
datatype_to_min_max = {
|
|
9
|
-
DataType.INT8: (-(2**7), 2**7 - 1),
|
|
10
|
-
DataType.INT16: (-(2**15), 2**15 - 1),
|
|
11
|
-
DataType.INT32: (-(2**31), 2**31 - 1),
|
|
12
|
-
DataType.UINT8: (0, 2**8 - 1),
|
|
13
|
-
DataType.UINT16: (0, 2**16 - 1),
|
|
14
|
-
DataType.UINT32: (0, 2**
|
|
15
|
-
DataType.REAL32: (-math.inf, math.inf),
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@dataclass
|
|
20
|
-
class InputGUI:
|
|
21
|
-
signal: Signal
|
|
22
|
-
value_id: list[str | int]
|
|
23
|
-
|
|
24
|
-
def __init__(self, signal: Signal):
|
|
25
|
-
self.signal = signal
|
|
26
|
-
dpg.add_text(signal.name)
|
|
27
|
-
with dpg.group():
|
|
28
|
-
self.value_id = [
|
|
29
|
-
dpg.add_text("N/A") for _ in range(signal.array_length or 1)
|
|
30
|
-
]
|
|
31
|
-
|
|
32
|
-
def update(self, value: list[int | float] | None):
|
|
33
|
-
if value is None:
|
|
34
|
-
for ix in range(self.signal.array_length or 1):
|
|
35
|
-
dpg.set_value(self.value_id[ix], "N/A")
|
|
36
|
-
else:
|
|
37
|
-
for ix in range(self.signal.array_length or 1):
|
|
38
|
-
dpg.set_value(self.value_id[ix], value[ix])
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@dataclass
|
|
42
|
-
class OutputGUI:
|
|
43
|
-
signal: Signal
|
|
44
|
-
value_id: list[str | int]
|
|
45
|
-
value: list[int] | None = None
|
|
46
|
-
|
|
47
|
-
def __init__(self, signal: Signal):
|
|
48
|
-
self.signal = signal
|
|
49
|
-
self.value = [0] * (signal.array_length or 1)
|
|
50
|
-
dpg.add_text(signal.name)
|
|
51
|
-
min, max = datatype_to_min_max[signal.datatype]
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def
|
|
91
|
-
dpg.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
dpg.
|
|
157
|
-
|
|
1
|
+
import math
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
import dearpygui.dearpygui as dpg
|
|
5
|
+
from upgen.model.uphy import DataType
|
|
6
|
+
from upgen.model.runtime import Root as RootModel, Device, Slot, Signal, Parameter
|
|
7
|
+
|
|
8
|
+
datatype_to_min_max = {
|
|
9
|
+
DataType.INT8: (-(2**7), 2**7 - 1),
|
|
10
|
+
DataType.INT16: (-(2**15), 2**15 - 1),
|
|
11
|
+
DataType.INT32: (-(2**31), 2**31 - 1),
|
|
12
|
+
DataType.UINT8: (0, 2**8 - 1),
|
|
13
|
+
DataType.UINT16: (0, 2**16 - 1),
|
|
14
|
+
DataType.UINT32: (0, 2**31 - 1), # Incorrect max but larger values do not work with dpg
|
|
15
|
+
DataType.REAL32: (-math.inf, math.inf),
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class InputGUI:
|
|
21
|
+
signal: Signal
|
|
22
|
+
value_id: list[str | int]
|
|
23
|
+
|
|
24
|
+
def __init__(self, signal: Signal):
|
|
25
|
+
self.signal = signal
|
|
26
|
+
dpg.add_text(signal.name)
|
|
27
|
+
with dpg.group():
|
|
28
|
+
self.value_id = [
|
|
29
|
+
dpg.add_text("N/A") for _ in range(signal.array_length or 1)
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
def update(self, value: list[int | float] | None):
|
|
33
|
+
if value is None:
|
|
34
|
+
for ix in range(self.signal.array_length or 1):
|
|
35
|
+
dpg.set_value(self.value_id[ix], "N/A")
|
|
36
|
+
else:
|
|
37
|
+
for ix in range(self.signal.array_length or 1):
|
|
38
|
+
dpg.set_value(self.value_id[ix], value[ix])
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class OutputGUI:
|
|
43
|
+
signal: Signal
|
|
44
|
+
value_id: list[str | int]
|
|
45
|
+
value: list[int] | None = None
|
|
46
|
+
|
|
47
|
+
def __init__(self, signal: Signal):
|
|
48
|
+
self.signal = signal
|
|
49
|
+
self.value = [0] * (signal.array_length or 1)
|
|
50
|
+
dpg.add_text(signal.name)
|
|
51
|
+
min, max = datatype_to_min_max[signal.datatype]
|
|
52
|
+
widget = dpg.add_input_float if signal.datatype == DataType.REAL32 else dpg.add_input_int
|
|
53
|
+
with dpg.group():
|
|
54
|
+
self.value_id = [
|
|
55
|
+
widget(
|
|
56
|
+
width=100,
|
|
57
|
+
callback=lambda sender, app_data, user_data: self.callback(
|
|
58
|
+
ix, app_data
|
|
59
|
+
),
|
|
60
|
+
min_value=min,
|
|
61
|
+
max_value=max,
|
|
62
|
+
min_clamped=True,
|
|
63
|
+
max_clamped=True,
|
|
64
|
+
)
|
|
65
|
+
for ix in range(signal.array_length or 1)
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
def callback(self, ix: int, value: int):
|
|
69
|
+
self.value[ix] = value
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ParamGUI:
|
|
73
|
+
def __init__(self, signal: Parameter):
|
|
74
|
+
self.signal = signal
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class SlotGUI:
|
|
79
|
+
inputs: dict[str, InputGUI]
|
|
80
|
+
outputs: dict[str, OutputGUI]
|
|
81
|
+
params: dict[str, ParamGUI]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class DeviceGUI:
|
|
86
|
+
widget: int | str
|
|
87
|
+
slots: dict[str, SlotGUI]
|
|
88
|
+
status_id: str | int
|
|
89
|
+
|
|
90
|
+
def update_status(self, status: str):
|
|
91
|
+
dpg.set_value(self.status_id, status)
|
|
92
|
+
|
|
93
|
+
def close(self):
|
|
94
|
+
dpg.delete_item(self.widget)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def add_device(model: RootModel, device: Device, suffix: str) -> DeviceGUI:
|
|
98
|
+
width = 300
|
|
99
|
+
windows = [
|
|
100
|
+
window
|
|
101
|
+
for window in dpg.get_windows()
|
|
102
|
+
if dpg.get_item_user_data(window) == "device"
|
|
103
|
+
]
|
|
104
|
+
for ix, window in enumerate(windows):
|
|
105
|
+
dpg.set_item_pos(window, [ix * width, 0])
|
|
106
|
+
ix = len(windows)
|
|
107
|
+
|
|
108
|
+
with dpg.window(
|
|
109
|
+
width=width,
|
|
110
|
+
pos=[ix * width, 0],
|
|
111
|
+
autosize=True,
|
|
112
|
+
label=f"{device.name} - {suffix}",
|
|
113
|
+
user_data="device"
|
|
114
|
+
) as window:
|
|
115
|
+
with dpg.group(horizontal=True):
|
|
116
|
+
dpg.add_text("Status")
|
|
117
|
+
status_id = dpg.add_text()
|
|
118
|
+
|
|
119
|
+
slot_guis = {}
|
|
120
|
+
for slot in device.slots:
|
|
121
|
+
with dpg.child_window(auto_resize_y=True):
|
|
122
|
+
slot_gui = _add_slot(model, slot)
|
|
123
|
+
slot_guis[slot.name] = slot_gui
|
|
124
|
+
|
|
125
|
+
return DeviceGUI(window, slot_guis, status_id)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _add_slot(model: RootModel, slot: Slot):
|
|
129
|
+
dpg.add_text(f"{slot.name}")
|
|
130
|
+
|
|
131
|
+
dpg.add_separator()
|
|
132
|
+
|
|
133
|
+
with dpg.group(indent=10), dpg.table(label=slot.name, header_row=False):
|
|
134
|
+
dpg.add_table_column()
|
|
135
|
+
dpg.add_table_column()
|
|
136
|
+
|
|
137
|
+
inputs: dict[str, InputGUI] = {}
|
|
138
|
+
for signal in slot.inputs:
|
|
139
|
+
with dpg.table_row():
|
|
140
|
+
inputs[signal.id] = InputGUI(signal)
|
|
141
|
+
|
|
142
|
+
outputs: dict[str, OutputGUI] = {}
|
|
143
|
+
for signal in slot.outputs:
|
|
144
|
+
with dpg.table_row():
|
|
145
|
+
outputs[signal.id] = OutputGUI(signal)
|
|
146
|
+
|
|
147
|
+
params: dict[str, ParamGUI] = {}
|
|
148
|
+
for signal in slot.parameters:
|
|
149
|
+
with dpg.table_row():
|
|
150
|
+
params[signal.id] = ParamGUI(signal)
|
|
151
|
+
|
|
152
|
+
return SlotGUI(inputs=inputs, outputs=outputs, params=params)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def setup():
|
|
156
|
+
dpg.create_context()
|
|
157
|
+
def on_key_q(s, a, u):
|
|
158
|
+
if dpg.is_key_down(dpg.mvKey_LControl):
|
|
159
|
+
dpg.stop_dearpygui()
|
|
160
|
+
|
|
161
|
+
def on_key_esc(s, a, u):
|
|
162
|
+
dpg.stop_dearpygui()
|
|
163
|
+
|
|
164
|
+
with dpg.handler_registry():
|
|
165
|
+
dpg.add_key_press_handler(key=dpg.mvKey_Q, callback=on_key_q)
|
|
166
|
+
dpg.add_key_press_handler(key=dpg.mvKey_Escape, callback=on_key_esc)
|
|
167
|
+
|
|
168
|
+
dpg.create_viewport(title="U-Phy Controller", width=800)
|
|
169
|
+
dpg.setup_dearpygui()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def run():
|
|
173
|
+
dpg.show_viewport()
|
|
174
|
+
dpg.start_dearpygui()
|
|
175
|
+
dpg.destroy_context()
|
{uphy_controller-0.1.0.dev2809 → uphy_controller-0.1.0.dev3125}/src/uphy/controller/modbus.py
RENAMED
|
@@ -1,103 +1,108 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from threading import Event
|
|
3
|
-
from time import sleep
|
|
4
|
-
|
|
5
|
-
import pymodbus.client
|
|
6
|
-
from pymodbus.client import ModbusBaseClient, ModbusTcpClient
|
|
7
|
-
import pymodbus.exceptions
|
|
8
|
-
from upgen.model.uphy import DataType, Signal
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
DataType.
|
|
15
|
-
DataType.
|
|
16
|
-
DataType.
|
|
17
|
-
DataType.
|
|
18
|
-
DataType.
|
|
19
|
-
DataType.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
payload
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
1
|
+
import logging
|
|
2
|
+
from threading import Event
|
|
3
|
+
from time import sleep
|
|
4
|
+
|
|
5
|
+
import pymodbus.client
|
|
6
|
+
from pymodbus.client import ModbusBaseClient, ModbusTcpClient
|
|
7
|
+
import pymodbus.exceptions
|
|
8
|
+
from upgen.model.uphy import DataType, Signal
|
|
9
|
+
import struct
|
|
10
|
+
|
|
11
|
+
from . import Target, gui
|
|
12
|
+
|
|
13
|
+
datatype_to_modbus_type = {
|
|
14
|
+
DataType.INT8: ModbusBaseClient.DATATYPE.INT16,
|
|
15
|
+
DataType.INT16: ModbusBaseClient.DATATYPE.INT16,
|
|
16
|
+
DataType.INT32: ModbusBaseClient.DATATYPE.INT32,
|
|
17
|
+
DataType.UINT8: ModbusBaseClient.DATATYPE.UINT16,
|
|
18
|
+
DataType.UINT16: ModbusBaseClient.DATATYPE.UINT16,
|
|
19
|
+
DataType.UINT32: ModbusBaseClient.DATATYPE.UINT32,
|
|
20
|
+
DataType.REAL32: ModbusBaseClient.DATATYPE.FLOAT32,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_type(datatype: DataType) -> tuple[ModbusBaseClient.DATATYPE, int]:
|
|
25
|
+
data_type = datatype_to_modbus_type[datatype]
|
|
26
|
+
data_type_bits = data_type.value[1] * 16
|
|
27
|
+
return data_type, data_type_bits
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def signal_to_regs_and_type(signal: Signal):
|
|
31
|
+
data_type, data_bits = get_type(signal.datatype)
|
|
32
|
+
data_regs = bits_to_regs(data_bits) * (signal.array_length or 1)
|
|
33
|
+
return data_regs, data_type
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def bits_to_regs(bits: int) -> None:
|
|
37
|
+
return (bits + 15) // 16
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def target_runner(stop: Event, target: Target, device_gui: gui.DeviceGUI):
|
|
41
|
+
device_gui.update_status("CONNECTING")
|
|
42
|
+
client = ModbusTcpClient(target.host, port=target.port)
|
|
43
|
+
|
|
44
|
+
while not stop.is_set():
|
|
45
|
+
try:
|
|
46
|
+
client.connect()
|
|
47
|
+
|
|
48
|
+
base = 0
|
|
49
|
+
for slot in device_gui.slots.values():
|
|
50
|
+
for input in slot.inputs.values():
|
|
51
|
+
count, data_type = signal_to_regs_and_type(input.signal)
|
|
52
|
+
index = base
|
|
53
|
+
base += count
|
|
54
|
+
try:
|
|
55
|
+
response = client.read_holding_registers(index, count=count)
|
|
56
|
+
if response.isError():
|
|
57
|
+
raise pymodbus.ModbusException("Error")
|
|
58
|
+
except pymodbus.exceptions.ConnectionException:
|
|
59
|
+
raise
|
|
60
|
+
except pymodbus.ModbusException as exception:
|
|
61
|
+
logging.error("Error on read %d - %r", index, exception)
|
|
62
|
+
input.update(None)
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
values = [
|
|
66
|
+
client.convert_from_registers(
|
|
67
|
+
response.registers[ix : ix + data_type.value[1]], data_type
|
|
68
|
+
)
|
|
69
|
+
for ix in range(0, count, data_type.value[1])
|
|
70
|
+
]
|
|
71
|
+
input.update(values)
|
|
72
|
+
|
|
73
|
+
for output in slot.outputs.values():
|
|
74
|
+
count, data_type = signal_to_regs_and_type(output.signal)
|
|
75
|
+
index = base
|
|
76
|
+
base += count
|
|
77
|
+
|
|
78
|
+
if output.value is None:
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
payload = []
|
|
83
|
+
for value in output.value:
|
|
84
|
+
payload.extend(client.convert_to_registers(value, data_type))
|
|
85
|
+
except struct.error:
|
|
86
|
+
logging.error("Bad payload")
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
response = client.write_registers(index, payload)
|
|
91
|
+
if response.isError():
|
|
92
|
+
raise pymodbus.ModbusException("Error")
|
|
93
|
+
except pymodbus.exceptions.ConnectionException:
|
|
94
|
+
raise
|
|
95
|
+
except pymodbus.ModbusException as exception:
|
|
96
|
+
logging.error("Error on read %d - %r", index, exception)
|
|
97
|
+
|
|
98
|
+
for param in slot.params.values():
|
|
99
|
+
count = bits_to_regs(param.signal.bitlen)
|
|
100
|
+
index = base
|
|
101
|
+
base += count
|
|
102
|
+
except pymodbus.ModbusException as exception:
|
|
103
|
+
device_gui.update_status("ERROR")
|
|
104
|
+
logging.error("%r", exception)
|
|
105
|
+
sleep(2)
|
|
106
|
+
else:
|
|
107
|
+
device_gui.update_status("CONNECTED")
|
|
108
|
+
sleep(0.5)
|
|
@@ -1,43 +1,51 @@
|
|
|
1
|
-
Metadata-Version: 2.2
|
|
2
|
-
Name: uphy-controller
|
|
3
|
-
Version: 0.1.0.
|
|
4
|
-
Summary: Add your description here
|
|
5
|
-
License: Copyright rt-labs AB, Sweden.
|
|
6
|
-
All rights reserved.
|
|
7
|
-
|
|
8
|
-
You may not use this software in a commercial product without
|
|
9
|
-
purchasing a license. Contact sales@rt-labs.com for more information.
|
|
10
|
-
|
|
11
|
-
Project-URL: RT-Labs, https://rt-labs.com
|
|
12
|
-
Project-URL: U-Phy, https://rt-labs.com/u-phy
|
|
13
|
-
Project-URL: Documentation, https://docs.rt-labs.com/u-phy
|
|
14
|
-
Requires-Python: >=3.10
|
|
15
|
-
Description-Content-Type: text/markdown
|
|
16
|
-
License-File: LICENSE.txt
|
|
17
|
-
Requires-Dist: dearpygui>=2.0.0
|
|
18
|
-
Requires-Dist: pymodbus
|
|
19
|
-
Requires-Dist: requests>=2.32.3
|
|
20
|
-
Requires-Dist: rich>=13.9.2
|
|
21
|
-
Requires-Dist: typer>=0.12.5
|
|
22
|
-
Requires-Dist:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: uphy-controller
|
|
3
|
+
Version: 0.1.0.dev3125
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
License: Copyright rt-labs AB, Sweden.
|
|
6
|
+
All rights reserved.
|
|
7
|
+
|
|
8
|
+
You may not use this software in a commercial product without
|
|
9
|
+
purchasing a license. Contact sales@rt-labs.com for more information.
|
|
10
|
+
|
|
11
|
+
Project-URL: RT-Labs, https://rt-labs.com
|
|
12
|
+
Project-URL: U-Phy, https://rt-labs.com/u-phy
|
|
13
|
+
Project-URL: Documentation, https://docs.rt-labs.com/u-phy
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE.txt
|
|
17
|
+
Requires-Dist: dearpygui>=2.0.0
|
|
18
|
+
Requires-Dist: pymodbus<3.9.0,>=3.8.3
|
|
19
|
+
Requires-Dist: requests>=2.32.3
|
|
20
|
+
Requires-Dist: rich>=13.9.2
|
|
21
|
+
Requires-Dist: typer>=0.12.5
|
|
22
|
+
Requires-Dist: uphy
|
|
23
|
+
Requires-Dist: zeroconf>=0.135.0
|
|
24
|
+
|
|
25
|
+
# U-Phy Controller
|
|
26
|
+
|
|
27
|
+
This contain a python based U-Phy device controller that act as a fieldbus master/controller.
|
|
28
|
+
|
|
29
|
+
Supported fieldbuses:
|
|
30
|
+
|
|
31
|
+
- modbus
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
uv pip install uphy-controller
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Workflow
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
# Start up controller against an existing device
|
|
43
|
+
uphy-controller modbus --model [PATH_TO_MODEL] --target [UUID]:[HOST]:[PORT]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
# Start up controller against discovered devices that expose their model via
|
|
49
|
+
# mdns.
|
|
50
|
+
uphy-controller mdns
|
|
51
|
+
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|