scadable-cli 0.1.1__py3-none-any.whl
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.
- scadable/__init__.py +0 -0
- scadable/edge/__init__.py +17 -0
- scadable/edge/cli.py +274 -0
- scadable/edge/constants.py +13 -0
- scadable/edge/device.py +63 -0
- scadable/edge/protocols/__init__.py +0 -0
- scadable/edge/protocols/base.py +10 -0
- scadable/edge/protocols/modbus.py +36 -0
- scadable_cli-0.1.1.dist-info/METADATA +51 -0
- scadable_cli-0.1.1.dist-info/RECORD +13 -0
- scadable_cli-0.1.1.dist-info/WHEEL +5 -0
- scadable_cli-0.1.1.dist-info/entry_points.txt +2 -0
- scadable_cli-0.1.1.dist-info/top_level.txt +1 -0
scadable/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .device import Device, PAYLOAD_SCHEMA
|
|
2
|
+
from .protocols.modbus import ModbusConnection, ModbusProtocol
|
|
3
|
+
from .protocols.base import Protocol
|
|
4
|
+
from .constants import (
|
|
5
|
+
MODBUS_TCP, MODBUS_RTU, OPCUA, MQTT,
|
|
6
|
+
ONE_SEC, FIVE_SEC, TEN_SEC, THIRTY_SEC, ONE_MIN, FIVE_MIN,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"Device",
|
|
11
|
+
"PAYLOAD_SCHEMA",
|
|
12
|
+
"ModbusConnection",
|
|
13
|
+
"ModbusProtocol",
|
|
14
|
+
"Protocol",
|
|
15
|
+
"MODBUS_TCP", "MODBUS_RTU", "OPCUA", "MQTT",
|
|
16
|
+
"ONE_SEC", "FIVE_SEC", "TEN_SEC", "THIRTY_SEC", "ONE_MIN", "FIVE_MIN",
|
|
17
|
+
]
|
scadable/edge/cli.py
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
import argparse
|
|
5
|
+
|
|
6
|
+
CONFIG_FILE = "scadable.json"
|
|
7
|
+
|
|
8
|
+
# ─── Templates ────────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
MODBUS_TCP_TEMPLATE = '''\
|
|
11
|
+
"""
|
|
12
|
+
Generated by Scadable CLI.
|
|
13
|
+
|
|
14
|
+
Only modify the variable values below (host, port, slave_id, frequency, etc).
|
|
15
|
+
Do not rename classes or change the structure — the runtime depends on it.
|
|
16
|
+
"""
|
|
17
|
+
from scadable.edge import Device, ModbusConnection
|
|
18
|
+
from scadable.edge.constants import MODBUS_TCP, FIVE_SEC
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Connection(ModbusConnection):
|
|
24
|
+
"""
|
|
25
|
+
Modbus TCP connection settings.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
host: IP address or hostname of the Modbus device.
|
|
29
|
+
Use "${{DEVICE_HOST}}" for environment variable injection.
|
|
30
|
+
port: TCP port number (default: 502 for Modbus).
|
|
31
|
+
slave_id: Modbus slave/unit ID (1-247).
|
|
32
|
+
"""
|
|
33
|
+
host: str = "${{DEVICE_HOST}}"
|
|
34
|
+
port: int = 502
|
|
35
|
+
slave_id: int = 1
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class {class_name}(Device):
|
|
39
|
+
"""
|
|
40
|
+
Device configuration.
|
|
41
|
+
|
|
42
|
+
Attributes:
|
|
43
|
+
id: Unique identifier for this device.
|
|
44
|
+
protocol: Communication protocol (MODBUS_TCP).
|
|
45
|
+
connection: Connection settings class.
|
|
46
|
+
frequency: Polling interval in seconds (how often to read data).
|
|
47
|
+
filter: List of register names to pull (empty = pull all).
|
|
48
|
+
"""
|
|
49
|
+
id = "{device_id}"
|
|
50
|
+
protocol = MODBUS_TCP
|
|
51
|
+
connection = Connection
|
|
52
|
+
frequency = FIVE_SEC
|
|
53
|
+
filter = [] # empty = pull all registers; e.g. ["reg_100", "reg_102"] to filter
|
|
54
|
+
'''
|
|
55
|
+
|
|
56
|
+
MODBUS_RTU_TEMPLATE = '''\
|
|
57
|
+
"""
|
|
58
|
+
Generated by Scadable CLI.
|
|
59
|
+
|
|
60
|
+
Only modify the variable values below (serial_port, baudrate, slave_id, etc).
|
|
61
|
+
Do not rename classes or change the structure — the runtime depends on it.
|
|
62
|
+
"""
|
|
63
|
+
from scadable.edge import Device, ModbusConnection
|
|
64
|
+
from scadable.edge.constants import MODBUS_RTU, FIVE_SEC
|
|
65
|
+
from dataclasses import dataclass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class Connection(ModbusConnection):
|
|
70
|
+
"""
|
|
71
|
+
Modbus RTU (serial) connection settings.
|
|
72
|
+
|
|
73
|
+
Attributes:
|
|
74
|
+
serial_port: Serial port path (e.g., "/dev/ttyUSB0" on Linux,
|
|
75
|
+
"COM3" on Windows).
|
|
76
|
+
slave_id: Modbus slave/unit ID (1-247).
|
|
77
|
+
baudrate: Serial baud rate (common: 9600, 19200, 38400, 115200).
|
|
78
|
+
parity: Parity bit — "N" (none), "E" (even), or "O" (odd).
|
|
79
|
+
stopbits: Number of stop bits (1 or 2).
|
|
80
|
+
bytesize: Data bits per byte (typically 8).
|
|
81
|
+
"""
|
|
82
|
+
serial_port: str = "/dev/ttyUSB0"
|
|
83
|
+
slave_id: int = 1
|
|
84
|
+
baudrate: int = 9600
|
|
85
|
+
parity: str = "N"
|
|
86
|
+
stopbits: int = 1
|
|
87
|
+
bytesize: int = 8
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class {class_name}(Device):
|
|
91
|
+
"""
|
|
92
|
+
Device configuration.
|
|
93
|
+
|
|
94
|
+
Attributes:
|
|
95
|
+
id: Unique identifier for this device.
|
|
96
|
+
protocol: Communication protocol (MODBUS_RTU).
|
|
97
|
+
connection: Connection settings class.
|
|
98
|
+
frequency: Polling interval in seconds (how often to read data).
|
|
99
|
+
filter: List of register names to pull (empty = pull all).
|
|
100
|
+
"""
|
|
101
|
+
id = "{device_id}"
|
|
102
|
+
protocol = MODBUS_RTU
|
|
103
|
+
connection = Connection
|
|
104
|
+
frequency = FIVE_SEC
|
|
105
|
+
filter = [] # empty = pull all registers; e.g. ["reg_100", "reg_102"] to filter
|
|
106
|
+
'''
|
|
107
|
+
|
|
108
|
+
TEMPLATES = {
|
|
109
|
+
"modbus-tcp": MODBUS_TCP_TEMPLATE,
|
|
110
|
+
"modbus-rtu": MODBUS_RTU_TEMPLATE,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
SCADABLE_ASCII = """
|
|
114
|
+
____ ____ _ ____ _ ____ _ _____
|
|
115
|
+
/ ___| / ___| / \\ | _ \\ / \\ | __ ) | | | ____|
|
|
116
|
+
\\___ \\ | | / _ \\ | | | | / _ \\ | _ \\ | | | _|
|
|
117
|
+
___) | | |___ / ___ \\ | |_| | / ___ \\ | |_) | | |___ | |___
|
|
118
|
+
|____/ \\____| /_/ \\_\\ |____/ /_/ \\_\\ |____/ |_____| |_____|
|
|
119
|
+
|
|
120
|
+
Edge SDK v0.1.0
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
# ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
def to_class_name(name: str) -> str:
|
|
126
|
+
return "".join(p.capitalize() for p in name.replace("_", "-").split("-"))
|
|
127
|
+
|
|
128
|
+
def get_gateways() -> list:
|
|
129
|
+
"""Get list of gateway names by scanning gateways/ subfolders."""
|
|
130
|
+
if not os.path.exists("gateways"):
|
|
131
|
+
return []
|
|
132
|
+
gateways = []
|
|
133
|
+
for name in os.listdir("gateways"):
|
|
134
|
+
path = os.path.join("gateways", name)
|
|
135
|
+
if os.path.isdir(path):
|
|
136
|
+
gateways.append(name)
|
|
137
|
+
return sorted(gateways)
|
|
138
|
+
|
|
139
|
+
def next_gateway_name() -> str:
|
|
140
|
+
"""Auto-increment gateway name: gateway-001, gateway-002 etc."""
|
|
141
|
+
existing = get_gateways()
|
|
142
|
+
i = 1
|
|
143
|
+
while True:
|
|
144
|
+
name = f"gateway-{i:03d}"
|
|
145
|
+
if name not in existing:
|
|
146
|
+
return name
|
|
147
|
+
i += 1
|
|
148
|
+
|
|
149
|
+
def require_project():
|
|
150
|
+
"""Exit if not in a Scadable project (no scadable.json)."""
|
|
151
|
+
if not os.path.exists(CONFIG_FILE):
|
|
152
|
+
print(f"No {CONFIG_FILE} found. Run 'scadable init' first.")
|
|
153
|
+
sys.exit(1)
|
|
154
|
+
|
|
155
|
+
def resolve_gateway(gateway_arg: str = None) -> str:
|
|
156
|
+
"""
|
|
157
|
+
Resolve which gateway to use.
|
|
158
|
+
- If --gateway is specified, use it.
|
|
159
|
+
- If only one gateway exists, use it automatically.
|
|
160
|
+
- If multiple exist, prompt the user to pick.
|
|
161
|
+
"""
|
|
162
|
+
gateways = get_gateways()
|
|
163
|
+
if not gateways:
|
|
164
|
+
print("No gateways found. Run 'scadable add gateway' first.")
|
|
165
|
+
sys.exit(1)
|
|
166
|
+
if gateway_arg:
|
|
167
|
+
if gateway_arg not in gateways:
|
|
168
|
+
print(f"Gateway '{gateway_arg}' not found. Available: {', '.join(gateways)}")
|
|
169
|
+
sys.exit(1)
|
|
170
|
+
return gateway_arg
|
|
171
|
+
if len(gateways) == 1:
|
|
172
|
+
return gateways[0]
|
|
173
|
+
# Multiple gateways — prompt
|
|
174
|
+
print("Multiple gateways found:")
|
|
175
|
+
for i, gw in enumerate(gateways, 1):
|
|
176
|
+
print(f" {i}. {gw}")
|
|
177
|
+
choice = input("Select gateway (number): ").strip()
|
|
178
|
+
try:
|
|
179
|
+
return gateways[int(choice) - 1]
|
|
180
|
+
except (ValueError, IndexError):
|
|
181
|
+
print("Invalid choice.")
|
|
182
|
+
sys.exit(1)
|
|
183
|
+
|
|
184
|
+
# ─── Commands ─────────────────────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
def cmd_init(project_name: str = None):
|
|
187
|
+
if os.path.exists(CONFIG_FILE):
|
|
188
|
+
print(f"{CONFIG_FILE} already exists.")
|
|
189
|
+
sys.exit(1)
|
|
190
|
+
name = project_name or os.path.basename(os.getcwd())
|
|
191
|
+
config = {"project": name, "version": "0.1.0"}
|
|
192
|
+
os.makedirs("gateways", exist_ok=True)
|
|
193
|
+
with open(CONFIG_FILE, "w") as f:
|
|
194
|
+
json.dump(config, f, indent=2)
|
|
195
|
+
print(SCADABLE_ASCII)
|
|
196
|
+
print(f"✓ Initialized project '{name}'")
|
|
197
|
+
print(f"✓ Created gateways/")
|
|
198
|
+
print(f"✓ Created {CONFIG_FILE}")
|
|
199
|
+
print(f"\nNext: scadable add gateway")
|
|
200
|
+
|
|
201
|
+
def cmd_add_gateway(gateway_name: str = None):
|
|
202
|
+
require_project()
|
|
203
|
+
existing = get_gateways()
|
|
204
|
+
name = gateway_name or next_gateway_name()
|
|
205
|
+
if name in existing:
|
|
206
|
+
print(f"Gateway '{name}' already exists.")
|
|
207
|
+
sys.exit(1)
|
|
208
|
+
path = os.path.join("gateways", name)
|
|
209
|
+
os.makedirs(path, exist_ok=True)
|
|
210
|
+
print(f"✓ Created gateways/{name}/")
|
|
211
|
+
print(f"\nNext: scadable add device modbus-tcp <device-name> --gateway {name}")
|
|
212
|
+
|
|
213
|
+
def cmd_add_device(protocol: str, device_name: str, gateway_arg: str = None):
|
|
214
|
+
require_project()
|
|
215
|
+
gateway = resolve_gateway(gateway_arg)
|
|
216
|
+
template = TEMPLATES.get(protocol)
|
|
217
|
+
if not template:
|
|
218
|
+
print(f"Unknown protocol '{protocol}'. Available: {', '.join(TEMPLATES.keys())}")
|
|
219
|
+
sys.exit(1)
|
|
220
|
+
device_path = os.path.join("gateways", gateway, device_name)
|
|
221
|
+
if os.path.exists(device_path):
|
|
222
|
+
print(f"Device '{device_name}' already exists in gateway '{gateway}'.")
|
|
223
|
+
sys.exit(1)
|
|
224
|
+
os.makedirs(device_path)
|
|
225
|
+
config_path = os.path.join(device_path, "config.py")
|
|
226
|
+
content = template.format(
|
|
227
|
+
class_name=to_class_name(device_name),
|
|
228
|
+
device_id=device_name,
|
|
229
|
+
)
|
|
230
|
+
with open(config_path, "w") as f:
|
|
231
|
+
f.write(content)
|
|
232
|
+
print(f"✓ Created {config_path}")
|
|
233
|
+
print(f"\nNext: edit {config_path} and set your connection details.")
|
|
234
|
+
|
|
235
|
+
# ─── Main ─────────────────────────────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
def main():
|
|
238
|
+
parser = argparse.ArgumentParser(prog="scadable")
|
|
239
|
+
sub = parser.add_subparsers(dest="command")
|
|
240
|
+
|
|
241
|
+
# scadable init [name]
|
|
242
|
+
p_init = sub.add_parser("init", help="Initialize a new Scadable project")
|
|
243
|
+
p_init.add_argument("name", nargs="?", help="Project name (default: current folder name)")
|
|
244
|
+
|
|
245
|
+
# scadable add ...
|
|
246
|
+
p_add = sub.add_parser("add", help="Add a gateway or device")
|
|
247
|
+
add_sub = p_add.add_subparsers(dest="resource")
|
|
248
|
+
|
|
249
|
+
# scadable add gateway [name]
|
|
250
|
+
p_gw = add_sub.add_parser("gateway", help="Add a new gateway")
|
|
251
|
+
p_gw.add_argument("name", nargs="?", help="Gateway name (default: gateway-001, gateway-002...)")
|
|
252
|
+
|
|
253
|
+
# scadable add device <protocol> <name> [--gateway <name>]
|
|
254
|
+
p_dev = add_sub.add_parser("device", help="Add a new device to a gateway")
|
|
255
|
+
p_dev.add_argument("protocol", choices=list(TEMPLATES.keys()))
|
|
256
|
+
p_dev.add_argument("name", help="Device name e.g. temp-sensor")
|
|
257
|
+
p_dev.add_argument("--gateway", help="Gateway name (auto-detected if only one exists)")
|
|
258
|
+
|
|
259
|
+
args = parser.parse_args()
|
|
260
|
+
|
|
261
|
+
if args.command == "init":
|
|
262
|
+
cmd_init(args.name)
|
|
263
|
+
elif args.command == "add":
|
|
264
|
+
if args.resource == "gateway":
|
|
265
|
+
cmd_add_gateway(getattr(args, "name", None))
|
|
266
|
+
elif args.resource == "device":
|
|
267
|
+
cmd_add_device(args.protocol, args.name, getattr(args, "gateway", None))
|
|
268
|
+
else:
|
|
269
|
+
p_add.print_help()
|
|
270
|
+
else:
|
|
271
|
+
parser.print_help()
|
|
272
|
+
|
|
273
|
+
if __name__ == "__main__":
|
|
274
|
+
main()
|
scadable/edge/device.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
PAYLOAD_SCHEMA = {
|
|
2
|
+
"device_id": str,
|
|
3
|
+
"protocol": str,
|
|
4
|
+
"timestamp": int,
|
|
5
|
+
"payload": dict,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Device:
|
|
10
|
+
"""
|
|
11
|
+
Base class for all gateway devices.
|
|
12
|
+
Subclass this in your config.py to define a device.
|
|
13
|
+
|
|
14
|
+
Required class variables:
|
|
15
|
+
id — unique identifier for this device
|
|
16
|
+
protocol — use a constant from scadable.edge.constants
|
|
17
|
+
connection — subclass of ModbusConnection (or other protocol connection)
|
|
18
|
+
frequency — polling interval in seconds, use constants: FIVE_SEC, TEN_SEC etc
|
|
19
|
+
|
|
20
|
+
Optional class variables:
|
|
21
|
+
filter — list of register names to pull (empty = pull all)
|
|
22
|
+
|
|
23
|
+
Usage:
|
|
24
|
+
class MyPLC(Device):
|
|
25
|
+
id = "device-001"
|
|
26
|
+
protocol = MODBUS_TCP
|
|
27
|
+
connection = Connection
|
|
28
|
+
frequency = FIVE_SEC
|
|
29
|
+
filter = [] # empty = pull all registers
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
id: str = None
|
|
33
|
+
protocol: str = None
|
|
34
|
+
connection = None
|
|
35
|
+
frequency: int = 10
|
|
36
|
+
filter: list = []
|
|
37
|
+
|
|
38
|
+
def __init_subclass__(cls, **kwargs):
|
|
39
|
+
super().__init_subclass__(**kwargs)
|
|
40
|
+
# Skip validation for intermediate base classes
|
|
41
|
+
if cls.__name__ == "Device":
|
|
42
|
+
return
|
|
43
|
+
errors = []
|
|
44
|
+
if not cls.id:
|
|
45
|
+
errors.append(f"Device subclass '{cls.__name__}' must define 'id'")
|
|
46
|
+
if not cls.protocol:
|
|
47
|
+
errors.append(f"Device subclass '{cls.__name__}' must define 'protocol'")
|
|
48
|
+
if cls.connection is None:
|
|
49
|
+
errors.append(f"Device subclass '{cls.__name__}' must define 'connection'")
|
|
50
|
+
if errors:
|
|
51
|
+
raise TypeError("\n".join(errors))
|
|
52
|
+
|
|
53
|
+
def __repr__(self):
|
|
54
|
+
return (
|
|
55
|
+
f"<Device id={self.id!r} protocol={self.protocol!r} "
|
|
56
|
+
f"connection={self.connection.__name__} frequency={self.frequency}s>"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def read(self, *args, **kwargs):
|
|
60
|
+
raise NotImplementedError("read() will be implemented by the WASM runtime")
|
|
61
|
+
|
|
62
|
+
def write(self, *args, **kwargs):
|
|
63
|
+
raise NotImplementedError("write() will be implemented by the WASM runtime")
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from .base import Protocol
|
|
4
|
+
from ..constants import MODBUS_TCP, MODBUS_RTU
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class ModbusConnection:
|
|
8
|
+
"""
|
|
9
|
+
Base Modbus connection config.
|
|
10
|
+
Subclass this in your config.py and override the fields you need.
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
@dataclass
|
|
14
|
+
class Connection(ModbusConnection):
|
|
15
|
+
host: str = "192.168.1.100"
|
|
16
|
+
port: int = 502
|
|
17
|
+
slave_id: int = 1
|
|
18
|
+
"""
|
|
19
|
+
host: str = ""
|
|
20
|
+
port: int = 502
|
|
21
|
+
slave_id: int = 1
|
|
22
|
+
timeout: float = 5.0
|
|
23
|
+
retries: int = 3
|
|
24
|
+
# RTU only — leave None for TCP
|
|
25
|
+
serial_port: Optional[str] = None
|
|
26
|
+
baudrate: int = 9600
|
|
27
|
+
parity: str = "N" # N=none, E=even, O=odd
|
|
28
|
+
stopbits: int = 1
|
|
29
|
+
bytesize: int = 8
|
|
30
|
+
|
|
31
|
+
class ModbusProtocol(Protocol):
|
|
32
|
+
def read(self, *args, **kwargs):
|
|
33
|
+
raise NotImplementedError
|
|
34
|
+
|
|
35
|
+
def write(self, *args, **kwargs):
|
|
36
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scadable-cli
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Edge SDK for the Scadable IoT platform
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: iot,scada,edge,modbus,industrial
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# Scadable Edge SDK
|
|
11
|
+
|
|
12
|
+
Python SDK for defining devices on the Scadable IoT platform.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install scadable-cli
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
Create a `config.py` for your device:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from scadable.edge import Device, ModbusConnection
|
|
26
|
+
from scadable.edge.constants import MODBUS_TCP, FIVE_SEC
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class Connection(ModbusConnection):
|
|
31
|
+
host: str = "${DEVICE_HOST}"
|
|
32
|
+
port: int = 502
|
|
33
|
+
slave_id: int = 1
|
|
34
|
+
|
|
35
|
+
class MyPLC(Device):
|
|
36
|
+
id = "device-001"
|
|
37
|
+
protocol = MODBUS_TCP
|
|
38
|
+
connection = Connection
|
|
39
|
+
frequency = FIVE_SEC
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Project Structure
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
gateways/
|
|
46
|
+
gateway-001/
|
|
47
|
+
device-001/
|
|
48
|
+
config.py
|
|
49
|
+
device-002/
|
|
50
|
+
config.py
|
|
51
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
scadable/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
scadable/edge/__init__.py,sha256=YAWyi96UbV6aelnuqHJ8ms_iQ7NPuf3-kYYzMAWvvwY,511
|
|
3
|
+
scadable/edge/cli.py,sha256=pe-Wl0lDjP8QOrTOz7q8yniXu9ylG84raUK743JFfO8,9763
|
|
4
|
+
scadable/edge/constants.py,sha256=JRsNYCO1iq7HRs3Dxr1JJAPEEXJ2dstlbpJvLqVu7Ms,226
|
|
5
|
+
scadable/edge/device.py,sha256=1ZLHNjaD0u7L0iGRgGusZPqYUtiwaAcpWxG-p-mi5VA,2079
|
|
6
|
+
scadable/edge/protocols/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
scadable/edge/protocols/base.py,sha256=6sVjl0hqKGcaQArT-ir_pkEW3PCK067VN8xECuWHsqc,200
|
|
8
|
+
scadable/edge/protocols/modbus.py,sha256=QJJDKrzQLeFk0S8K0HqRTDnWEb2iBnqV7Oo7dcT3lP8,945
|
|
9
|
+
scadable_cli-0.1.1.dist-info/METADATA,sha256=jpARYEaPmPi1wZ5wQhNzRXu5ShOLKwfdNbovI5jKydA,956
|
|
10
|
+
scadable_cli-0.1.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
11
|
+
scadable_cli-0.1.1.dist-info/entry_points.txt,sha256=q52zN8QCtxk8orpYu68MiTB9ga2IK8hjSkjiIr0geLE,52
|
|
12
|
+
scadable_cli-0.1.1.dist-info/top_level.txt,sha256=LlTtDPSogdWt7efFMKspIXEdaCchf2b3ujxRxNmMdN8,9
|
|
13
|
+
scadable_cli-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
scadable
|