hm310t 0.1.0__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.
hm310t-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Joey Marino
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ include tests/conftest.py
hm310t-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,230 @@
1
+ Metadata-Version: 2.4
2
+ Name: hm310t
3
+ Version: 0.1.0
4
+ Summary: Control a Hanmatek HM310T bench power supply over Modbus RTU
5
+ Author-email: Joey Marino <joey.da3rd@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/joeyda3rd/hm310t
8
+ Project-URL: Repository, https://github.com/joeyda3rd/hm310t
9
+ Project-URL: Issues, https://github.com/joeyda3rd/hm310t/issues
10
+ Project-URL: Changelog, https://github.com/joeyda3rd/hm310t/blob/main/CHANGELOG.md
11
+ Keywords: hm310t,hanmatek,power-supply,bench-power-supply,modbus,modbus-rtu,psu,instrument-control
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: System :: Hardware :: Hardware Drivers
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: pymodbus<3.10,>=3.2.2
25
+ Requires-Dist: pyserial>=3.5
26
+ Dynamic: license-file
27
+
28
+ # Hanmatek Modbus Power Supply Python Library
29
+
30
+ This repository contains a Python library to interact with the Hanmatek HM310T power supply over the Modbus interface.
31
+ Learn how this is accomplished for interacting with similar devices below in [#further reading](#further-reading).
32
+
33
+ > This library is the packaged continuation of an earlier prototype, [`joeyda3rd/modbus-power-supply`](https://github.com/joeyda3rd/modbus-power-supply). That repository remains as the original proof of concept; active development happens here.
34
+
35
+ <img src="https://raw.githubusercontent.com/joeyda3rd/hm310t/main/OEM-docs/61osnNY3qPL._SL1500_.jpg" width="250"> <img src="https://raw.githubusercontent.com/joeyda3rd/hm310t/main/OEM-docs/81EDT-klVJL._SL1500_.jpg" width="250">
36
+
37
+ ⚠️ **Safety Warning**
38
+
39
+ The Hanmatek HM310T power supply is a device that can produce potentially dangerous levels of voltage and current. Always follow safety guidelines when working with electricity. Ensure your device is properly grounded and do not work on live circuits.
40
+
41
+ This software is provided "as is", without warranty of any kind, express or implied. Incorrect use of this software could lead to damage to your power supply or other equipment, or personal injury. Use this software responsibly and at your own risk.
42
+
43
+ ⚠️ **Use At Your Own Risk**
44
+
45
+ By using this software, you agree that the authors and maintainers of this software are not liable for any damage to equipment, or any personal injury, that may occur through normal or abnormal use of this software. Always double-check your work and never leave a powered device unattended.
46
+
47
+ ⚠️ **AI-Assisted Code**
48
+
49
+ Portions of this library were written with AI assistance and reviewed by multiple AI models and by human developers. That review does not guarantee correctness. Read the code and validate its behavior against your own hardware before relying on it — especially given the safety considerations above.
50
+
51
+ ## Requirements
52
+
53
+ - Python 3.10 or higher
54
+ - PyModbus 3.2.2 or newer, < 3.10 (3.2.2 was used for development; 3.10 renamed the `slave=` keyword to `device_id=`)
55
+ - pyserial
56
+ - A Hanmatek HM310T power supply connected to a PC by USB
57
+ - May work with other Hanmatek or Modbus enabled power supplies, requires you to reverse engineer the unit. See [further reading](#further-reading) below for technical details and register values. Note that this library verifies it is talking to a genuine HM310T at connect time (registers `0x0003` and `0x0005`) and refuses other models.
58
+
59
+ ## Installation
60
+
61
+ ```bash
62
+ pip install hm310t
63
+ ```
64
+
65
+ Or from source:
66
+
67
+ ```bash
68
+ git clone https://github.com/joeyda3rd/hm310t.git
69
+ cd hm310t
70
+ pip install .
71
+ ```
72
+
73
+ ## Usage
74
+
75
+ The API is property-based: setpoints and protection limits are plain attributes you read and assign; live readings and status come from small methods that return typed values.
76
+
77
+ ```python
78
+ from hm310t import PowerSupply
79
+
80
+ # port, baudrate=9600, slave=1, voltage_limit=30.0, current_limit=10.0
81
+ with PowerSupply(port="/dev/ttyUSB0") as psu:
82
+ # Set protection trip points first, while the output is still off.
83
+ psu.ovp = 6.0 # over-voltage protection, volts
84
+ psu.ocp = 2.0 # over-current protection, amps
85
+ psu.opp = 20.0 # over-power protection, watts
86
+
87
+ # Setpoints the output will regulate to.
88
+ psu.voltage = 5.0
89
+ psu.current = 1.0
90
+ print(f"Setpoint: {psu.voltage} V, {psu.current} A")
91
+
92
+ # CAUTION: this energizes the terminals.
93
+ psu.output_enabled = True
94
+ print(f"Output enabled: {psu.output_enabled}")
95
+
96
+ # One atomic read of the live output (reads ~0 with no load attached).
97
+ m = psu.read_measurement()
98
+ print(f"Measured: {m.voltage} V, {m.current} A, {m.power} W")
99
+
100
+ # Protection status is a typed struct with a .tripped convenience flag.
101
+ status = psu.read_protection_status()
102
+ print(f"Tripped: {status.tripped} ({status})")
103
+
104
+ # Always disable the output when you are done -- closing does NOT do it.
105
+ psu.output_enabled = False
106
+ ```
107
+
108
+ Runnable examples live in [`examples/`](examples): `basic_usage.py` (the script above, hardened) and `tui_dashboard.py`, a small curses dashboard.
109
+
110
+ <img src="https://raw.githubusercontent.com/joeyda3rd/hm310t/main/images/screenshot-psui.jpg" width="500">
111
+
112
+ ### API
113
+
114
+ Setpoints and protection limits are **read/write properties** (float unless noted):
115
+
116
+ | Property | Range | Description |
117
+ | -------- | ----- | ----------- |
118
+ | `voltage` | 0 – `voltage_limit` | Output voltage setpoint (V) |
119
+ | `current` | 0 – `current_limit` | Output current setpoint (A) |
120
+ | `ovp` | 0 – 30 | Over-voltage protection trip point (V) |
121
+ | `ocp` | 0 – 10 | Over-current protection trip point (A) |
122
+ | `opp` | 0 – 300 | Over-power protection trip point (W) |
123
+ | `output_enabled` | `bool` | Output on/off. Assigning anything but a real `bool` raises `TypeError` |
124
+ | `comm_address` | 1 – 250 (`int`) | Modbus slave address; setting it retargets the connection |
125
+ | `voltage_limit`, `current_limit` | read-only | Software ceilings set in the constructor |
126
+
127
+ Methods:
128
+
129
+ | Method | Returns |
130
+ | ------ | ------- |
131
+ | `read_measurement()` | `Measurement(voltage, current, power)` — one atomic read of the live output |
132
+ | `read_protection_status()` | `ProtectionStatus(is_ovp, is_ocp, is_opp, is_otp, is_scp)` with a `.tripped` property |
133
+ | `read_raw_register(address)` | `int` — escape hatch to read any holding register directly |
134
+ | `close()` | Closes the serial connection (see "Known limitations") |
135
+
136
+ `PowerSupply` is a context manager (`with PowerSupply(...) as psu:`). Out-of-range setpoints raise `OutOfRangeError`; communication failures raise `PowerSupplyCommunicationError`; a non-HM310T device raises `IncompatibleDeviceError`. All derive from `HM310TError`.
137
+
138
+ ### Known limitations
139
+
140
+ - **A clean close does not disable the output.** `close()` — and a *normal* exit from the `with` block — only closes the serial connection; the supply keeps sourcing at its setpoints. Leaving the output running is a legitimate bench workflow, so a clean close never forces it off. (Exception: if the `with` block exits because of an *error*, `__exit__` disables the output as a fail-safe.) Assign `output_enabled = False` explicitly when you want it off.
141
+ - **HM310T only.** The connect-time identity check (registers `0x0003` = 3010 and `0x0005` = 0x0233) rejects other models in the family (e.g. the HM305), whose ratings or decimal scaling differ.
142
+ - **Multi-register writes use FC16.** OPP and the 32-bit reads/writes use Modbus function code 16 (write-multiple-registers). The OEM doc claims only FC03/FC06 are supported, but FC16 is verified working on the real unit; a firmware that rejects it will raise rather than silently misbehave.
143
+ - **Protection-status bits are not hardware-verified.** `read_protection_status()` decodes the OVP/OCP/OPP/OTP/SCP bits per the OEM doc and the original driver, but confirming each bit would require deliberately tripping each protection, which the suite does not do. Treat `.tripped` as advisory, not a safety interlock.
144
+ - **Not thread-safe.** A `PowerSupply` drives a single serial handle with no internal locking; do not share one instance across threads without providing your own mutex.
145
+
146
+ ## Contributing
147
+
148
+ Contributions are welcome! Please open an issue if you encounter a bug or have a feature request. If you want to contribute code, please open a pull request.
149
+
150
+ ## License
151
+
152
+ This library is licensed under the MIT license.
153
+
154
+ ---
155
+ ## Further Reading
156
+
157
+ When reverse engineering a power supply with a modbus interface, either over serial or other communication protocol, it's going to be essential to know the register addresses for the various I/O and the function code. In this case, we got lucky and the OEM provided that documentation.
158
+ It's possible to learn these by using a script to brute force read and write (and read) each address from 1 to 9999 (see `tools/scan_registers.py` in code) and/or sniffing the unencrypted traffic of OEM software. It's important to understand the Modbus protocol register addressing.
159
+
160
+ In the Modbus protocol, there are four types of data that can be accessed, each with its own address space:
161
+
162
+ 1. Coils (also known as Discrete Outputs): Addresses 00001 to 09999
163
+ 2. Discrete Inputs: Addresses 10001 to 19999
164
+ 3. Input Registers: Addresses 30001 to 39999
165
+ 4. Holding Registers: Addresses 40001 to 49999
166
+
167
+ Each of these address spaces can contain up to 10,000 addresses, for a total of 40,000 addresses. However, not all devices will use all of these addresses. The actual number of addresses used will depend on the specific device and its configuration.
168
+
169
+ It's also worth noting that in the Modbus protocol, addresses are often represented in a zero-based format. For example, the first holding register is often referred to as register 40001 in documentation, but in the actual Modbus messages, it would be referred to as holding register 0.
170
+
171
+ In our case the entirety of the registers we accessed were in the holding registers space. The use of holding registers is common in Modbus devices, including power supplies, because holding registers can be read from and written to, making them versatile for various types of data. However, it's not guaranteed that every Modbus power supply will only use holding registers.
172
+
173
+ The specific Modbus registers used, and their purpose, can vary widely between different devices and manufacturers. Some devices might use input registers to provide read-only data, or coils and discrete inputs for binary data.
174
+
175
+ The best source of information about which registers are used by a particular device is the device's Modbus map or register map, which is usually provided in the device's documentation or manual. This map will list all the Modbus addresses used by the device, along with a description of the data stored at each address.
176
+
177
+ It's important to know what programming protocol and communication protocol are being used with your device as there are others besides Modbus or serial.
178
+
179
+ [Documentation provided by the OEM](OEM-docs/Modbus.pdf) (This was included on a CD provided by OEM)
180
+
181
+ The registers will accept read (03) and write (06) instructions.
182
+
183
+ Registers from documentation
184
+ | Number | Function | Type | Decimal Places Capacity | Read/Write | Register Address |
185
+ | ------ | -------- | ---- | ----------------------- | ---------- | ---------------- |
186
+ | 0 | Output On/Off | Boolean | 0 | r,w | 0x0001 |
187
+ | 1 | Protect Status | Struct | 0 | r | 0x0002 |
188
+ | 2 | Specification | unsigned short | 0 | r | 0x0003 |
189
+ | 3 | Tail Classification | hexadecimal | 0 | r | 0x0004 |
190
+ | 4 | Decimal Point Values | hexadecimal | 0 | r | 0x0005 |
191
+ | 5 | Voltage Display Value | unsigned short | 2 | r | 0x0010 |
192
+ | 6 | Current Display Value | unsigned short | 3 | r | 0x0011 |
193
+ | 7 | Power Display Value | 2 integers? | 3 | r | 0x0012,0x0013 |
194
+ | 9 | Set Voltage | unsigned short | 2 | r,w | 0x0030 |
195
+ | 10 | Set Current | unsigned short | 3 | r,w | 0x0031 |
196
+ | 12 | Set OVP | unsigned short | 2 | r,w | 0x0020 |
197
+ | 13 | Set OCP | unsigned short | 2 | r,w | 0x0021 |
198
+ | 14 | Set OPP | unsigned short? | 2 | r,w | 0x0022,0x0023 |
199
+ | 15 | Set Comm Address | byte (1-250) | 0 | r,w | 0x9999 |
200
+
201
+ **Notes**
202
+ #1 See bit field below from documentation.
203
+ #3 no idea
204
+ #4 when it's reading 0x0233 that equals voltage has 2 decimal places, current 3, power 3
205
+ #7, #14 Two 16 bit registers are used to make one 32 bit value.
206
+ #14 type (range as it's called in docs) says 0-65535 (unsigned short) but I question that since it's a combination of two registers like #7
207
+ #15 docs say the range is 1-250, not sure of the best type to use for that, although not using a type in python.
208
+
209
+ ```
210
+ // protection status bit
211
+
212
+ union _ST
213
+ {
214
+ struct
215
+ {
216
+ uint8_t isOVP:1;//Over voltage protection
217
+ uint8_t isOCP:1;//Over current protection
218
+ uint8_t isOPP:1;//Over power protection
219
+ uint8_t isOTP:1;//Over tempreture protection
220
+ uint8_t isSCP:1;//short-circuit protection
221
+ }OP;
222
+ uint8_t Dat;
223
+
224
+ ```
225
+
226
+ Some previous work on the topic
227
+
228
+ http://www.roedan.com/controlling-a-cheap-usb-power-supply/
229
+ https://bitbucket.org/roedan/powersupply/src/master/
230
+ http://nightflyerfireworks.com/home/fun-with-cheap-programable-power-supplies
hm310t-0.1.0/README.md ADDED
@@ -0,0 +1,203 @@
1
+ # Hanmatek Modbus Power Supply Python Library
2
+
3
+ This repository contains a Python library to interact with the Hanmatek HM310T power supply over the Modbus interface.
4
+ Learn how this is accomplished for interacting with similar devices below in [#further reading](#further-reading).
5
+
6
+ > This library is the packaged continuation of an earlier prototype, [`joeyda3rd/modbus-power-supply`](https://github.com/joeyda3rd/modbus-power-supply). That repository remains as the original proof of concept; active development happens here.
7
+
8
+ <img src="https://raw.githubusercontent.com/joeyda3rd/hm310t/main/OEM-docs/61osnNY3qPL._SL1500_.jpg" width="250"> <img src="https://raw.githubusercontent.com/joeyda3rd/hm310t/main/OEM-docs/81EDT-klVJL._SL1500_.jpg" width="250">
9
+
10
+ ⚠️ **Safety Warning**
11
+
12
+ The Hanmatek HM310T power supply is a device that can produce potentially dangerous levels of voltage and current. Always follow safety guidelines when working with electricity. Ensure your device is properly grounded and do not work on live circuits.
13
+
14
+ This software is provided "as is", without warranty of any kind, express or implied. Incorrect use of this software could lead to damage to your power supply or other equipment, or personal injury. Use this software responsibly and at your own risk.
15
+
16
+ ⚠️ **Use At Your Own Risk**
17
+
18
+ By using this software, you agree that the authors and maintainers of this software are not liable for any damage to equipment, or any personal injury, that may occur through normal or abnormal use of this software. Always double-check your work and never leave a powered device unattended.
19
+
20
+ ⚠️ **AI-Assisted Code**
21
+
22
+ Portions of this library were written with AI assistance and reviewed by multiple AI models and by human developers. That review does not guarantee correctness. Read the code and validate its behavior against your own hardware before relying on it — especially given the safety considerations above.
23
+
24
+ ## Requirements
25
+
26
+ - Python 3.10 or higher
27
+ - PyModbus 3.2.2 or newer, < 3.10 (3.2.2 was used for development; 3.10 renamed the `slave=` keyword to `device_id=`)
28
+ - pyserial
29
+ - A Hanmatek HM310T power supply connected to a PC by USB
30
+ - May work with other Hanmatek or Modbus enabled power supplies, requires you to reverse engineer the unit. See [further reading](#further-reading) below for technical details and register values. Note that this library verifies it is talking to a genuine HM310T at connect time (registers `0x0003` and `0x0005`) and refuses other models.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install hm310t
36
+ ```
37
+
38
+ Or from source:
39
+
40
+ ```bash
41
+ git clone https://github.com/joeyda3rd/hm310t.git
42
+ cd hm310t
43
+ pip install .
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ The API is property-based: setpoints and protection limits are plain attributes you read and assign; live readings and status come from small methods that return typed values.
49
+
50
+ ```python
51
+ from hm310t import PowerSupply
52
+
53
+ # port, baudrate=9600, slave=1, voltage_limit=30.0, current_limit=10.0
54
+ with PowerSupply(port="/dev/ttyUSB0") as psu:
55
+ # Set protection trip points first, while the output is still off.
56
+ psu.ovp = 6.0 # over-voltage protection, volts
57
+ psu.ocp = 2.0 # over-current protection, amps
58
+ psu.opp = 20.0 # over-power protection, watts
59
+
60
+ # Setpoints the output will regulate to.
61
+ psu.voltage = 5.0
62
+ psu.current = 1.0
63
+ print(f"Setpoint: {psu.voltage} V, {psu.current} A")
64
+
65
+ # CAUTION: this energizes the terminals.
66
+ psu.output_enabled = True
67
+ print(f"Output enabled: {psu.output_enabled}")
68
+
69
+ # One atomic read of the live output (reads ~0 with no load attached).
70
+ m = psu.read_measurement()
71
+ print(f"Measured: {m.voltage} V, {m.current} A, {m.power} W")
72
+
73
+ # Protection status is a typed struct with a .tripped convenience flag.
74
+ status = psu.read_protection_status()
75
+ print(f"Tripped: {status.tripped} ({status})")
76
+
77
+ # Always disable the output when you are done -- closing does NOT do it.
78
+ psu.output_enabled = False
79
+ ```
80
+
81
+ Runnable examples live in [`examples/`](examples): `basic_usage.py` (the script above, hardened) and `tui_dashboard.py`, a small curses dashboard.
82
+
83
+ <img src="https://raw.githubusercontent.com/joeyda3rd/hm310t/main/images/screenshot-psui.jpg" width="500">
84
+
85
+ ### API
86
+
87
+ Setpoints and protection limits are **read/write properties** (float unless noted):
88
+
89
+ | Property | Range | Description |
90
+ | -------- | ----- | ----------- |
91
+ | `voltage` | 0 – `voltage_limit` | Output voltage setpoint (V) |
92
+ | `current` | 0 – `current_limit` | Output current setpoint (A) |
93
+ | `ovp` | 0 – 30 | Over-voltage protection trip point (V) |
94
+ | `ocp` | 0 – 10 | Over-current protection trip point (A) |
95
+ | `opp` | 0 – 300 | Over-power protection trip point (W) |
96
+ | `output_enabled` | `bool` | Output on/off. Assigning anything but a real `bool` raises `TypeError` |
97
+ | `comm_address` | 1 – 250 (`int`) | Modbus slave address; setting it retargets the connection |
98
+ | `voltage_limit`, `current_limit` | read-only | Software ceilings set in the constructor |
99
+
100
+ Methods:
101
+
102
+ | Method | Returns |
103
+ | ------ | ------- |
104
+ | `read_measurement()` | `Measurement(voltage, current, power)` — one atomic read of the live output |
105
+ | `read_protection_status()` | `ProtectionStatus(is_ovp, is_ocp, is_opp, is_otp, is_scp)` with a `.tripped` property |
106
+ | `read_raw_register(address)` | `int` — escape hatch to read any holding register directly |
107
+ | `close()` | Closes the serial connection (see "Known limitations") |
108
+
109
+ `PowerSupply` is a context manager (`with PowerSupply(...) as psu:`). Out-of-range setpoints raise `OutOfRangeError`; communication failures raise `PowerSupplyCommunicationError`; a non-HM310T device raises `IncompatibleDeviceError`. All derive from `HM310TError`.
110
+
111
+ ### Known limitations
112
+
113
+ - **A clean close does not disable the output.** `close()` — and a *normal* exit from the `with` block — only closes the serial connection; the supply keeps sourcing at its setpoints. Leaving the output running is a legitimate bench workflow, so a clean close never forces it off. (Exception: if the `with` block exits because of an *error*, `__exit__` disables the output as a fail-safe.) Assign `output_enabled = False` explicitly when you want it off.
114
+ - **HM310T only.** The connect-time identity check (registers `0x0003` = 3010 and `0x0005` = 0x0233) rejects other models in the family (e.g. the HM305), whose ratings or decimal scaling differ.
115
+ - **Multi-register writes use FC16.** OPP and the 32-bit reads/writes use Modbus function code 16 (write-multiple-registers). The OEM doc claims only FC03/FC06 are supported, but FC16 is verified working on the real unit; a firmware that rejects it will raise rather than silently misbehave.
116
+ - **Protection-status bits are not hardware-verified.** `read_protection_status()` decodes the OVP/OCP/OPP/OTP/SCP bits per the OEM doc and the original driver, but confirming each bit would require deliberately tripping each protection, which the suite does not do. Treat `.tripped` as advisory, not a safety interlock.
117
+ - **Not thread-safe.** A `PowerSupply` drives a single serial handle with no internal locking; do not share one instance across threads without providing your own mutex.
118
+
119
+ ## Contributing
120
+
121
+ Contributions are welcome! Please open an issue if you encounter a bug or have a feature request. If you want to contribute code, please open a pull request.
122
+
123
+ ## License
124
+
125
+ This library is licensed under the MIT license.
126
+
127
+ ---
128
+ ## Further Reading
129
+
130
+ When reverse engineering a power supply with a modbus interface, either over serial or other communication protocol, it's going to be essential to know the register addresses for the various I/O and the function code. In this case, we got lucky and the OEM provided that documentation.
131
+ It's possible to learn these by using a script to brute force read and write (and read) each address from 1 to 9999 (see `tools/scan_registers.py` in code) and/or sniffing the unencrypted traffic of OEM software. It's important to understand the Modbus protocol register addressing.
132
+
133
+ In the Modbus protocol, there are four types of data that can be accessed, each with its own address space:
134
+
135
+ 1. Coils (also known as Discrete Outputs): Addresses 00001 to 09999
136
+ 2. Discrete Inputs: Addresses 10001 to 19999
137
+ 3. Input Registers: Addresses 30001 to 39999
138
+ 4. Holding Registers: Addresses 40001 to 49999
139
+
140
+ Each of these address spaces can contain up to 10,000 addresses, for a total of 40,000 addresses. However, not all devices will use all of these addresses. The actual number of addresses used will depend on the specific device and its configuration.
141
+
142
+ It's also worth noting that in the Modbus protocol, addresses are often represented in a zero-based format. For example, the first holding register is often referred to as register 40001 in documentation, but in the actual Modbus messages, it would be referred to as holding register 0.
143
+
144
+ In our case the entirety of the registers we accessed were in the holding registers space. The use of holding registers is common in Modbus devices, including power supplies, because holding registers can be read from and written to, making them versatile for various types of data. However, it's not guaranteed that every Modbus power supply will only use holding registers.
145
+
146
+ The specific Modbus registers used, and their purpose, can vary widely between different devices and manufacturers. Some devices might use input registers to provide read-only data, or coils and discrete inputs for binary data.
147
+
148
+ The best source of information about which registers are used by a particular device is the device's Modbus map or register map, which is usually provided in the device's documentation or manual. This map will list all the Modbus addresses used by the device, along with a description of the data stored at each address.
149
+
150
+ It's important to know what programming protocol and communication protocol are being used with your device as there are others besides Modbus or serial.
151
+
152
+ [Documentation provided by the OEM](OEM-docs/Modbus.pdf) (This was included on a CD provided by OEM)
153
+
154
+ The registers will accept read (03) and write (06) instructions.
155
+
156
+ Registers from documentation
157
+ | Number | Function | Type | Decimal Places Capacity | Read/Write | Register Address |
158
+ | ------ | -------- | ---- | ----------------------- | ---------- | ---------------- |
159
+ | 0 | Output On/Off | Boolean | 0 | r,w | 0x0001 |
160
+ | 1 | Protect Status | Struct | 0 | r | 0x0002 |
161
+ | 2 | Specification | unsigned short | 0 | r | 0x0003 |
162
+ | 3 | Tail Classification | hexadecimal | 0 | r | 0x0004 |
163
+ | 4 | Decimal Point Values | hexadecimal | 0 | r | 0x0005 |
164
+ | 5 | Voltage Display Value | unsigned short | 2 | r | 0x0010 |
165
+ | 6 | Current Display Value | unsigned short | 3 | r | 0x0011 |
166
+ | 7 | Power Display Value | 2 integers? | 3 | r | 0x0012,0x0013 |
167
+ | 9 | Set Voltage | unsigned short | 2 | r,w | 0x0030 |
168
+ | 10 | Set Current | unsigned short | 3 | r,w | 0x0031 |
169
+ | 12 | Set OVP | unsigned short | 2 | r,w | 0x0020 |
170
+ | 13 | Set OCP | unsigned short | 2 | r,w | 0x0021 |
171
+ | 14 | Set OPP | unsigned short? | 2 | r,w | 0x0022,0x0023 |
172
+ | 15 | Set Comm Address | byte (1-250) | 0 | r,w | 0x9999 |
173
+
174
+ **Notes**
175
+ #1 See bit field below from documentation.
176
+ #3 no idea
177
+ #4 when it's reading 0x0233 that equals voltage has 2 decimal places, current 3, power 3
178
+ #7, #14 Two 16 bit registers are used to make one 32 bit value.
179
+ #14 type (range as it's called in docs) says 0-65535 (unsigned short) but I question that since it's a combination of two registers like #7
180
+ #15 docs say the range is 1-250, not sure of the best type to use for that, although not using a type in python.
181
+
182
+ ```
183
+ // protection status bit
184
+
185
+ union _ST
186
+ {
187
+ struct
188
+ {
189
+ uint8_t isOVP:1;//Over voltage protection
190
+ uint8_t isOCP:1;//Over current protection
191
+ uint8_t isOPP:1;//Over power protection
192
+ uint8_t isOTP:1;//Over tempreture protection
193
+ uint8_t isSCP:1;//short-circuit protection
194
+ }OP;
195
+ uint8_t Dat;
196
+
197
+ ```
198
+
199
+ Some previous work on the topic
200
+
201
+ http://www.roedan.com/controlling-a-cheap-usb-power-supply/
202
+ https://bitbucket.org/roedan/powersupply/src/master/
203
+ http://nightflyerfireworks.com/home/fun-with-cheap-programable-power-supplies
@@ -0,0 +1,71 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "hm310t"
7
+ version = "0.1.0"
8
+ description = "Control a Hanmatek HM310T bench power supply over Modbus RTU"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ authors = [{ name = "Joey Marino", email = "joey.da3rd@gmail.com" }]
12
+ requires-python = ">=3.10"
13
+ dependencies = [
14
+ "pymodbus>=3.2.2,<3.10",
15
+ "pyserial>=3.5",
16
+ ]
17
+ keywords = [
18
+ "hm310t",
19
+ "hanmatek",
20
+ "power-supply",
21
+ "bench-power-supply",
22
+ "modbus",
23
+ "modbus-rtu",
24
+ "psu",
25
+ "instrument-control",
26
+ ]
27
+ classifiers = [
28
+ "Development Status :: 4 - Beta",
29
+ "Intended Audience :: Science/Research",
30
+ "Programming Language :: Python :: 3",
31
+ "Programming Language :: Python :: 3.10",
32
+ "Programming Language :: Python :: 3.11",
33
+ "Programming Language :: Python :: 3.12",
34
+ "Programming Language :: Python :: 3.13",
35
+ "Topic :: System :: Hardware :: Hardware Drivers",
36
+ "Typing :: Typed",
37
+ ]
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/joeyda3rd/hm310t"
41
+ Repository = "https://github.com/joeyda3rd/hm310t"
42
+ Issues = "https://github.com/joeyda3rd/hm310t/issues"
43
+ Changelog = "https://github.com/joeyda3rd/hm310t/blob/main/CHANGELOG.md"
44
+
45
+ [tool.setuptools.packages.find]
46
+ where = ["src"]
47
+
48
+ [tool.setuptools.package-data]
49
+ hm310t = ["py.typed"]
50
+
51
+ [tool.pytest.ini_options]
52
+ markers = [
53
+ "hardware: needs a real HM310T attached (default port /dev/ttyUSB0, override with PSU_TEST_PORT)",
54
+ "requires_load: needs a real HM310T plus a physical load wired to the output terminals; opt in with PSU_LOAD_ATTACHED=1",
55
+ ]
56
+
57
+ [tool.ruff]
58
+ line-length = 100
59
+ target-version = "py310"
60
+
61
+ [tool.ruff.lint]
62
+ select = ["E", "F", "I"]
63
+
64
+ [tool.mypy]
65
+ python_version = "3.10"
66
+ strict = true
67
+ files = ["src"]
68
+
69
+ [[tool.mypy.overrides]]
70
+ module = "pymodbus.*"
71
+ ignore_missing_imports = true
hm310t-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,22 @@
1
+ """Control a Hanmatek HM310T power supply over Modbus RTU."""
2
+
3
+ from .client import Measurement, PowerSupply, ProtectionStatus
4
+ from .exceptions import (
5
+ HM310TError,
6
+ IncompatibleDeviceError,
7
+ OutOfRangeError,
8
+ PowerSupplyCommunicationError,
9
+ )
10
+
11
+ __version__ = "0.1.0"
12
+
13
+ __all__ = [
14
+ "PowerSupply",
15
+ "Measurement",
16
+ "ProtectionStatus",
17
+ "HM310TError",
18
+ "PowerSupplyCommunicationError",
19
+ "OutOfRangeError",
20
+ "IncompatibleDeviceError",
21
+ "__version__",
22
+ ]