Skip to content

Repository files navigation

INDI Client for Home Assistant

HACS Custom Validate Tests License: MIT

You can take a look at my issue and pr queue if you are wondering why is something stale for days here

A Home Assistant custom integration that connects to a running INDI server (indiserver, default TCP port 7624) as an additional client, exactly the way CCDciel, KStars/EKOS or indi_getprop/indi_setprop would. It does not take exclusive control of any device: it simply reads whatever properties the server broadcasts (already reflecting changes made by other clients) and, when a property allows it, can send its own commands to change it - bidirectionally.

Status: beta (v1.1.0). The protocol core and entity mapping are functional and unit tested, but this has not yet been run against every INDI driver in the wild. Feedback and bug reports are very welcome.

Disclaimer: this is an independent, unofficial client. It is not affiliated with, endorsed by, or sponsored by the INDI Library project. "INDI" is used here only to describe protocol compatibility with indiserver.

This integration is used in the author's private DevControl2 system (built for the Bombol.Space telescope hosting facility), but this integration itself is generic - it makes no assumption about any specific installation and works with any standard-compliant INDI driver/device.

Why

Typical astronomy setups already run indiserver with drivers for the mount, camera, focuser, filter wheel, dome, weather station, etc., controlled from an astronomy application (CCDciel, KStars/EKOS, ...). This integration lets Home Assistant sit alongside that application as a peer client, so you can, for example:

  • Park/unpark the mount from an HA automation (e.g. on rain or high wind).
  • Read and set a CCD's target temperature, and watch the current sensor temperature.
  • See the CCD's latest captured frame as a camera preview, right in a Lovable dashboard.
  • See device connection state and recent driver log messages inside HA.
  • Trigger dome/roof or power-switch style properties from dashboards and automations.

How it works

INDI is a push-based, XML-over-TCP protocol: the client sends getProperties, the server answers with def*Vector elements describing each device's properties, and later pushes set*Vector elements whenever a value changes (caused by any connected client, or by the driver itself). This integration implements that protocol directly in Python (custom_components/indi_client/indi/, no pyindi-client/SWIG dependency - the protocol core itself is pure stdlib, only camera-frame decoding needs numpy/Pillow), keeps a live model of every device/property, and maps them onto Home Assistant entities automatically - there is no hardcoded list of "supported" devices or drivers.

Entity mapping

INDI vector type Permission Rule Home Assistant entity
Number read-only - sensor (device_class temperature/humidity inferred by element name, e.g. *TEMPERATURE)
Number writable - number
Text read-only - sensor
Text writable - text
Light any - sensor (diagnostic; state is Idle/Ok/Busy/Alert)
Switch writable OneOfMany / AtMostOne select
Switch read-only OneOfMany / AtMostOne sensor (name of the active option)
Switch writable AnyOfMany switch per element
Switch read-only AnyOfMany binary_sensor per element
CONNECTION (standard property) writable - switch named Connected (special-cased for a nicer toggle instead of a Connect/Disconnect dropdown)
BLOB (captured frame) any - camera (see below)
device/server log messages - - sensor Last message per device, with recent history in history attribute
server TCP link - - diagnostic binary_sensor Server connected

Each INDI device becomes its own Home Assistant device (grouping all of its entities), linked to a parent "INDI Server (host:port)" hub device.

So, concretely:

  • Parking a mount - the standard TELESCOPE_PARK switch vector (rule OneOfMany, options PARK/UNPARK) becomes a select entity; picking Park sends the park command.
  • Setting CCD temperature - CCD_TEMPERATURE becomes a number entity; changing it sends a newNumberVector, and the driver's actual sensor reading (if exposed as a separate read-only element) is a sensor.
  • Checking logs - every device gets a diagnostic Last message sensor showing the latest INDI log line, with the last 25 messages available as an attribute.
  • Connectivity - the switch.<device>_connected entity reflects and controls the driver's CONNECTION property, and binary_sensor.server_connected reflects the TCP link to indiserver itself.

Camera previews

BLOB (image) data is opt-in per the INDI protocol - a driver only sends it once a client asks. As soon as a camera entity for a device's BLOB property (e.g. CCD1) is set up, this integration sends that request (enableBLOB ... Also) automatically, so the camera starts showing the latest captured frame with no extra configuration.

Frames are decoded with a small built-in FITS reader (the format virtually every INDI camera driver uses), stretched with a percentile clip for a reasonable preview, and JPEG-encoded (needs numpy and Pillow, both pulled in automatically). See Known limitations below for what this preview pipeline does not do (debayering, precise processing).

Escape hatch: raw property access

Not every property needs a dedicated entity to be usable. Two services cover anything the automatic mapping does not (yet) turn into a nice entity:

# Re-request property definitions (e.g. after a driver was reloaded)
service: indi_client.refresh
data:
  config_entry_id: <config entry id>
  device: "Telescope Simulator"   # optional

# Send a raw new*Vector command
service: indi_client.set_property
data:
  config_entry_id: <config entry id>
  device: "Telescope Simulator"
  property: "TELESCOPE_PARK"
  type: "Switch"
  values:
    PARK: "On"

Installation

Via HACS (custom repository)

This integration is not yet in the default HACS store. Add it as a custom repository:

  1. HACS -> Integrations -> the ⋮ menu (top right) -> Custom repositories.
  2. URL: https://github.com/jan-tdy/ha-indi-client, category: Integration.
  3. Install INDI Client, then restart Home Assistant.

Manual

Copy custom_components/indi_client into your Home Assistant config/custom_components/ directory, then restart Home Assistant.

Configuration

Everything is configured through the UI - no YAML required.

  1. Settings -> Devices & services -> Add integration -> INDI Client.
  2. Enter the host and port of your indiserver (default port 7624).
  3. Home Assistant validates the connection and creates one entry per server. Entities appear automatically as indiserver announces devices/properties - this can take a few seconds after setup, and again whenever a driver is (re)connected.

To change the host/port later, open the integration entry and use Configure (options flow).

Example automations

Park the mount when it starts raining:

automation:
  - alias: Park mount on rain
    trigger:
      - platform: state
        entity_id: binary_sensor.rain_sensor
        to: "on"
    action:
      - action: select.select_option
        target:
          entity_id: select.telescope_simulator_parking
        data:
          option: "Park"

Cool the CCD down before an imaging session:

automation:
  - alias: Cool CCD before imaging
    trigger:
      - platform: time
        at: "21:30:00"
    action:
      - action: number.set_value
        target:
          entity_id: number.ccd_simulator_temperature
        data:
          value: -10

Notify when a device reports an Alert state:

automation:
  - alias: INDI device alert
    trigger:
      - platform: state
        entity_id: sensor.telescope_simulator_last_message
    condition:
      - condition: template
        value_template: "{{ 'Alert' in trigger.to_state.attributes.get('history', [''])[-1] }}"
    action:
      - action: notify.mobile_app
        data:
          message: "{{ trigger.to_state.state }}"

Known limitations

  • Camera previews are a quick look, not calibrated/processed data. Frames are decoded and displayed with a simple percentile-clip stretch - there's no dark/flat calibration, no plate solving. A driver's raw single-plane Bayer frame is not debayered by this integration and shows as a grayscale mosaic pattern; a 3-plane RGB cube (NAXIS=3, one plane each for R/G/B - what a driver sends once it has already debayered the frame itself) is stretched and shown in color. Use CCDciel/KStars for actual image processing; this is just a "is it working / roughly in focus" preview inside HA. Non-FITS, non-JPEG BLOB formats (e.g. .xisf) aren't decoded.
  • Multi-element number vectors (e.g. EQUATORIAL_EOD_COORD with RA + DEC for a GOTO) are exposed as independent number entities. Setting one sends only that element; most drivers keep the other element's last known value, but this is driver-dependent. For coordinated multi-element commands, prefer the indi_client.set_property service, or set both entities and use an automation with a short delay.
  • Number units are inferred heuristically from the element name (e.g. *TEMPERATURE -> °C); INDI itself does not transmit explicit units.
  • Tested primarily against INDI's own driver simulators; real-hardware driver quirks may surface edge cases - please open an issue if you hit one.

Development

The custom_components/indi_client/indi/ package (protocol parsing, wire formatting, the asyncio client) is pure Python with no Home Assistant dependency, so it can be unit tested in isolation:

pip install -r requirements_test.txt
pytest

Releasing

HACS tracks GitHub Releases, not CHANGELOG.md directly - and releasing is fully automatic. Every PR into main must bump version in custom_components/indi_client/manifest.json (CI-enforced by .github/workflows/require-version-bump.yml; add a CHANGELOG.md entry alongside it). Once merged, .github/workflows/release.yml tags that version and publishes a GitHub Release with auto-generated notes by itself - no manual git tag/git push needed. See CLAUDE.md for the full flow.

License

MIT - permissive and compatible with HACS; contributions and forks are welcome.

Releases

Used by

Contributors

Languages