Skip to content

feat: raw command execution, flow tracking, auto-off#271

Merged
ng merged 9 commits intodevfrom
feature/hardware-features
Mar 30, 2026
Merged

feat: raw command execution, flow tracking, auto-off#271
ng merged 9 commits intodevfrom
feature/hardware-features

Conversation

@ng
Copy link
Copy Markdown
Contributor

@ng ng commented Mar 29, 2026

Summary

  • feat: add raw hardware command execution endpoint (Add raw hardware command execution endpoint #191) — New POST /device/execute endpoint for power users to send raw hardware commands by name. Includes ADR (0016) and disclaimer. This is an unsupported power user/debug feature with no validation on command arguments.
  • feat: track flowrate and pump RPM to DB (Track flowrate + pump RPM to DB for anomaly detection #241) — New flow_readings table in biometrics DB. DeviceStateSync persists flow data from frzHealth frames at 60s intervals. New GET /water-level/flow API endpoint for querying historical flow data.
  • feat: add auto-off schema and settings API (feat: auto-off when no bed presence detected #257) — New autoOffEnabled and autoOffMinutes columns on side_settings. Settings API updated. (Watcher service is future work.)
  • Migrations generated for both main and biometrics DBs.

Closes #191, #241, #257

Test plan

  • Test raw command execution with valid commands (DEVICE_STATUS, SET_SETTINGS)
  • Verify disclaimer is included in execute endpoint response
  • Test flow readings write and query via API
  • Test auto-off settings CRUD
  • Run migrations on fresh DB
  • Verify ADR 0016 exists at docs/adr/0016-raw-command-execution.md

Summary by CodeRabbit

  • New Features

    • Added raw hardware command execution capability for advanced users to send direct device commands.
    • Introduced flow rate and pump RPM monitoring with historical data retrieval.
    • Added configurable auto-off timer option for device sides (5–120 minute range).
  • Documentation

    • Added architecture decision record documenting raw command execution endpoint design.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

Warning

Rate limit exceeded

@ng has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 36 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 11 minutes and 36 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 882e404c-3bbd-417d-a4c3-2c732050f42f

📥 Commits

Reviewing files that changed from the base of the PR and between feb9f97 and ceb86b6.

📒 Files selected for processing (4)
  • src/hardware/dacMonitor.instance.ts
  • src/server/routers/settings.ts
  • src/server/routers/waterLevel.ts
  • src/streaming/piezoStream.ts
📝 Walkthrough

Walkthrough

This pull request introduces raw hardware command execution capabilities and expands biometric data collection. A new POST /device/execute endpoint enables allowlisted command passthrough, while database schema additions support flow rate and pump RPM recording. New GET /water-level/flow endpoint retrieves historical flow data. Side settings extend to include auto-off configurations.

Changes

Cohort / File(s) Summary
Architecture & Documentation
docs/adr/0016-raw-command-execution.md
New ADR documenting the raw command execution endpoint, its allowlist behavior, and power-user/unsupported status with noted consequences (no argument validation, bypassed safety mechanisms).
Flow Data Schema
src/db/biometrics-migrations/0006_tired_gressill.sql, src/db/biometrics-migrations/meta/0006_snapshot.json, src/db/biometrics-schema.ts
New flow_readings table migration and schema definition with timestamp, flow rate, and pump RPM columns; includes unique index on timestamp and snapshot metadata.
Migration Metadata
src/db/biometrics-migrations/meta/_journal.json, src/db/migrations/meta/_journal.json
Added journal entries tracking new migrations (0006_tired_gressill for biometrics, 0002_ambitious_sir_ram for main schema).
Side Settings Schema
src/db/migrations/0002_ambitious_sir_ram.sql, src/db/migrations/meta/0002_snapshot.json, src/db/schema.ts
Extended side_settings table with auto_off_enabled (default false) and auto_off_minutes (default 30) columns; includes migration snapshot.
Flow Data Recording
src/hardware/deviceStateSync.ts
Added recordFlowData(...) method to record flow rates and pump RPMs with 60-second rate limiting; imported flowReadings from biometrics schema.
Raw Command Execution
src/server/routers/device.ts
New execute tRPC mutation (POST /device/execute) that forwards allowlisted hardware commands with optional arguments; includes command name validation via COMMAND_MAP and error handling.
Settings Updates
src/server/routers/settings.ts
Extended updateSide input schema to accept autoOffEnabled and autoOffMinutes fields (5–120 minute range).
Flow Data Query
src/server/routers/waterLevel.ts
New getFlowReadings public query endpoint (GET /water-level/flow) that retrieves flow readings from the past N hours (1–168, default 24); results capped at 10080 records.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested labels

released

Poem

🐰 With commands now raw and endpoints so fresh,
Flow data streams through the database mesh,
Auto-off timers tick steadily on,
And power users dance till the break of dawn!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: raw command execution, flow tracking, auto-off' directly summarizes the three main features added: raw command execution endpoint, flow tracking system, and auto-off settings functionality.
Linked Issues check ✅ Passed The PR fully implements the requirements from issue #191: POST /device/execute endpoint with allowlisted hardware commands, ADR documentation, disclaimer inclusion, and passthrough to franken command interface.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the stated objectives: flow tracking system, auto-off settings, database migrations, and ADR documentation are all within scope as described in the PR objectives and linked issues.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/hardware-features

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

ng added 6 commits March 29, 2026 17:05
Add POST /device/execute endpoint for power users to send allowlisted
hardware commands with raw args passthrough. Includes disclaimer in
response and ADR 0016 documenting rationale and risks.
Add flow_readings table to biometrics schema, recordFlowData() method
to DeviceStateSync with 60s rate limiting, and GET /water-level/flow
endpoint to query historical readings.
Add autoOffEnabled and autoOffMinutes columns to side_settings table,
and accept them in the PATCH /settings/side endpoint. The actual
presence detection watcher is future work.
- FrankenHardwareClient.getRawClient() returns null in production,
  so the execute endpoint was broken. Use sendCommand from dacTransport
  directly (same transport all other hardware calls use).
- Add .limit(10080) to getFlowReadings to cap result size
@ng ng force-pushed the feature/hardware-features branch from 40058c7 to feb9f97 Compare March 30, 2026 00:05
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hardware/deviceStateSync.ts (1)

110-110: ⚠️ Potential issue | 🟡 Minor

Misleading comment: flowrate is not in "centidegrees".

The comment says // centidegrees, matching bed_temp convention but flowrate is a volumetric rate (e.g., L/min), not a temperature. The *100 conversion stores centi-units (hundredths), but the unit name in the comment is incorrect.

📝 Suggested comment fix
-  leftFlowrateCd: integer('left_flowrate_cd'), // centidegrees, matching bed_temp convention
+  leftFlowrateCd: integer('left_flowrate_cd'), // centi-units (flowrate × 100) for precision without floats
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hardware/deviceStateSync.ts` at line 110, The inline comment calling the
flowrate "centidegrees" is wrong; update the comment near the flowrate write
(where the value is multiplied by *100 and written alongside the
this.lastWaterLevelWrite check) to state that the code stores flowrate as
hundredths (centi-units) of the volumetric rate (e.g., hundredths of L/min)
instead of "centidegrees", so it accurately describes the unit conversion.
🧹 Nitpick comments (4)
docs/adr/0016-raw-command-execution.md (1)

10-12: Consider documenting the allowed command names.

The ADR mentions "command name allowlisting" but doesn't enumerate which commands are permitted. Documenting the allowlist here (SET_TEMP, SET_ALARM, ALARM_LEFT, ALARM_RIGHT, SET_SETTINGS, PRIME, DEVICE_STATUS, ALARM_CLEAR) would help future readers understand the scope without diving into code, and makes the ADR self-contained.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/adr/0016-raw-command-execution.md` around lines 10 - 12, Update the ADR
for the POST /device/execute decision to explicitly list the allowed command
names in the "Decision" or a new "Allowlist" section so the document is
self-contained; include the exact allowed tokens SET_TEMP, SET_ALARM,
ALARM_LEFT, ALARM_RIGHT, SET_SETTINGS, PRIME, DEVICE_STATUS, and ALARM_CLEAR and
state that these are the only command names accepted by the endpoint (with the
endpoint name POST /device/execute and the phrase "command name allowlisting"
kept intact).
src/server/routers/device.ts (2)

544-544: Consider adding a max length to the args parameter.

The args string is passed verbatim to the hardware socket with no length limit. While this is a power-user feature, an extremely long string could cause memory pressure when buffered or trigger unexpected behavior at the protocol level.

A reasonable upper bound (e.g., 1024 or 4096 characters) would add a minimal safety net without limiting legitimate use.

🛡️ Suggested change
-      args: z.string().optional(),
+      args: z.string().max(4096).optional(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/routers/device.ts` at line 544, The args input in the device
router is currently z.string().optional() and can be arbitrarily long; add a max
length to prevent memory/protocol issues by changing the zod schema for args
(the args parameter in the router definition) to include a .max(...) constraint
(e.g., 1024 or 4096 chars) while preserving optionality, and update any relevant
validation error message/handlers to surface that the args value is too long.

25-34: Consider deriving the allowlist from a single source of truth.

The allowed command names are duplicated: once in COMMAND_MAP keys and again in the z.enum() at line 543. If these drift apart, the endpoint could accept a command name that isn't in the map (causing hwCommand to be undefined) or reject valid commands.

♻️ Suggested approach
-const COMMAND_MAP: Record<string, HardwareCommand> = {
+const COMMAND_MAP = {
   SET_TEMP: HardwareCommand.SET_TEMP,
   SET_ALARM: HardwareCommand.SET_ALARM,
   ALARM_LEFT: HardwareCommand.ALARM_LEFT,
   ALARM_RIGHT: HardwareCommand.ALARM_RIGHT,
   SET_SETTINGS: HardwareCommand.SET_SETTINGS,
   PRIME: HardwareCommand.PRIME,
   DEVICE_STATUS: HardwareCommand.DEVICE_STATUS,
   ALARM_CLEAR: HardwareCommand.ALARM_CLEAR,
-}
+} as const satisfies Record<string, HardwareCommand>
+
+const ALLOWED_COMMANDS = Object.keys(COMMAND_MAP) as [keyof typeof COMMAND_MAP, ...(keyof typeof COMMAND_MAP)[]]

Then use in the schema:

command: z.enum(ALLOWED_COMMANDS),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/routers/device.ts` around lines 25 - 34, COMMAND_MAP's keys and
the z.enum allowlist are duplicated; create a single source of truth by deriving
an ALLOWED_COMMANDS constant from the keys of COMMAND_MAP (e.g., capture
Object.keys(COMMAND_MAP) as a readonly tuple/array and assert its literal
types), then use z.enum(ALLOWED_COMMANDS) in the schema instead of repeating the
strings; update any code that references the old enum list and ensure the
handler that looks up hwCommand from COMMAND_MAP (hwCommand) will no longer get
undefined because the schema only permits keys present in COMMAND_MAP.
src/db/migrations/0002_ambitious_sir_ram.sql (1)

1-2: Add DB-level constraints for auto-off fields.

auto_off_minutes and auto_off_enabled are validated at API level, but the table schema currently allows out-of-contract values at the DB layer. Please enforce bounds/domains in the migration too.

Proposed migration hardening
-ALTER TABLE `side_settings` ADD `auto_off_enabled` integer DEFAULT false NOT NULL;--> statement-breakpoint
-ALTER TABLE `side_settings` ADD `auto_off_minutes` integer DEFAULT 30 NOT NULL;
+ALTER TABLE `side_settings`
+  ADD `auto_off_enabled` integer DEFAULT false NOT NULL CHECK (`auto_off_enabled` IN (0, 1));--> statement-breakpoint
+ALTER TABLE `side_settings`
+  ADD `auto_off_minutes` integer DEFAULT 30 NOT NULL CHECK (`auto_off_minutes` BETWEEN 5 AND 120);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/db/migrations/0002_ambitious_sir_ram.sql` around lines 1 - 2, The
migration adds side_settings.auto_off_enabled and auto_off_minutes without
DB-level domains; update the ALTER TABLE statements to enforce types and bounds
by using a boolean/numeric default and CHECK constraints: set auto_off_enabled
DEFAULT 0 NOT NULL CHECK (auto_off_enabled IN (0,1)) and set auto_off_minutes
DEFAULT 30 NOT NULL CHECK (auto_off_minutes BETWEEN 0 AND 1440) (adjust the
upper bound if your policy differs), keeping the same column names
side_settings.auto_off_enabled and side_settings.auto_off_minutes so the DB will
reject out-of-contract values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/hardware/deviceStateSync.ts`:
- Around line 125-149: The recordFlowData method is never invoked; wire it into
the frame processing pipeline so frzHealth frames trigger a call to
DeviceStateSync.recordFlowData. Locate the frame handler (e.g., the function
that receives/parses incoming frames such as processFrame, handleFrame, or
onFrame) and when a frame is identified as a frzHealth payload (check the
frame.type or parsed object name), call this.recordFlowData(parsedFrzHealth)
where parsedFrzHealth matches the expected shape (left/right pump/temps). Ensure
the invocation is performed asynchronously-safe (no blocking) and does not
bypass the existing rate limit inside recordFlowData.

In `@src/server/routers/device.ts`:
- Around line 540-541: The execute publicProcedure (meta openapi POST path
'/device/execute') is currently exposed with protect: false; gate it behind a
debug/advanced feature flag (e.g., process.env.ENABLE_DEVICE_EXECUTE or a
Config.isAdvancedMode()) by changing the procedure to check that flag before
running (or attach a middleware that rejects requests when the flag is false)
and set meta.protect appropriately; update the execute handler to throw a
forbidden error when the feature flag is disabled so the endpoint is effectively
disabled in production unless the debug flag is enabled.

In `@src/server/routers/settings.ts`:
- Around line 189-190: The getAll response fallback objects in the settings
router are missing the new optional fields autoOffEnabled and autoOffMinutes,
causing inconsistent response shapes; update the fallback/default side objects
returned by getAll to include autoOffEnabled and autoOffMinutes (set them to
undefined or an appropriate default consistent with the zod schema) so the
response shape always contains these keys and matches the zod types for
autoOffEnabled and autoOffMinutes.

In `@src/server/routers/waterLevel.ts`:
- Around line 233-238: The DB query is returned without awaiting, so errors
escape the surrounding try/catch; modify the code that builds the query using
biometricsDb.select().from(flowReadings).where(gt(flowReadings.timestamp,
since)).orderBy(flowReadings.timestamp).limit(10080) to await the promise (i.e.,
use await with biometricsDb...) so any thrown error is caught by the existing
catch block; ensure the surrounding function (the procedure handling
flowReadings) remains async and keep existing error handling/logging intact.

---

Outside diff comments:
In `@src/hardware/deviceStateSync.ts`:
- Line 110: The inline comment calling the flowrate "centidegrees" is wrong;
update the comment near the flowrate write (where the value is multiplied by
*100 and written alongside the this.lastWaterLevelWrite check) to state that the
code stores flowrate as hundredths (centi-units) of the volumetric rate (e.g.,
hundredths of L/min) instead of "centidegrees", so it accurately describes the
unit conversion.

---

Nitpick comments:
In `@docs/adr/0016-raw-command-execution.md`:
- Around line 10-12: Update the ADR for the POST /device/execute decision to
explicitly list the allowed command names in the "Decision" or a new "Allowlist"
section so the document is self-contained; include the exact allowed tokens
SET_TEMP, SET_ALARM, ALARM_LEFT, ALARM_RIGHT, SET_SETTINGS, PRIME,
DEVICE_STATUS, and ALARM_CLEAR and state that these are the only command names
accepted by the endpoint (with the endpoint name POST /device/execute and the
phrase "command name allowlisting" kept intact).

In `@src/db/migrations/0002_ambitious_sir_ram.sql`:
- Around line 1-2: The migration adds side_settings.auto_off_enabled and
auto_off_minutes without DB-level domains; update the ALTER TABLE statements to
enforce types and bounds by using a boolean/numeric default and CHECK
constraints: set auto_off_enabled DEFAULT 0 NOT NULL CHECK (auto_off_enabled IN
(0,1)) and set auto_off_minutes DEFAULT 30 NOT NULL CHECK (auto_off_minutes
BETWEEN 0 AND 1440) (adjust the upper bound if your policy differs), keeping the
same column names side_settings.auto_off_enabled and
side_settings.auto_off_minutes so the DB will reject out-of-contract values.

In `@src/server/routers/device.ts`:
- Line 544: The args input in the device router is currently
z.string().optional() and can be arbitrarily long; add a max length to prevent
memory/protocol issues by changing the zod schema for args (the args parameter
in the router definition) to include a .max(...) constraint (e.g., 1024 or 4096
chars) while preserving optionality, and update any relevant validation error
message/handlers to surface that the args value is too long.
- Around line 25-34: COMMAND_MAP's keys and the z.enum allowlist are duplicated;
create a single source of truth by deriving an ALLOWED_COMMANDS constant from
the keys of COMMAND_MAP (e.g., capture Object.keys(COMMAND_MAP) as a readonly
tuple/array and assert its literal types), then use z.enum(ALLOWED_COMMANDS) in
the schema instead of repeating the strings; update any code that references the
old enum list and ensure the handler that looks up hwCommand from COMMAND_MAP
(hwCommand) will no longer get undefined because the schema only permits keys
present in COMMAND_MAP.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8041eec2-211b-4c3b-bf65-657bd94ee6b0

📥 Commits

Reviewing files that changed from the base of the PR and between d3e6bf8 and feb9f97.

📒 Files selected for processing (13)
  • docs/adr/0016-raw-command-execution.md
  • src/db/biometrics-migrations/0006_tired_gressill.sql
  • src/db/biometrics-migrations/meta/0006_snapshot.json
  • src/db/biometrics-migrations/meta/_journal.json
  • src/db/biometrics-schema.ts
  • src/db/migrations/0002_ambitious_sir_ram.sql
  • src/db/migrations/meta/0002_snapshot.json
  • src/db/migrations/meta/_journal.json
  • src/db/schema.ts
  • src/hardware/deviceStateSync.ts
  • src/server/routers/device.ts
  • src/server/routers/settings.ts
  • src/server/routers/waterLevel.ts

Comment on lines +125 to +149
/** Write flow/pump data to biometrics DB, rate-limited to once per 60s. */
recordFlowData(frzHealth: {
left: { pump: { rpm: number }, temps: { flowrate: number } }
right: { pump: { rpm: number }, temps: { flowrate: number } }
}): void {
const now = Date.now()
if (now - this.lastFlowWrite < 60_000) return

try {
biometricsDb
.insert(flowReadings)
.values({
timestamp: new Date(now),
leftFlowrateCd: Math.round(frzHealth.left.temps.flowrate * 100),
rightFlowrateCd: Math.round(frzHealth.right.temps.flowrate * 100),
leftPumpRpm: frzHealth.left.pump.rpm,
rightPumpRpm: frzHealth.right.pump.rpm,
})
.run()
this.lastFlowWrite = now
}
catch (error) {
console.error('DeviceStateSync: failed to write flow readings:', error instanceof Error ? error.message : error)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for calls to recordFlowData to verify it's actually used
rg -n 'recordFlowData' --type ts

Repository: sleepypod/core

Length of output: 123


Wire up recordFlowData to the frame processing pipeline.

The method is defined but never called anywhere in the codebase. Ensure it's invoked when frzHealth frames are processed to fulfill the intended functionality of persisting flow data at 60-second intervals.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hardware/deviceStateSync.ts` around lines 125 - 149, The recordFlowData
method is never invoked; wire it into the frame processing pipeline so frzHealth
frames trigger a call to DeviceStateSync.recordFlowData. Locate the frame
handler (e.g., the function that receives/parses incoming frames such as
processFrame, handleFrame, or onFrame) and when a frame is identified as a
frzHealth payload (check the frame.type or parsed object name), call
this.recordFlowData(parsedFrzHealth) where parsedFrzHealth matches the expected
shape (left/right pump/temps). Ensure the invocation is performed
asynchronously-safe (no blocking) and does not bypass the existing rate limit
inside recordFlowData.

Comment on lines +540 to +541
execute: publicProcedure
.meta({ openapi: { method: 'POST', path: '/device/execute', protect: false, tags: ['Device'] } })
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing debug/advanced mode gating as mentioned in requirements.

The PR objectives from issue #191 specify: "Gate access behind a debug/advanced mode flag (intended for power users/debugging, not normal app usage)." The current implementation has protect: false with no additional access control.

Consider whether this endpoint should be conditionally enabled (e.g., via environment variable or config flag) to prevent accidental exposure in production builds.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/routers/device.ts` around lines 540 - 541, The execute
publicProcedure (meta openapi POST path '/device/execute') is currently exposed
with protect: false; gate it behind a debug/advanced feature flag (e.g.,
process.env.ENABLE_DEVICE_EXECUTE or a Config.isAdvancedMode()) by changing the
procedure to check that flag before running (or attach a middleware that rejects
requests when the flag is false) and set meta.protect appropriately; update the
execute handler to throw a forbidden error when the feature flag is disabled so
the endpoint is effectively disabled in production unless the debug flag is
enabled.

Comment thread src/server/routers/settings.ts
Comment thread src/server/routers/waterLevel.ts Outdated
ng added 3 commits March 29, 2026 18:16
- Add missing `await` in getFlowReadings so errors hit the catch block
- Add autoOffEnabled/autoOffMinutes to settings side fallback defaults
- Wire recordFlowData into frzHealth frame pipeline via onServerFrame
- Filter server-side listeners to frzHealth only (avoid hot-path overhead)
- Store unsubscribe handle and clean up on shutdown
@ng ng merged commit a100fa7 into dev Mar 30, 2026
6 checks passed
@ng ng deleted the feature/hardware-features branch March 30, 2026 01:25
@ng ng mentioned this pull request Apr 1, 2026
ng added a commit that referenced this pull request Apr 1, 2026
## Summary
- feat: 24/7 temperature mode with keepalive and stale display fix
(#300)
- chore: add db:reset scripts for convenient database resets (#299)
- feat: track flowrate and pump RPM with anomaly detection (#302)
- feat: scheduler fixes and new scheduling features (#270)
- feat: raw command execution, flow tracking, auto-off (#271)
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 1, 2026

🎉 This PR is included in version 1.5.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add raw hardware command execution endpoint

1 participant