Home Assistant integration for Moonside smart lighting (Halo and other models), driven over Bluetooth LE using the documented Nordic UART command set.
The integration is write-only: the lamp does not acknowledge commands, so the
state shown in Home Assistant is optimistic (assumed_state) — it reflects the last
command sent. If you also control the lamp with its physical buttons or the Moonside
app, Home Assistant's view can drift.
- A single
lightentity per lamp: on/off, brightness (HA 0–255 is scaled to the lamp's 10–100 range) and RGB colour. - A quick effect list on the light card with baked-in colours (see
const.py). - Service
moonside.set_theme— run any theme with your own colours. The number of colours each theme uses is validated against the catalog. - Service
moonside.set_pixel— set a single addressable LED (0–171). - Service
moonside.set_backled— set one of the two back-light sections. - Service
moonside.send_command— send any raw ASCII command to the lamp. - A diagnostic firmware version sensor (read once on connect), also shown as the device's firmware version on its device page.
- The light reports unavailable when no Bluetooth proxy can see the lamp.
Colour and effect are mutually exclusive, just like on the device itself: choosing a colour clears the active effect, and applying an effect/theme while the lamp is on briefly powers it off first — otherwise the previous colour or effect bleeds through and tints the new one on the device.
- Home Assistant 2024.8 or newer.
- A working Bluetooth adapter or an ESPHome Bluetooth proxy within range of the lamp, with active connections enabled (the integration writes over BLE).
- The lamp advertises as
MOONSIDE-…. It only advertises while not connected to another device, so disconnect any phone/app before adding it.
Click the button above to add this repository to HACS directly, then install Moonside and restart Home Assistant. Or do it manually:
- HACS → three-dot menu → Custom repositories.
- Add this repository's URL with category Integration.
- Install Moonside and restart Home Assistant.
- Settings → Devices & services. The lamp is discovered automatically
(
MOONSIDE-*), or add it via Add integration → Moonside.
Copy custom_components/moonside/ into your Home Assistant config/custom_components/
folder and restart.
Run a theme with colours you choose. Target the light entity, pick a theme, and pass as many colours as that theme uses.
action: moonside.set_theme
target:
entity_id: light.moonside_o101
data:
theme: GRADIENT2
color: [255, 0, 0]
color2: [128, 0, 255]
color3: [255, 255, 255]Colours per theme:
| Colours | Themes |
|---|---|
| 0 (no colour) | RAINBOW3, FIRE1, PALETTE1 |
| speed only | RAINBOW1, RAINBOW2 (use the speed field) |
| 2 | THEME1, THEME2, THEME4, THEME5, COLORDROP1, GRADIENT1, GRADIENT3, TWINKLE1, PULSING1, BEAT2, WAVE1, M1, M3–M7, M9, M12 |
| 3 | GRADIENT2, BEAT1, BEAT3, LAVA1, M2, M8, M10, M11 |
| 4 | FIRE2 |
| 6 | THEME3, PALETTE2 |
Extra colours are ignored; too few returns an error.
Set a single addressable LED. The strip has 172 LEDs, numbered 0–171.
action: moonside.set_pixel
target:
entity_id: light.moonside_o101
data:
index: 42
color: [255, 0, 128]Set one of the two back-light sections (1 or 2).
action: moonside.set_backled
target:
entity_id: light.moonside_o101
data:
section: 1
color: [50, 50, 50]Send a raw ASCII command, for anything outside the structured services.
action: moonside.send_command
target:
entity_id: light.moonside_o101
data:
command: "THEME.GRADIENT1.255,0,0,128,0,255,"- Write-only. No acknowledgement or notification is returned, so state is
assumed, not read back. The read characteristic only returns
version|mac. - State survives restarts. The last assumed state (on/off, brightness, colour, effect) is restored when Home Assistant restarts — the lamp keeps its own state while HA is down, so no command is re-sent. If you control the lamp only through Home Assistant this stays accurate; mixing in the physical buttons or the Moonside app can still make the restored view drift.
- Colours are raw RGB. There is no per-lamp calibration, gamma or white-balance correction, so a value that looks right on a calibrated lamp (e.g. Hue) will be close but rarely identical here.
- Connection handling. The BLE connection is opened on demand, reused, and dropped after 30 s idle so a shared proxy slot is freed. The first action after an idle period reconnects first, which can feel briefly unresponsive.
- Availability. The entity follows Bluetooth presence: it goes
unavailablewhen no proxy has seen the lamp's advertisements for a while (for example when the lamp is powered off or out of range), and returns as soon as it is seen again. - Addressable LEDs / back sections.
PIXEL(172 addressable LEDs,0–171) andBACKLED(two back sections) are exposed viaset_pixelandset_backled. The docs describe this hardware but don't state which models have it, and the lamp returns no error or acknowledgement, so behaviour on a model without an addressable strip is unverified.
Command set: https://developer.moonside.design. This project bundles no Moonside artwork or assets; it only uses the publicly documented BLE command set and nominative brand references.
This is an unofficial, community-built integration. It is not affiliated with, endorsed by, or supported by Moonside. "Moonside" and any related names, logos, and product names are trademarks of their respective owner and are used here only for nominative, descriptive purposes (to identify the hardware this integration talks to).
The integration is provided "as is", without warranty of any kind. It talks to the lamp over its documented BLE command set; use it at your own risk. For issues with this integration, use its issue tracker — not Moonside support.
Released under the MIT License. © 2026 Dion Tavenier.