forked from M-Labs/thermostat
Compare commits
6 Commits
5bea19be36
...
d38864740b
Author | SHA1 | Date | |
---|---|---|---|
d38864740b | |||
6ec05808ce | |||
9af86be674 | |||
eabc7f6a12 | |||
52e35d2a98 | |||
f1da910c11 |
70
flake.nix
70
flake.nix
@ -2,14 +2,22 @@
|
|||||||
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 = { self, nixpkgs, rust-overlay }:
|
outputs =
|
||||||
|
{
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
rust-overlay,
|
||||||
|
}:
|
||||||
let
|
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 {
|
rust = pkgs.rust-bin.stable."1.66.0".default.override {
|
||||||
extensions = [ "rust-src" ];
|
extensions = [ "rust-src" ];
|
||||||
@ -25,7 +33,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=";
|
||||||
@ -49,9 +57,34 @@
|
|||||||
dontFixup = true;
|
dontFixup = true;
|
||||||
auditable = false;
|
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 = {
|
packages.x86_64-linux = {
|
||||||
inherit thermostat;
|
inherit thermostat pytec;
|
||||||
default = thermostat;
|
default = thermostat;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -61,12 +94,25 @@
|
|||||||
|
|
||||||
devShells.x86_64-linux.default = pkgs.mkShellNoCC {
|
devShells.x86_64-linux.default = pkgs.mkShellNoCC {
|
||||||
name = "thermostat-dev-shell";
|
name = "thermostat-dev-shell";
|
||||||
packages = with pkgs; [
|
packages =
|
||||||
rust llvm
|
with pkgs;
|
||||||
openocd dfu-util rlwrap
|
[
|
||||||
] ++ (with python3Packages; [
|
rust
|
||||||
numpy matplotlib
|
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
0
pytec/autotune.py
Normal file → Executable file
@ -1,3 +1,4 @@
|
|||||||
|
import time
|
||||||
from pytec.client import Client
|
from pytec.client import Client
|
||||||
|
|
||||||
tec = Client() #(host="localhost", port=6667)
|
tec = Client() #(host="localhost", port=6667)
|
||||||
@ -7,5 +8,6 @@ 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())
|
||||||
for data in tec.report_mode():
|
while True:
|
||||||
print(data)
|
print(tec.get_report())
|
||||||
|
time.sleep(0.05)
|
||||||
|
0
pytec/plot.py
Normal file → Executable file
0
pytec/plot.py
Normal file → Executable 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,6 +12,10 @@ 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:
|
||||||
@ -110,18 +114,18 @@ class Client:
|
|||||||
"""
|
"""
|
||||||
return self._get_conf("postfilter")
|
return self._get_conf("postfilter")
|
||||||
|
|
||||||
def report_mode(self):
|
def get_report(self):
|
||||||
"""Start reporting measurement values
|
"""Obtain one-time report on 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,
|
||||||
@ -129,16 +133,19 @@ class Client:
|
|||||||
'tec_u_meas': 2.5340000000000003,
|
'tec_u_meas': 2.5340000000000003,
|
||||||
'pid_output': 2.067581958092247}
|
'pid_output': 2.067581958092247}
|
||||||
"""
|
"""
|
||||||
while True:
|
return self._get_conf("report")
|
||||||
self._socket.sendall("report\n".encode('utf-8'))
|
|
||||||
line = self._read_line()
|
def get_ipv4(self):
|
||||||
if not line:
|
"""Get the IPv4 settings of the Thermostat"""
|
||||||
break
|
return self._command("ipv4")
|
||||||
try:
|
|
||||||
yield json.loads(line)
|
def get_fan(self):
|
||||||
except json.decoder.JSONDecodeError:
|
"""Get Thermostat current fan settings"""
|
||||||
pass
|
return self._command("fan")
|
||||||
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
|
||||||
@ -163,10 +170,38 @@ 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):
|
def save_config(self, channel=""):
|
||||||
"""Save current configuration to EEPROM"""
|
"""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"""
|
"""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)
|
||||||
|
Loading…
Reference in New Issue
Block a user