Compare commits

...

6 Commits

Author SHA1 Message Date
d38864740b flake: Add pytec runnables to devShell
Makes it possible to directly run `plot` and `autotune` in the devShell.
2024-11-18 11:12:33 +08:00
6ec05808ce flake: Introduce pytec to PYTHONPATH in devShell
For easier testing of pytec client code in the shell.
2024-11-18 10:40:54 +08:00
9af86be674 pytec: Remove artificial report mode in client
Encourage polling usage instead, as shown in example.
2024-11-16 13:11:59 +08:00
eabc7f6a12 flake: Register the pytec Python package 2024-11-11 17:11:37 +08:00
52e35d2a98 flake: Format with nixfmt-rfc-style
Also set up formatter so that `nix fmt` formats.
2024-11-04 18:38:08 +08:00
f1da910c11 pytec: Complete client
Expose all available Thermostat commands to the pytec client, and add
disconnect functionality.
2024-11-04 18:03:50 +08:00
5 changed files with 115 additions and 32 deletions

View File

@ -2,14 +2,22 @@
description = "Firmware for the Sinara 8451 Thermostat";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
inputs.rust-overlay = {
inputs.rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, rust-overlay }:
outputs =
{
self,
nixpkgs,
rust-overlay,
}:
let
pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ (import rust-overlay) ]; };
pkgs = import nixpkgs {
system = "x86_64-linux";
overlays = [ (import rust-overlay) ];
};
rust = pkgs.rust-bin.stable."1.66.0".default.override {
extensions = [ "rust-src" ];
@ -25,7 +33,7 @@
version = "0.0.0";
src = self;
cargoLock = {
cargoLock = {
lockFile = ./Cargo.lock;
outputHashes = {
"stm32-eth-0.2.0" = "sha256-48RpZgagUqgVeKm7GXdk3Oo0v19ScF9Uby0nTFlve2o=";
@ -49,9 +57,34 @@
dontFixup = true;
auditable = false;
};
in {
pytec = pkgs.python3Packages.buildPythonPackage {
pname = "pytec";
version = "0.0.0";
src = "${self}/pytec";
propagatedBuildInputs = with pkgs.python3Packages; [
numpy
matplotlib
];
};
pytec-dev-wrappers = pkgs.runCommandNoCC "pytec-dev-wrappers" { } ''
mkdir -p $out/bin
for program in ${self}/pytec/*.py; do
if [ -x $program ]; then
progname=`basename -s .py $program`
outname=$out/bin/$progname
echo "#!${pkgs.bash}/bin/bash" >> $outname
echo "exec python3 -m $progname \"\$@\"" >> $outname
chmod 755 $outname
fi
done
'';
in
{
packages.x86_64-linux = {
inherit thermostat;
inherit thermostat pytec;
default = thermostat;
};
@ -61,12 +94,25 @@
devShells.x86_64-linux.default = pkgs.mkShellNoCC {
name = "thermostat-dev-shell";
packages = with pkgs; [
rust llvm
openocd dfu-util rlwrap
] ++ (with python3Packages; [
numpy matplotlib
packages =
with pkgs;
[
rust
llvm
openocd
dfu-util
rlwrap
pytec-dev-wrappers
]
++ (with python3Packages; [
numpy
matplotlib
]);
shellHook = ''
export PYTHONPATH=`git rev-parse --show-toplevel`/pytec:$PYTHONPATH
'';
};
formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt-rfc-style;
};
}
}

0
pytec/autotune.py Normal file → Executable file
View File

View File

@ -1,3 +1,4 @@
import time
from pytec.client import Client
tec = Client() #(host="localhost", port=6667)
@ -7,5 +8,6 @@ print(tec.get_pid())
print(tec.get_output())
print(tec.get_postfilter())
print(tec.get_b_parameter())
for data in tec.report_mode():
print(data)
while True:
print(tec.get_report())
time.sleep(0.05)

0
pytec/plot.py Normal file → Executable file
View File

View File

@ -1,7 +1,7 @@
import socket
import json
import logging
import time
class CommandError(Exception):
pass
@ -12,6 +12,10 @@ class Client:
self._lines = [""]
self._check_zero_limits()
def disconnect(self):
self._socket.shutdown(socket.SHUT_RDWR)
self._socket.close()
def _check_zero_limits(self):
output_report = self.get_output()
for output_channel in output_report:
@ -110,18 +114,18 @@ class Client:
"""
return self._get_conf("postfilter")
def report_mode(self):
"""Start reporting measurement values
def get_report(self):
"""Obtain one-time report on measurement values
Example of yielded data::
{'channel': 0,
'time': 2302524,
'interval': 0.12
'adc': 0.6199188965423515,
'sens': 6138.519310282602,
'temperature': 36.87032392655527,
'pid_engaged': True,
'i_set': 2.0635816680889123,
'vref': 1.494,
'dac_value': 2.527790834044456,
'dac_feedback': 2.523,
'i_tec': 2.331,
@ -129,16 +133,19 @@ class Client:
'tec_u_meas': 2.5340000000000003,
'pid_output': 2.067581958092247}
"""
while True:
self._socket.sendall("report\n".encode('utf-8'))
line = self._read_line()
if not line:
break
try:
yield json.loads(line)
except json.decoder.JSONDecodeError:
pass
time.sleep(0.05)
return self._get_conf("report")
def get_ipv4(self):
"""Get the IPv4 settings of the Thermostat"""
return self._command("ipv4")
def get_fan(self):
"""Get Thermostat current fan settings"""
return self._command("fan")
def get_hwrev(self):
"""Get Thermostat hardware revision"""
return self._command("hwrev")
def set_param(self, topic, channel, field="", value=""):
"""Set configuration parameters
@ -163,10 +170,38 @@ class Client:
self.set_param("pid", channel, "target", value=target)
self.set_param("output", channel, "pid")
def save_config(self):
def save_config(self, channel=""):
"""Save current configuration to EEPROM"""
self._command("save")
self._command("save", channel)
if channel != "":
self._read_line() # read the extra {}
def load_config(self):
def load_config(self, channel=""):
"""Load current configuration from EEPROM"""
self._command("load")
self._command("load", channel)
if channel != "":
self._read_line() # read the extra {}
def reset(self):
"""Reset the device"""
self._socket.sendall("reset".encode("utf-8"))
self.disconnect() # resetting ends the TCP session, disconnect anyway
def enter_dfu_mode(self):
"""Reset device and enters USB device firmware update (DFU) mode"""
self._socket.sendall("dfu".encode("utf-8"))
self.disconnect() # resetting ends the TCP session, disconnect anyway
def set_ipv4(self, address, netmask, gateway=""):
"""Configure IPv4 address, netmask length, and optional default gateway"""
self._command("ipv4", f"{address}/{netmask}", gateway)
def set_fan(self, power=None):
"""Set fan power with values from 1 to 100. If omitted, set according to fcurve"""
if power is None:
power = "auto"
self._command("fan", power)
def set_fcurve(self, a=1.0, b=0.0, c=0.0):
"""Set fan controller curve coefficients"""
self._command("fcurve", a, b, c)