Add a device.get-battery-levels AI tool so the assistant can answer battery questions - #2898
Add a device.get-battery-levels AI tool so the assistant can answer battery questions#2898Pierre-Gilles wants to merge 2 commits into
Conversation
…uestions Battery features (category `battery`) were not part of `isSensorFeature`, so neither the `schema://home` resource nor `device.get-state` could see them: the AI had no way to answer "donne moi l'état en % de toutes les piles" or "quel est l'état de la pile du capteur température salon". Add a dedicated `device.get-battery-levels` tool, registered only when at least one device reports a battery level. It returns every battery feature with its room, device name, level and value age, optionally filtered by device or by room, sorted from the lowest level to the highest so the batteries to replace come first. Devices that never reported a level are kept last. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013yxguVaLdJ8ZKmw5x3HePT
|
Warning Review limit reached
Next review available in: 2 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
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. Comment |
Deploying gladys-plus with
|
| Latest commit: |
f3431f9
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://2a634ff6.gladys-plus.pages.dev |
| Branch Preview URL: | https://claude-ai-battery-tool.gladys-plus.pages.dev |
|
🐳 A Docker image has been built for this branch and pushed to the GitHub Container Registry. You can test this pull request (AMD64 only) by pulling the image below: For example, run it with: sudo docker run -d \
--log-driver json-file \
--log-opt max-size=10m \
--cgroupns=host \
--restart=always \
--privileged \
--network=host \
--name gladys-claude-ai-battery-tool \
-e NODE_ENV=production \
-e SERVER_PORT=80 \
-e TZ=Europe/Paris \
-e SQLITE_FILE_PATH=/var/lib/gladysassistant/gladys-production.db \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /var/lib/gladysassistant:/var/lib/gladysassistant \
-v /dev:/dev \
-v /run/udev:/run/udev:ro \
ghcr.io/gladysassistant/gladys-preview:claude-ai-battery-toolThis comment and the image are automatically updated on every new commit pushed to this pull request. Need an ARM64 image (Raspberry Pi, Apple Silicon, …)? Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2898 +/- ##
========================================
Coverage 99.51% 99.51%
========================================
Files 1235 1235
Lines 88064 88270 +206
========================================
+ Hits 87638 87844 +206
Misses 426 426 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Stale comment
This is a well-scoped read-only MCP tool for device piles (
DEVICE_FEATURE_CATEGORIES.BATTERY). Keeping battery out ofisSensorFeatureis the right call: it would pollute everydevice.get-stateanswer and the home schema, and it would make MQTT battery features writable viasensor.set-state. No new categories or types. Tests cover listing, sort (null last), device/room filters, the three error branches, and not registering the tool when nothing reports a level.Change requested:
device.get-statewas fixed in #2852 so a house name passed asroommeans the whole home. This callback copies that tool's unknown-room error but not the house fallback, even though the comment says it follows the same reasoning. Whole-home French phrasing ("piles de la maison") is exactly the production case #2852 hit.housesis already in scope — please reuse that branch and add a test.The system-prompt rule should also be narrowed from "any question about batteries" so it does not steal EV (
electrical-vehicle-battery) or home-storage (battery-storage) questions and then claim those devices do not report a level.Not
risk:high: additive MCP read path, no taxonomy/migration/auth change.Asking for human review of the product choices (dedicated tool vs
isSensorFeature, omittingbattery-low, not usingDEVICE_BATTERY_LEVEL_WARNING_THRESHOLDfor "which batteries are low"). Same class as #2852 / #2853.Residual, not blocking: the intent-router
device_queryblurb still does not mention battery levels (energy was added explicitly); a missingfeatureLastStateaftergetBySelectorcan throw, same asdevice.get-state.Sent by Cursor Automation: Automatic PR review
Handle a house name passed as `room`: the chat gateway calls the callback with the raw arguments of the model, so "l'état des piles de la maison" reached the tool as a room and was answered with "not a room of this home". Fall back to the house, like `device.get-state` does since #2852, and keep every battery device of its rooms plus the unassigned ones. Surface `DEVICE_BATTERY_LEVEL_WARNING_THRESHOLD`, the battery warning threshold of the instance already used by `device.checkBatteries` and the weekly digest, together with a `below_warning_threshold` flag on each level, so "which batteries are low" means the same thing in the AI chat as in the rest of Gladys instead of being left to the model. Scope the prompt rule and the tool description to battery powered devices: `battery-storage` and `electrical-vehicle-battery` are separate categories that this tool does not read, and a device that only publishes a binary low battery alert has no level to report. Autofix-Pass: 1
There was a problem hiding this comment.
Follow-up f3431f9 addresses the three changes requested on e6b8ae0b.
House name as room: the callback now mirrors device.get-state (#2852). If findBySimilarity(rooms, room) misses, it retries against houses and keeps every battery device of that house’s rooms plus unassigned devices. The unknown-room error is only returned when neither matches. Covered by should read device.get-battery-levels of a whole house passed as a room.
Prompt scope: the rule and the tool description are limited to battery-powered sensors/remotes/door sensors. EV (electrical-vehicle-battery) and home storage (battery-storage) are called out as different categories, and the result must not be used to answer them.
“Which batteries are low”: DEVICE_BATTERY_LEVEL_WARNING_THRESHOLD is read at call time. When it is a finite number, the payload carries warning_threshold and each numeric level gets below_warning_threshold (value < threshold, same comparison as device.checkBatteries / the weekly digest). Null levels are left unflagged. When the variable is unset, neither key is emitted. The prompt tells the model to use that flag instead of inventing a limit.
Keeping battery out of isSensorFeature is still the right call (it would pollute every device.get-state answer and make MQTT battery features writable via sensor.set-state). No new DEVICE_FEATURE_CATEGORIES / TYPES. Not risk:high: additive MCP read path, same class as #2852 / #2853. CI is green, including codecov/patch.
Keeping needs:human-review for the product choices a maintainer should still own: dedicated tool vs folding into device.get-state, and leaving battery-low out of this tool. atrovato is already requested.
Residuals, not blocking:
- The intent-router
device_queryblurb still does not mention battery levels (energy was added explicitly). The tool is also taggedother, so a misclassify-as-otherstill keeps it in the list; only a classify-as-weather/scenes-only drop would hide it, and FORCE_TOOL_CHOICE would then not apply. - Unchecked
featureLastStateaftergetBySelectorcan throw if the feature disappeared — same asdevice.get-state. - EV / storage still have no dedicated tool. The prompt fallback “if this tool is not available, say that no device reports a battery level” can still misfire on an EV-only home; that is a follow-up, not a merge blocker for this feature request.
Sent by Cursor Automation: Automatic PR review


Implements feature request: https://community.gladysassistant.com/t/concernant-lia-etat-des-piles/10354
This PR was opened by an automated Claude Code run and needs human review before merging.
Description
The forum request asks the AI to answer questions such as « donne moi l'état en % de toutes les piles » and « quel est l'état de la pile du capteur température salon ».
What was missing: battery levels are ordinary device features (
DEVICE_FEATURE_CATEGORIES.BATTERY, typeinteger, unit percent), but thebatterycategory is not part ofsensorFeaturesinserver/services/mcp/lib/selectFeature.js. As a consequence the AI could not read them at all today: battery features never appear in theschema://homeresource, they are absent from thedevice_typeenum ofdevice.get-state, and adevice.get-statecall filtered on them returns nothing. So neither of the two questions above could be answered — this is not a duplicate of an existing tool.What this PR adds:
isBatteryFeatureinselectFeature.js(alongside the other feature predicates) and wired on theMCPHandlerprototype.device.get-battery-levelsMCP tool inbuildSchemas.js, registered only when at least one device of the home reports a battery level, likedevice.set-shutter,device.set-light,sensor.set-stateanddevice.get-energy-consumptionare. Categories:device_queryandother.deviceandroomparameters (both zod enums built from the home), so the same tool serves the "all batteries" question (no parameter) and the "this one sensor" question.ageof the value, through the sharedformatValuehelper — same shape asdevice.get-state, so the existing prompt rules aboutageapply unchanged.device.getBySelectorat call time, likedevice.get-state, instead of the cached list built when the tools are registered.server/config/prompts/aiChat.prompt.txtpointing the model atdevice_get_battery_levelsfor every battery question, telling it that devices absent from the result do not report a level, and what to say when the tool is not available.Sorting choice: results are sorted by level ascending, lowest battery first, because what a user asking for the state of all their batteries actually wants is the ones to replace — and a model summarizing a long list answers from its head. Devices that have never reported a level (
last_valuenull) are kept at the end rather than sorted as if they were 0.Deliberately not done: the
batterycategory was not added toisSensorFeature. That would have been the smaller diff, but every battery feature would then be added to the home schema and to everydevice.get-stateanswer, where it is noise for questions about temperature, lights or shutters, and it would also have made battery features writable throughsensor.set-statefor MQTT devices. Onlybattery(percentage) is covered;battery-low(binary) is left out of scope.Forum
Forum: https://community.gladysassistant.com/t/concernant-lia-etat-des-piles/10354
Checklist
cd server && npm run coverage(Codecov requires 100% coverage on changed lines) and Cypress (npm run cypress:run) if the UI changednpm run eslint,npm run prettier)Details on what was actually run:
npm_config_service=mcp npm run test-service→ 144 passing, including 4 new tests: the sorted whole-home listing (with a device without a room and two devices that never reported a level), filtering by device and by room, the three error branches, and the tool not being registered when no device has a battery. Every existingmcpHandlermock inbuildSchemas.test.jsgotisBatteryFeatureadded, sincegetAllToolsnow calls it.c8over the MCP suite: no uncovered line and no uncovered branch in the new code (selectFeature.jsat 100%). The only linesc8reports as uncovered inbuildSchemas.js(798-801, adevice.turn-on-offbranch) are pre-existing and untouched.npm run eslintinserver/: 0 errors (only pre-existing warnings elsewhere in the repo, none in the touched files).npm run prettier-check: clean.test/lib/gateway/gateway.forwardMessageToAiChat.test.js: 43 passing. Not run: the full server suite.test/lib/gateway/gateway.restoreBackupEvent.test.jshas 2 failures, verified identical on an untouched checkout ofmaster— unrelated to this change.Generated by Claude Code