Compare commits

...

11 Commits

Author SHA1 Message Date
ff718ffdef Control Panel: Use codenames for parameters
Without cosmetic changes, avoid embedding display text in the code.

For displayed string representations, use the `title` key, or for
`ListParameter`s, use the dictionary mapping method instead.
2025-04-14 15:31:29 +08:00
dd9e5fe195 Control Panel: Nest readings into its own group
This should be more intuitive to first-time users, as there is explicit
indication to what the reading values are, corresponding to the plotted
values.
2025-03-31 17:55:36 +08:00
a5c40f706a Revert "Use inline-asm feature in cortex-m"
This reverts commit 9305d766d1aa52b8c2997cd38d75a24b2700acfd.
2025-03-31 13:08:18 +08:00
c8af8cb61f PyThermostat GUI: Fix max_v to only have unit "V"
Since most users do not need to limit load voltage with accuracy of less
than 1mV.
2025-03-26 22:19:45 +08:00
23ce99d6d8 Set max_v maximum: 4 V -> 4.3 V
Align with the MAX1968's hardware capabilities, as it can achieve a
maximum voltage of 4.3 V across output pins.
2025-03-26 22:17:17 +08:00
d594c93166 Rename GUI to Control Panel
Change the command name to thermostat_control_panel, and the Nix Flake
app name to simply control_panel.
2025-03-26 13:23:19 +08:00
e3abe9384a PyThermostat GUI: No crushing paramtree spinboxes
It might not be the case on some themes, but on the default Qt theme the
spinboxes are a bit too short for the containing numbers. See
https://github.com/pyqtgraph/pyqtgraph/issues/701.
2025-03-17 21:54:42 +08:00
33ef98c5a8 PyThermostat GUI: Fix broken thermistor settings
Corresponds to the s-h -> b-p change in
069d79180255e7e83358180f95692f0f5d93a9fb.
2025-03-17 21:47:07 +08:00
9305d766d1 Use inline-asm feature in cortex-m
Reduces function call overhead, since external assembly can be avoided.
2025-03-17 21:46:34 +08:00
1f406fad38 Update cortex-m to 0.7 2025-03-17 21:46:34 +08:00
e8b217d0fc flake: Update Nixpkgs to nixos-24.11 2025-03-10 10:21:09 +08:00
10 changed files with 170 additions and 117 deletions

18
Cargo.lock generated
View File

@ -99,15 +99,15 @@ dependencies = [
"aligned", "aligned",
"bare-metal 0.2.5", "bare-metal 0.2.5",
"bitfield", "bitfield",
"cortex-m 0.7.4", "cortex-m 0.7.7",
"volatile-register", "volatile-register",
] ]
[[package]] [[package]]
name = "cortex-m" name = "cortex-m"
version = "0.7.4" version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ff967e867ca14eba0c34ac25cd71ea98c678e741e3915d923999bb2fe7c826" checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9"
dependencies = [ dependencies = [
"bare-metal 0.2.5", "bare-metal 0.2.5",
"bitfield", "bitfield",
@ -173,7 +173,7 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bffa6c1454368a6aa4811ae60964c38e6996d397ff8095a8b9211b1c1f749bc" checksum = "6bffa6c1454368a6aa4811ae60964c38e6996d397ff8095a8b9211b1c1f749bc"
dependencies = [ dependencies = [
"cortex-m 0.7.4", "cortex-m 0.6.7",
] ]
[[package]] [[package]]
@ -371,7 +371,7 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d55dedd501dfd02514646e0af4d7016ce36bc12ae177ef52056989966a1eec" checksum = "c3d55dedd501dfd02514646e0af4d7016ce36bc12ae177ef52056989966a1eec"
dependencies = [ dependencies = [
"cortex-m 0.7.4", "cortex-m 0.6.7",
"cortex-m-semihosting", "cortex-m-semihosting",
] ]
@ -520,7 +520,7 @@ version = "0.2.0"
source = "git+https://github.com/stm32-rs/stm32-eth.git?rev=3759c5c9#3759c5c99c0ab69bb71759030766bc0fba0b6cde" source = "git+https://github.com/stm32-rs/stm32-eth.git?rev=3759c5c9#3759c5c99c0ab69bb71759030766bc0fba0b6cde"
dependencies = [ dependencies = [
"aligned", "aligned",
"cortex-m 0.7.4", "cortex-m 0.7.7",
"smoltcp", "smoltcp",
"stm32f4xx-hal", "stm32f4xx-hal",
"volatile-register", "volatile-register",
@ -533,7 +533,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da3d56009c8f32e4f208dbea17df72484154d1040a8969b75d8c73eb7b18fe8f" checksum = "da3d56009c8f32e4f208dbea17df72484154d1040a8969b75d8c73eb7b18fe8f"
dependencies = [ dependencies = [
"bare-metal 0.2.5", "bare-metal 0.2.5",
"cortex-m 0.7.4", "cortex-m 0.6.7",
"cortex-m-rt 0.6.13", "cortex-m-rt 0.6.13",
"vcell", "vcell",
] ]
@ -546,7 +546,7 @@ checksum = "3a06fde2dd27c0ba934c9e69b62af66eb1c20dbb6d741b187a763912e9892d13"
dependencies = [ dependencies = [
"bare-metal 1.0.0", "bare-metal 1.0.0",
"cast", "cast",
"cortex-m 0.7.4", "cortex-m 0.7.7",
"cortex-m-rt 0.7.1", "cortex-m-rt 0.7.1",
"embedded-dma", "embedded-dma",
"embedded-hal", "embedded-hal",
@ -587,7 +587,7 @@ dependencies = [
"bare-metal 1.0.0", "bare-metal 1.0.0",
"bit_field", "bit_field",
"byteorder", "byteorder",
"cortex-m 0.6.7", "cortex-m 0.7.7",
"cortex-m-log", "cortex-m-log",
"cortex-m-rt 0.6.13", "cortex-m-rt 0.6.13",
"eeprom24x", "eeprom24x",

View File

@ -18,7 +18,7 @@ panic-halt = "1.0"
panic-semihosting = { version = "0.5", optional = true } panic-semihosting = { version = "0.5", optional = true }
log = "0.4" log = "0.4"
bare-metal = "1" bare-metal = "1"
cortex-m = "0.6" cortex-m = "0.7"
cortex-m-rt = { version = "0.6", features = ["device"] } cortex-m-rt = { version = "0.6", features = ["device"] }
cortex-m-log = { version = "0.6", features = ["log-integration"] } cortex-m-log = { version = "0.6", features = ["log-integration"] }
stm32f4xx-hal = { version = "=0.10.1", features = ["rt", "stm32f427", "usb_fs"] } stm32f4xx-hal = { version = "=0.10.1", features = ["rt", "stm32f427", "usb_fs"] }

View File

@ -71,11 +71,11 @@ openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -c "program target/thumbv
A GUI has been developed for easy configuration and plotting of key parameters. A GUI has been developed for easy configuration and plotting of key parameters.
The Python GUI program is located at pythermostat/pythermostat/thermostat_qt.py, and is developed based on the Python libraries PyQt and PyQtGraph. The GUI can be configured and The Python GUI program is located at pythermostat/pythermostat/control_panel.py, and is developed based on the Python libraries PyQt and PyQtGraph. The GUI can be configured and
launched automatically by running: launched automatically by running:
``` ```
nix run .#thermostat_gui nix run .#control_panel
``` ```
## Command Line Usage ## Command Line Usage
@ -109,7 +109,7 @@ formatted as line-delimited JSON.
| `output` | Show current output settings | | `output` | Show current output settings |
| `output <0/1> max_i_pos <amp>` | Set maximum positive output current, clamped to [0, 2] | | `output <0/1> max_i_pos <amp>` | Set maximum positive output current, clamped to [0, 2] |
| `output <0/1> max_i_neg <amp>` | Set maximum negative output current, clamped to [0, 2] | | `output <0/1> max_i_neg <amp>` | Set maximum negative output current, clamped to [0, 2] |
| `output <0/1> max_v <volt>` | Set maximum output voltage, clamped to [0, 4] | | `output <0/1> max_v <volt>` | Set maximum output voltage, clamped to [0, 4.3] |
| `output <0/1> i_set <amp>` | Disengage PID, set fixed output current, clamped to [-2, 2] | | `output <0/1> i_set <amp>` | Disengage PID, set fixed output current, clamped to [-2, 2] |
| `output <0/1> polarity <normal/reversed>` | Set output current polarity, with 'normal' being the front panel polarity | | `output <0/1> polarity <normal/reversed>` | Set output current polarity, with 'normal' being the front panel polarity |
| `output <0/1> pid` | Let output current to be controlled by the PID | | `output <0/1> pid` | Let output current to be controlled by the PID |

14
flake.lock generated
View File

@ -2,16 +2,16 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1722791413, "lastModified": 1740865531,
"narHash": "sha256-rCTrlCWvHzMCNcKxPE3Z/mMK2gDZ+BvvpEVyRM4tKmU=", "narHash": "sha256-h00vGIh/jxcGl8aWdfnVRD74KuLpyY3mZgMFMy7iKIc=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "8b5b6723aca5a51edf075936439d9cd3947b7b2c", "rev": "5ef6c425980847c78a80d759abc476e941a9bf42",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-24.05", "ref": "nixos-24.11",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
@ -29,11 +29,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1719281921, "lastModified": 1740969088,
"narHash": "sha256-LIBMfhM9pMOlEvBI757GOK5l0R58SRi6YpwfYMbf4yc=", "narHash": "sha256-BajboqzFnDhxVT0SXTDKVJCKtFP96lZXccBlT/43mao=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "b6032d3a404d8a52ecfc8571ff0c26dfbe221d07", "rev": "20fdb02098fdda9a25a2939b975abdd7bc03f62d",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -1,7 +1,7 @@
{ {
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.11";
inputs.rust-overlay = { inputs.rust-overlay = {
url = "github:oxalica/rust-overlay"; url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
@ -64,7 +64,10 @@
format = "pyproject"; format = "pyproject";
src = "${self}/pythermostat"; src = "${self}/pythermostat";
nativeBuildInputs = [ pkgs.qt6.wrapQtAppsHook ]; nativeBuildInputs = [
pkgs.python3Packages.setuptools
pkgs.qt6.wrapQtAppsHook
];
propagatedBuildInputs = propagatedBuildInputs =
[ pkgs.qt6.qtbase ] [ pkgs.qt6.qtbase ]
++ (with pkgs.python3Packages; [ ++ (with pkgs.python3Packages; [
@ -78,7 +81,7 @@
dontWrapQtApps = true; dontWrapQtApps = true;
postFixup = '' postFixup = ''
wrapQtApp "$out/bin/thermostat_qt" wrapQtApp "$out/bin/thermostat_control_panel"
''; '';
}; };
@ -103,9 +106,9 @@
default = thermostat; default = thermostat;
}; };
apps.x86_64-linux.thermostat_gui = { apps.x86_64-linux.control_panel = {
type = "app"; type = "app";
program = "${self.packages.x86_64-linux.pythermostat}/bin/thermostat_qt"; program = "${pythermostat}/bin/thermostat_control_panel";
}; };
hydraJobs = { hydraJobs = {

View File

@ -20,7 +20,7 @@ dependencies = [
[project.gui-scripts] [project.gui-scripts]
thermostat_plot = "pythermostat.plot:main" thermostat_plot = "pythermostat.plot:main"
thermostat_qt = "pythermostat.thermostat_qt:main" thermostat_control_panel = "pythermostat.control_panel:main"
[project.scripts] [project.scripts]
thermostat_autotune = "pythermostat.autotune:main" thermostat_autotune = "pythermostat.autotune:main"

View File

@ -1,4 +1,4 @@
"""GUI for the Sinara 8451 Thermostat""" """GUI Control Panel for the Sinara 8451 Thermostat"""
import asyncio import asyncio
import logging import logging

View File

@ -84,15 +84,15 @@ class CtrlPanel(QObject):
self.params[i].setValue = self._setValue self.params[i].setValue = self._setValue
self.params[i].sigTreeStateChanged.connect(self.send_command) self.params[i].sigTreeStateChanged.connect(self.send_command)
self.params[i].child("Save to flash").sigActivated.connect( self.params[i].child("save").sigActivated.connect(
partial(self.save_settings, i) partial(self.save_settings, i)
) )
self.params[i].child("Load from flash").sigActivated.connect( self.params[i].child("load").sigActivated.connect(
partial(self.load_settings, i) partial(self.load_settings, i)
) )
self.params[i].child( self.params[i].child("pid", "pid_autotune", "run_pid").sigActivated.connect(
"PID Config", "PID Auto Tune", "Run" partial(self.pid_auto_tune_request, i)
).sigActivated.connect(partial(self.pid_auto_tune_request, i)) )
self.thermostat.pid_update.connect(self.update_pid) self.thermostat.pid_update.connect(self.update_pid)
self.thermostat.report_update.connect(self.update_report) self.thermostat.report_update.connect(self.update_report)
@ -146,13 +146,13 @@ class CtrlPanel(QObject):
# Handle thermostat command irregularities # Handle thermostat command irregularities
match inner_param.name(), new_value: match inner_param.name(), new_value:
case "Postfilter Rate", None: case "rate", None:
thermostat_param = thermostat_param.copy() thermostat_param = thermostat_param.copy()
thermostat_param["field"] = "off" thermostat_param["field"] = "off"
new_value = "" new_value = ""
case "Control Method", "Constant Current": case "control_method", "constant_current":
return return
case "Control Method", "Temperature PID": case "control_method", "temperature_pid":
new_value = "" new_value = ""
inner_param.setOpts(lock=True) inner_param.setOpts(lock=True)
@ -170,23 +170,23 @@ class CtrlPanel(QObject):
for settings in pid_settings: for settings in pid_settings:
channel = settings["channel"] channel = settings["channel"]
with QSignalBlocker(self.params[channel]): with QSignalBlocker(self.params[channel]):
self.params[channel].child("PID Config", "Kp").setValue( self.params[channel].child("pid", "kp").setValue(
settings["parameters"]["kp"] settings["parameters"]["kp"]
) )
self.params[channel].child("PID Config", "Ki").setValue( self.params[channel].child("pid", "ki").setValue(
settings["parameters"]["ki"] settings["parameters"]["ki"]
) )
self.params[channel].child("PID Config", "Kd").setValue( self.params[channel].child("pid", "kd").setValue(
settings["parameters"]["kd"] settings["parameters"]["kd"]
) )
self.params[channel].child( self.params[channel].child(
"PID Config", "PID Output Clamping", "Minimum" "pid", "pid_output_clamping", "output_min"
).setValue(settings["parameters"]["output_min"] * 1000) ).setValue(settings["parameters"]["output_min"] * 1000)
self.params[channel].child( self.params[channel].child(
"PID Config", "PID Output Clamping", "Maximum" "pid", "pid_output_clamping", "output_max"
).setValue(settings["parameters"]["output_max"] * 1000) ).setValue(settings["parameters"]["output_max"] * 1000)
self.params[channel].child( self.params[channel].child(
"Output Config", "Control Method", "Set Temperature" "output", "control_method", "target"
).setValue(settings["target"]) ).setValue(settings["target"])
@pyqtSlot(list) @pyqtSlot(list)
@ -194,18 +194,18 @@ class CtrlPanel(QObject):
for settings in report_data: for settings in report_data:
channel = settings["channel"] channel = settings["channel"]
with QSignalBlocker(self.params[channel]): with QSignalBlocker(self.params[channel]):
self.params[channel].child("Output Config", "Control Method").setValue( self.params[channel].child("output", "control_method").setValue(
"Temperature PID" if settings["pid_engaged"] else "Constant Current" "temperature_pid" if settings["pid_engaged"] else "constant_current"
) )
self.params[channel].child( self.params[channel].child(
"Output Config", "Control Method", "Set Current" "output", "control_method", "i_set"
).setValue(settings["i_set"] * 1000) ).setValue(settings["i_set"] * 1000)
if settings["temperature"] is not None: if settings["temperature"] is not None:
self.params[channel].child("Temperature").setValue( self.params[channel].child("readings", "temperature").setValue(
settings["temperature"] settings["temperature"]
) )
if settings["tec_i"] is not None: if settings["tec_i"] is not None:
self.params[channel].child("Current through TEC").setValue( self.params[channel].child("readings", "tec_i").setValue(
settings["tec_i"] * 1000 settings["tec_i"] * 1000
) )
@ -214,13 +214,13 @@ class CtrlPanel(QObject):
for sh_param in sh_data: for sh_param in sh_data:
channel = sh_param["channel"] channel = sh_param["channel"]
with QSignalBlocker(self.params[channel]): with QSignalBlocker(self.params[channel]):
self.params[channel].child("Thermistor Config", "T₀").setValue( self.params[channel].child("thermistor", "t0").setValue(
sh_param["params"]["t0"] - 273.15 sh_param["params"]["t0"] - 273.15
) )
self.params[channel].child("Thermistor Config", "R₀").setValue( self.params[channel].child("thermistor", "r0").setValue(
sh_param["params"]["r0"] sh_param["params"]["r0"]
) )
self.params[channel].child("Thermistor Config", "B").setValue( self.params[channel].child("thermistor", "b").setValue(
sh_param["params"]["b"] sh_param["params"]["b"]
) )
@ -229,39 +229,35 @@ class CtrlPanel(QObject):
for output_params in output_data: for output_params in output_data:
channel = output_params["channel"] channel = output_params["channel"]
with QSignalBlocker(self.params[channel]): with QSignalBlocker(self.params[channel]):
self.params[channel].child( self.params[channel].child("output", "limits", "max_v").setValue(
"Output Config", "Limits", "Max Voltage Difference" output_params["max_v"]
).setValue(output_params["max_v"]) )
self.params[channel].child( self.params[channel].child("output", "limits", "max_i_pos").setValue(
"Output Config", "Limits", "Max Cooling Current" output_params["max_i_pos"] * 1000
).setValue(output_params["max_i_pos"] * 1000) )
self.params[channel].child( self.params[channel].child("output", "limits", "max_i_neg").setValue(
"Output Config", "Limits", "Max Heating Current" output_params["max_i_neg"] * 1000
).setValue(output_params["max_i_neg"] * 1000) )
@pyqtSlot(list) @pyqtSlot(list)
def update_postfilter(self, postfilter_data): def update_postfilter(self, postfilter_data):
for postfilter_params in postfilter_data: for postfilter_params in postfilter_data:
channel = postfilter_params["channel"] channel = postfilter_params["channel"]
with QSignalBlocker(self.params[channel]): with QSignalBlocker(self.params[channel]):
self.params[channel].child( self.params[channel].child("thermistor", "rate").setValue(
"Thermistor Config", "Postfilter Rate" postfilter_params["rate"]
).setValue(postfilter_params["rate"]) )
def update_pid_autotune(self, ch, state): def update_pid_autotune(self, ch, state):
match state: match state:
case PIDAutotuneState.OFF: case PIDAutotuneState.OFF:
self.change_params_title( self.change_params_title(ch, ("pid", "pid_autotune", "run_pid"), "Run")
ch, ("PID Config", "PID Auto Tune", "Run"), "Run"
)
case ( case (
PIDAutotuneState.READY PIDAutotuneState.READY
| PIDAutotuneState.RELAY_STEP_UP | PIDAutotuneState.RELAY_STEP_UP
| PIDAutotuneState.RELAY_STEP_DOWN | PIDAutotuneState.RELAY_STEP_DOWN
): ):
self.change_params_title( self.change_params_title(ch, ("pid", "pid_autotune", "run_pid"), "Stop")
ch, ("PID Config", "PID Auto Tune", "Run"), "Stop"
)
case PIDAutotuneState.SUCCEEDED: case PIDAutotuneState.SUCCEEDED:
self.info_box.display_info_box( self.info_box.display_info_box(
"PID Autotune Success", "PID Autotune Success",

View File

@ -1,38 +1,50 @@
{ {
"ctrl_panel": [ "ctrl_panel": [
{ {
"name": "Temperature", "name": "readings",
"type": "float", "title": "Readings",
"format": "{value:.4f} °C", "type": "group",
"readonly": true "children": [
{
"name": "temperature",
"title": "Temperature",
"type": "float",
"format": "{value:.4f} °C",
"readonly": true
},
{
"name": "tec_i",
"title": "Current through TEC",
"type": "float",
"suffix": "mA",
"decimals": 6,
"readonly": true
}
]
}, },
{ {
"name": "Current through TEC", "name": "output",
"type": "float", "title": "Output Config",
"suffix": "mA",
"decimals": 6,
"readonly": true
},
{
"name": "Output Config",
"expanded": true, "expanded": true,
"type": "group", "type": "group",
"children": [ "children": [
{ {
"name": "Control Method", "name": "control_method",
"title": "Control Method",
"type": "mutex", "type": "mutex",
"limits": [ "limits": {
"Constant Current", "Constant Current": "constant_current",
"Temperature PID" "Temperature PID": "temperature_pid"
], },
"value": "Constant Current", "value": "constant_current",
"thermostat:set_param": { "thermostat:set_param": {
"topic": "output", "topic": "output",
"field": "pid" "field": "pid"
}, },
"children": [ "children": [
{ {
"name": "Set Current", "name": "i_set",
"title": "Set Current",
"type": "float", "type": "float",
"value": 0, "value": 0,
"step": 100, "step": 100,
@ -43,6 +55,7 @@
"triggerOnShow": true, "triggerOnShow": true,
"decimals": 6, "decimals": 6,
"suffix": "mA", "suffix": "mA",
"compactHeight": false,
"thermostat:set_param": { "thermostat:set_param": {
"topic": "output", "topic": "output",
"field": "i_set" "field": "i_set"
@ -50,7 +63,8 @@
"lock": false "lock": false
}, },
{ {
"name": "Set Temperature", "name": "target",
"title": "Set Temperature",
"type": "float", "type": "float",
"value": 25, "value": 25,
"step": 0.1, "step": 0.1,
@ -59,6 +73,7 @@
300 300
], ],
"format": "{value:.4f} °C", "format": "{value:.4f} °C",
"compactHeight": false,
"thermostat:set_param": { "thermostat:set_param": {
"topic": "pid", "topic": "pid",
"field": "target" "field": "target"
@ -68,12 +83,14 @@
] ]
}, },
{ {
"name": "Limits", "name": "limits",
"title": "Limits",
"expanded": true, "expanded": true,
"type": "group", "type": "group",
"children": [ "children": [
{ {
"name": "Max Cooling Current", "name": "max_i_pos",
"title": "Max Cooling Current",
"type": "float", "type": "float",
"value": 0, "value": 0,
"step": 100, "step": 100,
@ -83,6 +100,7 @@
2000 2000
], ],
"suffix": "mA", "suffix": "mA",
"compactHeight": false,
"thermostat:set_param": { "thermostat:set_param": {
"topic": "output", "topic": "output",
"field": "max_i_pos" "field": "max_i_pos"
@ -90,7 +108,8 @@
"lock": false "lock": false
}, },
{ {
"name": "Max Heating Current", "name": "max_i_neg",
"title": "Max Heating Current",
"type": "float", "type": "float",
"value": 0, "value": 0,
"step": 100, "step": 100,
@ -100,6 +119,7 @@
2000 2000
], ],
"suffix": "mA", "suffix": "mA",
"compactHeight": false,
"thermostat:set_param": { "thermostat:set_param": {
"topic": "output", "topic": "output",
"field": "max_i_neg" "field": "max_i_neg"
@ -107,16 +127,18 @@
"lock": false "lock": false
}, },
{ {
"name": "Max Voltage Difference", "name": "max_v",
"title": "Max Voltage Difference",
"type": "float", "type": "float",
"value": 0, "value": 0,
"step": 0.1, "step": 0.1,
"decimals": 3,
"limits": [ "limits": [
0, 0,
5 4.3
], ],
"siPrefix": true,
"suffix": "V", "suffix": "V",
"compactHeight": false,
"thermostat:set_param": { "thermostat:set_param": {
"topic": "output", "topic": "output",
"field": "max_v" "field": "max_v"
@ -128,12 +150,14 @@
] ]
}, },
{ {
"name": "Thermistor Config", "name": "thermistor",
"title": "Thermistor Config",
"expanded": true, "expanded": true,
"type": "group", "type": "group",
"children": [ "children": [
{ {
"name": "T₀", "name": "t0",
"title": "T₀",
"type": "float", "type": "float",
"value": 25, "value": 25,
"step": 0.1, "step": 0.1,
@ -142,40 +166,46 @@
100 100
], ],
"format": "{value:.4f} °C", "format": "{value:.4f} °C",
"compactHeight": false,
"thermostat:set_param": { "thermostat:set_param": {
"topic": "s-h", "topic": "b-p",
"field": "t0" "field": "t0"
}, },
"lock": false "lock": false
}, },
{ {
"name": "R₀", "name": "r0",
"title": "R₀",
"type": "float", "type": "float",
"value": 10000, "value": 10000,
"step": 1, "step": 1,
"siPrefix": true, "siPrefix": true,
"suffix": "Ω", "suffix": "Ω",
"compactHeight": false,
"thermostat:set_param": { "thermostat:set_param": {
"topic": "s-h", "topic": "b-p",
"field": "r0" "field": "r0"
}, },
"lock": false "lock": false
}, },
{ {
"name": "B", "name": "b",
"title": "B",
"type": "float", "type": "float",
"value": 3950, "value": 3950,
"step": 1, "step": 1,
"suffix": "K", "suffix": "K",
"decimals": 4, "decimals": 4,
"compactHeight": false,
"thermostat:set_param": { "thermostat:set_param": {
"topic": "s-h", "topic": "b-p",
"field": "b" "field": "b"
}, },
"lock": false "lock": false
}, },
{ {
"name": "Postfilter Rate", "name": "rate",
"title": "Postfilter Rate",
"type": "list", "type": "list",
"value": 16.67, "value": 16.67,
"thermostat:set_param": { "thermostat:set_param": {
@ -194,15 +224,18 @@
] ]
}, },
{ {
"name": "PID Config", "name": "pid",
"title": "PID Config",
"expanded": true, "expanded": true,
"type": "group", "type": "group",
"children": [ "children": [
{ {
"name": "Kp", "name": "kp",
"title": "Kp",
"type": "float", "type": "float",
"step": 0.1, "step": 0.1,
"suffix": "", "suffix": "",
"compactHeight": false,
"thermostat:set_param": { "thermostat:set_param": {
"topic": "pid", "topic": "pid",
"field": "kp" "field": "kp"
@ -210,10 +243,12 @@
"lock": false "lock": false
}, },
{ {
"name": "Ki", "name": "ki",
"title": "Ki",
"type": "float", "type": "float",
"step": 0.1, "step": 0.1,
"suffix": "Hz", "suffix": "Hz",
"compactHeight": false,
"thermostat:set_param": { "thermostat:set_param": {
"topic": "pid", "topic": "pid",
"field": "ki" "field": "ki"
@ -221,10 +256,12 @@
"lock": false "lock": false
}, },
{ {
"name": "Kd", "name": "kd",
"title": "Kd",
"type": "float", "type": "float",
"step": 0.1, "step": 0.1,
"suffix": "s", "suffix": "s",
"compactHeight": false,
"thermostat:set_param": { "thermostat:set_param": {
"topic": "pid", "topic": "pid",
"field": "kd" "field": "kd"
@ -232,12 +269,14 @@
"lock": false "lock": false
}, },
{ {
"name": "PID Output Clamping", "name": "pid_output_clamping",
"title": "PID Output Clamping",
"expanded": true, "expanded": true,
"type": "group", "type": "group",
"children": [ "children": [
{ {
"name": "Minimum", "name": "output_min",
"title": "Minimum",
"type": "float", "type": "float",
"step": 100, "step": 100,
"limits": [ "limits": [
@ -246,6 +285,7 @@
], ],
"decimals": 6, "decimals": 6,
"suffix": "mA", "suffix": "mA",
"compactHeight": false,
"thermostat:set_param": { "thermostat:set_param": {
"topic": "pid", "topic": "pid",
"field": "output_min" "field": "output_min"
@ -253,7 +293,8 @@
"lock": false "lock": false
}, },
{ {
"name": "Maximum", "name": "output_max",
"title": "Maximum",
"type": "float", "type": "float",
"step": 100, "step": 100,
"limits": [ "limits": [
@ -262,6 +303,7 @@
], ],
"decimals": 6, "decimals": 6,
"suffix": "mA", "suffix": "mA",
"compactHeight": false,
"thermostat:set_param": { "thermostat:set_param": {
"topic": "pid", "topic": "pid",
"field": "output_max" "field": "output_max"
@ -271,20 +313,24 @@
] ]
}, },
{ {
"name": "PID Auto Tune", "name": "pid_autotune",
"title": "PID Auto Tune",
"expanded": false, "expanded": false,
"type": "group", "type": "group",
"children": [ "children": [
{ {
"name": "Target Temperature", "name": "target_temp",
"title": "Target Temperature",
"type": "float", "type": "float",
"value": 20, "value": 20,
"step": 0.1, "step": 0.1,
"format": "{value:.4f} °C", "format": "{value:.4f} °C",
"compactHeight": false,
"pid_autotune": "target_temp" "pid_autotune": "target_temp"
}, },
{ {
"name": "Test Current", "name": "test_current",
"title": "Test Current",
"type": "float", "type": "float",
"value": 0, "value": 0,
"decimals": 6, "decimals": 6,
@ -294,27 +340,33 @@
2000 2000
], ],
"suffix": "mA", "suffix": "mA",
"compactHeight": false,
"pid_autotune": "test_current" "pid_autotune": "test_current"
}, },
{ {
"name": "Temperature Swing", "name": "temp_swing",
"title": "Temperature Swing",
"type": "float", "type": "float",
"value": 1.5, "value": 1.5,
"step": 0.1, "step": 0.1,
"prefix": "±", "prefix": "±",
"format": "{value:.4f} °C", "format": "{value:.4f} °C",
"compactHeight": false,
"pid_autotune": "temp_swing" "pid_autotune": "temp_swing"
}, },
{ {
"name": "Lookback", "name": "lookback",
"title": "Lookback",
"type": "float", "type": "float",
"value": 3.0, "value": 3.0,
"step": 0.1, "step": 0.1,
"format": "{value:.4f} s", "format": "{value:.4f} s",
"compactHeight": false,
"pid_autotune": "lookback" "pid_autotune": "lookback"
}, },
{ {
"name": "Run", "name": "run_pid",
"title": "Run",
"type": "action", "type": "action",
"tip": "Run" "tip": "Run"
} }
@ -323,12 +375,14 @@
] ]
}, },
{ {
"name": "Save to flash", "name": "save",
"title": "Save to flash",
"type": "action", "type": "action",
"tip": "Save config to thermostat, applies on reset" "tip": "Save config to thermostat, applies on reset"
}, },
{ {
"name": "Load from flash", "name": "load",
"title": "Load from flash",
"type": "action", "type": "action",
"tip": "Load config from flash" "tip": "Load config from flash"
} }

View File

@ -55,7 +55,7 @@ pub const MAX_TEC_I: ElectricCurrent = ElectricCurrent {
pub const MAX_TEC_V: ElectricPotential = ElectricPotential { pub const MAX_TEC_V: ElectricPotential = ElectricPotential {
dimension: PhantomData, dimension: PhantomData,
units: PhantomData, units: PhantomData,
value: 4.0, value: 4.3,
}; };
// DAC chip outputs 0-5v, which is then passed through a resistor dividor to provide 0-3v range // DAC chip outputs 0-5v, which is then passed through a resistor dividor to provide 0-3v range
const DAC_OUT_V_MAX: ElectricPotential = ElectricPotential { const DAC_OUT_V_MAX: ElectricPotential = ElectricPotential {