diff --git a/docs/README.md b/docs/README.md index f95eefe..225bac5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,127 +1,134 @@ # Indigo Plugin Development Documentation -**Streamlined, optimized documentation for building Indigo plugins.** +Optimized documentation for building Indigo plugins. ---- - -## 🚀 Start Here +## Start Here ### New to Indigo Plugin Development? **[Quick Start Guide](quick-start.md)** - Create your first plugin in minutes -- Setup and prerequisites -- Hello World plugin walkthrough -- Plugin structure explained -- Common beginner mistakes ---- +## Documentation Structure + +| Section | Purpose | +|---------|---------| +| [Concepts](concepts/) | Plugin lifecycle, devices, events, preferences | +| [API Reference](api/) | Indigo Object Model reference | +| [Patterns](patterns/) | Implementation patterns and best practices | +| [Examples](examples/) | SDK example plugin catalog | +| [Troubleshooting](troubleshooting/) | Common issues and solutions | + +## Quick Navigation by Use Case + +### "How do I create a device?" + +1. **Start**: [Concepts → Devices](concepts/devices.md) - Device types, Devices.xml, ConfigUI +2. **API details**: [API → IOM → Devices](api/iom/devices.md) - Device class properties/methods +3. **Example**: [SDK Examples Guide](examples/sdk-examples-guide.md) + +### "What device properties/methods exist?" + +→ [API → IOM → Devices](api/iom/devices.md) + +### "How do I handle device lifecycle?" -## 📖 Core Documentation +→ [Concepts → Plugin Lifecycle](concepts/plugin-lifecycle.md#device-lifecycle-callbacks) -### [Concepts](concepts/) -**Essential architectural concepts** -- **[Plugin Lifecycle](concepts/plugin-lifecycle.md)** - `__init__()`, `startup()`, `shutdown()`, concurrent threads -- **[Device Development](concepts/devices.md)** - Device types, states, configuration UIs +### "How do I update device state?" -### [API Reference](api/) -**Complete API documentation** -- **[Indigo Object Model](api/indigo-object-model.md)** - Devices, variables, server objects, constants +→ [Patterns → API Patterns](patterns/api-patterns.md#device-state-updates) -### [Examples](examples/) -**16 official SDK example plugins + patterns** -- **[SDK Examples Guide](examples/sdk-examples-guide.md)** - Complete catalog of all examples -- Device types, integrations, hardware protocols +### "How do I save plugin preferences?" -### [Troubleshooting](troubleshooting/) -**Common issues and solutions** -- **[Common Issues](troubleshooting/common-issues.md)** - Debugging guide with solutions +→ [Concepts → Plugin Preferences](concepts/plugin-preferences.md) -### [Patterns](patterns/) -**Implementation patterns** (coming soon) -- API integration, polling, error handling, configuration UI +### "How do I create custom trigger events?" ---- +→ [Concepts → Custom Events](concepts/events.md) -## đŸŽ¯ Quick Navigation +### "How do I iterate/filter devices?" -### I want to... +→ [API → IOM → Filters](api/iom/filters.md) -| Task | Go To | -|------|-------| -| Create my first plugin | [Quick Start](quick-start.md) | -| Understand plugin lifecycle | [Concepts → Plugin Lifecycle](concepts/plugin-lifecycle.md) | -| Create devices | [Concepts → Devices](concepts/devices.md) | -| Look up API methods | [API → Indigo Object Model](api/indigo-object-model.md) | -| Find code examples | [Examples → SDK Guide](examples/sdk-examples-guide.md) | -| Debug an error | [Troubleshooting → Common Issues](troubleshooting/common-issues.md) | +### "How does replaceOnServer work?" -### By Device Type +→ [API → IOM → Architecture](api/iom/architecture.md) + +### "How do I subscribe to changes?" + +→ [API → IOM → Subscriptions](api/iom/subscriptions.md) + +### "What constants/icons are available?" + +→ [API → IOM → Constants](api/iom/constants.md) + +## By Device Type | Device | Example | |--------|---------| -| Custom device with unique states | [Example Device - Custom](examples/sdk-examples-guide.md#example-device---custom) | +| Custom device | [Example Device - Custom](examples/sdk-examples-guide.md#example-device---custom) | | Lights/switches | [Example Device - Relay/Dimmer](examples/sdk-examples-guide.md#example-device---relay-and-dimmer) | | Thermostat | [Example Device - Thermostat](examples/sdk-examples-guide.md#example-device---thermostat) | | Sensors | [Example Device - Sensor](examples/sdk-examples-guide.md#example-device---sensor) | -| Web API/Dashboard | [Example HTTP Responder](examples/sdk-examples-guide.md#example-http-responder) | - ---- - -## đŸ“Ļ What's Included - -- ✅ **Comprehensive guides** - Step-by-step tutorials and references -- ✅ **16 official SDK examples** - Production-quality code in `../sdk-examples/` -- ✅ **API reference** - Complete Indigo Object Model documentation -- ✅ **Troubleshooting** - Common issues and solutions -- ✅ **Code snippets** - Ready-to-use templates in `../snippets/` +| Web API | [Example HTTP Responder](examples/sdk-examples-guide.md#example-http-responder) | ---- +## For Claude (Context Optimization) -## 🔍 For Claude (AI Assistant) +### File Loading Strategy -**Context optimization guidelines**: +**Concepts vs API**: These are complementary, not duplicates: +- `concepts/devices.md` → Design (Devices.xml, ConfigUI, validation) +- `api/iom/devices.md` → Reference (class properties, methods) -**Load these files (all < 12KB)**: -- `quick-start.md` - Complete getting started guide -- `concepts/plugin-lifecycle.md` - Lifecycle reference -- `concepts/devices.md` - Device development -- `api/indigo-object-model.md` - API reference -- `examples/sdk-examples-guide.md` - Example catalog -- `troubleshooting/common-issues.md` - Troubleshooting +**Load based on question type**: -**Never load all at once**: -- `../sdk-examples/` - 16 complete plugins (1.3MB) - Load individually only +| User Question | Load | +|---------------|------| +| "How do I create a device?" | `concepts/devices.md` | +| "What properties does a device have?" | `api/iom/devices.md` | +| "How do I update state?" | `patterns/api-patterns.md` | +| "How do I save settings?" | `concepts/plugin-preferences.md` | +| "How do I create trigger events?" | `concepts/events.md` | +| "Show me an example" | Specific example from `sdk-examples/` | -**Strategy**: -1. Check `examples/sdk-examples-guide.md` to find relevant example -2. Use Read tool to load ONLY that specific example -3. Don't load multiple examples simultaneously +### Modular IOM Files ---- +The IOM documentation is split into focused files (~4KB each): -## 📚 External Resources +| Topic | File | +|-------|------| +| Core architecture | `api/iom/architecture.md` | +| Device classes | `api/iom/devices.md` | +| Trigger classes | `api/iom/triggers.md` | +| Iteration filters | `api/iom/filters.md` | +| Change subscriptions | `api/iom/subscriptions.md` | +| Constants/icons | `api/iom/constants.md` | +| indigo.Dict/List | `api/iom/containers.md` | +| Utility functions | `api/iom/utilities.md` | -- [Official Plugin Guide](https://www.indigodomo.com/docs/plugin_guide) - Complete development guide -- [Object Model Reference](https://www.indigodomo.com/docs/object_model_reference) - API reference -- [Scripting Tutorial](https://www.indigodomo.com/docs/plugin_scripting_tutorial) - Learn by examples -- [Developer Forum](https://forums.indigodomo.com/viewforum.php?f=18) - Community support +### Concept Files ---- +| Topic | File | +|-------|------| +| Plugin lifecycle | `concepts/plugin-lifecycle.md` | +| Device development | `concepts/devices.md` | +| Plugin preferences | `concepts/plugin-preferences.md` | +| Custom events | `concepts/events.md` | -## 🤝 Contributing +Load only the specific topic needed. -This documentation is community-maintained! +### Never Load All at Once -Found an issue or want to add documentation? -- See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines -- Open an issue or pull request -- Join discussions on GitHub +- `sdk-examples/` - 16 complete plugins (1.3MB) - Load individually +- Don't load all IOM files together - load by topic ---- +## External Resources -## â„šī¸ Version Information +- [Official Plugin Guide](https://wiki.indigodomo.com/doku.php?id=indigo_2025.1_documentation:plugin_guide) +- [Object Model Reference](https://wiki.indigodomo.com/doku.php?id=indigo_2025.1_documentation:object_model_reference) +- [Developer Forum](https://forums.indigodomo.com/viewforum.php?f=18) -- **Current**: Indigo 2023.2+ (Python 3.10+) -- **Legacy**: 2022.x and earlier (Python 2.7, not recommended) +## Version Information -All documentation focuses on current versions. For migration help, see [`../reference/Python3-Migration-Guide.md`](../reference/Python3-Migration-Guide.md). +- **Current**: Indigo 2025.1+ (Python 3.10+) +- **Migration**: See [`../reference/Python3-Migration-Guide.md`](../reference/Python3-Migration-Guide.md) diff --git a/docs/api/README.md b/docs/api/README.md index 5c55b36..aaf4d1d 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -1,148 +1,48 @@ # API Reference -Detailed reference documentation for the Indigo Plugin API. - -## Available Documentation - -### [Indigo Object Model](indigo-object-model.md) -Complete reference for the Indigo Object Model (IOM) - the Python API for accessing Indigo objects. - -**Topics covered**: -- Core collections (devices, variables, triggers, actions, schedules) -- Device object properties and methods -- Variable object and management -- Server object and utilities -- Constants (state icons, device actions, protocols) -- Subscriptions and change callbacks - -**Use when**: Looking up specific API methods, constants, or object properties - -## Quick Lookups - -### Common API Tasks - -| Task | See | -|------|-----| -| Access device by ID/name | [Indigo Object Model → Devices](indigo-object-model.md#devices) | -| Update device states | [Indigo Object Model → Device Methods](indigo-object-model.md#device-methods) | -| Access variables | [Indigo Object Model → Variables](indigo-object-model.md#variables) | -| Subscribe to changes | [Indigo Object Model → Subscriptions](indigo-object-model.md#subscriptions) | -| State icons reference | [Indigo Object Model → State Icons](indigo-object-model.md#state-icons) | -| Device action constants | [Indigo Object Model → Device Actions](indigo-object-model.md#device-actions) | - -### Validation and Configuration - -These topics are covered in the [Concepts](../concepts/) section: - -- **Device configuration validation**: See [Concepts → Devices → Configuration Validation](../concepts/devices.md#configuration-validation) -- **Dynamic lists**: See [Concepts → Devices → Dynamic Lists](../concepts/devices.md#dynamic-lists) -- **Plugin configuration UI**: See [Concepts → Devices → Configuration UI](../concepts/devices.md#configuration-ui) - -## Documentation Roadmap - -We're actively expanding API documentation. Planned additions: - -### Plugin Base Class -- Complete method signatures and parameters -- Lifecycle method reference -- Action handling methods -- Validation callback reference -- Logging methods (`self.logger`) - -### XML Configuration Reference -- Devices.xml complete reference -- Actions.xml schema -- Events.xml schema -- MenuItems.xml patterns -- PluginConfig.xml reference - -### Advanced Topics -- Threading and concurrency patterns -- Database access patterns -- Performance optimization -- Memory management +Reference documentation for the Indigo Plugin API. + +## Indigo Object Model (IOM) + +The IOM provides Python access to all Indigo objects. Start with the [overview](indigo-object-model.md), then dive into specific topics: + +| Topic | Description | +|-------|-------------| +| [Overview & Quick Reference](indigo-object-model.md) | Index and common patterns | +| [Architecture](iom/architecture.md) | Client-server model, replaceOnServer pattern | +| [Command Namespaces](iom/command-namespaces.md) | indigo.dimmer.*, indigo.relay.*, etc. | +| [Device Classes](iom/devices.md) | DimmerDevice, SensorDevice, ThermostatDevice, etc. | +| [Trigger Classes](iom/triggers.md) | Trigger types and plugin events | +| [Containers](iom/containers.md) | indigo.Dict and indigo.List behavior | +| [Filters](iom/filters.md) | Device/trigger iteration filters | +| [Subscriptions](iom/subscriptions.md) | Change callbacks and low-level events | +| [Constants](iom/constants.md) | Icons, actions, protocols, modes | +| [Utilities](iom/utilities.md) | Helper functions and classes | + +## Quick Task Lookup + +| Task | Documentation | +|------|---------------| +| Turn on/off devices | [Command Namespaces](iom/command-namespaces.md) | +| Update device states | [Device Classes](iom/devices.md#device-methods) | +| Iterate specific device types | [Filters](iom/filters.md#device-filters) | +| Subscribe to device changes | [Subscriptions](iom/subscriptions.md) | +| Modify object properties | [Architecture](iom/architecture.md#replaceOnServer-pattern) | +| Handle nested indigo.Dict | [Containers](iom/containers.md#critical-copy-semantics) | +| Serve static files | [Utilities](iom/utilities.md#return_static_file) | +| State icon reference | [Constants](iom/constants.md#state-image-icons) | + +## Related Documentation + +| Topic | Location | +|-------|----------| +| Plugin structure and lifecycle | [Concepts](../concepts/) | +| Device configuration UI | [Concepts → Devices](../concepts/devices.md) | +| Implementation patterns | [Patterns](../patterns/) | +| Working examples | [Examples](../examples/) | +| SDK example plugins | [sdk-examples/](../../sdk-examples/) | ## External References -### Official Documentation - -- **[Object Model Reference](https://www.indigodomo.com/docs/object_model_reference)** - Complete official API reference -- **[Plugin Guide](https://www.indigodomo.com/docs/plugin_guide)** - Plugin development guide with API examples - -### Related Skill Documentation - -- [Quick Start](../quick-start.md) - Get started building plugins -- [Concepts](../concepts/) - Core architectural concepts -- [Patterns](../patterns/) - Implementation patterns and best practices -- [Examples](../examples/) - Working code examples - -## Usage Tips - -### For Claude (AI Assistant) - -When helping users with API questions: - -1. **Check [indigo-object-model.md](indigo-object-model.md) first** for constants, object properties, and core API methods -2. **Reference official docs** for detailed specifications -3. **Show code examples** from the skill's example plugins -4. **Link to patterns** for complex integrations - -### For Developers - -1. **Bookmark the official Object Model Reference** - It's comprehensive and authoritative -2. **Use this skill's API docs** for quick lookups and code examples -3. **Check [Concepts](../concepts/)** for understanding how APIs fit together -4. **See [Patterns](../patterns/)** for best practices using the APIs - -## Contributing - -This is a high-priority documentation area! Contributions welcome for: - -- Method signatures with parameter descriptions -- Return value documentation -- Code examples for API calls -- Common usage patterns -- Edge cases and gotchas -- Performance tips - -See [CONTRIBUTING.md](../../CONTRIBUTING.md) for guidelines. - -## Example: Quick API Reference - -### Accessing Devices - -```python -# By ID -dev = indigo.devices[123456] - -# By name -dev = indigo.devices["Living Room Lamp"] - -# Iterate plugin's devices only -for dev in indigo.devices.iter("self"): - print(dev.name) -``` - -### Updating States - -```python -# Single state -dev.updateStateOnServer('temperature', value=72.5) - -# Multiple states (more efficient) -dev.updateStatesOnServer([ - {'key': 'temperature', 'value': 72.5}, - {'key': 'humidity', 'value': 45.2}, - {'key': 'status', 'value': 'online'} -]) -``` - -### Setting Icons - -```python -dev.updateStateImageOnServer(indigo.kStateImageSel.SensorOn) -dev.updateStateImageOnServer(indigo.kStateImageSel.PowerOff) -dev.updateStateImageOnServer(indigo.kStateImageSel.Error) -``` - -For complete examples, see [indigo-object-model.md](indigo-object-model.md). +- [Official Object Model Reference](https://wiki.indigodomo.com/doku.php?id=indigo_2025.1_documentation:object_model_reference) +- [Plugin Developer's Guide](https://wiki.indigodomo.com/doku.php?id=indigo_2025.1_documentation:plugin_guide) diff --git a/docs/api/indigo-object-model.md b/docs/api/indigo-object-model.md index 482639f..23ab203 100644 --- a/docs/api/indigo-object-model.md +++ b/docs/api/indigo-object-model.md @@ -1,389 +1,103 @@ # Indigo Object Model (IOM) Reference -**Official Documentation**: https://www.indigodomo.com/docs/object_model_reference +The IOM provides Python access to Indigo objects. See [official documentation](https://wiki.indigodomo.com/doku.php?id=indigo_2025.1_documentation:object_model_reference). -## Overview +## Detailed Documentation -The Indigo Object Model provides Python access to all Indigo objects (devices, variables, triggers, actions, etc.). All objects are accessed through the `indigo` module which is automatically imported when your plugin runs. +| Topic | File | +|-------|------| +| Architecture & Core Concepts | [iom/architecture.md](iom/architecture.md) | +| Command Namespaces | [iom/command-namespaces.md](iom/command-namespaces.md) | +| Device Classes | [iom/devices.md](iom/devices.md) | +| Trigger Classes | [iom/triggers.md](iom/triggers.md) | +| indigo.Dict & indigo.List | [iom/containers.md](iom/containers.md) | +| Iteration Filters | [iom/filters.md](iom/filters.md) | +| Subscriptions & Callbacks | [iom/subscriptions.md](iom/subscriptions.md) | +| Constants | [iom/constants.md](iom/constants.md) | +| Utility Functions | [iom/utilities.md](iom/utilities.md) | -## Core Collections +## Quick Reference -### Devices +### Access Objects ```python -# Access by ID -dev = indigo.devices[123456] - -# Access by name -dev = indigo.devices["Living Room Lamp"] - -# Check existence -if "Living Room Lamp" in indigo.devices: - dev = indigo.devices["Living Room Lamp"] - -# Iterate all devices -for dev in indigo.devices: - print(dev.name) - -# Iterate only this plugin's devices -for dev in indigo.devices.iter("self"): - print(dev.name) - -# Iterate devices by plugin ID -for dev in indigo.devices.iter(pluginId="com.example.plugin"): - print(dev.name) - -# Iterate by filter -for dev in indigo.devices.iter(filter="indigo.dimmer"): - print(f"Dimmer: {dev.name}, brightness: {dev.brightness}") -``` - -### Variables - -```python -# Access by ID -var = indigo.variables[123456] - -# Access by name -var = indigo.variables["MyVariable"] - -# Get/set value -value = var.value -var.value = "new value" # Updates in Indigo - -# Create variable -new_var = indigo.variable.create("MyNewVariable", value="initial") - -# Update via ID -indigo.variable.updateValue(var_id, "new value") - -# Iterate all variables -for var in indigo.variables: - print(f"{var.name} = {var.value}") -``` - -### Triggers - -```python -# Access trigger +dev = indigo.devices[123456] # By ID +dev = indigo.devices["Device Name"] # By name +var = indigo.variables["VarName"] trigger = indigo.triggers[trigger_id] - -# Check if enabled -if trigger.enabled: - print("Trigger is active") - -# Iterate triggers -for trigger in indigo.triggers: - if trigger.pluginId == "self": - print(f"My trigger: {trigger.name}") -``` - -### Action Groups - -```python -# Access action group ag = indigo.actionGroups[ag_id] - -# Execute action group -indigo.actionGroup.execute(ag_id) - -# Iterate -for ag in indigo.actionGroups: - print(ag.name) ``` -### Schedules +### Iterate Objects ```python -# Access schedule -sched = indigo.schedules[sched_id] - -# Check if enabled -if sched.enabled: - print("Schedule active") +for dev in indigo.devices: # All devices +for dev in indigo.devices.iter("self"): # Plugin's devices +for dev in indigo.devices.iter("indigo.dimmer"): # All dimmers ``` -## Device Object - -**Reference**: https://www.indigodomo.com/docs/object_model_reference#device - -### Common Properties +### Modify Objects ```python -dev.id # Unique device ID (int) -dev.name # Device name (str) -dev.deviceTypeId # Type from Devices.xml (str) -dev.pluginId # Plugin bundle ID (str) -dev.enabled # Is enabled? (bool) -dev.configured # Is configured? (bool) -dev.description # Description text (str) -dev.model # Model name (str) -dev.protocol # Protocol type (indigo.kProtocol) -dev.address # Device address (str) -dev.version # Firmware version (str) -dev.subModel # Sub-model identifier (str) - -# Plugin properties -dev.pluginProps # indigo.Dict of ConfigUI values -dev.ownerProps # Read-only plugin properties - -# States -dev.states # indigo.Dict of all states -dev.states['stateName'] # Access specific state -dev.displayStateId # ID of primary display state -dev.displayStateValue # Value of primary display state -dev.displayStateImageSel # Current state icon - -# Remote display -dev.remoteDisplay # Text shown on remote control -``` - -### Device Type Specific Properties - -#### Relays/Dimmers -```python -dev.onState # Is device on? (bool) -dev.brightness # Brightness 0-100 (int) - dimmers only -``` +# Get copy, modify, push back +dev = indigo.devices[123456] +dev.name = "New Name" +dev.replaceOnServer() -#### Sensors -```python -dev.onState # Sensor tripped state -dev.onOffState # ON or OFF -dev.sensorValue # Numeric sensor value -``` +# Update state +dev.updateStateOnServer('myState', value=42) -#### Thermostats -```python -dev.heatSetpoint # Heat setpoint -dev.coolSetpoint # Cool setpoint -dev.hvacMode # Current mode -dev.fanMode # Fan mode -dev.temperatureInput1 # Current temperature +# Update plugin props +dev.replacePluginPropsOnServer(new_props) ``` -### Device Methods +### Send Commands ```python -# Update states -dev.updateStateOnServer('stateName', value=new_value) -dev.updateStatesOnServer(key_value_list) - -# Update icon -dev.updateStateImageOnServer(indigo.kStateImageSel.SensorOn) - -# Set error state -dev.setErrorStateOnServer("Error message") -dev.setErrorStateOnServer(None) # Clear error - -# Refresh state list (call if Devices.xml changed) -dev.stateListOrDisplayStateIdChanged() - -# Get as dict -dev_dict = dict(dev) +indigo.dimmer.turnOn(dev) +indigo.dimmer.setBrightness(dev, value=75) +indigo.relay.toggle(dev) +indigo.thermostat.setHeatSetpoint(dev, value=68) +indigo.variable.updateValue(var, value="new") +indigo.actionGroup.execute(ag) ``` -## Variable Object - -**Reference**: https://www.indigodomo.com/docs/object_model_reference#variable +### Server Commands ```python -var.id # Unique variable ID -var.name # Variable name -var.value # Current value (str) -var.readOnly # Is read-only? (bool) - -# Modify variable -var.value = "new value" -indigo.variable.updateValue(var.id, "new value") - -# Create variable -new_var = indigo.variable.create("VarName", value="initial", folder=folder_id) - -# Folder -var.folderId # Parent folder ID -var.displayInRemoteUI # Show in remote UI? -``` - -## Server Object - -```python -# Get server time -server_time = indigo.server.getTime() - -# Get API version -api_version = indigo.server.apiVersion # e.g., "3.6" - -# Get Indigo version -indigo_version = indigo.server.version # e.g., "2023.2.0" - -# Log to Event Log indigo.server.log("message") -indigo.server.error("error message") - -# Speak text -indigo.server.speak("Hello world") -``` - -## Trigger Object - -```python -trigger.id # Trigger ID -trigger.name # Trigger name -trigger.enabled # Is enabled? -trigger.pluginId # Plugin ID if plugin trigger -trigger.pluginTypeId # Event type ID -``` - -## Action Group Object - -```python -ag.id # Action group ID -ag.name # Action group name -ag.enabled # Is enabled? - -# Execute -indigo.actionGroup.execute(ag.id) -``` - -## Constants - -### State Icons - -```python -indigo.kStateImageSel.Auto -indigo.kStateImageSel.AvPaused -indigo.kStateImageSel.AvPlaying -indigo.kStateImageSel.AvStopped -indigo.kStateImageSel.DimmerOff -indigo.kStateImageSel.DimmerOn -indigo.kStateImageSel.Error -indigo.kStateImageSel.FanHigh -indigo.kStateImageSel.FanLow -indigo.kStateImageSel.FanMedium -indigo.kStateImageSel.FanOff -indigo.kStateImageSel.HvacAutoMode -indigo.kStateImageSel.HvacCoolMode -indigo.kStateImageSel.HvacFanMode -indigo.kStateImageSel.HvacHeatMode -indigo.kStateImageSel.HvacOff -indigo.kStateImageSel.LightSensor -indigo.kStateImageSel.LightSensorOn -indigo.kStateImageSel.Locked -indigo.kStateImageSel.MotionSensor -indigo.kStateImageSel.MotionSensorTripped -indigo.kStateImageSel.NoImage -indigo.kStateImageSel.PowerOff -indigo.kStateImageSel.PowerOn -indigo.kStateImageSel.SensorOff -indigo.kStateImageSel.SensorOn -indigo.kStateImageSel.SensorTripped -indigo.kStateImageSel.SprinklerOff -indigo.kStateImageSel.SprinklerOn -indigo.kStateImageSel.TimerOff -indigo.kStateImageSel.TimerOn -indigo.kStateImageSel.Unlocked -``` - -### Device Actions - -```python -# Relay/Dimmer -indigo.kDeviceAction.TurnOn -indigo.kDeviceAction.TurnOff -indigo.kDeviceAction.Toggle -indigo.kDeviceAction.SetBrightness -indigo.kDeviceAction.BrightenBy -indigo.kDeviceAction.DimBy -indigo.kDeviceAction.RequestStatus - -# Thermostat -indigo.kThermostatAction.SetHeatSetpoint -indigo.kThermostatAction.SetCoolSetpoint -indigo.kThermostatAction.SetHvacMode -indigo.kThermostatAction.SetFanMode - -# Sensor -indigo.kSensorAction.TurnOn -indigo.kSensorAction.TurnOff - -# Sprinkler -indigo.kSprinklerAction.ZoneOn -indigo.kSprinklerAction.ZoneOff -indigo.kSprinklerAction.AllZonesOff +indigo.server.speak("Hello") +time = indigo.server.getTime() ``` -### Protocols +### Subscribe to Changes ```python -indigo.kProtocol.Plugin -indigo.kProtocol.Insteon -indigo.kProtocol.X10 -indigo.kProtocol.ZWave -``` - -## indigo.Dict - -Special dictionary class used throughout Indigo: - -```python -# Create -d = indigo.Dict() - -# Access with dot notation or brackets -d.key = "value" -d['key'] = "value" -value = d.key -value = d['key'] - -# Get with default -value = d.get('key', 'default') - -# Check existence -if 'key' in d: - pass - -# Convert to regular dict -regular_dict = dict(d) -``` - -## Subscriptions - -Subscribe to object changes: - -```python -# Variables -indigo.variables.subscribeToChanges() - -# Devices +# In startup() indigo.devices.subscribeToChanges() -indigo.devices.subscribeToChanges(pluginId="com.example.plugin") -# Implement callbacks -def variableUpdated(self, orig_var, new_var): - super().variableUpdated(orig_var, new_var) - # Handle change - -def deviceUpdated(self, orig_dev, new_dev): - super().deviceUpdated(orig_dev, new_dev) +# Callback +def deviceUpdated(self, origDev, newDev): + indigo.PluginBase.deviceUpdated(self, origDev, newDev) # Handle change ``` -## JSON Encoding - -Use Indigo's JSON encoder for proper date handling: +## Key Concepts -```python -import json - -# Serialize device with proper date encoding -json_str = json.dumps(dict(dev), indent=4, cls=indigo.utils.JSONDateEncoder) -``` +1. **Objects are copies** - When you get an object, you get a copy, not the real object +2. **Use command namespaces** - `indigo.dimmer.turnOn(dev)` not `dev.turnOn()` +3. **replaceOnServer()** - Push local changes to server +4. **Store IDs, not names** - Names can change, IDs are permanent -## Official References +## Common Tasks -- [Complete Object Model Reference](https://www.indigodomo.com/docs/object_model_reference) -- [Device Object](https://www.indigodomo.com/docs/object_model_reference#device) -- [Variable Object](https://www.indigodomo.com/docs/object_model_reference#variable) -- [Server Object](https://www.indigodomo.com/docs/object_model_reference#server) -- [Constants Reference](https://www.indigodomo.com/docs/object_model_reference#constants) +| Task | Solution | +|------|----------| +| Turn on a device | `indigo.dimmer.turnOn(dev)` | +| Update device state | `dev.updateStateOnServer('key', value=val)` | +| Create variable | `indigo.variable.create("Name", value="val")` | +| Execute action group | `indigo.actionGroup.execute(ag_id)` | +| Get device name efficiently | `indigo.devices.getName(dev_id)` | +| Log message | `indigo.server.log("message")` | +| Convert to dict | `dict(dev)` | +| Serialize to JSON | `json.dumps(dict(dev), cls=indigo.utils.IndigoJSONEncoder)` | diff --git a/docs/api/iom/architecture.md b/docs/api/iom/architecture.md new file mode 100644 index 0000000..9332e2d --- /dev/null +++ b/docs/api/iom/architecture.md @@ -0,0 +1,243 @@ +# IOM Architecture and Core Concepts + +## Client-Server Architecture + +The Indigo Object Model runs in a **separate process** from IndigoServer. This design prevents scripts/plugins from crashing the server. + +**Key implication**: When you get an object, you receive a **copy**, not the actual object. + +## Object Manipulation Rules + +### For All Scripts and Plugins + +| Task | Method | +|------|--------| +| Create/duplicate/delete objects | Use command namespace (e.g., `indigo.device.create()`) | +| Send commands (turnOn, etc.) | Use command namespace with object reference | +| Modify object definition | Get copy → modify → `myObject.replaceOnServer()` | +| Refresh local copy | Call `myObject.refreshFromServer()` | + +### For Plugin Developers Only + +| Task | Method | +|------|--------| +| Update device plugin props | `myDevice.replacePluginPropsOnServer(newPropsDict)` | +| Update device state | `myDevice.updateStateOnServer(key="keyName", value="Value")` | + +## Object IDs + +Every top-level object has a **globally unique integer ID**: +- Action groups, devices, folders, schedules, triggers, variables +- Commands accept ID, object reference, or name +- **Always store IDs, never names** (names can change) + +```python +# All equivalent - but ID is preferred +indigo.device.turnOn(123456) # Best - ID +indigo.device.turnOn(dev) # Good - object reference +indigo.device.turnOn("Living Room") # Avoid - name can change +``` + +**Tip**: Control-click on any object in Indigo's Main Window to copy its ID. + +## replaceOnServer Pattern + +The standard pattern for modifying object definitions: + +```python +# Get a copy +dev = indigo.devices[123456] + +# Modify locally +dev.name = "New Name" +dev.description = "Updated description" + +# Push changes to server +dev.replaceOnServer() +``` + +## refreshFromServer Pattern + +Update your local copy to match the server: + +```python +dev = indigo.devices[123456] +# ... time passes, device may have changed ... +dev.refreshFromServer() +# dev now has current server values +``` + +## Plugin Props (pluginProps) + +Every Indigo object supports plugin-specific metadata via `pluginProps`. + +### Access Rules + +| Context | Access | +|---------|--------| +| Plugin's own objects | Read/Write | +| Scripts | Read-only | +| Other plugins | Read-only (via globalProps) | + +### Supported Value Types + +- Numbers (int, float) +- Booleans +- Strings +- `indigo.Dict()` +- `indigo.List()` + +### Reading pluginProps + +```python +dev = indigo.devices[123456] # Use ID, not name + +# Direct access +address = dev.pluginProps["address"] + +# Safe access with default +interval = dev.pluginProps.get("pollingInterval", 60) +``` + +### Writing pluginProps + +```python +dev = indigo.devices[123456] + +# Get current props +newProps = dev.pluginProps + +# Modify +newProps["onCycles"] = 5 +newProps["lastUpdate"] = str(datetime.now()) +newProps["customData"] = {"key": "value"} + +# Push to server +dev.replacePluginPropsOnServer(newProps) +``` + +### Incrementing Values + +```python +dev = indigo.devices[123456] +newProps = dev.pluginProps +newProps["onCycles"] = newProps.get("onCycles", 0) + 1 +dev.replacePluginPropsOnServer(newProps) +``` + +### globalProps (Cross-Plugin Access) + +Plugins have read-only access to other plugins' props: + +```python +dev = indigo.devices[123456] + +# Access another plugin's data (read-only) +other_plugin_data = dev.globalProps.get("com.other.plugin", {}) +some_value = other_plugin_data.get("someKey") +``` + +## Built-in Collection Objects + +| Object | Description | +|--------|-------------| +| `indigo.devices` | All devices | +| `indigo.triggers` | All triggers | +| `indigo.schedules` | All schedules | +| `indigo.actionGroups` | All action groups | +| `indigo.variables` | All variables | + +These collections are **read-only** - use command namespaces to modify. + +### Folders Attribute + +Each collection has a `folders` attribute: + +```python +# Access device folders +for folder in indigo.devices.folders: + print(folder.name) + +# Access variable folders +for folder in indigo.variables.folders: + print(folder.name) +``` + +### getName() Convenience Method + +Efficiently get an object's name without fetching the entire object: + +```python +# More efficient than indigo.devices[123].name +name = indigo.devices.getName(123456) + +# Works for folders too +folder_name = indigo.variables.folders.getName(folder_id) +``` + +### iterkeys() Method + +Iterate over just the IDs (more efficient when you only need IDs): + +```python +for dev_id in indigo.devices.iterkeys(): + # process just the ID without loading the full object + pass +``` + +## self vs indigo.activePlugin + +In plugin.py methods, `self` refers to the Plugin instance: + +```python +class Plugin(indigo.PluginBase): + def startup(self): + self.logger.info("Starting") # self is the Plugin +``` + +Outside plugin methods, use `indigo.activePlugin`: + +```python +# In a script or embedded Python +indigo.activePlugin.logger.info("Message") +``` + +## Common Exceptions + +| Exception | Cause | +|-----------|-------| +| `ArgumentError` | Missing or incorrect parameter type | +| `IndexError` | Index out of range | +| `KeyError` | Dictionary key not found | +| `PluginNotFoundError` | Target plugin is disabled or missing | +| `TypeError` | Incorrect runtime type | +| `ValueError` | Illegal or disallowed value | + +## Python Introspection + +Use standard Python introspection to explore objects: + +```python +dev = indigo.devices[123456] + +# Get class type +dev.__class__ # + +# List all attributes and methods +dir(dev) + +# Check for attribute +hasattr(dev, 'brightness') # True for dimmers + +# Get plugin device type +dev.deviceTypeId # 'myDeviceType' +``` + +## Object to Dictionary Conversion + +Convert any Indigo object to a Python dict for serialization: + +```python +dev = indigo.devices[123456] +dev_dict = dict(dev) # Standard Python dict with all properties +``` diff --git a/docs/api/iom/command-namespaces.md b/docs/api/iom/command-namespaces.md new file mode 100644 index 0000000..ca69556 --- /dev/null +++ b/docs/api/iom/command-namespaces.md @@ -0,0 +1,219 @@ +# Command Namespaces Reference + +Commands are organized into namespaces matching their target class type. + +## Device Commands + +| Class | Namespace | Description | +|-------|-----------|-------------| +| `indigo.Device` | `indigo.device.*` | All device subclasses | +| `indigo.DimmerDevice` | `indigo.dimmer.*` | Dimmer devices | +| `indigo.RelayDevice` | `indigo.relay.*` | Relay, lock, 2-state devices | +| `indigo.SensorDevice` | `indigo.sensor.*` | Sensor devices | +| `indigo.SpeedControlDevice` | `indigo.speedcontrol.*` | Speed control/motor devices | +| `indigo.SprinklerDevice` | `indigo.sprinkler.*` | Sprinkler devices | +| `indigo.ThermostatDevice` | `indigo.thermostat.*` | Thermostat devices | +| `indigo.MultiIODevice` | `indigo.iodevice.*` | Input/output devices | + +### Common Device Commands + +```python +# Create device (factory method for all types) +indigo.device.create(protocol, deviceTypeId=..., props=...) + +# Duplicate device +indigo.device.duplicate(dev_or_id, duplicateName="Copy") + +# Delete device +indigo.device.delete(dev_or_id) + +# Move to folder +indigo.device.moveToFolder(dev_or_id, folder_id) + +# Enable/disable +indigo.device.enable(dev_or_id, value=True) + +# Display in remote UI +indigo.device.displayInRemoteUI(dev_or_id, value=True) +``` + +### Dimmer Commands + +```python +indigo.dimmer.turnOn(dev) +indigo.dimmer.turnOff(dev) +indigo.dimmer.toggle(dev) +indigo.dimmer.setBrightness(dev, value=75) +indigo.dimmer.brightenBy(dev, value=10) +indigo.dimmer.dimBy(dev, value=10) +indigo.dimmer.statusRequest(dev) +``` + +### Relay Commands + +```python +indigo.relay.turnOn(dev) +indigo.relay.turnOff(dev) +indigo.relay.toggle(dev) +indigo.relay.statusRequest(dev) +``` + +### Sensor Commands + +```python +indigo.sensor.turnOn(dev) # For virtual sensors +indigo.sensor.turnOff(dev) +indigo.sensor.statusRequest(dev) +``` + +### Thermostat Commands + +```python +indigo.thermostat.setHeatSetpoint(dev, value=68) +indigo.thermostat.setCoolSetpoint(dev, value=76) +indigo.thermostat.setHvacMode(dev, value=indigo.kHvacMode.Auto) +indigo.thermostat.setFanMode(dev, value=indigo.kFanMode.Auto) +indigo.thermostat.statusRequest(dev) +``` + +### Sprinkler Commands + +```python +indigo.sprinkler.setActiveZone(dev, index=1) +indigo.sprinkler.run(dev) +indigo.sprinkler.stop(dev) +indigo.sprinkler.pause(dev) +indigo.sprinkler.resume(dev) +indigo.sprinkler.previousZone(dev) +indigo.sprinkler.nextZone(dev) +``` + +### Speed Control Commands + +```python +indigo.speedcontrol.turnOn(dev) +indigo.speedcontrol.turnOff(dev) +indigo.speedcontrol.toggle(dev) +indigo.speedcontrol.setSpeedLevel(dev, value=50) +indigo.speedcontrol.setSpeedIndex(dev, value=2) +indigo.speedcontrol.increaseSpeedIndex(dev) +indigo.speedcontrol.decreaseSpeedIndex(dev) +indigo.speedcontrol.statusRequest(dev) +``` + +## Trigger Commands + +| Class | Namespace | +|-------|-----------| +| `indigo.Trigger` | `indigo.trigger.*` | +| `indigo.DeviceStateChangeTrigger` | `indigo.devStateChange.*` | +| `indigo.VariableValueChangeTrigger` | `indigo.varValueChange.*` | +| `indigo.EmailReceivedTrigger` | `indigo.emailRcvd.*` | +| `indigo.InsteonCommandReceivedTrigger` | `indigo.insteonCmdRcvd.*` | +| `indigo.X10CommandReceivedTrigger` | `indigo.x10CmdRcvd.*` | +| `indigo.ServerStartupTrigger` | `indigo.serverStartup.*` | +| `indigo.PowerFailureTrigger` | `indigo.powerFail.*` | +| `indigo.InterfaceFailureTrigger` | `indigo.interfaceFail.*` | +| `indigo.InterfaceInitializedTrigger` | `indigo.interfaceInit.*` | +| `indigo.PluginEventTrigger` | `indigo.pluginEvent.*` | + +```python +# Common trigger commands +indigo.trigger.enable(trigger_or_id, value=True) +indigo.trigger.execute(trigger_or_id) +indigo.trigger.delete(trigger_or_id) +indigo.trigger.moveToFolder(trigger_or_id, folder_id) +``` + +## Variable Commands + +| Class | Namespace | +|-------|-----------| +| `indigo.Variable` | `indigo.variable.*` | + +```python +# Create variable +indigo.variable.create("VarName", value="initial", folder=folder_id) + +# Update value +indigo.variable.updateValue(var_or_id, value="new value") + +# Delete +indigo.variable.delete(var_or_id) + +# Move to folder +indigo.variable.moveToFolder(var_or_id, folder_id) +``` + +## Action Group Commands + +| Class | Namespace | +|-------|-----------| +| `indigo.ActionGroup` | `indigo.actionGroup.*` | + +```python +# Execute action group +indigo.actionGroup.execute(ag_or_id) + +# Enable/disable +indigo.actionGroup.enable(ag_or_id, value=True) + +# Delete +indigo.actionGroup.delete(ag_or_id) +``` + +## Schedule Commands + +| Class | Namespace | +|-------|-----------| +| `indigo.Schedule` | `indigo.schedule.*` | + +```python +# Enable/disable +indigo.schedule.enable(sched_or_id, value=True) + +# Delete +indigo.schedule.delete(sched_or_id) +``` + +## Protocol-Specific Commands + +### INSTEON + +```python +indigo.insteon.sendRawInsteon(address, cmd, cmd2=0, waitUntilAck=True) +indigo.insteon.sendRawExtended(address, cmd, cmd2, data) +indigo.insteon.subscribeToIncoming() # Low-level monitoring +indigo.insteon.subscribeToOutgoing() +``` + +### X10 + +```python +indigo.x10.sendRawX10(address, cmd) +indigo.x10.subscribeToIncoming() +indigo.x10.subscribeToOutgoing() +``` + +## Server Commands + +```python +indigo.server.log("message") +indigo.server.error("error message") +indigo.server.speak("text to speak", waitUntilDone=False) +indigo.server.getTime() +indigo.server.apiVersion # Property, e.g., "3.6" +indigo.server.version # Property, e.g., "2025.1" +``` + +## Common Command Patterns + +All namespaces support these methods for their object type: + +| Method | Description | +|--------|-------------| +| `create()` | Create new object | +| `duplicate()` | Duplicate existing object | +| `delete()` | Delete object | +| `enable()` | Enable/disable object | +| `moveToFolder()` | Move to folder | diff --git a/docs/api/iom/constants.md b/docs/api/iom/constants.md new file mode 100644 index 0000000..357727d --- /dev/null +++ b/docs/api/iom/constants.md @@ -0,0 +1,196 @@ +# Constants Reference + +## State Image Icons + +Used with `dev.updateStateImageOnServer()`: + +```python +dev.updateStateImageOnServer(indigo.kStateImageSel.SensorOn) +``` + +| Constant | Use Case | +|----------|----------| +| `indigo.kStateImageSel.Auto` | Automatic selection | +| `indigo.kStateImageSel.NoImage` | No icon | +| `indigo.kStateImageSel.Error` | Error state | + +### Dimmer/Relay Icons + +| Constant | +|----------| +| `indigo.kStateImageSel.DimmerOff` | +| `indigo.kStateImageSel.DimmerOn` | +| `indigo.kStateImageSel.PowerOff` | +| `indigo.kStateImageSel.PowerOn` | + +### Sensor Icons + +| Constant | +|----------| +| `indigo.kStateImageSel.SensorOff` | +| `indigo.kStateImageSel.SensorOn` | +| `indigo.kStateImageSel.SensorTripped` | +| `indigo.kStateImageSel.MotionSensor` | +| `indigo.kStateImageSel.MotionSensorTripped` | +| `indigo.kStateImageSel.LightSensor` | +| `indigo.kStateImageSel.LightSensorOn` | + +### HVAC Icons + +| Constant | +|----------| +| `indigo.kStateImageSel.HvacOff` | +| `indigo.kStateImageSel.HvacHeatMode` | +| `indigo.kStateImageSel.HvacCoolMode` | +| `indigo.kStateImageSel.HvacAutoMode` | +| `indigo.kStateImageSel.HvacFanMode` | + +### Fan Icons + +| Constant | +|----------| +| `indigo.kStateImageSel.FanOff` | +| `indigo.kStateImageSel.FanLow` | +| `indigo.kStateImageSel.FanMedium` | +| `indigo.kStateImageSel.FanHigh` | + +### Media Icons + +| Constant | +|----------| +| `indigo.kStateImageSel.AvPaused` | +| `indigo.kStateImageSel.AvPlaying` | +| `indigo.kStateImageSel.AvStopped` | + +### Lock Icons + +| Constant | +|----------| +| `indigo.kStateImageSel.Locked` | +| `indigo.kStateImageSel.Unlocked` | + +### Sprinkler Icons + +| Constant | +|----------| +| `indigo.kStateImageSel.SprinklerOff` | +| `indigo.kStateImageSel.SprinklerOn` | + +### Timer Icons + +| Constant | +|----------| +| `indigo.kStateImageSel.TimerOff` | +| `indigo.kStateImageSel.TimerOn` | + +## Device Action Constants + +### Relay/Dimmer Actions + +```python +indigo.kDeviceAction.TurnOn +indigo.kDeviceAction.TurnOff +indigo.kDeviceAction.Toggle +indigo.kDeviceAction.SetBrightness +indigo.kDeviceAction.BrightenBy +indigo.kDeviceAction.DimBy +indigo.kDeviceAction.RequestStatus +``` + +### Thermostat Actions + +```python +indigo.kThermostatAction.SetHeatSetpoint +indigo.kThermostatAction.SetCoolSetpoint +indigo.kThermostatAction.SetHvacMode +indigo.kThermostatAction.SetFanMode +indigo.kThermostatAction.RequestStatusAll +``` + +### Sensor Actions + +```python +indigo.kSensorAction.TurnOn +indigo.kSensorAction.TurnOff +indigo.kSensorAction.RequestStatus +``` + +### Sprinkler Actions + +```python +indigo.kSprinklerAction.ZoneOn +indigo.kSprinklerAction.ZoneOff +indigo.kSprinklerAction.AllZonesOff +indigo.kSprinklerAction.RunNewSchedule +indigo.kSprinklerAction.RunPreviousSchedule +indigo.kSprinklerAction.PauseSchedule +indigo.kSprinklerAction.ResumeSchedule +indigo.kSprinklerAction.StopSchedule +``` + +### Speed Control Actions + +```python +indigo.kSpeedControlAction.TurnOn +indigo.kSpeedControlAction.TurnOff +indigo.kSpeedControlAction.Toggle +indigo.kSpeedControlAction.SetSpeedLevel +indigo.kSpeedControlAction.SetSpeedIndex +indigo.kSpeedControlAction.IncreaseSpeedIndex +indigo.kSpeedControlAction.DecreaseSpeedIndex +``` + +## Protocol Constants + +```python +indigo.kProtocol.Plugin # Plugin-defined device +indigo.kProtocol.Insteon # INSTEON protocol +indigo.kProtocol.X10 # X10 protocol +indigo.kProtocol.ZWave # Z-Wave protocol +``` + +## HVAC Mode Constants + +```python +indigo.kHvacMode.Off +indigo.kHvacMode.Heat +indigo.kHvacMode.Cool +indigo.kHvacMode.HeatCool # Auto mode +indigo.kHvacMode.ProgramHeat +indigo.kHvacMode.ProgramCool +indigo.kHvacMode.ProgramHeatCool +``` + +## Fan Mode Constants + +```python +indigo.kFanMode.Auto +indigo.kFanMode.AlwaysOn +``` + +## State Value Types + +Used in `Devices.xml` state definitions: + +| Type | Description | +|------|-------------| +| `Integer` | Whole numbers | +| `Number` | Floating point | +| `String` | Text values | +| `Boolean` | True/False | +| `Separator` | UI separator (no value) | + +## UI Value Types + +Used in `ConfigUI` field definitions: + +| Type | Description | +|------|-------------| +| `textfield` | Single-line text input | +| `textarea` | Multi-line text input | +| `checkbox` | Boolean checkbox | +| `menu` | Dropdown menu | +| `list` | Selection list | +| `button` | Action button | +| `label` | Static text | +| `separator` | Visual separator | diff --git a/docs/api/iom/containers.md b/docs/api/iom/containers.md new file mode 100644 index 0000000..9ee8aaf --- /dev/null +++ b/docs/api/iom/containers.md @@ -0,0 +1,162 @@ +# indigo.Dict and indigo.List + +Indigo provides special container classes that integrate with the Indigo database and preference system. + +## Key Differences from Python Containers + +| Behavior | `indigo.Dict`/`indigo.List` | Python `dict`/`list` | +|----------|----------------------------|----------------------| +| Value retrieval | Returns **copy** | Returns **reference** | +| Nested modification | Must reassign parent | Modifies in place | +| Database integration | Native | Requires conversion | +| Key restrictions | ASCII only, no spaces | Any hashable | + +## indigo.Dict + +### Basic Usage + +```python +d = indigo.Dict() +d['key'] = "value" +d.key = "value" # Dot notation also works +value = d['key'] +value = d.key +value = d.get('key', 'default') + +if 'key' in d: + pass +``` + +### Key Restrictions + +Keys must: +- Contain only letters, numbers, and ASCII characters +- NOT contain spaces +- NOT start with a number or punctuation +- NOT start with "xml", "XML", or "Xml" + +### Value Type Restrictions + +Values must be: `bool`, `float`, `int`, `string`, `list`, or `dict` + +Nested containers must recursively contain only compatible values. + +## indigo.List + +```python +c = indigo.List() +c.append(4) +c.append("string") +c.append(True) + +for item in c: + print(item) +``` + +## Critical: Copy Semantics + +**Values are always retrieved as copies, not references.** + +### The Gotcha + +```python +c = indigo.List() +c.append(4) +c.append(5) + +a = indigo.Dict() +a['c'] = c # COPY of c is inserted + +# This does NOT modify a['c']: +c.append(6) # Only modifies local c +print(a['c']) # Still [4, 5] + +# This also does NOT work: +a['c'].append(6) # Gets copy, appends to copy, copy discarded +print(a['c']) # Still [4, 5] +``` + +### The Solution: Reassign + +```python +# Must reassign to parent container: +c.append(6) +a['c'] = c # Now a['c'] has [4, 5, 6] +``` + +## Efficient Nested Access + +Avoid creating temporary copies with helper methods: + +### setitem_in_item() + +```python +# Slow - creates temporary copy of a['c']: +temp = a['c'] +temp[4] = "value" +a['c'] = temp + +# Fast - no temporary copy: +a.setitem_in_item('c', 4, "value") +``` + +### getitem_in_item() + +```python +# Slow - creates temporary copy: +value = a['c'][4] + +# Fast - no temporary copy: +value = a.getitem_in_item('c', 4) +``` + +## Converting to Native Python + +### to_dict() / to_list() + +Recursively converts nested structures: + +```python +python_dict = my_indigo_dict.to_dict() +python_list = my_indigo_list.to_list() +``` + +### Native Conversion (Indigo 2021.2+) + +```python +python_dict = dict(my_indigo_dict) +python_list = list(my_indigo_list) +``` + +## Common Use Cases + +### Plugin Properties + +```python +# Read plugin props (returns indigo.Dict) +props = dev.pluginProps + +# Modify and save +props['setting'] = "new value" +dev.replacePluginPropsOnServer(props) +``` + +### Device States + +```python +# Access states (returns indigo.Dict) +states = dev.states +current_value = states['myState'] +``` + +### Config UI Values + +```python +def validateDeviceConfigUi(self, valuesDict, typeId, devId): + # valuesDict is an indigo.Dict + if not valuesDict.get('requiredField'): + errorsDict = indigo.Dict() + errorsDict['requiredField'] = "This field is required" + return (False, valuesDict, errorsDict) + return (True, valuesDict) +``` diff --git a/docs/api/iom/devices.md b/docs/api/iom/devices.md new file mode 100644 index 0000000..c78be01 --- /dev/null +++ b/docs/api/iom/devices.md @@ -0,0 +1,224 @@ +# Device Classes Reference + +## Device Class Hierarchy + +``` +indigo.Device (base) +├── indigo.DimmerDevice +├── indigo.RelayDevice +├── indigo.SensorDevice +├── indigo.ThermostatDevice +├── indigo.SprinklerDevice +├── indigo.SpeedControlDevice +└── indigo.MultiIODevice +``` + +## Common Device Properties + +All device types share these properties: + +```python +dev.id # Unique ID (int) +dev.name # Device name (str) +dev.enabled # Is enabled (bool) +dev.configured # Is configured (bool) +dev.description # Description (str) +dev.model # Model name (str) +dev.address # Device address (str) +dev.version # Firmware version (str) +dev.subModel # Sub-model identifier (str) +dev.protocol # Protocol (indigo.kProtocol.*) +dev.deviceTypeId # Plugin device type ID (str) +dev.pluginId # Plugin bundle ID (str) +dev.folderId # Parent folder ID (int) +dev.lastChanged # Last state change (datetime) +dev.batteryLevel # Battery level if applicable (int or None) +dev.buttonGroupCount # Number of button groups (int) + +# Plugin properties +dev.pluginProps # indigo.Dict of user config +dev.globalProps # indigo.Dict of global properties +dev.ownerProps # Read-only plugin properties + +# States +dev.states # indigo.Dict of all states +dev.displayStateId # Primary display state ID +dev.displayStateValue # Primary display state value +dev.displayStateImageSel # Current state icon + +# Remote UI +dev.remoteDisplay # Text for remote display + +# Capabilities +dev.supportsStatusRequest # Can request status +dev.supportsAllOff # Supports all-off +dev.supportsAllLightsOnOff # Supports all-lights commands +``` + +## DimmerDevice + +Dimmable lighting devices. + +```python +dev.onState # Is on (bool) +dev.brightness # Brightness 0-100 (int) +dev.supportsRGB # Supports color (bool) +dev.supportsWhite # Supports white level (bool) +dev.supportsWhiteTemperature # Supports color temp (bool) +``` + +### Color Properties (if supported) + +```python +dev.redLevel # Red 0-100 +dev.greenLevel # Green 0-100 +dev.blueLevel # Blue 0-100 +dev.whiteLevel # White 0-100 +dev.whiteTemperature # Color temp in Kelvin +``` + +## RelayDevice + +On/off devices (switches, locks, outlets). + +```python +dev.onState # Is on (bool) +``` + +## SensorDevice + +Sensors (motion, door/window, temperature). + +```python +dev.onState # Sensor triggered state (bool) +dev.sensorValue # Numeric sensor value (float) + +# For binary sensors +dev.onOffState # "on" or "off" string +``` + +## ThermostatDevice + +HVAC thermostats. + +```python +dev.hvacMode # Current mode (indigo.kHvacMode.*) +dev.fanMode # Fan mode (indigo.kFanMode.*) +dev.coolSetpoint # Cooling setpoint (float) +dev.heatSetpoint # Heating setpoint (float) +dev.coolIsOn # Cooling active (bool) +dev.heatIsOn # Heating active (bool) +dev.fanIsOn # Fan active (bool) + +# Temperature sensors +dev.temperatureSensorCount # Number of sensors +dev.temperatures # List of temperatures + +# Humidity sensors +dev.humiditySensorCount # Number of humidity sensors +dev.humidities # List of humidity values +``` + +## SprinklerDevice + +Irrigation controllers. + +```python +dev.zoneCount # Number of zones +dev.activeZone # Currently active zone (0 = none) +dev.zoneNames # List of zone names +dev.zoneEnableList # List of enabled zones +dev.zoneDurations # List of zone durations +dev.zoneScheduledDurations # Scheduled durations +``` + +## SpeedControlDevice + +Variable speed devices (fans, motors). + +```python +dev.onState # Is on (bool) +dev.speedLevel # Speed 0-100 (int) +dev.speedIndex # Discrete speed index (int) +dev.speedIndexCount # Number of speed levels +dev.speedLabels # List of speed labels +``` + +## MultiIODevice + +Input/output devices. + +```python +dev.binaryInputs # List of binary input states +dev.binaryOutputs # List of binary output states +dev.analogInputs # List of analog input values +dev.sensorInputs # List of sensor values +``` + +## Device Methods + +### State Updates + +```python +# Update single state +dev.updateStateOnServer('stateName', value=new_value) +dev.updateStateOnServer('stateName', value=new_value, uiValue="Display Text") + +# Update multiple states (more efficient) +dev.updateStatesOnServer([ + {'key': 'state1', 'value': value1}, + {'key': 'state2', 'value': value2, 'uiValue': 'Display'} +]) +``` + +### State Image + +```python +dev.updateStateImageOnServer(indigo.kStateImageSel.SensorOn) +``` + +### Error State + +```python +dev.setErrorStateOnServer("Connection failed") +dev.setErrorStateOnServer(None) # Clear error +``` + +### Refresh Device Definition + +```python +# Call if Devices.xml changed dynamically +dev.stateListOrDisplayStateIdChanged() +``` + +### Server Synchronization + +```python +# Get fresh copy from server +dev.refreshFromServer() + +# Push local changes to server +dev.replaceOnServer() + +# Update plugin props +dev.replacePluginPropsOnServer(new_props_dict) +``` + +## HVAC Mode Constants + +```python +indigo.kHvacMode.Off +indigo.kHvacMode.Heat +indigo.kHvacMode.Cool +indigo.kHvacMode.HeatCool # Auto +indigo.kHvacMode.ProgramHeat +indigo.kHvacMode.ProgramCool +indigo.kHvacMode.ProgramHeatCool +``` + +## Fan Mode Constants + +```python +indigo.kFanMode.Auto +indigo.kFanMode.AlwaysOn +``` diff --git a/docs/api/iom/filters.md b/docs/api/iom/filters.md new file mode 100644 index 0000000..7bf658a --- /dev/null +++ b/docs/api/iom/filters.md @@ -0,0 +1,149 @@ +# Iteration Filters Reference + +Use filters with `.iter()` to efficiently iterate subsets of objects. + +## Device Filters + +```python +for dev in indigo.devices.iter("filter"): + pass +``` + +### Protocol Filters + +| Filter | Description | +|--------|-------------| +| `indigo.insteon` | INSTEON devices | +| `indigo.zwave` | Z-Wave devices | +| `indigo.x10` | X10 devices | + +### Device Type Filters + +| Filter | Description | +|--------|-------------| +| `indigo.dimmer` | Dimmer devices | +| `indigo.relay` | Relay/switch/lock devices | +| `indigo.sensor` | Sensor devices | +| `indigo.thermostat` | Thermostat devices | +| `indigo.sprinkler` | Sprinkler devices | +| `indigo.speedcontrol` | Speed control devices | +| `indigo.iodevice` | Input/output devices | + +### Capability Filters + +| Filter | Description | +|--------|-------------| +| `indigo.responder` | Devices whose state can be changed | +| `indigo.controller` | Devices that can send commands | + +### Plugin Filters + +| Filter | Description | +|--------|-------------| +| `self` | Devices defined by calling plugin | +| `self.myDeviceType` | Specific device type from calling plugin | +| `com.company.plugin` | All devices from specific plugin | +| `com.company.plugin.deviceType` | Specific device type from plugin | + +### Property Filters + +| Filter | Description | +|--------|-------------| +| `props.SupportsOnState` | Devices supporting ON state | + +### Combining Filters + +Combine filters with commas: + +```python +# INSTEON dimmers only +for dev in indigo.devices.iter("indigo.dimmer, indigo.insteon"): + print(dev.name) + +# Z-Wave sensors only +for dev in indigo.devices.iter("indigo.sensor, indigo.zwave"): + print(dev.name) +``` + +## Trigger Filters + +```python +for trigger in indigo.triggers.iter("filter"): + pass +``` + +| Filter | Description | +|--------|-------------| +| `indigo.devStateChange` | Device state change triggers | +| `indigo.varValueChange` | Variable value change triggers | +| `indigo.emailRcvd` | Email received triggers | +| `indigo.insteonCmdRcvd` | INSTEON command received | +| `indigo.x10CmdRcvd` | X10 command received | +| `indigo.serverStartup` | Server startup triggers | +| `indigo.powerFail` | Power failure triggers | +| `indigo.interfaceFail` | Interface failure triggers | +| `indigo.interfaceInit` | Interface initialized triggers | +| `indigo.pluginEvent` | Plugin-defined event triggers | +| `self` | Triggers from calling plugin | +| `self.myTriggerType` | Specific trigger type from plugin | +| `com.company.plugin.triggerType` | Trigger type from other plugin | + +**Note**: Unlike devices, only a single trigger filter can be used. + +## Variable Filters + +```python +for var in indigo.variables.iter("filter"): + pass +``` + +| Filter | Description | +|--------|-------------| +| `indigo.readWrite` | Read/write variables only | + +## Examples + +### All Plugin Devices + +```python +for dev in indigo.devices.iter("self"): + self.logger.info(f"My device: {dev.name}") +``` + +### Specific Plugin Device Type + +```python +for dev in indigo.devices.iter("self.myThermostat"): + self.logger.info(f"My thermostat: {dev.name}") +``` + +### All Dimmers at Full Brightness + +```python +for dev in indigo.devices.iter("indigo.dimmer"): + if dev.brightness == 100: + self.logger.info(f"Full bright: {dev.name}") +``` + +### Device State Change Triggers + +```python +for trigger in indigo.triggers.iter("indigo.devStateChange"): + self.logger.info(f"Monitoring device {trigger.deviceId}") +``` + +### Iterate IDs Only (More Efficient) + +```python +# When you only need IDs, not full objects +for dev_id in indigo.devices.iterkeys(): + # Process without loading full device + pass +``` + +## Iteration Behavior + +When iteration begins, the list is fixed: +- Items added during iteration are NOT included +- Items deleted during iteration are gracefully skipped +- No exceptions are thrown for mid-iteration changes diff --git a/docs/api/iom/subscriptions.md b/docs/api/iom/subscriptions.md new file mode 100644 index 0000000..4391530 --- /dev/null +++ b/docs/api/iom/subscriptions.md @@ -0,0 +1,276 @@ +# Subscriptions and Event Callbacks + +Subscribe to object changes to receive real-time notifications. + +## Object Change Subscriptions + +### Available Subscriptions + +| Collection | Method | +|------------|--------| +| `indigo.devices` | `subscribeToChanges()` | +| `indigo.variables` | `subscribeToChanges()` | +| `indigo.triggers` | `subscribeToChanges()` | +| `indigo.schedules` | `subscribeToChanges()` | +| `indigo.actionGroups` | `subscribeToChanges()` | +| `indigo.controlPages` | `subscribeToChanges()` | + +### Subscribing + +Call in `__init__()` or `startup()`: + +```python +def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs): + super().__init__(pluginId, pluginDisplayName, pluginVersion, pluginPrefs) + + indigo.devices.subscribeToChanges() + indigo.variables.subscribeToChanges() + indigo.triggers.subscribeToChanges() + indigo.actionGroups.subscribeToChanges() +``` + +**Note**: Use sparingly - subscriptions generate significant traffic between IndigoServer and your plugin. + +## Device Callbacks + +```python +def deviceCreated(self, dev): + """Called when any device is created.""" + self.logger.debug(f"{dev.name} created") + +def deviceUpdated(self, origDev, newDev): + """Called when device state or properties change.""" + # Always call super first + indigo.PluginBase.deviceUpdated(self, origDev, newDev) + + # Check what changed + if origDev.onState != newDev.onState: + self.logger.info(f"{newDev.name} turned {'on' if newDev.onState else 'off'}") + +def deviceDeleted(self, dev): + """Called when any device is deleted.""" + self.logger.debug(f"{dev.name} deleted") +``` + +### Finding What Changed + +```python +def deviceUpdated(self, origDev, newDev): + indigo.PluginBase.deviceUpdated(self, origDev, newDev) + + # Convert to dicts for comparison + orig_dict = dict(origDev) + new_dict = dict(newDev) + + # Find changed attributes + diff = {k: new_dict[k] for k in orig_dict + if k in new_dict and orig_dict[k] != new_dict[k]} + + if diff: + self.logger.debug(f"Changed: {diff}") +``` + +## Variable Callbacks + +```python +def variableCreated(self, var): + """Called when any variable is created.""" + self.logger.debug(f"Variable created: {var.name}") + +def variableUpdated(self, origVar, newVar): + """Called when variable value changes.""" + indigo.PluginBase.variableUpdated(self, origVar, newVar) + + if origVar.value != newVar.value: + self.logger.info(f"{newVar.name}: {origVar.value} -> {newVar.value}") + +def variableDeleted(self, var): + """Called when any variable is deleted.""" + self.logger.debug(f"Variable deleted: {var.name}") +``` + +## Trigger Callbacks + +```python +def triggerCreated(self, trigger): + self.logger.debug(f"Trigger created: {trigger.name}") + +def triggerUpdated(self, origTrigger, newTrigger): + indigo.PluginBase.triggerUpdated(self, origTrigger, newTrigger) + +def triggerDeleted(self, trigger): + self.logger.debug(f"Trigger deleted: {trigger.name}") +``` + +## Schedule Callbacks + +```python +def scheduleCreated(self, schedule): + self.logger.debug(f"Schedule created: {schedule.name}") + +def scheduleUpdated(self, origSchedule, newSchedule): + indigo.PluginBase.scheduleUpdated(self, origSchedule, newSchedule) + +def scheduleDeleted(self, schedule): + self.logger.debug(f"Schedule deleted: {schedule.name}") +``` + +## Action Group Callbacks + +```python +def actionGroupCreated(self, actionGroup): + self.logger.debug(f"Action group created: {actionGroup.name}") + +def actionGroupUpdated(self, origActionGroup, newActionGroup): + indigo.PluginBase.actionGroupUpdated(self, origActionGroup, newActionGroup) + +def actionGroupDeleted(self, actionGroup): + self.logger.debug(f"Action group deleted: {actionGroup.name}") +``` + +## Control Page Callbacks + +```python +def controlPageCreated(self, controlPage): + self.logger.debug(f"Control page created: {controlPage.name}") + +def controlPageUpdated(self, origControlPage, newControlPage): + indigo.PluginBase.controlPageUpdated(self, origControlPage, newControlPage) + +def controlPageDeleted(self, controlPage): + self.logger.debug(f"Control page deleted: {controlPage.name}") +``` + +## Low-Level Protocol Subscriptions + +Monitor raw INSTEON or X10 commands (regardless of effect on device state). + +### INSTEON + +```python +def startup(self): + indigo.insteon.subscribeToIncoming() + indigo.insteon.subscribeToOutgoing() + +def insteonCommandReceived(self, cmd): + """Called for incoming INSTEON commands.""" + self.logger.debug(f"INSTEON received: {cmd}") + +def insteonCommandSent(self, cmd): + """Called for outgoing INSTEON commands.""" + self.logger.debug(f"INSTEON sent: {cmd}") +``` + +### X10 + +```python +def startup(self): + indigo.x10.subscribeToIncoming() + indigo.x10.subscribeToOutgoing() + +def x10CommandReceived(self, cmd): + """Called for incoming X10 commands.""" + self.logger.debug(f"X10 received: {cmd}") + + if cmd.cmdType == "sec": # Security command + if cmd.secCodeId == 6: + if cmd.secFunc == "sensor alert (max delay)": + self.logger.info("SENSOR OPEN") + elif cmd.secFunc == "sensor normal (max delay)": + self.logger.info("SENSOR CLOSED") + +def x10CommandSent(self, cmd): + """Called for outgoing X10 commands.""" + self.logger.debug(f"X10 sent: {cmd}") +``` + +## Best Practices + +### Use Sparingly + +Subscriptions create significant traffic. Good use cases: + +- Logging plugins (SQL Logger) +- Scene management (track device states) +- Integration bridges (sync with external systems) +- Fan controllers (monitor related devices) + +### Filter in Callbacks + +Check if the change is relevant before processing: + +```python +def deviceUpdated(self, origDev, newDev): + indigo.PluginBase.deviceUpdated(self, origDev, newDev) + + # Only process our plugin's devices + if newDev.pluginId != self.pluginId: + return + + # Only process if state actually changed + if origDev.states == newDev.states: + return + + # Now do work + self.processDeviceChange(newDev) +``` + +### Subscription Scope + +Note that `subscribeToChanges()` reports **actual changes only**: + +- Light turns ON → notification +- Light commanded ON when already ON → no notification (no change) + +### Plugin Device Subscriptions + +For your own plugin's devices, prefer the device lifecycle methods: + +```python +def deviceStartComm(self, dev): + """Called when your device starts.""" + pass + +def deviceStopComm(self, dev): + """Called when your device stops.""" + pass +``` + +## Example: Multi-Device Sync + +Sync multiple fans to act as one: + +```python +def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + indigo.devices.subscribeToChanges() + self.syncing = False # Prevent loops + +def deviceUpdated(self, origDev, newDev): + indigo.PluginBase.deviceUpdated(self, origDev, newDev) + + # Avoid recursive updates + if self.syncing: + return + + # Only handle our grouped fans + if newDev.pluginId != self.pluginId: + return + if newDev.deviceTypeId != "groupedFan": + return + + # Check if speed changed + if origDev.speedLevel == newDev.speedLevel: + return + + # Sync all fans in group + self.syncing = True + try: + group_id = newDev.pluginProps.get("groupId") + for dev in indigo.devices.iter("self.groupedFan"): + if dev.id != newDev.id: + if dev.pluginProps.get("groupId") == group_id: + indigo.speedcontrol.setSpeedLevel(dev, value=newDev.speedLevel) + finally: + self.syncing = False +``` diff --git a/docs/api/iom/triggers.md b/docs/api/iom/triggers.md new file mode 100644 index 0000000..5fa23f8 --- /dev/null +++ b/docs/api/iom/triggers.md @@ -0,0 +1,173 @@ +# Trigger Classes Reference + +## Trigger Class Hierarchy + +``` +indigo.Trigger (base) +├── indigo.DeviceStateChangeTrigger +├── indigo.VariableValueChangeTrigger +├── indigo.EmailReceivedTrigger +├── indigo.InsteonCommandReceivedTrigger +├── indigo.X10CommandReceivedTrigger +├── indigo.ServerStartupTrigger +├── indigo.PowerFailureTrigger +├── indigo.InterfaceFailureTrigger +├── indigo.InterfaceInitializedTrigger +└── indigo.PluginEventTrigger +``` + +## Common Trigger Properties + +All trigger types share these properties: + +```python +trigger.id # Unique ID (int) +trigger.name # Trigger name (str) +trigger.enabled # Is enabled (bool) +trigger.folderId # Parent folder ID +trigger.pluginId # Plugin ID if plugin-defined +trigger.pluginTypeId # Plugin's event type ID +``` + +## Trigger Types + +### DeviceStateChangeTrigger + +Fires when a device state changes. + +```python +trigger = indigo.triggers[trigger_id] +trigger.deviceId # Monitored device ID +trigger.stateSelector # Which state to monitor +trigger.stateValue # Value to match (if applicable) +``` + +### VariableValueChangeTrigger + +Fires when a variable value changes. + +```python +trigger.variableId # Monitored variable ID +trigger.variableValue # Value to match (if applicable) +``` + +### PluginEventTrigger + +Fires when a plugin raises an event. + +```python +trigger.pluginId # Source plugin ID +trigger.pluginTypeId # Event type from Events.xml +trigger.pluginProps # Configuration properties +``` + +### EmailReceivedTrigger + +Fires when email is received matching criteria. + +### InsteonCommandReceivedTrigger + +Fires when an INSTEON command is received. + +```python +trigger.address # INSTEON address to monitor +trigger.command # Command type to match +``` + +### X10CommandReceivedTrigger + +Fires when an X10 command is received. + +### ServerStartupTrigger + +Fires when Indigo server starts. + +### PowerFailureTrigger + +Fires on power failure detection. + +### InterfaceFailureTrigger + +Fires when a hardware interface fails. + +### InterfaceInitializedTrigger + +Fires when a hardware interface initializes. + +## Plugin-Defined Triggers + +Plugins define custom trigger types in `Events.xml`: + +```xml + + + Motion Detected + + + + + + + +``` + +### Firing Plugin Events + +```python +# In plugin code, fire the event +indigo.trigger.execute(trigger) + +# Or trigger all matching plugin events +for trigger in indigo.triggers.iter("self.motionDetected"): + if trigger.pluginProps.get("zone") == detected_zone: + indigo.trigger.execute(trigger) +``` + +### triggerStartProcessing / triggerStopProcessing + +```python +def triggerStartProcessing(self, trigger): + """Called when trigger is enabled.""" + self.logger.debug(f"Trigger started: {trigger.name}") + +def triggerStopProcessing(self, trigger): + """Called when trigger is disabled.""" + self.logger.debug(f"Trigger stopped: {trigger.name}") +``` + +## Iterating Triggers + +```python +# All triggers +for trigger in indigo.triggers: + print(trigger.name) + +# By type +for trigger in indigo.triggers.iter("indigo.devStateChange"): + print(f"Device trigger: {trigger.name}") + +# Plugin's own triggers +for trigger in indigo.triggers.iter("self"): + print(f"My trigger: {trigger.name}") + +# Specific plugin trigger type +for trigger in indigo.triggers.iter("self.motionDetected"): + print(f"Motion trigger: {trigger.name}") +``` + +## Subscribing to Trigger Changes + +```python +# In startup() +indigo.triggers.subscribeToChanges() + +# Implement callbacks +def triggerCreated(self, trigger): + pass + +def triggerUpdated(self, origTrigger, newTrigger): + pass + +def triggerDeleted(self, trigger): + pass +``` diff --git a/docs/api/iom/utilities.md b/docs/api/iom/utilities.md new file mode 100644 index 0000000..57fb5ec --- /dev/null +++ b/docs/api/iom/utilities.md @@ -0,0 +1,175 @@ +# Utility Classes and Functions + +The `indigo.utils` module provides helper classes and functions. + +## Classes + +### FileNotFoundError + +Exception for file operations: + +```python +try: + # file operation + pass +except indigo.utils.FileNotFoundError: + self.logger.error("File not found") +``` + +### IndigoJSONEncoder + +JSON encoder that handles Python date/time objects: + +```python +import json + +dev = indigo.devices[123456] +dev_dict = dict(dev) + +# Properly encodes datetime objects +json_str = json.dumps(dev_dict, indent=4, cls=indigo.utils.IndigoJSONEncoder) +``` + +## Functions + +### return_static_file() + +Returns a file for the Indigo Web Server to serve: + +```python +indigo.utils.return_static_file("relative/path/to/file.html") + +indigo.utils.return_static_file( + "/absolute/path/to/file.json", + status=200, + path_is_relative=False, + content_type="application/json" +) +``` + +**Parameters:** + +| Parameter | Required | Type | Description | +|-----------|----------|------|-------------| +| `file_path` | Yes | str | Path to file | +| `status` | No | int | HTTP status code (default: 200) | +| `path_is_relative` | No | bool | Relative to plugin (default: True) | +| `content_type` | No | str | MIME type (auto-detected if omitted) | + +**Returns:** `indigo.Dict` for IWS to process: + +```python +{ + "status": 200, + "headers": {"Content-Type": "text/html"}, + "file_path": "/full/path/to/file.html" +} +``` + +**Raises:** `FileNotFoundError` or `TypeError` + +### validate_email_address() + +Validates email address format: + +```python +if indigo.utils.validate_email_address("user@example.com"): + # Valid format + pass +``` + +**Note:** Validates format only, not whether the address exists. + +### str_to_bool() + +Converts boolean-like strings to actual booleans: + +```python +indigo.utils.str_to_bool("yes") # True +indigo.utils.str_to_bool("no") # False +indigo.utils.str_to_bool("on") # True +indigo.utils.str_to_bool("off") # False +indigo.utils.str_to_bool("open") # True +indigo.utils.str_to_bool("closed") # False +``` + +**Recognized mappings:** + +| True | False | +|------|-------| +| y | n | +| yes | no | +| t | f | +| true | false | +| on | off | +| 1 | 0 | +| open | closed | +| locked | unlocked | + +**Raises:** `ValueError` if string cannot be converted. + +### reverse_bool_str_value() + +Returns the opposite boolean string: + +```python +indigo.utils.reverse_bool_str_value("open") # "closed" +indigo.utils.reverse_bool_str_value("yes") # "no" +indigo.utils.reverse_bool_str_value("locked") # "unlocked" +``` + +**Raises:** `ValueError` if string not recognized. + +## Common Use Cases + +### Serving Static Files from HTTP Responder + +```python +def handleWebRequest(self, path, params): + if path == "/status": + return indigo.utils.return_static_file("static/status.html") + elif path == "/data.json": + return indigo.utils.return_static_file("static/data.json") + else: + return indigo.utils.return_static_file( + "static/404.html", + status=404 + ) +``` + +### Validating Email in Config UI + +```python +def validatePrefsConfigUi(self, valuesDict): + errorsDict = indigo.Dict() + + email = valuesDict.get("notificationEmail", "") + if email and not indigo.utils.validate_email_address(email): + errorsDict["notificationEmail"] = "Invalid email format" + + if errorsDict: + return (False, valuesDict, errorsDict) + return (True, valuesDict) +``` + +### Converting User Input + +```python +def processUserSetting(self, value_str): + try: + enabled = indigo.utils.str_to_bool(value_str) + return enabled + except ValueError: + self.logger.warning(f"Unrecognized value: {value_str}") + return False +``` + +### Serializing Device State + +```python +import json + +def getDeviceStateAsJson(self, dev): + dev_dict = dict(dev) + return json.dumps(dev_dict, cls=indigo.utils.IndigoJSONEncoder) +``` diff --git a/docs/concepts/devices.md b/docs/concepts/devices.md index 948fe58..5d311f7 100644 --- a/docs/concepts/devices.md +++ b/docs/concepts/devices.md @@ -1,6 +1,6 @@ # Device Development -**Official Documentation**: https://www.indigodomo.com/docs/plugin_guide#devices +**Official Documentation**: https://wiki.indigodomo.com/doku.php?id=indigo_2025.1_documentation:plugin_guide#devices ## Device Types @@ -19,8 +19,6 @@ These inherit states and actions from Indigo: | `thermostat` | Full HVAC control | Climate control | Smart thermostats | | `sprinkler` | Zone control | Irrigation | Sprinkler systems | -**Example**: [../IndigoSDK-2025.1/Example Device - Relay and Dimmer.indigoPlugin](../IndigoSDK-2025.1/Example%20Device%20-%20Relay%20and%20Dimmer.indigoPlugin) - ### Custom Device Type Complete control over states and actions: @@ -37,8 +35,6 @@ Complete control over states and actions: ``` -**Example**: [../IndigoSDK-2025.1/Example Device - Custom.indigoPlugin](../IndigoSDK-2025.1/Example%20Device%20-%20Custom.indigoPlugin) - ## Device Definition (Devices.xml) ### Basic Structure @@ -80,20 +76,12 @@ States store device data and can trigger events: Status Message Status - - Separator - ``` -**Value Types**: -- `Integer` - Whole numbers -- `Number` - Floating point -- `String` - Text -- `Boolean` - True/False -- `Separator` - Visual separator in UI +**Value Types**: `Integer`, `Number`, `String`, `Boolean`, `Separator` -### Configuration UI +### Configuration UI (ConfigUI) ```xml @@ -116,134 +104,16 @@ States store device data and can trigger events: ``` -**Field Types**: -- `textfield` - Single line text -- `textarea` - Multi-line text -- `checkbox` - Boolean checkbox -- `menu` - Dropdown menu -- `list` - Selection list -- `button` - Action button -- `label` - Static text/instructions -- `separator` - Visual divider - -## Device Lifecycle Callbacks - -### Device Creation/Start - -```python -def deviceStartComm(self, dev): - """ - Called when device communication should start: - - When device is created - - When device is enabled - - When plugin starts and device is enabled - """ - self.logger.debug(f"Starting device: {dev.name}") - - # Refresh state list if Devices.xml changed - dev.stateListOrDisplayStateIdChanged() +**Field Types**: `textfield`, `textarea`, `checkbox`, `menu`, `list`, `button`, `label`, `separator` - # Initialize hardware connection - address = dev.pluginProps.get('address', '') - self.connect_to_device(address) +## Device Lifecycle - # Set initial state - dev.updateStateOnServer('status', value='initializing') -``` +Device lifecycle callbacks are documented in [Plugin Lifecycle → Device Callbacks](plugin-lifecycle.md#device-lifecycle-callbacks). -### Device Stop - -```python -def deviceStopComm(self, dev): - """ - Called when device communication should stop: - - When device is disabled - - When device is deleted - - When plugin is shutting down - """ - self.logger.debug(f"Stopping device: {dev.name}") - - # Clean up device-specific resources - self.disconnect_from_device(dev) - - # Clear states if desired - dev.updateStateOnServer('status', value='offline') -``` - -### Device Updates - -```python -def deviceCreated(self, dev): - """Called when a new device is created""" - self.logger.debug(f"Device created: {dev.name}") - -def deviceUpdated(self, orig_dev, new_dev): - """ - Called when device configuration changes - - :param orig_dev: Device before changes - :param new_dev: Device after changes - """ - self.logger.debug(f"Device updated: {new_dev.name}") - - # Check if critical config changed - if orig_dev.pluginProps['address'] != new_dev.pluginProps['address']: - # Reconnect to new address - self.reconnect_device(new_dev) - -def deviceDeleted(self, dev): - """Called when device is deleted""" - self.logger.debug(f"Device deleted: {dev.name}") - # Clean up any persistent data -``` - -## Updating Device States - -### Single State Update - -```python -dev.updateStateOnServer('temperature', value=72.5) -dev.updateStateOnServer('status', value='online') -dev.updateStateOnServer('isOnline', value=True) -``` - -### Multiple States (More Efficient) - -```python -key_value_list = [ - {'key': 'temperature', 'value': 72.5, 'decimalPlaces': 1}, - {'key': 'humidity', 'value': 45.2, 'decimalPlaces': 1}, - {'key': 'status', 'value': 'online'}, - {'key': 'lastUpdate', 'value': str(datetime.now())} -] -dev.updateStatesOnServer(key_value_list) -``` - -### UI Error States - -```python -# Set error state with message -dev.setErrorStateOnServer("Connection failed") - -# Clear error state -dev.setErrorStateOnServer(None) -``` - -### State Icons - -```python -# Common state icons -dev.updateStateImageOnServer(indigo.kStateImageSel.SensorOn) -dev.updateStateImageOnServer(indigo.kStateImageSel.SensorOff) -dev.updateStateImageOnServer(indigo.kStateImageSel.SensorTripped) -dev.updateStateImageOnServer(indigo.kStateImageSel.TimerOn) -dev.updateStateImageOnServer(indigo.kStateImageSel.PowerOn) -dev.updateStateImageOnServer(indigo.kStateImageSel.PowerOff) -dev.updateStateImageOnServer(indigo.kStateImageSel.Error) - -# No icon (Python 3) -dev.updateStateImageOnServer(indigo.kStateImageSel.NoImage) -``` +Key callbacks: +- `deviceStartComm(dev)` - Initialize device communication +- `deviceStopComm(dev)` - Clean up device resources +- `deviceUpdated(origDev, newDev)` - Handle configuration changes ## Configuration Validation @@ -260,8 +130,6 @@ def validateDeviceConfigUi(self, values_dict, type_id, dev_id): address = values_dict.get('address', '').strip() if not address: errors_dict['address'] = "Address is required" - elif not self.is_valid_address(address): - errors_dict['address'] = "Invalid address format" # Validate polling interval try: @@ -283,11 +151,7 @@ Populate configuration fields dynamically: ```python def get_device_list(self, filter="", values_dict=None, type_id="", target_id=0): - """ - Return list of available devices for dropdown - - :return: List of tuples (value, display_name) - """ + """Return list of available devices for dropdown""" device_list = [] for dev in indigo.devices: if dev.id != target_id: # Don't include self @@ -302,24 +166,24 @@ def get_device_list(self, filter="", values_dict=None, type_id="", target_id=0): ``` -## Accessing Device Properties +## Device Properties + +For complete device class reference including all properties and methods, see [API → IOM → Devices](../api/iom/devices.md). + +Common access patterns: ```python # Plugin-defined properties (from ConfigUI) address = dev.pluginProps.get('address', '') -interval = dev.pluginProps.get('pollingInterval', 60) # Device states temp = dev.states['temperature'] -status = dev.states.get('status', 'unknown') # Built-in properties dev.id # Unique device ID dev.name # Device name dev.deviceTypeId # Type ID from Devices.xml dev.enabled # Is device enabled? -dev.configured # Is device configured? -dev.description # Device description ``` ## Best Practices @@ -327,36 +191,25 @@ dev.description # Device description ### State Design - Use descriptive state IDs: `temperatureSensor1` not `temp1` - Choose appropriate value types for your data -- Use separators to group related states - Set `UiDisplayStateId` to most important state ### Device Communication - Initialize connections in `deviceStartComm()` - Clean up in `deviceStopComm()` - Handle device offline gracefully -- Update states only when values change to reduce database writes ### Configuration - Provide sensible defaults - Validate all user input -- Give helpful error messages - Use dynamic lists for device/variable selection ### Performance -- Batch state updates when possible -- Don't poll devices too frequently -- Cache device references if accessed frequently +- Batch state updates with `updateStatesOnServer()` +- Update states only when values change - Use `indigo.devices.iter("self")` to iterate only your plugin's devices -## Example Plugins - -- **Simple Custom Device**: [../IndigoSDK-2025.1/Example Device - Custom.indigoPlugin](../IndigoSDK-2025.1/Example%20Device%20-%20Custom.indigoPlugin) -- **Relay/Dimmer**: [../IndigoSDK-2025.1/Example Device - Relay and Dimmer.indigoPlugin](../IndigoSDK-2025.1/Example%20Device%20-%20Relay%20and%20Dimmer.indigoPlugin) -- **Thermostat**: [../IndigoSDK-2025.1/Example Device - Thermostat.indigoPlugin](../IndigoSDK-2025.1/Example%20Device%20-%20Thermostat.indigoPlugin) -- **Sensor**: [../IndigoSDK-2025.1/Example Device - Sensor.indigoPlugin](../IndigoSDK-2025.1/Example%20Device%20-%20Sensor.indigoPlugin) - -## Official References +## See Also -- [Plugin Developer's Guide - Devices](https://www.indigodomo.com/docs/plugin_guide#devices) -- [Plugin Developer's Guide - Device XML](https://www.indigodomo.com/docs/plugin_guide#devices_xml) -- [Object Model Reference - Device Class](https://www.indigodomo.com/docs/object_model_reference#device) +- [Device Classes Reference](../api/iom/devices.md) - Device properties and methods +- [Plugin Lifecycle](plugin-lifecycle.md) - Lifecycle callbacks +- [Constants Reference](../api/iom/constants.md) - State icons diff --git a/docs/concepts/events.md b/docs/concepts/events.md new file mode 100644 index 0000000..1ea22c6 --- /dev/null +++ b/docs/concepts/events.md @@ -0,0 +1,242 @@ +# Custom Plugin Events (Events.xml) + +Define custom trigger events that users can respond to in Indigo. + +## Overview + +Events.xml defines plugin-specific events that appear in Indigo's trigger system alongside built-in events like Power Failure or Email Received. + +Use cases: +- Update notifications +- Battery low alerts +- Button press events +- Connection status changes +- Custom sensor events + +## Events.xml Structure + +```xml + + + https://example.com/plugin/events.html + + + Plugin Update Available + + + + + + + + + Battery Low + + + + + + + + + + + + Connection Lost + + +``` + +## Event Elements + +| Element | Description | +|---------|-------------| +| `` | Unique identifier for the event | +| `` | Display name in trigger UI | +| `` | Optional configuration fields | +| `` | Help link for the event | + +## Firing Events + +Trigger events from your plugin code: + +```python +def _check_for_updates(self): + if self._update_available(): + # Fire event for all matching triggers + for trigger in indigo.triggers.iter("self.updateAvailable"): + if trigger.enabled: + indigo.trigger.execute(trigger) + +def _check_battery(self, dev, level): + for trigger in indigo.triggers.iter("self.batteryLow"): + if not trigger.enabled: + continue + + threshold = int(trigger.pluginProps.get("threshold", 20)) + if level < threshold: + indigo.trigger.execute(trigger) +``` + +## Event Callback Methods + +### triggerStartProcessing + +Called when a trigger using your event is enabled: + +```python +def triggerStartProcessing(self, trigger): + """Called when trigger is enabled.""" + self.logger.debug(f"Trigger started: {trigger.name}") + + # Track active triggers + self.active_triggers[trigger.id] = trigger.pluginTypeId +``` + +### triggerStopProcessing + +Called when a trigger is disabled: + +```python +def triggerStopProcessing(self, trigger): + """Called when trigger is disabled.""" + self.logger.debug(f"Trigger stopped: {trigger.name}") + + # Remove from tracking + if trigger.id in self.active_triggers: + del self.active_triggers[trigger.id] +``` + +### validateEventConfigUi + +Validate event configuration: + +```python +def validateEventConfigUi(self, valuesDict, typeId, triggerId): + """Validate event configuration.""" + errorsDict = indigo.Dict() + + if typeId == "batteryLow": + try: + threshold = int(valuesDict.get("threshold", 20)) + if threshold < 1 or threshold > 100: + errorsDict["threshold"] = "Must be between 1 and 100" + except ValueError: + errorsDict["threshold"] = "Must be a number" + + if errorsDict: + return (False, valuesDict, errorsDict) + + return (True, valuesDict) +``` + +### getEventConfigUiValues + +Provide initial/default values: + +```python +def getEventConfigUiValues(self, pluginProps, typeId, triggerId): + """Return initial values for event config UI.""" + valuesDict = pluginProps + + if typeId == "batteryLow": + valuesDict.setdefault("threshold", "20") + + return valuesDict +``` + +### closedEventConfigUi + +Called after event config is saved: + +```python +def closedEventConfigUi(self, valuesDict, userCancelled, typeId, triggerId): + """Called after event config dialog closes.""" + if userCancelled: + return + + self.logger.debug(f"Event config saved: {typeId}") +``` + +## Complete Example + +### Events.xml + +```xml + + + + Motion Detected + + + + + + + + + + + + + + + + +``` + +### plugin.py + +```python +def getZoneList(self, filter="", valuesDict=None, typeId="", targetId=0): + """Return list of zones for event config.""" + zones = [] + for dev in indigo.devices.iter("self.motionSensor"): + zone = dev.pluginProps.get("zone", "unknown") + zones.append((zone, f"Zone: {zone}")) + return zones + +def triggerStartProcessing(self, trigger): + self.logger.debug(f"Motion trigger started: {trigger.name}") + self.motion_triggers.append(trigger.id) + +def triggerStopProcessing(self, trigger): + self.logger.debug(f"Motion trigger stopped: {trigger.name}") + if trigger.id in self.motion_triggers: + self.motion_triggers.remove(trigger.id) + +def _on_motion_detected(self, zone): + """Called when motion is detected.""" + for trigger_id in self.motion_triggers: + try: + trigger = indigo.triggers[trigger_id] + if not trigger.enabled: + continue + + # Check if zone matches + trigger_zone = trigger.pluginProps.get("zone", "") + if trigger_zone and trigger_zone != zone: + continue + + # Fire the trigger + indigo.trigger.execute(trigger) + + except KeyError: + # Trigger was deleted + self.motion_triggers.remove(trigger_id) +``` + +## Best Practices + +- Use descriptive event IDs and names +- Provide sensible defaults in ConfigUI +- Validate all configuration input +- Track active triggers to avoid unnecessary iteration +- Handle deleted triggers gracefully +- Use separators to group related events + +## See Also + +- [Trigger Classes Reference](../api/iom/triggers.md) - Trigger object properties +- [Subscriptions](../api/iom/subscriptions.md) - Monitor trigger changes +- [Plugin Lifecycle](plugin-lifecycle.md) - When callbacks are called diff --git a/docs/concepts/plugin-lifecycle.md b/docs/concepts/plugin-lifecycle.md index 7816d0a..16d8021 100644 --- a/docs/concepts/plugin-lifecycle.md +++ b/docs/concepts/plugin-lifecycle.md @@ -1,6 +1,6 @@ # Plugin Lifecycle -**Official Documentation**: https://www.indigodomo.com/docs/plugin_guide#plugin_lifecycle +**Official Documentation**: [Plugin Guide - Lifecycle](https://wiki.indigodomo.com/doku.php?id=indigo_2025.1_documentation:plugin_guide#plugin_lifecycle) Understanding the plugin lifecycle is essential for proper plugin development, resource management, and avoiding memory leaks or zombie processes. @@ -608,14 +608,14 @@ def shutdown(self): - [ ] Files and sockets closed in `shutdown()` - [ ] Event subscriptions cancelled in `shutdown()` -## Related Documentation +## See Also -- [Device Development](devices.md) - Device lifecycle callbacks and patterns -- [State Management](state-management.md) - Managing device states -- [Configuration Validation](../api/ui-validation.md) - Validating user input +- [Device Development](devices.md) - Device types, Devices.xml, ConfigUI +- [Plugin Preferences](plugin-preferences.md) - Persistent plugin settings +- [API Patterns](../patterns/api-patterns.md) - State updates and common patterns ## Official References -- [Plugin Developer's Guide - Plugin Lifecycle](https://www.indigodomo.com/docs/plugin_guide#plugin_lifecycle) -- [Plugin Developer's Guide - Concurrent Thread](https://www.indigodomo.com/docs/plugin_guide#concurrent_thread) -- [Object Model Reference - Plugin Class](https://www.indigodomo.com/docs/object_model_reference#plugin) +- [Plugin Developer's Guide - Lifecycle](https://wiki.indigodomo.com/doku.php?id=indigo_2025.1_documentation:plugin_guide#plugin_lifecycle) +- [Plugin Developer's Guide - Concurrent Thread](https://wiki.indigodomo.com/doku.php?id=indigo_2025.1_documentation:plugin_guide#concurrent_thread) +- [Object Model Reference](https://wiki.indigodomo.com/doku.php?id=indigo_2025.1_documentation:object_model_reference) diff --git a/docs/concepts/plugin-preferences.md b/docs/concepts/plugin-preferences.md new file mode 100644 index 0000000..cd5250f --- /dev/null +++ b/docs/concepts/plugin-preferences.md @@ -0,0 +1,188 @@ +# Plugin Preferences + +Plugin preferences store persistent configuration that survives plugin restarts. + +## Overview + +- Preferences are automatically saved to disk +- Available via `self.pluginPrefs` dictionary +- Fields in `PluginConfig.xml` map to `pluginPrefs` automatically +- Can store: numbers, booleans, strings, `indigo.Dict()`, `indigo.List()` + +## Reading Preferences + +```python +# Direct access (raises KeyError if missing) +api_key = self.pluginPrefs["apiKey"] + +# Safe access with default +debug = self.pluginPrefs.get("showDebugInfo", False) +interval = self.pluginPrefs.get("pollingInterval", 60) +``` + +## Writing Preferences + +```python +# Set/update a preference +self.pluginPrefs["lastUpdate"] = str(datetime.now()) +self.pluginPrefs["retryCount"] = 5 +self.pluginPrefs["cachedData"] = {"key": "value"} + +# Preferences auto-save, but force immediate save: +indigo.server.savePluginPrefs() +``` + +## PluginConfig.xml Integration + +Fields defined in `PluginConfig.xml` automatically map to `pluginPrefs`: + +```xml + + + + + + + + + + + + +``` + +Access in plugin: + +```python +def startup(self): + api_key = self.pluginPrefs.get("apiKey", "") + interval = int(self.pluginPrefs.get("pollingInterval", 60)) + debug = self.pluginPrefs.get("showDebugInfo", False) +``` + +## Hidden Preferences + +Store values not shown in the config UI: + +```python +def startup(self): + # Read hidden prefs + self.last_sync = self.pluginPrefs.get("_lastSync", None) + +def _after_sync(self): + # Store hidden prefs + self.pluginPrefs["_lastSync"] = str(datetime.now()) + self.pluginPrefs["_syncCount"] = self.pluginPrefs.get("_syncCount", 0) + 1 +``` + +## Validating Preferences + +```python +def validatePrefsConfigUi(self, valuesDict): + """Validate plugin preferences before saving.""" + errorsDict = indigo.Dict() + + # Validate API key + api_key = valuesDict.get("apiKey", "").strip() + if not api_key: + errorsDict["apiKey"] = "API key is required" + + # Validate polling interval + try: + interval = int(valuesDict.get("pollingInterval", 60)) + if interval < 10: + errorsDict["pollingInterval"] = "Minimum is 10 seconds" + except ValueError: + errorsDict["pollingInterval"] = "Must be a number" + + if errorsDict: + return (False, valuesDict, errorsDict) + + return (True, valuesDict) +``` + +## Preference Change Callback + +React when preferences are saved: + +```python +def closedPrefsConfigUi(self, valuesDict, userCancelled): + """Called after preferences dialog closes.""" + if userCancelled: + return + + # Apply new settings + self.debug = valuesDict.get("showDebugInfo", False) + + # Reinitialize if API key changed + if valuesDict.get("apiKey") != self.api_key: + self.api_key = valuesDict.get("apiKey") + self._reinitialize_api_client() +``` + +## Common Patterns + +### Caching API Data + +```python +def _fetch_data(self): + """Fetch data with caching.""" + cached = self.pluginPrefs.get("_cachedData") + cache_time = self.pluginPrefs.get("_cacheTime") + + # Check cache validity (1 hour) + if cached and cache_time: + if datetime.now() - datetime.fromisoformat(cache_time) < timedelta(hours=1): + return cached + + # Fetch fresh data + data = self.api_client.get_data() + + # Update cache + self.pluginPrefs["_cachedData"] = data + self.pluginPrefs["_cacheTime"] = datetime.now().isoformat() + + return data +``` + +### Tracking Statistics + +```python +def _on_successful_action(self): + """Track usage statistics in preferences.""" + self.pluginPrefs["_successCount"] = self.pluginPrefs.get("_successCount", 0) + 1 + self.pluginPrefs["_lastSuccess"] = str(datetime.now()) + +def _on_failed_action(self, error): + """Track errors in preferences.""" + self.pluginPrefs["_errorCount"] = self.pluginPrefs.get("_errorCount", 0) + 1 + self.pluginPrefs["_lastError"] = str(error) +``` + +### Migration Between Versions + +```python +def startup(self): + # Check preference version + pref_version = self.pluginPrefs.get("_prefVersion", 1) + + if pref_version < 2: + # Migrate old format to new + if "oldKey" in self.pluginPrefs: + self.pluginPrefs["newKey"] = self.pluginPrefs["oldKey"] + del self.pluginPrefs["oldKey"] + self.pluginPrefs["_prefVersion"] = 2 +``` + +## Best Practices + +- Use `get()` with defaults for safe access +- Prefix hidden preferences with underscore (`_cacheTime`) +- Validate all user input in `validatePrefsConfigUi()` +- React to changes in `closedPrefsConfigUi()` +- Don't store sensitive data like passwords in plain text + +## See Also + +- [Plugin Lifecycle](plugin-lifecycle.md) - When to access preferences +- [Device Development](devices.md) - Device-specific configuration (pluginProps) diff --git a/docs/patterns/README.md b/docs/patterns/README.md index a637c24..394f408 100644 --- a/docs/patterns/README.md +++ b/docs/patterns/README.md @@ -2,51 +2,46 @@ Proven patterns and best practices for common plugin development scenarios. +## Available Patterns + +### [API Patterns](api-patterns.md) +Core patterns for working with the Indigo Object Model: +- Object modification (replaceOnServer, refreshFromServer) +- Device state updates (single, batch, error states) +- Object access and iteration +- Variable and action group patterns +- Common anti-patterns to avoid + ## Coming Soon -We're building a collection of implementation patterns including: +We're building additional implementation patterns including: ### API Integration - RESTful API clients - Rate limiting and throttling - Authentication patterns (API keys, OAuth, tokens) - Error handling and retries -- Response parsing and validation ### Polling and Updates - Efficient polling strategies - Rate limit compliance - Change detection -- Batch updates - Push vs pull patterns ### Error Handling - Graceful degradation - Retry logic with exponential backoff -- Error logging and reporting -- User notification patterns - Recovery strategies ### Configuration - Dynamic configuration UIs - Configuration validation - Secure credential storage -- Configuration migration -- Per-device vs plugin-wide settings - -### State Management -- State synchronization -- Optimistic updates -- State caching -- Batch state updates -- State persistence ### Performance -- Efficient API usage - Caching strategies - Minimizing Indigo API calls - Thread pool patterns -- Memory management ## Contributing @@ -56,8 +51,6 @@ Good pattern documentation includes: - Problem description - Solution approach - Complete code example -- Pros and cons - When to use vs not use -- Alternative approaches See [CONTRIBUTING.md](../../CONTRIBUTING.md) for guidelines. diff --git a/docs/patterns/api-patterns.md b/docs/patterns/api-patterns.md new file mode 100644 index 0000000..5fbb90e --- /dev/null +++ b/docs/patterns/api-patterns.md @@ -0,0 +1,251 @@ +# Indigo API Patterns + +Common patterns for working with the Indigo Object Model. + +For core concepts like client-server architecture, object modification, and `replaceOnServer()` patterns, see [IOM Architecture](../api/iom/architecture.md). + +## Device State Updates + +### Single State + +```python +dev.updateStateOnServer('temperature', value=72.5) + +# With UI display value +dev.updateStateOnServer('temperature', value=72.5, uiValue="72.5°F") +``` + +### Batch Updates (Preferred) + +More efficient than multiple single updates: + +```python +states = [ + {'key': 'temperature', 'value': 72.5, 'uiValue': '72.5°F'}, + {'key': 'humidity', 'value': 45}, + {'key': 'status', 'value': 'online'}, + {'key': 'lastUpdate', 'value': str(datetime.now())} +] +dev.updateStatesOnServer(states) +``` + +### With Decimal Places + +```python +states = [ + {'key': 'temperature', 'value': 72.5, 'decimalPlaces': 1}, + {'key': 'humidity', 'value': 45.234, 'decimalPlaces': 2} +] +dev.updateStatesOnServer(states) +``` + +### Error States + +```python +# Set error (displays in UI) +dev.setErrorStateOnServer("Connection failed") + +# Clear error +dev.setErrorStateOnServer(None) +``` + +### State Icons + +```python +# Update icon based on state +if dev.onState: + dev.updateStateImageOnServer(indigo.kStateImageSel.SensorOn) +else: + dev.updateStateImageOnServer(indigo.kStateImageSel.SensorOff) + +# Error icon +dev.updateStateImageOnServer(indigo.kStateImageSel.Error) +``` + +## Object Access Patterns + +### By ID (Preferred) + +```python +dev = indigo.devices[123456] +var = indigo.variables[789012] +``` + +### By Name (Not Recommended for Storage) + +```python +dev = indigo.devices["Living Room Lamp"] +``` + +### Safe Access with Existence Check + +```python +if 123456 in indigo.devices: + dev = indigo.devices[123456] +else: + self.logger.error("Device not found") +``` + +### Using get() for Variables + +```python +# Returns None if not found +var = indigo.variables.get("MyVariable") +if var: + value = var.value +``` + +## Iteration Patterns + +### All Objects + +```python +for dev in indigo.devices: + self.logger.info(dev.name) +``` + +### Plugin's Own Devices + +```python +for dev in indigo.devices.iter("self"): + self.update_device(dev) +``` + +### Specific Device Type + +```python +for dev in indigo.devices.iter("self.myThermostat"): + self.poll_thermostat(dev) +``` + +### By Protocol + +```python +for dev in indigo.devices.iter("indigo.insteon"): + self.logger.info(f"INSTEON: {dev.address}") +``` + +### IDs Only (More Efficient) + +```python +for dev_id in indigo.devices.iterkeys(): + # Process without loading full device + name = indigo.devices.getName(dev_id) +``` + +## Variable Patterns + +### Create Variable + +```python +try: + var = indigo.variable.create("MyPluginStatus", value="initialized") +except: + # Variable already exists + var = indigo.variables["MyPluginStatus"] +``` + +### Update Variable + +```python +indigo.variable.updateValue(var.id, value="running") +# or +indigo.variable.updateValue("MyPluginStatus", value="running") +``` + +### Safe Variable Access + +```python +def get_variable_value(self, name, default=""): + """Safely get variable value with default""" + try: + return indigo.variables[name].value + except KeyError: + return default +``` + +## Action Group Execution + +```python +# By ID +indigo.actionGroup.execute(123456) + +# By name +ag = indigo.actionGroups["Morning Routine"] +indigo.actionGroup.execute(ag.id) +``` + +## Logging Patterns + +### Standard Logging + +```python +self.logger.debug("Detailed info for debugging") +self.logger.info("Normal operational info") +self.logger.warning("Something unexpected but recoverable") +self.logger.error("Error that needs attention") +``` + +### Server Log (Visible in Event Log) + +```python +indigo.server.log("Message visible in Event Log") +indigo.server.log("Error message", isError=True) +``` + +## JSON Serialization + +```python +import json + +# Serialize device with proper date handling +dev_dict = dict(dev) +json_str = json.dumps(dev_dict, indent=2, cls=indigo.utils.IndigoJSONEncoder) +``` + +## Common Anti-Patterns + +### Don't Store Names + +```python +# BAD - name can change +self.device_name = "Living Room Lamp" + +# GOOD - ID is permanent +self.device_id = 123456 +``` + +### Don't Modify Cached Objects + +```python +# BAD - changes lost +dev = indigo.devices[123456] +dev.name = "New Name" +# forgot replaceOnServer() + +# GOOD +dev = indigo.devices[123456] +dev.name = "New Name" +dev.replaceOnServer() +``` + +### Don't Update States Unnecessarily + +```python +# BAD - updates even when unchanged +def poll(self): + temp = self.read_temperature() + dev.updateStateOnServer('temperature', value=temp) + +# GOOD - only update on change +def poll(self): + temp = self.read_temperature() + if dev.states['temperature'] != temp: + dev.updateStateOnServer('temperature', value=temp) +``` + +## See Also + +- [IOM Architecture](../api/iom/architecture.md) - Core concepts, replaceOnServer, pluginProps +- [Device Classes](../api/iom/devices.md) - Device properties and methods +- [Filters](../api/iom/filters.md) - Iteration patterns diff --git a/docs/quick-start.md b/docs/quick-start.md index eef7085..0faee29 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -281,12 +281,12 @@ Now that you have a working plugin: 1. **Understand Plugin Architecture** - Read [`concepts/plugin-lifecycle.md`](concepts/plugin-lifecycle.md) - - Learn about device types in [`concepts/device-types.md`](concepts/device-types.md) + - Learn about device types in [`concepts/devices.md`](concepts/devices.md) 2. **Add Functionality** - Create devices: See [`concepts/devices.md`](concepts/devices.md) - - Add actions: See [`patterns/actions-and-events.md`](patterns/actions-and-events.md) - - Build config UIs: See [`api/ui-validation.md`](api/ui-validation.md) + - Add custom events: See [`concepts/events.md`](concepts/events.md) + - Plugin preferences: See [`concepts/plugin-preferences.md`](concepts/plugin-preferences.md) 3. **Study Examples** - Browse [`sdk-examples/README.md`](../sdk-examples/README.md) for 16 complete examples @@ -298,10 +298,10 @@ Now that you have a working plugin: ## External Resources -- 📖 [Official Plugin Developer's Guide](https://www.indigodomo.com/docs/plugin_guide) -- 📚 [Indigo Object Model Reference](https://www.indigodomo.com/docs/object_model_reference) -- đŸ’Ŧ [Indigo Developer Forum](https://forums.indigodomo.com/viewforum.php?f=18) -- 🔧 [GitHub: Indigo Skill Repository](https://github.com/simons-plugins/indigo-claude-skill) +- [Official Plugin Developer's Guide](https://wiki.indigodomo.com/doku.php?id=indigo_2025.1_documentation:plugin_guide) +- [Indigo Object Model Reference](https://wiki.indigodomo.com/doku.php?id=indigo_2025.1_documentation:object_model_reference) +- [Indigo Developer Forum](https://forums.indigodomo.com/viewforum.php?f=18) +- [GitHub: Indigo Skill Repository](https://github.com/simons-plugins/indigo-claude-skill) --- diff --git a/skill.md b/skill.md index 844d6b8..bde2062 100644 --- a/skill.md +++ b/skill.md @@ -10,184 +10,171 @@ Community-maintained expert assistant for Indigo home automation plugin developm ## CRITICAL: Context Optimization Strategy -**DO NOT load all files.** This skill contains 1.3MB of SDK examples. Use Read tool selectively: +**DO NOT load all files.** This skill contains 1.3MB of SDK examples. Use Read tool selectively. + +### Safe to Load (Small Files) + +| File | Size | Use For | +|------|------|---------| +| `docs/quick-start.md` | 9KB | Getting started | +| `docs/concepts/plugin-lifecycle.md` | 12KB | Lifecycle methods | +| `docs/concepts/devices.md` | 7KB | Device design (Devices.xml, ConfigUI) | +| `docs/concepts/plugin-preferences.md` | 4KB | Plugin preferences (pluginPrefs) | +| `docs/concepts/events.md` | 5KB | Custom trigger events (Events.xml) | +| `docs/api/indigo-object-model.md` | 3KB | API overview and quick reference | +| `docs/examples/sdk-examples-guide.md` | 8KB | Example catalog | +| `docs/troubleshooting/common-issues.md` | 11KB | Troubleshooting | +| `docs/patterns/api-patterns.md` | 5KB | Common API patterns | + +### Modular IOM Reference (Load by Topic) + +The Indigo Object Model is split into focused files (~4KB each): + +| Topic | File | +|-------|------| +| Architecture | `docs/api/iom/architecture.md` | +| Command namespaces | `docs/api/iom/command-namespaces.md` | +| Device classes | `docs/api/iom/devices.md` | +| Trigger classes | `docs/api/iom/triggers.md` | +| Filters | `docs/api/iom/filters.md` | +| Subscriptions | `docs/api/iom/subscriptions.md` | +| Constants | `docs/api/iom/constants.md` | +| indigo.Dict/List | `docs/api/iom/containers.md` | +| Utilities | `docs/api/iom/utilities.md` | + +### NEVER Load All at Once + +- `sdk-examples/` - 16 complete plugins (1.3MB total) +- All `docs/api/iom/` files together -✅ **Safe to load** (small files): -- `docs/quick-start.md` (9KB) - Getting started guide -- `docs/concepts/plugin-lifecycle.md` (12KB) - Lifecycle reference -- `docs/concepts/devices.md` (11KB) - Device development -- `docs/api/indigo-object-model.md` (9KB) - API reference -- `docs/examples/sdk-examples-guide.md` (8KB) - Example catalog -- `docs/troubleshooting/common-issues.md` (11KB) - Troubleshooting +## Query Routing Guide -❌ **NEVER load all at once**: -- `sdk-examples/` - 16 complete plugins (1.3MB total) - Load ONLY specific example when needed +### Concepts vs API vs Patterns -## Query Routing Guide +These are complementary - load based on question type: -Route user questions efficiently using Read tool: +| User Asks About | Load | +|-----------------|------| +| Device design, Devices.xml, ConfigUI | `docs/concepts/devices.md` | +| Device properties, methods | `docs/api/iom/devices.md` | +| State updates, replaceOnServer | `docs/patterns/api-patterns.md` | +| Plugin lifecycle | `docs/concepts/plugin-lifecycle.md` | +| Plugin preferences, pluginPrefs | `docs/concepts/plugin-preferences.md` | +| Custom events, Events.xml | `docs/concepts/events.md` | +| Filters/iteration | `docs/api/iom/filters.md` | +| Subscriptions | `docs/api/iom/subscriptions.md` | -### 1. "Create a plugin" / "Getting started" -``` -1. Read docs/quick-start.md -2. Read snippets/plugin-base-template.py -3. If specific device type: Read docs/examples/sdk-examples-guide.md to find example +### Specific Routing + +**"Create a plugin" / "Getting started"** +1. Read `docs/quick-start.md` +2. Read `snippets/plugin-base-template.py` +3. If specific device type: Read `docs/examples/sdk-examples-guide.md` 4. Read ONLY that specific SDK example if needed -``` -### 2. "Debug error" / "Plugin not working" -``` -1. Read docs/troubleshooting/common-issues.md -2. Match error to solution -3. If lifecycle issue: Read docs/concepts/plugin-lifecycle.md -``` +**"Debug error" / "Plugin not working"** +1. Read `docs/troubleshooting/common-issues.md` +2. If lifecycle issue: Read `docs/concepts/plugin-lifecycle.md` -### 3. "How do I [API task]?" -``` -1. Read docs/api/indigo-object-model.md -2. Show code example from doc -``` +**"How do I save plugin settings?"** +1. Read `docs/concepts/plugin-preferences.md` -### 4. "Show me an example" -``` -1. Read docs/examples/sdk-examples-guide.md (catalog) -2. Find matching example -3. Read ONLY that specific example's code -``` +**"How do I create trigger events?"** +1. Read `docs/concepts/events.md` -### 5. "Explain [concept]" -``` -1. Read appropriate docs/concepts/ file -2. Explain with examples -``` +**"How do I update device state?"** +1. Read `docs/patterns/api-patterns.md` -## Workflow: Creating Plugins +**"What device properties exist?"** +1. Read `docs/api/iom/devices.md` -**User**: "Create a thermostat plugin" +**"How does replaceOnServer work?"** +1. Read `docs/api/iom/architecture.md` -1. ✅ Read `docs/quick-start.md` - Setup and structure -2. ✅ Read `docs/examples/sdk-examples-guide.md` - Find thermostat example -3. ✅ Read `sdk-examples/Example Device - Thermostat.indigoPlugin/.../plugin.py` - Specific code -4. ✅ Generate custom code based on pattern -5. ❌ Don't load all 16 examples +**"Show me an example"** +1. Read `docs/examples/sdk-examples-guide.md` +2. Read ONLY that specific example's code -## Workflow: Debugging +## Workflow: Creating Plugins -**User**: "Plugin crashes on startup" +**User**: "Create a thermostat plugin" -1. ✅ Read `docs/troubleshooting/common-issues.md#plugin-crashes-on-startup` -2. ✅ Check common causes (API in __init__, missing super()) -3. ✅ If needed: Read `docs/concepts/plugin-lifecycle.md` -4. ❌ Don't load examples unless relevant +1. ✅ Read `docs/quick-start.md` +2. ✅ Read `docs/examples/sdk-examples-guide.md` +3. ✅ Read specific thermostat example +4. ✅ Generate custom code +5. ❌ Don't load all 16 examples ## Documentation Structure -**Optimized for selective loading**: - ``` docs/ -├── quick-start.md # Complete getting started (9KB) +├── quick-start.md # Getting started (9KB) ├── concepts/ -│ ├── plugin-lifecycle.md # Lifecycle methods (12KB) -│ └── devices.md # Device development (11KB) +│ ├── plugin-lifecycle.md # Lifecycle methods (12KB) +│ ├── devices.md # Device design (7KB) +│ ├── plugin-preferences.md # Plugin prefs (4KB) +│ └── events.md # Custom events (5KB) ├── api/ -│ └── indigo-object-model.md # Complete API reference (9KB) +│ ├── indigo-object-model.md # Overview (3KB) +│ └── iom/ # Modular reference (~40KB total) +│ ├── architecture.md # Core concepts (5KB) +│ ├── command-namespaces.md # Commands (5KB) +│ ├── devices.md # Device classes (5KB) +│ ├── triggers.md # Trigger classes (4KB) +│ ├── filters.md # Iteration (4KB) +│ ├── subscriptions.md # Callbacks (6KB) +│ ├── constants.md # Icons/enums (4KB) +│ ├── containers.md # Dict/List (3KB) +│ └── utilities.md # Helpers (4KB) +├── patterns/ +│ └── api-patterns.md # Common patterns (5KB) ├── examples/ -│ └── sdk-examples-guide.md # Catalog of 16 examples (8KB) +│ └── sdk-examples-guide.md # Example catalog (8KB) └── troubleshooting/ - └── common-issues.md # Common problems (11KB) - -sdk-examples/ # 16 complete plugins (1.3MB) -├── README.md # Quick catalog (6KB) -└── [16 example plugins] # Load individually only + └── common-issues.md # Debugging (11KB) +sdk-examples/ # 16 plugins (1.3MB) - Load individually snippets/ -└── plugin-base-template.py # Clean template +└── plugin-base-template.py # Clean template ``` ## SDK Examples: 16 Complete Plugins -**Device Types** (Load when user needs specific type): -- Custom - General-purpose with custom states -- Relay/Dimmer - On/off and brightness control -- Thermostat - Climate control with HVAC modes -- Sensor - Read-only monitoring -- Speed Control - Variable speed (fans) -- Sprinkler - Multi-zone irrigation -- Energy Meter - Power monitoring +**Device Types**: Custom, Relay/Dimmer, Thermostat, Sensor, Speed Control, Sprinkler, Energy Meter -**Integration** (Load when user needs integration): -- HTTP Responder - Web server/REST API -- Action API - Custom actions -- Broadcaster/Subscriber - Plugin communication -- Variable Subscriber - Monitor variables -- Database Traverse - Query database +**Integration**: HTTP Responder, Action API, Broadcaster/Subscriber, Variable Subscriber, Database Traverse -**Finding examples**: Read `sdk-examples/README.md` or `docs/examples/sdk-examples-guide.md` first, then load specific example. +**Finding examples**: Read `docs/examples/sdk-examples-guide.md` first, then load specific example. -## Version Information - -- **Current**: Indigo 2023.2+ (Python 3.10+) -- **Legacy**: 2022.x (Python 2.7, not recommended) -- Always use Python 3 syntax - -## Best Practices (Always Follow) +## Best Practices ### Code Quality -- ✅ Always call `super()` in all lifecycle methods +- ✅ Always call `super()` in `__init__()` (but NOT in startup/shutdown) - ✅ Use `self.sleep()` NOT `time.sleep()` in concurrent threads -- ✅ Validate all user input in validation callbacks +- ✅ Validate all user input - ✅ Log appropriately: `self.logger.debug/info/error/exception()` -- ✅ Bundle Python dependencies in `Contents/Packages/` - ✅ Handle exceptions gracefully - ✅ Close connections in `shutdown()` ### Security - Never expose API keys in logs - Validate all external input -- Handle exceptions without exposing sensitive data ## Response Guidelines 1. **Reference file paths**: Link to specific docs with markdown - - Example: "See [plugin-lifecycle.md](docs/concepts/plugin-lifecycle.md)" - -2. **Show code examples**: Always include working code snippets - +2. **Show code examples**: Include working code snippets 3. **Explain why**: Don't just show code, explain the pattern - 4. **Point to examples**: "This is like Example Device - Thermostat" -## When to Use Each Resource - -| User Need | Load These | Don't Load | -|-----------|-----------|------------| -| Create plugin | quick-start.md, template.py | Examples | -| Debug | troubleshooting/common-issues.md | Concepts | -| API question | api/indigo-object-model.md | Examples | -| Concept | Specific concept doc | All concepts | -| Example | sdk-examples-guide.md + specific example | All examples | - -## Quick Reference - -### Core Files -- `docs/quick-start.md` - Getting started -- `docs/concepts/plugin-lifecycle.md` - Lifecycle -- `docs/concepts/devices.md` - Devices -- `docs/api/indigo-object-model.md` - API -- `docs/examples/sdk-examples-guide.md` - Example catalog -- `docs/troubleshooting/common-issues.md` - Troubleshooting - -### External Resources -- Official Plugin Guide: https://www.indigodomo.com/docs/plugin_guide -- Object Model Reference: https://www.indigodomo.com/docs/object_model_reference -- Scripting Tutorial: https://www.indigodomo.com/docs/plugin_scripting_tutorial +## External Resources + +- Official Plugin Guide: https://wiki.indigodomo.com/doku.php?id=indigo_2025.1_documentation:plugin_guide +- Object Model Reference: https://wiki.indigodomo.com/doku.php?id=indigo_2025.1_documentation:object_model_reference - Developer Forum: https://forums.indigodomo.com/viewforum.php?f=18 ## Skill Maintenance -This skill is actively maintained by the Indigo community. - - **Report issues**: https://github.com/simons-plugins/indigo-claude-skill/issues - **Contribute**: https://github.com/simons-plugins/indigo-claude-skill/pulls -- **Discussions**: https://github.com/simons-plugins/indigo-claude-skill/discussions