Compare commits

..

7 Commits

Author SHA1 Message Date
d815a9ae55 flake: Add pytec runnables to devShell
Makes it possible to directly run `autotune`, `plot` and `test` in the
devShell.
2024-11-18 18:02:48 +08:00
c9defac87c flake: Introduce pytec to PYTHONPATH in devShell
For easier testing of pytec client code in the shell.
2024-11-18 18:02:29 +08:00
4beeec6021 PyThermostat: Remove all references to Pytec 2024-11-18 17:34:39 +08:00
6b8a5f5bb8 Rename the Pytec library to PyThermostat
Pytec is a misnomer, as the Thermostat is not limited to just
controlling TEC modules. The library also interfaces with and controls
the Thermostat itself, and not the TEC module directly.

See M-Labs/thermostat#149 (comment)
2024-11-18 16:22:57 +08:00
8dd58b364d README: Fix limits section 2024-11-18 14:01:51 +08:00
ae0d593139 pytec: Stop using client report mode in plot.py
Report mode has been removed from the client, stop using it.
2024-11-18 13:57:54 +08:00
adc25c9b2a pytec: Add hardware testing script
Eases the process of testing the hardware.

See #143.
2024-11-18 10:31:56 +08:00
10 changed files with 106 additions and 23 deletions

View File

@ -189,31 +189,30 @@ Testing heat flow direction with a low set current is recommended before install
### Limits ### Limits
Each MAX1968 TEC driver has analog/PWM inputs for setting Each channel has maximum value settings, for setting
output limits. output limits.
Use the `output` command to see current settings and maximum values. Use the `output` command to see them.
| Limit | Unit | Description | | Limit | Unit | Description |
| --- | :---: | --- | | --- | :---: | --- |
| `max_v` | Volts | Maximum voltage | | `max_v` | Volts | Maximum voltage |
| `max_i_pos` | Amperes | Maximum positive current | | `max_i_pos` | Amperes | Maximum positive current |
| `max_i_neg` | Amperes | Maximum negative current | | `max_i_neg` | Amperes | Maximum negative current |
| `i_set` | Amperes | (Not a limit; Open-loop mode) |
Example: set the maximum voltage of channel 0 to 1.5 V. Example: set the maximum voltage of channel 0 to 1.5 V.
``` ```
output 0 max_v 1.5 output 0 max_v 1.5
``` ```
Example: set the maximum negative current of channel 0 to -3 A. Example: set the maximum negative current of channel 0 to -2 A.
``` ```
output 0 max_i_neg 3 output 0 max_i_neg 2
``` ```
Example: set the maximum positive current of channel 1 to 3 A. Example: set the maximum positive current of channel 1 to 2 A.
``` ```
output 0 max_i_pos 3 output 1 max_i_pos 2
``` ```
### Open-loop mode ### Open-loop mode

View File

@ -13,7 +13,7 @@ When tuning Thermostat PID parameters, it is helpful to view the temperature, PI
To use the Python real-time plotting utility, run To use the Python real-time plotting utility, run
```shell ```shell
python pytec/plot.py python pythermostat/plot.py
``` ```
![default view](./assets/default%20view.png) ![default view](./assets/default%20view.png)
@ -44,12 +44,12 @@ Below are some general guidelines for manually tuning PID loops. Note that every
## Auto Tuning ## Auto Tuning
A PID auto tuning utility is provided in the Pytec library. The auto tuning utility drives the the load to a controlled oscillation, observes the ultimate gain and oscillation period and calculates a set of PID parameters. A PID auto tuning utility is provided in the PyThermostat library. The auto tuning utility drives the the load to a controlled oscillation, observes the ultimate gain and oscillation period and calculates a set of PID parameters.
To run the auto tuning utility, run To run the auto tuning utility, run
```shell ```shell
python pytec/autotune.py python pythermostat/autotune.py
``` ```
After some time, the auto tuning utility will output the auto tuning results, below is a sample output After some time, the auto tuning utility will output the auto tuning results, below is a sample output

View File

@ -58,10 +58,10 @@
auditable = false; auditable = false;
}; };
pytec = pkgs.python3Packages.buildPythonPackage { pythermostat = pkgs.python3Packages.buildPythonPackage {
pname = "pytec"; pname = "pythermostat";
version = "0.0.0"; version = "0.0.0";
src = "${self}/pytec"; src = "${self}/pythermostat";
propagatedBuildInputs = with pkgs.python3Packages; [ propagatedBuildInputs = with pkgs.python3Packages; [
numpy numpy
@ -69,9 +69,9 @@
]; ];
}; };
pytec-dev-wrappers = pkgs.runCommandNoCC "pytec-dev-wrappers" { } '' pythermostat-dev-wrappers = pkgs.runCommandNoCC "pythermostat-dev-wrappers" { } ''
mkdir -p $out/bin mkdir -p $out/bin
for program in ${self}/pytec/*.py; do for program in ${self}/pythermostat/*.py; do
if [ -x $program ]; then if [ -x $program ]; then
progname=`basename -s .py $program` progname=`basename -s .py $program`
outname=$out/bin/$progname outname=$out/bin/$progname
@ -84,7 +84,7 @@
in in
{ {
packages.x86_64-linux = { packages.x86_64-linux = {
inherit thermostat pytec; inherit thermostat pythermostat;
default = thermostat; default = thermostat;
}; };
@ -102,14 +102,14 @@
openocd openocd
dfu-util dfu-util
rlwrap rlwrap
pytec-dev-wrappers pythermostat-dev-wrappers
] ]
++ (with python3Packages; [ ++ (with python3Packages; [
numpy numpy
matplotlib matplotlib
]); ]);
shellHook = '' shellHook = ''
export PYTHONPATH=`git rev-parse --show-toplevel`/pytec:$PYTHONPATH export PYTHONPATH=`git rev-parse --show-toplevel`/pythermostat:$PYTHONPATH
''; '';
}; };

View File

@ -3,7 +3,7 @@ import logging
from collections import deque, namedtuple from collections import deque, namedtuple
from enum import Enum from enum import Enum
from pytec.client import Client from pythermostat.client import Client
# Based on hirshmann pid-autotune libiary # Based on hirshmann pid-autotune libiary
# See https://github.com/hirschmann/pid-autotune # See https://github.com/hirschmann/pid-autotune

View File

@ -1,5 +1,5 @@
import time import time
from pytec.client import Client from pythermostat.client import Client
tec = Client() #(host="localhost", port=6667) tec = Client() #(host="localhost", port=6667)
tec.set_param("b-p", 1, "t0", 20) tec.set_param("b-p", 1, "t0", 20)

View File

@ -1,8 +1,9 @@
import time
import numpy as np import numpy as np
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import matplotlib.animation as animation import matplotlib.animation as animation
from threading import Thread, Lock from threading import Thread, Lock
from pytec.client import Client from pythermostat.client import Client
TIME_WINDOW = 300.0 TIME_WINDOW = 300.0
@ -47,7 +48,8 @@ quit = False
def recv_data(tec): def recv_data(tec):
global last_packet_time global last_packet_time
for data in tec.report_mode(): while True:
data = tec.get_report()
ch0 = data[0] ch0 = data[0]
series_lock.acquire() series_lock.acquire()
try: try:
@ -61,6 +63,7 @@ def recv_data(tec):
if quit: if quit:
break break
time.sleep(0.05)
thread = Thread(target=recv_data, args=(tec,)) thread = Thread(target=recv_data, args=(tec,))
thread.start() thread.start()

View File

@ -1,7 +1,7 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
setup( setup(
name="pytec", name="pythermostat",
version="0.0", version="0.0",
author="M-Labs", author="M-Labs",
url="https://git.m-labs.hk/M-Labs/thermostat", url="https://git.m-labs.hk/M-Labs/thermostat",

81
pythermostat/test.py Executable file
View File

@ -0,0 +1,81 @@
import argparse
from contextlib import contextmanager
from pythermostat.client import Client
CHANNELS = 2
def get_argparser():
parser = argparse.ArgumentParser(description="Thermostat hardware testing script")
parser.add_argument("host", metavar="HOST", default="192.168.1.26", nargs="?")
parser.add_argument("port", metavar="PORT", default=23, nargs="?")
parser.add_argument(
"-r",
"--testing_resistance",
default=10_000,
help="Testing resistance value through SENS pin in Ohms",
)
parser.add_argument(
"-d",
"--deviation",
default=1,
help="Allowed deviation of resistance in percentage",
)
return parser
def main():
args = get_argparser().parse_args()
min_allowed_resistance = args.testing_resistance * (1 - args.deviation / 100)
max_allowed_resistance = args.testing_resistance * (1 + args.deviation / 100)
print(min_allowed_resistance, max_allowed_resistance)
thermostat = Client(args.host, args.port)
for channel in range(CHANNELS):
print(f"Channel {channel} is active")
print("Checking resistance through SENS input ....", end=" ")
sens_resistance = thermostat.get_report()[channel]["sens"]
if sens_resistance is not None:
print(sens_resistance, "Ω")
if min_allowed_resistance <= sens_resistance <= max_allowed_resistance:
print("PASSED")
else:
print("FAILED")
else:
print("Floating SENS input! Is the channel connected?")
with preserve_thermostat_output_settings(thermostat, channel):
test_output_settings = {
"max_i_pos": 2,
"max_i_neg": 2,
"max_v": 4,
"i_set": 0.1,
"polarity": "normal",
}
for field, value in test_output_settings.items():
thermostat.set_param("output", channel, field, value)
input(f"Check if channel {channel} current = 0.1 A, and press ENTER...")
input(f"Channel {channel} testing done, press ENTER to continue.")
print()
print("Testing complete.")
@contextmanager
def preserve_thermostat_output_settings(client, channel):
original_output_settings = client.get_output()[channel]
yield original_output_settings
for setting in "max_i_pos", "max_i_neg", "max_v", "i_set", "polarity":
client.set_param("output", channel, setting, original_output_settings[setting])
if __name__ == "__main__":
main()