Skip to content

Commit 4578883

Browse files
committed
WIP
1 parent 03e145b commit 4578883

9 files changed

Lines changed: 99 additions & 42 deletions

File tree

README.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
# HighFlowNext
2-
3-
HighFlowNext is a Rust library for communicating with the [Aquacomputer high flow NEXT](https://shop.aquacomputer.de/Monitoring-and-Controlling/Sensors/Flow-sensor-high-flow-NEXT-G1-4::3953.html) device. It provides strongly typed access to the device’s binary protocol, allowing you to interact with sensor values, settings, and configuration in a safe and structured way. The primary goal of this project is to enable clean integration of the high flow NEXT into [OpenRGB](https://openrgb.org/), so that device data and lighting controls can be accessed and managed alongside other RGB hardware.
1+
`HighFlowNext` is a Rust library for communicating with the [Aquacomputer high flow NEXT](https://shop.aquacomputer.de/Monitoring-and-Controlling/Sensors/Flow-sensor-high-flow-NEXT-G1-4::3953.html) device. It provides strongly typed access to the device’s binary protocol, allowing you to interact with sensor values, settings, and configuration in a safe and structured way. The primary goal of this project is to enable clean integration of the high flow NEXT into [OpenRGB](https://openrgb.org/), so that device data and lighting controls can be accessed and managed alongside other RGB hardware.
42

53
> **Notice**: This is **not an official implementation** from Aquacomputer.
64
75
> **Warning**: No guarantees are given regarding correctness, completeness, or stability of the protocol description. Use it at your own risk.
86
9-
## Features
7+
# Features
108

119
- Reading the settings frame (**done**)
1210
- Writing the settings frame (**planned**)
@@ -15,20 +13,20 @@ HighFlowNext is a Rust library for communicating with the [Aquacomputer high flo
1513
- Writing ambient color data (**planned**)
1614
- Writing sound data (**planned**)
1715

18-
## Use Cases
16+
# Use Cases
1917

20-
HighFlowNext is intended as a building block for an OpenRGB integration. By decoding the device’s settings and sensor values, it becomes straightforward to surface flow rate, temperatures, and conductivity within the OpenRGB UI or its plugins.
18+
`HighFlowNext` is intended as a building block for an `OpenRGB` integration. By decoding the device’s settings and sensor values, it becomes straightforward to surface flow rate, temperatures, and conductivity within the `OpenRGB` UI or its plugins.
2119

22-
Planned write-paths for ambient color and sound data aim to let OpenRGB drive RGBpx lighting effects on the high flow NEXT directly, coordinating lighting with the rest of a system’s devices.
20+
Planned write-paths for ambient color and sound data aim to let `OpenRGB` drive `RGBpx` lighting effects on the high flow NEXT directly, coordinating lighting with the rest of a system’s devices.
2321

24-
Beyond OpenRGB, the crate can also support monitoring, logging, and automation workflows where you want to read configuration, calibrations, and live telemetry and then act on it in your own services or dashboards.
22+
Beyond `OpenRGB`, the crate can also support monitoring, logging, and automation workflows where you want to read configuration, calibrations, and live telemetry and then act on it in your own services or dashboards.
2523

26-
## Protocol Specification
24+
# Protocol Specification
2725

2826
If you are only interested in the details of the communication protocol itself,
2927
please have a look at the included [specification file](https://github.com/Bergmann89/HighFlowNext/blob/master/protocol/SPECIFICATION). It describes the binary
3028
layout of settings, frames, effects, colors, and source control mappings.
3129

32-
## License
30+
# License
3331

3432
This crate is licensed under the MIT License.

src/misc/crc.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,27 @@ use crc::{Crc, Digest, Table, CRC_16_USB};
55

66
use crate::misc::{IoError, Reader};
77

8+
/// A writer wrapper that calculates a CRC checksum while writing data.
9+
///
10+
/// The checksum is updated automatically as bytes are written.
11+
/// Once writing is finished, [`finalize`](CrcWriter::finalize) can be used
12+
/// to obtain both the inner writer and the computed CRC value.
813
pub struct CrcWriter<W> {
914
writer: W,
1015
digest: Digest<'static, u16, Table<1>>,
1116
}
1217

1318
impl<W> CrcWriter<W> {
19+
/// Creates a new [`CrcWriter`] wrapping the given writer.
1420
pub fn new(writer: W) -> Self {
1521
Self {
1622
writer,
1723
digest: CRC.digest(),
1824
}
1925
}
2026

27+
/// Finalizes the CRC calculation and returns the inner writer along with
28+
/// the computed CRC value.
2129
pub fn finalize(self) -> (W, u16) {
2230
let Self { writer, digest } = self;
2331

@@ -42,30 +50,38 @@ impl<W> Write for CrcWriter<W>
4250
where
4351
W: Write,
4452
{
53+
/// Writes data to the underlying writer and updates the CRC checksum.
4554
fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
4655
self.digest.update(buf);
4756

4857
self.writer.write(buf)
4958
}
5059

60+
/// Flushes the underlying writer.
5161
fn flush(&mut self) -> IoResult<()> {
5262
self.writer.flush()
5363
}
5464
}
5565

66+
/// A reader wrapper that calculates a CRC checksum while reading data.
67+
///
68+
/// The checksum is updated automatically as bytes are read. After finishing
69+
/// reading, [`finalize`](CrcReader::finalize) returns the computed CRC value.
5670
pub struct CrcReader<'a, R> {
5771
reader: &'a mut R,
5872
digest: Digest<'static, u16, Table<1>>,
5973
}
6074

6175
impl<'a, R> CrcReader<'a, R> {
76+
/// Creates a new [`CrcReader`] wrapping the given reader.
6277
pub fn new(reader: &'a mut R) -> Self {
6378
Self {
6479
reader,
6580
digest: CRC.digest(),
6681
}
6782
}
6883

84+
/// Finalizes the CRC calculation and returns the computed value.
6985
#[must_use]
7086
pub fn finalize(self) -> u16 {
7187
self.digest.finalize()
@@ -89,13 +105,16 @@ where
89105
{
90106
type Guard = R::Guard;
91107

108+
/// Reads exactly `buf.len()` bytes from the underlying reader, updating
109+
/// the CRC checksum with the data.
92110
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), IoError> {
93111
self.reader.read_exact(buf)?;
94112
self.digest.update(buf);
95113

96114
Ok(())
97115
}
98116

117+
/// Delegates guard creation to the inner reader’s guard.
99118
fn guard<F, T>(f: F) -> <Self::Guard as super::Guard>::Output<T>
100119
where
101120
F: FnOnce(Self::Guard) -> T,
@@ -104,4 +123,5 @@ where
104123
}
105124
}
106125

126+
/// Constant CRC definition using the USB CRC-16 polynomial.
107127
const CRC: Crc<u16> = Crc::<u16>::new(&CRC_16_USB);

src/misc/io/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ mod reader;
44

55
pub use self::decode::Decode;
66
pub use self::error::Error;
7-
pub use self::reader::{Guard, GuardOutput, Reader};
7+
pub use self::reader::{Guard, GuardOutput, Reader, SkipGuard, SkipReader, ValueGuard};

src/misc/io/reader.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ where
103103
/// A wrapper around a [`Reader`] that discards values instead of keeping them.
104104
///
105105
/// Used by [`Decode::skip_bytes`](super::Decode::skip_bytes).
106-
pub(super) struct SkipReader<'a, R: Reader>(pub(super) &'a mut R);
106+
#[derive(Debug)]
107+
pub struct SkipReader<'a, R: Reader>(pub(super) &'a mut R);
107108

108109
impl<R> Reader for SkipReader<'_, R>
109110
where
@@ -156,7 +157,7 @@ impl Guard for ValueGuard {
156157
/// A [`Guard`] implementation for **skip mode**.
157158
/// Values are replaced with [`PhantomData`] and cannot be accessed.
158159
#[derive(Debug)]
159-
pub(super) struct SkipGuard;
160+
pub struct SkipGuard;
160161

161162
impl Guard for SkipGuard {
162163
type Output<T> = PhantomData<T>;

src/misc/mod.rs

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,12 @@
11
//! Miscellaneous utilities for binary I/O, checksums, and typed wrappers.
22
//!
33
//! This module collects helper traits and types that are reused across
4-
//! different parts of the codebase. It consists of three main areas:
5-
//!
6-
//! - [`io`] – Core traits and helpers for working with binary readers,
7-
//! decoding values, and guarding against unwanted allocations.
8-
//! Includes [`Reader`], [`Guard`], and [`Decode`], with errors reported
9-
//! via [`IoError`].
10-
//! - [`crc`] – Readers and writers with CRC (checksum) calculation built-in,
11-
//! allowing integrity verification of binary streams (`CrcReader`, `CrcWriter`).
12-
//! - [`wrapped`] – Strongly typed wrappers around primitive values, providing
13-
//! validation and range enforcement. Useful for ensuring effect parameters
14-
//! like brightness, delays, and widths remain within valid bounds
15-
//! (`Wrapped`, `Ranged`, `ValueVerifier`).
16-
//!
17-
//! The most important items are re-exported at the top level so you can simply write:
18-
//!
19-
//! This keeps protocol code concise while still enforcing correctness
20-
//! and data integrity.
4+
//! different parts of the codebase.
215
226
mod crc;
237
mod io;
248
mod wrapped;
259

2610
pub use self::crc::{CrcReader, CrcWriter};
27-
pub use self::io::{Decode, Error as IoError, Guard, GuardOutput, Reader};
28-
pub use self::wrapped::{Ranged, ValueVerifier, Wrapped};
11+
pub use self::io::{Decode, Error as IoError, Guard, GuardOutput, Reader, SkipGuard, SkipReader, ValueGuard};
12+
pub use self::wrapped::{Ranged, ValueVerifier, Wrapped, RangeError};

src/misc/wrapped.rs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ use thiserror::Error;
66

77
use crate::misc::{Decode, Guard, GuardOutput, IoError, Reader};
88

9+
/// Macro to define a new typed wrapper around a primitive value.
10+
///
11+
/// Example:
12+
/// ```rust,ignore
13+
/// define_wrapped! {
14+
/// /// Brightness level (0..255).
15+
/// pub type Brightness<u8, BrightnessTag>;
16+
/// }
17+
/// ```
918
#[macro_export]
1019
macro_rules! define_wrapped {
1120
($(#[$meta:meta])* $pub:vis type $name:ident<$base:ty, $tag:ident> ;) => {
@@ -18,6 +27,9 @@ macro_rules! define_wrapped {
1827
};
1928
}
2029

30+
/// Macro to implement a trivial verifier that accepts all values.
31+
///
32+
/// Use this when no validation is needed for the type.
2133
#[macro_export]
2234
macro_rules! impl_verify_simple {
2335
($value_type:ident<$base:ty, $tag:ident>) => {
@@ -31,6 +43,9 @@ macro_rules! impl_verify_simple {
3143
};
3244
}
3345

46+
/// Macro to implement a ranged verifier for a wrapper type.
47+
///
48+
/// The type will only accept values within the inclusive range [`min`, `max`].
3449
#[macro_export]
3550
macro_rules! impl_ranged {
3651
($value_type:ident<$base:ty, $tag:ident>, $min:expr, $max:expr) => {
@@ -48,6 +63,11 @@ macro_rules! impl_ranged {
4863
};
4964
}
5065

66+
/// A strongly typed wrapper around a primitive value with validation.
67+
///
68+
/// Wrappers are parameterized by a phantom `tag` type which implements
69+
/// either [`Ranged`] or [`ValueVerifier`]. This allows creating distinct
70+
/// types from the same base primitive while enforcing domain-specific rules.
5171
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
5272
pub struct Wrapped<T, X> {
5373
value: T,
@@ -59,11 +79,13 @@ where
5979
T: Ord,
6080
X: Ranged<T>,
6181
{
82+
/// Returns the minimum allowed value.
6283
#[must_use]
6384
pub fn min_inclusive() -> T {
6485
X::min_inclusive()
6586
}
6687

88+
/// Returns the maximum allowed value.
6789
#[must_use]
6890
pub fn max_inclusive() -> T {
6991
X::max_inclusive()
@@ -74,6 +96,8 @@ impl<T, X> Wrapped<T, X>
7496
where
7597
X: ValueVerifier<T>,
7698
{
99+
/// Creates a new wrapper from a primitive value,
100+
/// validating it with the associated verifier.
77101
pub fn from_value(val: T) -> Result<Self, X::Error> {
78102
let val = X::verify(val)?;
79103

@@ -92,6 +116,7 @@ impl<T, X> Deref for Wrapped<T, X> {
92116
}
93117
}
94118

119+
/// Implements [`Decode`] for `Wrapped<u8, X>`.
95120
impl<X> Decode for Wrapped<u8, X>
96121
where
97122
X: ValueVerifier<u8>,
@@ -105,6 +130,7 @@ where
105130
}
106131
}
107132

133+
/// Implements [`Decode`] for `Wrapped<u16, X>`.
108134
impl<X> Decode for Wrapped<u16, X>
109135
where
110136
X: ValueVerifier<u16>,
@@ -118,6 +144,7 @@ where
118144
}
119145
}
120146

147+
/// Implements [`Decode`] for `Wrapped<i16, X>`.
121148
impl<X> Decode for Wrapped<i16, X>
122149
where
123150
X: ValueVerifier<i16>,
@@ -131,17 +158,31 @@ where
131158
}
132159
}
133160

161+
/// Trait for types that define inclusive minimum and maximum bounds.
134162
pub trait Ranged<T> {
163+
/// Return the minimum inclusive value for this range.
135164
fn min_inclusive() -> T;
165+
166+
/// Return the maximum inclusive value for this range.
136167
fn max_inclusive() -> T;
137168
}
138169

170+
/// Trait for types that verify whether a value is valid.
171+
///
172+
/// Custom verifiers can reject values outside ranges, apply additional
173+
/// constraints, or simply accept all values.
139174
pub trait ValueVerifier<T> {
175+
/// Error returned by the verifier.
140176
type Error;
141177

142-
fn verify(val: T) -> Result<T, Self::Error>;
178+
/// Verify if the given `value` is valid, returning either the valid value
179+
/// `Ok(value)` or a suitable error `Err(error)`.
180+
fn verify(value: T) -> Result<T, Self::Error>;
143181
}
144182

183+
/// Default implementation of [`ValueVerifier`] for any [`Ranged`] type.
184+
///
185+
/// Rejects values outside the inclusive min/max bounds.
145186
impl<T, X> ValueVerifier<T> for X
146187
where
147188
X: Ranged<T>,
@@ -161,18 +202,26 @@ where
161202
}
162203
}
163204

205+
/// Error returned when a value lies outside the allowed range.
164206
#[derive(Debug, Error)]
165207
#[error("Value out of range (min={min}, max={max}, val={val})!")]
166208
pub struct RangeError<T> {
209+
/// Minimum inclusive value of the range.
167210
pub min: T,
211+
212+
/// Maximum inclusive value of the range.
168213
pub max: T,
214+
215+
/// Actual value (not in range).
169216
pub val: T,
170217
}
171218

172219
impl<T> RangeError<T>
173220
where
174221
T: Display,
175222
{
223+
/// Converts this error into an owned `RangeError<String>`,
224+
/// useful for error reporting where `T` is not `Clone`.
176225
pub fn to_owned(&self) -> RangeError<String> {
177226
RangeError {
178227
min: self.min.to_string(),

src/protocol/mod.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,36 @@ use crate::misc::{CrcReader, Decode, Guard, GuardOutput, IoError, Reader};
99

1010
pub use self::settings::Settings;
1111

12+
/// A top-level protocol frame received from or sent to the device.
13+
///
14+
/// Each frame starts with an operation code (`OpCode`), followed by a
15+
/// payload and a trailing CRC checksum. The `Frame` enum provides typed
16+
/// variants for the supported op codes.
17+
///
18+
/// Currently supported:
19+
/// - `0x03` → [`Frame::Settings`]
1220
#[derive(Debug, Clone, Eq, PartialEq)]
1321
pub enum Frame {
22+
/// Frame carrying the full device settings (decoded into [`Settings`]).
1423
Settings(Settings),
1524
}
1625

1726
impl Decode for Frame {
1827
fn decode<R: Reader>(reader: &mut R) -> Result<GuardOutput<R, Self>, IoError> {
28+
// Read the operation code
1929
let op_code = reader.read_u8()?;
2030
let mut crc = CrcReader::new(reader);
2131

32+
// Dispatch based on op code
2233
let ret = match op_code {
2334
0x03 => {
2435
let ret = Settings::decode(&mut crc)?;
25-
2636
R::guard(|x| Self::Settings(x.extract(ret)))
2737
}
2838
op_code => Err(IoError::InvalidValue("OpCode", op_code.into()))?,
2939
};
3040

41+
// Verify CRC
3142
let crc_actual = crc.finalize();
3243
let crc_expceted = reader.read_u16be()?;
3344

src/protocol/settings/mod.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,7 @@
22
//!
33
//! This module contains all types and decoding logic related to device
44
//! configuration and runtime settings. The `Settings` struct is the top-
5-
//! level container, which aggregates the following areas:
6-
//!
7-
//! - [`system`] – System-level settings (e.g. `AquaBus` address, standby flags).
8-
//! - [`sensor`] – Sensor-related calibration and correction values.
9-
//! - [`alarm`] – Alarm configuration (thresholds, delays, behaviors).
10-
//! - [`display`] – Display settings for the onboard screen.
11-
//! - [`lighting`] – Lighting / `RGBpx` settings.
5+
//! level container.
126
137
mod alarm;
148
mod display;

0 commit comments

Comments
 (0)