forked from M-Labs/kirdy
Compare commits
280 Commits
Author | SHA1 | Date | |
---|---|---|---|
26f12dbf77 | |||
388e6725ef | |||
f4c761c5ca | |||
e560d8f1eb | |||
0437c6e76e | |||
bad21806f8 | |||
cb2bc505c9 | |||
4f19d2c2be | |||
e8d3858fc9 | |||
9bec56ed6c | |||
51b82e0447 | |||
31e108a4b5 | |||
253f4410ee | |||
bd72c382b0 | |||
a512614de5 | |||
76f7875e3a | |||
0d64d62fb0 | |||
0aeffdbf7b | |||
89a1270c02 | |||
c2e78e6f17 | |||
86e6d3764e | |||
1111967a7b | |||
1896e2534b | |||
6d4b1b0574 | |||
0b5fda2cd9 | |||
b763350a8b | |||
838592c812 | |||
e632cbbfdd | |||
27bf573010 | |||
c267c30b89 | |||
c5826876a6 | |||
6782cda790 | |||
2fe2ef531b | |||
f2ad06ecae | |||
5166bb7ba8 | |||
9c611fc861 | |||
51913f2e2f | |||
c86d67b15c | |||
3d2294a90c | |||
3ba8b99084 | |||
af73ac8127 | |||
c214e4182d | |||
fb69ae3306 | |||
8c8ed12522 | |||
572e2dbc5d | |||
82c46e04d0 | |||
28f8c3c497 | |||
47bf166ecb | |||
1dcac25574 | |||
3f9a4bf140 | |||
201148fb21 | |||
a90031dd6c | |||
7b52072617 | |||
c241d34434 | |||
20c3d42dd7 | |||
aae89256c3 | |||
3344e7ab19 | |||
a76b8de994 | |||
3410a271fd | |||
8f38acd0f2 | |||
b95b9dcefa | |||
5e6a4c0646 | |||
ec5bf1d6b6 | |||
3737c2ed59 | |||
4cd328d98c | |||
af95de0f16 | |||
db2f76771a | |||
6f87cba9a6 | |||
80d94270a2 | |||
456691f79f | |||
0f3db7fec5 | |||
9002ca6992 | |||
92f6b83b16 | |||
da50f372d9 | |||
57bfd6d83c | |||
dacf9f0fa5 | |||
bfd8c0e43a | |||
dc97f42bd0 | |||
3813b79623 | |||
83a764889c | |||
c09da0db98 | |||
d5a620c76b | |||
d1660c6090 | |||
f35546b070 | |||
0dceb8c3c6 | |||
266110ba14 | |||
e8aaf5f66b | |||
70fed23c51 | |||
b8241d1f27 | |||
ded7dd7694 | |||
7fa4330797 | |||
7be35fe7f0 | |||
c5efc6ca57 | |||
b330f45260 | |||
554f292cab | |||
f303ab639a | |||
ba30575406 | |||
bfdb1f5066 | |||
5b35f32a42 | |||
2df22d5dcb | |||
b73eacd234 | |||
4a06a7579f | |||
9524601cb6 | |||
4116962175 | |||
0e087c6992 | |||
6b250aa1fd | |||
de80aedafc | |||
31a97bdeb4 | |||
784b8a357b | |||
ceb003e07e | |||
10873f4791 | |||
e160316bfc | |||
0a992c1dc8 | |||
ae74455f94 | |||
6bccbceb81 | |||
eaebdb390d | |||
489333c43d | |||
4288d465c9 | |||
09863353cd | |||
6d107d55cf | |||
d435dc06d8 | |||
898358f4e6 | |||
a13c6fa86c | |||
b1a1173075 | |||
af68b0e90a | |||
f8abfd4300 | |||
7d41edef20 | |||
607da8a822 | |||
7f06fc06fd | |||
67f9e65df8 | |||
7d2e14ef2f | |||
e525a3f354 | |||
8431e9f43d | |||
e9d5f4598a | |||
1867935047 | |||
99cf17f7e4 | |||
048245f674 | |||
0a01d299bc | |||
dbbd438e92 | |||
3ad7c417f6 | |||
c06491a8b2 | |||
0380c8d30b | |||
ed5cda6364 | |||
61624b0bd6 | |||
1480305c16 | |||
74c465d16f | |||
574abc2c2f | |||
a9dbff8250 | |||
8cd3f70721 | |||
b7231f48ff | |||
7f6a385e1d | |||
eff8adc184 | |||
a579e5c5d5 | |||
65b757ac3c | |||
911d9a7bc9 | |||
111d9a4226 | |||
a3885c365e | |||
a58b0954ec | |||
92c6cf12d4 | |||
ef2410f441 | |||
adfa4f5fa3 | |||
df939eb9a3 | |||
3aca712e1d | |||
4526536a48 | |||
f488786e1c | |||
3528d8a68f | |||
46393bacdb | |||
6fc800e562 | |||
76477065be | |||
b1fa0e51c8 | |||
9f82fa58f4 | |||
19341672a9 | |||
86a9fb039e | |||
a2bb390ae2 | |||
18dd0a7963 | |||
d1cc677668 | |||
c09ccc29cd | |||
c4135f6ac3 | |||
6ee45b4814 | |||
a8787430b1 | |||
ed785b7c85 | |||
30ab228b4b | |||
88cca12a60 | |||
8139ebdc1b | |||
5f83b73011 | |||
0642640da7 | |||
6dd1bf9301 | |||
d2efc02b6a | |||
89d415194d | |||
09b3765877 | |||
7a76325288 | |||
ffa5f4e8ff | |||
e9a396f001 | |||
412f5ec58b | |||
e29898f8f8 | |||
60a79d1780 | |||
b0edd3dba2 | |||
4c331dd319 | |||
6b05b2d851 | |||
f22ab430b8 | |||
4cd650367d | |||
de262b849d | |||
4132bfacd3 | |||
6096711d2c | |||
3d3d6f5cb5 | |||
172c3e1dfc | |||
ee0ed8ebe7 | |||
771f2813f7 | |||
4e0d14def2 | |||
f6677d874c | |||
d391e3a1fb | |||
edd30e94a0 | |||
ad731c2f15 | |||
9d8a553669 | |||
e22f424531 | |||
6af0f992d5 | |||
f50505feaf | |||
9ae867cd88 | |||
85b50bf824 | |||
07ea733b34 | |||
5bea3f0e5f | |||
af283b17ac | |||
c3022e9db1 | |||
c02181c80c | |||
095fe8ea69 | |||
a0b67cdb09 | |||
381f25f036 | |||
c768bdc93a | |||
0d12c902fc | |||
3cfdee917a | |||
5f582be143 | |||
2f7ca2a706 | |||
ff3d9b790a | |||
bc7bf9a6e7 | |||
ca110962f7 | |||
d0f226ce03 | |||
f49fd08c69 | |||
ccaf728c75 | |||
3ac287ace2 | |||
cdf900a5b6 | |||
af8d361b95 | |||
e355e83d28 | |||
285fc9b5d6 | |||
8c612fc0c9 | |||
42cd55645c | |||
d190b8b192 | |||
74325a3cee | |||
709eae8566 | |||
d3e3451d37 | |||
33ff0c3678 | |||
33d9cb45c4 | |||
8ae27725c6 | |||
20fc6d6fac | |||
654964831a | |||
27c7b5929e | |||
e48f1979f0 | |||
59f2385ecf | |||
d4e074cbd4 | |||
277796d2a4 | |||
be8bf0a8b6 | |||
b1123047c7 | |||
707fac0775 | |||
a99dde8b38 | |||
d1f7a20c15 | |||
58a8b15c40 | |||
590ba8171c | |||
0807f66b3c | |||
2953d4edde | |||
f2c026ffdd | |||
c7d36bc8d5 | |||
784fc03957 | |||
6cc2bc32c5 | |||
23ee568ea7 | |||
d3f3608136 | |||
4cf7b7fdf9 | |||
0179e7641a | |||
f6767b147b | |||
475fe28604 | |||
764a203dd8 | |||
f2b419f8d0 |
@ -1,12 +0,0 @@
|
||||
[target.thumbv7em-none-eabihf]
|
||||
runner = "gdb -q -x openocd.gdb"
|
||||
rustflags = [
|
||||
"-C", "link-arg=-Tlink.x",
|
||||
]
|
||||
|
||||
[build]
|
||||
# Pick ONE of these compilation targets
|
||||
# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
|
||||
# target = "thumbv7m-none-eabi" # Cortex-M3
|
||||
# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU)
|
||||
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
|
8
.cargo/config.toml
Normal file
8
.cargo/config.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[target.thumbv7em-none-eabihf]
|
||||
runner = "gdb -q -x openocd.gdb"
|
||||
rustflags = [
|
||||
"-C", "link-arg=-Tlink.x",
|
||||
]
|
||||
|
||||
[build]
|
||||
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,4 +1,4 @@
|
||||
target/
|
||||
result
|
||||
|
||||
*.pyc
|
||||
*.jdebug*
|
||||
|
60
BSD.md
Normal file
60
BSD.md
Normal file
@ -0,0 +1,60 @@
|
||||
# Building Rust embedded firmware on BSD
|
||||
|
||||
Using the Rust stable compiler from the BSD ports is mostly a matter of removing the arbitrary limitations that have been built into the Rust toolchain.
|
||||
|
||||
Get rid of the "nightly toolchain" check in cargo-xbuild:
|
||||
```
|
||||
diff --git a/src/lib.rs b/src/lib.rs
|
||||
index 204ad8f..9c61e39 100644
|
||||
--- a/src/lib.rs
|
||||
+++ b/src/lib.rs
|
||||
@@ -157,28 +157,11 @@ pub fn build(args: Args, command_name: &str, crate_config: Option<Config>) -> Re
|
||||
})
|
||||
})?;
|
||||
|
||||
- // We can't build sysroot with stable or beta due to unstable features
|
||||
let sysroot = rustc::sysroot(verbose)?;
|
||||
- let src = match meta.channel {
|
||||
- Channel::Dev => rustc::Src::from_env().ok_or(anyhow!(
|
||||
+ let src = rustc::Src::from_env().ok_or(anyhow!(
|
||||
"The XARGO_RUST_SRC env variable must be set and point to the \
|
||||
- Rust source directory when working with the 'dev' channel",
|
||||
- ))?,
|
||||
- Channel::Nightly => {
|
||||
- if let Some(src) = rustc::Src::from_env() {
|
||||
- src
|
||||
- } else {
|
||||
- sysroot.src()?
|
||||
- }
|
||||
- }
|
||||
- Channel::Stable | Channel::Beta => {
|
||||
- bail!(
|
||||
- "The sysroot can't be built for the {:?} channel. \
|
||||
- Switch to nightly.",
|
||||
- meta.channel
|
||||
- );
|
||||
- }
|
||||
- };
|
||||
+ Rust source directory",
|
||||
+ ))?;
|
||||
|
||||
let cmode = if let Some(triple) = args.target() {
|
||||
if triple == meta.host {
|
||||
```
|
||||
|
||||
Run ``cargo install --path .`` to install the modified cargo-xbuild
|
||||
|
||||
Get a copy of the Rust sources that corresponds to the installed compiler binary, and set up the environment accordingly:
|
||||
```
|
||||
> rustc --version
|
||||
rustc 1.72.1 (d5c2e9c34 2023-09-13) (built from a source tarball)
|
||||
> git clone --depth=1 --recurse-submodules --shallow-submodules https://github.com/rust-lang/rust --branch 1.72.1
|
||||
> export XARGO_RUST_SRC=`pwd`/rust/library
|
||||
```
|
||||
|
||||
Disable other arbitrary checks in the Rust compiler:
|
||||
```
|
||||
> export RUSTC_BOOTSTRAP=1
|
||||
```
|
||||
|
||||
And you can now simply run ``cargo xbuild`` to build the firmware.
|
782
Cargo.lock
generated
782
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
24
Cargo.toml
24
Cargo.toml
@ -15,23 +15,29 @@ default-target = "thumbv7em-none-eabihf"
|
||||
|
||||
[dependencies]
|
||||
panic-halt = "0.2.0"
|
||||
cortex-m = "0.7.6"
|
||||
cortex-m-rt = { version = "0.7.1", features = ["device"] }
|
||||
cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] }
|
||||
cortex-m-rt = { version = "0.7.3", features = ["device"] }
|
||||
cortex-m-semihosting = "0.5.0"
|
||||
log = "0.4.17"
|
||||
bare-metal = "1"
|
||||
nb = "1"
|
||||
cortex-m-log = { version = "0.7.0", features = ["log-integration", "semihosting"] }
|
||||
stm32f4xx-hal = { version = "0.13.2", features = ["rt", "stm32f407", "usb_fs"] }
|
||||
stm32-eth = { version = "0.3.0", features = ["stm32f407"] }
|
||||
smoltcp = { version = "0.8.0", default-features = false, features = ["proto-ipv4", "socket-tcp", "log", "medium-ethernet"] }
|
||||
stm32f4xx-hal = { version = "0.20.0", features = ["stm32f407", "otg-fs", "usb_fs"] }
|
||||
stm32-eth = { version = "0.6.0", features = ["stm32f407", "smoltcp-phy", "smoltcp"] }
|
||||
ieee802_3_miim = "0.8.0"
|
||||
smoltcp = { version = "0.11.0", default-features = false, features = ["proto-ipv4", "socket-tcp", "medium-ethernet", "iface-neighbor-cache-count-16"] }
|
||||
uom = { version = "0.30", default-features = false, features = ["autoconvert", "si", "f32", "use_serde"] }
|
||||
num-traits = { version = "0.2.15", default-features = false, features = ["libm"] }
|
||||
usb-device = "0.2.9"
|
||||
usbd-serial = "0.1.1"
|
||||
usb-device = "0.3.2"
|
||||
usbd-serial = "0.2.1"
|
||||
fugit = "0.3.6"
|
||||
rtt-target = { version = "0.3.1", features = ["cortex-m"] }
|
||||
|
||||
|
||||
miniconf = "0.9.0"
|
||||
serde = { version = "1.0.158", features = ["derive"], default-features = false }
|
||||
sfkv = "0.1"
|
||||
bit_field = "0.10"
|
||||
crc = "3.0.1"
|
||||
byteorder = { version = "1", default-features = false }
|
||||
[features]
|
||||
semihosting = ["cortex-m-log/semihosting"]
|
||||
RTT = []
|
||||
|
85
README.md
Normal file
85
README.md
Normal file
@ -0,0 +1,85 @@
|
||||
# Testing Firmware for the Sinara 1550 Kirdy
|
||||
|
||||
- This repo is for testing only. Not intended for production use.
|
||||
|
||||
## Building
|
||||
|
||||
### Reproducible build with Nix
|
||||
|
||||
kirdy firmware is packaged using the [Nix](https://nixos.org) Flakes system. Install Nix 2.4+ and enable flakes by adding ``experimental-features = nix-command flakes`` to ``nix.conf`` (e.g. ``~/.config/nix/nix.conf``).
|
||||
|
||||
Once you have Flakes enabled, you can use ``nix build`` to build the firmware.
|
||||
|
||||
### Development environment
|
||||
|
||||
Clone this repository and with Nix Flakes enabled, use the following commands:
|
||||
|
||||
```shell
|
||||
nix develop
|
||||
cargo build
|
||||
```
|
||||
|
||||
The resulting ELF file will be located under `target/thumbv7em-none-eabihf/release/kirdy`.
|
||||
|
||||
Alternatively, you can install the Rust toolchain without Nix using rustup; see the Rust manifest file pulled in `flake.nix` to determine which Rust version to use.
|
||||
|
||||
For building on FreeBSD or OpenBSD, see BSD.md.
|
||||
|
||||
## Debugging
|
||||
|
||||
Connect SWDIO/SWCLK/RST/GND to a programmer such as ST-Link v2.1. Run OpenOCD:
|
||||
```shell
|
||||
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
|
||||
```
|
||||
|
||||
You may need to power up the programmer before powering the device.
|
||||
Leave OpenOCD running. Run the GNU debugger:
|
||||
```shell
|
||||
gdb target/thumbv7em-none-eabihf/release/kirdy
|
||||
|
||||
(gdb) source openocd.gdb
|
||||
```
|
||||
|
||||
## Flashing
|
||||
If the firmware to be flashed involves an update on the flash settings, it is required to erase the flash settings before flashing the new firmware to avoid unexpected hardware behavior. There are several options for flashing kirdy. DFU requires only a USB-C cable or RJ45 cable, whereas OpenOCD needs a JTAG/SWD adapter.
|
||||
|
||||
### dfu-util on Linux
|
||||
* Install the DFU USB tool (dfu-util).
|
||||
* Convert firmware from ELF to BIN: `arm-none-eabi-objcopy -O binary kirdy kirdy.bin` (you can skip this step if using the BIN from Hydra)
|
||||
* Put STM32 into DFU Mode. You can either
|
||||
* Connect to the USB Type C cable to kirdy next to the RJ45 Jack. After that, add BOOT0 jumper to kirdy near programming headers and then cycle board power to put it in DFU mode. OR
|
||||
* Plug in RJ45 cable, which connect to a network that is accessible by your computer and send the corresponding dfu json command via TCP Socket to kirdy. Please see the python test script for the command.
|
||||
* Push firmware to flash: `dfu-util -a 0 -s 0x08000000:leave -D kirdy.bin`
|
||||
* If you plugged in the BOOT0 jumper, you will need to
|
||||
1. Remove BOOT0 jumper
|
||||
2. Cycle power to leave DFU update mode
|
||||
* If you plugged in the RJ45 cable, the MCU would start its application code automatically. No power cycle is needed.
|
||||
|
||||
### st.com DfuSe tool on Windows
|
||||
On a Windows machine install [st.com](https://st.com) DfuSe USB device firmware upgrade (DFU) software. [link](https://www.st.com/en/development-tools/stsw-stm32080.html).
|
||||
- add jumper to kirdy across 2-pin jumper adjacent to JTAG connector
|
||||
- cycle board power to put it in DFU update mode
|
||||
- connect USB Type C to PC
|
||||
- use st.com software to upload firmware
|
||||
- remove jumper
|
||||
- cycle power to leave DFU update mode
|
||||
|
||||
### OpenOCD
|
||||
```shell
|
||||
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -c "program target/thumbv7em-none-eabihf/debug/kirdy verify reset; exit"
|
||||
```
|
||||
|
||||
## Erasing Flash Settings
|
||||
The flash settings are stored in the last flash sector(ID: 11) of bank 0 of stm32f405. You can erase it with JTAG/SWD adapter or by putting the device in Dfu mode. You may find it useful if you have set network settings incorrectly.
|
||||
|
||||
With JTAG/SWD adapter connected, issue the following command:
|
||||
|
||||
```shell
|
||||
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -c "flash init; init; halt; flash erase_sector 0 11 last; reset; exit"
|
||||
```
|
||||
|
||||
With STM32 in DFU Mode, connect the USB Type C cable and then issue the following command:
|
||||
|
||||
```
|
||||
dfu-util -a 0 -s 0x080E0000:leave -D erase_flash_settings.bin
|
||||
```
|
5
build.rs
5
build.rs
@ -1,7 +1,4 @@
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs::File, io::Write, path::PathBuf};
|
||||
|
||||
fn main() {
|
||||
// Put the linker script somewhere the linker can find it
|
||||
|
56
docs/thermostat.md
Normal file
56
docs/thermostat.md
Normal file
@ -0,0 +1,56 @@
|
||||
# Thermostat
|
||||
Kirdy incorporates a single channel thermostat circuitry like Sinara-hw [Thermostat](https://github.com/sinara-hw/Thermostat) with improved noise. It can deliver more than 9W of cooling/heating power with better than +-1mK temperature stability.
|
||||
|
||||
## Power Input
|
||||
If you use Kirdy with a PoE (IEEE 802.3af Class 3) PSE, Kirdy may experience power failure due to power limit. If you need more power on Thermostat, you can plug in additional Power supply on the 12V power Jack(Recommend: 12V 2A). Although the TEC Driver has a very high 165 degree thermal shutdown threshold, it is highly recommended to provide active cooling to the TEC Driving Circuitry.
|
||||
|
||||
## Control Mode
|
||||
Like Sinara-hw [Thermostat](https://github.com/sinara-hw/Thermostat), they both share the same control mode and algorithm, namely constant Current Control Mode
|
||||
|
||||
### Constant Current Control Mode
|
||||
You can drive a constant current to the load. The end user to connect an external control loop. If end user wants to use it with an external control loop, it is recommended to retrieve reports via the ActiveReportMode instead of polling status report for better consistency and higher maximum update rate.
|
||||
|
||||
### PID Control Mode
|
||||
Thermostat uses the [PID algorithm](https://hackmd.io/IACbwcOTSt6Adj3_F9bKuw) derived by Quartiq. The included Pid Autotune Algorithm can already achieve the specified +-1mK stability.
|
||||
|
||||
During autotuning, Theremstat drives a user-specified step current into the load to calculate the pid parameters. Improper usage can damage the connected device.
|
||||
|
||||
#### Important Notes
|
||||
1. Configure all the protection mechanisms correctly before running the pid autotune algorithm.
|
||||
2. Supervise the PID autotune process.
|
||||
3. PID Autotune algorithm sometimes can produce unstable PID parameters. Do evaluate the PID performance after autotune is completed
|
||||
4. For best performance, you should do the autotune with Kirdy installed in the environment that it is gonna be used.
|
||||
5. If you are using the GUI, you need to set a high enough polling rate for PID autotune to function correctly.
|
||||
|
||||
#### Usage Guide
|
||||
|
||||
You can run pid autotune via the standalone auto
|
||||
|
||||
1. Target Temperature
|
||||
- Set it to the intended operating temperature for the connected device to operate in. You can still change temperature setpoint afterwards.
|
||||
2. Test Current
|
||||
- Set it to the maximum recommended operating current specified on the TEC/Resistive load.
|
||||
3. Update Rate
|
||||
- This can be set by configuring the filter settings in the Temperature ADC. High Update Rate can yield faster loop response with temperature regulation noise as the trade-off. Higher update rate can be useful if the load is actively cooled.
|
||||
4. Temperature Swing
|
||||
- Lower value can damp the control loop response.
|
||||
5. Lookback Period
|
||||
- Larger value can damp the control loop response. Do note that the larger lookback period also increases the autotune completion time.
|
||||
|
||||
You can give your system a try with the following parameters.
|
||||
1. Target Temperature: (User-Specified)
|
||||
2. Test Current: (User-Specified)
|
||||
3. Update Rate: 16.67Hz (Sinc5Sinc1 Filter with 50Hz and 60Hz Rejection)
|
||||
4. Temperature Swing: 0 Degree
|
||||
5. Lookback Period: 5s
|
||||
|
||||
Once autotune is successful, you can improve its stability by increasing the lookback period.
|
||||
|
||||
#### Troubleshooting
|
||||
If you obtain unstable PID parameters,
|
||||
1. Check if the polling rate is close to the PID update rate. If not, increase the polling rate
|
||||
2. Increase the lookback period and try again.
|
||||
|
||||
If the autotune does not finish running or faikls, you should
|
||||
1. Check if the thermostat has a hard time heating or cooling the connected device to the targe temperature. If yes, you should consider adjust the target temperature accordingly.
|
||||
2. Check if the polling rate is close to the PID update rate. If not, increase the polling rate.
|
BIN
erase_flash_settings.bin
Normal file
BIN
erase_flash_settings.bin
Normal file
Binary file not shown.
14
flake.lock
generated
14
flake.lock
generated
@ -3,11 +3,11 @@
|
||||
"mozilla-overlay": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1664789696,
|
||||
"narHash": "sha256-UGWJHQShiwLCr4/DysMVFrYdYYHcOqAOVsWNUu+l6YU=",
|
||||
"lastModified": 1704373101,
|
||||
"narHash": "sha256-+gi59LRWRQmwROrmE1E2b3mtocwueCQqZ60CwLG+gbg=",
|
||||
"owner": "mozilla",
|
||||
"repo": "nixpkgs-mozilla",
|
||||
"rev": "80627b282705101e7b38e19ca6e8df105031b072",
|
||||
"rev": "9b11a87c0cc54e308fa83aac5b4ee1816d5418a2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -18,16 +18,16 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1666164185,
|
||||
"narHash": "sha256-5v+YB4ijeUfg5LCz9ck4gIpCPhIS+qn02OyPJO48bCE=",
|
||||
"lastModified": 1713725259,
|
||||
"narHash": "sha256-9ZR/Rbx5/Z/JZf5ehVNMoz/s5xjpP0a22tL6qNvLt5E=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c5203abb1329f7ea084c04acda330ca75d5b9fb5",
|
||||
"rev": "a5e4bbcb4780c63c79c87d29ea409abf097de3f7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-22.05",
|
||||
"ref": "nixos-23.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
|
46
flake.nix
46
flake.nix
@ -1,15 +1,15 @@
|
||||
{
|
||||
description = "Firmware for kirdy";
|
||||
|
||||
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-22.05;
|
||||
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-23.11;
|
||||
inputs.mozilla-overlay = { url = github:mozilla/nixpkgs-mozilla; flake = false; };
|
||||
|
||||
outputs = { self, nixpkgs, mozilla-overlay }:
|
||||
let
|
||||
pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ (import mozilla-overlay) ]; };
|
||||
rustManifest = pkgs.fetchurl {
|
||||
url = "https://static.rust-lang.org/dist/2022-09-22/channel-rust-stable.toml";
|
||||
sha256 = "f257a7de2f284f025238964ca2c1865c704be4156e1909d6393d92b796005055";
|
||||
url = "https://static.rust-lang.org/dist/2024-03-21/channel-rust-nightly.toml";
|
||||
sha256 = "1c7db6ab80d20682b5cc5bda7360c63311d7188c0c082902d3790820527cd4e0";
|
||||
};
|
||||
|
||||
targets = [
|
||||
@ -22,7 +22,7 @@
|
||||
inherit targets;
|
||||
extensions = ["rust-src"];
|
||||
};
|
||||
rust = rustChannelOfTargets "stable" null targets;
|
||||
rust = rustChannelOfTargets "nightly" null targets;
|
||||
rustPlatform = pkgs.recurseIntoAttrs (pkgs.makeRustPlatform {
|
||||
rustc = rust;
|
||||
cargo = rust;
|
||||
@ -51,6 +51,34 @@
|
||||
'';
|
||||
|
||||
dontFixup = true;
|
||||
auditable = false;
|
||||
};
|
||||
|
||||
pglive = pkgs.python3Packages.buildPythonPackage rec {
|
||||
pname = "pglive";
|
||||
version = "0.7.2";
|
||||
format = "pyproject";
|
||||
src = pkgs.fetchPypi {
|
||||
inherit pname version;
|
||||
hash = "sha256-jqj8X6H1N5mJQ4OrY5ANqRB0YJByqg/bNneEALWmH1A=";
|
||||
};
|
||||
buildInputs = [ pkgs.python3Packages.poetry-core ];
|
||||
propagatedBuildInputs = with pkgs.python3Packages; [ pyqtgraph numpy ];
|
||||
};
|
||||
|
||||
kirdy_gui = pkgs.python3Packages.buildPythonPackage {
|
||||
pname = "kirdy_gui";
|
||||
version = "0.0.0";
|
||||
format = "pyproject";
|
||||
src = "${self}/pykirdy";
|
||||
|
||||
nativeBuildInputs = [ pkgs.qt6.wrapQtAppsHook ];
|
||||
propagatedBuildInputs = [ pkgs.qt6.qtbase ] ++ (with pkgs.python3Packages; [ pyqtgraph pyqt6 qasync pglive aenum sipyco]);
|
||||
|
||||
dontWrapQtApps = true;
|
||||
postFixup = ''
|
||||
wrapQtApp "$out/bin/tec_qt"
|
||||
'';
|
||||
};
|
||||
in {
|
||||
packages.x86_64-linux = {
|
||||
@ -64,16 +92,14 @@
|
||||
devShell.x86_64-linux = pkgs.mkShell {
|
||||
name = "kirdy-dev-shell";
|
||||
buildInputs = with pkgs; [
|
||||
rustPlatform.rust.rustc
|
||||
rustPlatform.rust.cargo
|
||||
openocd dfu-util glibc
|
||||
rust openocd dfu-util glibc
|
||||
] ++ (with python3Packages; [
|
||||
numpy matplotlib pyqtgraph
|
||||
numpy matplotlib pyqtgraph setuptools pyqt6 qasync pglive aenum sipyco
|
||||
]);
|
||||
shellHook=
|
||||
''
|
||||
export QT_PLUGIN_PATH=${pkgs.qt5.qtbase}/${pkgs.qt5.qtbase.dev.qtPluginPrefix}
|
||||
export QML2_IMPORT_PATH=${pkgs.qt5.qtbase}/${pkgs.qt5.qtbase.dev.qtQmlPrefix}
|
||||
export QT_PLUGIN_PATH=${pkgs.qt6.qtbase}/${pkgs.qt6.qtbase.dev.qtPluginPrefix}
|
||||
export QML2_IMPORT_PATH=${pkgs.qt6.qtbase}/${pkgs.qt6.qtbase.dev.qtQmlPrefix}
|
||||
'';
|
||||
};
|
||||
defaultPackage.x86_64-linux = kirdy;
|
||||
|
6
memory.x
6
memory.x
@ -2,9 +2,8 @@
|
||||
|
||||
MEMORY
|
||||
{
|
||||
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K
|
||||
/* reserved for config data */
|
||||
CONFIG (rx) : ORIGIN = 0x8100000, LENGTH = 16K
|
||||
/* The last flash sector is reserved for config */
|
||||
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 896K
|
||||
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 112K - 4
|
||||
/* reserved for DFU trigger message */
|
||||
DFU_MSG (wrx) : ORIGIN = 0x2001BFFC, LENGTH = 4
|
||||
@ -14,6 +13,5 @@ MEMORY
|
||||
}
|
||||
|
||||
_flash_start = ORIGIN(FLASH);
|
||||
_config_start = ORIGIN(CONFIG);
|
||||
_dfu_msg = ORIGIN(DFU_MSG);
|
||||
_stack_start = ORIGIN(CCMRAM) + LENGTH(CCMRAM);
|
||||
|
137
pykirdy/asyncio_exmaple.py
Normal file
137
pykirdy/asyncio_exmaple.py
Normal file
@ -0,0 +1,137 @@
|
||||
from pprint import pp
|
||||
from driver.kirdy import Kirdy, FilterConfig
|
||||
import signal
|
||||
import time
|
||||
import asyncio
|
||||
|
||||
async def enter_dfu_mode(kirdy: Kirdy):
|
||||
"""
|
||||
Enter Device Firmware Upgrade(Dfu) mode
|
||||
Please refer to README.md for firmware update instructions.
|
||||
"""
|
||||
await kirdy.device.dfu()
|
||||
|
||||
async def active_report(kirdy: Kirdy):
|
||||
"""
|
||||
Configure Kirdy to actively report status to connected socket
|
||||
Press Ctrl + C to exit active report mode
|
||||
"""
|
||||
async for data in kirdy.report_mode():
|
||||
pp(data)
|
||||
|
||||
async def device_cfg(kirdy: Kirdy):
|
||||
"""
|
||||
Configure Kirdy's network and board specific transconductance settings.
|
||||
These configs are saved to flash immediately after command is processed.
|
||||
"""
|
||||
# Kirdy rev0_3's gain and transconductance varies between boards to maximize the
|
||||
# PD current range resolution.
|
||||
await kirdy.device.set_pd_mon_fin_gain(1.0)
|
||||
await kirdy.device.set_pd_mon_transconductance(1/1000.0)
|
||||
|
||||
# Network Settings will be updated on next reboot.
|
||||
await kirdy.device.set_ip_settings(
|
||||
addr="192.168.1.128",
|
||||
port=1337,
|
||||
prefix_len=24,
|
||||
gateway="192.168.1.1"
|
||||
)
|
||||
# Hard reset Kirdy.
|
||||
await kirdy.device.hard_reset()
|
||||
|
||||
async def ld_thermostat_cfg(kirdy: Kirdy):
|
||||
"""
|
||||
Control and config laser diode and thermostat parameters.
|
||||
"""
|
||||
# Load the laser diode and thermostat settings from flash memory.
|
||||
await kirdy.device.restore_settings_from_flash()
|
||||
|
||||
# Power off the laser diode & thermostat and clear any asserted alarm
|
||||
await kirdy.laser.set_power_on(False)
|
||||
await kirdy.laser.clear_alarm()
|
||||
await kirdy.thermostat.set_power_on(False)
|
||||
await kirdy.thermostat.clear_alarm()
|
||||
|
||||
# Set the laser diode terminals not to be shorted
|
||||
await kirdy.laser.set_ld_terms_short(False)
|
||||
|
||||
# Do not power up the laser & thermostat during initial startup
|
||||
await kirdy.laser.set_default_pwr_on(False)
|
||||
await kirdy.thermostat.set_default_pwr_on(False)
|
||||
|
||||
await kirdy.laser.set_i(0)
|
||||
|
||||
# Configure the laser diode output power limit and photodiode parameters
|
||||
# Exceeding the power limit triggers overpower protection alarm.
|
||||
# The laser diode power will be cut off upon alarm assertion while the thermostat power remains unchanged.
|
||||
await kirdy.laser.set_ld_pwr_limit(0.0)
|
||||
await kirdy.laser.set_pd_mon_dark_current(0.0)
|
||||
await kirdy.laser.set_pd_mon_responsitivity(0.0)
|
||||
|
||||
# Configure the thermostat NTC thermistor parameters.
|
||||
await kirdy.thermostat.set_sh_r0(10.0 * 1000)
|
||||
await kirdy.thermostat.set_sh_t0(25)
|
||||
await kirdy.thermostat.set_sh_beta(3900)
|
||||
|
||||
# Configure the thermostat TEC settings.
|
||||
# The actual output current is limited by value set below.
|
||||
await kirdy.thermostat.set_tec_max_cooling_i(1.0)
|
||||
await kirdy.thermostat.set_tec_max_heating_i(1.0)
|
||||
await kirdy.thermostat.set_tec_max_v(4.0)
|
||||
|
||||
# Configure the thermostat temperature monitor limits.
|
||||
# Exceeding the temperature limits trigger over temperature protection alarm.
|
||||
# Both laser diode and thermostat power will be cut off upon alarm assertion.
|
||||
await kirdy.thermostat.set_temp_mon_upper_limit(70)
|
||||
await kirdy.thermostat.set_temp_mon_lower_limit(0)
|
||||
|
||||
# Configure the thermostat ADC Filter Setting / PID Update Rate / Report Rate.
|
||||
# The ADC sampling rate determines the report and pid update rate.
|
||||
# The chosen filter and sampling rate affects the noise of the readings.
|
||||
# For details, please refer to the AD7172 datasheet.
|
||||
await kirdy.thermostat.config_temp_adc_filter(FilterConfig.Sinc5Sinc1With50hz60HzRejection.f16sps)
|
||||
|
||||
# Configure the thermostat PID parameters.
|
||||
# You can configure the PID parameter by the included autotune script.
|
||||
# You should configure the filter sampling rate first before applying pid parameters.
|
||||
await kirdy.thermostat.set_temperature_setpoint(25)
|
||||
await kirdy.thermostat.set_pid_kp(0.15668282198105507)
|
||||
await kirdy.thermostat.set_pid_ki(0.0001281321)
|
||||
await kirdy.thermostat.set_pid_kd(13.82367)
|
||||
await kirdy.thermostat.set_pid_output_max(1.0)
|
||||
await kirdy.thermostat.set_pid_output_min(-1.0)
|
||||
|
||||
# Configure thermostat to run in PID control mode
|
||||
await kirdy.thermostat.set_pid_control_mode()
|
||||
|
||||
# When control mode is switched from PID to constant current(CC) control mode,
|
||||
# thermostat keeps its instantaneous output current unchanged.
|
||||
# Thermostat output current should only be set if it is in CC control mode
|
||||
# or the value set will be overwritten by PID output.
|
||||
await kirdy.thermostat.set_constant_current_control_mode()
|
||||
await kirdy.thermostat.set_tec_i_out(0.0)
|
||||
|
||||
# Save the current settings to flash memory
|
||||
await kirdy.device.save_current_settings_to_flash()
|
||||
|
||||
# Power on the laser diode and thermostat
|
||||
await kirdy.laser.set_power_on(True)
|
||||
await kirdy.thermostat.set_power_on(True)
|
||||
|
||||
pp(await kirdy.device.get_settings_summary())
|
||||
pp(await kirdy.device.get_status_report())
|
||||
|
||||
async def main():
|
||||
kirdy = Kirdy()
|
||||
kirdy.start_session(host='192.168.1.128', port=1337)
|
||||
await kirdy.wait_until_connected()
|
||||
|
||||
await ld_thermostat_cfg(kirdy)
|
||||
# await active_report(kirdy)
|
||||
# await device_cfg(kirdy)
|
||||
# await enter_dfu_mode(kirdy)
|
||||
|
||||
await kirdy.end_session()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
981
pykirdy/driver/kirdy.py
Normal file
981
pykirdy/driver/kirdy.py
Normal file
@ -0,0 +1,981 @@
|
||||
import types
|
||||
import socket
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from threading import Thread
|
||||
from aenum import StrEnum, NoAlias
|
||||
import queue
|
||||
import asyncio
|
||||
|
||||
class _dt(StrEnum):
|
||||
ip_settings = "ip_settings"
|
||||
temp_adc_filter = "temp_adc_filter"
|
||||
f32 = "data_f32"
|
||||
bool = "data_bool"
|
||||
none = "None"
|
||||
|
||||
class State(StrEnum):
|
||||
disconnected = "disconnected"
|
||||
connected = "connected"
|
||||
|
||||
class CmdList:
|
||||
class device(StrEnum, settings=NoAlias):
|
||||
_target = "device_cmd"
|
||||
SetIPSettings = _dt.ip_settings
|
||||
SetPdFinGain = _dt.f32
|
||||
SetPdTransconductance = _dt.f32
|
||||
SetActiveReportMode = _dt.bool
|
||||
GetHwRev = _dt.none
|
||||
GetStatusReport = _dt.none
|
||||
GetSettingsSummary = _dt.none
|
||||
Dfu = _dt.none
|
||||
SaveFlashSettings = _dt.none
|
||||
LoadFlashSettings = _dt.none
|
||||
HardReset = _dt.none
|
||||
|
||||
class ld(StrEnum, settings=NoAlias):
|
||||
_target = "laser_diode_cmd"
|
||||
SetDefaultPowerOn = _dt.bool
|
||||
PowerUp = _dt.none
|
||||
PowerDown = _dt.none
|
||||
LdTermsShort = _dt.none
|
||||
LdTermsOpen = _dt.none
|
||||
SetI = _dt.f32
|
||||
SetPdResponsitivity = _dt.f32
|
||||
SetPdDarkCurrent = _dt.f32
|
||||
ApplyPdParams = _dt.none
|
||||
SetLdPwrLimit = _dt.f32
|
||||
ClearAlarm = _dt.none
|
||||
|
||||
class thermostat(StrEnum, settings=NoAlias):
|
||||
_target = "thermostat_cmd"
|
||||
SetDefaultPowerOn = _dt.bool,
|
||||
PowerUp = _dt.f32,
|
||||
PowerDown = _dt.f32,
|
||||
SetTecMaxV = _dt.f32,
|
||||
SetTecMaxIPos = _dt.f32,
|
||||
SetTecMaxINeg = _dt.f32,
|
||||
SetTecIOut = _dt.f32,
|
||||
SetTemperatureSetpoint = _dt.f32,
|
||||
SetPidEngage = _dt.none,
|
||||
SetPidDisEngage = _dt.none,
|
||||
SetPidKp = _dt.f32,
|
||||
SetPidKi = _dt.f32,
|
||||
SetPidKd = _dt.f32,
|
||||
SetPidOutMin = _dt.f32,
|
||||
SetPidOutMax = _dt.f32,
|
||||
ConfigTempAdcFilter = _dt.temp_adc_filter,
|
||||
GetPollInterval = _dt.none,
|
||||
SetTempMonUpperLimit = _dt.f32,
|
||||
SetTempMonLowerLimit = _dt.f32,
|
||||
ClearAlarm = _dt.none,
|
||||
SetShT0 = _dt.f32,
|
||||
SetShR0 = _dt.f32,
|
||||
SetShBeta = _dt.f32,
|
||||
|
||||
class FilterConfig:
|
||||
class Sinc5Sinc1With50hz60HzRejection(StrEnum):
|
||||
f27sps = "F27SPS"
|
||||
f21sps = "F21SPS"
|
||||
f20sps = "F20SPS"
|
||||
f16sps = "F16SPS"
|
||||
_odr_type = "sinc5sinc1postfilter"
|
||||
|
||||
def _filter_type(self):
|
||||
return "Sinc5Sinc1With50hz60HzRejection"
|
||||
|
||||
@classmethod
|
||||
def get_list_of_settings(cls):
|
||||
ret = []
|
||||
for e in cls:
|
||||
if e not in [cls._odr_type]:
|
||||
ret.append(e)
|
||||
return ret
|
||||
|
||||
class Sinc5Sinc1(StrEnum):
|
||||
f31250_0sps = "F31250_0SPS"
|
||||
f15625_0sps = "F15625_0SPS"
|
||||
f10417_0sps = "F10417_0SPS"
|
||||
f5208_0sps = "F5208_0SPS"
|
||||
f2597_0sps = "F2597_0SPS"
|
||||
f1007_0sps = "F1007_0SPS"
|
||||
f503_8sps = "F503_8SPS"
|
||||
f381_0sps = "F381_0SPS"
|
||||
f200_3sps = "F200_3SPS"
|
||||
f100_2sps = "F100_2SPS"
|
||||
f59_52sps = "F59_52SPS"
|
||||
f49_68sps = "F49_68SPS"
|
||||
f20_01sps = "F20_01SPS"
|
||||
f16_63sps = "F16_63SPS"
|
||||
f10_0sps = "F10_0SPS"
|
||||
f5_0sps = "F5_0SPS"
|
||||
f2_5sps = "F2_5SPS"
|
||||
f1_25sps = "F1_25SPS"
|
||||
_odr_type = "sinc5sinc1odr"
|
||||
|
||||
def _filter_type(self):
|
||||
return "Sinc5Sinc1"
|
||||
|
||||
@classmethod
|
||||
def get_list_of_settings(cls):
|
||||
ret = []
|
||||
for e in cls:
|
||||
if e not in [cls._odr_type]:
|
||||
ret.append(e)
|
||||
return ret
|
||||
|
||||
class Sinc3(StrEnum):
|
||||
f31250_0sps = "F31250_0SPS"
|
||||
f15625_0sps = "F15625_0SPS"
|
||||
f10417_0sps = "F10417_0SPS"
|
||||
f5208_0sps = "F5208_0SPS"
|
||||
f2597_0sps = "F2597_0SPS"
|
||||
f1007_0sps = "F1007_0SPS"
|
||||
f503_8sps = "F503_8SPS"
|
||||
f381_0sps = "F381_0SPS"
|
||||
f200_3sps = "F200_3SPS"
|
||||
f100_2sps = "F100_2SPS"
|
||||
f59_52sps = "F59_52SPS"
|
||||
f49_68sps = "F49_68SPS"
|
||||
f20_01sps = "F20_01SPS"
|
||||
f16_63sps = "F16_63SPS"
|
||||
f10_0sps = "F10_0SPS"
|
||||
f5_0sps = "F5_0SPS"
|
||||
f2_5sps = "F2_5SPS"
|
||||
f1_25sps = "F1_25SPS"
|
||||
_odr_type = "sinc3odr"
|
||||
|
||||
def _filter_type(self):
|
||||
return "Sinc3"
|
||||
|
||||
@classmethod
|
||||
def get_list_of_settings(cls):
|
||||
ret = []
|
||||
for e in cls:
|
||||
if e not in [cls._odr_type]:
|
||||
ret.append(e)
|
||||
return ret
|
||||
|
||||
class Sinc3WithFineODR():
|
||||
upper_limit = 31250
|
||||
lower_limit = 1.907465
|
||||
_odr_type = "sinc3fineodr"
|
||||
|
||||
def __init__(self, rate):
|
||||
assert rate >= self.lower_limit and rate <= self.upper_limit
|
||||
self.rate = float(rate)
|
||||
|
||||
def _filter_type(self):
|
||||
return "Sinc3WithFineODR"
|
||||
|
||||
class InvalidDataType(Exception):
|
||||
pass
|
||||
|
||||
class InvalidCmd(Exception):
|
||||
pass
|
||||
|
||||
class Device:
|
||||
def __init__(self, send_cmd_handler, send_raw_cmd_handler):
|
||||
self._cmd = CmdList.device
|
||||
self._send_cmd = send_cmd_handler
|
||||
self._send_raw_cmd = send_raw_cmd_handler
|
||||
|
||||
async def set_ip_settings(self, addr="192.168.1.128", port=1337, prefix_len=24, gateway="192.168.1.1"):
|
||||
"""
|
||||
Upon command execution, the ip settings are saved into flash and are effective upon next reboot.
|
||||
"""
|
||||
try:
|
||||
socket.inet_aton(addr)
|
||||
socket.inet_aton(gateway)
|
||||
except OSError:
|
||||
raise InvalidDataType
|
||||
|
||||
addr = addr.split(".")
|
||||
gateway = gateway.split(".")
|
||||
|
||||
if not(isinstance(port, int) and isinstance(prefix_len, int)):
|
||||
raise InvalidDataType
|
||||
|
||||
return await self._send_raw_cmd(
|
||||
{
|
||||
"device_cmd": "SetIPSettings",
|
||||
"ip_settings": {
|
||||
"addr": [int(x) for x in addr],
|
||||
"port": port,
|
||||
"prefix_len": prefix_len,
|
||||
"gateway": [int(x) for x in gateway],
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
async def set_active_report_mode(self, on):
|
||||
"""
|
||||
Set active report to be on. If it is on, Kirdy will send status report
|
||||
to the client socket according to the temperature polling rate set.
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetActiveReportMode, on)
|
||||
|
||||
async def set_pd_mon_fin_gain(self, gain):
|
||||
"""
|
||||
Configure the photodiode monitor final analog front-end stage gain.
|
||||
- gain: unitless
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetPdFinGain, gain)
|
||||
|
||||
async def set_pd_mon_transconductance(self, transconductance):
|
||||
"""
|
||||
Configure the photodiode monitor transconductance value.
|
||||
- transconductance: 1/Ohm
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetPdTransconductance, transconductance)
|
||||
|
||||
async def get_hw_rev(self):
|
||||
"""
|
||||
Get hardware revision of the connected Kirdy
|
||||
|
||||
{
|
||||
'msg_type': 'HwRev',
|
||||
'hw_rev': {
|
||||
'major': 0,
|
||||
'minor': 3
|
||||
}
|
||||
}
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.GetHwRev, msg_type="HwRev")
|
||||
|
||||
async def get_status_report(self, sig=None):
|
||||
"""
|
||||
Get status of all peripherals in a json object.
|
||||
|
||||
{
|
||||
'ts': 227657, # Relative Timestamp (ms)
|
||||
'msg_type': 'Report' # Indicate it is a 'Report' json object
|
||||
'laser': {
|
||||
'pwr_on': False, # Laser Power is On (True/False)
|
||||
'pwr_excursion': False, # Was Laser experienced an Overpowered Condition? (True/False)
|
||||
'ld_i_set': 0.0, # Laser Diode Output Current (A)
|
||||
'pd_i': 2.0000002e-06, # Internal Photodiode Monitor current (A)
|
||||
'pd_pwr': None, # Power Readings from Internal Photodiode (W). Return None if pd_mon parameter(s) are not defined.
|
||||
'term_50ohm': 'On' # Is the Low Frequency Modulation Input's Impedance 50 Ohm? ("On"/"Off")
|
||||
},
|
||||
'thermostat': {
|
||||
'pwr_on': False, # Tec Power is On (True/False)
|
||||
'pid_engaged': False, # Is Pid_Engaged. If False, it is in Constant Current Mode (True/False)
|
||||
'temp_mon_status': { # Temperature Monitor:
|
||||
'status': 'Off', # "Off": Power is Off
|
||||
# "ConstantCurrentMode": Thermostat is regulated in CC mode
|
||||
# "PidStartUp": PID Regulation is not stable
|
||||
# "PidStable": PID Regulation is stable and the temperature is within +-1mK to the setpoint
|
||||
# "OverTempAlarm": Overtemperature Alarm is triggered
|
||||
'over_temp_alarm': False # Was Laser Diode experienced an Overtemperature condition (True/False)
|
||||
},
|
||||
'temperature': 25.03344, # Temperature Readings (Degree Celsius)
|
||||
'i_set': 0.0, # Tec Current Set by User/PID Controller(A)
|
||||
'tec_i': 0.0024998188, # Tec Current Readings (A)
|
||||
'tec_v': -0.00399971 # Tec Voltage Readings (V)
|
||||
}
|
||||
}
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.GetStatusReport, msg_type="Report", sig=sig)
|
||||
|
||||
async def get_settings_summary(self, sig=None):
|
||||
"""
|
||||
Get the current settings of laser and thermostat in a json object.
|
||||
|
||||
{
|
||||
'msg_type': 'Settings', # Indicate it is a 'Settings' json object
|
||||
'laser': {
|
||||
'default_pwr_on': False, # Power On Laser Diode at Startup
|
||||
'ld_drive_current': { # Laser Diode Output Current (A)
|
||||
'value': 0.0, # Value Set
|
||||
'max': 0.3 # Max Value Settable
|
||||
},
|
||||
'pd_mon_params': { # Photodiode Parameters
|
||||
'transconductance': 0.000115258765 # Board Specific Transconductance (1/ohm)
|
||||
'responsitivity': 0.0141, # Responsitivity (A/W)
|
||||
'i_dark': 0.0 # Max Value Settable (A)
|
||||
},
|
||||
'ld_pwr_limit': { # Laser Diode Power Limit (W)
|
||||
'value': 0.00975, # Value Set
|
||||
'max': 0.023321507 # Max Value Settable
|
||||
},
|
||||
'ld_terms_short: False, # Is Laser Diode Terminals short? (True/False)
|
||||
},
|
||||
'thermostat': {
|
||||
'default_pwr_on': True, # Power on Thermostat at Startup
|
||||
'pid_engaged': True, # True: PID Control Mode | False Constant Current Mode
|
||||
'temperature_setpoint': 25.0, # Temperature Setpoint (Degree Celsius)
|
||||
'tec_settings': {
|
||||
'i_set': { # Current TEC Current Set by PID Controller/User (A)
|
||||
'value': 0.04330516, # Value Set
|
||||
'max': 3.0 # Max Value Settable
|
||||
},
|
||||
'max_v': { # Max Voltage Across Tec Terminals (V)
|
||||
'value': 4.00000000, # Value Set
|
||||
'max': 4.3 # Max Value Settable
|
||||
},
|
||||
'max_i_pos': { # Max Cooling Current Across Tec Terminals (A)
|
||||
'value': 0.99628574, # Value Set
|
||||
'max': 3.0 # Max Value Settable
|
||||
},
|
||||
'max_i_neg': { # Max Heating Current Across Tec Terminals (A)
|
||||
'value': 0.99628574, # Value Set
|
||||
'max': 3.0 # Max Value Settable
|
||||
}
|
||||
},
|
||||
'pid_params': { # PID Controller Parameters
|
||||
'kp': 0.15668282, # Proportional Gain
|
||||
'ki': 0.0021359625, # Integral Gain
|
||||
'kd': 0.8292545, # Derivative Gain
|
||||
'output_min': -1.0, # Minimum Current Output (A)
|
||||
'output_max': 1.0 # Maximum Current Output (A)
|
||||
},
|
||||
'temp_adc_settings': { # Temperature ADC Settings (Please read AD7172-2 Documentation)
|
||||
'filter_type': 'Sinc5Sinc1With50hz60HzRejection', # Filter Types
|
||||
'sinc5sinc1odr': None, # (Unused)
|
||||
'sinc3odr': None, # (Unused)
|
||||
'sinc5sinc1postfilter': None, # (Unused)
|
||||
'sinc3fineodr': None, # (Unused)
|
||||
'rate': 16.67 # ADC Sampling Rate (Hz)
|
||||
},
|
||||
'temp_mon_settings': { # Temperature Monitor Settings
|
||||
'upper_limit': 40.0, # Temperature Upper Limit (Degree Celsius)
|
||||
'lower_limit': 10.0 # Temperature Lower Limit (Degree Celsius)
|
||||
},
|
||||
'thermistor_params': { # Thermistor Steinhart-Hart equation parameters
|
||||
't0': 25.0, # t0: Degree Celsius
|
||||
'r0': 10000.0, # r0: Ohm
|
||||
'b': 3900.0 # b: (unitless)
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.GetSettingsSummary, msg_type="Settings", sig=sig)
|
||||
|
||||
async def dfu(self):
|
||||
"""
|
||||
Hard reset and put the connected Kirdy into the Dfu mode for firmware update.
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.Dfu)
|
||||
|
||||
async def save_current_settings_to_flash(self):
|
||||
"""
|
||||
Save the current laser diode and thermostat configurations into flash.
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SaveFlashSettings)
|
||||
|
||||
async def restore_settings_from_flash(self):
|
||||
"""
|
||||
Restore the laser diode and thermostat settings from flash.
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.LoadFlashSettings)
|
||||
|
||||
async def hard_reset(self):
|
||||
"""
|
||||
Hard Reset Kirdy. The socket connection will be closed by Kirdy.
|
||||
Laser diode power and Tec power will be turned off.
|
||||
Kirdy will send out a json({'msg_type': 'HardReset'}) to all sockets before hard reset take place.
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.HardReset)
|
||||
|
||||
class Laser:
|
||||
def __init__(self, send_cmd_handler):
|
||||
self._cmd = CmdList.ld
|
||||
self._send_cmd = send_cmd_handler
|
||||
|
||||
async def set_power_on(self, on):
|
||||
"""
|
||||
Power Up or Power Down laser diode. Powering up the Laser Diode resets the pwr_excursion status
|
||||
- on (True/False)
|
||||
"""
|
||||
if on:
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.PowerUp, None)
|
||||
else:
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.PowerDown, None)
|
||||
|
||||
async def set_default_pwr_on(self, on):
|
||||
"""
|
||||
Set whether laser diode is powered up at Startup.
|
||||
- on (True/False)
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetDefaultPowerOn, on)
|
||||
|
||||
async def set_ld_terms_short(self, short):
|
||||
"""
|
||||
Open/Short laser diode terminals.
|
||||
- on (True/False)
|
||||
"""
|
||||
if short:
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.LdTermsShort, None)
|
||||
else:
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.LdTermsOpen, None)
|
||||
|
||||
async def set_i(self, i):
|
||||
"""
|
||||
Set laser diode output current: Max(0, Min(i_set, 300mA)).
|
||||
- i: A
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetI, i)
|
||||
|
||||
async def set_pd_mon_responsitivity(self, responsitivity):
|
||||
"""
|
||||
Configure the photodiode monitor responsitivity parameter.
|
||||
The value is only effective if ApplyPdParams cmd is issued.
|
||||
- responsitivity: A/W
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetPdResponsitivity, responsitivity)
|
||||
|
||||
async def set_pd_mon_dark_current(self, dark_current):
|
||||
"""
|
||||
Configure the photodiode monitor dark current parameter.
|
||||
The value is only effective if ApplyPdParams cmd is issued.
|
||||
- dark_current: A
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetPdDarkCurrent, dark_current)
|
||||
|
||||
async def apply_pd_params(self):
|
||||
"""
|
||||
Evaluate and apply photodiode monitor parameters that are set with SetPdDarkCurrent and SetPdResponsitivity cmd.
|
||||
After Kirdy receives the cmd, it will check if the current power limit is within the newly calculated
|
||||
power limit range. If it is out of range, the photodiode monitor parameters remains unchanged and Kirdy
|
||||
sends out a "InvalidSettings" message along with an error message.
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.ApplyPdParams)
|
||||
|
||||
async def set_ld_pwr_limit(self, pwr_limit):
|
||||
"""
|
||||
Set the power limit for the power excursion monitor.
|
||||
If the power limit settings is out of range, power limit remains unchanged and Kirdy
|
||||
sends out a "InvalidSettings" message along with an error message.
|
||||
|
||||
If the calculated power with the params of pd_mon > pwr_limit,
|
||||
overpower protection is triggered.
|
||||
- pwr_limit: W
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetLdPwrLimit, pwr_limit)
|
||||
|
||||
async def clear_alarm(self):
|
||||
"""
|
||||
Clear the power excursion monitor alarm.
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.ClearAlarm)
|
||||
|
||||
class Thermostat:
|
||||
def __init__(self, send_cmd_handler, send_raw_cmd_handler):
|
||||
self._cmd = CmdList.thermostat
|
||||
self._send_cmd = send_cmd_handler
|
||||
self._send_raw_cmd = send_raw_cmd_handler
|
||||
|
||||
async def set_power_on(self, on):
|
||||
"""
|
||||
Power up or power down thermostat.
|
||||
- Powering up the thermostat resets the pwr_excursion status
|
||||
"""
|
||||
if on:
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.PowerUp, None)
|
||||
else:
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.PowerDown, None)
|
||||
|
||||
async def set_default_pwr_on(self, on):
|
||||
"""
|
||||
Set whether thermostat is powered up at Startup.
|
||||
- on: (True/False)
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetDefaultPowerOn, on)
|
||||
|
||||
async def set_tec_max_v(self, max_v):
|
||||
"""
|
||||
Set Tec Maximum Voltage Across the TEC Terminals.
|
||||
- max_v: V
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetTecMaxV, max_v)
|
||||
|
||||
async def set_tec_max_cooling_i(self, max_i_pos):
|
||||
"""
|
||||
Set Tec maximum cooling current (Settable Range: 0.0 - 3.0)
|
||||
- max_i_pos: A
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetTecMaxIPos, max_i_pos)
|
||||
|
||||
async def set_tec_max_heating_i(self, max_i_neg):
|
||||
"""
|
||||
Set Tec maximum heating current (Settable Range: 0.0 - 3.0)
|
||||
- max_i_neg: A
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetTecMaxINeg, max_i_neg)
|
||||
|
||||
async def set_tec_i_out(self, i_out):
|
||||
"""
|
||||
Set Tec Output Current (Settable Range: 0.0 - 3.0)
|
||||
This cmd is only effective in constant current control mode
|
||||
or your newly set value will be overwritten by PID Controller Output
|
||||
- i_out: A
|
||||
"""
|
||||
if isinstance(i_out, float):
|
||||
return await self._send_raw_cmd({"tec_set_i": i_out})
|
||||
elif isinstance(i_out, int):
|
||||
return await self._send_raw_cmd({"tec_set_i": float(i_out)})
|
||||
else:
|
||||
raise InvalidDataType
|
||||
|
||||
async def set_constant_current_control_mode(self):
|
||||
"""
|
||||
Disable PID Controller and output current can be controlled with set_tec_i_out() cmd.
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetPidDisEngage, None)
|
||||
|
||||
async def set_temperature_setpoint(self, temperature):
|
||||
"""
|
||||
Set Temperature Setpoint for PID Controller. This parameter is not active in constant current control mode
|
||||
- temperature: Degree Celsius
|
||||
"""
|
||||
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetTemperatureSetpoint, temperature)
|
||||
|
||||
async def set_pid_control_mode(self):
|
||||
"""
|
||||
Enable PID Controller. Its PID Update Interval is controlled by the Temperature ADC polling rate.
|
||||
Please refer to config_temp_adc_filter for the possible polling rate options
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetPidEngage, None)
|
||||
|
||||
async def set_pid_kp(self, kp):
|
||||
"""
|
||||
Set Kp parameter for PID Controller
|
||||
kp: (unitless)
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetPidKp, kp)
|
||||
|
||||
async def set_pid_ki(self, ki):
|
||||
"""
|
||||
Set Ki parameter for PID Controller
|
||||
ki: (unitless)
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetPidKi, ki)
|
||||
|
||||
async def set_pid_kd(self, kd):
|
||||
"""
|
||||
Set Kd parameter for PID Controller
|
||||
kd: (unitless)
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetPidKd, kd)
|
||||
|
||||
async def set_pid_output_max(self, out_max):
|
||||
"""
|
||||
Set max output limit at the PID Output
|
||||
- out_max: A
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetPidOutMax, out_max)
|
||||
|
||||
async def set_pid_output_min(self, out_min):
|
||||
"""
|
||||
Set min output limit at the PID Output
|
||||
- out_min: A
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetPidOutMin, out_min)
|
||||
|
||||
async def set_temp_mon_upper_limit(self, upper_limit):
|
||||
"""
|
||||
Set Temperature Monitor Upper Limit Threshold. Exceeding the limit for too long
|
||||
will force the TEC Controller, PID Controller and Laser Diode Power to Shutdown
|
||||
- upper_limit: Degree Celsius
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetTempMonUpperLimit, upper_limit)
|
||||
|
||||
async def set_temp_mon_lower_limit(self, lower_limit):
|
||||
"""
|
||||
Set Temperature Monitor Lower Limit Threshold. Exceeding the limit for too long
|
||||
will force the TEC Controller, PID Controller and Laser Diode Power to Shutdown
|
||||
- lower_limit: Degree Celsius
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetTempMonLowerLimit, lower_limit)
|
||||
|
||||
async def clear_alarm(self):
|
||||
"""
|
||||
Clear the temperature monitor alarm
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.ClearAlarm)
|
||||
|
||||
async def set_sh_t0(self, t0):
|
||||
"""
|
||||
Set t0 Steinhart-Hart parameter for the laser diode NTC
|
||||
- t0: Degree Celsius
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetShT0, t0)
|
||||
|
||||
async def set_sh_r0(self, r0):
|
||||
"""
|
||||
Set r0 Steinhart-Hart parameter for the laser diode NTC
|
||||
- r0: Ohm
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetShR0, r0)
|
||||
|
||||
async def set_sh_beta(self, beta):
|
||||
"""
|
||||
Set beta Steinhart-Hart parameter for the laser diode NTC
|
||||
- beta: (unitless)
|
||||
"""
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.SetShBeta, beta)
|
||||
|
||||
async def config_temp_adc_filter(self, filter_config):
|
||||
"""
|
||||
Configure the temperature adc filter type and sampling rate.
|
||||
Please refer to AD7172 datasheet for the usage of various types of filter.
|
||||
The actual temperature polling rate is bottlenecked by the processing speed of the MCU and
|
||||
performs differently under different kinds of workload. Please verify the polling rate with
|
||||
the timestamp.
|
||||
"""
|
||||
cmd = {}
|
||||
cmd[self._cmd._target] = self._cmd.ConfigTempAdcFilter.name
|
||||
if hasattr(filter_config, 'rate'):
|
||||
cmd[self._cmd.ConfigTempAdcFilter] = {
|
||||
"filter_type": filter_config._filter_type(),
|
||||
filter_config._odr_type: filter_config.rate,
|
||||
}
|
||||
else:
|
||||
cmd[self._cmd.ConfigTempAdcFilter] = {
|
||||
"filter_type": filter_config._filter_type(),
|
||||
filter_config._odr_type: filter_config,
|
||||
}
|
||||
|
||||
return await self._send_raw_cmd(cmd)
|
||||
|
||||
async def get_poll_interval(self):
|
||||
return await self._send_cmd(self._cmd._target, self._cmd.GetPollInterval, msg_type="Interval")
|
||||
|
||||
class Kirdy:
|
||||
def __init__(self):
|
||||
self.device = Device(self._send_cmd, self._send_raw_cmd)
|
||||
self.laser = Laser(self._send_cmd)
|
||||
self.thermostat = Thermostat(self._send_cmd, self._send_raw_cmd)
|
||||
self.hw_rev = None
|
||||
|
||||
self._task_queue, self._int_msg_queue, self._report_queue = None, None, None
|
||||
self._timeout = 5.0
|
||||
self._writer, self._reader = None, None
|
||||
self._event_loop = None
|
||||
|
||||
self._msg_queue_get_report = False
|
||||
self._report_mode_on = False
|
||||
self._state = State.disconnected
|
||||
|
||||
self.read_response_task, self.handler_task = None, None
|
||||
self._lock = asyncio.Lock()
|
||||
|
||||
# PyQt Signal
|
||||
self._report_sig = None # Dict
|
||||
self._connected_sig = None # Bool
|
||||
self._err_msg_sig = None # Str
|
||||
|
||||
self.connected_event = None
|
||||
|
||||
def get_hw_rev(self):
|
||||
return self.hw_rev
|
||||
|
||||
def set_report_sig(self, sig):
|
||||
"""
|
||||
Connect a PyQt Signal to the active report output(dict). This should be configured before the session is started.
|
||||
"""
|
||||
self._report_sig = sig
|
||||
|
||||
def set_connected_sig(self, sig):
|
||||
"""
|
||||
Connect a PyQt Signal to connection status(bool)
|
||||
- True: Connection is established
|
||||
- False: Connection is dropped
|
||||
"""
|
||||
self._connected_sig = sig
|
||||
|
||||
def set_err_msg_sig(self, sig):
|
||||
"""
|
||||
Emit a error message to a PyQt Signal(str) when a cmd fails to execute
|
||||
"""
|
||||
self._err_msg_sig = sig
|
||||
|
||||
def start_session(self, host='192.168.1.128', port=1337):
|
||||
"""
|
||||
Start Kirdy Connection Session.
|
||||
In case of disconnection, all the queued tasks are cleared and the handler task retries TCP connection indefinitely.
|
||||
- host: Kirdy's IP Address
|
||||
- port: Kirdy's Port Number
|
||||
"""
|
||||
self._host, self._ctrl_port = host, port
|
||||
if self._event_loop is None:
|
||||
try:
|
||||
self._event_loop = asyncio.get_running_loop()
|
||||
except:
|
||||
self._event_loop = asyncio.new_event_loop()
|
||||
self._event_loop.run_forever()
|
||||
self.connected_event = asyncio.Event()
|
||||
self.handler_task = asyncio.create_task(self._handler())
|
||||
|
||||
async def end_session(self, block=False):
|
||||
"""
|
||||
Stop Kirdy's TCP connection and its associated thread.
|
||||
"""
|
||||
if self._event_loop is not None:
|
||||
if block:
|
||||
await self._task_queue.join()
|
||||
|
||||
if self.read_response_task is not None:
|
||||
self.read_response_task.cancel()
|
||||
await self.read_response_task
|
||||
self.read_response_task = None
|
||||
|
||||
if self.handler_task is not None:
|
||||
self.handler_task.cancel()
|
||||
await self.handler_task
|
||||
self.handler_task = None
|
||||
|
||||
self._writer = None
|
||||
|
||||
if self._connected_sig is not None:
|
||||
self._connected_sig.emit(False)
|
||||
|
||||
def connecting(self):
|
||||
"""
|
||||
Return True if client is connecting
|
||||
"""
|
||||
return self._state == State.disconnected and self.read_response_task is not None
|
||||
|
||||
def connected(self):
|
||||
"""
|
||||
Returns True if client is connected.
|
||||
"""
|
||||
return self._writer is not None
|
||||
|
||||
async def wait_until_connected(self):
|
||||
if not(self.connected()):
|
||||
await self.connected_event.wait()
|
||||
|
||||
async def report_mode(self):
|
||||
"""
|
||||
Enable and retrieve active report from Kirdy
|
||||
"""
|
||||
if self.connected():
|
||||
self._report_mode_on = True
|
||||
await self.device.set_active_report_mode(True)
|
||||
report = None
|
||||
|
||||
while self._report_mode_on:
|
||||
report = await self._report_queue.get()
|
||||
if not(isinstance(report, dict)):
|
||||
self.stop_active_report()
|
||||
else:
|
||||
yield report
|
||||
|
||||
if isinstance(report, dict):
|
||||
await self.device.set_active_report_mode(False)
|
||||
else:
|
||||
raise ConnectionError
|
||||
|
||||
def stop_report_mode(self):
|
||||
self._report_mode_on = False
|
||||
|
||||
def task_dispatcher(self, awaitable_fn):
|
||||
"""
|
||||
Enqueue a task to be handled by the handler.
|
||||
"""
|
||||
if self.connected():
|
||||
try:
|
||||
self._task_queue.put_nowait(lambda: awaitable_fn)
|
||||
return True
|
||||
except asyncio.queues.QueueFull:
|
||||
return False
|
||||
else:
|
||||
raise ConnectionError
|
||||
|
||||
async def _sock_disconnection_handling(self):
|
||||
# Reader needn't be closed
|
||||
try:
|
||||
self._writer.close()
|
||||
await self._writer.wait_closed()
|
||||
except:
|
||||
# In Hard Reset/DFU cmd, Kirdy may close its socket first
|
||||
pass
|
||||
self._reader = None
|
||||
self._writer = None
|
||||
|
||||
for i in range(self._report_queue.maxsize):
|
||||
if self._report_queue.full():
|
||||
self._report_queue.get_nowait()
|
||||
self._report_queue.put_nowait(None)
|
||||
|
||||
if self._connected_sig is not None:
|
||||
self._connected_sig.emit(False)
|
||||
|
||||
async def _handler(self):
|
||||
try:
|
||||
self._state = State.disconnected
|
||||
first_con = True
|
||||
task = None
|
||||
while True:
|
||||
if self._state == State.disconnected:
|
||||
try:
|
||||
self.hw_rev = None
|
||||
await self.__coninit(self._timeout)
|
||||
self.read_response_task = asyncio.create_task(self._read_response_handler())
|
||||
task = None
|
||||
logging.info("Connected to %s:%d", self._host, self._ctrl_port)
|
||||
|
||||
hw_rev = await self.device.get_hw_rev()
|
||||
self.hw_rev = hw_rev["hw_rev"]
|
||||
|
||||
if self._connected_sig is not None:
|
||||
self._connected_sig.emit(True)
|
||||
self.connected_event.set()
|
||||
|
||||
# State Transition
|
||||
self._state = State.connected
|
||||
except (OSError, TimeoutError):
|
||||
if first_con:
|
||||
first_con = False
|
||||
logging.warning("Cannot connect to %s:%d. Retrying in the background.", self._host, self._ctrl_port)
|
||||
await asyncio.sleep(5.0)
|
||||
|
||||
elif self._state == State.connected:
|
||||
try:
|
||||
task = await self._task_queue.get()
|
||||
if isinstance(task, Exception):
|
||||
raise task
|
||||
await task()
|
||||
self._task_queue.task_done()
|
||||
|
||||
except (TimeoutError, ConnectionResetError, ConnectionError):
|
||||
logging.warning("Connection to Kirdy is dropped.")
|
||||
first_con = True
|
||||
self.read_response_task.cancel()
|
||||
|
||||
# State Transition
|
||||
self._state = State.disconnected
|
||||
await self._sock_disconnection_handling()
|
||||
except asyncio.exceptions.CancelledError:
|
||||
pass
|
||||
except:
|
||||
logging.warning("Handler experienced an error.", exc_info=True)
|
||||
await self._sock_disconnection_handling()
|
||||
|
||||
async def _read_response_handler(self):
|
||||
try:
|
||||
while True:
|
||||
if self._report_mode_on:
|
||||
response = await asyncio.wait_for(self._read_response(), self._timeout)
|
||||
else:
|
||||
response = await self._read_response()
|
||||
|
||||
if response["msg_type"] == 'HardReset':
|
||||
logging.warn("Kirdy is being hard reset.")
|
||||
raise asyncio.exceptions.CancelledError
|
||||
if response["msg_type"] == 'Dfu':
|
||||
logging.warn("Kirdy enters Dfu Mode.")
|
||||
asyncio.create_task(self.end_session())
|
||||
if response["msg_type"] == 'ConnectionClose':
|
||||
logging.warn("Kirdy runs out of TCP sockets and closes this connected socket.")
|
||||
asyncio.create_task(self.end_session())
|
||||
if response["msg_type"] == 'Report' and not self._msg_queue_get_report:
|
||||
if self._report_sig is None:
|
||||
self._report_queue.put_nowait_overwrite(response)
|
||||
else:
|
||||
self._report_sig.emit(response)
|
||||
else:
|
||||
if self._msg_queue_get_report:
|
||||
self._msg_queue_get_report = False
|
||||
self._int_msg_queue.put_nowait_overwrite(response)
|
||||
except asyncio.exceptions.CancelledError:
|
||||
pass
|
||||
except (TimeoutError, ConnectionResetError, ConnectionError) as exec:
|
||||
self._task_queue.put_nowait_overwrite(exec)
|
||||
self._int_msg_queue.put_nowait_overwrite(exec)
|
||||
except Exception as exec:
|
||||
logging.warn("Read Response Handler experienced an error. Exiting.", exc_info=True)
|
||||
self._task_queue.put_nowait_overwrite(exec)
|
||||
self._int_msg_queue.put_nowait_overwrite(exec)
|
||||
if self._report_mode_on:
|
||||
self._report_mode_on = False
|
||||
self._report_queue.put_nowait_overwrite(TimeoutError)
|
||||
|
||||
async def _stop_handler(self):
|
||||
for task in asyncio.all_tasks():
|
||||
task.cancel()
|
||||
await asyncio.gather(*asyncio.all_tasks(), loop=self._event_loop)
|
||||
|
||||
async def __coninit(self, timeout):
|
||||
def _put_nowait_overwrite(self, item):
|
||||
if self.full():
|
||||
self.get_nowait()
|
||||
self.put_nowait(item)
|
||||
asyncio.Queue.put_nowait_overwrite = _put_nowait_overwrite
|
||||
|
||||
if self._task_queue is not None:
|
||||
while not(self._task_queue.empty()):
|
||||
task = self._task_queue.get_nowait()
|
||||
if isinstance(task, types.FunctionType):
|
||||
task().close()
|
||||
else:
|
||||
self._task_queue = asyncio.Queue(maxsize=16)
|
||||
self._int_msg_queue = asyncio.Queue(maxsize=4)
|
||||
self._report_queue = asyncio.Queue(maxsize=16)
|
||||
|
||||
self._reader, self._writer = await asyncio.wait_for(asyncio.open_connection(self._host, self._ctrl_port), timeout)
|
||||
writer_sock = self._writer.get_extra_info("socket")
|
||||
writer_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
|
||||
async def _read_response(self):
|
||||
raw_response = b''
|
||||
while len(raw_response) == 0:
|
||||
# Ignore 0 size packet
|
||||
raw_response = await self._reader.readuntil()
|
||||
response = raw_response.decode('utf-8', errors='ignore').split("\n")
|
||||
return json.loads(response[0])
|
||||
|
||||
def _response_handling(self, msg, msg_type, sig=None):
|
||||
if msg["msg_type"] in ["InvalidCmd", "InvalidDatatype"]:
|
||||
raise InvalidCmd
|
||||
elif msg["msg_type"] == msg_type:
|
||||
if sig is not None:
|
||||
sig.emit(msg)
|
||||
else:
|
||||
logging.warn(f"Commands fail to execute. {msg['msg_type']}:{msg['msg']}")
|
||||
if self._err_msg_sig is not None and msg['msg'] is not None:
|
||||
self._err_msg_sig.emit(msg['msg'])
|
||||
return msg
|
||||
|
||||
async def _send_raw_cmd(self, cmd, msg_type="Acknowledge", sig=None):
|
||||
if self.connected():
|
||||
async with self._lock:
|
||||
self._writer.write(bytes(json.dumps(cmd), "UTF-8"))
|
||||
await self._writer.drain()
|
||||
msg = await asyncio.wait_for(self._int_msg_queue.get(), self._timeout)
|
||||
return self._response_handling(msg, msg_type, sig)
|
||||
else:
|
||||
raise ConnectionError
|
||||
|
||||
async def _send_cmd(self, target, cmd, data=None, msg_type="Acknowledge", sig=None):
|
||||
cmd_dict = {}
|
||||
cmd_dict[target] = cmd.name
|
||||
|
||||
if cmd == _dt.f32:
|
||||
if isinstance(data, float):
|
||||
cmd_dict[cmd] = data
|
||||
elif isinstance(data, int):
|
||||
cmd_dict[cmd] = float(data)
|
||||
elif cmd == _dt.bool:
|
||||
if isinstance(data, bool):
|
||||
cmd_dict[cmd] = data
|
||||
else:
|
||||
raise InvalidDataType
|
||||
elif cmd == "None":
|
||||
pass
|
||||
|
||||
if msg_type == 'Report':
|
||||
self._msg_queue_get_report = True
|
||||
|
||||
async with self._lock:
|
||||
self._writer.write(bytes(json.dumps(cmd_dict), "UTF-8"))
|
||||
await self._writer.drain()
|
||||
|
||||
msg = await asyncio.wait_for(self._int_msg_queue.get(), self._timeout)
|
||||
if isinstance(msg, Exception):
|
||||
raise msg
|
||||
return self._response_handling(msg, msg_type, sig)
|
1192
pykirdy/kirdy_qt.py
Normal file
1192
pykirdy/kirdy_qt.py
Normal file
File diff suppressed because it is too large
Load Diff
358
pykirdy/pid_autotune.py
Normal file
358
pykirdy/pid_autotune.py
Normal file
@ -0,0 +1,358 @@
|
||||
import math
|
||||
import logging
|
||||
from collections import deque, namedtuple
|
||||
from enum import Enum
|
||||
import socket
|
||||
import json
|
||||
import time
|
||||
import signal
|
||||
from driver.kirdy import Kirdy, FilterConfig
|
||||
import asyncio
|
||||
from sipyco.asyncio_tools import SignalHandler
|
||||
|
||||
# Based on hirshmann pid-autotune libiary
|
||||
# See https://github.com/hirschmann/pid-autotune
|
||||
# Which is in turn based on a fork of Arduino PID AutoTune Library
|
||||
# See https://github.com/t0mpr1c3/Arduino-PID-AutoTune-Library
|
||||
|
||||
|
||||
class PIDAutotuneState(Enum):
|
||||
STATE_OFF = 'off'
|
||||
STATE_RELAY_STEP_UP = 'relay step up'
|
||||
STATE_RELAY_STEP_DOWN = 'relay step down'
|
||||
STATE_SUCCEEDED = 'succeeded'
|
||||
STATE_FAILED = 'failed'
|
||||
STATE_READY = 'ready'
|
||||
|
||||
|
||||
class PIDAutotune:
|
||||
PIDParams = namedtuple('PIDParams', ['Kp', 'Ki', 'Kd'])
|
||||
|
||||
PEAK_AMPLITUDE_TOLERANCE = 0.05
|
||||
|
||||
_tuning_rules = {
|
||||
"ziegler-nichols": [0.6, 1.2, 0.075],
|
||||
"tyreus-luyben": [0.4545, 0.2066, 0.07214],
|
||||
"ciancone-marlin": [0.303, 0.1364, 0.0481],
|
||||
"pessen-integral": [0.7, 1.75, 0.105],
|
||||
"some-overshoot": [0.333, 0.667, 0.111],
|
||||
"no-overshoot": [0.2, 0.4, 0.0667]
|
||||
}
|
||||
|
||||
def __init__(self, setpoint, out_step=10, lookback=60,
|
||||
noiseband=0.5, sampletime=1.2):
|
||||
if setpoint is None:
|
||||
raise ValueError('setpoint must be specified')
|
||||
|
||||
self._inputs = deque(maxlen=round(lookback / sampletime))
|
||||
self._sampletime = sampletime
|
||||
self._setpoint = setpoint
|
||||
self._outputstep = out_step
|
||||
self._noiseband = noiseband
|
||||
self._out_min = -out_step
|
||||
self._out_max = out_step
|
||||
self._state = PIDAutotuneState.STATE_OFF
|
||||
self._peak_timestamps = deque(maxlen=5)
|
||||
self._peaks = deque(maxlen=5)
|
||||
self._output = 0
|
||||
self._last_run_timestamp = 0
|
||||
self._peak_type = 0
|
||||
self._peak_count = 0
|
||||
self._initial_output = 0
|
||||
self._induced_amplitude = 0
|
||||
self._Ku = 0
|
||||
self._Pu = 0
|
||||
|
||||
def setParam(self, target, step, noiseband, sampletime, lookback):
|
||||
self._setpoint = target
|
||||
self._outputstep = step
|
||||
self._out_max = step
|
||||
self._out_min = -step
|
||||
self._noiseband = noiseband
|
||||
self._inputs = deque(maxlen=round(lookback / sampletime))
|
||||
self._sampletime = sampletime
|
||||
|
||||
def setReady(self):
|
||||
self._state = PIDAutotuneState.STATE_READY
|
||||
self._peak_count = 0
|
||||
|
||||
def setOff(self):
|
||||
self._state = PIDAutotuneState.STATE_OFF
|
||||
|
||||
def setFailed(self):
|
||||
self._state = PIDAutotuneState.STATE_FAILED
|
||||
self._peak_count = 30
|
||||
|
||||
def state(self):
|
||||
"""Get the current state."""
|
||||
return self._state
|
||||
|
||||
def output(self):
|
||||
"""Get the last output value."""
|
||||
return self._output
|
||||
|
||||
def tuning_rules(self):
|
||||
"""Get a list of all available tuning rules."""
|
||||
return self._tuning_rules.keys()
|
||||
|
||||
def get_tec_pid (self):
|
||||
divisors = self._tuning_rules["tyreus-luyben"]
|
||||
kp = self._Ku * divisors[0]
|
||||
ki = divisors[1] * self._Ku / self._Pu / (1 / self._sampletime)
|
||||
kd = divisors[2] * self._Ku * self._Pu * (1 / self._sampletime)
|
||||
return kp, ki, kd
|
||||
|
||||
def get_pid_parameters(self, tuning_rule='ziegler-nichols'):
|
||||
"""Get PID parameters.
|
||||
|
||||
Args:
|
||||
tuning_rule (str): Sets the rule which should be used to calculate
|
||||
the parameters.
|
||||
"""
|
||||
divisors = self._tuning_rules[tuning_rule]
|
||||
kp = self._Ku * divisors[0]
|
||||
ki = divisors[1] * self._Ku / self._Pu / (1 / self._sampletime)
|
||||
kd = divisors[2] * self._Ku * self._Pu * (1 / self._sampletime)
|
||||
return PIDAutotune.PIDParams(kp, ki, kd)
|
||||
|
||||
def run(self, input_val, time_input):
|
||||
"""To autotune a system, this method must be called periodically.
|
||||
|
||||
Args:
|
||||
input_val (float): The temperature input value.
|
||||
time_input (float): Current time in seconds.
|
||||
|
||||
Returns:
|
||||
`true` if tuning is finished, otherwise `false`.
|
||||
"""
|
||||
now = time_input * 1000
|
||||
|
||||
if (self._state == PIDAutotuneState.STATE_OFF
|
||||
or self._state == PIDAutotuneState.STATE_SUCCEEDED
|
||||
or self._state == PIDAutotuneState.STATE_FAILED
|
||||
or self._state == PIDAutotuneState.STATE_READY):
|
||||
self._state = PIDAutotuneState.STATE_RELAY_STEP_UP
|
||||
|
||||
self._last_run_timestamp = now
|
||||
|
||||
# check input and change relay state if necessary
|
||||
if (self._state == PIDAutotuneState.STATE_RELAY_STEP_UP
|
||||
and input_val > self._setpoint + self._noiseband):
|
||||
self._state = PIDAutotuneState.STATE_RELAY_STEP_DOWN
|
||||
logging.debug('switched state: {0}'.format(self._state))
|
||||
logging.debug('input: {0}'.format(input_val))
|
||||
elif (self._state == PIDAutotuneState.STATE_RELAY_STEP_DOWN
|
||||
and input_val < self._setpoint - self._noiseband):
|
||||
self._state = PIDAutotuneState.STATE_RELAY_STEP_UP
|
||||
logging.debug('switched state: {0}'.format(self._state))
|
||||
logging.debug('input: {0}'.format(input_val))
|
||||
|
||||
# set output
|
||||
if (self._state == PIDAutotuneState.STATE_RELAY_STEP_UP):
|
||||
self._output = self._initial_output - self._outputstep
|
||||
elif self._state == PIDAutotuneState.STATE_RELAY_STEP_DOWN:
|
||||
self._output = self._initial_output + self._outputstep
|
||||
|
||||
# respect output limits
|
||||
self._output = min(self._output, self._out_max)
|
||||
self._output = max(self._output, self._out_min)
|
||||
|
||||
# identify peaks
|
||||
is_max = True
|
||||
is_min = True
|
||||
|
||||
for val in self._inputs:
|
||||
is_max = is_max and (input_val >= val)
|
||||
is_min = is_min and (input_val <= val)
|
||||
|
||||
self._inputs.append(input_val)
|
||||
|
||||
# we don't trust the maxes or mins until the input array is full
|
||||
if len(self._inputs) < self._inputs.maxlen:
|
||||
return False
|
||||
|
||||
# increment peak count and record peak time for maxima and minima
|
||||
inflection = False
|
||||
|
||||
# peak types:
|
||||
# -1: minimum
|
||||
# +1: maximum
|
||||
if is_max:
|
||||
if self._peak_type == -1:
|
||||
inflection = True
|
||||
self._peak_type = 1
|
||||
elif is_min:
|
||||
if self._peak_type == 1:
|
||||
inflection = True
|
||||
self._peak_type = -1
|
||||
|
||||
# update peak times and values
|
||||
if inflection:
|
||||
self._peak_count += 1
|
||||
self._peaks.append(input_val)
|
||||
self._peak_timestamps.append(now)
|
||||
logging.debug('found peak: {0}'.format(input_val))
|
||||
logging.debug('peak count: {0}'.format(self._peak_count))
|
||||
|
||||
# check for convergence of induced oscillation
|
||||
# convergence of amplitude assessed on last 4 peaks (1.5 cycles)
|
||||
self._induced_amplitude = 0
|
||||
|
||||
if inflection and (self._peak_count > 4):
|
||||
abs_max = self._peaks[-2]
|
||||
abs_min = self._peaks[-2]
|
||||
for i in range(0, len(self._peaks) - 2):
|
||||
self._induced_amplitude += abs(self._peaks[i]
|
||||
- self._peaks[i+1])
|
||||
abs_max = max(self._peaks[i], abs_max)
|
||||
abs_min = min(self._peaks[i], abs_min)
|
||||
|
||||
self._induced_amplitude /= 6.0
|
||||
|
||||
# check convergence criterion for amplitude of induced oscillation
|
||||
amplitude_dev = ((0.5 * (abs_max - abs_min)
|
||||
- self._induced_amplitude)
|
||||
/ self._induced_amplitude)
|
||||
|
||||
logging.debug('amplitude: {0}'.format(self._induced_amplitude))
|
||||
logging.debug('amplitude deviation: {0}'.format(amplitude_dev))
|
||||
|
||||
if amplitude_dev < PIDAutotune.PEAK_AMPLITUDE_TOLERANCE:
|
||||
self._state = PIDAutotuneState.STATE_SUCCEEDED
|
||||
|
||||
# if the autotune has not already converged
|
||||
# terminate after 10 cycles
|
||||
if self._peak_count >= 20:
|
||||
self._output = 0
|
||||
self._state = PIDAutotuneState.STATE_FAILED
|
||||
return True
|
||||
|
||||
if self._state == PIDAutotuneState.STATE_SUCCEEDED:
|
||||
self._output = 0
|
||||
logging.debug('peak finding successful')
|
||||
|
||||
# calculate ultimate gain
|
||||
self._Ku = 4.0 * self._outputstep / \
|
||||
(self._induced_amplitude * math.pi)
|
||||
print('Ku: {0}'.format(self._Ku))
|
||||
|
||||
# calculate ultimate period in seconds
|
||||
period1 = self._peak_timestamps[3] - self._peak_timestamps[1]
|
||||
period2 = self._peak_timestamps[4] - self._peak_timestamps[2]
|
||||
self._Pu = 0.5 * (period1 + period2) / 1000.0
|
||||
print('Pu: {0}'.format(self._Pu))
|
||||
|
||||
for rule in self._tuning_rules:
|
||||
params = self.get_pid_parameters(rule)
|
||||
print('rule: {0}'.format(rule))
|
||||
print('Kp: {0}'.format(params.Kp))
|
||||
print('Ki: {0}'.format(params.Ki))
|
||||
print('Kd: {0}'.format(params.Kd))
|
||||
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
async def main():
|
||||
"""
|
||||
PID AutoTune Tools for Kirdy
|
||||
The obtained temperature works best at the target temperature specified.
|
||||
Before running PID AutoTune, please
|
||||
1. Secure the laser diode onto the LD adapter and copper heat sink
|
||||
2. Make sure Kirdy has warmed up and reached thermal equilibrium state
|
||||
|
||||
In case of PID Autotune Failure, you can
|
||||
1. Run the PID Autotune again
|
||||
2. Or increase the lookback period
|
||||
3. Or increase the sampling rate
|
||||
"""
|
||||
|
||||
# Target temperature of the autotune routine, celsius
|
||||
target_temperature = 20
|
||||
# Value by which output will be increased/decreased from zero, amps
|
||||
output_step = 1
|
||||
# Reference period for local minima/maxima, seconds
|
||||
lookback = 2.0
|
||||
# Determines by how much the input value must
|
||||
# overshoot/undershoot the setpoint, celsius
|
||||
noiseband = 2.0
|
||||
|
||||
kirdy = Kirdy()
|
||||
kirdy.start_session(host='192.168.1.126', port=1337)
|
||||
|
||||
await kirdy.wait_until_connected()
|
||||
|
||||
while not(kirdy.connected()):
|
||||
pass
|
||||
|
||||
await kirdy.laser.set_power_on(False)
|
||||
await kirdy.laser.set_i(0)
|
||||
|
||||
await kirdy.thermostat.set_power_on(False)
|
||||
await kirdy.thermostat.set_constant_current_control_mode()
|
||||
await kirdy.thermostat.set_tec_i_out(0)
|
||||
await kirdy.thermostat.clear_alarm()
|
||||
|
||||
|
||||
signal_handler = SignalHandler()
|
||||
signal_handler.setup()
|
||||
async def sig_handling():
|
||||
await signal_handler.wait_terminate()
|
||||
tuner.setFailed()
|
||||
asyncio.create_task(sig_handling())
|
||||
|
||||
await kirdy.device.set_active_report_mode(False)
|
||||
|
||||
# Configure the Thermistor Parameters
|
||||
await kirdy.thermostat.set_sh_beta(3950)
|
||||
await kirdy.thermostat.set_sh_r0(10.0 * 1000)
|
||||
await kirdy.thermostat.set_sh_t0(25)
|
||||
|
||||
# Set a large enough temperature range so that it won't trigger overtemperature protection
|
||||
await kirdy.thermostat.set_temp_mon_upper_limit(target_temperature + 20)
|
||||
await kirdy.thermostat.set_temp_mon_lower_limit(target_temperature - 20)
|
||||
|
||||
await kirdy.thermostat.set_tec_max_cooling_i(output_step)
|
||||
await kirdy.thermostat.set_tec_max_heating_i(output_step)
|
||||
|
||||
# The Polling Rate of Temperature Adc is equal to the PID Update Interval
|
||||
await kirdy.thermostat.config_temp_adc_filter(FilterConfig.Sinc5Sinc1With50hz60HzRejection.f16sps)
|
||||
settings = await kirdy.device.get_settings_summary()
|
||||
sampling_rate = settings["thermostat"]["temp_adc_settings"]["rate"]
|
||||
|
||||
print("Settings: {0}".format(settings))
|
||||
|
||||
tuner = PIDAutotune(target_temperature, output_step,
|
||||
lookback, noiseband, 1/sampling_rate)
|
||||
|
||||
await kirdy.thermostat.set_power_on(True)
|
||||
|
||||
while True:
|
||||
status_report = await kirdy.device.get_status_report()
|
||||
|
||||
temperature = status_report["thermostat"]["temperature"]
|
||||
ts = status_report['ts']
|
||||
print("Ts: {0} Current Temperature: {1} degree".format(ts, temperature))
|
||||
|
||||
if (tuner.run(temperature, ts / 1000.0)):
|
||||
print(tuner._state)
|
||||
break
|
||||
|
||||
tuner_out = tuner.output()
|
||||
await kirdy.thermostat.set_tec_i_out(float(tuner_out))
|
||||
|
||||
await kirdy.thermostat.set_tec_i_out(0)
|
||||
await kirdy.thermostat.set_power_on(False)
|
||||
|
||||
if tuner.state() == PIDAutotuneState.STATE_SUCCEEDED:
|
||||
pid_params = tuner.get_pid_parameters(tuning_rule="tyreus-luyben")
|
||||
await kirdy.thermostat.set_pid_kp(pid_params.Kp)
|
||||
await kirdy.thermostat.set_pid_ki(pid_params.Ki)
|
||||
await kirdy.thermostat.set_pid_kd(pid_params.Kd)
|
||||
await kirdy.thermostat.set_pid_output_max(1.0)
|
||||
await kirdy.thermostat.set_pid_output_min(1.0)
|
||||
|
||||
await kirdy.end_session()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
210
pykirdy/ui/config_adc_filter_form.ui
Normal file
210
pykirdy/ui/config_adc_filter_form.ui
Normal file
@ -0,0 +1,210 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Cfg_Adc_Filter_Form</class>
|
||||
<widget class="QDialog" name="Cfg_Adc_Filter_Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>786</width>
|
||||
<height>303</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Config Temperature ADC Filter</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="verticalLayoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
<width>731</width>
|
||||
<height>251</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="3,4,4">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Value</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Readings</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="filter_type_layout" stretch="3,4,4">
|
||||
<item>
|
||||
<widget class="QLabel" name="filter_type_lbl">
|
||||
<property name="text">
|
||||
<string>Filter Type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="filter_type_cbox">
|
||||
<property name="editable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="filter_type_reading_lbl">
|
||||
<property name="text">
|
||||
<string>Sinc5Sinc1With50hz60HzRejection</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="filter_sampling_rate_layout" stretch="3,4,4,4">
|
||||
<item>
|
||||
<widget class="QLabel" name="filter_sampling_rate_lbl">
|
||||
<property name="text">
|
||||
<string>Filter Sampling Rate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="filter_sampling_rate_cbox"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="fine_filter_sampling_rate_spinbox">
|
||||
<property name="maximum">
|
||||
<double>1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>16.670000000000002</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="filter_sampling_rate_reading_lbl">
|
||||
<property name="text">
|
||||
<string>F16SPS</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="recorded_sampling_rate_layout" stretch="3,4,4">
|
||||
<item>
|
||||
<widget class="QLabel" name="recorded_sampling_rate_lbl">
|
||||
<property name="text">
|
||||
<string>Recorded Sampling Rate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="recorded_sampling_rate_reading_lbl">
|
||||
<property name="text">
|
||||
<string>16.67</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="apply_btn_layout" stretch="3,2,3,2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="apply_btn">
|
||||
<property name="text">
|
||||
<string>Apply</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="close_btn">
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>close_btn</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>Cfg_Adc_Filter_Form</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>677</x>
|
||||
<y>246</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>392</x>
|
||||
<y>151</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
488
pykirdy/ui/config_pd_mon_form.ui
Normal file
488
pykirdy/ui/config_pd_mon_form.ui
Normal file
@ -0,0 +1,488 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Cfg_Pd_Mon_Form</class>
|
||||
<widget class="QDialog" name="Cfg_Pd_Mon_Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>500</width>
|
||||
<height>520</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>520</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>520</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>config_pd_mon_form</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="verticalLayoutWidget_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>481</width>
|
||||
<height>500</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="cfg_pd_mon_form_layout" stretch="0,2,2,2,2,2,1">
|
||||
<item>
|
||||
<widget class="QLabel" name="title_lbl">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>22</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Configure Photodiode Monitor</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="pwr_off_layout" stretch="5,4">
|
||||
<property name="spacing">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SizeConstraint::SetDefaultConstraint</enum>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="pwr_off_lbl">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string> Step 1: Turn off Laser Power</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pwr_off_btn">
|
||||
<property name="text">
|
||||
<string>Power Off</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="rst_ld_pwr_limit_layout" stretch="5,4">
|
||||
<item>
|
||||
<widget class="QLabel" name="rst_ld_pwr_limit_lbl">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string> Step 2: Reset Ld Pwr Limit to 0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="rst_ld_pwr_limit_btn">
|
||||
<property name="text">
|
||||
<string>Reset Ld Pwr Limit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="cfg_pd_params_layout">
|
||||
<item>
|
||||
<widget class="QLabel" name="cfg_pd_params_lbl">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string> Step 3: Configure Photodiode Parameters</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="5,2,2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string> Value</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Reading</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="cfg_responsitivity_layout" stretch="5,2,2">
|
||||
<item>
|
||||
<widget class="QLabel" name="cfg_responsitivity_lbl">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Responsitivity: </string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="cfg_responsitivity_spinbox">
|
||||
<property name="suffix">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.001000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="cfg_responsitivity_reading">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0.0000</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="cfg_dark_current_layout" stretch="5,2,2">
|
||||
<item>
|
||||
<widget class="QLabel" name="cfg_dark_current_lbl">
|
||||
<property name="text">
|
||||
<string>Dark Current: </string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="cfg_dark_current_spinbox">
|
||||
<property name="decimals">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.001000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="cfg_dark_current_reading">
|
||||
<property name="text">
|
||||
<string>0.0000</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="apply_pd_params_layout" stretch="5,4">
|
||||
<property name="spacing">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>10</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="apply_pd_params_btn">
|
||||
<property name="text">
|
||||
<string>Apply</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="cfg_pwr_limit_layout">
|
||||
<item>
|
||||
<widget class="QLabel" name="cfg_pd_pwr_limit_lbl">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string> Step 4: Configure Laser Diode Power Limit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="5,2,2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string> Value</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Reading</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="pwr_limit_layout" stretch="5,2,2">
|
||||
<item>
|
||||
<widget class="QLabel" name="cfg_pwr_limit_lbl">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Power Limit:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="cfg_pwr_limit_spinbox">
|
||||
<property name="decimals">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.001000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="cfg_pwr_limit_reading">
|
||||
<property name="text">
|
||||
<string>0.0000</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="settable_pwr_range_layout" stretch="5,4">
|
||||
<property name="spacing">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="settable_pwr_range_lbl">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Settable Power Range:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="settable_pwr_range_display_lbl">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>28</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>( Power Range )</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="apply_pwr_limit_layout" stretch="2,3,4">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="apply_pwr_limit_max_btn">
|
||||
<property name="text">
|
||||
<string>Apply Max</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="apply_pwr_limit_btn">
|
||||
<property name="text">
|
||||
<string>Apply</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="pwr_on_layout" stretch="5,4">
|
||||
<property name="spacing">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="pwr_on_lbl">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string> Step 5: Turn On Laser Power</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pwr_on_btn">
|
||||
<property name="text">
|
||||
<string>Clear Alarm and Power On</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="close_btn_layout" stretch="5,4">
|
||||
<property name="spacing">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="close_btn">
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>close_btn</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>Cfg_Pd_Mon_Form</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>141</x>
|
||||
<y>456</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>281</x>
|
||||
<y>355</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
376
pykirdy/ui/conn_settings_form.ui
Normal file
376
pykirdy/ui/conn_settings_form.ui
Normal file
@ -0,0 +1,376 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Conn_Settings_Form</class>
|
||||
<widget class="QDialog" name="Conn_Settings_Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>415</width>
|
||||
<height>145</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>415</width>
|
||||
<height>145</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>415</width>
|
||||
<height>145</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Connection Settings</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="horizontalLayoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>10</y>
|
||||
<width>371</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="ip_addr_layout">
|
||||
<item>
|
||||
<widget class="QLabel" name="ip_addr_label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>IP Address:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="addr_in_0">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>192</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="dot_0_label">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>10</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
<bold>false</bold>
|
||||
<kerning>true</kerning>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="addr_in_1">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>168</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="dot_1_label">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>10</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
<bold>false</bold>
|
||||
<kerning>true</kerning>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="addr_in_2">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="dot_2_label">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>10</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
<bold>false</bold>
|
||||
<kerning>true</kerning>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="addr_in_3">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>128</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="horizontalLayoutWidget_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>50</y>
|
||||
<width>371</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="port_layout" stretch="3,0,6">
|
||||
<item>
|
||||
<widget class="QLabel" name="port_no_label">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>97</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Port:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="port_in">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>1337</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="port_layout_spacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Preferred</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="horizontalLayoutWidget_4">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>90</y>
|
||||
<width>371</width>
|
||||
<height>47</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="buttons_layout">
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="connect_btn">
|
||||
<property name="text">
|
||||
<string>Connect</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cancel_btn">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>addr_in_0</tabstop>
|
||||
<tabstop>addr_in_1</tabstop>
|
||||
<tabstop>addr_in_2</tabstop>
|
||||
<tabstop>addr_in_3</tabstop>
|
||||
<tabstop>port_in</tabstop>
|
||||
<tabstop>connect_btn</tabstop>
|
||||
<tabstop>cancel_btn</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>cancel_btn</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>Conn_Settings_Form</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>340</x>
|
||||
<y>140</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>364</x>
|
||||
<y>96</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>connect_btn</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>Conn_Settings_Form</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>258</x>
|
||||
<y>147</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>237</x>
|
||||
<y>99</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
696
pykirdy/ui/kirdy_qt.ui
Normal file
696
pykirdy/ui/kirdy_qt.ui
Normal file
@ -0,0 +1,696 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1280</width>
|
||||
<height>720</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>1280</width>
|
||||
<height>720</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>3840</width>
|
||||
<height>2160</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Kirdy Control Panel</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset theme="application-x-executable"/>
|
||||
</property>
|
||||
<widget class="QWidget" name="main_widget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout" name="main_layout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SizeConstraint::SetDefaultConstraint</enum>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="ctrl_vertical_layout" stretch="1,1,10,1,1,1,10,1">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SizeConstraint::SetMaximumSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="ld_section_label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string> Laser Diode</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ParameterTree" name="ld_status" native="true">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>57</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ParameterTree" name="ld_tree" native="true">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="ld_btns_layout">
|
||||
<property name="spacing">
|
||||
<number>30</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="ld_pwr_on_btn">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>POWER ON</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="ld_pwr_off_btn">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>POWER OFF</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="ld_clear_alarm_btn">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CLEAR ALARM</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="tec_section_label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string> Thermostat</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ParameterTree" name="tec_status" native="true">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>57</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ParameterTree" name="tec_tree" native="true">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="tec_btns_layout">
|
||||
<property name="spacing">
|
||||
<number>30</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="tec_pwr_on_btn">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>POWER ON</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="tec_pwr_off_btn">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>POWER OFF</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="tec_clear_alarm_btn">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CLEAR ALARM</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="graphgroup">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SizeConstraint::SetNoConstraint</enum>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<widget class="LivePlotWidget" name="tec_temp_graph" native="true">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="LivePlotWidget" name="tec_i_graph" native="true">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="LivePlotWidget" name="pd_mon_pwr_graph" native="true">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="LivePlotWidget" name="ld_i_set_graph" native="true">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="bottom_settings_group">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Shape::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="settings_layout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="connect_btn">
|
||||
<property name="text">
|
||||
<string>Connect</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="status_lbl">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>480</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>480</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="baseSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Disconnected</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="plot_settings">
|
||||
<property name="toolTip">
|
||||
<string>Plot Settings</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>📉</string>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::ToolButtonPopupMode::InstantPopup</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="limits_warning">
|
||||
<property name="toolTipDuration">
|
||||
<number>1000000000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="background_task_lbl">
|
||||
<property name="text">
|
||||
<string>Ready.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QtWaitingSpinner" name="loading_spinner" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="polling_rate_lbl">
|
||||
<property name="text">
|
||||
<string>Polling Rate (Hz): </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="report_group" native="true">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="polling_rate_spinbox">
|
||||
<property name="maximum">
|
||||
<double>1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>20.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="polling_rate_apply_btn">
|
||||
<property name="text">
|
||||
<string>Apply</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menuBar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1280</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuKirdy">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Kirdy</string>
|
||||
</property>
|
||||
<addaction name="menu_action_about_kirdy"/>
|
||||
<addaction name="menu_action_update_net_settings"/>
|
||||
<addaction name="menu_action_save"/>
|
||||
<addaction name="menu_action_load"/>
|
||||
<addaction name="menu_action_dfu_mode"/>
|
||||
<addaction name="menu_action_hard_reset"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuAbout">
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>About</string>
|
||||
</property>
|
||||
<addaction name="menu_action_about_gui"/>
|
||||
</widget>
|
||||
<addaction name="menuKirdy"/>
|
||||
<addaction name="menuAbout"/>
|
||||
</widget>
|
||||
<action name="actionReset">
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Reset the Kirdy</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionEnter_DFU_Mode">
|
||||
<property name="text">
|
||||
<string>Enter DFU Mode</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Reset kirdy and enter USB device firmware update (DFU) mode</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionNetwork_Settings">
|
||||
<property name="text">
|
||||
<string>Network Settings</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Configure IPv4 address, netmask length, and optional default gateway</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAbout_Kirdy">
|
||||
<property name="text">
|
||||
<string>About Kirdy</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Show Kirdy hardware revision, and settings related to i</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionLoad_all_configs">
|
||||
<property name="text">
|
||||
<string>Load all channel configs from flash</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Restore configuration for all channels from flash</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSave_all_configs">
|
||||
<property name="text">
|
||||
<string>Save all channel configs to flash</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Save configuration for all channels to flash</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionConnection_Settings">
|
||||
<property name="text">
|
||||
<string>Connect</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="menu_action_update_net_settings">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Update Network Settings</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="menu_action_dfu_mode">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enter DFU Mode</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="menu_action_save">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save Settings to Flash</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="menu_action_hard_reset">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Hard Reset</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="menu_action_connect">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Connect</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="menu_action_disconnect">
|
||||
<property name="text">
|
||||
<string>Disconnect</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="menu_action_about_gui">
|
||||
<property name="text">
|
||||
<string>About GUI</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action">
|
||||
<property name="text">
|
||||
<string>test</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="menu_action_about_kirdy">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>About Kirdy</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="menu_action_load">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Load Settings from Flash</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ParameterTree</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>pyqtgraph.parametertree</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>LivePlotWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>pglive.sources.live_plot_widget</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QtWaitingSpinner</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>waitingspinnerwidget</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
6
pykirdy/ui/mainwindow.qss
Normal file
6
pykirdy/ui/mainwindow.qss
Normal file
@ -0,0 +1,6 @@
|
||||
QPushButton:disabled { color: gray }
|
||||
ParameterTree:disabled { color: gray }
|
||||
QToolButton:disabled { color: gray }
|
||||
QDoubleSpinBox:disabled { color: gray }
|
||||
QCheckBox:disabled { color: gray }
|
||||
QMenu:disabled { color: gray }
|
131
pykirdy/ui/ui_config_adc_filter_form.py
Normal file
131
pykirdy/ui/ui_config_adc_filter_form.py
Normal file
@ -0,0 +1,131 @@
|
||||
# Form implementation generated from reading ui file 'config_adc_filter_form.ui'
|
||||
#
|
||||
# Created by: PyQt6 UI code generator 6.6.0
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_Cfg_Adc_Filter_Form(object):
|
||||
def setupUi(self, Cfg_Adc_Filter_Form):
|
||||
Cfg_Adc_Filter_Form.setObjectName("Cfg_Adc_Filter_Form")
|
||||
Cfg_Adc_Filter_Form.resize(786, 303)
|
||||
self.verticalLayoutWidget = QtWidgets.QWidget(parent=Cfg_Adc_Filter_Form)
|
||||
self.verticalLayoutWidget.setGeometry(QtCore.QRect(20, 20, 731, 251))
|
||||
self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
|
||||
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
|
||||
self.horizontalLayout.addItem(spacerItem)
|
||||
self.label_4 = QtWidgets.QLabel(parent=self.verticalLayoutWidget)
|
||||
self.label_4.setObjectName("label_4")
|
||||
self.horizontalLayout.addWidget(self.label_4)
|
||||
self.label_5 = QtWidgets.QLabel(parent=self.verticalLayoutWidget)
|
||||
self.label_5.setObjectName("label_5")
|
||||
self.horizontalLayout.addWidget(self.label_5)
|
||||
self.horizontalLayout.setStretch(0, 3)
|
||||
self.horizontalLayout.setStretch(1, 4)
|
||||
self.horizontalLayout.setStretch(2, 4)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout)
|
||||
self.filter_type_layout = QtWidgets.QHBoxLayout()
|
||||
self.filter_type_layout.setObjectName("filter_type_layout")
|
||||
self.filter_type_lbl = QtWidgets.QLabel(parent=self.verticalLayoutWidget)
|
||||
self.filter_type_lbl.setObjectName("filter_type_lbl")
|
||||
self.filter_type_layout.addWidget(self.filter_type_lbl)
|
||||
self.filter_type_cbox = QtWidgets.QComboBox(parent=self.verticalLayoutWidget)
|
||||
self.filter_type_cbox.setEditable(False)
|
||||
self.filter_type_cbox.setObjectName("filter_type_cbox")
|
||||
self.filter_type_layout.addWidget(self.filter_type_cbox)
|
||||
self.filter_type_reading_lbl = QtWidgets.QLabel(parent=self.verticalLayoutWidget)
|
||||
self.filter_type_reading_lbl.setObjectName("filter_type_reading_lbl")
|
||||
self.filter_type_layout.addWidget(self.filter_type_reading_lbl)
|
||||
self.filter_type_layout.setStretch(0, 3)
|
||||
self.filter_type_layout.setStretch(1, 4)
|
||||
self.filter_type_layout.setStretch(2, 4)
|
||||
self.verticalLayout.addLayout(self.filter_type_layout)
|
||||
self.filter_sampling_rate_layout = QtWidgets.QHBoxLayout()
|
||||
self.filter_sampling_rate_layout.setObjectName("filter_sampling_rate_layout")
|
||||
self.filter_sampling_rate_lbl = QtWidgets.QLabel(parent=self.verticalLayoutWidget)
|
||||
self.filter_sampling_rate_lbl.setObjectName("filter_sampling_rate_lbl")
|
||||
self.filter_sampling_rate_layout.addWidget(self.filter_sampling_rate_lbl)
|
||||
self.filter_sampling_rate_cbox = QtWidgets.QComboBox(parent=self.verticalLayoutWidget)
|
||||
self.filter_sampling_rate_cbox.setObjectName("filter_sampling_rate_cbox")
|
||||
self.filter_sampling_rate_layout.addWidget(self.filter_sampling_rate_cbox)
|
||||
self.fine_filter_sampling_rate_spinbox = QtWidgets.QDoubleSpinBox(parent=self.verticalLayoutWidget)
|
||||
self.fine_filter_sampling_rate_spinbox.setMaximum(1000.0)
|
||||
self.fine_filter_sampling_rate_spinbox.setProperty("value", 16.67)
|
||||
self.fine_filter_sampling_rate_spinbox.setObjectName("fine_filter_sampling_rate_spinbox")
|
||||
self.filter_sampling_rate_layout.addWidget(self.fine_filter_sampling_rate_spinbox)
|
||||
self.filter_sampling_rate_reading_lbl = QtWidgets.QLabel(parent=self.verticalLayoutWidget)
|
||||
self.filter_sampling_rate_reading_lbl.setObjectName("filter_sampling_rate_reading_lbl")
|
||||
self.filter_sampling_rate_layout.addWidget(self.filter_sampling_rate_reading_lbl)
|
||||
self.filter_sampling_rate_layout.setStretch(0, 3)
|
||||
self.filter_sampling_rate_layout.setStretch(1, 4)
|
||||
self.filter_sampling_rate_layout.setStretch(2, 4)
|
||||
self.filter_sampling_rate_layout.setStretch(3, 4)
|
||||
self.verticalLayout.addLayout(self.filter_sampling_rate_layout)
|
||||
self.recorded_sampling_rate_layout = QtWidgets.QHBoxLayout()
|
||||
self.recorded_sampling_rate_layout.setObjectName("recorded_sampling_rate_layout")
|
||||
self.recorded_sampling_rate_lbl = QtWidgets.QLabel(parent=self.verticalLayoutWidget)
|
||||
self.recorded_sampling_rate_lbl.setObjectName("recorded_sampling_rate_lbl")
|
||||
self.recorded_sampling_rate_layout.addWidget(self.recorded_sampling_rate_lbl)
|
||||
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
|
||||
self.recorded_sampling_rate_layout.addItem(spacerItem1)
|
||||
self.recorded_sampling_rate_reading_lbl = QtWidgets.QLabel(parent=self.verticalLayoutWidget)
|
||||
self.recorded_sampling_rate_reading_lbl.setObjectName("recorded_sampling_rate_reading_lbl")
|
||||
self.recorded_sampling_rate_layout.addWidget(self.recorded_sampling_rate_reading_lbl)
|
||||
self.recorded_sampling_rate_layout.setStretch(0, 3)
|
||||
self.recorded_sampling_rate_layout.setStretch(1, 4)
|
||||
self.recorded_sampling_rate_layout.setStretch(2, 4)
|
||||
self.verticalLayout.addLayout(self.recorded_sampling_rate_layout)
|
||||
self.apply_btn_layout = QtWidgets.QHBoxLayout()
|
||||
self.apply_btn_layout.setObjectName("apply_btn_layout")
|
||||
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
|
||||
self.apply_btn_layout.addItem(spacerItem2)
|
||||
self.apply_btn = QtWidgets.QPushButton(parent=self.verticalLayoutWidget)
|
||||
self.apply_btn.setObjectName("apply_btn")
|
||||
self.apply_btn_layout.addWidget(self.apply_btn)
|
||||
spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
|
||||
self.apply_btn_layout.addItem(spacerItem3)
|
||||
self.close_btn = QtWidgets.QPushButton(parent=self.verticalLayoutWidget)
|
||||
self.close_btn.setObjectName("close_btn")
|
||||
self.apply_btn_layout.addWidget(self.close_btn)
|
||||
self.apply_btn_layout.setStretch(0, 3)
|
||||
self.apply_btn_layout.setStretch(1, 2)
|
||||
self.apply_btn_layout.setStretch(2, 3)
|
||||
self.apply_btn_layout.setStretch(3, 2)
|
||||
self.verticalLayout.addLayout(self.apply_btn_layout)
|
||||
|
||||
self.retranslateUi(Cfg_Adc_Filter_Form)
|
||||
self.close_btn.clicked.connect(Cfg_Adc_Filter_Form.accept) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(Cfg_Adc_Filter_Form)
|
||||
|
||||
def retranslateUi(self, Cfg_Adc_Filter_Form):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
Cfg_Adc_Filter_Form.setWindowTitle(_translate("Cfg_Adc_Filter_Form", "Config Temperature ADC Filter"))
|
||||
self.label_4.setText(_translate("Cfg_Adc_Filter_Form", "Value"))
|
||||
self.label_5.setText(_translate("Cfg_Adc_Filter_Form", "Readings"))
|
||||
self.filter_type_lbl.setText(_translate("Cfg_Adc_Filter_Form", "Filter Type"))
|
||||
self.filter_type_reading_lbl.setText(_translate("Cfg_Adc_Filter_Form", "Sinc5Sinc1With50hz60HzRejection"))
|
||||
self.filter_sampling_rate_lbl.setText(_translate("Cfg_Adc_Filter_Form", "Filter Sampling Rate"))
|
||||
self.filter_sampling_rate_reading_lbl.setText(_translate("Cfg_Adc_Filter_Form", "F16SPS"))
|
||||
self.recorded_sampling_rate_lbl.setText(_translate("Cfg_Adc_Filter_Form", "Recorded Sampling Rate"))
|
||||
self.recorded_sampling_rate_reading_lbl.setText(_translate("Cfg_Adc_Filter_Form", "16.67"))
|
||||
self.apply_btn.setText(_translate("Cfg_Adc_Filter_Form", "Apply"))
|
||||
self.close_btn.setText(_translate("Cfg_Adc_Filter_Form", "Close"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
Cfg_Adc_Filter_Form = QtWidgets.QDialog()
|
||||
ui = Ui_Cfg_Adc_Filter_Form()
|
||||
ui.setupUi(Cfg_Adc_Filter_Form)
|
||||
Cfg_Adc_Filter_Form.show()
|
||||
sys.exit(app.exec())
|
295
pykirdy/ui/ui_config_pd_mon_form.py
Normal file
295
pykirdy/ui/ui_config_pd_mon_form.py
Normal file
@ -0,0 +1,295 @@
|
||||
# Form implementation generated from reading ui file 'config_pd_mon_form.ui'
|
||||
#
|
||||
# Created by: PyQt6 UI code generator 6.6.0
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_Cfg_Pd_Mon_Form(object):
|
||||
def setupUi(self, Cfg_Pd_Mon_Form):
|
||||
Cfg_Pd_Mon_Form.setObjectName("Cfg_Pd_Mon_Form")
|
||||
Cfg_Pd_Mon_Form.resize(500, 520)
|
||||
Cfg_Pd_Mon_Form.setMinimumSize(QtCore.QSize(500, 520))
|
||||
Cfg_Pd_Mon_Form.setMaximumSize(QtCore.QSize(500, 520))
|
||||
self.verticalLayoutWidget_2 = QtWidgets.QWidget(parent=Cfg_Pd_Mon_Form)
|
||||
self.verticalLayoutWidget_2.setGeometry(QtCore.QRect(10, 10, 481, 500))
|
||||
self.verticalLayoutWidget_2.setObjectName("verticalLayoutWidget_2")
|
||||
self.cfg_pd_mon_form_layout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget_2)
|
||||
self.cfg_pd_mon_form_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.cfg_pd_mon_form_layout.setObjectName("cfg_pd_mon_form_layout")
|
||||
self.title_lbl = QtWidgets.QLabel(parent=self.verticalLayoutWidget_2)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(22)
|
||||
self.title_lbl.setFont(font)
|
||||
self.title_lbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.title_lbl.setObjectName("title_lbl")
|
||||
self.cfg_pd_mon_form_layout.addWidget(self.title_lbl)
|
||||
self.pwr_off_layout = QtWidgets.QHBoxLayout()
|
||||
self.pwr_off_layout.setSizeConstraint(QtWidgets.QLayout.SizeConstraint.SetDefaultConstraint)
|
||||
self.pwr_off_layout.setSpacing(12)
|
||||
self.pwr_off_layout.setObjectName("pwr_off_layout")
|
||||
self.pwr_off_lbl = QtWidgets.QLabel(parent=self.verticalLayoutWidget_2)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(14)
|
||||
self.pwr_off_lbl.setFont(font)
|
||||
self.pwr_off_lbl.setObjectName("pwr_off_lbl")
|
||||
self.pwr_off_layout.addWidget(self.pwr_off_lbl)
|
||||
self.pwr_off_btn = QtWidgets.QPushButton(parent=self.verticalLayoutWidget_2)
|
||||
self.pwr_off_btn.setObjectName("pwr_off_btn")
|
||||
self.pwr_off_layout.addWidget(self.pwr_off_btn)
|
||||
self.pwr_off_layout.setStretch(0, 5)
|
||||
self.pwr_off_layout.setStretch(1, 4)
|
||||
self.cfg_pd_mon_form_layout.addLayout(self.pwr_off_layout)
|
||||
self.rst_ld_pwr_limit_layout = QtWidgets.QHBoxLayout()
|
||||
self.rst_ld_pwr_limit_layout.setObjectName("rst_ld_pwr_limit_layout")
|
||||
self.rst_ld_pwr_limit_lbl = QtWidgets.QLabel(parent=self.verticalLayoutWidget_2)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(14)
|
||||
self.rst_ld_pwr_limit_lbl.setFont(font)
|
||||
self.rst_ld_pwr_limit_lbl.setObjectName("rst_ld_pwr_limit_lbl")
|
||||
self.rst_ld_pwr_limit_layout.addWidget(self.rst_ld_pwr_limit_lbl)
|
||||
self.rst_ld_pwr_limit_btn = QtWidgets.QPushButton(parent=self.verticalLayoutWidget_2)
|
||||
self.rst_ld_pwr_limit_btn.setObjectName("rst_ld_pwr_limit_btn")
|
||||
self.rst_ld_pwr_limit_layout.addWidget(self.rst_ld_pwr_limit_btn)
|
||||
self.rst_ld_pwr_limit_layout.setStretch(0, 5)
|
||||
self.rst_ld_pwr_limit_layout.setStretch(1, 4)
|
||||
self.cfg_pd_mon_form_layout.addLayout(self.rst_ld_pwr_limit_layout)
|
||||
self.cfg_pd_params_layout = QtWidgets.QVBoxLayout()
|
||||
self.cfg_pd_params_layout.setObjectName("cfg_pd_params_layout")
|
||||
self.cfg_pd_params_lbl = QtWidgets.QLabel(parent=self.verticalLayoutWidget_2)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(14)
|
||||
self.cfg_pd_params_lbl.setFont(font)
|
||||
self.cfg_pd_params_lbl.setObjectName("cfg_pd_params_lbl")
|
||||
self.cfg_pd_params_layout.addWidget(self.cfg_pd_params_lbl)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
|
||||
self.horizontalLayout.addItem(spacerItem)
|
||||
self.label_2 = QtWidgets.QLabel(parent=self.verticalLayoutWidget_2)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.horizontalLayout.addWidget(self.label_2)
|
||||
self.label = QtWidgets.QLabel(parent=self.verticalLayoutWidget_2)
|
||||
self.label.setObjectName("label")
|
||||
self.horizontalLayout.addWidget(self.label)
|
||||
self.horizontalLayout.setStretch(0, 5)
|
||||
self.horizontalLayout.setStretch(1, 2)
|
||||
self.horizontalLayout.setStretch(2, 2)
|
||||
self.cfg_pd_params_layout.addLayout(self.horizontalLayout)
|
||||
self.cfg_responsitivity_layout = QtWidgets.QHBoxLayout()
|
||||
self.cfg_responsitivity_layout.setObjectName("cfg_responsitivity_layout")
|
||||
self.cfg_responsitivity_lbl = QtWidgets.QLabel(parent=self.verticalLayoutWidget_2)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.cfg_responsitivity_lbl.sizePolicy().hasHeightForWidth())
|
||||
self.cfg_responsitivity_lbl.setSizePolicy(sizePolicy)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
self.cfg_responsitivity_lbl.setFont(font)
|
||||
self.cfg_responsitivity_lbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
|
||||
self.cfg_responsitivity_lbl.setObjectName("cfg_responsitivity_lbl")
|
||||
self.cfg_responsitivity_layout.addWidget(self.cfg_responsitivity_lbl)
|
||||
self.cfg_responsitivity_spinbox = QtWidgets.QDoubleSpinBox(parent=self.verticalLayoutWidget_2)
|
||||
self.cfg_responsitivity_spinbox.setSuffix("")
|
||||
self.cfg_responsitivity_spinbox.setDecimals(4)
|
||||
self.cfg_responsitivity_spinbox.setMaximum(1000.0)
|
||||
self.cfg_responsitivity_spinbox.setSingleStep(0.001)
|
||||
self.cfg_responsitivity_spinbox.setObjectName("cfg_responsitivity_spinbox")
|
||||
self.cfg_responsitivity_layout.addWidget(self.cfg_responsitivity_spinbox)
|
||||
self.cfg_responsitivity_reading = QtWidgets.QLabel(parent=self.verticalLayoutWidget_2)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.cfg_responsitivity_reading.sizePolicy().hasHeightForWidth())
|
||||
self.cfg_responsitivity_reading.setSizePolicy(sizePolicy)
|
||||
self.cfg_responsitivity_reading.setObjectName("cfg_responsitivity_reading")
|
||||
self.cfg_responsitivity_layout.addWidget(self.cfg_responsitivity_reading)
|
||||
self.cfg_responsitivity_layout.setStretch(0, 5)
|
||||
self.cfg_responsitivity_layout.setStretch(1, 2)
|
||||
self.cfg_responsitivity_layout.setStretch(2, 2)
|
||||
self.cfg_pd_params_layout.addLayout(self.cfg_responsitivity_layout)
|
||||
self.cfg_dark_current_layout = QtWidgets.QHBoxLayout()
|
||||
self.cfg_dark_current_layout.setObjectName("cfg_dark_current_layout")
|
||||
self.cfg_dark_current_lbl = QtWidgets.QLabel(parent=self.verticalLayoutWidget_2)
|
||||
self.cfg_dark_current_lbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
|
||||
self.cfg_dark_current_lbl.setObjectName("cfg_dark_current_lbl")
|
||||
self.cfg_dark_current_layout.addWidget(self.cfg_dark_current_lbl)
|
||||
self.cfg_dark_current_spinbox = QtWidgets.QDoubleSpinBox(parent=self.verticalLayoutWidget_2)
|
||||
self.cfg_dark_current_spinbox.setDecimals(4)
|
||||
self.cfg_dark_current_spinbox.setSingleStep(0.001)
|
||||
self.cfg_dark_current_spinbox.setObjectName("cfg_dark_current_spinbox")
|
||||
self.cfg_dark_current_layout.addWidget(self.cfg_dark_current_spinbox)
|
||||
self.cfg_dark_current_reading = QtWidgets.QLabel(parent=self.verticalLayoutWidget_2)
|
||||
self.cfg_dark_current_reading.setObjectName("cfg_dark_current_reading")
|
||||
self.cfg_dark_current_layout.addWidget(self.cfg_dark_current_reading)
|
||||
self.cfg_dark_current_layout.setStretch(0, 5)
|
||||
self.cfg_dark_current_layout.setStretch(1, 2)
|
||||
self.cfg_dark_current_layout.setStretch(2, 2)
|
||||
self.cfg_pd_params_layout.addLayout(self.cfg_dark_current_layout)
|
||||
self.apply_pd_params_layout = QtWidgets.QHBoxLayout()
|
||||
self.apply_pd_params_layout.setSpacing(12)
|
||||
self.apply_pd_params_layout.setObjectName("apply_pd_params_layout")
|
||||
spacerItem1 = QtWidgets.QSpacerItem(10, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
|
||||
self.apply_pd_params_layout.addItem(spacerItem1)
|
||||
self.apply_pd_params_btn = QtWidgets.QPushButton(parent=self.verticalLayoutWidget_2)
|
||||
self.apply_pd_params_btn.setObjectName("apply_pd_params_btn")
|
||||
self.apply_pd_params_layout.addWidget(self.apply_pd_params_btn)
|
||||
self.apply_pd_params_layout.setStretch(0, 5)
|
||||
self.apply_pd_params_layout.setStretch(1, 4)
|
||||
self.cfg_pd_params_layout.addLayout(self.apply_pd_params_layout)
|
||||
self.cfg_pd_mon_form_layout.addLayout(self.cfg_pd_params_layout)
|
||||
self.cfg_pwr_limit_layout = QtWidgets.QVBoxLayout()
|
||||
self.cfg_pwr_limit_layout.setObjectName("cfg_pwr_limit_layout")
|
||||
self.cfg_pd_pwr_limit_lbl = QtWidgets.QLabel(parent=self.verticalLayoutWidget_2)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(14)
|
||||
self.cfg_pd_pwr_limit_lbl.setFont(font)
|
||||
self.cfg_pd_pwr_limit_lbl.setObjectName("cfg_pd_pwr_limit_lbl")
|
||||
self.cfg_pwr_limit_layout.addWidget(self.cfg_pd_pwr_limit_lbl)
|
||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
||||
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
|
||||
self.horizontalLayout_2.addItem(spacerItem2)
|
||||
self.label_3 = QtWidgets.QLabel(parent=self.verticalLayoutWidget_2)
|
||||
self.label_3.setObjectName("label_3")
|
||||
self.horizontalLayout_2.addWidget(self.label_3)
|
||||
self.label_4 = QtWidgets.QLabel(parent=self.verticalLayoutWidget_2)
|
||||
self.label_4.setObjectName("label_4")
|
||||
self.horizontalLayout_2.addWidget(self.label_4)
|
||||
self.horizontalLayout_2.setStretch(0, 5)
|
||||
self.horizontalLayout_2.setStretch(1, 2)
|
||||
self.horizontalLayout_2.setStretch(2, 2)
|
||||
self.cfg_pwr_limit_layout.addLayout(self.horizontalLayout_2)
|
||||
self.pwr_limit_layout = QtWidgets.QHBoxLayout()
|
||||
self.pwr_limit_layout.setObjectName("pwr_limit_layout")
|
||||
self.cfg_pwr_limit_lbl = QtWidgets.QLabel(parent=self.verticalLayoutWidget_2)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
self.cfg_pwr_limit_lbl.setFont(font)
|
||||
self.cfg_pwr_limit_lbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
|
||||
self.cfg_pwr_limit_lbl.setObjectName("cfg_pwr_limit_lbl")
|
||||
self.pwr_limit_layout.addWidget(self.cfg_pwr_limit_lbl)
|
||||
self.cfg_pwr_limit_spinbox = QtWidgets.QDoubleSpinBox(parent=self.verticalLayoutWidget_2)
|
||||
self.cfg_pwr_limit_spinbox.setDecimals(4)
|
||||
self.cfg_pwr_limit_spinbox.setSingleStep(0.001)
|
||||
self.cfg_pwr_limit_spinbox.setObjectName("cfg_pwr_limit_spinbox")
|
||||
self.pwr_limit_layout.addWidget(self.cfg_pwr_limit_spinbox)
|
||||
self.cfg_pwr_limit_reading = QtWidgets.QLabel(parent=self.verticalLayoutWidget_2)
|
||||
self.cfg_pwr_limit_reading.setObjectName("cfg_pwr_limit_reading")
|
||||
self.pwr_limit_layout.addWidget(self.cfg_pwr_limit_reading)
|
||||
self.pwr_limit_layout.setStretch(0, 5)
|
||||
self.pwr_limit_layout.setStretch(1, 2)
|
||||
self.pwr_limit_layout.setStretch(2, 2)
|
||||
self.cfg_pwr_limit_layout.addLayout(self.pwr_limit_layout)
|
||||
self.settable_pwr_range_layout = QtWidgets.QHBoxLayout()
|
||||
self.settable_pwr_range_layout.setSpacing(12)
|
||||
self.settable_pwr_range_layout.setObjectName("settable_pwr_range_layout")
|
||||
self.settable_pwr_range_lbl = QtWidgets.QLabel(parent=self.verticalLayoutWidget_2)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
self.settable_pwr_range_lbl.setFont(font)
|
||||
self.settable_pwr_range_lbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
|
||||
self.settable_pwr_range_lbl.setObjectName("settable_pwr_range_lbl")
|
||||
self.settable_pwr_range_layout.addWidget(self.settable_pwr_range_lbl)
|
||||
self.settable_pwr_range_display_lbl = QtWidgets.QLabel(parent=self.verticalLayoutWidget_2)
|
||||
self.settable_pwr_range_display_lbl.setMinimumSize(QtCore.QSize(0, 28))
|
||||
self.settable_pwr_range_display_lbl.setObjectName("settable_pwr_range_display_lbl")
|
||||
self.settable_pwr_range_layout.addWidget(self.settable_pwr_range_display_lbl)
|
||||
self.settable_pwr_range_layout.setStretch(0, 5)
|
||||
self.settable_pwr_range_layout.setStretch(1, 4)
|
||||
self.cfg_pwr_limit_layout.addLayout(self.settable_pwr_range_layout)
|
||||
self.apply_pwr_limit_layout = QtWidgets.QHBoxLayout()
|
||||
self.apply_pwr_limit_layout.setObjectName("apply_pwr_limit_layout")
|
||||
spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
|
||||
self.apply_pwr_limit_layout.addItem(spacerItem3)
|
||||
self.apply_pwr_limit_max_btn = QtWidgets.QPushButton(parent=self.verticalLayoutWidget_2)
|
||||
self.apply_pwr_limit_max_btn.setObjectName("apply_pwr_limit_max_btn")
|
||||
self.apply_pwr_limit_layout.addWidget(self.apply_pwr_limit_max_btn)
|
||||
self.apply_pwr_limit_btn = QtWidgets.QPushButton(parent=self.verticalLayoutWidget_2)
|
||||
self.apply_pwr_limit_btn.setObjectName("apply_pwr_limit_btn")
|
||||
self.apply_pwr_limit_layout.addWidget(self.apply_pwr_limit_btn)
|
||||
self.apply_pwr_limit_layout.setStretch(0, 2)
|
||||
self.apply_pwr_limit_layout.setStretch(1, 3)
|
||||
self.apply_pwr_limit_layout.setStretch(2, 4)
|
||||
self.cfg_pwr_limit_layout.addLayout(self.apply_pwr_limit_layout)
|
||||
self.cfg_pd_mon_form_layout.addLayout(self.cfg_pwr_limit_layout)
|
||||
self.pwr_on_layout = QtWidgets.QHBoxLayout()
|
||||
self.pwr_on_layout.setSpacing(12)
|
||||
self.pwr_on_layout.setObjectName("pwr_on_layout")
|
||||
self.pwr_on_lbl = QtWidgets.QLabel(parent=self.verticalLayoutWidget_2)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(14)
|
||||
self.pwr_on_lbl.setFont(font)
|
||||
self.pwr_on_lbl.setObjectName("pwr_on_lbl")
|
||||
self.pwr_on_layout.addWidget(self.pwr_on_lbl)
|
||||
self.pwr_on_btn = QtWidgets.QPushButton(parent=self.verticalLayoutWidget_2)
|
||||
self.pwr_on_btn.setObjectName("pwr_on_btn")
|
||||
self.pwr_on_layout.addWidget(self.pwr_on_btn)
|
||||
self.pwr_on_layout.setStretch(0, 5)
|
||||
self.pwr_on_layout.setStretch(1, 4)
|
||||
self.cfg_pd_mon_form_layout.addLayout(self.pwr_on_layout)
|
||||
self.close_btn_layout = QtWidgets.QHBoxLayout()
|
||||
self.close_btn_layout.setSpacing(12)
|
||||
self.close_btn_layout.setObjectName("close_btn_layout")
|
||||
spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
|
||||
self.close_btn_layout.addItem(spacerItem4)
|
||||
self.close_btn = QtWidgets.QPushButton(parent=self.verticalLayoutWidget_2)
|
||||
self.close_btn.setObjectName("close_btn")
|
||||
self.close_btn_layout.addWidget(self.close_btn)
|
||||
self.close_btn_layout.setStretch(0, 5)
|
||||
self.close_btn_layout.setStretch(1, 4)
|
||||
self.cfg_pd_mon_form_layout.addLayout(self.close_btn_layout)
|
||||
self.cfg_pd_mon_form_layout.setStretch(1, 2)
|
||||
self.cfg_pd_mon_form_layout.setStretch(2, 2)
|
||||
self.cfg_pd_mon_form_layout.setStretch(3, 2)
|
||||
self.cfg_pd_mon_form_layout.setStretch(4, 2)
|
||||
self.cfg_pd_mon_form_layout.setStretch(5, 2)
|
||||
self.cfg_pd_mon_form_layout.setStretch(6, 1)
|
||||
|
||||
self.retranslateUi(Cfg_Pd_Mon_Form)
|
||||
self.close_btn.clicked.connect(Cfg_Pd_Mon_Form.accept) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(Cfg_Pd_Mon_Form)
|
||||
|
||||
def retranslateUi(self, Cfg_Pd_Mon_Form):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
Cfg_Pd_Mon_Form.setWindowTitle(_translate("Cfg_Pd_Mon_Form", "config_pd_mon_form"))
|
||||
self.title_lbl.setText(_translate("Cfg_Pd_Mon_Form", "Configure Photodiode Monitor"))
|
||||
self.pwr_off_lbl.setText(_translate("Cfg_Pd_Mon_Form", " Step 1: Turn off Laser Power"))
|
||||
self.pwr_off_btn.setText(_translate("Cfg_Pd_Mon_Form", "Power Off"))
|
||||
self.rst_ld_pwr_limit_lbl.setText(_translate("Cfg_Pd_Mon_Form", " Step 2: Reset Ld Pwr Limit to 0"))
|
||||
self.rst_ld_pwr_limit_btn.setText(_translate("Cfg_Pd_Mon_Form", "Reset Ld Pwr Limit"))
|
||||
self.cfg_pd_params_lbl.setText(_translate("Cfg_Pd_Mon_Form", " Step 3: Configure Photodiode Parameters"))
|
||||
self.label_2.setText(_translate("Cfg_Pd_Mon_Form", " Value"))
|
||||
self.label.setText(_translate("Cfg_Pd_Mon_Form", "Reading"))
|
||||
self.cfg_responsitivity_lbl.setText(_translate("Cfg_Pd_Mon_Form", "Responsitivity: "))
|
||||
self.cfg_responsitivity_reading.setText(_translate("Cfg_Pd_Mon_Form", "0.0000"))
|
||||
self.cfg_dark_current_lbl.setText(_translate("Cfg_Pd_Mon_Form", "Dark Current: "))
|
||||
self.cfg_dark_current_reading.setText(_translate("Cfg_Pd_Mon_Form", "0.0000"))
|
||||
self.apply_pd_params_btn.setText(_translate("Cfg_Pd_Mon_Form", "Apply"))
|
||||
self.cfg_pd_pwr_limit_lbl.setText(_translate("Cfg_Pd_Mon_Form", " Step 4: Configure Laser Diode Power Limit"))
|
||||
self.label_3.setText(_translate("Cfg_Pd_Mon_Form", " Value"))
|
||||
self.label_4.setText(_translate("Cfg_Pd_Mon_Form", "Reading"))
|
||||
self.cfg_pwr_limit_lbl.setText(_translate("Cfg_Pd_Mon_Form", "Power Limit:"))
|
||||
self.cfg_pwr_limit_reading.setText(_translate("Cfg_Pd_Mon_Form", "0.0000"))
|
||||
self.settable_pwr_range_lbl.setText(_translate("Cfg_Pd_Mon_Form", "Settable Power Range:"))
|
||||
self.settable_pwr_range_display_lbl.setText(_translate("Cfg_Pd_Mon_Form", "( Power Range )"))
|
||||
self.apply_pwr_limit_max_btn.setText(_translate("Cfg_Pd_Mon_Form", "Apply Max"))
|
||||
self.apply_pwr_limit_btn.setText(_translate("Cfg_Pd_Mon_Form", "Apply"))
|
||||
self.pwr_on_lbl.setText(_translate("Cfg_Pd_Mon_Form", " Step 5: Turn On Laser Power"))
|
||||
self.pwr_on_btn.setText(_translate("Cfg_Pd_Mon_Form", "Clear Alarm and Power On"))
|
||||
self.close_btn.setText(_translate("Cfg_Pd_Mon_Form", "Close"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
Cfg_Pd_Mon_Form = QtWidgets.QDialog()
|
||||
ui = Ui_Cfg_Pd_Mon_Form()
|
||||
ui.setupUi(Cfg_Pd_Mon_Form)
|
||||
Cfg_Pd_Mon_Form.show()
|
||||
sys.exit(app.exec())
|
161
pykirdy/ui/ui_conn_settings_form.py
Normal file
161
pykirdy/ui/ui_conn_settings_form.py
Normal file
@ -0,0 +1,161 @@
|
||||
# Form implementation generated from reading ui file 'conn_settings_form.ui'
|
||||
#
|
||||
# Created by: PyQt6 UI code generator 6.6.0
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_Conn_Settings_Form(object):
|
||||
def setupUi(self, Conn_Settings_Form):
|
||||
Conn_Settings_Form.setObjectName("Conn_Settings_Form")
|
||||
Conn_Settings_Form.resize(415, 145)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(Conn_Settings_Form.sizePolicy().hasHeightForWidth())
|
||||
Conn_Settings_Form.setSizePolicy(sizePolicy)
|
||||
Conn_Settings_Form.setMinimumSize(QtCore.QSize(415, 145))
|
||||
Conn_Settings_Form.setMaximumSize(QtCore.QSize(415, 145))
|
||||
self.horizontalLayoutWidget = QtWidgets.QWidget(parent=Conn_Settings_Form)
|
||||
self.horizontalLayoutWidget.setGeometry(QtCore.QRect(20, 10, 371, 41))
|
||||
self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
|
||||
self.ip_addr_layout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
|
||||
self.ip_addr_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.ip_addr_layout.setObjectName("ip_addr_layout")
|
||||
self.ip_addr_label = QtWidgets.QLabel(parent=self.horizontalLayoutWidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.ip_addr_label.sizePolicy().hasHeightForWidth())
|
||||
self.ip_addr_label.setSizePolicy(sizePolicy)
|
||||
self.ip_addr_label.setMaximumSize(QtCore.QSize(120, 16777215))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
self.ip_addr_label.setFont(font)
|
||||
self.ip_addr_label.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
|
||||
self.ip_addr_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
|
||||
self.ip_addr_label.setObjectName("ip_addr_label")
|
||||
self.ip_addr_layout.addWidget(self.ip_addr_label)
|
||||
self.addr_in_0 = QtWidgets.QLineEdit(parent=self.horizontalLayoutWidget)
|
||||
self.addr_in_0.setMaximumSize(QtCore.QSize(50, 16777215))
|
||||
self.addr_in_0.setMaxLength(3)
|
||||
self.addr_in_0.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.addr_in_0.setObjectName("addr_in_0")
|
||||
self.ip_addr_layout.addWidget(self.addr_in_0)
|
||||
self.dot_0_label = QtWidgets.QLabel(parent=self.horizontalLayoutWidget)
|
||||
self.dot_0_label.setMaximumSize(QtCore.QSize(10, 16777215))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
font.setBold(False)
|
||||
font.setKerning(True)
|
||||
self.dot_0_label.setFont(font)
|
||||
self.dot_0_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.dot_0_label.setObjectName("dot_0_label")
|
||||
self.ip_addr_layout.addWidget(self.dot_0_label)
|
||||
self.addr_in_1 = QtWidgets.QLineEdit(parent=self.horizontalLayoutWidget)
|
||||
self.addr_in_1.setMaximumSize(QtCore.QSize(50, 16777215))
|
||||
self.addr_in_1.setMaxLength(3)
|
||||
self.addr_in_1.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.addr_in_1.setObjectName("addr_in_1")
|
||||
self.ip_addr_layout.addWidget(self.addr_in_1)
|
||||
self.dot_1_label = QtWidgets.QLabel(parent=self.horizontalLayoutWidget)
|
||||
self.dot_1_label.setMaximumSize(QtCore.QSize(10, 16777215))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
font.setBold(False)
|
||||
font.setKerning(True)
|
||||
self.dot_1_label.setFont(font)
|
||||
self.dot_1_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.dot_1_label.setObjectName("dot_1_label")
|
||||
self.ip_addr_layout.addWidget(self.dot_1_label)
|
||||
self.addr_in_2 = QtWidgets.QLineEdit(parent=self.horizontalLayoutWidget)
|
||||
self.addr_in_2.setMaximumSize(QtCore.QSize(50, 16777215))
|
||||
self.addr_in_2.setMaxLength(3)
|
||||
self.addr_in_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.addr_in_2.setObjectName("addr_in_2")
|
||||
self.ip_addr_layout.addWidget(self.addr_in_2)
|
||||
self.dot_2_label = QtWidgets.QLabel(parent=self.horizontalLayoutWidget)
|
||||
self.dot_2_label.setMaximumSize(QtCore.QSize(10, 16777215))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
font.setBold(False)
|
||||
font.setKerning(True)
|
||||
self.dot_2_label.setFont(font)
|
||||
self.dot_2_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.dot_2_label.setObjectName("dot_2_label")
|
||||
self.ip_addr_layout.addWidget(self.dot_2_label)
|
||||
self.addr_in_3 = QtWidgets.QLineEdit(parent=self.horizontalLayoutWidget)
|
||||
self.addr_in_3.setMaximumSize(QtCore.QSize(50, 16777215))
|
||||
self.addr_in_3.setMaxLength(3)
|
||||
self.addr_in_3.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.addr_in_3.setObjectName("addr_in_3")
|
||||
self.ip_addr_layout.addWidget(self.addr_in_3)
|
||||
self.horizontalLayoutWidget_2 = QtWidgets.QWidget(parent=Conn_Settings_Form)
|
||||
self.horizontalLayoutWidget_2.setGeometry(QtCore.QRect(20, 50, 371, 41))
|
||||
self.horizontalLayoutWidget_2.setObjectName("horizontalLayoutWidget_2")
|
||||
self.port_layout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_2)
|
||||
self.port_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.port_layout.setObjectName("port_layout")
|
||||
self.port_no_label = QtWidgets.QLabel(parent=self.horizontalLayoutWidget_2)
|
||||
self.port_no_label.setMaximumSize(QtCore.QSize(97, 16777215))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
self.port_no_label.setFont(font)
|
||||
self.port_no_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
|
||||
self.port_no_label.setObjectName("port_no_label")
|
||||
self.port_layout.addWidget(self.port_no_label)
|
||||
self.port_in = QtWidgets.QLineEdit(parent=self.horizontalLayoutWidget_2)
|
||||
self.port_in.setMaximumSize(QtCore.QSize(50, 16777215))
|
||||
self.port_in.setMaxLength(5)
|
||||
self.port_in.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.port_in.setObjectName("port_in")
|
||||
self.port_layout.addWidget(self.port_in)
|
||||
spacerItem = QtWidgets.QSpacerItem(50, 20, QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Minimum)
|
||||
self.port_layout.addItem(spacerItem)
|
||||
self.port_layout.setStretch(0, 3)
|
||||
self.port_layout.setStretch(2, 6)
|
||||
self.horizontalLayoutWidget_4 = QtWidgets.QWidget(parent=Conn_Settings_Form)
|
||||
self.horizontalLayoutWidget_4.setGeometry(QtCore.QRect(20, 90, 371, 47))
|
||||
self.horizontalLayoutWidget_4.setObjectName("horizontalLayoutWidget_4")
|
||||
self.buttons_layout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_4)
|
||||
self.buttons_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.buttons_layout.setObjectName("buttons_layout")
|
||||
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
|
||||
self.buttons_layout.addItem(spacerItem1)
|
||||
self.connect_btn = QtWidgets.QPushButton(parent=self.horizontalLayoutWidget_4)
|
||||
self.connect_btn.setObjectName("connect_btn")
|
||||
self.buttons_layout.addWidget(self.connect_btn)
|
||||
self.cancel_btn = QtWidgets.QPushButton(parent=self.horizontalLayoutWidget_4)
|
||||
self.cancel_btn.setObjectName("cancel_btn")
|
||||
self.buttons_layout.addWidget(self.cancel_btn)
|
||||
|
||||
self.retranslateUi(Conn_Settings_Form)
|
||||
self.cancel_btn.clicked.connect(Conn_Settings_Form.reject) # type: ignore
|
||||
self.connect_btn.clicked.connect(Conn_Settings_Form.accept) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(Conn_Settings_Form)
|
||||
Conn_Settings_Form.setTabOrder(self.addr_in_0, self.addr_in_1)
|
||||
Conn_Settings_Form.setTabOrder(self.addr_in_1, self.addr_in_2)
|
||||
Conn_Settings_Form.setTabOrder(self.addr_in_2, self.addr_in_3)
|
||||
Conn_Settings_Form.setTabOrder(self.addr_in_3, self.port_in)
|
||||
Conn_Settings_Form.setTabOrder(self.port_in, self.connect_btn)
|
||||
Conn_Settings_Form.setTabOrder(self.connect_btn, self.cancel_btn)
|
||||
|
||||
def retranslateUi(self, Conn_Settings_Form):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
Conn_Settings_Form.setWindowTitle(_translate("Conn_Settings_Form", "Connection Settings"))
|
||||
self.ip_addr_label.setText(_translate("Conn_Settings_Form", "IP Address:"))
|
||||
self.addr_in_0.setText(_translate("Conn_Settings_Form", "192"))
|
||||
self.dot_0_label.setText(_translate("Conn_Settings_Form", "."))
|
||||
self.addr_in_1.setText(_translate("Conn_Settings_Form", "168"))
|
||||
self.dot_1_label.setText(_translate("Conn_Settings_Form", "."))
|
||||
self.addr_in_2.setText(_translate("Conn_Settings_Form", "1"))
|
||||
self.dot_2_label.setText(_translate("Conn_Settings_Form", "."))
|
||||
self.addr_in_3.setText(_translate("Conn_Settings_Form", "128"))
|
||||
self.port_no_label.setText(_translate("Conn_Settings_Form", "Port:"))
|
||||
self.port_in.setText(_translate("Conn_Settings_Form", "1337"))
|
||||
self.connect_btn.setText(_translate("Conn_Settings_Form", "Connect"))
|
||||
self.cancel_btn.setText(_translate("Conn_Settings_Form", "Cancel"))
|
264
pykirdy/ui/ui_update_network_settings_form.py
Normal file
264
pykirdy/ui/ui_update_network_settings_form.py
Normal file
@ -0,0 +1,264 @@
|
||||
# Form implementation generated from reading ui file 'update_network_settings_form.ui'
|
||||
#
|
||||
# Created by: PyQt6 UI code generator 6.6.0
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_Update_Network_Settings_Form(object):
|
||||
def setupUi(self, Update_Network_Settings_Form):
|
||||
Update_Network_Settings_Form.setObjectName("Update_Network_Settings_Form")
|
||||
Update_Network_Settings_Form.resize(415, 180)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(Update_Network_Settings_Form.sizePolicy().hasHeightForWidth())
|
||||
Update_Network_Settings_Form.setSizePolicy(sizePolicy)
|
||||
Update_Network_Settings_Form.setMinimumSize(QtCore.QSize(415, 180))
|
||||
Update_Network_Settings_Form.setMaximumSize(QtCore.QSize(415, 180))
|
||||
self.horizontalLayoutWidget = QtWidgets.QWidget(parent=Update_Network_Settings_Form)
|
||||
self.horizontalLayoutWidget.setGeometry(QtCore.QRect(20, 10, 371, 41))
|
||||
self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
|
||||
self.ip_addr_layout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
|
||||
self.ip_addr_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.ip_addr_layout.setObjectName("ip_addr_layout")
|
||||
self.ip_addr_label = QtWidgets.QLabel(parent=self.horizontalLayoutWidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.ip_addr_label.sizePolicy().hasHeightForWidth())
|
||||
self.ip_addr_label.setSizePolicy(sizePolicy)
|
||||
self.ip_addr_label.setMaximumSize(QtCore.QSize(120, 16777215))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
self.ip_addr_label.setFont(font)
|
||||
self.ip_addr_label.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
|
||||
self.ip_addr_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
|
||||
self.ip_addr_label.setObjectName("ip_addr_label")
|
||||
self.ip_addr_layout.addWidget(self.ip_addr_label)
|
||||
self.addr_in_0 = QtWidgets.QLineEdit(parent=self.horizontalLayoutWidget)
|
||||
self.addr_in_0.setMaximumSize(QtCore.QSize(50, 16777215))
|
||||
self.addr_in_0.setMaxLength(3)
|
||||
self.addr_in_0.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.addr_in_0.setObjectName("addr_in_0")
|
||||
self.ip_addr_layout.addWidget(self.addr_in_0)
|
||||
self.dot_0_lbl = QtWidgets.QLabel(parent=self.horizontalLayoutWidget)
|
||||
self.dot_0_lbl.setMaximumSize(QtCore.QSize(10, 16777215))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
font.setBold(False)
|
||||
font.setKerning(True)
|
||||
self.dot_0_lbl.setFont(font)
|
||||
self.dot_0_lbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.dot_0_lbl.setObjectName("dot_0_lbl")
|
||||
self.ip_addr_layout.addWidget(self.dot_0_lbl)
|
||||
self.addr_in_1 = QtWidgets.QLineEdit(parent=self.horizontalLayoutWidget)
|
||||
self.addr_in_1.setMaximumSize(QtCore.QSize(50, 16777215))
|
||||
self.addr_in_1.setMaxLength(3)
|
||||
self.addr_in_1.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.addr_in_1.setObjectName("addr_in_1")
|
||||
self.ip_addr_layout.addWidget(self.addr_in_1)
|
||||
self.dot_1_lbl = QtWidgets.QLabel(parent=self.horizontalLayoutWidget)
|
||||
self.dot_1_lbl.setMaximumSize(QtCore.QSize(10, 16777215))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
font.setBold(False)
|
||||
font.setKerning(True)
|
||||
self.dot_1_lbl.setFont(font)
|
||||
self.dot_1_lbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.dot_1_lbl.setObjectName("dot_1_lbl")
|
||||
self.ip_addr_layout.addWidget(self.dot_1_lbl)
|
||||
self.addr_in_2 = QtWidgets.QLineEdit(parent=self.horizontalLayoutWidget)
|
||||
self.addr_in_2.setMaximumSize(QtCore.QSize(50, 16777215))
|
||||
self.addr_in_2.setMaxLength(3)
|
||||
self.addr_in_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.addr_in_2.setObjectName("addr_in_2")
|
||||
self.ip_addr_layout.addWidget(self.addr_in_2)
|
||||
self.dot_2_lbl = QtWidgets.QLabel(parent=self.horizontalLayoutWidget)
|
||||
self.dot_2_lbl.setMaximumSize(QtCore.QSize(10, 16777215))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
font.setBold(False)
|
||||
font.setKerning(True)
|
||||
self.dot_2_lbl.setFont(font)
|
||||
self.dot_2_lbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.dot_2_lbl.setObjectName("dot_2_lbl")
|
||||
self.ip_addr_layout.addWidget(self.dot_2_lbl)
|
||||
self.addr_in_3 = QtWidgets.QLineEdit(parent=self.horizontalLayoutWidget)
|
||||
self.addr_in_3.setMaximumSize(QtCore.QSize(50, 16777215))
|
||||
self.addr_in_3.setMaxLength(3)
|
||||
self.addr_in_3.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.addr_in_3.setObjectName("addr_in_3")
|
||||
self.ip_addr_layout.addWidget(self.addr_in_3)
|
||||
self.horizontalLayoutWidget_4 = QtWidgets.QWidget(parent=Update_Network_Settings_Form)
|
||||
self.horizontalLayoutWidget_4.setGeometry(QtCore.QRect(20, 130, 371, 47))
|
||||
self.horizontalLayoutWidget_4.setObjectName("horizontalLayoutWidget_4")
|
||||
self.buttons_layout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_4)
|
||||
self.buttons_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.buttons_layout.setObjectName("buttons_layout")
|
||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
|
||||
self.buttons_layout.addItem(spacerItem)
|
||||
self.update_btn = QtWidgets.QPushButton(parent=self.horizontalLayoutWidget_4)
|
||||
self.update_btn.setObjectName("update_btn")
|
||||
self.buttons_layout.addWidget(self.update_btn)
|
||||
self.cancel_btn = QtWidgets.QPushButton(parent=self.horizontalLayoutWidget_4)
|
||||
self.cancel_btn.setObjectName("cancel_btn")
|
||||
self.buttons_layout.addWidget(self.cancel_btn)
|
||||
self.horizontalLayoutWidget_3 = QtWidgets.QWidget(parent=Update_Network_Settings_Form)
|
||||
self.horizontalLayoutWidget_3.setGeometry(QtCore.QRect(20, 90, 371, 41))
|
||||
self.horizontalLayoutWidget_3.setObjectName("horizontalLayoutWidget_3")
|
||||
self.port_layout_2 = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_3)
|
||||
self.port_layout_2.setContentsMargins(0, 0, 0, 0)
|
||||
self.port_layout_2.setSpacing(6)
|
||||
self.port_layout_2.setObjectName("port_layout_2")
|
||||
self.prefix_len_lbl = QtWidgets.QLabel(parent=self.horizontalLayoutWidget_3)
|
||||
self.prefix_len_lbl.setMaximumSize(QtCore.QSize(98, 16777215))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
self.prefix_len_lbl.setFont(font)
|
||||
self.prefix_len_lbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
|
||||
self.prefix_len_lbl.setObjectName("prefix_len_lbl")
|
||||
self.port_layout_2.addWidget(self.prefix_len_lbl)
|
||||
self.prefix_len_in = QtWidgets.QLineEdit(parent=self.horizontalLayoutWidget_3)
|
||||
self.prefix_len_in.setMaximumSize(QtCore.QSize(50, 16777215))
|
||||
self.prefix_len_in.setMaxLength(2)
|
||||
self.prefix_len_in.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.prefix_len_in.setObjectName("prefix_len_in")
|
||||
self.port_layout_2.addWidget(self.prefix_len_in)
|
||||
self.port_no_lbl = QtWidgets.QLabel(parent=self.horizontalLayoutWidget_3)
|
||||
self.port_no_lbl.setMinimumSize(QtCore.QSize(82, 0))
|
||||
self.port_no_lbl.setMaximumSize(QtCore.QSize(50, 16777215))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
self.port_no_lbl.setFont(font)
|
||||
self.port_no_lbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
|
||||
self.port_no_lbl.setObjectName("port_no_lbl")
|
||||
self.port_layout_2.addWidget(self.port_no_lbl)
|
||||
self.port_in = QtWidgets.QLineEdit(parent=self.horizontalLayoutWidget_3)
|
||||
self.port_in.setMaximumSize(QtCore.QSize(50, 16777215))
|
||||
self.port_in.setMaxLength(5)
|
||||
self.port_in.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.port_in.setObjectName("port_in")
|
||||
self.port_layout_2.addWidget(self.port_in)
|
||||
spacerItem1 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
|
||||
self.port_layout_2.addItem(spacerItem1)
|
||||
self.horizontalLayoutWidget_5 = QtWidgets.QWidget(parent=Update_Network_Settings_Form)
|
||||
self.horizontalLayoutWidget_5.setGeometry(QtCore.QRect(20, 50, 371, 41))
|
||||
self.horizontalLayoutWidget_5.setObjectName("horizontalLayoutWidget_5")
|
||||
self.gateway_layout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_5)
|
||||
self.gateway_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.gateway_layout.setObjectName("gateway_layout")
|
||||
self.gateway_lbl = QtWidgets.QLabel(parent=self.horizontalLayoutWidget_5)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.gateway_lbl.sizePolicy().hasHeightForWidth())
|
||||
self.gateway_lbl.setSizePolicy(sizePolicy)
|
||||
self.gateway_lbl.setMaximumSize(QtCore.QSize(120, 16777215))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
self.gateway_lbl.setFont(font)
|
||||
self.gateway_lbl.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
|
||||
self.gateway_lbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
|
||||
self.gateway_lbl.setObjectName("gateway_lbl")
|
||||
self.gateway_layout.addWidget(self.gateway_lbl)
|
||||
self.gateway_in_0 = QtWidgets.QLineEdit(parent=self.horizontalLayoutWidget_5)
|
||||
self.gateway_in_0.setMaximumSize(QtCore.QSize(50, 16777215))
|
||||
self.gateway_in_0.setMaxLength(3)
|
||||
self.gateway_in_0.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.gateway_in_0.setObjectName("gateway_in_0")
|
||||
self.gateway_layout.addWidget(self.gateway_in_0)
|
||||
self.dot_0_lbl_2 = QtWidgets.QLabel(parent=self.horizontalLayoutWidget_5)
|
||||
self.dot_0_lbl_2.setMaximumSize(QtCore.QSize(10, 16777215))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
font.setBold(False)
|
||||
font.setKerning(True)
|
||||
self.dot_0_lbl_2.setFont(font)
|
||||
self.dot_0_lbl_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.dot_0_lbl_2.setObjectName("dot_0_lbl_2")
|
||||
self.gateway_layout.addWidget(self.dot_0_lbl_2)
|
||||
self.gateway_in_1 = QtWidgets.QLineEdit(parent=self.horizontalLayoutWidget_5)
|
||||
self.gateway_in_1.setMaximumSize(QtCore.QSize(50, 16777215))
|
||||
self.gateway_in_1.setMaxLength(3)
|
||||
self.gateway_in_1.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.gateway_in_1.setObjectName("gateway_in_1")
|
||||
self.gateway_layout.addWidget(self.gateway_in_1)
|
||||
self.dot_1_lbl_2 = QtWidgets.QLabel(parent=self.horizontalLayoutWidget_5)
|
||||
self.dot_1_lbl_2.setMaximumSize(QtCore.QSize(10, 16777215))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
font.setBold(False)
|
||||
font.setKerning(True)
|
||||
self.dot_1_lbl_2.setFont(font)
|
||||
self.dot_1_lbl_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.dot_1_lbl_2.setObjectName("dot_1_lbl_2")
|
||||
self.gateway_layout.addWidget(self.dot_1_lbl_2)
|
||||
self.gateway_in_2 = QtWidgets.QLineEdit(parent=self.horizontalLayoutWidget_5)
|
||||
self.gateway_in_2.setMaximumSize(QtCore.QSize(50, 16777215))
|
||||
self.gateway_in_2.setMaxLength(3)
|
||||
self.gateway_in_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.gateway_in_2.setObjectName("gateway_in_2")
|
||||
self.gateway_layout.addWidget(self.gateway_in_2)
|
||||
self.dot_2_lbl_2 = QtWidgets.QLabel(parent=self.horizontalLayoutWidget_5)
|
||||
self.dot_2_lbl_2.setMaximumSize(QtCore.QSize(10, 16777215))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
font.setBold(False)
|
||||
font.setKerning(True)
|
||||
self.dot_2_lbl_2.setFont(font)
|
||||
self.dot_2_lbl_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.dot_2_lbl_2.setObjectName("dot_2_lbl_2")
|
||||
self.gateway_layout.addWidget(self.dot_2_lbl_2)
|
||||
self.gateway_in_3 = QtWidgets.QLineEdit(parent=self.horizontalLayoutWidget_5)
|
||||
self.gateway_in_3.setMaximumSize(QtCore.QSize(50, 16777215))
|
||||
self.gateway_in_3.setMaxLength(3)
|
||||
self.gateway_in_3.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
self.gateway_in_3.setObjectName("gateway_in_3")
|
||||
self.gateway_layout.addWidget(self.gateway_in_3)
|
||||
|
||||
self.retranslateUi(Update_Network_Settings_Form)
|
||||
self.cancel_btn.clicked.connect(Update_Network_Settings_Form.reject) # type: ignore
|
||||
self.update_btn.clicked.connect(Update_Network_Settings_Form.accept) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(Update_Network_Settings_Form)
|
||||
Update_Network_Settings_Form.setTabOrder(self.addr_in_0, self.addr_in_1)
|
||||
Update_Network_Settings_Form.setTabOrder(self.addr_in_1, self.addr_in_2)
|
||||
Update_Network_Settings_Form.setTabOrder(self.addr_in_2, self.addr_in_3)
|
||||
Update_Network_Settings_Form.setTabOrder(self.addr_in_3, self.gateway_in_0)
|
||||
Update_Network_Settings_Form.setTabOrder(self.gateway_in_0, self.gateway_in_1)
|
||||
Update_Network_Settings_Form.setTabOrder(self.gateway_in_1, self.gateway_in_2)
|
||||
Update_Network_Settings_Form.setTabOrder(self.gateway_in_2, self.gateway_in_3)
|
||||
Update_Network_Settings_Form.setTabOrder(self.gateway_in_3, self.prefix_len_in)
|
||||
Update_Network_Settings_Form.setTabOrder(self.prefix_len_in, self.port_in)
|
||||
Update_Network_Settings_Form.setTabOrder(self.port_in, self.update_btn)
|
||||
Update_Network_Settings_Form.setTabOrder(self.update_btn, self.cancel_btn)
|
||||
|
||||
def retranslateUi(self, Update_Network_Settings_Form):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
Update_Network_Settings_Form.setWindowTitle(_translate("Update_Network_Settings_Form", "Update Network Settings"))
|
||||
self.ip_addr_label.setText(_translate("Update_Network_Settings_Form", "IP Address:"))
|
||||
self.addr_in_0.setText(_translate("Update_Network_Settings_Form", "192"))
|
||||
self.dot_0_lbl.setText(_translate("Update_Network_Settings_Form", "."))
|
||||
self.addr_in_1.setText(_translate("Update_Network_Settings_Form", "168"))
|
||||
self.dot_1_lbl.setText(_translate("Update_Network_Settings_Form", "."))
|
||||
self.addr_in_2.setText(_translate("Update_Network_Settings_Form", "1"))
|
||||
self.dot_2_lbl.setText(_translate("Update_Network_Settings_Form", "."))
|
||||
self.addr_in_3.setText(_translate("Update_Network_Settings_Form", "128"))
|
||||
self.update_btn.setText(_translate("Update_Network_Settings_Form", "Update"))
|
||||
self.cancel_btn.setText(_translate("Update_Network_Settings_Form", "Cancel"))
|
||||
self.prefix_len_lbl.setText(_translate("Update_Network_Settings_Form", "Prefix Length:"))
|
||||
self.prefix_len_in.setText(_translate("Update_Network_Settings_Form", "24"))
|
||||
self.port_no_lbl.setText(_translate("Update_Network_Settings_Form", "Port:"))
|
||||
self.port_in.setText(_translate("Update_Network_Settings_Form", "1337"))
|
||||
self.gateway_lbl.setText(_translate("Update_Network_Settings_Form", "Gateway:"))
|
||||
self.gateway_in_0.setText(_translate("Update_Network_Settings_Form", "192"))
|
||||
self.dot_0_lbl_2.setText(_translate("Update_Network_Settings_Form", "."))
|
||||
self.gateway_in_1.setText(_translate("Update_Network_Settings_Form", "168"))
|
||||
self.dot_1_lbl_2.setText(_translate("Update_Network_Settings_Form", "."))
|
||||
self.gateway_in_2.setText(_translate("Update_Network_Settings_Form", "1"))
|
||||
self.dot_2_lbl_2.setText(_translate("Update_Network_Settings_Form", "."))
|
||||
self.gateway_in_3.setText(_translate("Update_Network_Settings_Form", "1"))
|
614
pykirdy/ui/update_network_settings_form.ui
Normal file
614
pykirdy/ui/update_network_settings_form.ui
Normal file
@ -0,0 +1,614 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Update_Network_Settings_Form</class>
|
||||
<widget class="QDialog" name="Update_Network_Settings_Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>415</width>
|
||||
<height>180</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>415</width>
|
||||
<height>180</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>415</width>
|
||||
<height>180</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Update Network Settings</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="horizontalLayoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>10</y>
|
||||
<width>371</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="ip_addr_layout">
|
||||
<item>
|
||||
<widget class="QLabel" name="ip_addr_label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>IP Address:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="addr_in_0">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>192</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="dot_0_lbl">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>10</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
<bold>false</bold>
|
||||
<kerning>true</kerning>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="addr_in_1">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>168</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="dot_1_lbl">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>10</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
<bold>false</bold>
|
||||
<kerning>true</kerning>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="addr_in_2">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="dot_2_lbl">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>10</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
<bold>false</bold>
|
||||
<kerning>true</kerning>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="addr_in_3">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>128</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="horizontalLayoutWidget_4">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>130</y>
|
||||
<width>371</width>
|
||||
<height>47</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="buttons_layout">
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="update_btn">
|
||||
<property name="text">
|
||||
<string>Update</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cancel_btn">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="horizontalLayoutWidget_3">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>90</y>
|
||||
<width>371</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="port_layout_2" stretch="0,0,0,0,0">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="prefix_len_lbl">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>98</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Prefix Length:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="prefix_len_in">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>24</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="port_no_lbl">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>82</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Port:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="port_in">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>1337</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="horizontalLayoutWidget_5">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>50</y>
|
||||
<width>371</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="gateway_layout">
|
||||
<item>
|
||||
<widget class="QLabel" name="gateway_lbl">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Gateway:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="gateway_in_0">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>192</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="dot_0_lbl_2">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>10</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
<bold>false</bold>
|
||||
<kerning>true</kerning>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="gateway_in_1">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>168</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="dot_1_lbl_2">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>10</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
<bold>false</bold>
|
||||
<kerning>true</kerning>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="gateway_in_2">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="dot_2_lbl_2">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>10</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
<bold>false</bold>
|
||||
<kerning>true</kerning>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="gateway_in_3">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>addr_in_0</tabstop>
|
||||
<tabstop>addr_in_1</tabstop>
|
||||
<tabstop>addr_in_2</tabstop>
|
||||
<tabstop>addr_in_3</tabstop>
|
||||
<tabstop>gateway_in_0</tabstop>
|
||||
<tabstop>gateway_in_1</tabstop>
|
||||
<tabstop>gateway_in_2</tabstop>
|
||||
<tabstop>gateway_in_3</tabstop>
|
||||
<tabstop>prefix_len_in</tabstop>
|
||||
<tabstop>port_in</tabstop>
|
||||
<tabstop>update_btn</tabstop>
|
||||
<tabstop>cancel_btn</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>cancel_btn</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>Update_Network_Settings_Form</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>340</x>
|
||||
<y>140</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>364</x>
|
||||
<y>96</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>update_btn</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>Update_Network_Settings_Form</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>258</x>
|
||||
<y>147</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>237</x>
|
||||
<y>99</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
194
pykirdy/waitingspinnerwidget.py
Normal file
194
pykirdy/waitingspinnerwidget.py
Normal file
@ -0,0 +1,194 @@
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2012-2014 Alexander Turkin
|
||||
Copyright (c) 2014 William Hallatt
|
||||
Copyright (c) 2015 Jacob Dawid
|
||||
Copyright (c) 2016 Luca Weiss
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
|
||||
|
||||
class QtWaitingSpinner(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
# WAS IN initialize()
|
||||
self._color = QColor(Qt.GlobalColor.black)
|
||||
self._roundness = 100.0
|
||||
self._minimumTrailOpacity = 3.14159265358979323846
|
||||
self._trailFadePercentage = 80.0
|
||||
self._revolutionsPerSecond = 1.57079632679489661923
|
||||
self._numberOfLines = 20
|
||||
self._lineLength = 5
|
||||
self._lineWidth = 2
|
||||
self._innerRadius = 5
|
||||
self._currentCounter = 0
|
||||
|
||||
self._timer = QTimer(self)
|
||||
self._timer.timeout.connect(self.rotate)
|
||||
self.updateSize()
|
||||
self.updateTimer()
|
||||
# END initialize()
|
||||
|
||||
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||
|
||||
def paintEvent(self, QPaintEvent):
|
||||
painter = QPainter(self)
|
||||
painter.fillRect(self.rect(), Qt.GlobalColor.transparent)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
|
||||
|
||||
if self._currentCounter >= self._numberOfLines:
|
||||
self._currentCounter = 0
|
||||
|
||||
painter.setPen(Qt.PenStyle.NoPen)
|
||||
for i in range(0, self._numberOfLines):
|
||||
painter.save()
|
||||
painter.translate(self._innerRadius + self._lineLength, self._innerRadius + self._lineLength)
|
||||
rotateAngle = float(360 * i) / float(self._numberOfLines)
|
||||
painter.rotate(rotateAngle)
|
||||
painter.translate(self._innerRadius, 0)
|
||||
distance = self.lineCountDistanceFromPrimary(i, self._currentCounter, self._numberOfLines)
|
||||
color = self.currentLineColor(distance, self._numberOfLines, self._trailFadePercentage,
|
||||
self._minimumTrailOpacity, self._color)
|
||||
painter.setBrush(color)
|
||||
painter.drawRoundedRect(QRect(0, int(-self._lineWidth / 2), self._lineLength, self._lineWidth), self._roundness,
|
||||
self._roundness, Qt.SizeMode.RelativeSize)
|
||||
painter.restore()
|
||||
|
||||
def start(self):
|
||||
if not self._timer.isActive():
|
||||
self._timer.start()
|
||||
self._currentCounter = 0
|
||||
|
||||
def stop(self):
|
||||
if self._timer.isActive():
|
||||
self._timer.stop()
|
||||
self._currentCounter = 0
|
||||
|
||||
def setNumberOfLines(self, lines):
|
||||
self._numberOfLines = lines
|
||||
self._currentCounter = 0
|
||||
self.updateTimer()
|
||||
|
||||
def setLineLength(self, length):
|
||||
self._lineLength = length
|
||||
self.updateSize()
|
||||
|
||||
def setLineWidth(self, width):
|
||||
self._lineWidth = width
|
||||
self.updateSize()
|
||||
|
||||
def setInnerRadius(self, radius):
|
||||
self._innerRadius = radius
|
||||
self.updateSize()
|
||||
|
||||
def color(self):
|
||||
return self._color
|
||||
|
||||
def roundness(self):
|
||||
return self._roundness
|
||||
|
||||
def minimumTrailOpacity(self):
|
||||
return self._minimumTrailOpacity
|
||||
|
||||
def trailFadePercentage(self):
|
||||
return self._trailFadePercentage
|
||||
|
||||
def revolutionsPersSecond(self):
|
||||
return self._revolutionsPerSecond
|
||||
|
||||
def numberOfLines(self):
|
||||
return self._numberOfLines
|
||||
|
||||
def lineLength(self):
|
||||
return self._lineLength
|
||||
|
||||
def lineWidth(self):
|
||||
return self._lineWidth
|
||||
|
||||
def innerRadius(self):
|
||||
return self._innerRadius
|
||||
|
||||
def setRoundness(self, roundness):
|
||||
self._roundness = max(0.0, min(100.0, roundness))
|
||||
|
||||
def setColor(self, color=Qt.GlobalColor.black):
|
||||
self._color = QColor(color)
|
||||
|
||||
def setRevolutionsPerSecond(self, revolutionsPerSecond):
|
||||
self._revolutionsPerSecond = revolutionsPerSecond
|
||||
self.updateTimer()
|
||||
|
||||
def setTrailFadePercentage(self, trail):
|
||||
self._trailFadePercentage = trail
|
||||
|
||||
def setMinimumTrailOpacity(self, minimumTrailOpacity):
|
||||
self._minimumTrailOpacity = minimumTrailOpacity
|
||||
|
||||
def rotate(self):
|
||||
self._currentCounter += 1
|
||||
if self._currentCounter >= self._numberOfLines:
|
||||
self._currentCounter = 0
|
||||
self.update()
|
||||
|
||||
def updateSize(self):
|
||||
self.size = (self._innerRadius + self._lineLength) * 2
|
||||
self.setFixedSize(self.size, self.size)
|
||||
|
||||
def updateTimer(self):
|
||||
self._timer.setInterval(int(1000 / (self._numberOfLines * self._revolutionsPerSecond)))
|
||||
|
||||
def lineCountDistanceFromPrimary(self, current, primary, totalNrOfLines):
|
||||
distance = primary - current
|
||||
if distance < 0:
|
||||
distance += totalNrOfLines
|
||||
return distance
|
||||
|
||||
def currentLineColor(self, countDistance, totalNrOfLines, trailFadePerc, minOpacity, colorinput):
|
||||
color = QColor(colorinput)
|
||||
if countDistance == 0:
|
||||
return color
|
||||
minAlphaF = minOpacity / 100.0
|
||||
distanceThreshold = int(math.ceil((totalNrOfLines - 1) * trailFadePerc / 100.0))
|
||||
if countDistance > distanceThreshold:
|
||||
color.setAlphaF(minAlphaF)
|
||||
else:
|
||||
alphaDiff = color.alphaF() - minAlphaF
|
||||
gradient = alphaDiff / float(distanceThreshold + 1)
|
||||
resultAlpha = color.alphaF() - gradient * countDistance
|
||||
# If alpha is out of bounds, clip it.
|
||||
resultAlpha = min(1.0, max(0.0, resultAlpha))
|
||||
color.setAlphaF(resultAlpha)
|
||||
return color
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication([])
|
||||
waiting_spinner = QtWaitingSpinner()
|
||||
waiting_spinner.show()
|
||||
waiting_spinner.start()
|
||||
app.exec()
|
64
rustfmt.toml
Normal file
64
rustfmt.toml
Normal file
@ -0,0 +1,64 @@
|
||||
max_width = 120
|
||||
hard_tabs = false
|
||||
tab_spaces = 4
|
||||
newline_style = "Auto"
|
||||
use_small_heuristics = "Default"
|
||||
indent_style = "Block"
|
||||
wrap_comments = false
|
||||
format_code_in_doc_comments = false
|
||||
comment_width = 100
|
||||
normalize_comments = false
|
||||
normalize_doc_attributes = false
|
||||
format_strings = true
|
||||
format_macro_matchers = true
|
||||
format_macro_bodies = true
|
||||
empty_item_single_line = true
|
||||
struct_lit_single_line = true
|
||||
fn_single_line = false
|
||||
where_single_line = true
|
||||
imports_indent = "Visual"
|
||||
imports_layout = "Mixed"
|
||||
imports_granularity="Crate"
|
||||
group_imports = "StdExternalCrate"
|
||||
reorder_imports = true
|
||||
reorder_modules = true
|
||||
reorder_impl_items = false
|
||||
type_punctuation_density = "Wide"
|
||||
space_before_colon = false
|
||||
space_after_colon = true
|
||||
spaces_around_ranges = false
|
||||
binop_separator = "Front"
|
||||
remove_nested_parens = true
|
||||
combine_control_expr = true
|
||||
overflow_delimited_expr = false
|
||||
struct_field_align_threshold = 0
|
||||
enum_discrim_align_threshold = 0
|
||||
match_arm_blocks = true
|
||||
match_arm_leading_pipes = "Never"
|
||||
force_multiline_blocks = false
|
||||
fn_params_layout = "Tall"
|
||||
brace_style = "SameLineWhere"
|
||||
control_brace_style = "AlwaysSameLine"
|
||||
trailing_semicolon = true
|
||||
trailing_comma = "Vertical"
|
||||
match_block_trailing_comma = false
|
||||
blank_lines_upper_bound = 1
|
||||
blank_lines_lower_bound = 0
|
||||
edition = "2018"
|
||||
version = "Two"
|
||||
inline_attribute_width = 0
|
||||
merge_derives = true
|
||||
use_try_shorthand = false
|
||||
use_field_init_shorthand = false
|
||||
force_explicit_abi = true
|
||||
condense_wildcard_suffixes = false
|
||||
color = "Auto"
|
||||
unstable_features = false
|
||||
disable_all_formatting = false
|
||||
skip_children = false
|
||||
hide_parse_errors = false
|
||||
error_on_line_overflow = false
|
||||
error_on_unformatted = false
|
||||
ignore = []
|
||||
emit_mode = "Files"
|
||||
make_backup = false
|
@ -1,20 +1,30 @@
|
||||
use super::{gpio, sys_timer, usb};
|
||||
use crate::laser_diode::current_sources::*;
|
||||
use fugit::ExtU32;
|
||||
use log::info;
|
||||
use stm32f4xx_hal::{
|
||||
pac::{CorePeripherals, Peripherals},
|
||||
rcc::RccExt,
|
||||
time::MegaHertz,
|
||||
watchdog::IndependentWatchdog,
|
||||
};
|
||||
use log::{debug, info};
|
||||
use stm32f4xx_hal::{pac::{CorePeripherals, Peripherals},
|
||||
rcc::RccExt,
|
||||
time::MegaHertz,
|
||||
timer::TimerExt,
|
||||
watchdog::IndependentWatchdog};
|
||||
use uom::si::{electric_current::{ampere, milliampere},
|
||||
f32::ElectricCurrent};
|
||||
|
||||
use super::{gpio, sys_timer, usb};
|
||||
use crate::{device::{flash_store::{self, FlashStore},
|
||||
hw_rev::HWRev},
|
||||
laser_diode::{laser_diode::LdDrive, ld_ctrl::*},
|
||||
net::net::{IpSettings, ServerHandle},
|
||||
thermostat::{max1968::MAX1968, thermostat::Thermostat},
|
||||
DeviceSettings};
|
||||
|
||||
#[cfg(not(feature = "semihosting"))]
|
||||
const WATCHDOG_PERIOD: u32 = 1000;
|
||||
const WATCHDOG_PERIOD: u32 = 4000;
|
||||
#[cfg(feature = "semihosting")]
|
||||
const WATCHDOG_PERIOD: u32 = 30000;
|
||||
|
||||
pub fn bootup(mut core_perif: CorePeripherals, perif: Peripherals) -> IndependentWatchdog {
|
||||
pub fn bootup(
|
||||
mut core_perif: CorePeripherals,
|
||||
perif: Peripherals,
|
||||
) -> (IndependentWatchdog, FlashStore, HWRev, LdDrive, Thermostat) {
|
||||
core_perif.SCB.enable_icache();
|
||||
core_perif.SCB.enable_dcache(&mut core_perif.CPUID);
|
||||
|
||||
@ -25,42 +35,90 @@ pub fn bootup(mut core_perif: CorePeripherals, perif: Peripherals) -> Independen
|
||||
.use_hse(MegaHertz::from_raw(8).convert())
|
||||
.sysclk(MegaHertz::from_raw(168).convert())
|
||||
.hclk(MegaHertz::from_raw(168).convert())
|
||||
.pclk1(MegaHertz::from_raw(32).convert())
|
||||
.pclk2(MegaHertz::from_raw(64).convert())
|
||||
.pclk1(MegaHertz::from_raw(42).convert())
|
||||
.pclk2(MegaHertz::from_raw(84).convert())
|
||||
.freeze();
|
||||
|
||||
sys_timer::setup(core_perif.SYST, clocks);
|
||||
|
||||
let (_eth_pins, usb, current_source_phy) = gpio::setup(
|
||||
clocks,
|
||||
perif.GPIOA,
|
||||
perif.GPIOB,
|
||||
perif.GPIOC,
|
||||
perif.GPIOD,
|
||||
perif.GPIOG,
|
||||
perif.SPI2,
|
||||
perif.OTG_FS_GLOBAL,
|
||||
perif.OTG_FS_DEVICE,
|
||||
perif.OTG_FS_PWRCLK,
|
||||
);
|
||||
let (mut hw_rev, eth_pins, eth_mgmt_pins, usb, current_source_phy, ad7172_phy, max1968_phy, pd_mon_phy) =
|
||||
gpio::setup(
|
||||
clocks,
|
||||
perif.TIM4,
|
||||
perif.GPIOA,
|
||||
perif.GPIOB,
|
||||
perif.GPIOC,
|
||||
perif.GPIOD,
|
||||
perif.GPIOE,
|
||||
perif.SPI1,
|
||||
perif.SPI2,
|
||||
perif.SPI3,
|
||||
perif.OTG_FS_GLOBAL,
|
||||
perif.OTG_FS_DEVICE,
|
||||
perif.OTG_FS_PWRCLK,
|
||||
);
|
||||
|
||||
usb::State::setup(usb);
|
||||
|
||||
let mut laser = CurrentSource {
|
||||
phy: current_source_phy,
|
||||
setting: CurrentSourceSettings {
|
||||
output_current: 0.0,
|
||||
},
|
||||
};
|
||||
debug!("Setting up TEC");
|
||||
let tec_driver = MAX1968::new(max1968_phy, perif.ADC1, perif.ADC2, perif.DMA2);
|
||||
let mut thermostat = Thermostat::new(tec_driver, ad7172_phy);
|
||||
thermostat.setup();
|
||||
thermostat.calibrate_dac_value();
|
||||
thermostat.set_i(ElectricCurrent::new::<ampere>(0.0));
|
||||
|
||||
debug!("Setting up Laser Driver");
|
||||
let current_source = LdCtrl::new(current_source_phy);
|
||||
let mut laser = LdDrive::new(current_source, perif.ADC3, perif.TIM2.counter(&clocks), pd_mon_phy);
|
||||
laser.setup();
|
||||
laser.set_current(0.1).unwrap();
|
||||
laser.ld_open();
|
||||
laser.ld_set_i(ElectricCurrent::new::<ampere>(0.0));
|
||||
laser.set_pd_i_limit(ElectricCurrent::new::<milliampere>(2.5));
|
||||
laser.set_pd_mon_calibrated_vdda(thermostat.get_calibrated_vdda());
|
||||
|
||||
debug!("Setting up Internal Flash Driver");
|
||||
let flash_store = flash_store::store(perif.FLASH);
|
||||
|
||||
let mut ip_settings: IpSettings = IpSettings::default();
|
||||
let device_settings: DeviceSettings;
|
||||
match flash_store.read_value("Device") {
|
||||
Ok(Some(config)) => {
|
||||
device_settings = config;
|
||||
ip_settings = device_settings.ip_settings;
|
||||
debug!("Found Device Settings");
|
||||
}
|
||||
Ok(None) => {
|
||||
debug!("Flash does not have IP Settings");
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Cannot Store Flash: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Setting up ETH");
|
||||
let mac_addr = hw_rev.get_mac_address();
|
||||
let ethernet_parts_in = stm32_eth::PartsIn {
|
||||
dma: perif.ETHERNET_DMA,
|
||||
mac: perif.ETHERNET_MAC,
|
||||
mmc: perif.ETHERNET_MMC,
|
||||
ptp: perif.ETHERNET_PTP,
|
||||
};
|
||||
ServerHandle::new(
|
||||
eth_pins,
|
||||
eth_mgmt_pins,
|
||||
ethernet_parts_in,
|
||||
clocks,
|
||||
perif.TIM5.counter(&clocks),
|
||||
mac_addr,
|
||||
ip_settings,
|
||||
);
|
||||
|
||||
debug!("Setting Watchdog");
|
||||
let mut wd = IndependentWatchdog::new(perif.IWDG);
|
||||
wd.start(WATCHDOG_PERIOD.millis());
|
||||
wd.feed();
|
||||
|
||||
info!("Kirdy setup complete");
|
||||
|
||||
wd
|
||||
(wd, flash_store, hw_rev, laser, thermostat)
|
||||
}
|
||||
|
47
src/device/dfu.rs
Normal file
47
src/device/dfu.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use core::arch::asm;
|
||||
|
||||
use cortex_m_rt::pre_init;
|
||||
use stm32f4xx_hal::pac::{RCC, SYSCFG};
|
||||
|
||||
const DFU_TRIG_MSG: u32 = 0xDECAFBAD;
|
||||
|
||||
extern "C" {
|
||||
// This symbol comes from memory.x
|
||||
static mut _dfu_msg: u32;
|
||||
}
|
||||
|
||||
pub unsafe fn set_dfu_trigger() {
|
||||
_dfu_msg = DFU_TRIG_MSG;
|
||||
}
|
||||
|
||||
/// Called by reset handler in lib.rs immediately after reset.
|
||||
/// This function should not be called outside of reset handler as
|
||||
/// bootloader expects MCU to be in reset state when called.
|
||||
#[cfg(target_arch = "arm")]
|
||||
#[pre_init]
|
||||
unsafe fn __pre_init() {
|
||||
if _dfu_msg == DFU_TRIG_MSG {
|
||||
_dfu_msg = 0x00000000;
|
||||
|
||||
// Enable system config controller clock
|
||||
let rcc = &*RCC::ptr();
|
||||
rcc.apb2enr.modify(|_, w| w.syscfgen().set_bit());
|
||||
|
||||
// Bypass BOOT pins and remap bootloader to 0x00000000
|
||||
let syscfg = &*SYSCFG::ptr();
|
||||
syscfg.memrm.write(|w| w.mem_mode().bits(0b01));
|
||||
|
||||
// Impose instruction and memory barriers
|
||||
cortex_m::asm::isb();
|
||||
cortex_m::asm::dsb();
|
||||
|
||||
asm!(
|
||||
// Set stack pointer to bootloader location
|
||||
"LDR R0, =0x1FFF0000",
|
||||
"LDR SP,[R0, #0]",
|
||||
// Jump to bootloader
|
||||
"LDR R0,[R0, #4]",
|
||||
"BX R0",
|
||||
);
|
||||
}
|
||||
}
|
58
src/device/flash_store.rs
Normal file
58
src/device/flash_store.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use log::error;
|
||||
use sfkv::{Store, StoreBackend};
|
||||
use stm32f4xx_hal::{flash::{Error, FlashExt},
|
||||
pac::FLASH};
|
||||
|
||||
// Last flash sector is used to avoid overwriting the code in flash.
|
||||
pub const FLASH_SECTOR: u8 = 11;
|
||||
pub const FLASH_SECTOR_11_OFFSET: u32 = 0xE0000;
|
||||
|
||||
/// Only 16 KiB out of 128KiB in the Sector is used to save RAM
|
||||
pub const RESERVED_MEMORY: usize = 0x4000;
|
||||
static mut BACKUP_SPACE: [u8; RESERVED_MEMORY] = [0; RESERVED_MEMORY];
|
||||
|
||||
pub struct FlashBackend {
|
||||
flash: FLASH,
|
||||
}
|
||||
|
||||
fn get_offset() -> usize {
|
||||
FLASH_SECTOR_11_OFFSET as usize
|
||||
}
|
||||
|
||||
impl StoreBackend for FlashBackend {
|
||||
type Data = [u8];
|
||||
|
||||
fn data(&self) -> &Self::Data {
|
||||
&self.flash.read()[get_offset()..(get_offset() + RESERVED_MEMORY)]
|
||||
}
|
||||
|
||||
type Error = Error;
|
||||
fn erase(&mut self) -> Result<(), Self::Error> {
|
||||
self.flash.unlocked().erase(FLASH_SECTOR)
|
||||
}
|
||||
|
||||
fn program(&mut self, offset: usize, payload: &[u8]) -> Result<(), Self::Error> {
|
||||
self.flash.unlocked().program(get_offset() + offset, payload.iter())
|
||||
}
|
||||
|
||||
fn backup_space(&self) -> &'static mut [u8] {
|
||||
unsafe { BACKUP_SPACE.as_mut() }
|
||||
}
|
||||
}
|
||||
|
||||
pub type FlashStore = Store<FlashBackend>;
|
||||
|
||||
pub fn store(flash: FLASH) -> FlashStore {
|
||||
let backend = FlashBackend { flash };
|
||||
let mut store = FlashStore::new(backend);
|
||||
|
||||
match store.get_bytes_used() {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("corrupt store, erasing. error: {:?}", e);
|
||||
let _ = store.erase().map_err(|e| error!("flash erase failed: {:?}", e));
|
||||
}
|
||||
}
|
||||
|
||||
store
|
||||
}
|
@ -1,48 +1,71 @@
|
||||
use crate::laser_diode::current_sources::*;
|
||||
use fugit::RateExtU32;
|
||||
use stm32_eth::EthPins;
|
||||
use stm32f4xx_hal::{
|
||||
gpio::{gpioa::*, gpiob::*, gpioc::*, gpiog::*, GpioExt, Input},
|
||||
otg_fs::USB,
|
||||
pac::{GPIOA, GPIOB, GPIOC, GPIOD, GPIOG, OTG_FS_DEVICE, OTG_FS_GLOBAL, OTG_FS_PWRCLK, SPI2},
|
||||
rcc::Clocks,
|
||||
spi,
|
||||
spi::{NoMiso, Spi},
|
||||
};
|
||||
use stm32f4xx_hal::{gpio::{alt::otg_fs::{Dm, Dp},
|
||||
gpioa::*,
|
||||
gpiob::*,
|
||||
gpioc::*,
|
||||
GpioExt, Input, Speed},
|
||||
otg_fs::USB,
|
||||
pac::{GPIOA, GPIOB, GPIOC, GPIOD, GPIOE, OTG_FS_DEVICE, OTG_FS_GLOBAL, OTG_FS_PWRCLK, SPI1, SPI2,
|
||||
SPI3, TIM4},
|
||||
rcc::Clocks,
|
||||
spi::{NoMiso, Spi},
|
||||
timer::{pwm::PwmExt, Channel1, Channel2, Channel3}};
|
||||
|
||||
pub type EthernetPins =
|
||||
EthPins<PA1<Input>, PA7<Input>, PB11<Input>, PG13<Input>, PB13<Input>, PC4<Input>, PC5<Input>>;
|
||||
use crate::{device::hw_rev::{HWRev, HwRevPins},
|
||||
laser_diode::{ld_ctrl::{self, LdCtrlPhy},
|
||||
ld_pwr_exc_protector::LdPwrExcProtectorPhy,
|
||||
max5719},
|
||||
net::net::EthernetMgmtPins,
|
||||
thermostat::{ad5680, ad7172,
|
||||
max1968::{self, MAX1968Phy, MAX1968PinSet, PWM_FREQ_KHZ}}};
|
||||
|
||||
pub type EthernetPins = EthPins<PA1<Input>, PA7<Input>, PB11<Input>, PB12<Input>, PB13<Input>, PC4<Input>, PC5<Input>>;
|
||||
|
||||
pub fn setup(
|
||||
clocks: Clocks,
|
||||
tim4: TIM4,
|
||||
gpioa: GPIOA,
|
||||
gpiob: GPIOB,
|
||||
gpioc: GPIOC,
|
||||
gpiod: GPIOD,
|
||||
gpiog: GPIOG,
|
||||
gpioe: GPIOE,
|
||||
spi1: SPI1,
|
||||
spi2: SPI2,
|
||||
spi3: SPI3,
|
||||
otg_fs_global: OTG_FS_GLOBAL,
|
||||
otg_fs_device: OTG_FS_DEVICE,
|
||||
otg_fs_pwrclk: OTG_FS_PWRCLK,
|
||||
) -> (
|
||||
HWRev,
|
||||
EthernetPins,
|
||||
EthernetMgmtPins,
|
||||
USB,
|
||||
CurrentSourcePhyConstruct<CurrentSourcePhyCh0>,
|
||||
// photo_diode_phy,
|
||||
// thermostat_phy
|
||||
LdCtrlPhy<ld_ctrl::Channel0>,
|
||||
ad7172::AdcPhy,
|
||||
MAX1968Phy<max1968::Channel0>,
|
||||
LdPwrExcProtectorPhy,
|
||||
) {
|
||||
let gpioa = gpioa.split();
|
||||
let gpiob = gpiob.split();
|
||||
let gpioc = gpioc.split();
|
||||
let gpiod = gpiod.split();
|
||||
let gpiog = gpiog.split();
|
||||
let gpioe = gpioe.split();
|
||||
|
||||
let mut hw_rev = HWRev::detect_hw_rev(HwRevPins {
|
||||
h0: gpioe.pe8.into_input(),
|
||||
h1: gpioe.pe9.into_input(),
|
||||
h2: gpioe.pe10.into_input(),
|
||||
h3: gpioe.pe11.into_input(),
|
||||
});
|
||||
|
||||
hw_rev.startup_delay_before_gpio_init();
|
||||
|
||||
let usb = USB {
|
||||
usb_global: otg_fs_global,
|
||||
usb_device: otg_fs_device,
|
||||
usb_pwrclk: otg_fs_pwrclk,
|
||||
pin_dm: gpioa.pa11.into_alternate(),
|
||||
pin_dp: gpioa.pa12.into_alternate(),
|
||||
pin_dm: Dm::PA11(gpioa.pa11.into_alternate()),
|
||||
pin_dp: Dp::PA12(gpioa.pa12.into_alternate()),
|
||||
hclk: clocks.hclk(),
|
||||
};
|
||||
|
||||
@ -50,32 +73,92 @@ pub fn setup(
|
||||
ref_clk: gpioa.pa1,
|
||||
crs: gpioa.pa7,
|
||||
tx_en: gpiob.pb11,
|
||||
tx_d0: gpiog.pg13,
|
||||
tx_d0: gpiob.pb12,
|
||||
tx_d1: gpiob.pb13,
|
||||
rx_d0: gpioc.pc4,
|
||||
rx_d1: gpioc.pc5,
|
||||
};
|
||||
|
||||
let current_source_phy = CurrentSourcePhyConstruct {
|
||||
max5719_spi: Spi::new(
|
||||
spi2,
|
||||
(
|
||||
gpiob.pb10.into_alternate(),
|
||||
NoMiso {},
|
||||
gpiob.pb15.into_alternate(),
|
||||
let mut eth_mgmt_pins = EthernetMgmtPins {
|
||||
mdio: gpioa.pa2.into_alternate::<11>(),
|
||||
mdc: gpioc.pc1.into_alternate::<11>(),
|
||||
};
|
||||
eth_mgmt_pins.mdio.set_speed(Speed::VeryHigh);
|
||||
eth_mgmt_pins.mdc.set_speed(Speed::VeryHigh);
|
||||
|
||||
let current_source_phy = LdCtrlPhy {
|
||||
dac: max5719::Dac::new(
|
||||
Spi::new(
|
||||
spi2,
|
||||
(gpiob.pb10.into_alternate(), NoMiso::new(), gpiob.pb15.into_alternate()),
|
||||
max5719::SPI_MODE,
|
||||
max5719::SPI_CLOCK_MHZ.convert(),
|
||||
&clocks,
|
||||
),
|
||||
spi::Mode {
|
||||
polarity: spi::Polarity::IdleLow,
|
||||
phase: spi::Phase::CaptureOnFirstTransition,
|
||||
},
|
||||
10_u32.MHz(),
|
||||
&clocks,
|
||||
gpiod.pd8.into_push_pull_output(),
|
||||
gpiob.pb14.into_push_pull_output(),
|
||||
),
|
||||
max5719_load: gpiob.pb14.into_push_pull_output(),
|
||||
max5719_cs: gpiod.pd8.into_push_pull_output(),
|
||||
current_source_ldo_en: gpiod.pd9.into_push_pull_output(),
|
||||
current_source_short: gpioa.pa4.into_push_pull_output(),
|
||||
current_source_short_pin: gpioa.pa4.into_push_pull_output(),
|
||||
termination_status_pin: gpiod.pd7.internal_pull_up(true),
|
||||
};
|
||||
|
||||
(eth_pins, usb, current_source_phy)
|
||||
let pd_mon_phy = LdPwrExcProtectorPhy {
|
||||
_pd_mon_ch0: gpioa.pa3.into_analog(),
|
||||
pwr_en_ch0: gpiod.pd9.into_push_pull_output(),
|
||||
};
|
||||
|
||||
let pwm_chs = (
|
||||
Channel1::new(gpiob.pb6),
|
||||
Channel2::new(gpiob.pb7),
|
||||
Channel3::new(gpiob.pb8),
|
||||
);
|
||||
let (max_i_neg0, max_v0, max_i_pos0) = tim4.pwm_hz(pwm_chs, PWM_FREQ_KHZ.convert(), &clocks).split();
|
||||
|
||||
let max1968_phy = MAX1968Phy::new(MAX1968PinSet {
|
||||
dac: ad5680::Dac::new(
|
||||
Spi::new(
|
||||
spi1,
|
||||
(gpiob.pb3.into_alternate(), NoMiso::new(), gpiob.pb5.into_alternate()),
|
||||
ad5680::SPI_MODE,
|
||||
ad5680::SPI_CLOCK_MHZ.convert(),
|
||||
&clocks,
|
||||
),
|
||||
gpiob.pb4.into_push_pull_output(),
|
||||
),
|
||||
shdn: gpioa.pa5.into_push_pull_output(),
|
||||
vref_pin: gpioa.pa6.into_analog(),
|
||||
itec_pin: gpiob.pb1.into_analog(),
|
||||
dac_feedback_pin: gpioc.pc0.into_analog(),
|
||||
vtec_pin: gpiob.pb0.into_analog(),
|
||||
max_v: max_v0,
|
||||
max_i_pos: max_i_pos0,
|
||||
max_i_neg: max_i_neg0,
|
||||
});
|
||||
|
||||
let ad7172_phy = ad7172::Adc::new(
|
||||
Spi::new(
|
||||
spi3,
|
||||
(
|
||||
gpioc.pc10.into_alternate(),
|
||||
gpioc.pc11.into_alternate(),
|
||||
gpioc.pc12.into_alternate(),
|
||||
),
|
||||
ad7172::SPI_MODE,
|
||||
ad7172::SPI_CLOCK_MHZ.convert(),
|
||||
&clocks,
|
||||
),
|
||||
gpioa.pa15.into_push_pull_output(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
(
|
||||
hw_rev,
|
||||
eth_pins,
|
||||
eth_mgmt_pins,
|
||||
usb,
|
||||
current_source_phy,
|
||||
ad7172_phy,
|
||||
max1968_phy,
|
||||
pd_mon_phy,
|
||||
)
|
||||
}
|
||||
|
68
src/device/hw_rev.rs
Normal file
68
src/device/hw_rev.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use crc::{Crc, CRC_24_BLE};
|
||||
use miniconf::Tree;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use stm32f4xx_hal::{gpio::{Input, PE10, PE11, PE8, PE9},
|
||||
signature};
|
||||
|
||||
use crate::device::sys_timer::sleep;
|
||||
|
||||
pub struct HwRevPins {
|
||||
pub h0: PE8<Input>,
|
||||
pub h1: PE9<Input>,
|
||||
pub h2: PE10<Input>,
|
||||
pub h3: PE11<Input>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct HWRev {
|
||||
pub major: u8,
|
||||
pub minor: u8,
|
||||
}
|
||||
|
||||
impl HWRev {
|
||||
pub fn detect_hw_rev(hwrev_pins: HwRevPins) -> Self {
|
||||
let (h0, h1, h2, h3) = (
|
||||
hwrev_pins.h0.is_high(),
|
||||
hwrev_pins.h1.is_high(),
|
||||
hwrev_pins.h2.is_high(),
|
||||
hwrev_pins.h3.is_high(),
|
||||
);
|
||||
match (h0, h1, h2, h3) {
|
||||
(true, true, true, true) => HWRev { major: 0, minor: 3 },
|
||||
(_, _, _, _) => HWRev { major: 0, minor: 0 },
|
||||
}
|
||||
}
|
||||
|
||||
/// On Rev0_3, during power up, digital power rails are stabilized way before analog power rails
|
||||
/// This causes improper initialization on any peripherals requiring calibrations
|
||||
/// See Issue #32 on Kirdy Hw Repo
|
||||
pub fn startup_delay_before_gpio_init(&mut self) {
|
||||
if self.major == 0 && self.minor == 3 {
|
||||
sleep(5000);
|
||||
}
|
||||
}
|
||||
|
||||
/// On Rev0_3, it lacks pre-allocated Mac Addresses accessible on PCB.
|
||||
/// This functions generate a random Mac Address with 96bit unique UUID inside STM32
|
||||
/// See Issue #36 on Kirdy Hw Repo
|
||||
pub fn get_mac_address(&mut self) -> [u8; 6] {
|
||||
if self.major == 0 && self.minor == 3 {
|
||||
let uid = signature::Uid::get();
|
||||
let mut uid_data: [u8; 12] = [0; 12];
|
||||
uid_data[0] = uid.x() as u8;
|
||||
uid_data[1] = (uid.x() >> 8) as u8;
|
||||
uid_data[2] = uid.y() as u8;
|
||||
uid_data[3] = (uid.y() >> 8) as u8;
|
||||
uid_data[4..11].clone_from_slice(uid.lot_num().as_bytes());
|
||||
|
||||
let crc: Crc<u32> = Crc::<u32>::new(&CRC_24_BLE);
|
||||
let mut digest = crc.digest();
|
||||
digest.update(&uid_data);
|
||||
let crc24 = digest.finalize();
|
||||
|
||||
[0x02, 0xE0, 0xD5, (crc24 >> 16) as u8, (crc24 >> 8) as u8, (crc24 as u8)]
|
||||
} else {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
@ -3,13 +3,15 @@ pub fn init_log() {
|
||||
use super::usb;
|
||||
static USB_LOGGER: usb::Logger = usb::Logger;
|
||||
let _ = log::set_logger(&USB_LOGGER);
|
||||
log::set_max_level(log::LevelFilter::Debug);
|
||||
// log::set_max_level(log::LevelFilter::Debug);
|
||||
log::set_max_level(log::LevelFilter::Trace);
|
||||
}
|
||||
|
||||
#[cfg(feature = "RTT")]
|
||||
pub fn init_log() {
|
||||
use super::rtt_logger;
|
||||
use rtt_target::rtt_init_print;
|
||||
|
||||
use super::rtt_logger;
|
||||
static RTT_LOGGER: rtt_logger::Logger = rtt_logger::Logger;
|
||||
rtt_init_print!();
|
||||
let _ = log::set_logger(&RTT_LOGGER);
|
||||
@ -18,8 +20,8 @@ pub fn init_log() {
|
||||
|
||||
#[cfg(feature = "semihosting")]
|
||||
pub fn init_log() {
|
||||
use cortex_m_log::log::{init, Logger};
|
||||
use cortex_m_log::printer::semihosting::{hio::HStdout, InterruptOk};
|
||||
use cortex_m_log::{log::{init, Logger},
|
||||
printer::semihosting::{hio::HStdout, InterruptOk}};
|
||||
use log::LevelFilter;
|
||||
static mut LOGGER: Option<Logger<InterruptOk<HStdout>>> = None;
|
||||
let logger = Logger {
|
||||
|
@ -1,5 +1,8 @@
|
||||
pub mod boot;
|
||||
pub mod dfu;
|
||||
pub mod flash_store;
|
||||
pub mod gpio;
|
||||
pub mod hw_rev;
|
||||
pub mod log_setup;
|
||||
pub mod rtt_logger;
|
||||
pub mod sys_timer;
|
||||
|
@ -1,9 +1,8 @@
|
||||
use core::cell::RefCell;
|
||||
use core::ops::Deref;
|
||||
use cortex_m::interrupt::Mutex;
|
||||
use cortex_m::peripheral::syst::SystClkSource;
|
||||
use core::{cell::RefCell, ops::Deref};
|
||||
|
||||
use cortex_m::{interrupt::Mutex, peripheral::syst::SystClkSource};
|
||||
use cortex_m_rt::exception;
|
||||
use stm32f4xx_hal::{pac::SYST, rcc::Clocks};
|
||||
use stm32f4xx_hal::{pac::{SYST, Peripherals}, rcc::Clocks};
|
||||
|
||||
/// Rate in Hz
|
||||
const TIMER_RATE: u32 = 1000;
|
||||
@ -11,9 +10,13 @@ const TIMER_RATE: u32 = 1000;
|
||||
const TIMER_DELTA: u32 = 1000 / TIMER_RATE;
|
||||
/// Elapsed time in milliseconds
|
||||
static TIMER_MS: Mutex<RefCell<u32>> = Mutex::new(RefCell::new(0));
|
||||
static mut US_COUNT: u32 = 168;
|
||||
|
||||
/// Setup SysTick exception
|
||||
pub fn setup(mut syst: SYST, clocks: Clocks) {
|
||||
unsafe {
|
||||
US_COUNT = clocks.hclk().to_MHz()
|
||||
}
|
||||
syst.set_clock_source(SystClkSource::Core);
|
||||
syst.set_reload(clocks.hclk().to_Hz() / TIMER_RATE - 1);
|
||||
syst.enable_counter();
|
||||
@ -33,6 +36,15 @@ pub fn now() -> u32 {
|
||||
cortex_m::interrupt::free(|cs| *TIMER_MS.borrow(cs).borrow().deref())
|
||||
}
|
||||
|
||||
/// Obtain current time in milliseconds + microseconds
|
||||
pub fn now_precise() -> (u32, u32) {
|
||||
let ms = now();
|
||||
unsafe {
|
||||
let us = (Peripherals::steal().STK.load.read().bits() - Peripherals::steal().STK.val.read().bits()) / US_COUNT;
|
||||
(ms, us)
|
||||
}
|
||||
}
|
||||
|
||||
/// block for `amount` milliseconds
|
||||
pub fn sleep(amount: u32) {
|
||||
if amount == 0 {
|
||||
|
@ -1,17 +1,14 @@
|
||||
use core::{
|
||||
fmt::{self, Write},
|
||||
mem::MaybeUninit,
|
||||
};
|
||||
use core::{fmt::{self, Write},
|
||||
mem::MaybeUninit};
|
||||
|
||||
use cortex_m::interrupt::free;
|
||||
use log::{Log, Metadata, Record};
|
||||
use stm32f4xx_hal::{
|
||||
otg_fs::{UsbBus as Bus, USB},
|
||||
pac::{interrupt, Interrupt, NVIC},
|
||||
};
|
||||
use usb_device::{
|
||||
class_prelude::UsbBusAllocator,
|
||||
prelude::{UsbDevice, UsbDeviceBuilder, UsbVidPid},
|
||||
};
|
||||
use stm32f4xx_hal::{otg_fs::{UsbBus as Bus, USB},
|
||||
pac::{interrupt, Interrupt, NVIC}};
|
||||
use usb_device::{class_prelude::UsbBusAllocator,
|
||||
descriptor::lang_id,
|
||||
device::StringDescriptors,
|
||||
prelude::{UsbDevice, UsbDeviceBuilder, UsbVidPid}};
|
||||
use usbd_serial::SerialPort;
|
||||
|
||||
static mut EP_MEMORY: [u32; 1024] = [0; 1024];
|
||||
@ -27,16 +24,20 @@ pub struct State {
|
||||
|
||||
impl State {
|
||||
pub fn setup(usb: USB) {
|
||||
unsafe { BUS.write(Bus::new(usb, &mut EP_MEMORY)) };
|
||||
unsafe { BUS.write(Bus::new(usb, EP_MEMORY.as_mut())) };
|
||||
|
||||
let str_descriptor = StringDescriptors::new(lang_id::LangID::EN)
|
||||
.manufacturer("M-Labs")
|
||||
.product("Kirdy");
|
||||
|
||||
let bus = unsafe { BUS.assume_init_ref() };
|
||||
let serial = SerialPort::new(bus);
|
||||
let dev = UsbDeviceBuilder::new(bus, UsbVidPid(0x16c0, 0x27dd))
|
||||
.manufacturer("M-Labs")
|
||||
.product("thermostat")
|
||||
.device_release(0x20)
|
||||
.self_powered(true)
|
||||
.device_class(usbd_serial::USB_CLASS_CDC)
|
||||
.strings(&[str_descriptor])
|
||||
.unwrap()
|
||||
.build();
|
||||
|
||||
free(|_| unsafe {
|
||||
|
@ -1,71 +0,0 @@
|
||||
use stm32f4xx_hal::{
|
||||
gpio::{gpioa::*, gpiob::*, gpiod::*, Alternate, Output, PushPull, PD9},
|
||||
hal::{blocking::spi::Write, digital::v2::OutputPin},
|
||||
pac::SPI2,
|
||||
spi::{NoMiso, Spi, TransferModeNormal},
|
||||
};
|
||||
|
||||
use crate::device::sys_timer::sleep;
|
||||
pub trait CurrentSourcePhy {
|
||||
type CurrentSourceLdoEn: OutputPin;
|
||||
type CurrentSourceShort: OutputPin;
|
||||
type Max5719Load: OutputPin;
|
||||
type Max5719Cs: OutputPin;
|
||||
type Max5719Spi: Write<u8>;
|
||||
}
|
||||
|
||||
pub struct CurrentSourcePhyConstruct<C: CurrentSourcePhy> {
|
||||
pub max5719_spi: C::Max5719Spi,
|
||||
pub max5719_load: C::Max5719Load,
|
||||
pub max5719_cs: C::Max5719Cs,
|
||||
pub current_source_ldo_en: C::CurrentSourceLdoEn,
|
||||
pub current_source_short: C::CurrentSourceShort,
|
||||
}
|
||||
pub struct CurrentSourceSettings {
|
||||
pub output_current: f32,
|
||||
}
|
||||
pub struct CurrentSource<C: CurrentSourcePhy> {
|
||||
pub phy: CurrentSourcePhyConstruct<C>,
|
||||
pub setting: CurrentSourceSettings,
|
||||
}
|
||||
|
||||
pub struct CurrentSourcePhyCh0;
|
||||
|
||||
impl CurrentSourcePhy for CurrentSourcePhyCh0 {
|
||||
type CurrentSourceLdoEn = PD9<Output<PushPull>>;
|
||||
type CurrentSourceShort = PA4<Output<PushPull>>;
|
||||
type Max5719Load = PB14<Output<PushPull>>;
|
||||
type Max5719Cs = PD8<Output<PushPull>>;
|
||||
type Max5719Spi =
|
||||
Spi<SPI2, (PB10<Alternate<5>>, NoMiso, PB15<Alternate<5>>), TransferModeNormal>;
|
||||
}
|
||||
|
||||
impl<C: CurrentSourcePhy> CurrentSource<C> {
|
||||
pub fn setup(&mut self) {
|
||||
let _ = self.phy.max5719_load.set_high();
|
||||
let _ = self.phy.max5719_cs.set_high();
|
||||
let _ = self.phy.current_source_ldo_en.set_high();
|
||||
sleep(10_u32);
|
||||
let _ = self.phy.current_source_short.set_high();
|
||||
sleep(10_u32);
|
||||
}
|
||||
|
||||
pub fn set_current(
|
||||
&mut self,
|
||||
current: f32,
|
||||
) -> Result<(), <<C as CurrentSourcePhy>::Max5719Spi as Write<u8>>::Error> {
|
||||
let _ = self.phy.max5719_load.set_high();
|
||||
let _ = self.phy.max5719_cs.set_low();
|
||||
self.setting.output_current = current * 10.0 / 0.75;
|
||||
let word = (((self.setting.output_current / 4.096) * 1048576.0) as u32) << 4;
|
||||
let mut buf = [
|
||||
((word >> 16) & 0xFF) as u8,
|
||||
((word >> 8) & 0xFF) as u8,
|
||||
((word >> 0) & 0xFF) as u8,
|
||||
];
|
||||
self.phy.max5719_spi.write(&mut buf)?;
|
||||
let _ = self.phy.max5719_cs.set_high();
|
||||
let _ = self.phy.max5719_load.set_low();
|
||||
Ok(())
|
||||
}
|
||||
}
|
311
src/laser_diode/laser_diode.rs
Normal file
311
src/laser_diode/laser_diode.rs
Normal file
@ -0,0 +1,311 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use log::info;
|
||||
use miniconf::Tree;
|
||||
use num_traits::Zero;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use stm32f4xx_hal::{pac::{ADC3, TIM2},
|
||||
timer::CounterUs};
|
||||
use uom::si::{electric_current::{ampere, milliampere},
|
||||
f32::{ElectricCurrent, ElectricPotential, ElectricalConductance, ElectricalResistance, Power},
|
||||
power::milliwatt};
|
||||
|
||||
use crate::{device::sys_timer::sleep,
|
||||
laser_diode::{ld_ctrl::{Impedance50Ohm, LdCtrl},
|
||||
ld_current_out_ctrl_timer::LdCurrentOutCtrlTimer,
|
||||
ld_pwr_exc_protector::{self, LdPwrExcProtector},
|
||||
pd_mon_params}};
|
||||
|
||||
impl Settings {
|
||||
pub const LD_CURRENT_MAX: ElectricCurrent = ElectricCurrent {
|
||||
dimension: PhantomData,
|
||||
units: PhantomData,
|
||||
value: 0.3,
|
||||
};
|
||||
pub const DAC_OUT_V_MAX: ElectricPotential = ElectricPotential {
|
||||
dimension: PhantomData,
|
||||
units: PhantomData,
|
||||
value: 4.096,
|
||||
};
|
||||
|
||||
const LD_DRIVE_TRANSIMPEDANCE: ElectricalResistance = ElectricalResistance {
|
||||
dimension: PhantomData,
|
||||
units: PhantomData,
|
||||
value: 10.0 / 0.75,
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Copy, Debug, Tree)]
|
||||
struct Settings {
|
||||
pwr_on: bool,
|
||||
default_pwr_on: bool,
|
||||
ld_drive_current: ElectricCurrent,
|
||||
pd_mon_params: pd_mon_params::Parameters,
|
||||
ld_pwr_limit: Power,
|
||||
ld_terms_short: bool,
|
||||
incoming_pd_mon_params: pd_mon_params::Parameters,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pwr_on: false,
|
||||
default_pwr_on: false,
|
||||
ld_drive_current: ElectricCurrent::new::<milliampere>(0.0),
|
||||
pd_mon_params: pd_mon_params::Parameters::default(),
|
||||
incoming_pd_mon_params: pd_mon_params::Parameters::default(),
|
||||
ld_pwr_limit: Power::new::<milliwatt>(0.0),
|
||||
ld_terms_short: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct StatusReport {
|
||||
pwr_on: bool,
|
||||
pwr_excursion: bool,
|
||||
ld_i_set: ElectricCurrent,
|
||||
pd_i: ElectricCurrent,
|
||||
pd_pwr: Power,
|
||||
term_50ohm: Impedance50Ohm,
|
||||
}
|
||||
|
||||
pub struct LdDrive {
|
||||
ctrl: LdCtrl,
|
||||
settings: Settings,
|
||||
}
|
||||
|
||||
impl LdDrive {
|
||||
pub fn new(
|
||||
current_source: LdCtrl,
|
||||
pins_adc: ADC3,
|
||||
tim2: CounterUs<TIM2>,
|
||||
phy: ld_pwr_exc_protector::LdPwrExcProtectorPhy,
|
||||
) -> Self {
|
||||
LdPwrExcProtector::setup(pins_adc, phy);
|
||||
LdCurrentOutCtrlTimer::setup(tim2);
|
||||
|
||||
LdDrive {
|
||||
ctrl: current_source,
|
||||
settings: Settings::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup(&mut self) {
|
||||
LdPwrExcProtector::pwr_off();
|
||||
self.ctrl.set_i(
|
||||
ElectricCurrent::new::<milliampere>(0.0),
|
||||
Settings::LD_DRIVE_TRANSIMPEDANCE,
|
||||
Settings::DAC_OUT_V_MAX,
|
||||
);
|
||||
LdCurrentOutCtrlTimer::reset();
|
||||
self.ld_short();
|
||||
}
|
||||
|
||||
pub fn ld_short(&mut self) {
|
||||
self.ctrl.ld_short_enable();
|
||||
self.settings.ld_terms_short = true;
|
||||
}
|
||||
|
||||
pub fn ld_open(&mut self) {
|
||||
self.ctrl.ld_short_disable();
|
||||
self.settings.ld_terms_short = false;
|
||||
}
|
||||
|
||||
pub fn power_up(&mut self) {
|
||||
let prev_i_set = self.settings.ld_drive_current;
|
||||
LdCurrentOutCtrlTimer::reset();
|
||||
let _ = self.ctrl.set_i(
|
||||
ElectricCurrent::new::<milliampere>(0.0),
|
||||
Settings::LD_DRIVE_TRANSIMPEDANCE,
|
||||
Settings::DAC_OUT_V_MAX,
|
||||
);
|
||||
// Wait for the DAC to reset its voltage back to 0V
|
||||
sleep(35);
|
||||
LdPwrExcProtector::pwr_on_and_arm_protection();
|
||||
// Wait for LD Power Supply to start up before driving current to laser diode
|
||||
sleep(30);
|
||||
self.ld_set_i(prev_i_set);
|
||||
self.settings.pwr_on = true;
|
||||
}
|
||||
|
||||
pub fn power_down(&mut self) {
|
||||
LdPwrExcProtector::pwr_off();
|
||||
self.settings.pwr_on = false;
|
||||
}
|
||||
|
||||
pub fn get_pd_i(&mut self) -> ElectricCurrent {
|
||||
self.settings
|
||||
.pd_mon_params
|
||||
.get_pd_i_from_pd_v(LdPwrExcProtector::get_status().v)
|
||||
}
|
||||
|
||||
pub fn get_pd_pwr(&mut self) -> Power {
|
||||
self.settings.pd_mon_params.get_ld_pwr_from_ld_i(
|
||||
self.settings
|
||||
.pd_mon_params
|
||||
.get_pd_i_from_pd_v(LdPwrExcProtector::get_status().v),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn ld_set_i(&mut self, i: ElectricCurrent) {
|
||||
self.settings.ld_drive_current = i.min(Settings::LD_CURRENT_MAX);
|
||||
LdCurrentOutCtrlTimer::set_target_i_and_listen_irq(self.settings.ld_drive_current, self.ctrl.get_i_set());
|
||||
}
|
||||
|
||||
pub fn poll_pd_mon_v(&mut self) -> ElectricPotential {
|
||||
LdPwrExcProtector::poll_pd_mon_v()
|
||||
}
|
||||
|
||||
pub fn poll_and_update_output_current(&mut self) -> ElectricCurrent {
|
||||
match LdCurrentOutCtrlTimer::get_irq_status() {
|
||||
Some(i_set) => {
|
||||
let i_set = self
|
||||
.ctrl
|
||||
.set_i(i_set, Settings::LD_DRIVE_TRANSIMPEDANCE, Settings::DAC_OUT_V_MAX);
|
||||
LdCurrentOutCtrlTimer::clear_alarm_and_resume_listening();
|
||||
i_set
|
||||
}
|
||||
None => ElectricCurrent::new::<ampere>(0.0),
|
||||
}
|
||||
}
|
||||
|
||||
// Set the calibrated VDDA value obtained from ADC1 calibration
|
||||
pub fn set_pd_mon_calibrated_vdda(&mut self, val_cal: u32) {
|
||||
LdPwrExcProtector::set_calibrated_vdda(val_cal)
|
||||
}
|
||||
|
||||
pub fn pd_mon_clear_alarm(&mut self) {
|
||||
LdPwrExcProtector::clear_alarm_status();
|
||||
}
|
||||
|
||||
pub fn set_pd_transconductance(&mut self, transconductance: ElectricalConductance) {
|
||||
self.settings.pd_mon_params.set_transconductance(transconductance)
|
||||
}
|
||||
|
||||
pub fn set_pd_responsitivity(&mut self, responsitivity: pd_mon_params::ResponsitivityUnit) {
|
||||
self.settings.incoming_pd_mon_params.set_responsitivity(responsitivity);
|
||||
}
|
||||
|
||||
pub fn set_pd_dark_current(&mut self, i_dark: ElectricCurrent) {
|
||||
self.settings.incoming_pd_mon_params.set_i_dark(i_dark);
|
||||
}
|
||||
|
||||
pub fn apply_pd_params(&mut self) -> bool {
|
||||
let prev_pd_params = self.settings.pd_mon_params;
|
||||
self.settings.incoming_pd_mon_params.transconductance = self.settings.pd_mon_params.transconductance;
|
||||
self.settings.pd_mon_params = self.settings.incoming_pd_mon_params;
|
||||
|
||||
let max_settable_pwr = self.get_ld_power_limit_range();
|
||||
let is_legal = self.settings.ld_pwr_limit <= max_settable_pwr;
|
||||
if is_legal {
|
||||
self.set_ld_power_limit(self.settings.ld_pwr_limit);
|
||||
} else {
|
||||
self.settings.pd_mon_params = prev_pd_params;
|
||||
}
|
||||
is_legal
|
||||
}
|
||||
|
||||
pub fn set_ld_power_limit(&mut self, pwr_limit: Power) -> bool {
|
||||
let is_legal = LdPwrExcProtector::set_trigger_threshold_v(
|
||||
self.settings.pd_mon_params.get_pd_i_from_ld_pwr(pwr_limit) / self.settings.pd_mon_params.transconductance,
|
||||
);
|
||||
if is_legal {
|
||||
self.settings.ld_pwr_limit = pwr_limit;
|
||||
}
|
||||
is_legal
|
||||
}
|
||||
|
||||
pub fn get_ld_power_limit_range(&mut self) -> Power {
|
||||
let v_range = LdPwrExcProtector::get_settable_volt_range();
|
||||
let i_range = self.settings.pd_mon_params.get_pd_i_from_pd_v(v_range);
|
||||
let max_settable_pwr = self.settings.pd_mon_params.get_ld_pwr_from_ld_i(i_range);
|
||||
if max_settable_pwr.is_nan() || max_settable_pwr.is_infinite() {
|
||||
Power::zero()
|
||||
} else {
|
||||
max_settable_pwr
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_pd_i_limit(&mut self, i: ElectricCurrent) -> bool {
|
||||
return LdPwrExcProtector::set_trigger_threshold_v(i / self.settings.pd_mon_params.transconductance);
|
||||
}
|
||||
|
||||
pub fn set_default_pwr_on(&mut self, pwr_on: bool) {
|
||||
self.settings.default_pwr_on = pwr_on;
|
||||
}
|
||||
|
||||
pub fn get_term_status(&mut self) -> Impedance50Ohm {
|
||||
self.ctrl.get_lf_mod_in_impedance()
|
||||
}
|
||||
|
||||
pub fn get_status_report(&mut self) -> StatusReport {
|
||||
let ld_i_set = if LdPwrExcProtector::get_status().pwr_engaged {
|
||||
self.ctrl.get_i_set()
|
||||
} else {
|
||||
ElectricCurrent::new::<ampere>(0.0)
|
||||
};
|
||||
StatusReport {
|
||||
pwr_on: LdPwrExcProtector::get_status().pwr_engaged,
|
||||
pwr_excursion: LdPwrExcProtector::get_status().pwr_excursion,
|
||||
ld_i_set: ld_i_set,
|
||||
pd_i: self.get_pd_i(),
|
||||
pd_pwr: self.get_pd_pwr(),
|
||||
term_50ohm: self.get_term_status(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_settings_summary(&mut self) -> LdSettingsSummary {
|
||||
let settings = self.settings;
|
||||
LdSettingsSummary {
|
||||
default_pwr_on: self.settings.default_pwr_on,
|
||||
ld_drive_current: LdSettingsSummaryField {
|
||||
value: settings.ld_drive_current,
|
||||
max: Settings::LD_CURRENT_MAX,
|
||||
},
|
||||
pd_mon_params: settings.pd_mon_params,
|
||||
ld_pwr_limit: LdSettingsSummaryField {
|
||||
value: settings.ld_pwr_limit,
|
||||
max: self.get_ld_power_limit_range(),
|
||||
},
|
||||
ld_terms_short: settings.ld_terms_short,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_settings_from_summary(&mut self, settings: LdSettingsSummary) {
|
||||
self.power_down();
|
||||
self.settings.ld_drive_current = settings.ld_drive_current.value;
|
||||
self.settings.pd_mon_params = settings.pd_mon_params;
|
||||
let max_pwr_limit = self.get_ld_power_limit_range();
|
||||
self.settings.ld_pwr_limit = settings.ld_pwr_limit.value.min(max_pwr_limit);
|
||||
self.settings.default_pwr_on = settings.default_pwr_on;
|
||||
self.set_ld_power_limit(self.settings.ld_pwr_limit);
|
||||
|
||||
if self.settings.ld_terms_short {
|
||||
self.ld_short();
|
||||
} else {
|
||||
self.ld_open();
|
||||
}
|
||||
|
||||
if settings.default_pwr_on {
|
||||
self.power_up();
|
||||
} else {
|
||||
self.power_down();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct LdSettingsSummary {
|
||||
default_pwr_on: bool,
|
||||
ld_drive_current: LdSettingsSummaryField<ElectricCurrent>,
|
||||
pd_mon_params: pd_mon_params::Parameters,
|
||||
ld_pwr_limit: LdSettingsSummaryField<Power>,
|
||||
ld_terms_short: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct LdSettingsSummaryField<T> {
|
||||
value: T,
|
||||
max: T,
|
||||
}
|
96
src/laser_diode/ld_ctrl.rs
Normal file
96
src/laser_diode/ld_ctrl.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use stm32f4xx_hal::{gpio::{gpioa::*, gpiob::*, gpiod::*, Input, Output, PushPull},
|
||||
hal::{digital::{InputPin, OutputPin},
|
||||
spi::SpiBus},
|
||||
pac::SPI2,
|
||||
spi::Spi};
|
||||
use uom::si::{electric_current::ampere,
|
||||
f32::{ElectricCurrent, ElectricPotential, ElectricalResistance},
|
||||
ratio::ratio};
|
||||
|
||||
use crate::laser_diode::max5719::{self, Dac};
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, Copy)]
|
||||
pub enum Impedance50Ohm {
|
||||
On,
|
||||
Off,
|
||||
}
|
||||
|
||||
pub trait ChannelPins {
|
||||
type CurrentSourceShort: OutputPin;
|
||||
type TerminationStatus: InputPin;
|
||||
type Max5719Load: OutputPin;
|
||||
type Max5719Cs: OutputPin;
|
||||
type Max5719Spi: SpiBus<u8>;
|
||||
}
|
||||
|
||||
pub struct LdCtrlPhy<C: ChannelPins> {
|
||||
pub dac: Dac<C::Max5719Spi, C::Max5719Cs, C::Max5719Load>,
|
||||
pub current_source_short_pin: C::CurrentSourceShort,
|
||||
pub termination_status_pin: C::TerminationStatus,
|
||||
}
|
||||
pub struct Channel0;
|
||||
|
||||
impl ChannelPins for Channel0 {
|
||||
type CurrentSourceShort = PA4<Output<PushPull>>;
|
||||
type TerminationStatus = PD7<Input>;
|
||||
type Max5719Load = DacLoad;
|
||||
type Max5719Cs = DacCs;
|
||||
type Max5719Spi = DacSpi;
|
||||
}
|
||||
|
||||
type DacSpi = Spi<SPI2>;
|
||||
type DacCs = PD8<Output<PushPull>>;
|
||||
type DacLoad = PB14<Output<PushPull>>;
|
||||
|
||||
pub struct LdCtrl {
|
||||
pub phy: LdCtrlPhy<Channel0>,
|
||||
i_set: ElectricCurrent,
|
||||
}
|
||||
|
||||
impl LdCtrl {
|
||||
pub fn new(phy_ch0: LdCtrlPhy<Channel0>) -> Self {
|
||||
LdCtrl {
|
||||
phy: phy_ch0,
|
||||
i_set: ElectricCurrent::new::<ampere>(0.0),
|
||||
}
|
||||
}
|
||||
|
||||
// LD Terminals are shorted together
|
||||
pub fn ld_short_enable(&mut self) {
|
||||
self.phy.current_source_short_pin.set_low();
|
||||
}
|
||||
|
||||
// LD Current flows from anode to cathode
|
||||
pub fn ld_short_disable(&mut self) {
|
||||
self.phy.current_source_short_pin.set_high();
|
||||
}
|
||||
|
||||
pub fn get_lf_mod_in_impedance(&mut self) -> Impedance50Ohm {
|
||||
if self.phy.termination_status_pin.is_high() {
|
||||
Impedance50Ohm::On
|
||||
} else {
|
||||
Impedance50Ohm::Off
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_dac(&mut self, voltage: ElectricPotential, dac_out_v_max: ElectricPotential) -> ElectricPotential {
|
||||
let value = ((voltage / dac_out_v_max).get::<ratio>() * (max5719::MAX_VALUE as f32)) as u32;
|
||||
self.phy.dac.set(value).unwrap();
|
||||
value as f32 * dac_out_v_max / max5719::MAX_VALUE as f32
|
||||
}
|
||||
|
||||
pub fn set_i(
|
||||
&mut self,
|
||||
current: ElectricCurrent,
|
||||
transimpedance: ElectricalResistance,
|
||||
dac_out_v_max: ElectricPotential,
|
||||
) -> ElectricCurrent {
|
||||
self.i_set = self.set_dac(current * transimpedance, dac_out_v_max) / transimpedance;
|
||||
self.i_set
|
||||
}
|
||||
|
||||
pub fn get_i_set(&mut self) -> ElectricCurrent {
|
||||
self.i_set
|
||||
}
|
||||
}
|
128
src/laser_diode/ld_current_out_ctrl_timer.rs
Normal file
128
src/laser_diode/ld_current_out_ctrl_timer.rs
Normal file
@ -0,0 +1,128 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use fugit::{KilohertzU32, TimerDurationU32};
|
||||
use log::debug;
|
||||
use stm32f4xx_hal::{pac::{interrupt, Interrupt, TIM2},
|
||||
timer::{Counter, Event},
|
||||
Listen};
|
||||
use uom::si::{electric_current::ampere, f32::ElectricCurrent};
|
||||
|
||||
pub struct LdCurrentOutCtrlTimer {
|
||||
target_i: ElectricCurrent,
|
||||
now_i: ElectricCurrent,
|
||||
timer: Counter<TIM2, FREQ>,
|
||||
timeout: bool,
|
||||
}
|
||||
static mut LD_CURRENT_OUT_CTRL_TIMER: Option<LdCurrentOutCtrlTimer> = None;
|
||||
|
||||
pub const FREQ: u32 = 1000000;
|
||||
|
||||
/// This timer notifies the main loop to set the correct output current so that ld output current can ramp up/down slowly.
|
||||
/// The current output slope is guaranteed to be larger but not necessarily equal to than the preset value.
|
||||
impl LdCurrentOutCtrlTimer {
|
||||
const TIME_STEP_MS: TimerDurationU32<FREQ> = TimerDurationU32::from_rate(KilohertzU32::from_raw(1));
|
||||
const STEP_SIZE: ElectricCurrent = ElectricCurrent {
|
||||
dimension: PhantomData,
|
||||
units: PhantomData,
|
||||
value: 0.0001,
|
||||
};
|
||||
|
||||
pub fn setup(mut tim2: Counter<TIM2, FREQ>) {
|
||||
tim2.start(LdCurrentOutCtrlTimer::TIME_STEP_MS).unwrap();
|
||||
unsafe {
|
||||
cortex_m::peripheral::NVIC::unmask(Interrupt::TIM2);
|
||||
}
|
||||
unsafe {
|
||||
LD_CURRENT_OUT_CTRL_TIMER = Some(LdCurrentOutCtrlTimer {
|
||||
target_i: ElectricCurrent::new::<ampere>(0.0),
|
||||
now_i: ElectricCurrent::new::<ampere>(0.0),
|
||||
timer: tim2,
|
||||
timeout: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn get() -> Option<&'static mut Self> {
|
||||
unsafe { LD_CURRENT_OUT_CTRL_TIMER.as_mut() }
|
||||
}
|
||||
|
||||
pub fn reset() {
|
||||
if let Some(ref mut ld_current_out_ctrl_timer) = LdCurrentOutCtrlTimer::get() {
|
||||
ld_current_out_ctrl_timer.target_i = ElectricCurrent::new::<ampere>(0.0);
|
||||
ld_current_out_ctrl_timer.now_i = ElectricCurrent::new::<ampere>(0.0);
|
||||
ld_current_out_ctrl_timer.timeout = false;
|
||||
ld_current_out_ctrl_timer.timer.unlisten(Event::Update);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_target_i_and_listen_irq(target: ElectricCurrent, now: ElectricCurrent) {
|
||||
if let Some(ref mut ld_current_out_ctrl_timer) = LdCurrentOutCtrlTimer::get() {
|
||||
cortex_m::interrupt::free(|_| {
|
||||
ld_current_out_ctrl_timer.target_i = target;
|
||||
ld_current_out_ctrl_timer.now_i = now;
|
||||
ld_current_out_ctrl_timer.timer.listen(Event::Update);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_alarm() {
|
||||
if let Some(ref mut ld_current_out_ctrl_timer) = LdCurrentOutCtrlTimer::get() {
|
||||
ld_current_out_ctrl_timer.timeout = true;
|
||||
}
|
||||
}
|
||||
pub fn clear_alarm_and_resume_listening() {
|
||||
if let Some(ref mut ld_current_out_ctrl_timer) = LdCurrentOutCtrlTimer::get() {
|
||||
ld_current_out_ctrl_timer.timeout = false;
|
||||
ld_current_out_ctrl_timer.timer.listen(Event::Update);
|
||||
}
|
||||
}
|
||||
pub fn get_irq_status() -> Option<ElectricCurrent> {
|
||||
if let Some(ref mut ld_current_out_ctrl_timer) = LdCurrentOutCtrlTimer::get() {
|
||||
if ld_current_out_ctrl_timer.timeout {
|
||||
return Some(ld_current_out_ctrl_timer.now_i);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn clear_irq_flag() {
|
||||
if let Some(ref mut ld_current_out_ctrl_timer) = LdCurrentOutCtrlTimer::get() {
|
||||
match ld_current_out_ctrl_timer.timer.wait() {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
debug!("LD CTRL TIMER Interrupt is not present when clear_irq_flag() is called")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_next_i_set() -> bool {
|
||||
if let Some(ref mut ld_current_out_ctrl_timer) = LdCurrentOutCtrlTimer::get() {
|
||||
let update = ld_current_out_ctrl_timer.now_i != ld_current_out_ctrl_timer.target_i;
|
||||
if ld_current_out_ctrl_timer.target_i > ld_current_out_ctrl_timer.now_i {
|
||||
ld_current_out_ctrl_timer.now_i = (ld_current_out_ctrl_timer.now_i + LdCurrentOutCtrlTimer::STEP_SIZE)
|
||||
.min(ld_current_out_ctrl_timer.target_i);
|
||||
} else {
|
||||
ld_current_out_ctrl_timer.now_i = (ld_current_out_ctrl_timer.now_i - LdCurrentOutCtrlTimer::STEP_SIZE)
|
||||
.max(ld_current_out_ctrl_timer.target_i);
|
||||
}
|
||||
return update;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn stop_listening() {
|
||||
if let Some(ref mut ld_current_out_ctrl_timer) = LdCurrentOutCtrlTimer::get() {
|
||||
ld_current_out_ctrl_timer.timer.unlisten(Event::Update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[interrupt]
|
||||
fn TIM2() {
|
||||
if LdCurrentOutCtrlTimer::update_next_i_set() {
|
||||
LdCurrentOutCtrlTimer::set_alarm();
|
||||
}
|
||||
LdCurrentOutCtrlTimer::stop_listening();
|
||||
LdCurrentOutCtrlTimer::clear_irq_flag();
|
||||
}
|
296
src/laser_diode/ld_pwr_exc_protector.rs
Normal file
296
src/laser_diode/ld_pwr_exc_protector.rs
Normal file
@ -0,0 +1,296 @@
|
||||
use num_traits::{Float, Zero};
|
||||
use stm32f4xx_hal::{adc::{config::{self, AdcConfig},
|
||||
Adc},
|
||||
gpio::{gpioa::PA3, gpiod::PD9, Analog, Output, PushPull},
|
||||
interrupt, pac,
|
||||
pac::{Peripherals, ADC3, NVIC},
|
||||
rcc::Enable};
|
||||
use uom::si::{electric_potential::millivolt, f32::ElectricPotential, ratio::ratio};
|
||||
|
||||
// 12 bit Resolution
|
||||
const MAX_SAMPLE: u16 = 4095;
|
||||
pub type LdPwrEnPinType = PD9<Output<PushPull>>;
|
||||
pub type PdMonAdcPinType = PA3<Analog>;
|
||||
const PD_MON_ADC_CH_ID: u8 = 0x03;
|
||||
|
||||
static mut LD_PWR_EXC_PROTECTOR: Option<LdPwrExcProtector> = None;
|
||||
|
||||
pub struct LdPwrExcProtectorPhy {
|
||||
// To make sure Pd Mon Pin is configured to Analog mode
|
||||
pub _pd_mon_ch0: PdMonAdcPinType,
|
||||
pub pwr_en_ch0: LdPwrEnPinType,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Status {
|
||||
pub pwr_excursion: bool,
|
||||
pub v_tripped: ElectricPotential,
|
||||
pub pwr_engaged: bool,
|
||||
pub v: ElectricPotential,
|
||||
}
|
||||
|
||||
impl Default for Status {
|
||||
fn default() -> Self {
|
||||
Status {
|
||||
pwr_excursion: false,
|
||||
v_tripped: ElectricPotential::new::<millivolt>(0.0),
|
||||
pwr_engaged: false,
|
||||
v: ElectricPotential::new::<millivolt>(0.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LdPwrExcProtector {
|
||||
pac: ADC3,
|
||||
phy: LdPwrExcProtectorPhy,
|
||||
alarm_status: Status,
|
||||
calibrated_vdda: u32,
|
||||
offset: u32,
|
||||
prev_samples: [u16; 32],
|
||||
ptr: u16,
|
||||
}
|
||||
|
||||
impl LdPwrExcProtector {
|
||||
/// ADC Analog Watchdog is configured to guard a single regular Adc channel on Pd Mon Pin.
|
||||
/// ADC is configured to start continuous conversion without using DMA immediately.
|
||||
/// Interrupt is disabled by default.
|
||||
pub fn setup(pac_adc: ADC3, mut phy: LdPwrExcProtectorPhy) {
|
||||
let mut offset = 0;
|
||||
unsafe {
|
||||
let adc_config = AdcConfig::default()
|
||||
.clock(config::Clock::Pclk2_div_8)
|
||||
.default_sample_time(config::SampleTime::Cycles_480);
|
||||
let mut tmp_adc = Adc::adc3(Peripherals::steal().ADC3, false, adc_config);
|
||||
|
||||
for _ in (0..1024).rev() {
|
||||
offset += tmp_adc.convert(&phy._pd_mon_ch0, stm32f4xx_hal::adc::config::SampleTime::Cycles_480) as u32;
|
||||
}
|
||||
offset /= 1024 as u32
|
||||
}
|
||||
|
||||
// Do not set reset RCCs as it causes other ADCs' clock to be disabled
|
||||
unsafe {
|
||||
// All ADCs share the same reset interface.
|
||||
// NOTE(unsafe) this reference will only be used for atomic writes with no side effects.
|
||||
let rcc = &(*pac::RCC::ptr());
|
||||
// Enable the ADC3 Clock
|
||||
pac::ADC3::enable(rcc);
|
||||
// Enable ADC Interrupt
|
||||
NVIC::unmask(interrupt::ADC);
|
||||
}
|
||||
|
||||
pac_adc.cr1.reset();
|
||||
pac_adc.cr2.reset();
|
||||
pac_adc.sqr1.reset();
|
||||
pac_adc.sqr2.reset();
|
||||
pac_adc.sqr3.reset();
|
||||
|
||||
pac_adc.cr1.write(|w| {
|
||||
w
|
||||
// 12 Bit Resolution
|
||||
.res()
|
||||
.twelve_bit()
|
||||
// Set Analog Watchdog to guard Single Regular Channel
|
||||
.awden()
|
||||
.enabled()
|
||||
.awdsgl()
|
||||
.single_channel()
|
||||
.jawden()
|
||||
.disabled()
|
||||
// Disable Analog Watchdog Interrupt
|
||||
.awdie()
|
||||
.disabled()
|
||||
// Set Analog Watchdog to monitor Pd Mon Pin
|
||||
.awdch()
|
||||
.variant(PD_MON_ADC_CH_ID)
|
||||
});
|
||||
pac_adc.cr2.write(|w| {
|
||||
w
|
||||
// Continous Conversion Mode
|
||||
.cont()
|
||||
.set_bit()
|
||||
// Power up ADC
|
||||
.adon()
|
||||
.set_bit()
|
||||
// Set data alignment to the right
|
||||
.align()
|
||||
.right()
|
||||
// End of conversion selection: Each Sequence
|
||||
.eocs()
|
||||
.each_sequence()
|
||||
.exten()
|
||||
.disabled()
|
||||
.extsel()
|
||||
.tim1cc1()
|
||||
});
|
||||
// Set the Conversion Sequence to include Pd Mon Pin
|
||||
pac_adc.sqr3.write(|w| w.sq1().variant(PD_MON_ADC_CH_ID));
|
||||
|
||||
// Set all sampling channels to have slowest sampling interval
|
||||
pac_adc.smpr1.write(|w| unsafe { w.bits(0xFFFF) });
|
||||
pac_adc.smpr2.write(|w| unsafe { w.bits(0xFFFF) });
|
||||
|
||||
// Set the higher threshold to be max value initially
|
||||
pac_adc.htr.write(|w| w.ht().variant(MAX_SAMPLE));
|
||||
// Set the lower threshold to be min value initially
|
||||
pac_adc.ltr.write(|w| w.lt().variant(0));
|
||||
|
||||
// SWStart should only be set when ADON = 1. Otherwise no conversion is launched.
|
||||
pac_adc.cr2.modify(|_, w| w.swstart().set_bit());
|
||||
|
||||
phy.pwr_en_ch0.set_low();
|
||||
|
||||
unsafe {
|
||||
LD_PWR_EXC_PROTECTOR = Some(LdPwrExcProtector {
|
||||
pac: pac_adc,
|
||||
phy: phy,
|
||||
alarm_status: Status::default(),
|
||||
calibrated_vdda: 3300,
|
||||
offset: offset,
|
||||
prev_samples: [0; 32],
|
||||
ptr: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn get() -> Option<&'static mut Self> {
|
||||
unsafe { LD_PWR_EXC_PROTECTOR.as_mut() }
|
||||
}
|
||||
|
||||
fn convert_sample_to_volt(sample: u16) -> ElectricPotential {
|
||||
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
||||
return ElectricPotential::new::<millivolt>(
|
||||
(((i32::from(sample) - wdg.offset as i32).max(0) as u32 * wdg.calibrated_vdda) / u32::from(MAX_SAMPLE))
|
||||
as f32,
|
||||
);
|
||||
}
|
||||
ElectricPotential::new::<millivolt>(0.0)
|
||||
}
|
||||
|
||||
pub fn set_trigger_threshold_v(htr: ElectricPotential) -> bool {
|
||||
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
||||
let code: u32 = (((htr / (ElectricPotential::new::<millivolt>(wdg.calibrated_vdda as f32))).get::<ratio>()
|
||||
* (MAX_SAMPLE as f32))
|
||||
.ceil() as u32)
|
||||
+ wdg.offset;
|
||||
if code <= MAX_SAMPLE as u32 {
|
||||
wdg.pac.htr.write(|w| unsafe { w.bits(code) });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn set_calibrated_vdda(val: u32) {
|
||||
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
||||
wdg.calibrated_vdda = val;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_pd_mon_v() -> ElectricPotential {
|
||||
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
||||
if wdg.pac.sr.read().eoc().bit() {
|
||||
wdg.prev_samples[wdg.ptr as usize] = wdg.pac.dr.read().data().bits();
|
||||
wdg.ptr = (wdg.ptr + 1) % 32 as u16;
|
||||
|
||||
let mut samples: u32 = 0;
|
||||
for idx in 0..32 {
|
||||
samples += wdg.prev_samples[idx] as u32;
|
||||
}
|
||||
samples = samples >> 5;
|
||||
wdg.alarm_status.v = LdPwrExcProtector::convert_sample_to_volt(samples as u16);
|
||||
}
|
||||
return wdg.alarm_status.v;
|
||||
}
|
||||
ElectricPotential::zero()
|
||||
}
|
||||
|
||||
pub fn get_status() -> Status {
|
||||
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
||||
LdPwrExcProtector::poll_pd_mon_v();
|
||||
return wdg.alarm_status.clone();
|
||||
}
|
||||
Status::default()
|
||||
}
|
||||
|
||||
pub fn get_settable_volt_range() -> ElectricPotential {
|
||||
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
||||
return ElectricPotential::new::<millivolt>(wdg.calibrated_vdda as f32)
|
||||
* ((MAX_SAMPLE as u32 - wdg.offset) as f32)
|
||||
/ MAX_SAMPLE as f32;
|
||||
}
|
||||
ElectricPotential::zero()
|
||||
}
|
||||
|
||||
pub fn pwr_on_and_arm_protection() {
|
||||
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
||||
if !wdg.alarm_status.pwr_excursion {
|
||||
wdg.alarm_status = Status::default();
|
||||
LdPwrExcProtector::clear_interrupt_bit();
|
||||
LdPwrExcProtector::pwr_on();
|
||||
// Interrupt should be enabled after power on to tackle the following edge case:
|
||||
// Pd_Mon pin voltage has already exceed threshold before LD Power is on.
|
||||
LdPwrExcProtector::enable_watchdog_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_alarm_status() {
|
||||
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
||||
wdg.alarm_status.pwr_excursion = false;
|
||||
wdg.alarm_status.v_tripped = ElectricPotential::new::<millivolt>(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn enable_watchdog_interrupt() {
|
||||
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
||||
wdg.pac.cr1.modify(|_, w| w.awdie().set_bit());
|
||||
}
|
||||
}
|
||||
|
||||
fn disable_watchdog_interrupt() {
|
||||
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
||||
wdg.pac.cr1.modify(|_, w| w.awdie().clear_bit());
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_interrupt_bit() {
|
||||
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
||||
wdg.pac.sr.modify(|_, w| w.awd().clear_bit());
|
||||
}
|
||||
}
|
||||
|
||||
fn pwr_on() {
|
||||
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
||||
wdg.alarm_status.pwr_engaged = true;
|
||||
wdg.phy.pwr_en_ch0.set_high()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pwr_off() {
|
||||
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
||||
wdg.alarm_status.pwr_engaged = false;
|
||||
wdg.phy.pwr_en_ch0.set_low();
|
||||
LdPwrExcProtector::disable_watchdog_interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
fn pwr_excursion_handler() {
|
||||
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
||||
let sample = wdg.pac.dr.read().data().bits();
|
||||
LdPwrExcProtector::pwr_off();
|
||||
wdg.alarm_status.pwr_excursion = true;
|
||||
wdg.alarm_status.v_tripped = LdPwrExcProtector::convert_sample_to_volt(sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[interrupt]
|
||||
fn ADC() {
|
||||
cortex_m::interrupt::free(|_| {
|
||||
LdPwrExcProtector::pwr_excursion_handler();
|
||||
// Disable interrupt to avoid getting stuck in infinite loop
|
||||
LdPwrExcProtector::disable_watchdog_interrupt();
|
||||
LdPwrExcProtector::clear_interrupt_bit();
|
||||
})
|
||||
}
|
53
src/laser_diode/max5719.rs
Normal file
53
src/laser_diode/max5719.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use fugit::MegahertzU32;
|
||||
use stm32f4xx_hal::{hal::{digital::OutputPin, spi::SpiBus},
|
||||
spi};
|
||||
|
||||
use crate::device::sys_timer::sleep;
|
||||
|
||||
pub const SPI_MODE: spi::Mode = spi::Mode {
|
||||
polarity: spi::Polarity::IdleLow,
|
||||
phase: spi::Phase::CaptureOnFirstTransition,
|
||||
};
|
||||
// MAX5719 Max Frequency: 50MHz | SPI2 Max Frequency: 21MHz
|
||||
pub const SPI_CLOCK_MHZ: MegahertzU32 = MegahertzU32::from_raw(21);
|
||||
|
||||
pub const MAX_VALUE: u32 = 0xFFFFF;
|
||||
|
||||
pub struct Dac<SPI: SpiBus<u8>, S1: OutputPin, S2: OutputPin> {
|
||||
spi: SPI,
|
||||
cs_n: S1,
|
||||
load_n: S2,
|
||||
}
|
||||
|
||||
impl<SPI: SpiBus<u8>, S1: OutputPin, S2: OutputPin> Dac<SPI, S1, S2> {
|
||||
pub fn new(spi: SPI, mut cs_n: S1, mut load_n: S2) -> Self {
|
||||
let _ = cs_n.set_high();
|
||||
let _ = load_n.set_high();
|
||||
|
||||
Dac { spi, cs_n, load_n }
|
||||
}
|
||||
|
||||
fn write(&mut self, buf: &mut [u8]) -> Result<(), SPI::Error> {
|
||||
let _ = self.cs_n.set_low();
|
||||
self.spi.write(buf)?;
|
||||
let _ = self.cs_n.set_high();
|
||||
sleep(1);
|
||||
// must be high for >= 20 ns
|
||||
let _ = self.load_n.set_low();
|
||||
// must be low for >= 20 ns
|
||||
sleep(1);
|
||||
let _ = self.load_n.set_high();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set(&mut self, value: u32) -> Result<u32, SPI::Error> {
|
||||
let value = value.min(MAX_VALUE) << 4;
|
||||
let mut buf = [
|
||||
((value >> 16) & 0xFF) as u8,
|
||||
((value >> 8) & 0xFF) as u8,
|
||||
((value >> 0) & 0xFF) as u8,
|
||||
];
|
||||
self.write(&mut buf)?;
|
||||
Ok(value)
|
||||
}
|
||||
}
|
@ -1 +1,6 @@
|
||||
pub mod current_sources;
|
||||
pub mod laser_diode;
|
||||
pub mod ld_ctrl;
|
||||
pub mod ld_current_out_ctrl_timer;
|
||||
pub mod ld_pwr_exc_protector;
|
||||
pub mod max5719;
|
||||
pub mod pd_mon_params;
|
||||
|
73
src/laser_diode/pd_mon_params.rs
Normal file
73
src/laser_diode/pd_mon_params.rs
Normal file
@ -0,0 +1,73 @@
|
||||
use core::{f32::NAN, marker::PhantomData};
|
||||
|
||||
use miniconf::Tree;
|
||||
use num_traits::Zero;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uom::{si::{electric_current::microampere,
|
||||
f32::{ElectricCurrent, ElectricPotential, ElectricalConductance, Power},
|
||||
Quantity, ISQ, SI},
|
||||
typenum::*};
|
||||
|
||||
// Ampere / Watt
|
||||
pub type ResponsitivityUnit = Quantity<ISQ<N2, N1, P3, P1, Z0, Z0, Z0>, SI<f32>, f32>;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq, Tree)]
|
||||
pub struct Parameters {
|
||||
pub transconductance: ElectricalConductance,
|
||||
responsitivity: ResponsitivityUnit,
|
||||
i_dark: ElectricCurrent,
|
||||
}
|
||||
|
||||
impl Parameters {
|
||||
pub fn get_ld_pwr_from_ld_i(&self, i: ElectricCurrent) -> Power {
|
||||
let ld_power = (i - self.i_dark) / self.responsitivity;
|
||||
ld_power
|
||||
}
|
||||
|
||||
pub fn get_pd_i_from_ld_pwr(&self, pwr: Power) -> ElectricCurrent {
|
||||
let ld_i = pwr * self.responsitivity + self.i_dark;
|
||||
ld_i
|
||||
}
|
||||
|
||||
pub fn get_pd_i_from_pd_v(&self, v: ElectricPotential) -> ElectricCurrent {
|
||||
(v * self.transconductance).max(ElectricCurrent::zero())
|
||||
}
|
||||
|
||||
pub fn set_responsitivity(&mut self, responsitivity: ResponsitivityUnit) {
|
||||
self.responsitivity = responsitivity;
|
||||
}
|
||||
|
||||
pub fn get_responsitivity(&mut self) -> ResponsitivityUnit {
|
||||
self.responsitivity
|
||||
}
|
||||
|
||||
pub fn set_i_dark(&mut self, i_dark: ElectricCurrent) {
|
||||
self.i_dark = i_dark;
|
||||
}
|
||||
|
||||
pub fn get_i_dark(&mut self) -> ElectricCurrent {
|
||||
self.i_dark
|
||||
}
|
||||
|
||||
pub fn set_transconductance(&mut self, transconductance: ElectricalConductance) {
|
||||
self.transconductance = transconductance;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Parameters {
|
||||
fn default() -> Self {
|
||||
Parameters {
|
||||
transconductance: ElectricalConductance {
|
||||
dimension: PhantomData,
|
||||
units: PhantomData,
|
||||
value: 1.0 / 1000.0,
|
||||
},
|
||||
responsitivity: ResponsitivityUnit {
|
||||
dimension: PhantomData,
|
||||
units: PhantomData,
|
||||
value: NAN,
|
||||
},
|
||||
i_dark: ElectricCurrent::new::<microampere>(0.0),
|
||||
}
|
||||
}
|
||||
}
|
331
src/main.rs
331
src/main.rs
@ -1,44 +1,343 @@
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
#![cfg_attr(not(test), no_main)]
|
||||
#![cfg_attr(not(test), no_std)]
|
||||
|
||||
use core::{marker::PhantomData, u32};
|
||||
|
||||
use cortex_m_rt::entry;
|
||||
use log::info;
|
||||
use log::{debug, info};
|
||||
use stm32f4xx_hal::pac::{CorePeripherals, Peripherals};
|
||||
|
||||
use uom::si::f32::ElectricalConductance;
|
||||
mod device;
|
||||
mod laser_diode;
|
||||
use device::{boot::bootup, log_setup, sys_timer};
|
||||
mod net;
|
||||
mod thermostat;
|
||||
|
||||
use core::ptr::addr_of_mut;
|
||||
|
||||
use device::{boot::bootup, log_setup, sys_timer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use stm32f4xx_hal::pac::SCB;
|
||||
// If RTT is used, print panic info through RTT
|
||||
#[cfg(feature = "RTT")]
|
||||
use {
|
||||
core::panic::PanicInfo,
|
||||
rtt_target::rprintln,
|
||||
};
|
||||
#[cfg(feature = "RTT")]
|
||||
#[cfg(all(feature = "RTT", not(test)))]
|
||||
use {core::panic::PanicInfo, rtt_target::rprintln};
|
||||
|
||||
use crate::net::net::IpSettings;
|
||||
|
||||
#[cfg(all(feature = "RTT", not(test)))]
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
rprintln!("{}", info);
|
||||
loop {}
|
||||
}
|
||||
// Otherwise use panic halt
|
||||
#[cfg(not(feature = "RTT"))]
|
||||
#[cfg(all(not(feature = "RTT"), not(test)))]
|
||||
use panic_halt as _;
|
||||
|
||||
static mut ETH_DATA_BUFFER: [u8; 1024] = [0; 1024];
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Copy, Debug)]
|
||||
pub struct DeviceSettings {
|
||||
ip_settings: IpSettings,
|
||||
pd_mon_fin_gain: f32,
|
||||
pd_mon_transconductance: ElectricalConductance,
|
||||
}
|
||||
|
||||
const CONFIG_KEY: [&str; 3] = ["Device", "Laser_0", "Thermostat_0"];
|
||||
|
||||
#[derive(Default)]
|
||||
pub enum State {
|
||||
#[default]
|
||||
LoadFlashSettings,
|
||||
MainLoop,
|
||||
SaveLdThermostatSettings,
|
||||
SaveDeviceSettings,
|
||||
PrepareForHardReset,
|
||||
PrepareForDfu,
|
||||
HardReset,
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
|
||||
log_setup::init_log();
|
||||
info!("Kirdy init");
|
||||
|
||||
let core_perif = CorePeripherals::take().unwrap();
|
||||
let perif = Peripherals::take().unwrap();
|
||||
|
||||
let mut wd = bootup(core_perif, perif);
|
||||
let (mut wd, mut flash_store, mut hw_rev, mut laser, mut thermostat) = bootup(core_perif, perif);
|
||||
|
||||
let mut device_settings = DeviceSettings {
|
||||
ip_settings: IpSettings::default(),
|
||||
pd_mon_fin_gain: 1.0,
|
||||
pd_mon_transconductance: ElectricalConductance {
|
||||
dimension: PhantomData,
|
||||
units: PhantomData,
|
||||
value: 1.0 / 1000.0,
|
||||
},
|
||||
};
|
||||
|
||||
let mut active_report: [bool; net::net::NUM_OF_SOCKETS] = [false; net::net::NUM_OF_SOCKETS];
|
||||
|
||||
let mut state = State::default();
|
||||
|
||||
let eth_data_buffer = unsafe { addr_of_mut!(ETH_DATA_BUFFER).as_mut().unwrap() };
|
||||
|
||||
let mut sock_ts: [u32; net::net::NUM_OF_SOCKETS] = [0; net::net::NUM_OF_SOCKETS];
|
||||
|
||||
loop {
|
||||
wd.feed();
|
||||
info!("looping");
|
||||
sys_timer::sleep(10);
|
||||
|
||||
if net::net::eth_poll_link_status_and_update_link_speed() {
|
||||
active_report = [false; net::net::NUM_OF_SOCKETS];
|
||||
}
|
||||
|
||||
match state {
|
||||
State::LoadFlashSettings => {
|
||||
// State Transition
|
||||
state = State::MainLoop;
|
||||
|
||||
wd.feed();
|
||||
let device_settings_flash: DeviceSettings;
|
||||
match flash_store.read_value(CONFIG_KEY[0]) {
|
||||
Ok(Some(config)) => {
|
||||
device_settings_flash = config;
|
||||
laser.set_pd_transconductance(config.pd_mon_fin_gain * config.pd_mon_transconductance);
|
||||
debug!("Found Device Settings");
|
||||
device_settings = device_settings_flash;
|
||||
}
|
||||
Ok(None) => {
|
||||
info!("Flash does not have Device Settings");
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Cannot Store Flash: {:?}", e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
wd.feed();
|
||||
let laser_settings: laser_diode::laser_diode::LdSettingsSummary;
|
||||
match flash_store.read_value(CONFIG_KEY[1]) {
|
||||
Ok(Some(config)) => {
|
||||
laser_settings = config;
|
||||
debug!("Found Laser Diode Settings");
|
||||
}
|
||||
Ok(None) => {
|
||||
debug!("Does not have laser diode Settings");
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Cannot Store Flash: {:?}", e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
wd.feed();
|
||||
let thermostat_settings: thermostat::thermostat::ThermostatSettingsSummary;
|
||||
match flash_store.read_value(CONFIG_KEY[2]) {
|
||||
Ok(Some(config)) => {
|
||||
thermostat_settings = config;
|
||||
debug!("Found Thermostat Settings");
|
||||
}
|
||||
Ok(None) => {
|
||||
debug!("Does not have thermostat Settings");
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Cannot Store Flash: {:?}", e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
wd.feed();
|
||||
thermostat.load_settings_from_summary(thermostat_settings);
|
||||
laser.load_settings_from_summary(laser_settings);
|
||||
}
|
||||
State::MainLoop => {
|
||||
laser.poll_and_update_output_current();
|
||||
laser.poll_pd_mon_v();
|
||||
|
||||
net::net::eth_update_iface_poll_timer();
|
||||
|
||||
if thermostat.poll_adc() {
|
||||
thermostat.update_pid();
|
||||
if thermostat.get_temp_mon_status().over_temp_alarm {
|
||||
laser.power_down();
|
||||
thermostat.power_down();
|
||||
}
|
||||
|
||||
net::net::for_each(|mut socket, id| {
|
||||
if net::net::eth_is_socket_active(socket, true) && net::net::eth_is_socket_connected(socket) {
|
||||
if active_report[id] {
|
||||
if net::net::eth_can_sock_send(socket) {
|
||||
net::cmd_handler::send_status_report(
|
||||
eth_data_buffer,
|
||||
&mut laser,
|
||||
&mut thermostat,
|
||||
&mut socket,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
active_report[id] = false;
|
||||
}
|
||||
});
|
||||
|
||||
thermostat.start_tec_readings_conversion();
|
||||
}
|
||||
|
||||
let mut num_of_connected_sock: u8 = 0;
|
||||
let mut oldest_connected_sock_ts: u32 = u32::MAX;
|
||||
let mut oldest_connected_sock_id: usize = 0;
|
||||
|
||||
net::net::for_each(|mut socket, id| {
|
||||
if net::net::eth_is_socket_active(socket, true) && net::net::eth_is_socket_connected(socket) {
|
||||
if net::net::eth_can_sock_recv(socket) && net::net::eth_can_sock_send(socket) {
|
||||
let bytes = net::net::eth_recv(eth_data_buffer, socket);
|
||||
if bytes != 0 {
|
||||
info!("Ts: {:?}", sys_timer::now());
|
||||
debug!("Number of bytes recv: {:?}", bytes);
|
||||
// State Transition
|
||||
net::cmd_handler::execute_cmd(
|
||||
eth_data_buffer,
|
||||
bytes,
|
||||
&mut socket,
|
||||
&mut hw_rev,
|
||||
&mut laser,
|
||||
&mut thermostat,
|
||||
&mut state,
|
||||
&mut device_settings,
|
||||
&mut active_report[id],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
num_of_connected_sock += 1;
|
||||
|
||||
if sock_ts[id] == 0 {
|
||||
sock_ts[id] = sys_timer::now();
|
||||
}
|
||||
if oldest_connected_sock_ts > sock_ts[id] {
|
||||
oldest_connected_sock_ts = sock_ts[id];
|
||||
oldest_connected_sock_id = id;
|
||||
}
|
||||
} else {
|
||||
sock_ts[id] = 0;
|
||||
}
|
||||
});
|
||||
|
||||
if num_of_connected_sock == net::net::NUM_OF_SOCKETS as u8 {
|
||||
let mut sock_handle = net::net::eth_get_sock_handle(oldest_connected_sock_id);
|
||||
net::cmd_handler::send_response(
|
||||
eth_data_buffer,
|
||||
net::cmd_handler::ResponseEnum::ConnectionClose,
|
||||
None,
|
||||
&mut sock_handle,
|
||||
);
|
||||
net::net::eth_poll_iface();
|
||||
debug!("Waiting for ConnectionClose msg to be sent");
|
||||
while !net::net::eth_is_data_sent() { }
|
||||
net::net::eth_close_socket_by_id(oldest_connected_sock_id);
|
||||
debug!("Closing socket id: {:?}", oldest_connected_sock_id);
|
||||
}
|
||||
}
|
||||
State::SaveLdThermostatSettings => {
|
||||
// State Transition
|
||||
state = State::MainLoop;
|
||||
|
||||
wd.feed();
|
||||
let mut store_value_buf = [0u8; 1024];
|
||||
match flash_store.write_value(CONFIG_KEY[1], &laser.get_settings_summary(), &mut store_value_buf) {
|
||||
Ok(()) => {
|
||||
debug!("Laser Diode Settings is stored in flash");
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Cannot Store Flash: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
wd.feed();
|
||||
match flash_store.write_value(CONFIG_KEY[2], &thermostat.get_settings_summary(), &mut store_value_buf) {
|
||||
Ok(()) => {
|
||||
debug!("Thermostat Settings is stored in flash");
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Cannot Store Flash: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
State::SaveDeviceSettings => {
|
||||
// State Transition
|
||||
state = State::MainLoop;
|
||||
|
||||
wd.feed();
|
||||
let mut store_value_buf = [0u8; 1024];
|
||||
match flash_store.write_value(CONFIG_KEY[0], &device_settings, &mut store_value_buf) {
|
||||
Ok(()) => {
|
||||
debug!("Device Settings is stored in flash");
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Cannot Store Flash: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
State::PrepareForHardReset => {
|
||||
// State Transition
|
||||
state = State::HardReset;
|
||||
|
||||
wd.feed();
|
||||
laser.power_down();
|
||||
thermostat.power_down();
|
||||
net::net::for_each(|mut socket, _| {
|
||||
if net::net::eth_is_socket_active(socket, false) {
|
||||
net::cmd_handler::send_response(
|
||||
eth_data_buffer,
|
||||
net::cmd_handler::ResponseEnum::HardReset,
|
||||
None,
|
||||
&mut socket,
|
||||
);
|
||||
net::net::eth_poll_iface();
|
||||
}
|
||||
});
|
||||
}
|
||||
State::PrepareForDfu => {
|
||||
// State Transition
|
||||
state = State::HardReset;
|
||||
|
||||
wd.feed();
|
||||
laser.power_down();
|
||||
thermostat.power_down();
|
||||
net::net::for_each(|mut socket, _| {
|
||||
if net::net::eth_is_socket_active(socket, false) {
|
||||
net::cmd_handler::send_response(
|
||||
eth_data_buffer,
|
||||
net::cmd_handler::ResponseEnum::Dfu,
|
||||
None,
|
||||
&mut socket,
|
||||
);
|
||||
net::net::eth_poll_iface();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
State::HardReset => {
|
||||
wd.feed();
|
||||
laser.power_down();
|
||||
thermostat.power_down();
|
||||
let mut any_socket_alive = false;
|
||||
net::net::for_each(|socket, _| {
|
||||
if net::net::eth_is_socket_active(socket, false) {
|
||||
net::net::eth_close_socket(socket);
|
||||
any_socket_alive = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Must let loop run for one more cycle to poll server for RST to be sent,
|
||||
// this makes sure system does not reset right after socket.abort() is called.
|
||||
if !any_socket_alive {
|
||||
SCB::sys_reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
895
src/net/cmd_handler.rs
Normal file
895
src/net/cmd_handler.rs
Normal file
@ -0,0 +1,895 @@
|
||||
use core::{fmt::Debug, marker::PhantomData};
|
||||
|
||||
use log::{debug, info};
|
||||
use miniconf::{JsonCoreSlash, Tree};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smoltcp::iface::SocketHandle;
|
||||
use uom::si::{electric_current::{ampere, ElectricCurrent},
|
||||
electric_potential::{volt, ElectricPotential},
|
||||
electrical_conductance::{siemens, ElectricalConductance},
|
||||
electrical_resistance::{ohm, ElectricalResistance},
|
||||
power::{watt, Power}};
|
||||
|
||||
use crate::{device::{dfu, hw_rev::HWRev, sys_timer},
|
||||
laser_diode::{laser_diode::{LdDrive, LdSettingsSummary, StatusReport as LdStatusReport},
|
||||
pd_mon_params::{self, ResponsitivityUnit}},
|
||||
net::net,
|
||||
thermostat::{ad7172::FilterType,
|
||||
pid_state::PidSettings::*,
|
||||
thermostat::{StatusReport as TecStatusReport, TempAdcFilter, Thermostat,
|
||||
ThermostatSettingsSummary, Time}},
|
||||
DeviceSettings, IpSettings, State};
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Default, Debug)]
|
||||
pub enum ResponseEnum {
|
||||
#[default]
|
||||
Reserved,
|
||||
Settings,
|
||||
Report,
|
||||
HwRev,
|
||||
Interval,
|
||||
Acknowledge,
|
||||
InvalidDatatype,
|
||||
InvalidSettings,
|
||||
InvalidCmd,
|
||||
HardReset,
|
||||
Dfu,
|
||||
ConnectionClose,
|
||||
}
|
||||
|
||||
pub type MsgType = Option<&'static str>;
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug)]
|
||||
pub struct Response<'a> {
|
||||
msg_type: ResponseEnum,
|
||||
msg: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl Default for Response<'static> {
|
||||
fn default() -> Self {
|
||||
Response {
|
||||
msg_type: ResponseEnum::Reserved,
|
||||
msg: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct ResponseObj<'a> {
|
||||
#[serde(borrow)]
|
||||
json: Response<'a>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Default, Debug)]
|
||||
enum DeviceCmd {
|
||||
#[default]
|
||||
Reserved,
|
||||
GetHwRev,
|
||||
SetIPSettings,
|
||||
SetActiveReportMode,
|
||||
SetPdFinGain,
|
||||
SetPdTransconductance,
|
||||
GetStatusReport,
|
||||
GetSettingsSummary,
|
||||
Dfu,
|
||||
SaveFlashSettings,
|
||||
LoadFlashSettings,
|
||||
HardReset,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Default, Debug)]
|
||||
enum LdCmdEnum {
|
||||
#[default]
|
||||
Reserved,
|
||||
// LD Drive Related
|
||||
SetDefaultPowerOn,
|
||||
PowerUp,
|
||||
PowerDown,
|
||||
LdTermsShort,
|
||||
LdTermsOpen,
|
||||
SetI,
|
||||
// PD Mon Related
|
||||
SetPdResponsitivity,
|
||||
SetPdDarkCurrent,
|
||||
ApplyPdParams,
|
||||
SetLdPwrLimit,
|
||||
ClearAlarm,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Default, Debug)]
|
||||
enum ThermostatCmdEnum {
|
||||
#[default]
|
||||
Reserved,
|
||||
SetDefaultPowerOn,
|
||||
PowerUp,
|
||||
PowerDown,
|
||||
// TEC
|
||||
SetTecMaxV,
|
||||
SetTecMaxIPos,
|
||||
SetTecMaxINeg,
|
||||
SetTecIOut, // Constant Current Mode
|
||||
SetTemperatureSetpoint,
|
||||
// PID
|
||||
SetPidEngage,
|
||||
SetPidDisEngage,
|
||||
SetPidKp,
|
||||
SetPidKi,
|
||||
SetPidKd,
|
||||
SetPidOutMin,
|
||||
SetPidOutMax,
|
||||
SetPidUpdateInterval, // Update Interval is set based on the sampling rate of ADC
|
||||
// Temperature ADC
|
||||
ConfigTempAdcFilter,
|
||||
GetPollInterval,
|
||||
// TempMon
|
||||
SetTempMonUpperLimit,
|
||||
SetTempMonLowerLimit,
|
||||
ClearAlarm,
|
||||
// Steinhart-Hart Equation
|
||||
SetShT0,
|
||||
SetShR0,
|
||||
SetShBeta,
|
||||
}
|
||||
|
||||
const ERR_MSG_MISSING_DATA_F32: &str = "Required field \"data_f32\" does not exist";
|
||||
const ERR_MSG_MISSING_DATA_BOOL: &str = "Required field \"bool\" does not exist";
|
||||
const ERR_MSG_MISSING_IP_SETTINGS: &str = "Required field \"ip_settings\" does not exist";
|
||||
const ERR_MSG_MISSING_TEMP_ADC_FILTER: &str = "Required field \"temp_adc_filter\" does not exist";
|
||||
const ERR_MSG_MISSING_SINC5SINC1ODR: &str = "Required field \"sinc5sinc1odr\" does not exist";
|
||||
const ERR_MSG_MISSING_SINC3ODR: &str = "Required field \"sinc3odr\" does not exist";
|
||||
const ERR_MSG_MISSING_POSTFILTER: &str = "Required field \"PostFilter\" does not exist";
|
||||
const ERR_MSG_MISSING_SINC3FINEODR: &str = "Required field \"sinc3fineodr\" does not exist";
|
||||
const ERR_MSG_INVALID_PDMON_SETTINGS: &str = "Invalid PD Mon Parameter Setting(s)";
|
||||
const ERR_MSG_INVALID_LD_PWR_LIMIT_SETTING: &str = "Invalid LD Power Limit Setting";
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Default, Tree)]
|
||||
pub struct CmdJsonObj {
|
||||
laser_diode_cmd: Option<LdCmdEnum>,
|
||||
thermostat_cmd: Option<ThermostatCmdEnum>,
|
||||
device_cmd: Option<DeviceCmd>,
|
||||
data_bool: Option<bool>,
|
||||
data_f32: Option<f32>,
|
||||
data_f64: Option<f64>,
|
||||
temp_adc_filter: Option<TempAdcFilter>,
|
||||
ip_settings: Option<IpSettings>,
|
||||
}
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Default, Tree)]
|
||||
pub struct Cmd {
|
||||
json: CmdJsonObj,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct StatusReport {
|
||||
ts: u32,
|
||||
msg_type: ResponseEnum,
|
||||
laser: LdStatusReport,
|
||||
thermostat: TecStatusReport,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct StatusReportObj {
|
||||
json: StatusReport,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct SettingsSummary {
|
||||
msg_type: ResponseEnum,
|
||||
laser: LdSettingsSummary,
|
||||
thermostat: ThermostatSettingsSummary,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct SettingsSummaryObj {
|
||||
json: SettingsSummary,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct IntervalType {
|
||||
msg_type: ResponseEnum,
|
||||
interval: Time,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct IntervalObj {
|
||||
json: IntervalType,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct HwRevType {
|
||||
msg_type: ResponseEnum,
|
||||
hw_rev: HWRev,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct HwRevObj {
|
||||
json: HwRevType,
|
||||
}
|
||||
|
||||
pub fn send_response(buffer: &mut [u8], msg_type: ResponseEnum, msg: MsgType, socket: &mut SocketHandle) {
|
||||
let response = ResponseObj {
|
||||
json: Response {
|
||||
msg_type: msg_type,
|
||||
msg: msg,
|
||||
},
|
||||
};
|
||||
debug!("{:?}", response.json);
|
||||
|
||||
let mut num_bytes = response.get_json("/json", buffer).unwrap();
|
||||
buffer[num_bytes] = b'\n';
|
||||
num_bytes += 1;
|
||||
net::eth_send(buffer, num_bytes, *socket);
|
||||
}
|
||||
|
||||
pub fn send_settings_summary(
|
||||
buffer: &mut [u8],
|
||||
laser: &mut LdDrive,
|
||||
thermostat: &mut Thermostat,
|
||||
socket: &mut SocketHandle,
|
||||
) {
|
||||
let settings_summary = SettingsSummaryObj {
|
||||
json: SettingsSummary {
|
||||
msg_type: ResponseEnum::Settings,
|
||||
laser: laser.get_settings_summary(),
|
||||
thermostat: thermostat.get_settings_summary(),
|
||||
},
|
||||
};
|
||||
let mut num_bytes = settings_summary.get_json("/json", buffer).unwrap();
|
||||
buffer[num_bytes] = b'\n';
|
||||
num_bytes += 1;
|
||||
net::eth_send(buffer, num_bytes, *socket);
|
||||
}
|
||||
|
||||
pub fn send_status_report(
|
||||
buffer: &mut [u8],
|
||||
laser: &mut LdDrive,
|
||||
thermostat: &mut Thermostat,
|
||||
socket: &mut SocketHandle,
|
||||
) {
|
||||
let status_report = StatusReportObj {
|
||||
json: StatusReport {
|
||||
ts: sys_timer::now(),
|
||||
msg_type: ResponseEnum::Report,
|
||||
laser: laser.get_status_report(),
|
||||
thermostat: thermostat.get_status_report(),
|
||||
},
|
||||
};
|
||||
let mut num_bytes = status_report.get_json("/json", buffer).unwrap();
|
||||
buffer[num_bytes] = b'\n';
|
||||
num_bytes += 1;
|
||||
net::eth_send(buffer, num_bytes, *socket);
|
||||
}
|
||||
|
||||
pub fn send_hw_rev(buffer: &mut [u8], hw_rev_o: &mut HWRev, socket: &mut SocketHandle) {
|
||||
let hw_rev = HwRevObj {
|
||||
json: HwRevType {
|
||||
msg_type: ResponseEnum::HwRev,
|
||||
hw_rev: *hw_rev_o,
|
||||
},
|
||||
};
|
||||
let mut num_bytes = hw_rev.get_json("/json", buffer).unwrap();
|
||||
buffer[num_bytes] = b'\n';
|
||||
num_bytes += 1;
|
||||
net::eth_send(buffer, num_bytes, *socket);
|
||||
}
|
||||
|
||||
pub fn send_interval(buffer: &mut [u8], interval: Time, socket: &mut SocketHandle) {
|
||||
let hw_rev = IntervalObj {
|
||||
json: IntervalType {
|
||||
msg_type: ResponseEnum::Interval,
|
||||
interval: interval,
|
||||
},
|
||||
};
|
||||
let mut num_bytes = hw_rev.get_json("/json", buffer).unwrap();
|
||||
buffer[num_bytes] = b'\n';
|
||||
num_bytes += 1;
|
||||
net::eth_send(buffer, num_bytes, *socket);
|
||||
}
|
||||
|
||||
// Use a minimal struct for high speed cmd ctrl to reduce processing overhead
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Default, Tree)]
|
||||
pub struct TecSetICmdJson {
|
||||
tec_set_i: f32,
|
||||
}
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Default, Tree)]
|
||||
pub struct TecSetICmd {
|
||||
json: TecSetICmdJson,
|
||||
}
|
||||
|
||||
/// Miniconf is very slow in debug builds (~3-4ms of cmd decoding time).
|
||||
/// Make sure kirdy's firmware is flashed with release builds.
|
||||
/// The received message must contain only one json cmd. TCP client should set TCP_NODELAY or equivalent flag in its TCP Socket
|
||||
/// Settings to avoid unwanted buffering on TX Data and minimize TX latency.
|
||||
pub fn execute_cmd(
|
||||
buffer: &mut [u8],
|
||||
buffer_size: usize,
|
||||
socket: &mut SocketHandle,
|
||||
hw_rev: &mut HWRev,
|
||||
laser: &mut LdDrive,
|
||||
thermostat: &mut Thermostat,
|
||||
state: &mut State,
|
||||
device_settings: &mut DeviceSettings,
|
||||
active_report: &mut bool,
|
||||
) {
|
||||
let mut cmd = TecSetICmd {
|
||||
json: TecSetICmdJson::default(),
|
||||
};
|
||||
match cmd.set_json("/json", &buffer[0..buffer_size]) {
|
||||
Ok(_) => {
|
||||
thermostat.set_i(ElectricCurrent::new::<ampere>(cmd.json.tec_set_i));
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
return;
|
||||
}
|
||||
Err(_) => { /* Do Nothing */ }
|
||||
}
|
||||
|
||||
let mut cmd = Cmd {
|
||||
json: CmdJsonObj::default(),
|
||||
};
|
||||
match cmd.set_json("/json", &buffer[0..buffer_size]) {
|
||||
Ok(_) => {
|
||||
info!(
|
||||
"############ Laser Diode Command Received {:?}",
|
||||
cmd.json.laser_diode_cmd
|
||||
);
|
||||
info!("############ Thermostat Command Received {:?}", cmd.json.thermostat_cmd);
|
||||
info!("############ Device Command Received {:?}", cmd.json.device_cmd);
|
||||
|
||||
match cmd.json.device_cmd {
|
||||
Some(DeviceCmd::SetIPSettings) => match cmd.json.ip_settings {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
device_settings.ip_settings = val;
|
||||
*state = State::SaveDeviceSettings;
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_IP_SETTINGS),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(DeviceCmd::Dfu) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
unsafe {
|
||||
dfu::set_dfu_trigger();
|
||||
}
|
||||
net::eth_poll_iface();
|
||||
*state = State::PrepareForDfu;
|
||||
}
|
||||
Some(DeviceCmd::SetActiveReportMode) => match cmd.json.data_bool {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
*active_report = val;
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_BOOL),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(DeviceCmd::SetPdFinGain) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
device_settings.pd_mon_fin_gain = val;
|
||||
laser.set_pd_transconductance(
|
||||
device_settings.pd_mon_fin_gain * device_settings.pd_mon_transconductance,
|
||||
);
|
||||
*state = State::SaveDeviceSettings;
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(DeviceCmd::SetPdTransconductance) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
device_settings.pd_mon_transconductance = ElectricalConductance::new::<siemens>(val);
|
||||
laser.set_pd_transconductance(
|
||||
device_settings.pd_mon_fin_gain * device_settings.pd_mon_transconductance,
|
||||
);
|
||||
*state = State::SaveDeviceSettings;
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(DeviceCmd::GetHwRev) => {
|
||||
send_hw_rev(buffer, hw_rev, socket);
|
||||
}
|
||||
Some(DeviceCmd::GetStatusReport) => {
|
||||
send_status_report(buffer, laser, thermostat, socket);
|
||||
}
|
||||
Some(DeviceCmd::GetSettingsSummary) => {
|
||||
send_settings_summary(buffer, laser, thermostat, socket);
|
||||
}
|
||||
Some(DeviceCmd::SaveFlashSettings) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
*state = State::SaveLdThermostatSettings;
|
||||
}
|
||||
Some(DeviceCmd::LoadFlashSettings) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
*state = State::LoadFlashSettings;
|
||||
}
|
||||
Some(DeviceCmd::HardReset) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
net::eth_poll_iface();
|
||||
*state = State::PrepareForHardReset;
|
||||
}
|
||||
None => { /* Do Nothing */ }
|
||||
_ => {
|
||||
send_response(buffer, ResponseEnum::InvalidCmd, None, socket);
|
||||
debug!("Unimplemented Command")
|
||||
}
|
||||
}
|
||||
|
||||
match cmd.json.laser_diode_cmd {
|
||||
Some(LdCmdEnum::SetDefaultPowerOn) => match cmd.json.data_bool {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
laser.set_default_pwr_on(val);
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_BOOL),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(LdCmdEnum::PowerUp) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
laser.power_up()
|
||||
}
|
||||
Some(LdCmdEnum::PowerDown) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
laser.power_down()
|
||||
}
|
||||
Some(LdCmdEnum::LdTermsShort) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
laser.ld_short();
|
||||
}
|
||||
Some(LdCmdEnum::LdTermsOpen) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
laser.ld_open();
|
||||
}
|
||||
Some(LdCmdEnum::SetI) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
laser.ld_set_i(ElectricCurrent::new::<ampere>(val));
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(LdCmdEnum::SetPdResponsitivity) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
laser.set_pd_responsitivity(ResponsitivityUnit {
|
||||
dimension: PhantomData,
|
||||
units: PhantomData,
|
||||
value: val,
|
||||
});
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(LdCmdEnum::SetPdDarkCurrent) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
laser.set_pd_dark_current(ElectricCurrent::new::<ampere>(val));
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(LdCmdEnum::ApplyPdParams) => {
|
||||
let is_legal = laser.apply_pd_params();
|
||||
if is_legal {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
} else {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidSettings,
|
||||
Some(ERR_MSG_INVALID_PDMON_SETTINGS),
|
||||
socket,
|
||||
)
|
||||
}
|
||||
}
|
||||
Some(LdCmdEnum::SetLdPwrLimit) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
let is_legal = laser.set_ld_power_limit(Power::new::<watt>(val));
|
||||
if is_legal {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
} else {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidSettings,
|
||||
Some(ERR_MSG_INVALID_LD_PWR_LIMIT_SETTING),
|
||||
socket,
|
||||
)
|
||||
}
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(LdCmdEnum::ClearAlarm) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
laser.pd_mon_clear_alarm()
|
||||
}
|
||||
None => { /* Do Nothing*/ }
|
||||
_ => {
|
||||
send_response(buffer, ResponseEnum::InvalidCmd, Some(ERR_MSG_MISSING_DATA_F32), socket);
|
||||
info!("Unimplemented Command")
|
||||
}
|
||||
}
|
||||
|
||||
match cmd.json.thermostat_cmd {
|
||||
Some(ThermostatCmdEnum::SetDefaultPowerOn) => match cmd.json.data_bool {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_default_pwr_on(val);
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(ThermostatCmdEnum::PowerUp) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.power_up()
|
||||
}
|
||||
Some(ThermostatCmdEnum::PowerDown) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.power_down()
|
||||
}
|
||||
Some(ThermostatCmdEnum::SetTecMaxV) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_max_v(ElectricPotential::new::<volt>(val));
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(ThermostatCmdEnum::SetTecMaxIPos) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_max_i_pos(ElectricCurrent::new::<ampere>(val));
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(ThermostatCmdEnum::SetTecMaxINeg) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_max_i_neg(ElectricCurrent::new::<ampere>(val));
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(ThermostatCmdEnum::SetTecIOut) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_i(ElectricCurrent::new::<ampere>(val));
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(ThermostatCmdEnum::SetTemperatureSetpoint) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_temperature_setpoint(val);
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(ThermostatCmdEnum::SetPidEngage) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_pid_engaged(true);
|
||||
}
|
||||
Some(ThermostatCmdEnum::SetPidDisEngage) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_pid_engaged(false);
|
||||
}
|
||||
Some(ThermostatCmdEnum::SetPidKp) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_pid(Kp, val);
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(ThermostatCmdEnum::SetPidKi) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_pid(Ki, val);
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(ThermostatCmdEnum::SetPidKd) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_pid(Kd, val);
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(ThermostatCmdEnum::SetPidOutMin) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_pid(Min, val);
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(ThermostatCmdEnum::SetPidOutMax) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_pid(Max, val);
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(ThermostatCmdEnum::SetPidUpdateInterval) => {
|
||||
send_response(buffer, ResponseEnum::InvalidCmd, None, socket);
|
||||
debug!("Not supported Yet")
|
||||
}
|
||||
Some(ThermostatCmdEnum::ConfigTempAdcFilter) => match cmd.json.temp_adc_filter {
|
||||
Some(val) => match val.filter_type {
|
||||
FilterType::Sinc5Sinc1With50hz60HzRejection => match val.sinc5sinc1postfilter {
|
||||
Some(val2) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_temp_adc_sinc5_sinc1_with_postfilter(0, val2);
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_POSTFILTER),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
FilterType::Sinc5Sinc1 => match val.sinc5sinc1odr {
|
||||
Some(val2) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_temp_adc_sinc5_sinc1_filter(0, val2);
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_SINC5SINC1ODR),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
FilterType::Sinc3WithFineODR => match val.sinc3fineodr {
|
||||
Some(val2) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_temp_adc_sinc3_fine_filter(0, val2);
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_SINC3FINEODR),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
FilterType::Sinc3 => match val.sinc3odr {
|
||||
Some(val2) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_temp_adc_sinc3_filter(0, val2);
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_SINC3ODR),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_TEMP_ADC_FILTER),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(ThermostatCmdEnum::GetPollInterval) => {
|
||||
send_interval(buffer, thermostat.get_poll_interval(), socket)
|
||||
}
|
||||
Some(ThermostatCmdEnum::SetTempMonUpperLimit) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_temp_mon_upper_limit(val);
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(ThermostatCmdEnum::SetTempMonLowerLimit) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_temp_mon_lower_limit(val);
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(ThermostatCmdEnum::ClearAlarm) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.clear_temp_mon_alarm();
|
||||
}
|
||||
Some(ThermostatCmdEnum::SetShT0) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_sh_t0(val);
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(ThermostatCmdEnum::SetShR0) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_sh_r0(ElectricalResistance::new::<ohm>(val));
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(ThermostatCmdEnum::SetShBeta) => match cmd.json.data_f32 {
|
||||
Some(val) => {
|
||||
send_response(buffer, ResponseEnum::Acknowledge, None, socket);
|
||||
thermostat.set_sh_beta(val);
|
||||
}
|
||||
None => {
|
||||
send_response(
|
||||
buffer,
|
||||
ResponseEnum::InvalidDatatype,
|
||||
Some(ERR_MSG_MISSING_DATA_F32),
|
||||
socket,
|
||||
);
|
||||
}
|
||||
},
|
||||
None => { /* Do Nothing*/ }
|
||||
_ => {
|
||||
send_response(buffer, ResponseEnum::InvalidCmd, None, socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
info!("cmd_recv: {:?}", buffer);
|
||||
send_response(buffer, ResponseEnum::InvalidCmd, None, socket);
|
||||
}
|
||||
}
|
||||
}
|
2
src/net/mod.rs
Normal file
2
src/net/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod cmd_handler;
|
||||
pub mod net;
|
668
src/net/net.rs
Normal file
668
src/net/net.rs
Normal file
@ -0,0 +1,668 @@
|
||||
use core::mem::{self, MaybeUninit};
|
||||
|
||||
use fugit::TimerDurationU32;
|
||||
use log::{debug, info};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smoltcp::{iface::{self, Interface, SocketHandle, SocketSet, SocketStorage},
|
||||
socket::tcp::{Socket, SocketBuffer, State},
|
||||
time::{Duration, Instant},
|
||||
wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv4Cidr}};
|
||||
use stm32_eth::{dma::{EthernetDMA, RxRingEntry, TxRingEntry},
|
||||
mac::{EthernetMACWithMii, Speed},
|
||||
EthPins, Parts, PartsIn};
|
||||
use stm32f4xx_hal::{gpio::{gpioa::*, gpiob::*, gpioc::*, Alternate, Input, Pin},
|
||||
pac::{interrupt, Interrupt, Peripherals, TIM5},
|
||||
rcc::Clocks,
|
||||
timer::{Counter, Event},
|
||||
Listen};
|
||||
|
||||
use crate::device::sys_timer;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
|
||||
pub struct IpSettings {
|
||||
addr: [u8; 4],
|
||||
port: u16,
|
||||
prefix_len: u8,
|
||||
gateway: [u8; 4],
|
||||
}
|
||||
|
||||
impl Default for IpSettings {
|
||||
fn default() -> Self {
|
||||
IpSettings {
|
||||
addr: [192, 168, 1, 128],
|
||||
port: 1337,
|
||||
prefix_len: 24,
|
||||
gateway: [192, 168, 1, 1],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static mut IFACE_TIMER: Option<IfacePollTimer> = None;
|
||||
|
||||
pub struct ServerHandle {
|
||||
socket_handles: [SocketHandle; NUM_OF_SOCKETS],
|
||||
socket_set: SocketSet<'static>,
|
||||
socket_addr: (IpAddress, u16),
|
||||
iface: EthInterface,
|
||||
dma: EthernetDMA<'static, 'static>,
|
||||
phy: EthernetPhy<EthernetMACWithMii<Pin<'A', 2, Alternate<11>>, Pin<'C', 1, Alternate<11>>>>,
|
||||
link_was_up: bool,
|
||||
data_sent: bool,
|
||||
}
|
||||
pub type EthernetPins = EthPins<PA1<Input>, PA7<Input>, PB11<Input>, PB12<Input>, PB13<Input>, PC4<Input>, PC5<Input>>;
|
||||
pub struct EthernetMgmtPins {
|
||||
pub mdio: PA2<Alternate<11>>,
|
||||
pub mdc: PC1<Alternate<11>>,
|
||||
}
|
||||
pub type EthInterface = Interface;
|
||||
|
||||
pub const NUM_OF_SOCKETS: usize = 4 + 1;
|
||||
const TCP_BUFFER_SIZE: usize = 4096;
|
||||
static mut RX_RING: Option<[RxRingEntry; 8]> = None;
|
||||
static mut TX_RING: Option<[TxRingEntry; 8]> = None;
|
||||
static mut SOCKET_STORAGE: Option<[SocketStorage<'static>; NUM_OF_SOCKETS]> = None;
|
||||
|
||||
fn now_fn() -> smoltcp::time::Instant {
|
||||
Instant::from_millis(i64::from(sys_timer::now()))
|
||||
}
|
||||
|
||||
static mut SERVER_HANDLE: Option<ServerHandle> = None;
|
||||
|
||||
pub const FREQ: u32 = 1000000;
|
||||
|
||||
impl ServerHandle {
|
||||
pub fn new(
|
||||
eth_pins: EthernetPins,
|
||||
eth_mgmt_pins: EthernetMgmtPins,
|
||||
ethernet_parts_in: PartsIn,
|
||||
clocks: Clocks,
|
||||
tim5: Counter<TIM5, FREQ>,
|
||||
mac_addr: [u8; 6],
|
||||
ip_settings: IpSettings,
|
||||
) {
|
||||
let rx_ring = unsafe { RX_RING.get_or_insert(Default::default()) };
|
||||
let tx_ring = unsafe { TX_RING.get_or_insert(Default::default()) };
|
||||
let socket_storage = unsafe { SOCKET_STORAGE.get_or_insert([SocketStorage::EMPTY; NUM_OF_SOCKETS]) };
|
||||
|
||||
let Parts { mut dma, mac, .. } = stm32_eth::new_with_mii(
|
||||
ethernet_parts_in,
|
||||
&mut rx_ring[..],
|
||||
&mut tx_ring[..],
|
||||
clocks,
|
||||
eth_pins,
|
||||
eth_mgmt_pins.mdio,
|
||||
eth_mgmt_pins.mdc,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let ip_init = IpCidr::Ipv4(Ipv4Cidr::new(
|
||||
Ipv4Address::new(
|
||||
ip_settings.addr[0],
|
||||
ip_settings.addr[1],
|
||||
ip_settings.addr[2],
|
||||
ip_settings.addr[3],
|
||||
),
|
||||
ip_settings.prefix_len,
|
||||
));
|
||||
let socket_addr: (IpAddress, u16) = (
|
||||
IpAddress::Ipv4(Ipv4Address::new(
|
||||
ip_settings.addr[0],
|
||||
ip_settings.addr[1],
|
||||
ip_settings.addr[2],
|
||||
ip_settings.addr[3],
|
||||
)),
|
||||
ip_settings.port,
|
||||
);
|
||||
|
||||
let mut routes = smoltcp::iface::Routes::new();
|
||||
routes
|
||||
.add_default_ipv4_route(Ipv4Address::new(
|
||||
ip_settings.gateway[0],
|
||||
ip_settings.gateway[1],
|
||||
ip_settings.gateway[2],
|
||||
ip_settings.gateway[3],
|
||||
))
|
||||
.ok();
|
||||
dma.enable_interrupt();
|
||||
|
||||
let config = iface::Config::new(EthernetAddress::from_bytes(&mac_addr).into());
|
||||
let mut iface = Interface::new(config, &mut &mut dma, smoltcp::time::Instant::ZERO);
|
||||
iface.set_hardware_addr(EthernetAddress(mac_addr).into());
|
||||
debug!("MAC ADDRESS: {:02X?}", EthernetAddress(mac_addr));
|
||||
debug!("IP Settings: {:?}", ip_settings);
|
||||
iface.update_ip_addrs(|addr| {
|
||||
addr.push(ip_init).unwrap();
|
||||
});
|
||||
|
||||
let mut socket_set = SocketSet::new(&mut socket_storage[..]);
|
||||
|
||||
let tcp_handles = {
|
||||
// Do not use NUM_OF_SOCKETS to define array size to
|
||||
// remind developers to create/remove tcp_handles accordingly after changing NUM_OF_SOCKETS
|
||||
let mut tcp_handles: [MaybeUninit<SocketHandle>; 5] = unsafe { MaybeUninit::uninit().assume_init() };
|
||||
|
||||
macro_rules! create_tcp_handle {
|
||||
($rx_storage:ident, $tx_storage:ident, $handle:expr) => {
|
||||
static mut $rx_storage: [u8; TCP_BUFFER_SIZE] = [0; TCP_BUFFER_SIZE];
|
||||
static mut $tx_storage: [u8; TCP_BUFFER_SIZE] = [0; TCP_BUFFER_SIZE];
|
||||
unsafe {
|
||||
let rx_buffer = SocketBuffer::new(&mut $rx_storage[..]);
|
||||
let tx_buffer = SocketBuffer::new(&mut $tx_storage[..]);
|
||||
$handle.write(socket_set.add(Socket::new(rx_buffer, tx_buffer)));
|
||||
}
|
||||
};
|
||||
}
|
||||
create_tcp_handle!(RX_STORAGE0, TX_STORAGE0, tcp_handles[0]);
|
||||
create_tcp_handle!(RX_STORAGE1, TX_STORAGE1, tcp_handles[1]);
|
||||
create_tcp_handle!(RX_STORAGE2, TX_STORAGE2, tcp_handles[2]);
|
||||
create_tcp_handle!(RX_STORAGE3, TX_STORAGE3, tcp_handles[3]);
|
||||
create_tcp_handle!(RX_STORAGE4, TX_STORAGE4, tcp_handles[4]);
|
||||
|
||||
unsafe { mem::transmute::<_, [SocketHandle; 5]>(tcp_handles) }
|
||||
};
|
||||
|
||||
for i in 0..NUM_OF_SOCKETS {
|
||||
let socket = socket_set.get_mut::<Socket>(tcp_handles[i]);
|
||||
socket.listen(socket_addr).ok();
|
||||
socket.set_timeout(Some(Duration::from_secs(5)));
|
||||
// Value from Linux net.ipv4.tcp_keepalive_time
|
||||
socket.set_keep_alive(Some(Duration::from_secs(7200)));
|
||||
socket.set_nagle_enabled(false);
|
||||
}
|
||||
|
||||
iface.poll(
|
||||
Instant::from_millis(i64::from(sys_timer::now())),
|
||||
&mut &mut dma,
|
||||
&mut socket_set,
|
||||
);
|
||||
|
||||
IfacePollTimer::setup(tim5);
|
||||
|
||||
if let Ok(mut phy) = EthernetPhy::from_miim(mac, 0) {
|
||||
info!("Resetting PHY as an extra step. Type: {}", phy.ident_string());
|
||||
|
||||
phy.phy_init();
|
||||
|
||||
let server = ServerHandle {
|
||||
socket_handles: tcp_handles,
|
||||
socket_set: socket_set,
|
||||
socket_addr: socket_addr,
|
||||
iface: iface,
|
||||
dma: dma,
|
||||
phy: phy,
|
||||
link_was_up: false,
|
||||
data_sent: true,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
SERVER_HANDLE = Some(server);
|
||||
}
|
||||
} else {
|
||||
panic!("Ethernet Phy is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_link_speed(&mut self) -> bool {
|
||||
if !self.link_was_up & self.phy.phy_link_up() {
|
||||
if let Some(speed) = self.phy.speed().map(|s| match s {
|
||||
PhySpeed::HalfDuplexBase10T => Speed::HalfDuplexBase10T,
|
||||
PhySpeed::FullDuplexBase10T => Speed::FullDuplexBase10T,
|
||||
PhySpeed::HalfDuplexBase100Tx => Speed::HalfDuplexBase100Tx,
|
||||
PhySpeed::FullDuplexBase100Tx => Speed::FullDuplexBase100Tx,
|
||||
}) {
|
||||
info!("New eth link is up. Setting detected PhySpeed: {:?}", speed);
|
||||
self.phy.get_miim().set_speed(speed);
|
||||
self.link_was_up = self.phy.phy_link_up();
|
||||
return true;
|
||||
} else {
|
||||
debug!("Failed to detect link speed.");
|
||||
}
|
||||
}
|
||||
self.link_was_up = self.phy.phy_link_up();
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn poll_iface(&mut self) {
|
||||
cortex_m::interrupt::free(|_| {
|
||||
self.iface.poll(now_fn(), &mut &mut self.dma, &mut self.socket_set);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn poll_at_iface(&mut self) -> Option<Instant> {
|
||||
self.iface.poll_at(now_fn(), &mut self.socket_set)
|
||||
}
|
||||
|
||||
pub fn poll_delay_iface(&mut self) -> Option<Duration> {
|
||||
self.iface.poll_delay(now_fn(), &mut self.socket_set)
|
||||
}
|
||||
|
||||
pub fn recv(
|
||||
&mut self,
|
||||
buffer: &mut [u8],
|
||||
socket_handles: SocketHandle,
|
||||
) -> Result<usize, smoltcp::socket::tcp::RecvError> {
|
||||
let socket = self.socket_set.get_mut::<Socket>(socket_handles);
|
||||
|
||||
socket.recv_slice(buffer)
|
||||
}
|
||||
|
||||
pub fn send(&mut self, buffer: &mut [u8], num_bytes: usize, socket_handles: SocketHandle) {
|
||||
let socket = self.socket_set.get_mut::<Socket>(socket_handles);
|
||||
if num_bytes > 0 {
|
||||
cortex_m::interrupt::free(|_| {
|
||||
match socket.send_slice(&buffer[..num_bytes]) {
|
||||
Ok(_) => {
|
||||
self.data_sent = false;
|
||||
info!("Enqueued {} bytes.", num_bytes);
|
||||
}
|
||||
Err(err) => {
|
||||
info!("Bytes cannot be sent. Error: {:?}", err)
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_socket_connected(&mut self, socket_handles: SocketHandle) -> bool {
|
||||
let socket = self.socket_set.get_mut::<Socket>(socket_handles);
|
||||
socket.state() == State::Established
|
||||
}
|
||||
|
||||
pub fn poll_socket_status(&mut self, socket_handles: SocketHandle, reopen_sock: bool) -> bool {
|
||||
let socket = self.socket_set.get_mut::<Socket>(socket_handles);
|
||||
if !socket.is_listening() && !socket.is_open() || socket.state() == State::CloseWait {
|
||||
if reopen_sock {
|
||||
socket.abort();
|
||||
socket.listen(self.socket_addr).ok();
|
||||
info!("Disconnected... Reopening listening socket.");
|
||||
}
|
||||
return false;
|
||||
} else if socket.state() == State::Closed || socket.state() == State::Closing || socket.is_listening() {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn close_socket(&mut self, socket_handles: SocketHandle) {
|
||||
let socket = self.socket_set.get_mut::<Socket>(socket_handles);
|
||||
socket.abort();
|
||||
}
|
||||
|
||||
pub fn can_send(&mut self, socket_handles: SocketHandle) -> bool {
|
||||
self.socket_set.get_mut::<Socket>(socket_handles).can_send()
|
||||
}
|
||||
|
||||
pub fn can_recv(&mut self, socket_handles: SocketHandle) -> bool {
|
||||
self.socket_set.get_mut::<Socket>(socket_handles).can_recv()
|
||||
}
|
||||
}
|
||||
|
||||
use ieee802_3_miim::{phy::{lan87xxa::{LAN8720A, LAN8742A},
|
||||
BarePhy, PhySpeed, KSZ8081R},
|
||||
Miim, Pause, Phy};
|
||||
|
||||
/// An ethernet PHY
|
||||
pub enum EthernetPhy<M: Miim> {
|
||||
/// LAN8720A
|
||||
LAN8720A(LAN8720A<M>),
|
||||
/// LAN8742A
|
||||
LAN8742A(LAN8742A<M>),
|
||||
/// KSZ8081R
|
||||
KSZ8081R(KSZ8081R<M>),
|
||||
}
|
||||
|
||||
impl<M: Miim> Phy<M> for EthernetPhy<M> {
|
||||
fn best_supported_advertisement(&self) -> ieee802_3_miim::AutoNegotiationAdvertisement {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn get_miim(&mut self) -> &mut M {
|
||||
match self {
|
||||
EthernetPhy::LAN8720A(phy) => phy.get_miim(),
|
||||
EthernetPhy::LAN8742A(phy) => phy.get_miim(),
|
||||
EthernetPhy::KSZ8081R(phy) => phy.get_miim(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_phy_addr(&self) -> u8 {
|
||||
match self {
|
||||
EthernetPhy::LAN8720A(phy) => phy.get_phy_addr(),
|
||||
EthernetPhy::LAN8742A(phy) => phy.get_phy_addr(),
|
||||
EthernetPhy::KSZ8081R(phy) => phy.get_phy_addr(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Miim> EthernetPhy<M> {
|
||||
/// Attempt to create one of the known PHYs from the given
|
||||
/// MIIM.
|
||||
///
|
||||
/// Returns an error if the PHY does not support the extended register
|
||||
/// set, or if the PHY's identifier does not correspond to a known PHY.
|
||||
pub fn from_miim(miim: M, phy_addr: u8) -> Result<Self, M> {
|
||||
let mut bare = BarePhy::new(miim, phy_addr, Pause::NoPause);
|
||||
let phy_ident = if let Some(id) = bare.phy_ident() {
|
||||
id.raw_u32()
|
||||
} else {
|
||||
return Err(bare.release());
|
||||
};
|
||||
let miim = bare.release();
|
||||
match phy_ident & 0xFFFFFFF0 {
|
||||
0x0007C0F0 => Ok(Self::LAN8720A(LAN8720A::new(miim, phy_addr))),
|
||||
0x0007C130 => Ok(Self::LAN8742A(LAN8742A::new(miim, phy_addr))),
|
||||
0x00221560 => Ok(Self::KSZ8081R(KSZ8081R::new(miim, phy_addr))),
|
||||
_ => Err(miim),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a string describing the type of PHY
|
||||
pub const fn ident_string(&self) -> &'static str {
|
||||
match self {
|
||||
EthernetPhy::LAN8720A(_) => "LAN8720A",
|
||||
EthernetPhy::LAN8742A(_) => "LAN8742A",
|
||||
EthernetPhy::KSZ8081R(_) => "KSZ8081R",
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the PHY
|
||||
pub fn phy_init(&mut self) {
|
||||
match self {
|
||||
EthernetPhy::LAN8720A(phy) => phy.phy_init(),
|
||||
EthernetPhy::LAN8742A(phy) => phy.phy_init(),
|
||||
EthernetPhy::KSZ8081R(phy) => {
|
||||
phy.set_autonegotiation_advertisement(phy.best_supported_advertisement());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn speed(&mut self) -> Option<ieee802_3_miim::phy::PhySpeed> {
|
||||
match self {
|
||||
EthernetPhy::LAN8720A(phy) => phy.link_speed(),
|
||||
EthernetPhy::LAN8742A(phy) => phy.link_speed(),
|
||||
EthernetPhy::KSZ8081R(phy) => phy.link_speed(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn release(self) -> M {
|
||||
match self {
|
||||
EthernetPhy::LAN8720A(phy) => phy.release(),
|
||||
EthernetPhy::LAN8742A(phy) => phy.release(),
|
||||
EthernetPhy::KSZ8081R(phy) => phy.release(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_can_sock_send(socket_handles: SocketHandle) -> bool {
|
||||
unsafe {
|
||||
if let Some(ref mut server_handle) = SERVER_HANDLE {
|
||||
server_handle.can_send(socket_handles)
|
||||
} else {
|
||||
panic!("eth_check_if_sock_can_send is called before init");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_can_sock_recv(socket_handles: SocketHandle) -> bool {
|
||||
unsafe {
|
||||
if let Some(ref mut server_handle) = SERVER_HANDLE {
|
||||
server_handle.can_recv(socket_handles)
|
||||
} else {
|
||||
panic!("eth_check_if_sock_can_recv is called before init");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_poll_link_status_and_update_link_speed() -> bool {
|
||||
unsafe {
|
||||
if let Some(ref mut server_handle) = SERVER_HANDLE {
|
||||
let new_link_is_up = server_handle.update_link_speed();
|
||||
if new_link_is_up {
|
||||
info!("Resetting TCP Sockets");
|
||||
for_each(|socket, _| {
|
||||
eth_close_socket(socket);
|
||||
});
|
||||
}
|
||||
return new_link_is_up;
|
||||
} else {
|
||||
panic!("eth_poll_link_status_and_update_link_speed is called before init");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_update_iface_poll_timer() {
|
||||
unsafe {
|
||||
if let Some(ref mut server_handle) = SERVER_HANDLE {
|
||||
IfacePollTimer::set_timer(server_handle.poll_at_iface(), server_handle.poll_delay_iface())
|
||||
} else {
|
||||
panic!("eth_update_iface_poll_timer is called before init")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_poll_iface() {
|
||||
unsafe {
|
||||
if let Some(ref mut server_handle) = SERVER_HANDLE {
|
||||
server_handle.poll_iface();
|
||||
} else {
|
||||
panic!("eth_poll_packet is called before init");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_send(buffer: &mut [u8], num_bytes: usize, socket_handles: SocketHandle) {
|
||||
unsafe {
|
||||
if let Some(ref mut server_handle) = SERVER_HANDLE {
|
||||
server_handle.send(buffer, num_bytes, socket_handles);
|
||||
} else {
|
||||
panic!("eth_send is called before init");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_recv(buffer: &mut [u8], socket_handles: SocketHandle) -> usize {
|
||||
unsafe {
|
||||
if let Some(ref mut server_handle) = SERVER_HANDLE {
|
||||
match server_handle.recv(buffer, socket_handles) {
|
||||
Ok(recv_bytes) => return recv_bytes,
|
||||
Err(err) => {
|
||||
debug!("TCP Recv Error: {}", err);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
panic!("eth_send is called before init");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_is_socket_connected(socket_handles: SocketHandle) -> bool {
|
||||
unsafe {
|
||||
if let Some(ref mut server_handle) = SERVER_HANDLE {
|
||||
server_handle.is_socket_connected(socket_handles)
|
||||
} else {
|
||||
panic!("eth_is_socket_connected is called before init");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_is_socket_active(socket_handles: SocketHandle, reopen_sock: bool) -> bool {
|
||||
unsafe {
|
||||
if let Some(ref mut server_handle) = SERVER_HANDLE {
|
||||
server_handle.poll_socket_status(socket_handles, reopen_sock)
|
||||
} else {
|
||||
panic!("eth_is_socket_active is called before init");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_close_socket(socket_handles: SocketHandle) {
|
||||
unsafe {
|
||||
if let Some(ref mut server_handle) = SERVER_HANDLE {
|
||||
server_handle.close_socket(socket_handles)
|
||||
} else {
|
||||
panic!("eth_close_socket is called before init");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_get_sock_handle(id: usize) -> SocketHandle {
|
||||
unsafe {
|
||||
if let Some(ref mut server_handle) = SERVER_HANDLE {
|
||||
server_handle.socket_handles[id]
|
||||
} else {
|
||||
panic!("eth_get_sock_handle is called before init");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_close_socket_by_id(id: usize) {
|
||||
unsafe {
|
||||
if let Some(ref mut server_handle) = SERVER_HANDLE {
|
||||
server_handle.close_socket(server_handle.socket_handles[id])
|
||||
} else {
|
||||
panic!("eth_close_socket_by_id is called before init");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_is_data_sent() -> bool {
|
||||
unsafe {
|
||||
if let Some(ref mut server_handle) = SERVER_HANDLE {
|
||||
server_handle.data_sent
|
||||
} else {
|
||||
panic!("eth_is_data_sent is called before init");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn eth_set_data_sent(val: bool) {
|
||||
unsafe {
|
||||
if let Some(ref mut server_handle) = SERVER_HANDLE {
|
||||
server_handle.data_sent = val;
|
||||
} else {
|
||||
panic!("eth_is_data_sent is called before init");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_each<F: FnMut(SocketHandle, usize)>(mut callback: F) {
|
||||
unsafe {
|
||||
if let Some(ref mut server_handle) = SERVER_HANDLE {
|
||||
for i in 0..NUM_OF_SOCKETS {
|
||||
callback(server_handle.socket_handles[i], i);
|
||||
}
|
||||
} else {
|
||||
panic!("eth_close_socket is called before init");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Potentially wake up from `wfi()`, set the interrupt pending flag,
|
||||
/// clear interrupt flags.
|
||||
#[interrupt]
|
||||
fn ETH() {
|
||||
let interrupt_reason = stm32_eth::eth_interrupt_handler();
|
||||
if interrupt_reason.rx {
|
||||
eth_poll_iface();
|
||||
}
|
||||
if interrupt_reason.tx {
|
||||
eth_set_data_sent(true);
|
||||
}
|
||||
debug!("Ethernet Interrupt{:?}", interrupt_reason);
|
||||
}
|
||||
|
||||
pub struct IfacePollTimer {
|
||||
timer: Counter<TIM5, FREQ>,
|
||||
poll_at_set: Instant,
|
||||
}
|
||||
|
||||
impl IfacePollTimer {
|
||||
fn get() -> Option<&'static mut Self> {
|
||||
unsafe { IFACE_TIMER.as_mut() }
|
||||
}
|
||||
|
||||
pub fn setup(tim5: Counter<TIM5, FREQ>) {
|
||||
unsafe {
|
||||
cortex_m::peripheral::NVIC::unmask(Interrupt::TIM5);
|
||||
IFACE_TIMER = Some(IfacePollTimer {
|
||||
timer: tim5,
|
||||
poll_at_set: Instant::from_micros(u32::max_value()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn is_timer_running() -> bool {
|
||||
unsafe { Peripherals::steal().TIM5.cr1.read().cen().bit() }
|
||||
}
|
||||
|
||||
pub fn set_timer(poll_at: Option<Instant>, delay: Option<Duration>) {
|
||||
if let Some(ref mut iface_timer) = IfacePollTimer::get() {
|
||||
if !IfacePollTimer::is_timer_running() {
|
||||
match poll_at {
|
||||
Some(val) => {
|
||||
IfacePollTimer::set_timer_delay(delay);
|
||||
iface_timer.poll_at_set = val;
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
} else {
|
||||
match poll_at {
|
||||
Some(val) => {
|
||||
if iface_timer.poll_at_set != val {
|
||||
IfacePollTimer::stop_listening();
|
||||
IfacePollTimer::set_timer_delay(delay);
|
||||
iface_timer.poll_at_set = val;
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_timer_delay(delay: Option<Duration>) {
|
||||
if let Some(ref mut iface_timer) = IfacePollTimer::get() {
|
||||
match delay {
|
||||
Some(val) => {
|
||||
if val.micros() != 0 {
|
||||
match iface_timer.timer.start(TimerDurationU32::micros(val.micros() as u32)) {
|
||||
Ok(_) => {
|
||||
iface_timer.timer.listen(Event::Update);
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Timer cannot be set {:?}", e)
|
||||
}
|
||||
};
|
||||
} else {
|
||||
eth_poll_iface();
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_irq_flag() {
|
||||
if let Some(ref mut iface_timer) = IfacePollTimer::get() {
|
||||
iface_timer.timer.wait().ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop_listening() {
|
||||
if let Some(ref mut iface_timer) = IfacePollTimer::get() {
|
||||
iface_timer.timer.unlisten(Event::Update);
|
||||
iface_timer.timer.cancel().ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[interrupt]
|
||||
fn TIM5() {
|
||||
eth_poll_iface();
|
||||
cortex_m::interrupt::free(|_| {
|
||||
IfacePollTimer::stop_listening();
|
||||
IfacePollTimer::clear_irq_flag();
|
||||
});
|
||||
}
|
46
src/thermostat/ad5680.rs
Normal file
46
src/thermostat/ad5680.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use fugit::MegahertzU32;
|
||||
use stm32f4xx_hal::{hal::{digital::OutputPin, spi::SpiBus},
|
||||
spi};
|
||||
|
||||
use crate::device::sys_timer::sleep;
|
||||
|
||||
/// SPI Mode 1
|
||||
pub const SPI_MODE: spi::Mode = spi::Mode {
|
||||
polarity: spi::Polarity::IdleLow,
|
||||
phase: spi::Phase::CaptureOnSecondTransition,
|
||||
};
|
||||
// MAX Clock Frequency: 30MHz | SPI1 Max Frequency 42MHz
|
||||
pub const SPI_CLOCK_MHZ: MegahertzU32 = MegahertzU32::from_raw(21);
|
||||
|
||||
pub const MAX_VALUE: u32 = 0x3FFFF;
|
||||
|
||||
pub struct Dac<SPI: SpiBus<u8>, S: OutputPin> {
|
||||
spi: SPI,
|
||||
sync: S,
|
||||
}
|
||||
|
||||
impl<SPI: SpiBus<u8>, S: OutputPin> Dac<SPI, S> {
|
||||
pub fn new(spi: SPI, mut sync: S) -> Self {
|
||||
let _ = sync.set_low();
|
||||
|
||||
Dac { spi, sync }
|
||||
}
|
||||
|
||||
fn write(&mut self, buf: &mut [u8]) -> Result<(), SPI::Error> {
|
||||
// pulse sync to start a new transfer. leave sync idle low
|
||||
// afterwards to save power as recommended per datasheet.
|
||||
let _ = self.sync.set_high();
|
||||
// must be high for >= 33 ns
|
||||
sleep(1);
|
||||
let _ = self.sync.set_low();
|
||||
self.spi.transfer_in_place(buf)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set(&mut self, value: u32) -> Result<u32, SPI::Error> {
|
||||
let value = value.min(MAX_VALUE);
|
||||
let mut buf = [(value >> 14) as u8, (value >> 6) as u8, (value << 2) as u8];
|
||||
self.write(&mut buf)?;
|
||||
Ok(value)
|
||||
}
|
||||
}
|
351
src/thermostat/ad7172/adc.rs
Normal file
351
src/thermostat/ad7172/adc.rs
Normal file
@ -0,0 +1,351 @@
|
||||
use core::fmt;
|
||||
|
||||
use log::{info, warn};
|
||||
use stm32f4xx_hal::{gpio::{Output, PushPull, PA15},
|
||||
hal::{digital::OutputPin, spi::SpiBus},
|
||||
pac::SPI3,
|
||||
spi::Spi};
|
||||
use uom::si::{electric_potential::volt, f32::ElectricPotential};
|
||||
|
||||
use super::{checksum::{Checksum, ChecksumMode},
|
||||
regs::{self, Register, RegisterData},
|
||||
sinc3_fine_odr_closest, sinc3_fine_odr_output_rate, DigitalFilterOrder, FilterType, Input, Mode,
|
||||
PostFilter, RefSource, SingleChODR};
|
||||
|
||||
/// AD7172-2 implementation
|
||||
///
|
||||
/// [Manual](https://www.analog.com/media/en/technical-documentation/data-sheets/AD7172-2.pdf)
|
||||
pub struct Adc<SPI: SpiBus<u8>, NSS: OutputPin> {
|
||||
spi: SPI,
|
||||
nss: NSS,
|
||||
checksum_mode: ChecksumMode,
|
||||
}
|
||||
|
||||
type AdcSpi = Spi<SPI3>;
|
||||
type AdcNss = PA15<Output<PushPull>>;
|
||||
pub type AdcPhy = Adc<AdcSpi, AdcNss>;
|
||||
|
||||
impl<SPI: SpiBus<u8, Error = E>, NSS: OutputPin, E: fmt::Debug> Adc<SPI, NSS> {
|
||||
pub fn new(spi: SPI, mut nss: NSS) -> Result<Self, SPI::Error> {
|
||||
let _ = nss.set_high();
|
||||
let mut adc = Adc {
|
||||
spi,
|
||||
nss,
|
||||
checksum_mode: ChecksumMode::Off,
|
||||
};
|
||||
adc.reset()?;
|
||||
adc.set_checksum_mode(ChecksumMode::Crc).unwrap();
|
||||
|
||||
let mut retries = 0;
|
||||
let mut adc_id;
|
||||
loop {
|
||||
adc_id = adc.identify()?;
|
||||
if adc_id & 0xFFF0 == 0x00D0 {
|
||||
break;
|
||||
} else {
|
||||
retries += 1;
|
||||
}
|
||||
}
|
||||
info!("ADC id: {:04X} ({} retries)", adc_id, retries);
|
||||
|
||||
let mut adc_mode = <regs::AdcMode as Register>::Data::empty();
|
||||
adc_mode.set_ref_en(true);
|
||||
adc_mode.set_sing_cyc(false);
|
||||
adc_mode.set_mode(Mode::Standby);
|
||||
adc.write_reg(®s::AdcMode, &mut adc_mode)?;
|
||||
|
||||
Ok(adc)
|
||||
}
|
||||
|
||||
/// `0x00DX` for AD7172-2
|
||||
pub fn identify(&mut self) -> Result<u16, SPI::Error> {
|
||||
self.read_reg(®s::Id).map(|id| id.id())
|
||||
}
|
||||
|
||||
pub fn set_checksum_mode(&mut self, mode: ChecksumMode) -> Result<(), SPI::Error> {
|
||||
// Cannot use update_reg() here because checksum_mode is
|
||||
// updated between read_reg() and write_reg().
|
||||
let mut ifmode = self.read_reg(®s::IfMode)?;
|
||||
ifmode.set_crc(mode);
|
||||
self.checksum_mode = mode;
|
||||
self.write_reg(®s::IfMode, &mut ifmode)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_sync_enable(&mut self, enable: bool) -> Result<(), SPI::Error> {
|
||||
self.update_reg(®s::GpioCon, |data| {
|
||||
data.set_sync_en(enable);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn setup_channel(&mut self, index: u8, in_pos: Input, in_neg: Input) -> Result<(), SPI::Error> {
|
||||
self.update_reg(®s::SetupCon { index }, |data| {
|
||||
data.set_bipolar(false);
|
||||
data.set_refbuf_pos(true);
|
||||
data.set_refbuf_neg(true);
|
||||
data.set_ainbuf_pos(true);
|
||||
data.set_ainbuf_neg(true);
|
||||
data.set_ref_sel(RefSource::External);
|
||||
})?;
|
||||
self.set_sinc5_sinc1_with_50hz_60hz_rejection(index, PostFilter::F16SPS)?;
|
||||
self.update_reg(®s::Channel { index }, |data| {
|
||||
data.set_setup(index);
|
||||
data.set_enabled(true);
|
||||
data.set_a_in_pos(in_pos);
|
||||
data.set_a_in_neg(in_neg);
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_calibration(&mut self, index: u8) -> Result<ChannelCalibration, SPI::Error> {
|
||||
let offset = self.read_reg(®s::Offset { index })?.offset();
|
||||
let gain = self.read_reg(®s::Gain { index })?.gain();
|
||||
let bipolar = self.read_reg(®s::SetupCon { index })?.bipolar();
|
||||
Ok(ChannelCalibration { offset, gain, bipolar })
|
||||
}
|
||||
|
||||
pub fn start_continuous_conversion(&mut self) -> Result<(), SPI::Error> {
|
||||
let mut adc_mode = <regs::AdcMode as Register>::Data::empty();
|
||||
adc_mode.set_ref_en(true);
|
||||
// Set SING_CYC = 0 to maximize sampling rate with lowest noise for single channel of temperature measurement
|
||||
adc_mode.set_sing_cyc(false);
|
||||
adc_mode.set_mode(Mode::ContinuousConversion);
|
||||
self.write_reg(®s::AdcMode, &mut adc_mode)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Rate is only valid with single channel enabled
|
||||
pub fn get_filter_type_and_rate(&mut self, index: u8) -> Result<(FilterType, Option<SingleChODR>, Option<PostFilter>, f32), SPI::Error> {
|
||||
let mut filter_type: FilterType = FilterType::Sinc5Sinc1With50hz60HzRejection;
|
||||
let mut rate: f32 = -1.0;
|
||||
let mut single_ch_odr : Option<SingleChODR> = None;
|
||||
let mut post_filter: Option<PostFilter> = None;
|
||||
self.read_reg(®s::FiltCon { index }).map(|data| {
|
||||
if data.sinc3_map() {
|
||||
filter_type = FilterType::Sinc3WithFineODR;
|
||||
let odr: u16 = (data.sinc3_map_fine_odr_msb() as u16) << 8 | data.sinc3_map_fine_odr_lsb() as u16;
|
||||
rate = sinc3_fine_odr_output_rate(odr);
|
||||
} else if data.enh_filt_en() {
|
||||
filter_type = FilterType::Sinc5Sinc1With50hz60HzRejection;
|
||||
match data.enh_filt().output_rate() {
|
||||
Some(val) => {
|
||||
rate = val;
|
||||
}
|
||||
None => {
|
||||
rate = -1.0;
|
||||
}
|
||||
};
|
||||
post_filter = Some(data.enh_filt())
|
||||
} else if data.order() == DigitalFilterOrder::Sinc5Sinc1 {
|
||||
filter_type = FilterType::Sinc5Sinc1;
|
||||
match data.odr().output_rate() {
|
||||
Some(val) => {
|
||||
rate = val;
|
||||
}
|
||||
None => {
|
||||
rate = -1.0;
|
||||
}
|
||||
}
|
||||
single_ch_odr = Some(data.odr())
|
||||
} else {
|
||||
filter_type = FilterType::Sinc3;
|
||||
match data.odr().output_rate() {
|
||||
Some(val) => {
|
||||
rate = val;
|
||||
}
|
||||
None => {
|
||||
rate = -1.0;
|
||||
}
|
||||
}
|
||||
single_ch_odr = Some(data.odr())
|
||||
}
|
||||
})?;
|
||||
Ok((filter_type, single_ch_odr, post_filter, rate))
|
||||
}
|
||||
|
||||
pub fn set_sinc5_sinc1_with_50hz_60hz_rejection(&mut self, index: u8, rate: PostFilter) -> Result<(), SPI::Error> {
|
||||
self.update_reg(®s::FiltCon { index }, |data| {
|
||||
data.set_sinc3_map(false);
|
||||
data.set_enh_filt_en(true);
|
||||
data.set_order(DigitalFilterOrder::Sinc5Sinc1);
|
||||
data.set_enh_filt(rate);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_sinc5_sinc1_filter(&mut self, index: u8, rate: SingleChODR) -> Result<(), SPI::Error> {
|
||||
self.update_reg(®s::FiltCon { index }, |data| {
|
||||
data.set_sinc3_map(false);
|
||||
data.set_enh_filt_en(false);
|
||||
data.set_order(DigitalFilterOrder::Sinc5Sinc1);
|
||||
data.set_odr(rate);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_sinc3_filter(&mut self, index: u8, rate: SingleChODR) -> Result<(), SPI::Error> {
|
||||
self.update_reg(®s::FiltCon { index }, |data| {
|
||||
data.set_sinc3_map(false);
|
||||
data.set_enh_filt_en(false);
|
||||
data.set_order(DigitalFilterOrder::Sinc3);
|
||||
data.set_odr(rate);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_sinc3_fine_filter(&mut self, index: u8, rate: f32) -> Result<f32, SPI::Error> {
|
||||
let sinc3_fine_odr_u16 = sinc3_fine_odr_closest(rate);
|
||||
|
||||
self.update_reg(®s::FiltCon { index }, |data| {
|
||||
data.set_sinc3_map(true);
|
||||
data.set_sinc3_map_fine_odr_msb((sinc3_fine_odr_u16 >> 8 & 0xFF) as u8);
|
||||
data.set_sinc3_map_fine_odr_lsb((sinc3_fine_odr_u16 & 0xFF) as u8);
|
||||
})
|
||||
.map(|_| sinc3_fine_odr_output_rate(sinc3_fine_odr_u16))
|
||||
}
|
||||
|
||||
/// Returns the channel the data is from
|
||||
pub fn data_ready(&mut self) -> Result<Option<u8>, SPI::Error> {
|
||||
self.read_reg(®s::Status)
|
||||
.map(|status| if status.ready() { Some(status.channel()) } else { None })
|
||||
}
|
||||
|
||||
/// Get data
|
||||
pub fn read_data(&mut self) -> Result<u32, SPI::Error> {
|
||||
self.read_reg(®s::Data).map(|data| data.data())
|
||||
}
|
||||
|
||||
fn read_reg<R: regs::Register>(&mut self, reg: &R) -> Result<R::Data, SPI::Error> {
|
||||
let mut reg_data = R::Data::empty();
|
||||
let address = 0x40 | reg.address();
|
||||
let mut checksum = Checksum::new(self.checksum_mode);
|
||||
checksum.feed(&[address]);
|
||||
let checksum_out = checksum.result();
|
||||
|
||||
loop {
|
||||
let checksum_in = self.transfer(address, reg_data.as_mut(), checksum_out)?;
|
||||
|
||||
checksum.feed(®_data);
|
||||
let checksum_expected = checksum.result();
|
||||
if checksum_expected == checksum_in {
|
||||
break;
|
||||
}
|
||||
// Retry
|
||||
warn!(
|
||||
"read_reg {:02X}: checksum error: {:?}!={:?}, retrying",
|
||||
reg.address(),
|
||||
checksum_expected,
|
||||
checksum_in
|
||||
);
|
||||
}
|
||||
Ok(reg_data)
|
||||
}
|
||||
|
||||
fn write_reg<R: regs::Register>(&mut self, reg: &R, reg_data: &mut R::Data) -> Result<(), SPI::Error> {
|
||||
loop {
|
||||
let address = reg.address();
|
||||
let mut checksum = Checksum::new(match self.checksum_mode {
|
||||
ChecksumMode::Off => ChecksumMode::Off,
|
||||
// write checksums are always crc
|
||||
ChecksumMode::Xor => ChecksumMode::Crc,
|
||||
ChecksumMode::Crc => ChecksumMode::Crc,
|
||||
});
|
||||
checksum.feed(&[address]);
|
||||
checksum.feed(®_data);
|
||||
let checksum_out = checksum.result();
|
||||
|
||||
let mut data = reg_data.clone();
|
||||
self.transfer(address, data.as_mut(), checksum_out)?;
|
||||
|
||||
// Verification
|
||||
let readback_data = self.read_reg(reg)?;
|
||||
if *readback_data == **reg_data {
|
||||
return Ok(());
|
||||
}
|
||||
warn!(
|
||||
"write_reg {:02X}: readback error, {:?}!={:?}, retrying",
|
||||
address, &*readback_data, &**reg_data
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_reg<R, F, A>(&mut self, reg: &R, f: F) -> Result<A, SPI::Error>
|
||||
where
|
||||
R: regs::Register,
|
||||
F: FnOnce(&mut R::Data) -> A,
|
||||
{
|
||||
let mut reg_data = self.read_reg(reg)?;
|
||||
let result = f(&mut reg_data);
|
||||
self.write_reg(reg, &mut reg_data)?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) -> Result<(), SPI::Error> {
|
||||
let mut buf = [0xFFu8; 8];
|
||||
let _ = self.nss.set_low();
|
||||
let result = self.spi.transfer_in_place(&mut buf);
|
||||
let _ = self.nss.set_high();
|
||||
result?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transfer<'w>(
|
||||
&mut self,
|
||||
addr: u8,
|
||||
reg_data: &'w mut [u8],
|
||||
checksum: Option<u8>,
|
||||
) -> Result<Option<u8>, SPI::Error> {
|
||||
let mut addr_buf = [addr];
|
||||
|
||||
let _ = self.nss.set_low();
|
||||
let result = match self.spi.transfer_in_place(&mut addr_buf) {
|
||||
Ok(_) => self.spi.transfer_in_place(reg_data),
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
let result = match (result, checksum) {
|
||||
(Ok(_), None) => Ok(None),
|
||||
(Ok(_), Some(checksum_out)) => {
|
||||
let mut checksum_buf = [checksum_out; 1];
|
||||
match self.spi.transfer_in_place(&mut checksum_buf) {
|
||||
Ok(_) => Ok(Some(checksum_buf[0])),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
(Err(e), _) => Err(e),
|
||||
};
|
||||
let _ = self.nss.set_high();
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ChannelCalibration {
|
||||
offset: u32,
|
||||
gain: u32,
|
||||
bipolar: bool,
|
||||
}
|
||||
|
||||
impl Default for ChannelCalibration {
|
||||
fn default() -> Self {
|
||||
ChannelCalibration {
|
||||
offset: 0,
|
||||
gain: 0,
|
||||
bipolar: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChannelCalibration {
|
||||
pub fn convert_data(&self, data: u32) -> ElectricPotential {
|
||||
let data = if self.bipolar {
|
||||
(data as i32 - 0x80_0000) as f32
|
||||
} else {
|
||||
data as f32 / 2.0
|
||||
};
|
||||
let data = data / (self.gain as f32 / (0x40_0000 as f32));
|
||||
let data = data + (self.offset as i32 - 0x80_0000) as f32;
|
||||
let data = data / (2 << 23) as f32;
|
||||
|
||||
const V_REF: f32 = 3.3;
|
||||
ElectricPotential::new::<volt>(data * V_REF / 0.75)
|
||||
}
|
||||
}
|
60
src/thermostat/ad7172/checksum.rs
Normal file
60
src/thermostat/ad7172/checksum.rs
Normal file
@ -0,0 +1,60 @@
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum ChecksumMode {
|
||||
Off = 0b00,
|
||||
/// Seems much less reliable than `Crc`
|
||||
Xor = 0b01,
|
||||
Crc = 0b10,
|
||||
}
|
||||
|
||||
impl From<u8> for ChecksumMode {
|
||||
fn from(x: u8) -> Self {
|
||||
match x {
|
||||
0 => ChecksumMode::Off,
|
||||
1 => ChecksumMode::Xor,
|
||||
_ => ChecksumMode::Crc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Checksum {
|
||||
mode: ChecksumMode,
|
||||
state: u8,
|
||||
}
|
||||
|
||||
impl Checksum {
|
||||
pub fn new(mode: ChecksumMode) -> Self {
|
||||
Checksum { mode, state: 0 }
|
||||
}
|
||||
|
||||
fn feed_byte(&mut self, input: u8) {
|
||||
match self.mode {
|
||||
ChecksumMode::Off => {}
|
||||
ChecksumMode::Xor => self.state ^= input,
|
||||
ChecksumMode::Crc => {
|
||||
for i in 0..8 {
|
||||
let input_mask = 0x80 >> i;
|
||||
self.state = (self.state << 1)
|
||||
^ if ((self.state & 0x80) != 0) != ((input & input_mask) != 0) {
|
||||
0x07 /* x8 + x2 + x + 1 */
|
||||
} else {
|
||||
0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn feed(&mut self, input: &[u8]) {
|
||||
for &b in input {
|
||||
self.feed_byte(b);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn result(&self) -> Option<u8> {
|
||||
match self.mode {
|
||||
ChecksumMode::Off => None,
|
||||
_ => Some(self.state),
|
||||
}
|
||||
}
|
||||
}
|
350
src/thermostat/ad7172/mod.rs
Normal file
350
src/thermostat/ad7172/mod.rs
Normal file
@ -0,0 +1,350 @@
|
||||
use core::fmt;
|
||||
|
||||
use fugit::MegahertzU32;
|
||||
use num_traits::float::Float;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use stm32f4xx_hal::spi;
|
||||
|
||||
mod checksum;
|
||||
pub mod regs;
|
||||
pub use checksum::ChecksumMode;
|
||||
mod adc;
|
||||
pub use adc::*;
|
||||
|
||||
/// SPI Mode 3
|
||||
pub const SPI_MODE: spi::Mode = spi::Mode {
|
||||
polarity: spi::Polarity::IdleHigh,
|
||||
phase: spi::Phase::CaptureOnSecondTransition,
|
||||
};
|
||||
/// AD7172 Max Frequency: 40MHz | SPI3 Max Frequency: 21MHz
|
||||
pub const SPI_CLOCK_MHZ: MegahertzU32 = MegahertzU32::from_raw(21);
|
||||
|
||||
pub const MAX_VALUE: u32 = 0xFF_FFFF;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum Mode {
|
||||
ContinuousConversion = 0b000,
|
||||
SingleConversion = 0b001,
|
||||
Standby = 0b010,
|
||||
PowerDown = 0b011,
|
||||
InternalOffsetCalibration = 0b100,
|
||||
Invalid,
|
||||
SystemOffsetCalibration = 0b110,
|
||||
SystemGainCalibration = 0b111,
|
||||
}
|
||||
|
||||
impl From<u8> for Mode {
|
||||
fn from(x: u8) -> Self {
|
||||
use Mode::*;
|
||||
match x {
|
||||
0b000 => ContinuousConversion,
|
||||
0b001 => SingleConversion,
|
||||
0b010 => Standby,
|
||||
0b011 => PowerDown,
|
||||
0b100 => InternalOffsetCalibration,
|
||||
0b110 => SystemOffsetCalibration,
|
||||
0b111 => SystemGainCalibration,
|
||||
_ => Invalid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum Input {
|
||||
Ain0 = 0,
|
||||
Ain1 = 1,
|
||||
Ain2 = 2,
|
||||
Ain3 = 3,
|
||||
Ain4 = 4,
|
||||
TemperaturePos = 17,
|
||||
TemperatureNeg = 18,
|
||||
AnalogSupplyPos = 19,
|
||||
AnalogSupplyNeg = 20,
|
||||
RefPos = 21,
|
||||
RefNeg = 22,
|
||||
Invalid = 0b11111,
|
||||
}
|
||||
|
||||
impl From<u8> for Input {
|
||||
fn from(x: u8) -> Self {
|
||||
match x {
|
||||
0 => Input::Ain0,
|
||||
1 => Input::Ain1,
|
||||
2 => Input::Ain2,
|
||||
3 => Input::Ain3,
|
||||
4 => Input::Ain4,
|
||||
17 => Input::TemperaturePos,
|
||||
18 => Input::TemperatureNeg,
|
||||
19 => Input::AnalogSupplyPos,
|
||||
20 => Input::AnalogSupplyNeg,
|
||||
21 => Input::RefPos,
|
||||
22 => Input::RefNeg,
|
||||
_ => Input::Invalid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Input {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
use Input::*;
|
||||
|
||||
match self {
|
||||
Ain0 => "ain0",
|
||||
Ain1 => "ain1",
|
||||
Ain2 => "ain2",
|
||||
Ain3 => "ain3",
|
||||
Ain4 => "ain4",
|
||||
TemperaturePos => "temperature+",
|
||||
TemperatureNeg => "temperature-",
|
||||
AnalogSupplyPos => "analogsupply+",
|
||||
AnalogSupplyNeg => "analogsupply-",
|
||||
RefPos => "ref+",
|
||||
RefNeg => "ref-",
|
||||
_ => "<INVALID>",
|
||||
}
|
||||
.fmt(fmt)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reference source for ADC conversion
|
||||
#[repr(u8)]
|
||||
pub enum RefSource {
|
||||
/// External reference
|
||||
External = 0b00,
|
||||
/// Internal 2.5V reference
|
||||
Internal = 0b10,
|
||||
/// AVDD1 − AVSS
|
||||
Avdd1MinusAvss = 0b11,
|
||||
Invalid = 0b01,
|
||||
}
|
||||
|
||||
impl From<u8> for RefSource {
|
||||
fn from(x: u8) -> Self {
|
||||
match x {
|
||||
0 => RefSource::External,
|
||||
1 => RefSource::Internal,
|
||||
2 => RefSource::Avdd1MinusAvss,
|
||||
_ => RefSource::Invalid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RefSource {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
use RefSource::*;
|
||||
|
||||
match self {
|
||||
External => "external",
|
||||
Internal => "internal",
|
||||
Avdd1MinusAvss => "avdd1-avss",
|
||||
_ => "<INVALID>",
|
||||
}
|
||||
.fmt(fmt)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[repr(u8)]
|
||||
pub enum PostFilter {
|
||||
/// 27 SPS, 47 dB rejection, 36.7 ms settling
|
||||
F27SPS = 0b010,
|
||||
/// 21.25 SPS, 62 dB rejection, 40 ms settling
|
||||
F21SPS = 0b011,
|
||||
/// 20 SPS, 86 dB rejection, 50 ms settling
|
||||
F20SPS = 0b101,
|
||||
/// 16.67 SPS, 92 dB rejection, 60 ms settling
|
||||
F16SPS = 0b110,
|
||||
Invalid = 0b111,
|
||||
}
|
||||
|
||||
impl PostFilter {
|
||||
pub const VALID_VALUES: &'static [Self] = &[
|
||||
PostFilter::F27SPS,
|
||||
PostFilter::F21SPS,
|
||||
PostFilter::F20SPS,
|
||||
PostFilter::F16SPS,
|
||||
];
|
||||
|
||||
pub fn closest(rate: f32) -> Option<Self> {
|
||||
let mut best: Option<(f32, Self)> = None;
|
||||
for value in Self::VALID_VALUES {
|
||||
let error = (rate - value.output_rate().unwrap()).abs();
|
||||
let better = best.map(|(best_error, _)| error < best_error).unwrap_or(true);
|
||||
if better {
|
||||
best = Some((error, *value));
|
||||
}
|
||||
}
|
||||
best.map(|(_, best)| best)
|
||||
}
|
||||
|
||||
/// Samples per Second
|
||||
pub fn output_rate(&self) -> Option<f32> {
|
||||
match self {
|
||||
PostFilter::F27SPS => Some(27.0),
|
||||
PostFilter::F21SPS => Some(21.25),
|
||||
PostFilter::F20SPS => Some(20.0),
|
||||
PostFilter::F16SPS => Some(16.67),
|
||||
PostFilter::Invalid => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for PostFilter {
|
||||
fn from(x: u8) -> Self {
|
||||
match x {
|
||||
0b010 => PostFilter::F27SPS,
|
||||
0b011 => PostFilter::F21SPS,
|
||||
0b101 => PostFilter::F20SPS,
|
||||
0b110 => PostFilter::F16SPS,
|
||||
_ => PostFilter::Invalid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Default)]
|
||||
pub enum FilterType {
|
||||
#[default]
|
||||
Sinc5Sinc1With50hz60HzRejection,
|
||||
Sinc5Sinc1,
|
||||
Sinc3,
|
||||
Sinc3WithFineODR,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum DigitalFilterOrder {
|
||||
Sinc5Sinc1 = 0b00,
|
||||
Sinc3 = 0b11,
|
||||
Invalid = 0b10,
|
||||
}
|
||||
|
||||
impl From<u8> for DigitalFilterOrder {
|
||||
fn from(x: u8) -> Self {
|
||||
match x {
|
||||
0b00 => DigitalFilterOrder::Sinc5Sinc1,
|
||||
0b11 => DigitalFilterOrder::Sinc3,
|
||||
_ => DigitalFilterOrder::Invalid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[allow(unused)]
|
||||
#[repr(u8)]
|
||||
pub enum SingleChODR {
|
||||
F31250_0SPS = 0b00101,
|
||||
F15625_0SPS = 0b00110,
|
||||
F10417_0SPS = 0b00111,
|
||||
F5208_0SPS = 0b01000,
|
||||
F2597_0SPS = 0b01001,
|
||||
F1007_0SPS = 0b01010,
|
||||
F503_8SPS = 0b01011,
|
||||
F381_0SPS = 0b01100,
|
||||
F200_3SPS = 0b01101,
|
||||
F100_2SPS = 0b01110,
|
||||
F59_52SPS = 0b01111,
|
||||
F49_68SPS = 0b10000,
|
||||
F20_01SPS = 0b10001,
|
||||
F16_63SPS = 0b10010,
|
||||
F10_0SPS = 0b10011,
|
||||
F5_0SPS = 0b10100,
|
||||
F2_5SPS = 0b10101,
|
||||
F1_25SPS = 0b10110,
|
||||
Invalid = 0b11111,
|
||||
}
|
||||
|
||||
impl SingleChODR {
|
||||
pub const VALID_VALUES: &'static [Self] = &[
|
||||
SingleChODR::F31250_0SPS,
|
||||
SingleChODR::F15625_0SPS,
|
||||
SingleChODR::F10417_0SPS,
|
||||
SingleChODR::F5208_0SPS,
|
||||
SingleChODR::F2597_0SPS,
|
||||
SingleChODR::F1007_0SPS,
|
||||
SingleChODR::F503_8SPS,
|
||||
SingleChODR::F381_0SPS,
|
||||
SingleChODR::F200_3SPS,
|
||||
SingleChODR::F100_2SPS,
|
||||
SingleChODR::F59_52SPS,
|
||||
SingleChODR::F49_68SPS,
|
||||
SingleChODR::F20_01SPS,
|
||||
SingleChODR::F16_63SPS,
|
||||
SingleChODR::F10_0SPS,
|
||||
SingleChODR::F5_0SPS,
|
||||
SingleChODR::F2_5SPS,
|
||||
SingleChODR::F1_25SPS,
|
||||
];
|
||||
|
||||
pub fn closest(rate: f32) -> Option<Self> {
|
||||
let mut best: Option<(f32, Self)> = None;
|
||||
for value in Self::VALID_VALUES {
|
||||
let error = (rate - value.output_rate().unwrap()).abs();
|
||||
let better = best.map(|(best_error, _)| error < best_error).unwrap_or(true);
|
||||
if better {
|
||||
best = Some((error, *value));
|
||||
}
|
||||
}
|
||||
best.map(|(_, best)| best)
|
||||
}
|
||||
|
||||
/// Samples per Second
|
||||
pub fn output_rate(&self) -> Option<f32> {
|
||||
match self {
|
||||
SingleChODR::F31250_0SPS => Some(31250.0),
|
||||
SingleChODR::F15625_0SPS => Some(15625.0),
|
||||
SingleChODR::F10417_0SPS => Some(10417.0),
|
||||
SingleChODR::F5208_0SPS => Some(5208.0),
|
||||
SingleChODR::F2597_0SPS => Some(2597.0),
|
||||
SingleChODR::F1007_0SPS => Some(1007.0),
|
||||
SingleChODR::F503_8SPS => Some(503.8),
|
||||
SingleChODR::F381_0SPS => Some(381.0),
|
||||
SingleChODR::F200_3SPS => Some(200.3),
|
||||
SingleChODR::F100_2SPS => Some(100.2),
|
||||
SingleChODR::F59_52SPS => Some(59.52),
|
||||
SingleChODR::F49_68SPS => Some(49.68),
|
||||
SingleChODR::F20_01SPS => Some(20.01),
|
||||
SingleChODR::F16_63SPS => Some(16.63),
|
||||
SingleChODR::F10_0SPS => Some(10.0),
|
||||
SingleChODR::F5_0SPS => Some(5.0),
|
||||
SingleChODR::F2_5SPS => Some(2.5),
|
||||
SingleChODR::F1_25SPS => Some(1.25),
|
||||
SingleChODR::Invalid => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for SingleChODR {
|
||||
fn from(x: u8) -> Self {
|
||||
match x {
|
||||
0b00101 => SingleChODR::F31250_0SPS,
|
||||
0b00110 => SingleChODR::F15625_0SPS,
|
||||
0b00111 => SingleChODR::F10417_0SPS,
|
||||
0b01000 => SingleChODR::F5208_0SPS,
|
||||
0b01001 => SingleChODR::F2597_0SPS,
|
||||
0b01010 => SingleChODR::F1007_0SPS,
|
||||
0b01011 => SingleChODR::F503_8SPS,
|
||||
0b01100 => SingleChODR::F381_0SPS,
|
||||
0b01101 => SingleChODR::F200_3SPS,
|
||||
0b01110 => SingleChODR::F100_2SPS,
|
||||
0b01111 => SingleChODR::F59_52SPS,
|
||||
0b10000 => SingleChODR::F49_68SPS,
|
||||
0b10001 => SingleChODR::F20_01SPS,
|
||||
0b10010 => SingleChODR::F16_63SPS,
|
||||
0b10011 => SingleChODR::F10_0SPS,
|
||||
0b10100 => SingleChODR::F5_0SPS,
|
||||
0b10101 => SingleChODR::F2_5SPS,
|
||||
0b10110 => SingleChODR::F1_25SPS,
|
||||
_ => SingleChODR::Invalid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sinc3_fine_odr_output_rate(odr: u16) -> f32 {
|
||||
1.0 * 1e6 / (32.0 * odr as f32)
|
||||
}
|
||||
|
||||
pub fn sinc3_fine_odr_closest(rate: f32) -> u16 {
|
||||
(1.0e6 / (32.0 * rate)).max(1.0 as f32).min(0x7FFF as f32) as u16
|
||||
}
|
342
src/thermostat/ad7172/regs.rs
Normal file
342
src/thermostat/ad7172/regs.rs
Normal file
@ -0,0 +1,342 @@
|
||||
use core::ops::{Deref, DerefMut};
|
||||
|
||||
use bit_field::BitField;
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub trait Register {
|
||||
type Data: RegisterData;
|
||||
fn address(&self) -> u8;
|
||||
}
|
||||
|
||||
pub trait RegisterData: Clone + Deref<Target = [u8]> + DerefMut {
|
||||
fn empty() -> Self;
|
||||
}
|
||||
|
||||
macro_rules! def_reg {
|
||||
($Reg:ident, $reg:ident, $addr:expr, $size:expr) => {
|
||||
/// AD7172 register
|
||||
pub struct $Reg;
|
||||
impl Register for $Reg {
|
||||
/// Register contents
|
||||
type Data = $reg::Data;
|
||||
/// Register address
|
||||
fn address(&self) -> u8 {
|
||||
$addr
|
||||
}
|
||||
}
|
||||
mod $reg {
|
||||
/// Register contents
|
||||
#[derive(Clone)]
|
||||
pub struct Data(pub [u8; $size]);
|
||||
impl super::RegisterData for Data {
|
||||
/// Generate zeroed register contents
|
||||
fn empty() -> Self {
|
||||
Data([0; $size])
|
||||
}
|
||||
}
|
||||
impl core::ops::Deref for Data {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl core::ops::DerefMut for Data {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
($Reg:ident,u8, $reg:ident, $addr:expr, $size:expr) => {
|
||||
pub struct $Reg {
|
||||
pub index: u8,
|
||||
}
|
||||
impl Register for $Reg {
|
||||
type Data = $reg::Data;
|
||||
fn address(&self) -> u8 {
|
||||
$addr + self.index
|
||||
}
|
||||
}
|
||||
mod $reg {
|
||||
#[derive(Clone)]
|
||||
pub struct Data(pub [u8; $size]);
|
||||
impl super::RegisterData for Data {
|
||||
fn empty() -> Self {
|
||||
Data([0; $size])
|
||||
}
|
||||
}
|
||||
impl core::ops::Deref for Data {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl core::ops::DerefMut for Data {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! reg_bit {
|
||||
($getter:ident, $byte:expr, $bit:expr, $doc:expr) => {
|
||||
#[allow(unused)]
|
||||
#[doc = $doc]
|
||||
pub fn $getter(&self) -> bool {
|
||||
self.0[$byte].get_bit($bit)
|
||||
}
|
||||
};
|
||||
($getter:ident, $setter:ident, $byte:expr, $bit:expr, $doc:expr) => {
|
||||
#[allow(unused)]
|
||||
#[doc = $doc]
|
||||
pub fn $getter(&self) -> bool {
|
||||
self.0[$byte].get_bit($bit)
|
||||
}
|
||||
#[allow(unused)]
|
||||
#[doc = $doc]
|
||||
pub fn $setter(&mut self, value: bool) {
|
||||
self.0[$byte].set_bit($bit, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! reg_bits {
|
||||
($getter:ident, $byte:expr, $bits:expr, $doc:expr) => {
|
||||
#[allow(unused)]
|
||||
#[doc = $doc]
|
||||
pub fn $getter(&self) -> u8 {
|
||||
self.0[$byte].get_bits($bits)
|
||||
}
|
||||
};
|
||||
($getter:ident, $setter:ident, $byte:expr, $bits:expr, $doc:expr) => {
|
||||
#[allow(unused)]
|
||||
#[doc = $doc]
|
||||
pub fn $getter(&self) -> u8 {
|
||||
self.0[$byte].get_bits($bits)
|
||||
}
|
||||
#[allow(unused)]
|
||||
#[doc = $doc]
|
||||
pub fn $setter(&mut self, value: u8) {
|
||||
self.0[$byte].set_bits($bits, value);
|
||||
}
|
||||
};
|
||||
($getter:ident, $byte:expr, $bits:expr, $ty:ty, $doc:expr) => {
|
||||
#[allow(unused)]
|
||||
#[doc = $doc]
|
||||
pub fn $getter(&self) -> $ty {
|
||||
self.0[$byte].get_bits($bits) as $ty
|
||||
}
|
||||
};
|
||||
($getter:ident, $setter:ident, $byte:expr, $bits:expr, $ty:ty, $doc:expr) => {
|
||||
#[allow(unused)]
|
||||
#[doc = $doc]
|
||||
pub fn $getter(&self) -> $ty {
|
||||
self.0[$byte].get_bits($bits).into()
|
||||
}
|
||||
#[allow(unused)]
|
||||
#[doc = $doc]
|
||||
pub fn $setter(&mut self, value: $ty) {
|
||||
self.0[$byte].set_bits($bits, value as u8);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
def_reg!(Status, status, 0x00, 1);
|
||||
impl status::Data {
|
||||
/// Is there new data to read?
|
||||
pub fn ready(&self) -> bool {
|
||||
!self.not_ready()
|
||||
}
|
||||
|
||||
reg_bit!(not_ready, 0, 7, "No data ready indicator");
|
||||
reg_bits!(channel, 0, 0..=1, "Channel for which data is ready");
|
||||
reg_bit!(adc_error, 0, 6, "ADC error");
|
||||
reg_bit!(crc_error, 0, 5, "SPI CRC error");
|
||||
reg_bit!(reg_error, 0, 4, "Register error");
|
||||
}
|
||||
|
||||
def_reg!(AdcMode, adc_mode, 0x01, 2);
|
||||
impl adc_mode::Data {
|
||||
reg_bits!(delay, set_delay, 0, 0..=2, "Delay after channel switch");
|
||||
reg_bit!(sing_cyc, set_sing_cyc, 0, 5, "Can only used with single channel");
|
||||
reg_bit!(hide_delay, set_hide_delay, 0, 6, "Hide delay");
|
||||
reg_bit!(
|
||||
ref_en,
|
||||
set_ref_en,
|
||||
0,
|
||||
7,
|
||||
"Enable internal reference, output buffered 2.5 V to REFOUT"
|
||||
);
|
||||
reg_bits!(clockset, set_clocksel, 1, 2..=3, "Clock source");
|
||||
reg_bits!(mode, set_mode, 1, 4..=6, Mode, "Operating mode");
|
||||
}
|
||||
|
||||
def_reg!(IfMode, if_mode, 0x02, 2);
|
||||
impl if_mode::Data {
|
||||
reg_bits!(crc, set_crc, 1, 2..=3, ChecksumMode, "SPI checksum mode");
|
||||
}
|
||||
|
||||
def_reg!(Data, data, 0x04, 3);
|
||||
impl data::Data {
|
||||
pub fn data(&self) -> u32 {
|
||||
(u32::from(self.0[0]) << 16) | (u32::from(self.0[1]) << 8) | u32::from(self.0[2])
|
||||
}
|
||||
}
|
||||
|
||||
def_reg!(GpioCon, gpio_con, 0x06, 2);
|
||||
impl gpio_con::Data {
|
||||
reg_bit!(sync_en, set_sync_en, 0, 3, "Enables the SYNC/ERROR pin as a sync input");
|
||||
}
|
||||
|
||||
def_reg!(Id, id, 0x07, 2);
|
||||
impl id::Data {
|
||||
pub fn id(&self) -> u16 {
|
||||
BigEndian::read_u16(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
def_reg!(Channel, u8, channel, 0x10, 2);
|
||||
impl channel::Data {
|
||||
reg_bit!(enabled, set_enabled, 0, 7, "Channel enabled");
|
||||
reg_bits!(setup, set_setup, 0, 4..=5, "Setup number");
|
||||
|
||||
/// Which input is connected to positive input of this channel
|
||||
#[allow(unused)]
|
||||
pub fn a_in_pos(&self) -> Input {
|
||||
((self.0[0].get_bits(0..=1) << 3) | self.0[1].get_bits(5..=7)).into()
|
||||
}
|
||||
/// Set which input is connected to positive input of this channel
|
||||
#[allow(unused)]
|
||||
pub fn set_a_in_pos(&mut self, value: Input) {
|
||||
let value = value as u8;
|
||||
self.0[0].set_bits(0..=1, value >> 3);
|
||||
self.0[1].set_bits(5..=7, value & 0x7);
|
||||
}
|
||||
reg_bits!(
|
||||
a_in_neg,
|
||||
set_a_in_neg,
|
||||
1,
|
||||
0..=4,
|
||||
Input,
|
||||
"Which input is connected to negative input of this channel"
|
||||
);
|
||||
}
|
||||
|
||||
def_reg!(SetupCon, u8, setup_con, 0x20, 2);
|
||||
impl setup_con::Data {
|
||||
reg_bit!(
|
||||
bipolar,
|
||||
set_bipolar,
|
||||
0,
|
||||
4,
|
||||
"Unipolar (`false`) or bipolar (`true`) coded output"
|
||||
);
|
||||
reg_bit!(refbuf_pos, set_refbuf_pos, 0, 3, "Enable REF+ input buffer");
|
||||
reg_bit!(refbuf_neg, set_refbuf_neg, 0, 2, "Enable REF- input buffer");
|
||||
reg_bit!(ainbuf_pos, set_ainbuf_pos, 0, 1, "Enable AIN+ input buffer");
|
||||
reg_bit!(ainbuf_neg, set_ainbuf_neg, 0, 0, "Enable AIN- input buffer");
|
||||
reg_bit!(
|
||||
burnout_en,
|
||||
1,
|
||||
7,
|
||||
"enables a 10 µA current source on the positive analog input selected and a 10 µA current sink on the \
|
||||
negative analog input selected"
|
||||
);
|
||||
reg_bits!(
|
||||
ref_sel,
|
||||
set_ref_sel,
|
||||
1,
|
||||
4..=5,
|
||||
RefSource,
|
||||
"Select reference source for conversion"
|
||||
);
|
||||
}
|
||||
|
||||
def_reg!(FiltCon, u8, filt_con, 0x28, 2);
|
||||
impl filt_con::Data {
|
||||
reg_bit!(
|
||||
sinc3_map,
|
||||
set_sinc3_map,
|
||||
0,
|
||||
7,
|
||||
"If set, Sinc3 Filter's notch frequency rejection position can be fine tuned with FiltCon[14:0]. Best to be \
|
||||
used with Single Channel Enabled"
|
||||
);
|
||||
reg_bit!(
|
||||
enh_filt_en,
|
||||
set_enh_filt_en,
|
||||
0,
|
||||
3,
|
||||
"Enable postfilters for enhanced 50Hz and 60Hz rejection"
|
||||
);
|
||||
reg_bits!(
|
||||
enh_filt,
|
||||
set_enh_filt,
|
||||
0,
|
||||
0..=2,
|
||||
PostFilter,
|
||||
"Select postfilters output data rate for enhanced 50Hz and 60Hz rejection"
|
||||
);
|
||||
reg_bits!(
|
||||
order,
|
||||
set_order,
|
||||
1,
|
||||
5..=6,
|
||||
DigitalFilterOrder,
|
||||
"order of the digital filter that processes the modulator data"
|
||||
);
|
||||
reg_bits!(
|
||||
odr,
|
||||
set_odr,
|
||||
1,
|
||||
0..=4,
|
||||
SingleChODR,
|
||||
"Output data rate for normal Sin5c + Sinc1 and Sinc3 filter with SING_CYC = 0 and Single Channel Enabled"
|
||||
);
|
||||
reg_bits!(
|
||||
sinc3_map_fine_odr_msb,
|
||||
set_sinc3_map_fine_odr_msb,
|
||||
0,
|
||||
0..=6,
|
||||
"MSB Byte Sinc3 Fine Output Config"
|
||||
);
|
||||
reg_bits!(
|
||||
sinc3_map_fine_odr_lsb,
|
||||
set_sinc3_map_fine_odr_lsb,
|
||||
1,
|
||||
0..=7,
|
||||
"LSB Byte Sinc3 Fine Output Config"
|
||||
);
|
||||
}
|
||||
|
||||
def_reg!(Offset, u8, offset, 0x30, 3);
|
||||
impl offset::Data {
|
||||
#[allow(unused)]
|
||||
pub fn offset(&self) -> u32 {
|
||||
(u32::from(self.0[0]) << 16) | (u32::from(self.0[1]) << 8) | u32::from(self.0[2])
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn set_offset(&mut self, value: u32) {
|
||||
self.0[0] = (value >> 16) as u8;
|
||||
self.0[1] = (value >> 8) as u8;
|
||||
self.0[2] = value as u8;
|
||||
}
|
||||
}
|
||||
|
||||
def_reg!(Gain, u8, gain, 0x38, 3);
|
||||
impl gain::Data {
|
||||
#[allow(unused)]
|
||||
pub fn gain(&self) -> u32 {
|
||||
(u32::from(self.0[0]) << 16) | (u32::from(self.0[1]) << 8) | u32::from(self.0[2])
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn set_gain(&mut self, value: u32) {
|
||||
self.0[0] = (value >> 16) as u8;
|
||||
self.0[1] = (value >> 8) as u8;
|
||||
self.0[2] = value as u8;
|
||||
}
|
||||
}
|
414
src/thermostat/max1968.rs
Normal file
414
src/thermostat/max1968.rs
Normal file
@ -0,0 +1,414 @@
|
||||
use core::ptr::addr_of_mut;
|
||||
|
||||
use fugit::KilohertzU32;
|
||||
use stm32f4xx_hal::{adc::{config::{self, AdcConfig},
|
||||
Adc},
|
||||
dma::{config::DmaConfig, PeripheralToMemory, Stream2, StreamsTuple, Transfer as DMA_Transfer},
|
||||
gpio::{gpioa::*, gpiob::*, gpioc::*, Analog, Output, PushPull},
|
||||
hal::{digital::OutputPin, spi::SpiBus},
|
||||
interrupt,
|
||||
pac::{Peripherals, ADC1, ADC2, DMA2, NVIC, SPI1, TIM4},
|
||||
spi::Spi,
|
||||
timer::pwm::PwmChannel};
|
||||
use uom::si::{electric_potential::millivolt, f32::ElectricPotential, ratio::ratio};
|
||||
|
||||
use crate::{device::sys_timer::sleep, thermostat::ad5680};
|
||||
|
||||
pub const PWM_FREQ_KHZ: KilohertzU32 = KilohertzU32::from_raw(20);
|
||||
|
||||
pub trait ChannelPins {
|
||||
type DacSpi: SpiBus<u8>;
|
||||
type DacSync: OutputPin;
|
||||
type ShdnPin: OutputPin;
|
||||
type VRefPin;
|
||||
type ItecPin;
|
||||
type DacFeedbackPin;
|
||||
type VTecPin;
|
||||
type MaxVPin;
|
||||
type MaxIPosPin;
|
||||
type MAXINegPin;
|
||||
}
|
||||
|
||||
pub struct Channel0;
|
||||
|
||||
impl ChannelPins for Channel0 {
|
||||
type DacSpi = DacSpi;
|
||||
type DacSync = DacSync;
|
||||
type ShdnPin = PA5<Output<PushPull>>;
|
||||
type VRefPin = PA6<Analog>;
|
||||
type ItecPin = PB1<Analog>;
|
||||
type DacFeedbackPin = PC0<Analog>;
|
||||
type VTecPin = PB0<Analog>;
|
||||
type MaxVPin = PwmChannel<TIM4, 1>;
|
||||
type MaxIPosPin = PwmChannel<TIM4, 2>;
|
||||
type MAXINegPin = PwmChannel<TIM4, 0>;
|
||||
}
|
||||
|
||||
pub struct MAX1968Phy<C: ChannelPins> {
|
||||
pub dac: ad5680::Dac<C::DacSpi, C::DacSync>,
|
||||
pub shdn: C::ShdnPin,
|
||||
pub vref_pin: C::VRefPin,
|
||||
pub itec_pin: C::ItecPin,
|
||||
pub dac_feedback_pin: C::DacFeedbackPin,
|
||||
pub vtec_pin: C::VTecPin,
|
||||
pub max_v: C::MaxVPin,
|
||||
pub max_i_pos: C::MaxIPosPin,
|
||||
pub max_i_neg: C::MAXINegPin,
|
||||
}
|
||||
|
||||
pub struct MAX1968PinSet<C: ChannelPins> {
|
||||
pub dac: ad5680::Dac<C::DacSpi, C::DacSync>,
|
||||
pub shdn: C::ShdnPin,
|
||||
pub vref_pin: C::VRefPin,
|
||||
pub itec_pin: C::ItecPin,
|
||||
pub dac_feedback_pin: C::DacFeedbackPin,
|
||||
pub vtec_pin: C::VTecPin,
|
||||
pub max_v: C::MaxVPin,
|
||||
pub max_i_pos: C::MaxIPosPin,
|
||||
pub max_i_neg: C::MAXINegPin,
|
||||
}
|
||||
|
||||
type DacSpi = Spi<SPI1>;
|
||||
type DacSync = PB4<Output<PushPull>>;
|
||||
|
||||
static mut DMA_TRANSFER_COMPLETE: bool = true;
|
||||
|
||||
pub struct MAX1968 {
|
||||
pub phy: MAX1968Phy<Channel0>,
|
||||
pub pins_adc: Adc<ADC1>,
|
||||
pub dma_adc: DMA_Transfer<Stream2<DMA2>, 1, Adc<ADC2>, PeripheralToMemory, &'static mut [u16; 16]>,
|
||||
pub dac_out_range: ElectricPotential,
|
||||
prev_vtec_volt: ElectricPotential,
|
||||
prev_itec_volt: ElectricPotential,
|
||||
}
|
||||
|
||||
pub enum PwmPinsEnum {
|
||||
MaxV,
|
||||
MaxPosI,
|
||||
MaxNegI,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub enum AdcReadTarget {
|
||||
VREF,
|
||||
DacVfb,
|
||||
ITec,
|
||||
VTec,
|
||||
}
|
||||
|
||||
impl<C: ChannelPins> MAX1968Phy<C> {
|
||||
pub fn new(pins: MAX1968PinSet<C>) -> Self {
|
||||
MAX1968Phy {
|
||||
dac: pins.dac,
|
||||
shdn: pins.shdn,
|
||||
vref_pin: pins.vref_pin,
|
||||
itec_pin: pins.itec_pin,
|
||||
dac_feedback_pin: pins.dac_feedback_pin,
|
||||
vtec_pin: pins.vtec_pin,
|
||||
max_v: pins.max_v,
|
||||
max_i_pos: pins.max_i_pos,
|
||||
max_i_neg: pins.max_i_neg,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static mut ADC2_FIRST_BUFFER: [u16; 16] = [0; 16];
|
||||
static mut ADC2_LOCAL_BUFFER: [u16; 16] = [0; 16];
|
||||
|
||||
impl MAX1968 {
|
||||
pub fn new(mut phy_ch0: MAX1968Phy<Channel0>, adc1: ADC1, adc2: ADC2, dma2: DMA2) -> Self {
|
||||
let adc_config = AdcConfig::default()
|
||||
.clock(config::Clock::Pclk2_div_8)
|
||||
.default_sample_time(config::SampleTime::Cycles_480);
|
||||
// Do not set reset RCCs as it causes other ADCs' clock to be disabled
|
||||
let mut pins_adc1 = Adc::adc1(adc1, false, adc_config);
|
||||
|
||||
// adc1.calibrate() fn only read REFINT once to assign the calibration value.
|
||||
// It does not take the STM32F4's ADC Precision Limitation into account.
|
||||
// AN4073: ADC Reading Dispersion can be reduced through Averaging
|
||||
let mut vdda_mv: u32 = 0;
|
||||
for _ in (0..512).rev() {
|
||||
pins_adc1.calibrate();
|
||||
vdda_mv += pins_adc1.reference_voltage();
|
||||
}
|
||||
vdda_mv = vdda_mv / 512 as u32;
|
||||
pins_adc1.apply_config(adc_config.reference_voltage(vdda_mv));
|
||||
|
||||
let adc_config = AdcConfig::default()
|
||||
.clock(config::Clock::Pclk2_div_8)
|
||||
.default_sample_time(config::SampleTime::Cycles_480)
|
||||
.dma(config::Dma::Continuous)
|
||||
.scan(config::Scan::Enabled)
|
||||
.reference_voltage(pins_adc1.reference_voltage());
|
||||
let dma_config = DmaConfig::default()
|
||||
.transfer_complete_interrupt(true)
|
||||
.memory_increment(true)
|
||||
.double_buffer(false);
|
||||
let mut pins_adc2 = Adc::adc2(adc2, false, adc_config);
|
||||
pins_adc2.configure_channel(&phy_ch0.itec_pin, config::Sequence::One, config::SampleTime::Cycles_480);
|
||||
pins_adc2.configure_channel(&phy_ch0.vtec_pin, config::Sequence::Two, config::SampleTime::Cycles_480);
|
||||
pins_adc2.configure_channel(
|
||||
&phy_ch0.itec_pin,
|
||||
config::Sequence::Three,
|
||||
config::SampleTime::Cycles_480,
|
||||
);
|
||||
pins_adc2.configure_channel(
|
||||
&phy_ch0.vtec_pin,
|
||||
config::Sequence::Four,
|
||||
config::SampleTime::Cycles_480,
|
||||
);
|
||||
pins_adc2.configure_channel(
|
||||
&phy_ch0.itec_pin,
|
||||
config::Sequence::Five,
|
||||
config::SampleTime::Cycles_480,
|
||||
);
|
||||
pins_adc2.configure_channel(&phy_ch0.vtec_pin, config::Sequence::Six, config::SampleTime::Cycles_480);
|
||||
pins_adc2.configure_channel(
|
||||
&phy_ch0.itec_pin,
|
||||
config::Sequence::Seven,
|
||||
config::SampleTime::Cycles_480,
|
||||
);
|
||||
pins_adc2.configure_channel(
|
||||
&phy_ch0.vtec_pin,
|
||||
config::Sequence::Eight,
|
||||
config::SampleTime::Cycles_480,
|
||||
);
|
||||
pins_adc2.configure_channel(
|
||||
&phy_ch0.itec_pin,
|
||||
config::Sequence::Nine,
|
||||
config::SampleTime::Cycles_480,
|
||||
);
|
||||
pins_adc2.configure_channel(&phy_ch0.vtec_pin, config::Sequence::Ten, config::SampleTime::Cycles_480);
|
||||
pins_adc2.configure_channel(
|
||||
&phy_ch0.itec_pin,
|
||||
config::Sequence::Eleven,
|
||||
config::SampleTime::Cycles_480,
|
||||
);
|
||||
pins_adc2.configure_channel(
|
||||
&phy_ch0.vtec_pin,
|
||||
config::Sequence::Twelve,
|
||||
config::SampleTime::Cycles_480,
|
||||
);
|
||||
pins_adc2.configure_channel(
|
||||
&phy_ch0.itec_pin,
|
||||
config::Sequence::Thirteen,
|
||||
config::SampleTime::Cycles_480,
|
||||
);
|
||||
pins_adc2.configure_channel(
|
||||
&phy_ch0.vtec_pin,
|
||||
config::Sequence::Fourteen,
|
||||
config::SampleTime::Cycles_480,
|
||||
);
|
||||
pins_adc2.configure_channel(
|
||||
&phy_ch0.itec_pin,
|
||||
config::Sequence::Fifteen,
|
||||
config::SampleTime::Cycles_480,
|
||||
);
|
||||
pins_adc2.configure_channel(
|
||||
&phy_ch0.vtec_pin,
|
||||
config::Sequence::Sixteen,
|
||||
config::SampleTime::Cycles_480,
|
||||
);
|
||||
|
||||
let dma = StreamsTuple::new(dma2);
|
||||
let dma_adc: DMA_Transfer<Stream2<DMA2>, 1, Adc<ADC2>, PeripheralToMemory, &'static mut [u16; 16]>;
|
||||
unsafe {
|
||||
dma_adc = DMA_Transfer::init_peripheral_to_memory(
|
||||
dma.2,
|
||||
pins_adc2,
|
||||
addr_of_mut!(ADC2_FIRST_BUFFER).as_mut().unwrap(),
|
||||
None,
|
||||
dma_config,
|
||||
);
|
||||
NVIC::unmask(interrupt::DMA2_STREAM2);
|
||||
}
|
||||
|
||||
phy_ch0.dac.set(ad5680::MAX_VALUE).unwrap();
|
||||
sleep(500);
|
||||
let mut sample = 0;
|
||||
for _ in 0..512 {
|
||||
sample += pins_adc1.convert(
|
||||
&phy_ch0.dac_feedback_pin,
|
||||
stm32f4xx_hal::adc::config::SampleTime::Cycles_480,
|
||||
) as u32;
|
||||
}
|
||||
let sample = sample / 512 as u32;
|
||||
let mv = pins_adc1.sample_to_millivolts(sample as u16);
|
||||
let dac_out_range = ElectricPotential::new::<millivolt>(mv as f32);
|
||||
phy_ch0.dac.set(0).unwrap();
|
||||
|
||||
|
||||
MAX1968 {
|
||||
phy: phy_ch0,
|
||||
pins_adc: pins_adc1,
|
||||
dma_adc: dma_adc,
|
||||
dac_out_range: dac_out_range,
|
||||
prev_vtec_volt: ElectricPotential::new::<millivolt>(0.0),
|
||||
prev_itec_volt: ElectricPotential::new::<millivolt>(0.0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dma_adc_start_conversion(&mut self) {
|
||||
if unsafe { DMA_TRANSFER_COMPLETE } {
|
||||
unsafe {
|
||||
DMA_TRANSFER_COMPLETE = false;
|
||||
}
|
||||
self.dma_adc.start(|adc| {
|
||||
adc.clear_end_of_conversion_flag();
|
||||
adc.start_conversion();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tec_readings(&mut self) -> (ElectricPotential, ElectricPotential) {
|
||||
if unsafe { DMA_TRANSFER_COMPLETE } {
|
||||
let buffer: &[u16; 16];
|
||||
unsafe {
|
||||
(buffer, _) = self
|
||||
.dma_adc
|
||||
.next_transfer(addr_of_mut!(ADC2_LOCAL_BUFFER).as_mut().unwrap())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let sample_to_millivolts = self.dma_adc.peripheral().make_sample_to_millivolts();
|
||||
let mut itec: u16 = 0;
|
||||
for data in buffer.into_iter().step_by(2) {
|
||||
itec += *data;
|
||||
}
|
||||
itec = itec >> 3;
|
||||
|
||||
let mut vtec: u16 = 0;
|
||||
for data in buffer.into_iter().skip(1).step_by(2) {
|
||||
vtec += *data;
|
||||
}
|
||||
vtec = vtec >> 3;
|
||||
|
||||
unsafe {
|
||||
ADC2_LOCAL_BUFFER = *buffer;
|
||||
}
|
||||
self.prev_vtec_volt = ElectricPotential::new::<millivolt>(sample_to_millivolts(vtec) as f32);
|
||||
self.prev_itec_volt = ElectricPotential::new::<millivolt>(sample_to_millivolts(itec) as f32);
|
||||
}
|
||||
(self.prev_vtec_volt, self.prev_itec_volt)
|
||||
}
|
||||
|
||||
// Return the calibrated VDDA Voltage
|
||||
// Can be used to set reference voltage for other ADC
|
||||
pub fn get_calibrated_vdda(&mut self) -> u32 {
|
||||
self.pins_adc.reference_voltage()
|
||||
}
|
||||
|
||||
pub fn is_powered_on(&mut self) -> bool {
|
||||
self.phy.shdn.is_set_high()
|
||||
}
|
||||
|
||||
pub fn power_down(&mut self) {
|
||||
self.phy.shdn.set_low();
|
||||
}
|
||||
|
||||
pub fn power_up(&mut self) {
|
||||
self.phy.shdn.set_high();
|
||||
}
|
||||
|
||||
pub fn set_dac(&mut self, voltage: ElectricPotential, dac_out_v_max: ElectricPotential) -> ElectricPotential {
|
||||
let value = ((voltage / dac_out_v_max).get::<ratio>() * (ad5680::MAX_VALUE as f32)) as u32;
|
||||
self.phy.dac.set(value).unwrap();
|
||||
voltage
|
||||
}
|
||||
|
||||
// AN4073: ADC Reading Dispersion can be reduced through Averaging
|
||||
// Upon test, 16 Point Averaging = +-3 LSB Dispersion
|
||||
pub fn adc_read(&mut self, adc_read_target: AdcReadTarget, avg_pt: u16) -> ElectricPotential {
|
||||
let mut sample: u32 = 0;
|
||||
sample = match adc_read_target {
|
||||
AdcReadTarget::VREF => {
|
||||
for _ in (0..avg_pt).rev() {
|
||||
sample += self
|
||||
.pins_adc
|
||||
.convert(&self.phy.vref_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
|
||||
as u32;
|
||||
}
|
||||
sample / avg_pt as u32
|
||||
}
|
||||
AdcReadTarget::DacVfb => {
|
||||
for _ in (0..avg_pt).rev() {
|
||||
sample += self.pins_adc.convert(
|
||||
&self.phy.dac_feedback_pin,
|
||||
stm32f4xx_hal::adc::config::SampleTime::Cycles_480,
|
||||
) as u32;
|
||||
}
|
||||
sample / avg_pt as u32
|
||||
}
|
||||
AdcReadTarget::ITec => {
|
||||
for _ in (0..avg_pt).rev() {
|
||||
sample += self
|
||||
.pins_adc
|
||||
.convert(&self.phy.itec_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
|
||||
as u32;
|
||||
}
|
||||
sample / avg_pt as u32
|
||||
}
|
||||
AdcReadTarget::VTec => {
|
||||
for _ in (0..avg_pt).rev() {
|
||||
sample += self
|
||||
.pins_adc
|
||||
.convert(&self.phy.vtec_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
|
||||
as u32;
|
||||
}
|
||||
sample / avg_pt as u32
|
||||
}
|
||||
};
|
||||
let mv = self.pins_adc.sample_to_millivolts(sample as u16);
|
||||
ElectricPotential::new::<millivolt>(mv as f32)
|
||||
}
|
||||
|
||||
pub fn set_pwm(&mut self, pwm_pin: PwmPinsEnum, duty: f64, max_duty: f64) -> f64 {
|
||||
fn duty_cycle_value(duty_cycle: f64, duty_cycle_limit: f64, max_value: u16) -> u16 {
|
||||
((duty_cycle.min(duty_cycle_limit) * (max_value as f64)) as u16).min(max_value)
|
||||
}
|
||||
|
||||
let value: u16;
|
||||
let max_value: u16;
|
||||
match pwm_pin {
|
||||
PwmPinsEnum::MaxV => {
|
||||
self.phy.max_v.enable();
|
||||
max_value = self.phy.max_v.get_max_duty();
|
||||
value = duty_cycle_value(duty, max_duty, max_value);
|
||||
self.phy.max_v.set_duty(value);
|
||||
}
|
||||
PwmPinsEnum::MaxPosI => {
|
||||
self.phy.max_i_pos.enable();
|
||||
max_value = self.phy.max_i_pos.get_max_duty();
|
||||
value = duty_cycle_value(duty, max_duty, max_value);
|
||||
self.phy.max_i_pos.set_duty(value);
|
||||
}
|
||||
PwmPinsEnum::MaxNegI => {
|
||||
self.phy.max_i_neg.enable();
|
||||
max_value = self.phy.max_i_neg.get_max_duty();
|
||||
value = duty_cycle_value(duty, max_duty, max_value);
|
||||
self.phy.max_i_neg.set_duty(value);
|
||||
}
|
||||
}
|
||||
return (value as f64) / (max_value as f64);
|
||||
}
|
||||
}
|
||||
|
||||
#[interrupt]
|
||||
fn DMA2_STREAM2() {
|
||||
cortex_m::interrupt::free(|_| {
|
||||
unsafe {
|
||||
// Clear all DMA2_STREAM2 interrupt flags
|
||||
Peripherals::steal().DMA2.lifcr.write(|w| {
|
||||
w.ctcif2()
|
||||
.set_bit()
|
||||
.cdmeif2()
|
||||
.set_bit()
|
||||
.chtif2()
|
||||
.set_bit()
|
||||
.cteif2()
|
||||
.set_bit()
|
||||
});
|
||||
DMA_TRANSFER_COMPLETE = true;
|
||||
}
|
||||
})
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
pub mod ad5680;
|
||||
pub mod ad7172;
|
||||
pub mod max1968;
|
||||
pub mod pid_state;
|
||||
pub mod steinhart_hart;
|
||||
pub mod temp_mon;
|
||||
pub mod thermostat;
|
227
src/thermostat/pid_state.rs
Normal file
227
src/thermostat/pid_state.rs
Normal file
@ -0,0 +1,227 @@
|
||||
use miniconf::Tree;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uom::si::{electric_potential::volt,
|
||||
electrical_resistance::ohm,
|
||||
f32::{ElectricPotential, ElectricalResistance}};
|
||||
|
||||
use crate::thermostat::{ad7172, steinhart_hart as sh};
|
||||
const R_INNER: f32 = 2.0 * 5100.0;
|
||||
const VREF_SENS: f32 = 3.3 / 2.0;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq, Tree)]
|
||||
pub struct Parameters {
|
||||
/// Gain coefficient for proportional term
|
||||
pub kp: f32,
|
||||
/// Gain coefficient for integral term
|
||||
pub ki: f32,
|
||||
/// Gain coefficient for derivative term
|
||||
pub kd: f32,
|
||||
/// Output limit minimum
|
||||
pub output_min: f32,
|
||||
/// Output limit maximum
|
||||
pub output_max: f32,
|
||||
}
|
||||
|
||||
impl Default for Parameters {
|
||||
fn default() -> Self {
|
||||
Parameters {
|
||||
kp: 0.0,
|
||||
ki: 0.0,
|
||||
kd: 0.0,
|
||||
output_min: -1.0,
|
||||
output_max: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Controller {
|
||||
pub parameters: Parameters,
|
||||
u1: f64,
|
||||
x1: f64,
|
||||
x2: f64,
|
||||
pub y1: f64,
|
||||
}
|
||||
|
||||
pub struct PidState {
|
||||
adc_data: Option<u32>,
|
||||
adc_calibration: ad7172::ChannelCalibration,
|
||||
pid_engaged: bool,
|
||||
set_point: f32,
|
||||
sh: sh::Parameters,
|
||||
controller: Controller,
|
||||
}
|
||||
|
||||
impl Default for PidState {
|
||||
fn default() -> Self {
|
||||
PidState {
|
||||
adc_data: None,
|
||||
adc_calibration: ad7172::ChannelCalibration::default(),
|
||||
pid_engaged: false,
|
||||
set_point: 0.0,
|
||||
sh: sh::Parameters::default(),
|
||||
controller: Controller {
|
||||
parameters: Parameters::default(),
|
||||
u1: 0.0,
|
||||
x1: 0.0,
|
||||
x2: 0.0,
|
||||
y1: 0.0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum PidSettings {
|
||||
Kp,
|
||||
Ki,
|
||||
Kd,
|
||||
Min,
|
||||
Max,
|
||||
}
|
||||
|
||||
impl PidState {
|
||||
pub fn update(&mut self, adc_data: u32) {
|
||||
self.adc_data = if adc_data == ad7172::MAX_VALUE {
|
||||
// this means there is no thermistor plugged into the ADC.
|
||||
None
|
||||
} else {
|
||||
Some(adc_data)
|
||||
};
|
||||
}
|
||||
|
||||
// Based on https://hackmd.io/IACbwcOTSt6Adj3_F9bKuw PID implementation
|
||||
// Input x(t), target u(t), output y(t)
|
||||
// y0' = y1 - ki * u0
|
||||
// + x0 * (kp + ki + kd)
|
||||
// - x1 * (kp + 2kd)
|
||||
// + x2 * kd
|
||||
// y0 = clip(y0', ymin, ymax)
|
||||
pub fn update_pid(&mut self) -> Option<f64> {
|
||||
let input = self.get_temperature()?;
|
||||
let setpoint = self.set_point;
|
||||
let mut output: f64 = self.controller.y1 - setpoint as f64 * f64::from(self.controller.parameters.ki)
|
||||
+ input as f64
|
||||
* f64::from(
|
||||
self.controller.parameters.kp + self.controller.parameters.ki + self.controller.parameters.kd,
|
||||
)
|
||||
- self.controller.x1 * f64::from(self.controller.parameters.kp + 2.0 * self.controller.parameters.kd)
|
||||
+ self.controller.x2 * f64::from(self.controller.parameters.kd);
|
||||
if output < self.controller.parameters.output_min.into() {
|
||||
output = self.controller.parameters.output_min.into();
|
||||
}
|
||||
if output > self.controller.parameters.output_max.into() {
|
||||
output = self.controller.parameters.output_max.into();
|
||||
}
|
||||
self.controller.x2 = self.controller.x1;
|
||||
self.controller.x1 = input as f64;
|
||||
self.controller.u1 = setpoint as f64;
|
||||
self.controller.y1 = output;
|
||||
Some(output)
|
||||
}
|
||||
|
||||
pub fn get_adc(&self) -> Option<ElectricPotential> {
|
||||
Some(self.adc_calibration.convert_data(self.adc_data?))
|
||||
}
|
||||
|
||||
/// Get `SENS[01]` input resistance
|
||||
pub fn get_sens(&self) -> Option<ElectricalResistance> {
|
||||
let r_inner = ElectricalResistance::new::<ohm>(R_INNER);
|
||||
let vref = ElectricPotential::new::<volt>(VREF_SENS);
|
||||
let adc_input = self.get_adc()?;
|
||||
let r = r_inner * adc_input / (vref - adc_input);
|
||||
Some(r)
|
||||
}
|
||||
|
||||
pub fn get_temperature(&self) -> Option<f32> {
|
||||
let r = self.get_sens()?;
|
||||
let temperature = self.sh.get_temperature(r);
|
||||
Some(temperature)
|
||||
}
|
||||
|
||||
pub fn apply_pid_params(&mut self, pid_params: Parameters) {
|
||||
self.controller.parameters = pid_params;
|
||||
}
|
||||
|
||||
pub fn set_pid_params(&mut self, param: PidSettings, val: f32, curr_rate: f32) {
|
||||
match param {
|
||||
PidSettings::Kp => {
|
||||
self.controller.parameters.kp = val;
|
||||
}
|
||||
PidSettings::Ki => {
|
||||
self.controller.parameters.ki = val * curr_rate;
|
||||
}
|
||||
PidSettings::Kd => {
|
||||
self.controller.parameters.kd = val / curr_rate;
|
||||
}
|
||||
PidSettings::Min => {
|
||||
self.controller.parameters.output_min = val;
|
||||
}
|
||||
PidSettings::Max => {
|
||||
self.controller.parameters.output_max = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_pid_params_with_new_sampling_rate(&mut self, old_rate: f32, new_rate: f32) {
|
||||
self.controller.parameters.ki = self.controller.parameters.ki * old_rate / new_rate;
|
||||
self.controller.parameters.kd = self.controller.parameters.kd * new_rate / old_rate;
|
||||
}
|
||||
|
||||
pub fn get_abs_pid_params(&mut self, curr_rate: f32) -> Parameters {
|
||||
let mut pid_params = self.controller.parameters.clone();
|
||||
pid_params.ki = self.controller.parameters.ki / curr_rate;
|
||||
pid_params.kd = self.controller.parameters.kd * curr_rate;
|
||||
pid_params
|
||||
}
|
||||
|
||||
pub fn reset_pid_state(&mut self) {
|
||||
self.controller.u1 = 0.0;
|
||||
self.controller.x1 = 0.0;
|
||||
self.controller.x2 = 0.0;
|
||||
self.controller.y1 = 0.0;
|
||||
}
|
||||
|
||||
pub fn set_pid_setpoint(&mut self, temperature: f32) {
|
||||
self.set_point = temperature;
|
||||
}
|
||||
|
||||
pub fn get_pid_setpoint(&mut self) -> f32 {
|
||||
self.set_point
|
||||
}
|
||||
|
||||
pub fn set_sh_t0(&mut self, t0: f32) {
|
||||
self.sh.t0 = t0
|
||||
}
|
||||
|
||||
pub fn set_sh_r0(&mut self, r0: ElectricalResistance) {
|
||||
self.sh.r0 = r0
|
||||
}
|
||||
|
||||
pub fn set_sh_beta(&mut self, beta: f32) {
|
||||
self.sh.b = beta
|
||||
}
|
||||
|
||||
pub fn set_adc_calibration(&mut self, adc_cal: ad7172::ChannelCalibration) {
|
||||
self.adc_calibration = adc_cal;
|
||||
}
|
||||
|
||||
pub fn set_pid_engaged(&mut self, pid_engaged: bool) {
|
||||
self.pid_engaged = pid_engaged;
|
||||
}
|
||||
|
||||
pub fn get_pid_engaged(&mut self) -> bool {
|
||||
self.pid_engaged
|
||||
}
|
||||
|
||||
pub fn get_pid_settings(&mut self) -> Parameters {
|
||||
self.controller.parameters
|
||||
}
|
||||
|
||||
pub fn get_sh(&mut self) -> sh::Parameters {
|
||||
self.sh
|
||||
}
|
||||
|
||||
pub fn apply_sh(&mut self, sh: sh::Parameters) {
|
||||
self.sh = sh;
|
||||
}
|
||||
}
|
35
src/thermostat/steinhart_hart.rs
Normal file
35
src/thermostat/steinhart_hart.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use miniconf::Tree;
|
||||
use num_traits::float::Float;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uom::si::{electrical_resistance::ohm,
|
||||
f32::ElectricalResistance,
|
||||
ratio::ratio};
|
||||
|
||||
/// Steinhart-Hart equation parameters
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct Parameters {
|
||||
/// Base temperature (Degree Celsius)
|
||||
pub t0: f32,
|
||||
/// Base resistance
|
||||
pub r0: ElectricalResistance,
|
||||
/// Beta (Kelvin)
|
||||
pub b: f32,
|
||||
}
|
||||
|
||||
impl Parameters {
|
||||
/// Perform the voltage to temperature conversion.
|
||||
pub fn get_temperature(&self, r: ElectricalResistance) -> f32 {
|
||||
let inv_temp = 1.0 / (self.t0 + 273.15) + (r / self.r0).get::<ratio>().ln() / self.b;
|
||||
1.0 / inv_temp - 273.15
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Parameters {
|
||||
fn default() -> Self {
|
||||
Parameters {
|
||||
t0: 25.0,
|
||||
r0: ElectricalResistance::new::<ohm>(10_000.0),
|
||||
b: 3800.0,
|
||||
}
|
||||
}
|
||||
}
|
197
src/thermostat/temp_mon.rs
Normal file
197
src/thermostat/temp_mon.rs
Normal file
@ -0,0 +1,197 @@
|
||||
use miniconf::Tree;
|
||||
use num_traits::Float;
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[derive(PartialEq, Deserialize, Serialize, Copy, Clone, Default, Debug)]
|
||||
pub enum TempStatusEnum {
|
||||
#[default]
|
||||
Off,
|
||||
OverTemp,
|
||||
Unstable,
|
||||
Stable,
|
||||
ConstantCurrentMode,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct TempStatus {
|
||||
pub status: TempStatusEnum,
|
||||
pub over_temp_alarm: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct TempMonSettings {
|
||||
pub upper_limit: f32,
|
||||
pub lower_limit: f32,
|
||||
}
|
||||
|
||||
pub struct TempMon {
|
||||
pub upper_limit: f32,
|
||||
pub lower_limit: f32,
|
||||
pub set_point: f32,
|
||||
pub status: TempStatus,
|
||||
state: State,
|
||||
count: u32,
|
||||
is_set_point_changed: bool,
|
||||
is_limit_changed: bool,
|
||||
}
|
||||
|
||||
impl Default for TempMon {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
upper_limit: 45.0,
|
||||
lower_limit: 0.0,
|
||||
set_point: 0.0,
|
||||
status: TempStatus {
|
||||
status: TempStatusEnum::Off,
|
||||
over_temp_alarm: false,
|
||||
},
|
||||
state: State::default(),
|
||||
count: 0,
|
||||
// stable_temp_count: 0,
|
||||
is_set_point_changed: false,
|
||||
is_limit_changed: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Debug)]
|
||||
enum State {
|
||||
#[default]
|
||||
PwrOff,
|
||||
ConstantCurrentMode,
|
||||
PidStartUp,
|
||||
PidStable,
|
||||
OverTempAlarm,
|
||||
}
|
||||
|
||||
impl TempMon {
|
||||
const OVER_TEMP_COUNT_LIMIT: u32 = 25;
|
||||
const TEMP_STABLE_COUNT_LIMIT: u32 = 100;
|
||||
|
||||
pub fn set_upper_limit(&mut self, upper_limit: f32) {
|
||||
self.upper_limit = upper_limit;
|
||||
self.is_limit_changed = true;
|
||||
}
|
||||
|
||||
pub fn get_upper_limit(&mut self) -> f32 {
|
||||
self.upper_limit
|
||||
}
|
||||
|
||||
pub fn set_lower_limit(&mut self, lower_limit: f32) {
|
||||
self.lower_limit = lower_limit;
|
||||
self.is_limit_changed = true;
|
||||
}
|
||||
|
||||
pub fn get_lower_limit(&mut self) -> f32 {
|
||||
self.lower_limit
|
||||
}
|
||||
|
||||
pub fn set_setpoint(&mut self, set_point: f32) {
|
||||
if self.set_point != set_point {
|
||||
self.is_set_point_changed = true;
|
||||
self.count = 0;
|
||||
}
|
||||
self.set_point = set_point;
|
||||
}
|
||||
|
||||
pub fn clear_alarm(&mut self) {
|
||||
self.status.over_temp_alarm = false;
|
||||
self.state = State::default();
|
||||
}
|
||||
|
||||
pub fn update_status(&mut self, pid_engaged: bool, pwr_on: bool, temp: f32) {
|
||||
match self.state {
|
||||
State::PwrOff => {
|
||||
self.is_set_point_changed = false;
|
||||
self.status.status = TempStatusEnum::Off;
|
||||
self.count = 0;
|
||||
|
||||
// State Transition
|
||||
if pwr_on {
|
||||
if pid_engaged {
|
||||
self.state = State::PidStartUp;
|
||||
} else {
|
||||
self.state = State::ConstantCurrentMode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
State::ConstantCurrentMode => {
|
||||
let is_over_temp = temp > self.upper_limit || temp < self.lower_limit;
|
||||
self.status.status = TempStatusEnum::ConstantCurrentMode;
|
||||
|
||||
if is_over_temp {
|
||||
self.state = State::OverTempAlarm;
|
||||
self.status.status = TempStatusEnum::OverTemp;
|
||||
} else if !pwr_on {
|
||||
self.state = State::PwrOff;
|
||||
self.status.status = TempStatusEnum::Off;
|
||||
} else if pid_engaged {
|
||||
self.state = State::PidStartUp;
|
||||
self.status.status = TempStatusEnum::Unstable;
|
||||
self.is_set_point_changed = false;
|
||||
}
|
||||
}
|
||||
State::PidStartUp | State::PidStable => {
|
||||
let mut is_over_temp = temp > self.upper_limit || temp < self.lower_limit;
|
||||
if self.state != State::PidStartUp {
|
||||
is_over_temp = is_over_temp || (temp - self.set_point).abs() > 0.5;
|
||||
}
|
||||
|
||||
let is_within_spec: bool = (temp - self.set_point).abs() < 0.001;
|
||||
if is_over_temp {
|
||||
if self.count > TempMon::OVER_TEMP_COUNT_LIMIT {
|
||||
self.status.status = TempStatusEnum::OverTemp;
|
||||
} else {
|
||||
self.count += 1;
|
||||
}
|
||||
} else if is_within_spec {
|
||||
if self.count > TempMon::TEMP_STABLE_COUNT_LIMIT {
|
||||
self.status.status = TempStatusEnum::Stable;
|
||||
} else {
|
||||
self.count += 1;
|
||||
}
|
||||
} else {
|
||||
self.status.status = TempStatusEnum::Unstable;
|
||||
self.count = 0;
|
||||
}
|
||||
|
||||
// State Transition
|
||||
if !pwr_on {
|
||||
self.state = State::PwrOff;
|
||||
} else {
|
||||
if self.status.status == TempStatusEnum::OverTemp {
|
||||
self.state = State::OverTempAlarm;
|
||||
} else if self.is_set_point_changed {
|
||||
self.is_set_point_changed = false;
|
||||
self.state = State::PidStartUp;
|
||||
} else if self.is_limit_changed {
|
||||
self.is_limit_changed = false;
|
||||
if is_over_temp {
|
||||
self.state = State::PidStartUp;
|
||||
self.count = 0;
|
||||
}
|
||||
} else if self.status.status == TempStatusEnum::Stable {
|
||||
self.state = State::PidStable;
|
||||
} else if !pid_engaged {
|
||||
self.state = State::ConstantCurrentMode
|
||||
}
|
||||
}
|
||||
}
|
||||
State::OverTempAlarm => {
|
||||
self.is_set_point_changed = false;
|
||||
self.status.over_temp_alarm = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_status(&mut self) -> TempStatus {
|
||||
self.status
|
||||
}
|
||||
|
||||
pub fn get_settings(&mut self) -> TempMonSettings {
|
||||
TempMonSettings {
|
||||
upper_limit: self.upper_limit,
|
||||
lower_limit: self.lower_limit,
|
||||
}
|
||||
}
|
||||
}
|
645
src/thermostat/thermostat.rs
Normal file
645
src/thermostat/thermostat.rs
Normal file
@ -0,0 +1,645 @@
|
||||
use core::{f32::NAN, marker::PhantomData};
|
||||
|
||||
use log::debug;
|
||||
use miniconf::Tree;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uom::si::{electric_current::ampere,
|
||||
electric_potential::volt,
|
||||
electrical_resistance::ohm,
|
||||
f32::{ElectricCurrent, ElectricPotential, ElectricalResistance},
|
||||
ratio::ratio};
|
||||
|
||||
use crate::{sys_timer,
|
||||
thermostat::{ad5680,
|
||||
ad7172::{self, FilterType, PostFilter, SingleChODR},
|
||||
max1968::{AdcReadTarget, PwmPinsEnum, MAX1968},
|
||||
pid_state,
|
||||
pid_state::{Parameters as PidParams, PidSettings, PidState},
|
||||
steinhart_hart::Parameters as Sh_Params,
|
||||
temp_mon::{TempMon, TempMonSettings, TempStatus}}};
|
||||
|
||||
pub const R_SENSE: ElectricalResistance = ElectricalResistance {
|
||||
dimension: PhantomData,
|
||||
units: PhantomData,
|
||||
value: 0.05,
|
||||
};
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Default, Tree)]
|
||||
pub struct TempAdcFilter {
|
||||
pub filter_type: FilterType,
|
||||
pub sinc5sinc1odr: Option<SingleChODR>,
|
||||
pub sinc3odr: Option<SingleChODR>,
|
||||
pub sinc5sinc1postfilter: Option<PostFilter>,
|
||||
pub sinc3fineodr: Option<f32>,
|
||||
pub rate: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Copy, Debug, Tree)]
|
||||
pub struct TecSettings {
|
||||
pub default_pwr_on: bool,
|
||||
pub center_pt: ElectricPotential,
|
||||
pub max_v_set: ElectricPotential,
|
||||
pub max_i_pos_set: ElectricCurrent,
|
||||
pub max_i_neg_set: ElectricCurrent,
|
||||
pub i_set: ElectricCurrent,
|
||||
pub vref: ElectricPotential,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Copy, Debug, Tree, Default)]
|
||||
pub struct Time {
|
||||
ms: u32,
|
||||
us: u32,
|
||||
}
|
||||
|
||||
impl TecSettings {
|
||||
pub const TEC_VSEC_BIAS_V: ElectricPotential = ElectricPotential {
|
||||
dimension: PhantomData,
|
||||
units: PhantomData,
|
||||
value: 1.65,
|
||||
};
|
||||
const MAX_I_SET: ElectricCurrent = ElectricCurrent {
|
||||
dimension: PhantomData,
|
||||
units: PhantomData,
|
||||
value: 3.0,
|
||||
};
|
||||
const MAX_V_DUTY_TO_VOLTAGE_RATE: ElectricPotential = ElectricPotential {
|
||||
dimension: PhantomData,
|
||||
units: PhantomData,
|
||||
value: 4.0 * 3.3,
|
||||
};
|
||||
pub const MAX_V_MAX: ElectricPotential = ElectricPotential {
|
||||
dimension: PhantomData,
|
||||
units: PhantomData,
|
||||
value: 4.3,
|
||||
};
|
||||
const MAX_V_DUTY_MAX: f64 =
|
||||
TecSettings::MAX_V_MAX.value as f64 / TecSettings::MAX_V_DUTY_TO_VOLTAGE_RATE.value as f64;
|
||||
const MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE: ElectricCurrent = ElectricCurrent {
|
||||
dimension: PhantomData,
|
||||
units: PhantomData,
|
||||
value: 1.0 / (10.0 * R_SENSE.value / 3.3),
|
||||
};
|
||||
pub const MAX_I_POS_CURRENT: ElectricCurrent = ElectricCurrent {
|
||||
dimension: PhantomData,
|
||||
units: PhantomData,
|
||||
value: 3.0,
|
||||
};
|
||||
pub const MAX_I_NEG_CURRENT: ElectricCurrent = ElectricCurrent {
|
||||
dimension: PhantomData,
|
||||
units: PhantomData,
|
||||
value: 3.0,
|
||||
};
|
||||
// .get::<ratio>() is not implemented for const
|
||||
const MAX_I_POS_DUTY_MAX: f64 =
|
||||
TecSettings::MAX_I_POS_CURRENT.value as f64 / TecSettings::MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE.value as f64;
|
||||
const MAX_I_NEG_DUTY_MAX: f64 =
|
||||
TecSettings::MAX_I_NEG_CURRENT.value as f64 / TecSettings::MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE.value as f64;
|
||||
}
|
||||
|
||||
impl Default for TecSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
default_pwr_on: false,
|
||||
center_pt: ElectricPotential::new::<volt>(1.5),
|
||||
max_v_set: ElectricPotential::new::<volt>(5.0),
|
||||
max_i_pos_set: ElectricCurrent::new::<ampere>(1.0),
|
||||
max_i_neg_set: ElectricCurrent::new::<ampere>(1.0),
|
||||
i_set: ElectricCurrent::new::<ampere>(0.0),
|
||||
vref: ElectricPotential::new::<volt>(1.5),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Thermostat {
|
||||
max1968: MAX1968,
|
||||
ad7172: ad7172::AdcPhy,
|
||||
interval: Time,
|
||||
prev_ts: Time,
|
||||
pub tec_settings: TecSettings,
|
||||
pid_ctrl_ch0: PidState,
|
||||
temp_mon: TempMon,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct ThermostatSettingsSummary {
|
||||
default_pwr_on: bool,
|
||||
pid_engaged: bool,
|
||||
temperature_setpoint: f32,
|
||||
tec_settings: TecSettingSummary,
|
||||
pid_params: PidParams,
|
||||
temp_adc_settings: TempAdcFilter,
|
||||
temp_mon_settings: TempMonSettings,
|
||||
thermistor_params: ThermistorParams,
|
||||
}
|
||||
|
||||
impl Thermostat {
|
||||
pub fn new(max1968: MAX1968, ad7172: ad7172::AdcPhy) -> Self {
|
||||
Thermostat {
|
||||
max1968: max1968,
|
||||
ad7172: ad7172,
|
||||
interval: Time::default(),
|
||||
prev_ts: Time::default(),
|
||||
tec_settings: TecSettings::default(),
|
||||
pid_ctrl_ch0: PidState::default(),
|
||||
temp_mon: TempMon::default(),
|
||||
}
|
||||
}
|
||||
pub fn setup(&mut self) {
|
||||
self.tec_setup();
|
||||
let t_adc_ch0_cal = self.t_adc_setup();
|
||||
self.pid_ctrl_ch0.set_adc_calibration(t_adc_ch0_cal);
|
||||
}
|
||||
|
||||
/// start_tec_readings_conversion() should not be called before the current
|
||||
/// DMA request is serviced or the conversion process will be restarted
|
||||
/// Thus, no new readings is available when you call get_tec_readings() fn
|
||||
pub fn start_tec_readings_conversion(&mut self) {
|
||||
self.max1968.dma_adc_start_conversion();
|
||||
}
|
||||
|
||||
fn tec_setup(&mut self) {
|
||||
self.power_down();
|
||||
|
||||
self.tec_settings = TecSettings::default();
|
||||
|
||||
self.set_i(self.tec_settings.i_set);
|
||||
|
||||
self.set_max_v(self.tec_settings.max_v_set);
|
||||
self.set_max_i_pos(self.tec_settings.max_i_pos_set);
|
||||
self.set_max_i_neg(self.tec_settings.max_i_neg_set);
|
||||
}
|
||||
|
||||
fn t_adc_setup(&mut self) -> ad7172::ChannelCalibration {
|
||||
self.ad7172.set_sync_enable(false).unwrap();
|
||||
self.ad7172
|
||||
.setup_channel(0, ad7172::Input::Ain0, ad7172::Input::Ain1)
|
||||
.unwrap();
|
||||
let adc_calibration0 = self.ad7172.get_calibration(0).expect("adc_calibration0");
|
||||
self.ad7172.start_continuous_conversion().unwrap();
|
||||
adc_calibration0
|
||||
}
|
||||
|
||||
pub fn poll_adc(&mut self) -> bool {
|
||||
let mut data_rdy = false;
|
||||
self.ad7172.data_ready().unwrap().map(|_ch| {
|
||||
let data = self.ad7172.read_data().unwrap();
|
||||
let state: &mut PidState = &mut self.pid_ctrl_ch0;
|
||||
state.update(data);
|
||||
let pid_engaged = state.get_pid_engaged();
|
||||
let temp = self.get_temperature();
|
||||
self.temp_mon
|
||||
.update_status(pid_engaged, self.max1968.is_powered_on(), temp);
|
||||
debug!("state.get_pid_engaged(): {:?}", pid_engaged);
|
||||
debug!("Temperature: {:?} degree", temp);
|
||||
data_rdy = true;
|
||||
|
||||
let (ms, us) = sys_timer::now_precise();
|
||||
if us < self.prev_ts.us {
|
||||
self.interval = Time {
|
||||
ms: ms - self.prev_ts.ms - 1,
|
||||
us: (us + 1000).abs_diff(self.prev_ts.us),
|
||||
};
|
||||
} else {
|
||||
self.interval = Time {
|
||||
ms: ms - self.prev_ts.ms,
|
||||
us: (us).abs_diff(self.prev_ts.us),
|
||||
};
|
||||
}
|
||||
self.prev_ts = Time {
|
||||
ms: ms,
|
||||
us: us,
|
||||
};
|
||||
});
|
||||
data_rdy
|
||||
}
|
||||
|
||||
pub fn get_poll_interval(&mut self) -> Time {
|
||||
self.interval
|
||||
}
|
||||
|
||||
pub fn update_pid(&mut self) {
|
||||
let state: &mut PidState = &mut self.pid_ctrl_ch0;
|
||||
let pid_engaged = state.get_pid_engaged();
|
||||
if pid_engaged {
|
||||
match state.update_pid() {
|
||||
Some(pid_output) => {
|
||||
self.set_i(ElectricCurrent::new::<ampere>(pid_output as f32));
|
||||
debug!(
|
||||
"Temperature Set Point: {:?} degree",
|
||||
self.pid_ctrl_ch0.get_pid_setpoint()
|
||||
);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_temp_mon_status(&mut self) -> TempStatus {
|
||||
self.temp_mon.get_status()
|
||||
}
|
||||
|
||||
pub fn power_up(&mut self) {
|
||||
if !self.max1968.is_powered_on() {
|
||||
self.max1968.power_up();
|
||||
self.pid_ctrl_ch0.reset_pid_state();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn power_down(&mut self) {
|
||||
self.max1968.power_down();
|
||||
}
|
||||
|
||||
fn set_center_pt(&mut self, value: ElectricPotential) {
|
||||
self.tec_settings.center_pt = value;
|
||||
}
|
||||
|
||||
pub fn set_default_pwr_on(&mut self, pwr_on: bool) {
|
||||
self.tec_settings.default_pwr_on = pwr_on;
|
||||
}
|
||||
|
||||
pub fn set_i(&mut self, i_tec: ElectricCurrent) -> ElectricCurrent {
|
||||
let voltage = i_tec * 10.0 * R_SENSE + self.tec_settings.center_pt;
|
||||
let _voltage = self.max1968.set_dac(voltage, self.max1968.dac_out_range);
|
||||
|
||||
self.tec_settings.i_set = i_tec;
|
||||
self.tec_settings.i_set
|
||||
}
|
||||
|
||||
pub fn set_max_v(&mut self, max_v: ElectricPotential) -> ElectricPotential {
|
||||
let duty = (max_v / TecSettings::MAX_V_DUTY_TO_VOLTAGE_RATE).get::<ratio>();
|
||||
let _duty = self
|
||||
.max1968
|
||||
.set_pwm(PwmPinsEnum::MaxV, duty as f64, TecSettings::MAX_V_DUTY_MAX);
|
||||
|
||||
self.tec_settings.max_v_set = max_v;
|
||||
self.tec_settings.max_v_set
|
||||
}
|
||||
|
||||
pub fn set_max_i_pos(&mut self, max_i_pos: ElectricCurrent) -> ElectricCurrent {
|
||||
let duty = (max_i_pos / TecSettings::MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE).get::<ratio>();
|
||||
let _duty = self
|
||||
.max1968
|
||||
.set_pwm(PwmPinsEnum::MaxPosI, duty as f64, TecSettings::MAX_I_POS_DUTY_MAX);
|
||||
|
||||
self.tec_settings.max_i_pos_set = max_i_pos;
|
||||
self.tec_settings.max_i_pos_set
|
||||
}
|
||||
|
||||
pub fn set_max_i_neg(&mut self, max_i_neg: ElectricCurrent) -> ElectricCurrent {
|
||||
let duty = (max_i_neg / TecSettings::MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE).get::<ratio>();
|
||||
let _duty = self
|
||||
.max1968
|
||||
.set_pwm(PwmPinsEnum::MaxNegI, duty as f64, TecSettings::MAX_I_NEG_DUTY_MAX);
|
||||
|
||||
self.tec_settings.max_i_neg_set = max_i_neg;
|
||||
self.tec_settings.max_i_neg_set
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn get_dac_vfb(&mut self) -> ElectricPotential {
|
||||
self.max1968.adc_read(AdcReadTarget::DacVfb, 16)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn get_vref(&mut self) -> ElectricPotential {
|
||||
self.max1968.adc_read(AdcReadTarget::VREF, 16)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn get_tec_i(&mut self) -> ElectricCurrent {
|
||||
let vref = self.max1968.adc_read(AdcReadTarget::VREF, 16);
|
||||
(self.max1968.adc_read(AdcReadTarget::ITec, 16) - vref) / ElectricalResistance::new::<ohm>(0.4)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn get_tec_v(&mut self) -> ElectricPotential {
|
||||
(self.max1968.adc_read(AdcReadTarget::VTec, 16) - TecSettings::TEC_VSEC_BIAS_V) * 4.0
|
||||
}
|
||||
|
||||
pub fn get_tec_readings(&mut self) -> (ElectricPotential, ElectricCurrent) {
|
||||
let vref = self.tec_settings.vref;
|
||||
let (vtec, itec) = self.max1968.get_tec_readings();
|
||||
(
|
||||
(vtec - TecSettings::TEC_VSEC_BIAS_V) * 4.0,
|
||||
(itec - vref) / ElectricalResistance::new::<ohm>(0.4),
|
||||
)
|
||||
}
|
||||
|
||||
/// Calibrates the DAC output to match vref of the MAX driver to reduce zero-current offset of the MAX driver output.
|
||||
///
|
||||
/// The thermostat DAC applies a control voltage signal to the CTLI pin of MAX driver chip to control its output current.
|
||||
/// The CTLI input signal is centered around VREF of the MAX chip. Applying VREF to CTLI sets the output current to 0.
|
||||
///
|
||||
/// This calibration routine measures the VREF voltage and the DAC output with the STM32 ADC, and uses a breadth-first
|
||||
/// search to find the DAC setting that will produce a DAC output voltage closest to VREF. This DAC output voltage will
|
||||
/// be stored and used in subsequent i_set routines to bias the current control signal to the measured VREF, reducing
|
||||
/// the offset error of the current control signal.
|
||||
///
|
||||
/// The input offset of the STM32 ADC is eliminated by using the same ADC for the measurements, and by only using the
|
||||
/// difference in VREF and DAC output for the calibration.
|
||||
///
|
||||
/// This routine should be called only once after boot, repeated reading of the vref signal and changing of the stored
|
||||
/// VREF measurement can introduce significant noise at the current output, degrading the stabilily performance of the
|
||||
/// thermostat.
|
||||
pub fn calibrate_dac_value(&mut self) {
|
||||
const DAC_BIT: u32 = 18;
|
||||
const ADC_BIT: u32 = 12;
|
||||
let target_voltage = self.max1968.adc_read(AdcReadTarget::VREF, 512);
|
||||
let mut start_value = 1;
|
||||
let mut best_error = ElectricPotential::new::<volt>(100.0);
|
||||
for step in (DAC_BIT - ADC_BIT - 1..DAC_BIT).rev() {
|
||||
for value in (start_value..=ad5680::MAX_VALUE).step_by(1 << step) {
|
||||
self.max1968.phy.dac.set(value).unwrap();
|
||||
sys_timer::sleep(5);
|
||||
|
||||
let dac_feedback = self.max1968.adc_read(AdcReadTarget::DacVfb, 64);
|
||||
let error = target_voltage - dac_feedback;
|
||||
if error < ElectricPotential::new::<volt>(0.0) {
|
||||
break;
|
||||
} else if error < best_error {
|
||||
best_error = error;
|
||||
start_value = value;
|
||||
|
||||
let vref = (value as f32 / ad5680::MAX_VALUE as f32) * self.max1968.dac_out_range;
|
||||
self.set_center_pt(vref);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.tec_settings.vref = target_voltage;
|
||||
}
|
||||
|
||||
pub fn set_pid_engaged(&mut self, val: bool) {
|
||||
self.pid_ctrl_ch0.set_pid_engaged(val);
|
||||
}
|
||||
|
||||
fn get_pid_engaged(&mut self) -> bool {
|
||||
self.pid_ctrl_ch0.get_pid_engaged()
|
||||
}
|
||||
|
||||
pub fn get_status_report(&mut self) -> StatusReport {
|
||||
let (tec_v, tec_i) = self.get_tec_readings();
|
||||
let temperature: Option<f32>;
|
||||
|
||||
match self.pid_ctrl_ch0.get_temperature() {
|
||||
Some(val) => temperature = Some(val),
|
||||
None => {
|
||||
temperature = None;
|
||||
}
|
||||
}
|
||||
|
||||
StatusReport {
|
||||
interval: self.get_poll_interval(),
|
||||
pwr_on: self.max1968.is_powered_on(),
|
||||
pid_engaged: self.get_pid_engaged(),
|
||||
temp_mon_status: self.temp_mon.get_status(),
|
||||
temperature: temperature,
|
||||
i_set: self.tec_settings.i_set,
|
||||
tec_i: tec_i,
|
||||
tec_v: tec_v,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_temperature(&mut self) -> f32 {
|
||||
match self.pid_ctrl_ch0.get_temperature() {
|
||||
Some(val) => val,
|
||||
None => NAN,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_pid_settings(&mut self) -> pid_state::Parameters {
|
||||
self.pid_ctrl_ch0.get_pid_settings()
|
||||
}
|
||||
|
||||
fn get_steinhart_hart(&mut self) -> ThermistorParams {
|
||||
let sh = self.pid_ctrl_ch0.get_sh();
|
||||
ThermistorParams {
|
||||
t0: sh.t0,
|
||||
r0: sh.r0,
|
||||
b: sh.b,
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_steinhart_hart(&mut self, sh: ThermistorParams) {
|
||||
self.pid_ctrl_ch0.apply_sh(Sh_Params {
|
||||
t0: sh.t0,
|
||||
r0: sh.r0,
|
||||
b: sh.b,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_tec_settings(&mut self) -> TecSettingSummary {
|
||||
TecSettingSummary {
|
||||
i_set: TecSettingsSummaryField {
|
||||
value: self.tec_settings.i_set,
|
||||
max: TecSettings::MAX_I_SET,
|
||||
},
|
||||
max_v: TecSettingsSummaryField {
|
||||
value: self.tec_settings.max_v_set,
|
||||
max: TecSettings::MAX_V_MAX,
|
||||
},
|
||||
max_i_pos: TecSettingsSummaryField {
|
||||
value: self.tec_settings.max_i_pos_set,
|
||||
max: TecSettings::MAX_I_POS_CURRENT,
|
||||
},
|
||||
max_i_neg: TecSettingsSummaryField {
|
||||
value: self.tec_settings.max_i_neg_set,
|
||||
max: TecSettings::MAX_I_NEG_CURRENT,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_calibrated_vdda(&mut self) -> u32 {
|
||||
self.max1968.get_calibrated_vdda()
|
||||
}
|
||||
|
||||
pub fn set_pid(&mut self, param: PidSettings, val: f32) {
|
||||
let curr_rate = self.ad7172.get_filter_type_and_rate(0).unwrap().3;
|
||||
self.pid_ctrl_ch0.set_pid_params(param, val, curr_rate);
|
||||
}
|
||||
|
||||
pub fn set_sh_beta(&mut self, beta: f32) {
|
||||
self.pid_ctrl_ch0.set_sh_beta(beta);
|
||||
}
|
||||
|
||||
pub fn set_sh_r0(&mut self, r0: ElectricalResistance) {
|
||||
self.pid_ctrl_ch0.set_sh_r0(r0);
|
||||
}
|
||||
|
||||
pub fn set_sh_t0(&mut self, t0: f32) {
|
||||
self.pid_ctrl_ch0.set_sh_t0(t0);
|
||||
}
|
||||
|
||||
pub fn set_temperature_setpoint(&mut self, t: f32) {
|
||||
let t = t
|
||||
.min(self.temp_mon.get_upper_limit())
|
||||
.max(self.temp_mon.get_lower_limit());
|
||||
self.pid_ctrl_ch0.set_pid_setpoint(t);
|
||||
self.temp_mon.set_setpoint(t);
|
||||
}
|
||||
|
||||
pub fn apply_temp_mon_settings(&mut self, settings: TempMonSettings) {
|
||||
self.temp_mon
|
||||
.set_upper_limit(settings.upper_limit);
|
||||
self.temp_mon
|
||||
.set_lower_limit(settings.lower_limit);
|
||||
}
|
||||
|
||||
pub fn set_temp_mon_upper_limit(&mut self, t: f32) {
|
||||
self.temp_mon.set_upper_limit(t);
|
||||
}
|
||||
|
||||
pub fn set_temp_mon_lower_limit(&mut self, t: f32) {
|
||||
self.temp_mon.set_lower_limit(t);
|
||||
}
|
||||
|
||||
pub fn set_temp_adc_sinc5_sinc1_filter(&mut self, index: u8, odr: ad7172::SingleChODR) {
|
||||
let old_rate = self.ad7172.get_filter_type_and_rate(0).unwrap().3;
|
||||
let new_rate = odr.output_rate().unwrap();
|
||||
self.pid_ctrl_ch0
|
||||
.update_pid_params_with_new_sampling_rate(old_rate, new_rate);
|
||||
self.ad7172.set_sinc5_sinc1_filter(index, odr).unwrap();
|
||||
}
|
||||
|
||||
pub fn set_temp_adc_sinc3_filter(&mut self, index: u8, odr: ad7172::SingleChODR) {
|
||||
let old_rate = self.ad7172.get_filter_type_and_rate(0).unwrap().3;
|
||||
let new_rate = odr.output_rate().unwrap();
|
||||
self.pid_ctrl_ch0
|
||||
.update_pid_params_with_new_sampling_rate(old_rate, new_rate);
|
||||
self.ad7172.set_sinc3_filter(index, odr).unwrap();
|
||||
}
|
||||
|
||||
pub fn set_temp_adc_sinc5_sinc1_with_postfilter(&mut self, index: u8, odr: ad7172::PostFilter) {
|
||||
let old_rate = self.ad7172.get_filter_type_and_rate(0).unwrap().3;
|
||||
let new_rate = odr.output_rate().unwrap();
|
||||
self.pid_ctrl_ch0
|
||||
.update_pid_params_with_new_sampling_rate(old_rate, new_rate);
|
||||
self.ad7172
|
||||
.set_sinc5_sinc1_with_50hz_60hz_rejection(index, odr)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn set_temp_adc_sinc3_fine_filter(&mut self, index: u8, rate: f32) {
|
||||
let old_rate = self.ad7172.get_filter_type_and_rate(0).unwrap().3;
|
||||
self.pid_ctrl_ch0
|
||||
.update_pid_params_with_new_sampling_rate(old_rate, rate);
|
||||
self.ad7172.set_sinc3_fine_filter(index, rate).unwrap();
|
||||
}
|
||||
|
||||
pub fn clear_temp_mon_alarm(&mut self) {
|
||||
self.temp_mon.clear_alarm();
|
||||
}
|
||||
|
||||
fn get_temp_mon_settings(&mut self) -> TempMonSettings {
|
||||
self.temp_mon.get_settings()
|
||||
}
|
||||
|
||||
pub fn get_settings_summary(&mut self) -> ThermostatSettingsSummary {
|
||||
let mut temp_adc_settings: TempAdcFilter = TempAdcFilter::default();
|
||||
match self.ad7172.get_filter_type_and_rate(0) {
|
||||
Ok((filter_type, single_ch_odr, post_filter, rate)) => {
|
||||
temp_adc_settings.filter_type = filter_type;
|
||||
temp_adc_settings.rate = Some(rate);
|
||||
match temp_adc_settings.filter_type {
|
||||
FilterType::Sinc5Sinc1 => {
|
||||
temp_adc_settings.sinc5sinc1odr = single_ch_odr;
|
||||
}
|
||||
FilterType::Sinc3 => {
|
||||
temp_adc_settings.sinc3odr = single_ch_odr;
|
||||
}
|
||||
FilterType::Sinc3WithFineODR => {
|
||||
temp_adc_settings.sinc3fineodr = Some(rate)
|
||||
}
|
||||
FilterType::Sinc5Sinc1With50hz60HzRejection => {
|
||||
temp_adc_settings.sinc5sinc1postfilter = post_filter
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
panic!("Cannot read ADC filter type and rate");
|
||||
}
|
||||
}
|
||||
|
||||
ThermostatSettingsSummary {
|
||||
default_pwr_on: self.tec_settings.default_pwr_on,
|
||||
pid_engaged: self.get_pid_engaged(),
|
||||
temperature_setpoint: self.pid_ctrl_ch0.get_pid_setpoint(),
|
||||
tec_settings: self.get_tec_settings(),
|
||||
pid_params: self.pid_ctrl_ch0.get_abs_pid_params(temp_adc_settings.rate.unwrap()),
|
||||
temp_adc_settings: temp_adc_settings,
|
||||
temp_mon_settings: self.get_temp_mon_settings(),
|
||||
thermistor_params: self.get_steinhart_hart(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_settings_from_summary(&mut self, settings: ThermostatSettingsSummary) {
|
||||
self.power_down();
|
||||
self.set_max_i_neg(settings.tec_settings.max_i_neg.value);
|
||||
self.set_max_i_pos(settings.tec_settings.max_i_pos.value);
|
||||
self.set_max_v(settings.tec_settings.max_v.value);
|
||||
|
||||
self.apply_steinhart_hart(settings.thermistor_params);
|
||||
self.apply_temp_mon_settings(settings.temp_mon_settings);
|
||||
|
||||
match settings.temp_adc_settings.rate {
|
||||
Some(rate) => match settings.temp_adc_settings.filter_type {
|
||||
FilterType::Sinc3 => self.set_temp_adc_sinc3_filter(0, SingleChODR::closest(rate).unwrap()),
|
||||
FilterType::Sinc5Sinc1 => self.set_temp_adc_sinc5_sinc1_filter(0, SingleChODR::closest(rate).unwrap()),
|
||||
FilterType::Sinc3WithFineODR => self.set_temp_adc_sinc3_fine_filter(0, rate),
|
||||
FilterType::Sinc5Sinc1With50hz60HzRejection => {
|
||||
self.set_temp_adc_sinc5_sinc1_with_postfilter(0, PostFilter::closest(rate).unwrap())
|
||||
}
|
||||
},
|
||||
None => {
|
||||
debug!(" Temperature ADC Settings is not found");
|
||||
}
|
||||
}
|
||||
|
||||
self.set_pid_engaged(settings.pid_engaged);
|
||||
self.pid_ctrl_ch0.apply_pid_params(settings.pid_params);
|
||||
self.pid_ctrl_ch0.update_pid_params_with_new_sampling_rate(settings.temp_adc_settings.rate.unwrap(), 1.0);
|
||||
self.set_temperature_setpoint(settings.temperature_setpoint);
|
||||
if !settings.pid_engaged {
|
||||
self.set_i(settings.tec_settings.i_set.value);
|
||||
}
|
||||
self.clear_temp_mon_alarm();
|
||||
self.set_default_pwr_on(settings.default_pwr_on);
|
||||
if settings.default_pwr_on {
|
||||
self.power_up();
|
||||
} else {
|
||||
self.power_down();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct StatusReport {
|
||||
interval: Time,
|
||||
pwr_on: bool,
|
||||
pid_engaged: bool,
|
||||
temp_mon_status: TempStatus,
|
||||
temperature: Option<f32>,
|
||||
i_set: ElectricCurrent,
|
||||
tec_i: ElectricCurrent,
|
||||
tec_v: ElectricPotential,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct TecSettingsSummaryField<T> {
|
||||
value: T,
|
||||
max: T,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
||||
pub struct TecSettingSummary {
|
||||
i_set: TecSettingsSummaryField<ElectricCurrent>,
|
||||
max_v: TecSettingsSummaryField<ElectricPotential>,
|
||||
max_i_pos: TecSettingsSummaryField<ElectricCurrent>,
|
||||
max_i_neg: TecSettingsSummaryField<ElectricCurrent>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Copy, Debug, Tree)]
|
||||
pub struct ThermistorParams {
|
||||
t0: f32,
|
||||
r0: ElectricalResistance,
|
||||
b: f32,
|
||||
}
|
Loading…
Reference in New Issue
Block a user