Refactor App - #24
Open
craigde wants to merge 30 commits into
Open
Conversation
…iver - Add TemperatureMeasurement capability so oven appears in Rule Machine temperature conditions, dashboards, and third-party integrations - Add temperatureUnit attribute that tracks F/C setting from appliance - Parse TEMPERATURE_UNIT ERD (0x0007) to determine unit preference - Include unit in all temperature sendEvent calls - Map upper oven raw temperature to standard "temperature" attribute - Fix empty map pattern [[:]] to use cleaner [] syntax
Add TemperatureMeasurement capability and F/C unit support to oven dr…
- Create new SmartHQ app (apps/smartHQ_app) to replace parent driver - Handles OAuth2 authentication with GE SmartHQ - Manages WebSocket connection for real-time events - Creates and manages child appliance devices - Proper app preferences page for configuration - Update smarthqHelpers library - Add support for app parent via getUserIdForChild() method - Maintain backward compatibility with legacy driver parent - Update packageManifest.json - Add SmartHQ app as required component - Remove SmartHQ System driver from manifest - Bump version to 1.0.0 - Mark smartHQ_system driver as deprecated - Users should migrate to SmartHQ app - Driver kept for reference but not in manifest This follows Hubitat best practices where cloud integrations use apps for auth/connection management and drivers for devices.
- Change namespace from "tomw" to "craigde" across all files - Add acknowledgment to tomw in all file headers and README - Update packageManifest.json with: - New unique UUIDs for all components - GitHub URLs pointing to craigde/hubitat-SmartHQ - Updated author and namespace references - Update README with: - Acknowledgments section crediting tomw - Updated installation instructions for app-based architecture - Complete version history preserving original contributions - Update library includes to use craigde namespace - Preserve complete change history from original author Files updated: - apps/smartHQ_app - devices/* (all 9 device drivers) - libraries/smarthqHelpers - libraries/wsReinitialize - smartHQ_system (deprecated) - packageManifest.json - README.md
Refactor architecture
The generic Exception catch block in initialize() was missing a reinitialize() call, so when a NullPointerException occurred on interfaces.webSocket, the connection died permanently with no retry. Also wrap sendWssMap() in try/catch so child device commands don't cause unhandled errors when the WebSocket is temporarily down. https://claude.ai/code/session_01TArAXAXbb51BEW6dryM3GU
…webSocket' on null object Fix WebSocket not reconnecting after NullPointerException
Removed outdated change history entries from smartHQ_app.
Architecture: - Delete deprecated smartHQ_system driver (no users on this fork) - Delete wsReinitialize library; inline its WebSocket reconnection code into smartHQ_app, where it was the only real consumer - Move OAuth credentials and login flow out of smarthqHelpers into the app, so device drivers no longer carry secrets they never use - Reduce smarthqHelpers to pure utilities (ERD codec, message builders, common parse/setter helpers) - Extract duplicated setIntegerErd / parseIntegerErd into helpers (previously copy-pasted in hood, ice maker, portable AC) App / WebSocket fixes: - Generic exception in initialize() now triggers reinitialize() so a transient NPE no longer kills the connection permanently - unschedule(initialize) at start of initialize() and in uninstalled() to prevent schedule pile-up - Defer subscribe_all + get_appliance_list to webSocketStatus "status: open" so we don't send over an unopened socket - Validate WSS endpoint before connect; reinitialize on missing endpoint - Clamp token-refresh re-auth delay to a safe minimum - Concatenate region + JSESSION cookies into a single Cookie header (Groovy map literal was silently dropping the region cookie) - Add `def` to local vars in OAuth flow that were leaking to script scope - Use `any` instead of `findAll` truthy-list trick in webSocketStatus Helpers fixes: - decodeErdSignedByte: 0x80 now decodes to -128, not +128 (off-by-one) - parseDoorStatusByte / flushEvents: drop empty-map initializer that produced bogus sendEvent calls; flushEvents now skips entries with no name as defense in depth - parseSabbathMode / parseControlLock: wrap in try/catch Driver fixes: - oven: try/catch on parseCookState and parseCooktopStatus, init numberOfButtons for PushableButton capability - laundry: try/catch on parseMachineState, parseDoorLock, parseSmartDispenseTankStatus - refrigerator: bounds-check adjustSetpoints, declare missing sabbathMode attribute, route SABBATH_MODE ERD to parseSabbathMode, fix parseFilterStatus null/array safety, drop bogus events initializer, add F unit to temperature events - portable AC: setCoolingSetpoint no longer forces "cool" mode (was silently overriding energy_saver/dry), bounds-check adjustSetpoint, add fallback "unknown" on enum lookups, drop duplicated setIntegerErd, add F unit to temperature events - microwave: drop blocking pauseExecution(500) pattern; warn and bail when required state is unknown instead of pretending to refresh-and-wait - home water filter: wrap parse in try/catch, add `def` to local vars that were leaking to script scope - hood: fix cycleSpeed null check (indexOf returns -1, not null), don't pre-emit speed event before appliance confirms, move supportedFanSpeeds out of setSpeed into refresh/installed/updated, drop duplicated setIntegerErd / parseIntegerErd - ice maker: drop duplicated helpers, route through logDebug, wrap OIM_POWER parsing - dishwasher: mark Operation parameter as required, wrap parse methods in try/catch, drop bogus events initializer, drop unused locals Package manifest: - Bump to 1.0.1, refresh dateReleased and release notes Note: token / WSS credential debug logging is intentionally retained for v1 troubleshooting; will be addressed before release. https://claude.ai/code/session_01TArAXAXbb51BEW6dryM3GU
The packageManifest used to deliver the helpers library via a bundle ZIP at hubitat_SmartHQ.zip. That ZIP is no longer maintained, so HPM would have failed to install. Drop the bundles entry and add an explicit libraries entry pointing at the helpers source on main. https://claude.ai/code/session_01TArAXAXbb51BEW6dryM3GU
Refactor App
HPM does not support libraries directly in packageManifest.json (the HPM source has zero references to "librar"). The library entry I added in 99e3f1b was being silently ignored, leaving #include directives unresolvable on install. Restore the bundles entry so the helpers library ships via a Hubitat bundle ZIP, and document the bundle regeneration steps in README so it's clear when/how to rebuild. The bundle only contains smarthqHelpers; apps and drivers continue to install as plain files, so day-to-day code changes don't require re-bundling. https://claude.ai/code/session_01TArAXAXbb51BEW6dryM3GU
Restore bundle ZIP for shared library; document regeneration
Hubitat rejects "Cannot parse library definition" when the library() block contains empty-string values for `category` or `documentationLink`. The previous block (carried over from the original fork) had both empty, which only worked when delivered via a bundle ZIP - the bundle import path is more lenient than direct paste into Libraries Code. Give both fields real values so the library can be loaded either way. https://claude.ai/code/session_01TArAXAXbb51BEW6dryM3GU
Fix library() block so Hubitat parser accepts it
Hubitat's library parser rejects the file with "Cannot parse library definition" when there's a /* ... */ comment block before the library() declaration. Move the attribution / explanation to // line comments *after* the library() block, and add a warning so the next person who edits this file doesn't reintroduce the issue. https://claude.ai/code/session_01TArAXAXbb51BEW6dryM3GU
The exported bundle was committed to main as SmartHQHelpersLibrary.zip, not the placeholder name hubitat_SmartHQ.zip the manifest was using. Update both the manifest and README to match. https://claude.ai/code/session_01TArAXAXbb51BEW6dryM3GU
Add bundle
Hubitat's docs are explicit: interfaces.webSocket is driver-only, not available in apps. The previous architecture (refactored from tomw's original driver to an app) was broken from the start - any attempt to open the WebSocket threw "Cannot get property 'webSocket' on null object" because interfaces is null in app context. The earlier fix to call reinitialize() on generic exceptions just turned the fatal error into an infinite retry loop with the same NPE on each cycle. New architecture: - The app keeps the credentials UI, OAuth flow, message dispatch to appliance child devices, and reconnect scheduling - A new "SmartHQ Connector" driver (devices/smartHQ_connector) is the only thing that talks to interfaces.webSocket. The app spawns one connector child automatically (isComponent: true so the user can't accidentally delete it) and hands it the WSS endpoint after obtaining a valid OAuth token - Connector forwards Hubitat's parse() and webSocketStatus() callbacks to the app via connectorParse() / connectorOpened() / connectorDisconnected() - Appliance child drivers continue to call parent?.sendWssMap() on the app; the app just routes to connector.sendWssMessage() Manifest: - Added SmartHQ Connector driver entry, marked required: true - Bumped to 1.0.2; release notes describe the architecture change README: - Documented the connector in manual install steps and added a brief Architecture section explaining the app + connector + appliance child layout
Move WebSocket out of app into SmartHQ Connector child driver
Audited each driver against typical Hubitat automation use cases and added the missing standard capabilities so dashboard tiles render correctly and "is X running?" / "filter needs replacing" automations work natively without custom rules on string attributes. Refrigerator: added TemperatureMeasurement (mirrors fridgeActual into the standard `temperature` attribute) and FilterStatus (rolls air + water filter state up into the standard `filterStatus` attribute as normal/replace; detailed per-filter values still in airFilterStatus / waterFilterStatus). Oven: added Switch driven by upper/lower cookMode != "off". on/off log a warning - ovens cannot be started remotely via SmartHQ. Dishwasher: added Actuator and Switch. on/off map to the existing start/stop commands. Switch attribute is on while the cycle is in an active wash phase (excluding pause/delay/end so "running" is accurate). Laundry: added Switch driven by machineState run/delay states. on/off log a warning - laundry can't be started remotely. Microwave: added Switch driven by cookStatus. on/off log a warning (safety: microwaves shouldn't be remotely startable). Portable AC: dropped redundant RelaySwitch (Switch capability already provides on/off), added ThermostatMode and FilterStatus capabilities, emit thermostatMode attribute alongside thermostatOperatingState, normalized filter values from "clean"/"ok" to standard "replace"/ "normal". Ice Maker: added Actuator and FilterStatus capabilities, normalized filter values from "ok"/"expired" to standard "normal"/"replace". Hood: added Actuator capability. ROADMAP.md: tracks Phase 2 (component child devices for refrigerator doors, hood/icemaker lights, oven cavities) and other deferred items including the token-logging cleanup before wider release.
Phase 1 capability cleanup across all appliance drivers
Background: a user reported MissingMethodException calling
getDataValue("userId") on the app during first install. Root cause is
the pre-restructure version of getUserId() which had a fallback chain
parent?.getUserIdForChild() ?: parent?.getDataValue("userId") - and
parent (the app) doesn't have getDataValue. The current library has
the fallback removed, so the immediate trigger is a stale bundle ZIP
holding the old library code on the user's hub.
Add a defensive layer for the underlying race condition that will
exist with any version of the library: between WebSocket open and the
List-appliances response, state.userId is null. Previously commands
in that window built JSON messages with null userId and either
silently failed or got rejected by the API. Now buildDevDetails()
returns null when userId isn't ready, which buildErdSetter and
buildAppCtrlSetter already treat as "skip and return null" (their
existing [devDetails, erdDetails].contains(null) check).
Also add a release-checklist item to ROADMAP.md reminding maintainers
to regenerate SmartHQHelpersLibrary.zip whenever the library changes
- HPM installs bundles before plain files, so a stale bundle silently
ships old library code to every new install.
Alan_F from the Hubitat community wrote and tested a dehumidifier driver and shared it for inclusion in the package. Integrated with minor consistency edits: - Declared capabilities Actuator, FanControl, and FilterStatus alongside the attributes that already backed them. The original declared only Refresh, RelativeHumidityMeasurement, Switch, and TemperatureMeasurement, so commands and filter status didn't surface as capability-typed features to dashboards and rules. - Normalized filterStatus values from "ok"/"replace" to the standard Hubitat FilterStatus vocabulary "normal"/"replace" to match the rest of the project (refrigerator, ice maker, portable AC, etc). - Wrapped the ERD switch dispatch in a try/catch so a single malformed message can't take out the whole parse, matching the defensive pattern used across the other drivers. - Fixed a small logInfo() glitch where the else branch emitted a debug log every time info logging was disabled (accidentally noisy). It now simply no-ops when disabled. - Replaced hardcoded hex strings in the parse switch with @field constants (DH_TARGET_HUMIDITY, DH_CONTINUOUS_MODE_A/B, etc) so the ERD vocabulary is visible at a glance alongside the existing DH_POWER_STATUS / DH_FAN_SETTING constants. - Added appliance unit to temperature events (matches refrigerator and portable AC). - Simplified installed()/updated()/refresh() - the original pre- populated a large set of default attribute values which risked asserting state we didn't actually know (e.g. filterStatus="ok" before the first ERD arrives). Now refresh() only emits supportedFanSpeeds and calls refreshAppliance(); parse events drive everything else. New features beyond the other drivers: - Info-level logging preference (Alan's design): when enabled, mirrors each attribute change to log.info so external log aggregators can capture detailed device state without running debug logging globally. See ROADMAP.md for whether to roll this pattern out to the other drivers. - User-selectable F/C temperature unit via driver preferences. Manifest: - Registered SmartHQ Dehumidifier as an optional driver - Release notes credit Alan_F README / ROADMAP: - Listed Dehumidifier in Supported Appliances - ROADMAP notes that if the SmartHQ cloud returns a type string other than exactly "Dehumidifier" (e.g. "Home Dehumidifier"), apps/smartHQ_app manageChildDevice() will need an alias case - ROADMAP tracks the decision on whether to adopt Alan's info- logging preference across the rest of the drivers
Add SmartHQ Dehumidifier driver (contributed by Alan_F)
User tested install and confirmed the SmartHQ cloud returns type="Dehumidifier" (exactly matching Alan_F's driver name), so no alias case is needed in apps/smartHQ_app manageChildDevice(). Remove the ROADMAP entry that flagged this as a known-unknown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.