tinytoolslib 0.2.5__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.
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.1
2
+ Name: tinytoolslib
3
+ Version: 0.2.5
4
+ Summary: Set of tools for use with Tinycontrol devices like LK2.X, LK3.X, LK4.X or tcPDU.
5
+ Author-email: Bartek Barszczewski <tinycontrol.software@gmail.com>
6
+ Keywords: tinycontrol,lk,tcpdu
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: License :: OSI Approved :: Apache Software License
10
+ Requires-Python: >=3.7
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: aiohttp<4,>=3.9.3
13
+ Requires-Dist: netifaces<1,>=0.11.0
14
+ Requires-Dist: requests<3,>=2.31.0
15
+ Requires-Dist: tftpy<1,>=0.8.2
16
+
17
+ # tinyToolsLib
18
+
19
+ Set of tools for use with tinycontrol devices like LK2.X, LK3.X, LK4.X or tcPDU.
20
+
21
+ ## Features
22
+
23
+ Easy to use functions for common actions with tinycontrol devices:
24
+
25
+ - Flashing firmware via TFTP (LK2.X, LK3.X).
26
+ - Flashing firmware via HTTP (LK4, tcPDU).
27
+ - Getting data from devices.
28
+ - Sending commands to devices (for common tasks like controlling OUTs, PWMs, etc.).
29
+ - Checking device version.
30
+
31
+ ## Usage
32
+
33
+ Discovering devices in network:
34
+
35
+ ```py
36
+ from tinytoolslib.discovery import run_discovery_all
37
+
38
+ devices = run_discovery_all()
39
+ for device in devices:
40
+ print('{ip_address:20}{name:20}{mac_address:20}{family:10}{hardware_version:10}{software_version:15}'.format_map(device))
41
+ ```
42
+
43
+ Flashing firmware:
44
+
45
+ ```py
46
+ from tinytoolslib.flash import get_latest_firmware, Flasher
47
+
48
+ success, data = get_latest_firmware(IP_ADDRESS, USERNAME, PASSWORD, DIRECTORY_FOR_FIRMWARE_FILES)
49
+ if success:
50
+ flasher = Flasher()
51
+ success = flasher.run(data['path'], IP_ADDRESS, USERNAME, PASSWORD)
52
+ ```
53
+
54
+ Working with tinycontrol devices:
55
+
56
+ ```py
57
+ from tinytoolslib.models import get_version
58
+
59
+ version_info = get_version(IP_ADDRESS, with_device=True)
60
+ if version_info:
61
+ device_model = version_info['device_model']
62
+ # Get reading from device
63
+ device_model.get_all()
64
+ # Control outputs OUT
65
+ device_model.set_out(1, 1)
66
+ ```
67
+
68
+ ## File structure
69
+
70
+ - constants.py - constants related to tinycontrol devices.
71
+ - discovery.py - functions for discovering devices in network via UDP broadcast. Works for LK2.X, LK3.5 SW 1.26+, LK4.0, tcPDU.
72
+ - exceptions.py - errors raised in this library.
73
+ - flash.py - functions for updating the firmware of devices. Includes both method: TFTP and HTTP.
74
+ - models.py - models for working with different device types.
75
+ - parsers.py - functions for working with data formats used on LKs.
76
+ - requests.py - base request functions.
@@ -0,0 +1,60 @@
1
+ # tinyToolsLib
2
+
3
+ Set of tools for use with tinycontrol devices like LK2.X, LK3.X, LK4.X or tcPDU.
4
+
5
+ ## Features
6
+
7
+ Easy to use functions for common actions with tinycontrol devices:
8
+
9
+ - Flashing firmware via TFTP (LK2.X, LK3.X).
10
+ - Flashing firmware via HTTP (LK4, tcPDU).
11
+ - Getting data from devices.
12
+ - Sending commands to devices (for common tasks like controlling OUTs, PWMs, etc.).
13
+ - Checking device version.
14
+
15
+ ## Usage
16
+
17
+ Discovering devices in network:
18
+
19
+ ```py
20
+ from tinytoolslib.discovery import run_discovery_all
21
+
22
+ devices = run_discovery_all()
23
+ for device in devices:
24
+ print('{ip_address:20}{name:20}{mac_address:20}{family:10}{hardware_version:10}{software_version:15}'.format_map(device))
25
+ ```
26
+
27
+ Flashing firmware:
28
+
29
+ ```py
30
+ from tinytoolslib.flash import get_latest_firmware, Flasher
31
+
32
+ success, data = get_latest_firmware(IP_ADDRESS, USERNAME, PASSWORD, DIRECTORY_FOR_FIRMWARE_FILES)
33
+ if success:
34
+ flasher = Flasher()
35
+ success = flasher.run(data['path'], IP_ADDRESS, USERNAME, PASSWORD)
36
+ ```
37
+
38
+ Working with tinycontrol devices:
39
+
40
+ ```py
41
+ from tinytoolslib.models import get_version
42
+
43
+ version_info = get_version(IP_ADDRESS, with_device=True)
44
+ if version_info:
45
+ device_model = version_info['device_model']
46
+ # Get reading from device
47
+ device_model.get_all()
48
+ # Control outputs OUT
49
+ device_model.set_out(1, 1)
50
+ ```
51
+
52
+ ## File structure
53
+
54
+ - constants.py - constants related to tinycontrol devices.
55
+ - discovery.py - functions for discovering devices in network via UDP broadcast. Works for LK2.X, LK3.5 SW 1.26+, LK4.0, tcPDU.
56
+ - exceptions.py - errors raised in this library.
57
+ - flash.py - functions for updating the firmware of devices. Includes both method: TFTP and HTTP.
58
+ - models.py - models for working with different device types.
59
+ - parsers.py - functions for working with data formats used on LKs.
60
+ - requests.py - base request functions.
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tinytoolslib"
7
+ version = "0.2.5"
8
+ authors = [
9
+ { name="Bartek Barszczewski", email="tinycontrol.software@gmail.com" },
10
+ ]
11
+ description = "Set of tools for use with Tinycontrol devices like LK2.X, LK3.X, LK4.X or tcPDU."
12
+ requires-python = ">=3.7"
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Operating System :: OS Independent",
16
+ "License :: OSI Approved :: Apache Software License",
17
+ ]
18
+ dependencies = [
19
+ "aiohttp >= 3.9.3, < 4",
20
+ "netifaces >= 0.11.0, < 1",
21
+ "requests >= 2.31.0, < 3",
22
+ "tftpy >= 0.8.2, < 1",
23
+ ]
24
+ readme = "README.md"
25
+ keywords = ["tinycontrol", "lk", "tcpdu"]
26
+
27
+ [tool.setuptools.packages]
28
+ find = {}
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ from .__version__ import __version__
@@ -0,0 +1 @@
1
+ __version__ = '0.2.5'
@@ -0,0 +1,14 @@
1
+ """Constants related to tinycontrol devices."""
2
+
3
+ # Discovery related stuff
4
+ LK_UDP_PORT = 30403
5
+ LK_UDP_BOOTLOADER_MSG = b"\x12\xf4\x81"
6
+ LK_UDP_DISCOVERY_MSG = b"Discovery: Who is out there?"
7
+
8
+ # Device families. PS and DCDC are pretty much the same as LK (UI differs)
9
+ FAMILY_LK = "LK"
10
+ FAMILY_PS = "PS" # Power socket
11
+ FAMILY_DCDC = "DCDC" # Converter DC/DC
12
+ FAMILY_TCPDU = "tcPDU"
13
+
14
+ FW_URL_TEMPLATE = "https://tinycontrol.pl/firmware/{}/latest/"
@@ -0,0 +1,167 @@
1
+ import concurrent.futures
2
+ import logging
3
+ import socket
4
+ import socketserver
5
+ import threading
6
+ import time
7
+
8
+ import netifaces
9
+
10
+ from tinytoolslib.constants import LK_UDP_DISCOVERY_MSG, LK_UDP_PORT
11
+ from tinytoolslib.models import detect_version, get_device_info
12
+
13
+
14
+ def get_ips():
15
+ """Return list of IPs to check."""
16
+ try:
17
+ gateways = netifaces.gateways()
18
+ interfaces = []
19
+ for key, value in gateways.items():
20
+ if key != "default":
21
+ for item in value:
22
+ interfaces.append(item[1])
23
+ addresses = set()
24
+ for interface in interfaces:
25
+ addresses_tmp = netifaces.ifaddresses(interface).get(2)
26
+ if addresses_tmp:
27
+ for addr in addresses_tmp:
28
+ addresses.add(addr["addr"])
29
+ except ValueError:
30
+ addresses = socket.gethostbyname_ex(socket.gethostname())[2]
31
+ return addresses
32
+
33
+
34
+ def run_discovery_single(address, timelimit=3):
35
+ """Run discovery for given address (tuple) in timelimit."""
36
+ devices = []
37
+ with DiscoveryServer(address, DiscoveryHandler) as server:
38
+ server_thread = threading.Thread(target=server.serve_forever)
39
+ server_thread.start()
40
+ time.sleep(timelimit)
41
+ server.shutdown()
42
+ devices = server.devices.copy()
43
+ del server_thread
44
+ return devices
45
+
46
+
47
+ def serve_forever(server):
48
+ """Wrapper for serve_forever method of server."""
49
+ server.serve_forever()
50
+
51
+
52
+ def run_discovery_all(timelimit=3, port=LK_UDP_PORT, version=2, addresses=None):
53
+ """Run discovery on all available addresses.
54
+
55
+ `version` - 1 and 2 are parallel, where time execution of 2
56
+ is closer to timelimit; 3 is sequential run;
57
+ """
58
+ if addresses is None:
59
+ addresses = [ip for ip in get_ips() if not ip.startswith("169.254")]
60
+ devices = []
61
+ if version == 1:
62
+ with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
63
+ servers = [
64
+ DiscoveryServer((address, port), DiscoveryHandler)
65
+ for address in addresses
66
+ ]
67
+ executor.map(serve_forever, servers)
68
+ time.sleep(timelimit)
69
+ for server in servers:
70
+ server.shutdown()
71
+ devices.extend(server.devices)
72
+ elif version == 2:
73
+ with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
74
+ servers = [
75
+ DiscoveryServerAuto(
76
+ (address, port), DiscoveryHandler, timelimit=timelimit
77
+ )
78
+ for address in addresses
79
+ ]
80
+ futures = {
81
+ executor.submit(server.serve_forever): server for server in servers
82
+ }
83
+ for future in concurrent.futures.as_completed(futures):
84
+ try:
85
+ data = future.result()
86
+ except Exception as exc:
87
+ logging.warning("discovery error: %s", str(exc))
88
+ else:
89
+ devices.extend(data)
90
+ else:
91
+ for address in addresses:
92
+ temp = run_discovery_single((address, port))
93
+ devices.extend(temp)
94
+ return devices
95
+
96
+
97
+ class DiscoveryHandler(socketserver.BaseRequestHandler):
98
+ """Handler for LK discovery server."""
99
+
100
+ def handle(self):
101
+ data = self.request[0].strip()
102
+ if self.server.server_address[0] != self.client_address[0]:
103
+ try:
104
+ device_response = data.decode(errors="ignore").splitlines()
105
+ device_data = {
106
+ "ip_address": self.client_address[0],
107
+ "name": device_response[0],
108
+ "mac_address": device_response[1].replace("-", ":"),
109
+ "software_version": None,
110
+ "hardware_version": None,
111
+ }
112
+ if len(device_response) == 4:
113
+ device_data["software_version"] = device_response[2]
114
+ device_data["hardware_version"] = device_response[3]
115
+ else:
116
+ # LK2.0/2.5 response do not include hardware_version, so detect it.
117
+ device_data["software_version"] = device_response[2][:-1]
118
+ device_data["hardware_version"] = detect_version(
119
+ device_data["software_version"]
120
+ )
121
+ device_data.update(
122
+ get_device_info(
123
+ device_data["hardware_version"],
124
+ device_data["software_version"],
125
+ asdict_=True,
126
+ )
127
+ )
128
+ self.server.devices.append(device_data)
129
+ except (UnicodeDecodeError, IndexError):
130
+ pass
131
+
132
+
133
+ class DiscoveryServer(socketserver.UDPServer):
134
+ """Server for finding LKs in networks."""
135
+
136
+ def __init__(self, *args, **kwargs):
137
+ super().__init__(*args, **kwargs)
138
+ self.devices = []
139
+
140
+ def server_activate(self):
141
+ """Send discovery message after activation."""
142
+ super().server_activate()
143
+ dst_ip = ".".join(self.server_address[0].split(".")[:3]) + ".255"
144
+ dst_port = self.server_address[1]
145
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
146
+ self.socket.sendto(LK_UDP_DISCOVERY_MSG, (dst_ip, dst_port))
147
+
148
+
149
+ class DiscoveryServerAuto(DiscoveryServer):
150
+ """DiscoveryServer that autmatically shuts down."""
151
+
152
+ def __init__(self, *args, **kwargs):
153
+ self.started_at = time.time()
154
+ self.timelimit = kwargs.pop("timelimit", 3)
155
+ super().__init__(*args, **kwargs)
156
+
157
+ def service_actions(self):
158
+ super().service_actions()
159
+ now = time.time()
160
+ if now - self.started_at >= self.timelimit:
161
+ if not self._BaseServer__shutdown_request:
162
+ self._BaseServer__shutdown_request = True
163
+
164
+ def serve_forever(self, *args, **kwargs):
165
+ """Return devices list after auto shutdown."""
166
+ super().serve_forever(*args, **kwargs)
167
+ return self.devices
@@ -0,0 +1,49 @@
1
+ """Exceptions for tinyToolsLib."""
2
+
3
+
4
+ class TinyToolsError(Exception):
5
+ """Generic tinyTools exception."""
6
+
7
+
8
+ class TinyToolsUnsupported(TinyToolsError):
9
+ """Unsupported action of tinycontrol device."""
10
+
11
+
12
+ # region Request related errors
13
+ class TinyToolsRequestError(TinyToolsError):
14
+ """Base exception for request errors."""
15
+
16
+
17
+ class TinyToolsRequestTimeout(TinyToolsRequestError):
18
+ """Request timed out while trying to connect to server."""
19
+
20
+
21
+ class TinyToolsRequestConnectionError(TinyToolsRequestError):
22
+ """Connection error occurred."""
23
+
24
+
25
+ class TinyToolsRequestSSLError(TinyToolsRequestConnectionError):
26
+ """An SSL error occurred."""
27
+
28
+
29
+ class TinyToolsRequestHTTPError(TinyToolsRequestError):
30
+ """HTTP error occurred while handling request(HTTP 4xx/5xx)."""
31
+
32
+
33
+ class TinyToolsRequestUnauthenticated(TinyToolsRequestHTTPError):
34
+ """Request requires authentication or is invalid (HTTP 401)."""
35
+
36
+
37
+ class TinyToolsRequestNotFound(TinyToolsRequestHTTPError):
38
+ """Server could not find the requested resource (HTTP 404)."""
39
+
40
+
41
+ class TinyToolsRequestInternalServerError(TinyToolsRequestHTTPError):
42
+ """Server has encountered a situation it does not know how to handle. (HTTP 500)."""
43
+
44
+
45
+ # endregion
46
+
47
+
48
+ class TinyToolsFlashError(TinyToolsError):
49
+ """tinyTools flashing exception."""