Unlocking Custom Integrations: Advanced RESTful Sensors & Switches in Home Assistant

Represent Unlocking Custom Integrations: Advanced RESTful Sensors & Switches in Home Assistant article
8m read

Unlocking Custom Integrations: Advanced RESTful Sensors & Switches in Home Assistant

In the vast and ever-expanding universe of smart home devices, it’s common to encounter a “smart” gadget or web service that lacks a direct, officially supported integration within Home Assistant. This often leaves enthusiasts and professionals alike reliant on clunky cloud apps, limited functionalities, or simply unable to incorporate these elements into their unified smart home ecosystem. If you’ve ever found yourself wishing you could pull data from a unique sensor or control a bespoke IoT device directly from Home Assistant, but hit a wall with native integrations, you’re in the right place.

Home Assistant’s RESTful Sensor and Switch integrations are powerful, often-underestimated tools that act as a universal translator for HTTP-based APIs. They allow you to define custom interactions with virtually any device or service that exposes a RESTful API – from your DIY ESP32 projects to web-based services like public weather data, cryptocurrency prices, or even custom smart appliances. This guide will take you beyond the basics, showing you how to unlock truly custom control, reduce cloud dependencies where possible, and significantly expand the capabilities of your Home Assistant instance.

Setting Up Your First RESTful Sensor

A RESTful sensor in Home Assistant is designed to read data from a specific HTTP endpoint. This data could be anything from a simple string to a complex JSON object. Let’s start with a practical example: fetching the current price of a cryptocurrency.

Example: Tracking Cryptocurrency Prices from a Public API

Many public APIs (like CoinGecko or CoinMarketCap) offer free access to real-time crypto prices. We’ll use CoinGecko for this example. The endpoint we’ll use might look something like: https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd.

Add the following to your configuration.yaml or a separate file included via !include sensors.yaml:

# configuration.yaml or sensors.yaml
sensor:
  - platform: restful
    name: Bitcoin Price USD
    resource: https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd
    scan_interval: 300 # Update every 5 minutes (300 seconds)
    value_template: "{{ value_json.bitcoin.usd | round(2) }}"
    unit_of_measurement: "USD"
    device_class: monetary
    unique_id: bitcoin_price_usd_rest

Explanation:

  • platform: restful: Specifies the integration type.
  • name: The friendly name for your sensor in Home Assistant.
  • resource: The URL endpoint Home Assistant will send a GET request to.
  • scan_interval: How often Home Assistant should poll the resource (in seconds). Be mindful of API rate limits.
  • value_template: This is where the magic of Jinja2 templating comes in. The value_json variable contains the parsed JSON response from the API. We access nested values using dot notation (e.g., value_json.bitcoin.usd) and then use filters like round(2) to format the output.
  • unit_of_measurement & device_class: Provide context for the sensor’s value, helping Home Assistant display it appropriately.
  • unique_id: Essential for managing the entity via the UI and for future updates.

After adding this, restart Home Assistant. You should see a new sensor (sensor.bitcoin_price_usd) reflecting the current price.

Placeholder for Home Assistant UI showing Bitcoin price sensor

(Screenshot Placeholder: Home Assistant UI showing the created "Bitcoin Price USD" sensor with its current value.)

Controlling Devices with RESTful Switches (POST/PUT)

Reading data is just half the story. RESTful switches allow Home Assistant to send commands to external devices or services using POST, PUT, or DELETE requests. This is perfect for custom IoT devices with simple web servers.

Example: Controlling a Custom ESP32 LED via a Simple Web Server

Imagine you have an ESP32 board running a basic web server that exposes two endpoints: /led/on to turn an LED on and /led/off to turn it off. It also provides a /led/status endpoint to report the current state as a simple JSON (e.g., {"state": "ON"} or {"state": "OFF"}).

Here’s how you’d configure a RESTful switch:

# configuration.yaml or switches.yaml
switch:
  - platform: restful
    name: ESP32 LED Control
    resource: http://192.168.1.100/led
    state_resource: http://192.168.1.100/led/status
    is_on_template: "{{ value_json.state == 'ON' }}"
    body_on: "state=on"
    body_off: "state=off"
    headers:
      Content-Type: application/x-www-form-urlencoded
    unique_id: esp32_led_control_rest

Explanation:

  • platform: restful: Again, specifying the integration type.
  • name: Friendly name for your switch.
  • resource: The base URL for sending ON/OFF commands. When body_on or body_off is used, Home Assistant typically sends a POST request to this resource.
  • state_resource: A separate URL (GET request) to poll for the current state of the device. This is crucial for Home Assistant to accurately display whether the switch is on or off.
  • is_on_template: A Jinja2 template that evaluates to true if the switch is on, based on the response from state_resource.
  • body_on / body_off: The actual payload sent in the POST request when turning the switch ON or OFF. Here, we’re sending URL-encoded form data.
  • headers: Defines HTTP headers to be sent with the request. Content-Type is important for the server to correctly interpret the body.

Restart Home Assistant, and you’ll have a new switch (switch.esp32_led_control) to toggle your custom device.

Placeholder for Home Assistant UI showing custom ESP32 LED switch

(Screenshot Placeholder: Home Assistant UI showing the created "ESP32 LED Control" switch.)

Common RESTful Integration Issues and Solutions

Integrating with external APIs can be tricky. Here are common problems and how to troubleshoot them:

Entities Not Showing Up or Unavailable

  • Configuration Errors: Always run Home Assistant’s configuration check before restarting: ha core check config or go to Developer Tools -> YAML configuration -> CHECK CONFIGURATION. Even a small indentation error can prevent entities from loading.

  • Unreachable Resource:

    • From the machine running Home Assistant, try to access the resource URL using curl or wget in the terminal.
    • Example: curl -v https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd
    • This helps verify network connectivity, DNS resolution, and if the API endpoint itself is returning data.
  • Home Assistant Logs: Check your Home Assistant logs (Settings -> System -> Logs). Look for messages related to the rest platform. Errors like “Connection refused”, “Timeout”, or “JSONDecodeError” are common indicators.

  • Incorrect value_template: If the API returns data, but your sensor is “unavailable” or shows “unknown”, your value_template might be trying to parse a non-existent key or malformed data. Use Developer Tools -> Template to test your Jinja2 template with sample API responses.

Switch State Not Updating or Not Responding

  • Incorrect state_resource / is_on_template: Ensure your state_resource correctly returns the device’s state and that your is_on_template accurately interprets that response (e.g., value_json.status == 'active'). Test the state_resource with curl and then test the template in Developer Tools.

  • Device/API Not Registering Commands: Verify that the target device or API actually receives and acts upon the body_on / body_off payloads. Use the network inspector in your browser (if testing a web UI) or check the logs of your custom device/API.

  • Wrong HTTP Method: By default, RESTful switches use POST for body_on/body_off and GET for state_resource. If your API requires PUT or another method, you’ll need to specify it with the method option (e.g., method: PUT).

  • Rate Limiting: If you’re interacting with an external API, frequent polling for state or sending many commands can trigger rate limits, leading to temporary unresponsiveness. Check the API documentation and adjust your scan_interval accordingly.

Authentication Errors

  • Missing/Incorrect Headers: Many APIs require an API key or a Bearer token in the HTTP headers. Double-check the API documentation and ensure your headers section in the YAML config is correct. Remember to keep sensitive tokens in secrets.yaml!

    # Example with an API Key in headers
    sensor:
      - platform: restful
        name: Secure Data
        resource: https://api.example.com/data
        headers:
          Authorization: !secret my_api_bearer_token
          X-API-Key: !secret my_api_key
        value_template: "{{ value_json.data }}"
    
  • SSL/TLS Issues: If you’re connecting to an HTTPS endpoint and encounter SSL certificate errors (often seen in logs), you might need to adjust verify_ssl (though generally, it’s best to keep this as true for security). Only set to false for self-signed certificates on trusted local networks after understanding the risks.

Optimizing and Securing Your RESTful Integrations

As you scale your custom integrations, consider these advanced configurations and best practices:

Dynamic Resources & Headers with Templates

Sometimes, your RESTful endpoint or headers need to be dynamic. For instance, if your device's IP address might change, or if an API token expires and needs to be refreshed (though token refreshing usually involves more complex scripts).

# Example: Dynamic resource from an input_text helper
sensor:
  - platform: restful
    name: Dynamic Sensor Value
    resource_template: "http://{{ states('input_text.device_ip') }}/data"
    value_template: "{{ value_json.value }}"

Here, the resource_template fetches the device IP from an input_text helper, making your configuration more flexible.

Error Handling in Jinja2 Templates

To make your sensors more robust, use Jinja2’s default filter or {% if ... is defined %} checks to prevent errors if the API response is malformed or missing data.

# More robust value_template
value_template: "{{ value_json.bitcoin.usd | default(0) | round(2) }}"

# Or for potentially missing attributes
value_template: "{% if value_json.device is defined and value_json.device.temp is defined %}
  {{ value_json.device.temp }}
{% else %}
  Unavailable
{% endif %}"

Handling Complex JSON Responses

If your API returns a large JSON object with multiple useful attributes, you don’t need to create separate sensors for each. Use json_attributes_path and json_attributes to extract them into sensor attributes.

sensor:
  - platform: restful
    name: Weather Conditions
    resource: https://api.weather.com/v1/current?location=london
    value_template: "{{ value_json.current.temp_c }}"
    json_attributes_path: "$.current" # Point to the 'current' object
    json_attributes:
      - humidity
      - pressure
      - wind_speed
    unit_of_measurement: "°C"
    device_class: temperature

This creates one sensor (sensor.weather_conditions) with humidity, pressure, and wind_speed as attributes, accessible via state_attr('sensor.weather_conditions', 'humidity') in automations.

Case Study: Smart Greenhouse Control via Custom REST API

Let’s apply these concepts to a real-world scenario: a small, automated greenhouse. An ESP32-based controller monitors temperature and soil moisture, and can activate a fan or a water pump. The ESP32 exposes a simple REST API:

  • GET /api/status: Returns {"temperature": 25.5, "humidity": 70, "soil_moisture": 450, "fan_state": "OFF", "pump_state": "OFF"}
  • POST /api/fan with {"command": "ON"} or {"command": "OFF"}
  • POST /api/pump with {"command": "ON"} or {"command": "OFF"}

Here’s how to integrate this into Home Assistant:

# configuration.yaml
sensor:
  - platform: restful
    name: Greenhouse Temperature
    resource: http://192.168.1.150/api/status
    scan_interval: 30 # Poll every 30 seconds
    value_template: "{{ value_json.temperature | round(1) }}"
    unit_of_measurement: "°C"
    device_class: temperature
    unique_id: greenhouse_temp_rest

  - platform: restful
    name: Greenhouse Humidity
    resource: http://192.168.1.150/api/status
    scan_interval: 30
    value_template: "{{ value_json.humidity | round(0) }}"
    unit_of_measurement: "%"
    device_class: humidity
    unique_id: greenhouse_humid_rest

  - platform: restful
    name: Greenhouse Soil Moisture
    resource: http://192.168.1.150/api/status
    scan_interval: 30
    value_template: "{{ value_json.soil_moisture }}"
    unit_of_measurement: "raw" # or custom unit if calibrated
    icon: mdi:water-percent
    unique_id: greenhouse_soil_rest

switch:
  - platform: restful
    name: Greenhouse Fan
    resource: http://192.168.1.150/api/fan
    state_resource: http://192.168.1.150/api/status
    is_on_template: "{{ value_json.fan_state == 'ON' }}"
    body_on: '{"command": "ON"}'
    body_off: '{"command": "OFF"}'
    headers:
      Content-Type: application/json
    unique_id: greenhouse_fan_rest

  - platform: restful
    name: Greenhouse Pump
    resource: http://192.168.1.150/api/pump
    state_resource: http://192.168.1.150/api/status
    is_on_template: "{{ value_json.pump_state == 'ON' }}"
    body_on: '{"command": "ON"}'
    body_off: '{"command": "OFF"}'
    headers:
      Content-Type: application/json
    unique_id: greenhouse_pump_rest

Now, you can create powerful automations:

# Automate the fan
automation:
  - alias: "Turn on greenhouse fan if too hot"
    trigger:
      - platform: numeric_state
        entity_id: sensor.greenhouse_temperature
        above: 28 # degrees Celsius
    condition: []
    action:
      - service: switch.turn_on
        entity_id: switch.greenhouse_fan
    mode: single

  - alias: "Turn off greenhouse fan if cool"
    trigger:
      - platform: numeric_state
        entity_id: sensor.greenhouse_temperature
        below: 26 # degrees Celsius
    condition: []
    action:
      - service: switch.turn_off
        entity_id: switch.greenhouse_fan
    mode: single

# Automate watering
  - alias: "Water greenhouse if soil is dry"
    trigger:
      - platform: numeric_state
        entity_id: sensor.greenhouse_soil_moisture
        below: 400 # arbitrary dry threshold
    condition:
      - condition: time
        after: '07:00:00'
        before: '19:00:00'
    action:
      - service: switch.turn_on
        entity_id: switch.greenhouse_pump
      - delay: "00:00:15" # Run pump for 15 seconds
      - service: switch.turn_off
        entity_id: switch.greenhouse_pump
    mode: single

Best Practices for Robust RESTful Integrations

  • Local First: Prioritize integrating local APIs over external cloud services whenever possible. This reduces latency, increases reliability (no internet dependency), and enhances privacy.

  • API Documentation is Your Bible: Always refer to the official API documentation of the service or device you're integrating. It provides crucial details on endpoints, required headers, authentication, and response formats.

  • Robust Error Handling: As shown with default and if ... is defined in Jinja2, anticipate that API responses might not always be perfect. Build templates that gracefully handle missing data or unexpected formats.

  • Security First (Secrets.yaml): Never hardcode sensitive API keys, tokens, or passwords directly in your configuration.yaml. Use secrets.yaml for all credentials.

  • Testing External APIs: Before even touching Home Assistant, test your API endpoints using tools like Postman, Insomnia, or a simple curl command. This isolates API-side issues from Home Assistant configuration problems.

  • Mind the scan_interval: Be considerate of the polling frequency. For external public APIs, too frequent requests can lead to rate limiting, account bans, or unnecessary network strain. For local devices, balance responsiveness with device load. If a device has a push mechanism (like webhooks), that's often more efficient than polling.

  • Configuration Structure: For better organization, use split configurations. Create separate files like rest_sensors.yaml and rest_switches.yaml and include them in your main configuration.yaml.

  • Read Home Assistant Logs: Your logs are your best friend for debugging. Always check them after any configuration change or if an entity behaves unexpectedly.

Mastering RESTful sensors and switches significantly expands Home Assistant’s capabilities, allowing you to truly customize and unify your smart home experience. With careful configuration and a solid understanding of the target API, the possibilities are nearly endless.

Avatar picture of NGC 224
Written by:

NGC 224

Author bio: DIY Smart Home Creator

There are no comments yet
loading...