Somfy SDK
A Rust library providing type-safe, async access to the Somfy API for controlling smart home devices.
Overview
The SDK provides a comprehensive, type-safe interface for interacting with Somfy smart home devices through the Somfy API. It supports device discovery, state management, event handling, and action execution with built-in error handling and TLS support for self-signed certificates.
Features
- Type-safe API client with async support using Tokio
- Comprehensive API coverage - all Somfy API endpoints
- Extensible command system for adding new API endpoints
- Robust error handling with custom error types
- TLS/SSL support with custom certificate handling
- Bearer token authentication for secure API access
- Structured logging with configurable log levels
Installation
Add this to your Cargo.toml:
[]
= { = "somfy-sdk", = "0.2" }
= { = "1.0", = ["full"] }
Quick Start
use ApiClient;
use RequestError;
async
Supported API Endpoints
This SDK implements the complete Somfy API:
| Category | Endpoint | Method | SDK Method | Description |
|---|---|---|---|---|
| System | /apiVersion |
GET | get_version() |
Get API protocol version |
| Setup | /setup/gateways |
GET | get_gateways() |
List available gateways |
| Setup | /setup |
GET | get_setup() |
Get complete setup information |
| Setup | /setup/devices |
GET | get_devices() |
List all devices |
| Setup | /setup/devices/{deviceURL} |
GET | get_device() |
Get specific device details |
| Setup | /setup/devices/{deviceURL}/states |
GET | get_device_states() |
Get device states |
| Setup | /setup/devices/{deviceURL}/states/{name} |
GET | get_device_state() |
Get specific device state |
| Setup | /setup/devices/controllables/{controllableName} |
GET | get_devices_by_controllable() |
Get devices by controllable type |
| Events | /events/register |
POST | register_event_listener() |
Register event listener |
| Events | /events/{listenerId}/fetch |
POST | fetch_events() |
Fetch events from listener |
| Events | /events/{listenerId}/unregister |
POST | unregister_event_listener() |
Unregister event listener |
| Execution | /exec/apply |
POST | execute_actions() ⚠️ |
Execute action group (requires generic-exec feature) |
| Execution | /exec/current |
GET | get_current_executions() |
Get all current executions |
| Execution | /exec/current/{executionId} |
GET | get_execution() |
Get specific execution status |
| Execution | /exec/current/setup |
DELETE | cancel_all_executions() |
Cancel all executions |
| Execution | /exec/current/setup/{executionId} |
DELETE | cancel_execution() |
Cancel specific execution |
Configuration
Easy Setup
The simplest way to create a client:
// Gateway ID format: "0000-1111-2222"
// This automatically configures HTTPS, port 8443, and certificate handling
let client = from.await?;
Advanced Configuration
For more control, use the full configuration:
use ;
let config = ApiClientConfig ;
let client = new.await?;
Certificate Handling
Somfy gateways use self-signed certificates, requiring specific certificate handling strategies. The SDK provides three approaches:
DefaultCert (Recommended & Default)
Automatically transparently downloads the root CA from here to $HOME/.somfy_sdk/cert.crt and trusts it.
The certificate will be cached indefinitely and will not be checked for expiry. Delete the local file to trigger a redownload.
let config = ApiClientConfig ;
DefaultCert is the default strategy used for the shorthand ApiClient::from(..).
// This uses DefaultCert automatically
let client = from;
CertProvided(path)
Use a manually provided certificate file.
The cert will not be cached and needs to be provided for every instantiation of ApiClient
let config = ApiClientConfig ;
NoCustomCert
Do not add a root certificate to the reqwest trust chain.
This will only work against endpoints that present certificates of trusted CAs. somfy-sdk uses reqwest with rustls-tls-native-roots which respects certificates trusted at the OS level
let config = ApiClientConfig ;
Feature Flags
The SDK uses feature flags to control access to potentially dangerous functionality:
generic-exec feature
The execute_actions() method is gated behind the generic-exec feature flag because it provides raw access to the /exec/apply endpoint, which can potentially harm your Somfy devices if used incorrectly.
Enabling the feature
Add the feature to your Cargo.toml:
[]
= { = "somfy-sdk", = "0.2", = ["generic-exec"]}
Why is this feature gated?
The generic execution API allows sending arbitrary commands to any device:
// ⚠️ This can be dangerous - wrong device URL or command can cause damage
let actions = vec!;
client.execute_actions.await;
Safer Alternative: Custom Commands
Instead of using the generic API, we strongly recommend creating type-safe, domain-specific commands (see Extending the SDK section). These provide compile-time safety and prevent accidental misuse.
API Reference
Core Types
ApiClient
The main client for interacting with Somfy APIs:
Usage Examples
Device Discovery and Management
// Get complete setup information
let setup = client.get_setup.await?;
println!;
// Get all devices
let devices = client.get_devices.await?;
for device in devices
// Get device states
if let Some = devices.first
Event Management
// Register event listener
let listener = client.register_event_listener.await?;
println!;
// Fetch events (typically done in a loop)
let events = client.fetch_events.await?;
println!;
// Unregister when done
client.unregister_event_listener.await?;
Action Execution
use ;
let actions = vec!;
let request = ActionGroup ;
let execution = client.execute_actions.await?;
println!;
// Monitor execution
let execution_details = client.get_execution.await?;
println!;
Error Handling
The SDK provides comprehensive error handling through the RequestError enum:
use RequestError;
match client.get_version.await
Error Types
CertError- TLS certificate validation issues (common with self-signed certs)AuthError- Authentication failures (invalid API key, unauthorized)InvalidBody- JSON parsing or response format errorsInvalidRequestError- Malformed requestsNotFoundError- Resource not found (404)ServerError- Server-side errors (5xx)UnknownError- Catch-all for unexpected errors
Testing
Run the SDK tests:
# Run SDK tests only
# Run Integration tests against local mock server
# Uses json-server@0.17.x
Architecture
SDK Structure
sdk/
├── src/
│ ├── api_client.rs # Main API client implementation
│ ├── commands/ # API command definitions
│ │ ├── traits.rs # Command traits and interfaces
│ │ ├── types.rs # Shared types and data structures
│ │ ├── get_version.rs # Version command implementation
│ │ ├── get_setup.rs # Setup command implementation
│ │ └── ... # Other command implementations
│ ├── config/ # Configuration modules
│ ├── err/ # Error handling
│ └── lib.rs # Library root
└── tests/ # Integration tests
└── fixtures/ # Test data
Extending the SDK with Custom Commands
The SDK is built for extensibility. You can adapt to API changes, handle undocumented behaviors, and create type-safe, domain-specific commands by implementing the required traits.
Why Extend the SDK?
There are two primary use cases for creating custom commands:
1. Adapting to API Changes and Undocumented Behavior
The real-world API sometimes deviates from the API specification (e.g., see example below). While we strive to find and cover all such scenarios (please raise an issue here), this may happen with your specific Somfy configuration. In such scenarios, you can use a custom command to work around this behavior until the fix is implemented in mainline.
// ./sdk/src/get_execution.rs
2. Creating Type-Safe, Domain-Specific Commands
The generic execute actions API (/exec/apply) is powerful but can be dangerous if misused.
It is thus disabled by default and needs to be enabled through the "generic-exec" feature flag.
Custom commands provide compile-time safety and prevent accidental misuse by making commands explicit and known at compile time.
Consider the following example:
// ❌ Generic API with client.execute_actions(action) enabled - easy to make potentially destructive mistakes
let request = ActionGroup ;
api_client.execute_actions.await
// ✅ Type-safe domain command - impossible to misuse, client.execute_actions(..) not even available
let cmd = CloseLivingRoomShuttersCommand ; // see implementation below
client.execute.await?;
Implementation Examples
Type-Safe Device Commands
Here's how to create a domain-specific command that prevents dangerous mistakes:
use Body;
use ApiClient;
use ExecuteActionGroupResponse;
use ;
use ;
use RequestError;
use HashMap;
// Type-safe command for a specific device with validation
async
Handling API Quirks and Custom Response Processing
Adapt to undocumented behaviors by customizing response handling. Here's a hypothetical example where the API introduces inconsistent response formats:
Best Practices for Custom Commands
- Safety First: Use type-safe, domain-specific commands for potentially harmful operations
- Handle API quirks: Override
from_body()to handle undocumented behaviors gracefully - Validation: Validate parameters at compile-time with newtypes or at runtime with bounds checking
- Hard-code device URLs: For device-specific commands, hard-code URLs to prevent targeting wrong devices
- Meaningful errors: Provide clear error messages for validation failures
- Testing: Add comprehensive unit tests, especially for edge cases and API quirks
- Documentation: Document any API behaviors your commands work around
Integration with Built-in Commands
Your custom commands work seamlessly with the existing SDK infrastructure:
// Mix custom and built-in commands
let version = client.get_version.await?;
let response = client.execute.await?;
let devices = client.get_devices.await?;
println!;
License
This project is licensed under the MIT License - see the LICENSE file for details.