Skip to content

Commit 1b8f49f

Browse files
committed
Fix set_mode command to use correct Maker API endpoint
- Changed endpoint from /mode/{id} to /modes/{id} (plural) - Confirmed GET method is correct (consistent with all Maker API commands) - Updated all tests to match the correct endpoint - Fixes 404 Not Found error when setting hub mode
1 parent 6702e2f commit 1b8f49f

File tree

13 files changed

+1248
-29
lines changed

13 files changed

+1248
-29
lines changed
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
# Design Document
2+
3+
## Overview
4+
5+
This feature adds Hubitat modes control to the Telegram bot by implementing three new commands: `/get_mode`, `/list_modes`, and `/set_mode`. The implementation follows the existing architecture pattern using the Maker API for hub communication, the NetworkClient for HTTP operations, and CommandHandlers for command processing.
6+
7+
Modes in Hubitat represent the operational state of the home automation system. Each hub has a set of defined modes (typically Home, Away, Night, Day, etc.) with one mode active at any time. This feature enables users to query and change modes remotely via Telegram.
8+
9+
## Architecture
10+
11+
The modes control feature integrates into the existing bot architecture:
12+
13+
1. **Main.kt** - Registers new command handlers in the bot dispatcher
14+
2. **CommandHandlers** - Implements handler functions for mode commands
15+
3. **ModeOperations** (new) - Encapsulates mode-related API operations
16+
4. **NetworkClient** - Existing interface for HTTP communication (note: needs PUT support)
17+
5. **BotConfiguration** - Existing configuration for API credentials
18+
19+
The design follows the same pattern as existing features like device commands and hub operations, ensuring consistency and maintainability.
20+
21+
**Important Note:** The NetworkClient interface currently only has a `get()` method. Since the Maker API uses PUT for setting modes, we need to either:
22+
- Add a `put()` method to NetworkClient, or
23+
- Use the underlying HTTP client directly for PUT requests
24+
25+
The implementation will need to handle this appropriately.
26+
27+
## Components and Interfaces
28+
29+
### ModeOperations Object
30+
31+
A new singleton object that encapsulates mode-related operations:
32+
33+
```kotlin
34+
data class ModeInfo(
35+
val id: Int,
36+
val name: String,
37+
val active: Boolean
38+
)
39+
40+
object ModeOperations {
41+
suspend fun getAllModes(
42+
networkClient: NetworkClient,
43+
makerApiAppId: String,
44+
makerApiToken: String,
45+
hubIp: String
46+
): Result<List<ModeInfo>>
47+
48+
suspend fun getCurrentMode(
49+
networkClient: NetworkClient,
50+
makerApiAppId: String,
51+
makerApiToken: String,
52+
hubIp: String
53+
): Result<ModeInfo>
54+
55+
suspend fun setMode(
56+
networkClient: NetworkClient,
57+
makerApiAppId: String,
58+
makerApiToken: String,
59+
hubIp: String,
60+
modeName: String
61+
): Result<String>
62+
63+
// Internal helper to find mode ID by name
64+
private suspend fun findModeIdByName(
65+
modes: List<ModeInfo>,
66+
modeName: String
67+
): Int?
68+
}
69+
```
70+
71+
### CommandHandlers Extensions
72+
73+
Add three new handler functions to the existing CommandHandlers object:
74+
75+
```kotlin
76+
object CommandHandlers {
77+
// ... existing handlers ...
78+
79+
suspend fun handleGetModeCommand(
80+
networkClient: NetworkClient,
81+
makerApiAppId: String,
82+
makerApiToken: String,
83+
hubIp: String
84+
): String
85+
86+
suspend fun handleListModesCommand(
87+
networkClient: NetworkClient,
88+
makerApiAppId: String,
89+
makerApiToken: String,
90+
hubIp: String
91+
): String
92+
93+
suspend fun handleSetModeCommand(
94+
message: Message,
95+
networkClient: NetworkClient,
96+
makerApiAppId: String,
97+
makerApiToken: String,
98+
hubIp: String
99+
): String
100+
}
101+
```
102+
103+
### Main.kt Integration
104+
105+
Register command handlers in the bot dispatcher:
106+
107+
```kotlin
108+
command("get_mode") {
109+
val result = runBlocking {
110+
CommandHandlers.handleGetModeCommand(
111+
networkClient, config.makerApiAppId,
112+
config.makerApiToken, config.defaultHubIp
113+
)
114+
}
115+
bot.sendMessage(chatId = ChatId.fromId(message.chat.id), text = result)
116+
}
117+
118+
command("list_modes") {
119+
val result = runBlocking {
120+
CommandHandlers.handleListModesCommand(
121+
networkClient, config.makerApiAppId,
122+
config.makerApiToken, config.defaultHubIp
123+
)
124+
}
125+
bot.sendMessage(chatId = ChatId.fromId(message.chat.id), text = result)
126+
}
127+
128+
command("set_mode") {
129+
val result = runBlocking {
130+
CommandHandlers.handleSetModeCommand(
131+
message, networkClient, config.makerApiAppId,
132+
config.makerApiToken, config.defaultHubIp
133+
)
134+
}
135+
bot.sendMessage(chatId = ChatId.fromId(message.chat.id), text = result)
136+
}
137+
```
138+
139+
## Data Models
140+
141+
### Mode Response Format
142+
143+
The Maker API returns mode information in JSON format:
144+
145+
**Get All Modes Response:**
146+
```json
147+
[
148+
{
149+
"name": "Home",
150+
"id": 1,
151+
"active": true
152+
},
153+
{
154+
"name": "Away",
155+
"id": 2,
156+
"active": false
157+
},
158+
{
159+
"name": "Night",
160+
"id": 3,
161+
"active": false
162+
}
163+
]
164+
```
165+
166+
The current mode is identified by the `"active": true` field in the modes list.
167+
168+
### API Endpoints
169+
170+
Based on Hubitat Maker API documentation:
171+
172+
- **Get All Modes:** `GET /apps/api/{appId}/modes?access_token={token}`
173+
- Returns a JSON array of all available modes with their IDs and active status
174+
- The current mode has `"active": true`
175+
176+
- **Set Mode:** `PUT /apps/api/{appId}/modes/{modeId}?access_token={token}`
177+
- Sets the hub mode to the specified mode ID
178+
- Note: The endpoint uses PUT method and requires the mode ID (not name)
179+
- **Implementation Flow:** When a user provides a mode name, the implementation must:
180+
1. Call GET /modes to retrieve all available modes with their IDs
181+
2. Find the mode with matching name (case-insensitive)
182+
3. Extract the mode ID
183+
4. Call PUT /modes/{modeId} with the extracted ID
184+
185+
## Correctness Properties
186+
187+
*A property is a characteristic or behavior that should hold true across all valid executions of a system—essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
188+
189+
### Property 1: Mode list display indicates active mode
190+
191+
*For any* mode list response, the currently active mode should be clearly indicated in the formatted output string.
192+
193+
**Validates: Requirements 2.2**
194+
195+
### Property 2: Successful mode retrieval includes mode name
196+
197+
*For any* successful getCurrentMode operation, the response string should contain the mode name in a clear, readable format.
198+
199+
**Validates: Requirements 1.3**
200+
201+
### Property 3: Set mode with valid name succeeds
202+
203+
*For any* mode name that appears in the getAllModes response, calling setMode with that name should succeed and return a success status.
204+
205+
**Validates: Requirements 3.1**
206+
207+
### Property 4: Invalid mode name rejection
208+
209+
*For any* string that does not appear in the getAllModes response, calling setMode with that string should fail with an appropriate error message.
210+
211+
**Validates: Requirements 3.3**
212+
213+
### Property 5: Successful mode change confirmation includes mode name
214+
215+
*For any* successful setMode operation, the confirmation message should contain the new mode name.
216+
217+
**Validates: Requirements 3.4**
218+
219+
## Error Handling
220+
221+
### Network Errors
222+
223+
- Connection failures to the Hubitat hub should return user-friendly error messages
224+
- HTTP error responses should be logged and converted to readable error messages
225+
- Timeout scenarios should be handled gracefully
226+
227+
### Validation Errors
228+
229+
- Missing mode name in `/set_mode` command should prompt the user for input
230+
- Invalid mode names should return a message listing valid modes
231+
- Empty or malformed API responses should be handled with appropriate error messages
232+
233+
### Error Message Format
234+
235+
All error messages should:
236+
- Be clear and actionable
237+
- Include relevant context (e.g., which mode was invalid)
238+
- Suggest next steps when appropriate (e.g., "Use /list_modes to see available modes")
239+
240+
## Testing Strategy
241+
242+
### Unit Tests
243+
244+
Unit tests will verify:
245+
- Command handler functions return expected messages for various inputs
246+
- Error handling for missing parameters
247+
- Message formatting for mode lists and confirmations
248+
- Integration between command handlers and ModeOperations
249+
250+
### Property-Based Tests
251+
252+
Property-based tests will use Kotest's property testing framework to verify:
253+
- Universal properties that should hold across all mode operations
254+
- Round-trip consistency (set then get returns the same mode)
255+
- Validation logic for mode names
256+
- Error handling across various invalid inputs
257+
258+
Each property-based test will:
259+
- Run a minimum of 100 iterations
260+
- Be tagged with a comment referencing the correctness property from this design document
261+
- Use the format: `**Feature: modes-control, Property {number}: {property_text}**`
262+
- Implement exactly one correctness property per test
263+
264+
The dual testing approach ensures both specific examples work correctly (unit tests) and general correctness properties hold across all inputs (property tests).
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Requirements Document
2+
3+
## Introduction
4+
5+
This feature adds Hubitat modes control capabilities to the Telegram bot. Hubitat modes represent the operational state of a smart home (e.g., Home, Away, Night, Day) and are used to trigger automations and control device behavior. Users need the ability to view the current mode and change modes through Telegram commands.
6+
7+
## Glossary
8+
9+
- **Hubitat Hub**: A smart home automation controller that manages devices and modes
10+
- **Mode**: A named state in Hubitat that represents the operational context of the home (e.g., Home, Away, Night, Day)
11+
- **Telegram Bot**: The bot application that receives commands from users via Telegram messaging
12+
- **Maker API**: Hubitat's REST API for external integrations
13+
- **Mode Manager**: The component responsible for retrieving and setting modes via the Maker API
14+
15+
## Requirements
16+
17+
### Requirement 1
18+
19+
**User Story:** As a user, I want to view the current mode of my Hubitat hub, so that I can understand the current state of my home automation system.
20+
21+
#### Acceptance Criteria
22+
23+
1. WHEN a user sends the /get_mode command THEN the Telegram Bot SHALL retrieve the current mode from the Hubitat Hub and display it to the user
24+
2. WHEN the Hubitat Hub is unreachable THEN the Telegram Bot SHALL return an error message indicating the connection failure
25+
3. WHEN the mode retrieval succeeds THEN the Telegram Bot SHALL display the mode name in a clear, readable format
26+
27+
### Requirement 2
28+
29+
**User Story:** As a user, I want to view all available modes on my Hubitat hub, so that I can see what modes I can switch to.
30+
31+
#### Acceptance Criteria
32+
33+
1. WHEN a user sends the /list_modes command THEN the Telegram Bot SHALL retrieve all available modes from the Hubitat Hub
34+
2. WHEN displaying available modes THEN the Telegram Bot SHALL indicate which mode is currently active
35+
3. WHEN the mode list retrieval fails THEN the Telegram Bot SHALL return an error message with failure details
36+
37+
### Requirement 3
38+
39+
**User Story:** As a user, I want to change the mode of my Hubitat hub, so that I can control my home automation state remotely.
40+
41+
#### Acceptance Criteria
42+
43+
1. WHEN a user sends the /set_mode command with a valid mode name THEN the Telegram Bot SHALL change the Hubitat Hub mode to the specified mode
44+
2. WHEN a user sends the /set_mode command without a mode name THEN the Telegram Bot SHALL return a message requesting the mode name
45+
3. WHEN a user sends the /set_mode command with an invalid mode name THEN the Telegram Bot SHALL return an error message listing valid mode names
46+
4. WHEN the mode change succeeds THEN the Telegram Bot SHALL return a confirmation message with the new mode name
47+
5. WHEN the mode change fails THEN the Telegram Bot SHALL return an error message with failure details
48+
49+
### Requirement 4
50+
51+
**User Story:** As a developer, I want mode operations to integrate with the existing network client and configuration system, so that the implementation is consistent with the current codebase architecture.
52+
53+
#### Acceptance Criteria
54+
55+
1. WHEN making mode API calls THEN the Mode Manager SHALL use the existing NetworkClient interface
56+
2. WHEN accessing hub configuration THEN the Mode Manager SHALL use the existing BotConfiguration for API credentials
57+
3. WHEN handling mode commands THEN the CommandHandlers object SHALL follow the same pattern as existing command handlers

0 commit comments

Comments
 (0)