1
0
forked from M-Labs/thermostat

Compare commits

..

2 Commits

Author SHA1 Message Date
5bea19be36 flake: Add pytec runnables to devShell
Makes it possible to directly run `plot` and `autotune` in the shell.
2024-11-04 16:56:29 +08:00
a7aa78a608 flake: Introduce pytec to PYTHONPATH in devShell
For easier testing of pytec client code in the shell.
2024-11-04 16:26:51 +08:00
3 changed files with 34 additions and 100 deletions

View File

@ -2,22 +2,14 @@
description = "Firmware for the Sinara 8451 Thermostat"; description = "Firmware for the Sinara 8451 Thermostat";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
inputs.rust-overlay = { inputs.rust-overlay = {
url = "github:oxalica/rust-overlay"; url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
outputs = outputs = { self, nixpkgs, rust-overlay }:
{
self,
nixpkgs,
rust-overlay,
}:
let let
pkgs = import nixpkgs { pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ (import rust-overlay) ]; };
system = "x86_64-linux";
overlays = [ (import rust-overlay) ];
};
rust = pkgs.rust-bin.stable."1.66.0".default.override { rust = pkgs.rust-bin.stable."1.66.0".default.override {
extensions = [ "rust-src" ]; extensions = [ "rust-src" ];
@ -33,7 +25,7 @@
version = "0.0.0"; version = "0.0.0";
src = self; src = self;
cargoLock = { cargoLock = {
lockFile = ./Cargo.lock; lockFile = ./Cargo.lock;
outputHashes = { outputHashes = {
"stm32-eth-0.2.0" = "sha256-48RpZgagUqgVeKm7GXdk3Oo0v19ScF9Uby0nTFlve2o="; "stm32-eth-0.2.0" = "sha256-48RpZgagUqgVeKm7GXdk3Oo0v19ScF9Uby0nTFlve2o=";
@ -58,17 +50,6 @@
auditable = false; auditable = false;
}; };
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" { } '' pytec-dev-wrappers = pkgs.runCommandNoCC "pytec-dev-wrappers" { } ''
mkdir -p $out/bin mkdir -p $out/bin
for program in ${self}/pytec/*.py; do for program in ${self}/pytec/*.py; do
@ -81,10 +62,9 @@
fi fi
done done
''; '';
in in {
{
packages.x86_64-linux = { packages.x86_64-linux = {
inherit thermostat pytec; inherit thermostat;
default = thermostat; default = thermostat;
}; };
@ -94,25 +74,16 @@
devShells.x86_64-linux.default = pkgs.mkShellNoCC { devShells.x86_64-linux.default = pkgs.mkShellNoCC {
name = "thermostat-dev-shell"; name = "thermostat-dev-shell";
packages = packages = with pkgs; [
with pkgs; rust llvm
[ openocd dfu-util rlwrap
rust pytec-dev-wrappers
llvm ] ++ (with python3Packages; [
openocd numpy matplotlib
dfu-util
rlwrap
pytec-dev-wrappers
]
++ (with python3Packages; [
numpy
matplotlib
]); ]);
shellHook = '' shellHook = ''
export PYTHONPATH=`git rev-parse --show-toplevel`/pytec:$PYTHONPATH export PYTHONPATH=`pwd`/pytec:$PYTHONPATH
''; '';
}; };
formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt-rfc-style;
}; };
} }

View File

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

View File

@ -1,7 +1,7 @@
import socket import socket
import json import json
import logging import logging
import time
class CommandError(Exception): class CommandError(Exception):
pass pass
@ -12,10 +12,6 @@ class Client:
self._lines = [""] self._lines = [""]
self._check_zero_limits() self._check_zero_limits()
def disconnect(self):
self._socket.shutdown(socket.SHUT_RDWR)
self._socket.close()
def _check_zero_limits(self): def _check_zero_limits(self):
output_report = self.get_output() output_report = self.get_output()
for output_channel in output_report: for output_channel in output_report:
@ -114,18 +110,18 @@ class Client:
""" """
return self._get_conf("postfilter") return self._get_conf("postfilter")
def get_report(self): def report_mode(self):
"""Obtain one-time report on measurement values """Start reporting measurement values
Example of yielded data:: Example of yielded data::
{'channel': 0, {'channel': 0,
'time': 2302524, 'time': 2302524,
'interval': 0.12
'adc': 0.6199188965423515, 'adc': 0.6199188965423515,
'sens': 6138.519310282602, 'sens': 6138.519310282602,
'temperature': 36.87032392655527, 'temperature': 36.87032392655527,
'pid_engaged': True, 'pid_engaged': True,
'i_set': 2.0635816680889123, 'i_set': 2.0635816680889123,
'vref': 1.494,
'dac_value': 2.527790834044456, 'dac_value': 2.527790834044456,
'dac_feedback': 2.523, 'dac_feedback': 2.523,
'i_tec': 2.331, 'i_tec': 2.331,
@ -133,19 +129,16 @@ class Client:
'tec_u_meas': 2.5340000000000003, 'tec_u_meas': 2.5340000000000003,
'pid_output': 2.067581958092247} 'pid_output': 2.067581958092247}
""" """
return self._get_conf("report") while True:
self._socket.sendall("report\n".encode('utf-8'))
def get_ipv4(self): line = self._read_line()
"""Get the IPv4 settings of the Thermostat""" if not line:
return self._command("ipv4") break
try:
def get_fan(self): yield json.loads(line)
"""Get Thermostat current fan settings""" except json.decoder.JSONDecodeError:
return self._command("fan") pass
time.sleep(0.05)
def get_hwrev(self):
"""Get Thermostat hardware revision"""
return self._command("hwrev")
def set_param(self, topic, channel, field="", value=""): def set_param(self, topic, channel, field="", value=""):
"""Set configuration parameters """Set configuration parameters
@ -170,38 +163,10 @@ class Client:
self.set_param("pid", channel, "target", value=target) self.set_param("pid", channel, "target", value=target)
self.set_param("output", channel, "pid") self.set_param("output", channel, "pid")
def save_config(self, channel=""): def save_config(self):
"""Save current configuration to EEPROM""" """Save current configuration to EEPROM"""
self._command("save", channel) self._command("save")
if channel != "":
self._read_line() # read the extra {}
def load_config(self, channel=""): def load_config(self):
"""Load current configuration from EEPROM""" """Load current configuration from EEPROM"""
self._command("load", channel) self._command("load")
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)