Update README and implement device listing tool
- Updated prerequisites in README.md to require Node.js 20.10.0 and NPM instead of Yarn. - Changed repository clone URL to reflect the new username. - Introduced a new 'list_devices' tool in src/index.ts to fetch and display all available Home Assistant devices, enhancing interaction capabilities. - Updated README.md to include detailed usage instructions for the new device listing and control tools. - Refactored environment variable names for consistency and clarity.
This commit is contained in:
419
README.md
419
README.md
@@ -16,8 +16,8 @@ The server uses the MCP protocol to share access to a local Home Assistant insta
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Node.js 16 or higher
|
- Node.js 20.10.0 or higher
|
||||||
- Yarn package manager
|
- NPM package manager
|
||||||
- A running Home Assistant instance
|
- A running Home Assistant instance
|
||||||
- A long-lived access token from Home Assistant
|
- A long-lived access token from Home Assistant
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ The server uses the MCP protocol to share access to a local Home Assistant insta
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone the repository
|
# Clone the repository
|
||||||
git clone https://github.com/yourusername/homeassistant-mcp.git
|
git clone https://github.com/jango-blockchained/homeassistant-mcp.git
|
||||||
cd homeassistant-mcp
|
cd homeassistant-mcp
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
@@ -45,9 +45,9 @@ yarn build
|
|||||||
Create a `.env` file with:
|
Create a `.env` file with:
|
||||||
|
|
||||||
```env
|
```env
|
||||||
TOKEN=your_home_assistant_token
|
NODE_ENV=development
|
||||||
BASE_URL=your_home_assistant_url # e.g., http://homeassistant.local:8123
|
HASS_HOST=your_home_assistant_url # e.g., http://homeassistant.local:8123
|
||||||
PORT=3000 # Optional, defaults to 3000
|
HASS_TOKEN=your_home_assistant_token
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **MCP Client Configuration**
|
3. **MCP Client Configuration**
|
||||||
@@ -62,120 +62,210 @@ yarn build
|
|||||||
"/path/to/dist/index.js"
|
"/path/to/dist/index.js"
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"TOKEN": "your_home_assistant_token",
|
"HASS_TOKEN": "your_home_assistant_token",
|
||||||
"BASE_URL": "your_home_assistant_url"
|
"HASS_HOST": "your_home_assistant_url"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Supported Commands
|
## How to Use
|
||||||
|
|
||||||
### Common Commands (All Entities)
|
The server provides two main tools for interacting with Home Assistant:
|
||||||
|
|
||||||
- `turn_on`: Turn entity on
|
### 1. List Devices Tool
|
||||||
- `turn_off`: Turn entity off
|
|
||||||
- `toggle`: Toggle entity state
|
|
||||||
|
|
||||||
### Light-Specific Commands
|
Use this tool to discover all available devices and their current states:
|
||||||
|
|
||||||
- Control brightness (0-255)
|
```json
|
||||||
|
{
|
||||||
|
"tool": "list_devices"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
```json
|
This will return a structured response with all devices grouped by domain:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"devices": {
|
||||||
|
"light": [
|
||||||
{
|
{
|
||||||
|
"entity_id": "light.living_room",
|
||||||
|
"state": "on",
|
||||||
|
"attributes": {
|
||||||
|
"brightness": 128,
|
||||||
|
"color_temp": 4000,
|
||||||
|
"friendly_name": "Living Room Light"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"climate": [
|
||||||
|
{
|
||||||
|
"entity_id": "climate.bedroom",
|
||||||
|
"state": "heat",
|
||||||
|
"attributes": {
|
||||||
|
"temperature": 22,
|
||||||
|
"hvac_mode": "heat",
|
||||||
|
"friendly_name": "Bedroom Thermostat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Control Tool
|
||||||
|
|
||||||
|
Use this tool to control your devices. Here are some common usage examples:
|
||||||
|
|
||||||
|
#### Light Control
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Turn on a light
|
||||||
|
{
|
||||||
|
"tool": "control",
|
||||||
|
"command": "turn_on",
|
||||||
|
"entity_id": "light.living_room"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set brightness
|
||||||
|
{
|
||||||
|
"tool": "control",
|
||||||
"command": "turn_on",
|
"command": "turn_on",
|
||||||
"entity_id": "light.living_room",
|
"entity_id": "light.living_room",
|
||||||
"brightness": 128
|
"brightness": 128
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
- Set color temperature
|
// Set color temperature
|
||||||
|
{
|
||||||
```json
|
"tool": "control",
|
||||||
{
|
|
||||||
"command": "turn_on",
|
"command": "turn_on",
|
||||||
"entity_id": "light.living_room",
|
"entity_id": "light.living_room",
|
||||||
"color_temp": 4000
|
"color_temp": 4000
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
- Set RGB color values
|
// Set RGB color (red)
|
||||||
|
{
|
||||||
```json
|
"tool": "control",
|
||||||
{
|
|
||||||
"command": "turn_on",
|
"command": "turn_on",
|
||||||
"entity_id": "light.living_room",
|
"entity_id": "light.living_room",
|
||||||
"rgb_color": [255, 0, 0]
|
"rgb_color": [255, 0, 0]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cover Commands
|
#### Climate Control
|
||||||
|
|
||||||
- `open`: Open cover
|
```json
|
||||||
- `close`: Close cover
|
// Set temperature
|
||||||
- `stop`: Stop cover movement
|
{
|
||||||
- `set_position`: Set cover position (0-100)
|
"tool": "control",
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"command": "set_position",
|
|
||||||
"entity_id": "cover.living_room",
|
|
||||||
"position": 50
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- `set_tilt_position`: Set cover tilt (0-100)
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"command": "set_tilt_position",
|
|
||||||
"entity_id": "cover.living_room",
|
|
||||||
"tilt_position": 45
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Climate Commands
|
|
||||||
|
|
||||||
- `set_temperature`: Set target temperature
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"command": "set_temperature",
|
"command": "set_temperature",
|
||||||
"entity_id": "climate.living_room",
|
"entity_id": "climate.living_room",
|
||||||
"temperature": 22
|
"temperature": 22
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
- `set_hvac_mode`: Set mode (off, heat, cool, heat_cool, auto, dry, fan_only)
|
// Set HVAC mode
|
||||||
|
{
|
||||||
```json
|
"tool": "control",
|
||||||
{
|
|
||||||
"command": "set_hvac_mode",
|
"command": "set_hvac_mode",
|
||||||
"entity_id": "climate.living_room",
|
"entity_id": "climate.living_room",
|
||||||
"hvac_mode": "heat"
|
"hvac_mode": "heat"
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
- `set_fan_mode`: Set fan mode (auto, low, medium, high)
|
// Set fan mode
|
||||||
|
{
|
||||||
```json
|
"tool": "control",
|
||||||
{
|
|
||||||
"command": "set_fan_mode",
|
"command": "set_fan_mode",
|
||||||
"entity_id": "climate.living_room",
|
"entity_id": "climate.living_room",
|
||||||
"fan_mode": "auto"
|
"fan_mode": "auto"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `set_humidity`: Set target humidity (0-100)
|
#### Cover Control
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
// Open/Close cover
|
||||||
"command": "set_humidity",
|
{
|
||||||
"entity_id": "climate.living_room",
|
"tool": "control",
|
||||||
"humidity": 45
|
"command": "open_cover", // or "close_cover"
|
||||||
}
|
"entity_id": "cover.living_room"
|
||||||
```
|
}
|
||||||
|
|
||||||
|
// Set position
|
||||||
|
{
|
||||||
|
"tool": "control",
|
||||||
|
"command": "set_position",
|
||||||
|
"entity_id": "cover.living_room",
|
||||||
|
"position": 50
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set tilt
|
||||||
|
{
|
||||||
|
"tool": "control",
|
||||||
|
"command": "set_tilt_position",
|
||||||
|
"entity_id": "cover.living_room",
|
||||||
|
"tilt_position": 45
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Switch Control
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Turn on/off
|
||||||
|
{
|
||||||
|
"tool": "control",
|
||||||
|
"command": "turn_on", // or "turn_off"
|
||||||
|
"entity_id": "switch.office"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle
|
||||||
|
{
|
||||||
|
"tool": "control",
|
||||||
|
"command": "toggle",
|
||||||
|
"entity_id": "switch.office"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
The server provides clear error messages when something goes wrong:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "Failed to execute set_temperature for light.living_room: Unsupported operation for domain: light"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Common error scenarios:
|
||||||
|
1. Invalid entity ID
|
||||||
|
2. Unsupported operation for domain
|
||||||
|
3. Invalid parameter values
|
||||||
|
4. Home Assistant connection issues
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
|
||||||
|
1. **Entity Discovery**
|
||||||
|
- Always use `list_devices` first to discover available entities
|
||||||
|
- Note the supported attributes for each device
|
||||||
|
|
||||||
|
2. **Parameter Validation**
|
||||||
|
- Brightness: 0-255
|
||||||
|
- Position/Tilt: 0-100
|
||||||
|
- Temperature: Depends on your system's configuration
|
||||||
|
- Color temperature: Typically 2000-6500K
|
||||||
|
|
||||||
|
3. **Error Recovery**
|
||||||
|
- If a command fails, check:
|
||||||
|
- Entity ID exists and is correct
|
||||||
|
- Command is supported by the domain
|
||||||
|
- Parameters are within valid ranges
|
||||||
|
|
||||||
|
4. **State Awareness**
|
||||||
|
- Use `list_devices` to check current state before making changes
|
||||||
|
- Verify command execution by checking state afterward
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
@@ -196,7 +286,7 @@ yarn test
|
|||||||
|
|
||||||
1. **Connection Errors**
|
1. **Connection Errors**
|
||||||
- Verify your Home Assistant instance is running
|
- Verify your Home Assistant instance is running
|
||||||
- Check the BASE_URL is correct and accessible
|
- Check the HASS_HOST is correct and accessible
|
||||||
- Ensure your token has the required permissions
|
- Ensure your token has the required permissions
|
||||||
|
|
||||||
2. **Entity Control Issues**
|
2. **Entity Control Issues**
|
||||||
@@ -211,7 +301,6 @@ yarn test
|
|||||||
## Project Status
|
## Project Status
|
||||||
|
|
||||||
### Completed
|
### Completed
|
||||||
|
|
||||||
- [x] Access to entities
|
- [x] Access to entities
|
||||||
- [x] Access to Floors
|
- [x] Access to Floors
|
||||||
- [x] Access to Areas
|
- [x] Access to Areas
|
||||||
@@ -224,7 +313,6 @@ yarn test
|
|||||||
- [x] Switches
|
- [x] Switches
|
||||||
|
|
||||||
### In Progress
|
### In Progress
|
||||||
|
|
||||||
- [ ] Testing / writing custom prompts
|
- [ ] Testing / writing custom prompts
|
||||||
- [ ] Testing using resources for high-level context
|
- [ ] Testing using resources for high-level context
|
||||||
- [ ] Test varying tool organization
|
- [ ] Test varying tool organization
|
||||||
@@ -248,3 +336,164 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|||||||
- [Model Context Protocol Documentation](https://modelcontextprotocol.io/introduction)
|
- [Model Context Protocol Documentation](https://modelcontextprotocol.io/introduction)
|
||||||
- [Home Assistant Documentation](https://www.home-assistant.io)
|
- [Home Assistant Documentation](https://www.home-assistant.io)
|
||||||
- [Home Assistant REST API](https://developers.home-assistant.io/docs/api/rest)
|
- [Home Assistant REST API](https://developers.home-assistant.io/docs/api/rest)
|
||||||
|
|
||||||
|
## Using with LLMs (AI Assistants)
|
||||||
|
|
||||||
|
The MCP server is designed to work seamlessly with AI language models. Here's how to interact with your Home Assistant using natural language:
|
||||||
|
|
||||||
|
### Natural Language Examples
|
||||||
|
|
||||||
|
1. **Discovering Devices**
|
||||||
|
```
|
||||||
|
"What devices do I have in my home?"
|
||||||
|
"Show me all my lights"
|
||||||
|
"List the climate controls in the bedroom"
|
||||||
|
```
|
||||||
|
The LLM will use the `list_devices` tool to fetch and present this information in a human-readable format.
|
||||||
|
|
||||||
|
2. **Basic Controls**
|
||||||
|
```
|
||||||
|
"Turn on the living room lights"
|
||||||
|
"Set the bedroom temperature to 22 degrees"
|
||||||
|
"Close all the blinds"
|
||||||
|
```
|
||||||
|
The LLM will translate these commands into appropriate tool calls using the `control` tool.
|
||||||
|
|
||||||
|
3. **Complex Operations**
|
||||||
|
```
|
||||||
|
"Make the living room cozy for movie night"
|
||||||
|
→ LLM might:
|
||||||
|
- Dim the lights (set brightness to 30%)
|
||||||
|
- Set warm color temperature
|
||||||
|
- Lower the blinds
|
||||||
|
- Adjust the temperature
|
||||||
|
|
||||||
|
"Set up my morning routine"
|
||||||
|
→ LLM might:
|
||||||
|
- Open the bedroom blinds
|
||||||
|
- Turn on specific lights
|
||||||
|
- Adjust the thermostat
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **State-Aware Commands**
|
||||||
|
```
|
||||||
|
"Is my front door closed?"
|
||||||
|
"Which lights are currently on?"
|
||||||
|
"What's the temperature in the bedroom?"
|
||||||
|
```
|
||||||
|
The LLM will check current states using `list_devices` before responding.
|
||||||
|
|
||||||
|
### Context and Memory
|
||||||
|
|
||||||
|
The LLM can maintain context across multiple interactions:
|
||||||
|
|
||||||
|
```
|
||||||
|
User: "How warm is it in the bedroom?"
|
||||||
|
LLM: [checks temperature] "The bedroom is currently 20°C"
|
||||||
|
User: "Make it a bit warmer"
|
||||||
|
LLM: [remembers context, adjusts by reasonable increment] "I'll increase it to 22°C"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Natural Parameter Handling
|
||||||
|
|
||||||
|
The LLM can interpret natural language into specific parameters:
|
||||||
|
|
||||||
|
```
|
||||||
|
"Make the lights very dim" → brightness: 10%
|
||||||
|
"Set a comfortable temperature" → temperature: 21-23°C
|
||||||
|
"Change the lights to a warm color" → color_temp: ~2700K
|
||||||
|
```
|
||||||
|
|
||||||
|
### Intelligent Error Prevention
|
||||||
|
|
||||||
|
The LLM will:
|
||||||
|
1. Validate commands before execution
|
||||||
|
2. Check device capabilities
|
||||||
|
3. Ensure parameters are within acceptable ranges
|
||||||
|
4. Provide helpful feedback if a command can't be executed
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
User: "Set the kitchen light to blue"
|
||||||
|
LLM: [checks if the light supports RGB]
|
||||||
|
- If supported: Sets rgb_color to [0, 0, 255]
|
||||||
|
- If not supported: "I'm sorry, but your kitchen light doesn't support color changes. I can only adjust its brightness."
|
||||||
|
```
|
||||||
|
|
||||||
|
### Best Practices for LLM Interactions
|
||||||
|
|
||||||
|
1. **Be Specific with Locations**
|
||||||
|
- Good: "Turn on the kitchen lights"
|
||||||
|
- Better: "Turn on the lights above the kitchen counter"
|
||||||
|
|
||||||
|
2. **Use Natural Increments**
|
||||||
|
- "Make it a little brighter" → +20% brightness
|
||||||
|
- "Make it much warmer" → +3-4°C
|
||||||
|
|
||||||
|
3. **Group Related Commands**
|
||||||
|
```
|
||||||
|
"Set up the living room for watching TV:
|
||||||
|
- Dim the lights to 20%
|
||||||
|
- Set them to a warm color
|
||||||
|
- Lower the blinds
|
||||||
|
- Set the temperature to 22 degrees"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Ask for Confirmation**
|
||||||
|
```
|
||||||
|
User: "Turn off all lights"
|
||||||
|
LLM: "I'll turn off all 12 lights in your home. Would you like me to proceed?"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Handling Complex Scenarios
|
||||||
|
|
||||||
|
1. **Conditional Commands**
|
||||||
|
```
|
||||||
|
"If the temperature is above 25°C, turn on the fan"
|
||||||
|
→ LLM will:
|
||||||
|
1. Check current temperature
|
||||||
|
2. Execute command if condition is met
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Time-Based Context**
|
||||||
|
```
|
||||||
|
"Set up my evening lighting"
|
||||||
|
→ LLM considers:
|
||||||
|
- Time of day
|
||||||
|
- Current light levels
|
||||||
|
- User preferences
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Multi-Room Coordination**
|
||||||
|
```
|
||||||
|
"Prepare the house for bedtime"
|
||||||
|
→ LLM orchestrates:
|
||||||
|
- Turning off main living area lights
|
||||||
|
- Dimming hallway lights
|
||||||
|
- Setting night mode temperatures
|
||||||
|
- Ensuring doors are locked
|
||||||
|
```
|
||||||
|
|
||||||
|
### Troubleshooting with LLMs
|
||||||
|
|
||||||
|
The LLM can help diagnose issues:
|
||||||
|
```
|
||||||
|
User: "The living room lights aren't responding"
|
||||||
|
LLM: Let me check:
|
||||||
|
1. Verifies device availability
|
||||||
|
2. Checks current state
|
||||||
|
3. Reviews recent commands
|
||||||
|
4. Suggests potential solutions
|
||||||
|
```
|
||||||
|
|
||||||
|
### Security Considerations
|
||||||
|
|
||||||
|
1. **Confirmation for Critical Actions**
|
||||||
|
- The LLM will ask for confirmation before:
|
||||||
|
- Controlling security devices
|
||||||
|
- Making large temperature changes
|
||||||
|
- Executing commands affecting multiple devices
|
||||||
|
|
||||||
|
2. **Permission Awareness**
|
||||||
|
- The LLM respects device permissions
|
||||||
|
- Provides clear feedback when actions aren't permitted
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { DomainSchema } from '../schemas.js';
|
|||||||
// Define types for tool and server
|
// Define types for tool and server
|
||||||
interface Tool {
|
interface Tool {
|
||||||
name: string;
|
name: string;
|
||||||
|
description: string;
|
||||||
execute: (params: any) => Promise<any>;
|
execute: (params: any) => Promise<any>;
|
||||||
parameters: z.ZodType<any>;
|
parameters: z.ZodType<any>;
|
||||||
}
|
}
|
||||||
|
|||||||
46
src/index.ts
46
src/index.ts
@@ -34,6 +34,51 @@ async function main() {
|
|||||||
// Create MCP server
|
// Create MCP server
|
||||||
const server = new LiteMCP('home-assistant', '0.1.0');
|
const server = new LiteMCP('home-assistant', '0.1.0');
|
||||||
|
|
||||||
|
// Add the list devices tool
|
||||||
|
server.addTool({
|
||||||
|
name: 'list_devices',
|
||||||
|
description: 'List all available Home Assistant devices',
|
||||||
|
parameters: z.object({}),
|
||||||
|
execute: async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${process.env.HASS_HOST}/api/states`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${process.env.HASS_TOKEN}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch devices: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const states = await response.json();
|
||||||
|
const devices = states.reduce((acc: any, state: any) => {
|
||||||
|
const domain = state.entity_id.split('.')[0];
|
||||||
|
if (!acc[domain]) {
|
||||||
|
acc[domain] = [];
|
||||||
|
}
|
||||||
|
acc[domain].push({
|
||||||
|
entity_id: state.entity_id,
|
||||||
|
state: state.state,
|
||||||
|
attributes: state.attributes,
|
||||||
|
});
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
devices,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: error instanceof Error ? error.message : 'Unknown error occurred',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Add the Home Assistant control tool
|
// Add the Home Assistant control tool
|
||||||
server.addTool({
|
server.addTool({
|
||||||
name: 'control',
|
name: 'control',
|
||||||
@@ -137,6 +182,7 @@ async function main() {
|
|||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported operation for domain: ${domain}`);
|
throw new Error(`Unsupported operation for domain: ${domain}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call Home Assistant service
|
// Call Home Assistant service
|
||||||
try {
|
try {
|
||||||
await hass.services[domain][service](serviceData);
|
await hass.services[domain][service](serviceData);
|
||||||
|
|||||||
Reference in New Issue
Block a user