npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

homebridge-cycle-light-switches

v1.1.0

Published

Homebridge plugin that adds configurable virtual on/off lights, each paired with a configurable number of virtual single-press switches that fire in a round-robin sequence every time the light is turned on.

Readme

homebridge-cycle-light-switches

A Homebridge plugin that adds any number of virtual on/off lights. Every light is paired with a configurable number of virtual, single-press switches. Each time the virtual light is turned on — from the Home app, Siri, or an automation — the plugin fires the next switch in a round-robin rotation. Point HomeKit automations at each switch's "Single Press" trigger to chain further actions off a light being turned on, cycling through a different action every time.

Built from the official Homebridge plugin template, using the Homebridge Plugin API.

Why?

The basic problem to solve is using a trigger (e.g. a wall switch) to trigger different actions (e.g. different light scenes/scenarios) in sequence. The first time you hit the a switch you might want to set a mood, the second time blaze all lights max, and finally turn everything off. And the following time start the cycle again.

I used to achieve this by nested if/else in Shortcuts in Apple Home, but after switching to Matter over Thread, the delay of seconds for running a shortcut became very apparent, and annoying. So this plugin runs the logic without having to slow everything down with Shortcuts.

How?

This plugin implements these HAP services:

  • Light → a HAP Lightbulb service exposing only the On characteristic (no brightness/color) — an on/off-only light.

  • Switch → by default, a HAP StatelessProgrammableSwitch service, restricted to only ever report a SINGLE_PRESS event — HomeKit's stateless "button" accessory type, which is what Home/Homebridge calls a Generic Switch, configured for single press only. When a light has more than one switch, they're grouped under a ServiceLabel service, which is the standard HomeKit pattern for exposing several buttons on one accessory. These switches are read-only: you can react to them, but nothing can trigger one directly, since a stateless switch has no "on" state to set.

  • Stateful switch (statefulSwitches: true) → an ordinary HAP Switch service instead — the same service type used for a plain HomeKit switch accessory. Because it's a regular settable on/off characteristic, a scene, automation, or a tap in the Home app can turn a specific switch on directly, jumping the rotation to it. This is how you let something other than "turn the light on" pick which step comes next. Exactly one switch per light is ever on — whichever one the rotation is currently sitting on — and it stays on until the rotation moves elsewhere, rather than pulsing back off a moment later. That matters for scenes in particular: a Home scene can only capture and re-apply explicit target states, so a switch that reverted itself a moment later made the scene that had just set it look "off" again, even though the jump it triggered had already worked — staying on keeps the scene's own displayed state honest too. As a bonus, each one is also individually renameable in the Home app, unlike the grouped "Button 1, Button 2, ..." stateless switches (which Home hardcodes those labels for, regardless of the name configured here).

    Stateful switches live on their own standalone accessory each — one accessory per switch, not shared with the light or with each other. That's deliberate, for two reasons. First, Home collapses any accessory exposing more than one controllable service into a secondary picker screen instead of a direct-tap tile, so the switches can't share the light's own accessory without dragging the light into that screen too. Second — and this is the part that isn't obvious until you hit it — a Switch service has no naming characteristic beyond its plain Name. If several switches shared one accessory instead (as an earlier version of this plugin did), Home's automation picker falls back to labelling every one of them with that shared accessory's name, making them indistinguishable when you go to pick "which switch" for an action. A standalone accessory per switch has no such ambiguity: the accessory's name is the switch's name, everywhere in Home. The tradeoff is one extra paired accessory per switch, which does clutter up the room view. If that bothers you, each switch accessory can be individually hidden from Home's main screen — its own accessory settings have a "Add to Home View" toggle — without affecting its availability to automations or Siri; you just won't see a tile for it the Home View. An option to also keep them out of room views: assign all of a light's switches to a dedicated room (e.g. "Hidden" or "Automation") that has no other accessories shown in the Home View — a room with nothing visible in it doesn't get listed as a room there at all, so the whole room (and everything in it) disappears from the tab bar rather than needing each accessory hidden individually. They're still fully there for automations either way. Stateless switches don't have either problem: a StatelessProgrammableSwitch never renders as a controllable tile in the first place, so it's harmless to leave several of them nested inside the light's own accessory.

Installation

npm install
npm run build

Then either:

  • npm link and add it to a local Homebridge instance's node_modules, or
  • publish it to npm and install it normally (npm install -g homebridge-cycle-light-switches).

Configuration

Add a platform block to Homebridge's config.json (or use the plugin's settings UI, which reads config.schema.json):

{
  "platforms": [
    {
      "platform": "CycleLightSwitches",
      "name": "Cycle Light Switches",
      "lights": [
        {
          "name": "Kitchen Scene",
          "switchCount": 3,
          "resetCountOnOff": false
        },
        {
          "name": "Spotlights",
          "switchCount": 3,
          "resetCountOnOff": true,
          "turnOffOnCycleComplete": true
        },
        {
          "name": "Front Door Chime",
          "switchCount": 2,
          "resetCountOnOff": true,
          "switchNames": ["Front Door Chime First", "Front Door Chime Second"]
        },
        {
          "name": "Evening Routine",
          "switchCount": 3,
          "resetCountOnOff": true,
          "statefulSwitches": true
        }
      ]
    }
  ]
}

Options (per light)

| Option | Type | Default | Description | | --- | --- | --- | --- | | name | string | — | Display name of the light. Must be unique. Renaming a light creates a new HomeKit accessory (and resets its switch counter), since the accessory identity is derived from the name. | | switchCount | integer | 1 | How many switches accompany this light. | | resetCountOnOff | boolean | false | See below. | | turnOffOnCycleComplete | boolean | false | See below. | | statefulSwitches | boolean | false | See below. | | switchNames | string[] | — | Optional custom names for each switch, in order. Falls back to "<Light Name> Switch <n>" (stateless) or "<Light Name> Cycle <n>" (statefulSwitches: true) — "Switch" is avoided there since these are settable HomeKit switches, easily confused with a real wall switch for the same light. |

Behavior

Turning the light on — regardless of whether it was already on (e.g. an automation re-sends "turn on" while it's already on) — advances an internal counter and fires exactly one switch: the counter modulo switchCount. Turning the light off never fires a switch.

resetCountOnOff: false (default) — the rotation never resets. With 3 switches, turning the light on four times in a row fires switches 1, 2, 3, 1, in that order, no matter how many times the light was turned off in between.

resetCountOnOff: true — turning the light off resets the rotation back to the start. With 3 switches, turning on twice fires 1, 2; turning the light off; turning it on twice more fires 1, 2 again — i.e. the full sequence is 1, 2, (off), 1, 2.

turnOffOnCycleComplete: true — as soon as the switch that completes a full rotation fires (e.g. switch 3 of 3), the plugin turns the virtual light off on its own, immediately, as if it were a momentary trigger rather than something you leave on. This is useful to combine an automation, e.g. a physical switch trigger to cycle scenes, with being able to control the lights with buttons in the Home app (see Advanced cycle below). This combines with resetCountOnOff: if both are enabled, both manual and automatic off resets the rotation, so the next "on" whether from trigger or button press, always starts the cycle from switch 1.

statefulSwitches: true — switches become settable, so something other than the light itself can pick which step fires next, and each one moves to its own standalone accessory, named for that switch specifically (e.g. "Kitchen Scene Cycle 2") — see "How?" above for why they can't share an accessory. They default to "Cycle" rather than "Switch" in their name, since a settable, named Switch accessory reads too much like an actual wall switch for the same light — "Kitchen Scene Switch 2" next to a real "Kitchen Switch" invites mix-ups that "Cycle" avoids (override it per switch with switchNames if you'd rather call them something else). Turning a specific switch on directly (from a scene, an automation action, or a tap in the Home app) jumps the rotation to it — regardless of what the rotation was doing before — so the next time the light turns on, it fires the switch that follows the one you triggered. Triggering a switch directly also turns the light itself on, if it wasn't already — the same as if the rotation had reached that step on its own — so a scene that jumps to a step also brings the light along with it, rather than leaving it looking off while a switch quietly claims to be "active". For example, with 3 switches, triggering Cycle 1 directly turns the light on and fires nothing further by itself; turning the light on again afterward is what then fires Cycle 2, even if the rotation had already moved past Cycle 1 long ago. Whether a switch fired because the light turned on or because it was triggered directly, it stays on — and every other switch for that light turns off — until the rotation moves on again, so whichever switch is "on" at any moment tells you exactly which step the rotation is sitting on. If resetCountOnOff also fires (the light turns off with that enabled), every switch turns off too, since no step is "current" until the next "on". The reverse also holds: turning off the switch currently on — directly, the same way you'd turn it on — turns the light off too, since that's the one saying "there's an active step"; turning off any other (already-off) switch does nothing, since it was never the one representing the current step. This also honors resetCountOnOff the same way a direct light-off would.

Toggling statefulSwitches for a light you've already set up in Home doesn't lose the light's own history: the light keeps its accessory (and its rotation position) either way, and only the switches move in or out of their own standalone accessories.

Other than a reset, the rotation position is kept indefinitely: it's stored in Homebridge's accessory cache on disk, so it survives Homebridge restarts. It only resets to zero if you explicitly enable resetCountOnOff, or if the accessory is removed (e.g. by renaming the light or deleting it from the config).

Example automations

Simple cycle

  1. In the Home app, create an automation (or a button effect) that turns on "Kitchen Scene".
  2. On "Kitchen Scene" there are Button 1, Button 2, Button 3. On Button 1, run scene A or set lights as required. Repeat for Switch 2 → scene B, Switch 3 → scene C.
  3. Every time you (or an automation) turn on "Kitchen Scene", it cycles through scenes A, B, C, A, B, C, ...

Advanced on/off scenes

Used for light scenes when the last step is always turn off, and both automation triggers and manual control is used in combination.

  1. In the Home app, set one or several automation triggers to turn on "Spotlights".
  2. On "Spotlights" there are Button 1, Button 2, Button 3. Use Button 1 and Button 2 to control lights (or any devices) for two distinct scenes.
  3. Leave Button 3 empty, instead create an automation that turns off your scene/lights when "Spotlights" is turned off.

Since turnOffOnCycleComplete is true, the scene/lights will turn off anyway on the third "turn on". They will also turn off when manually turning off "Spotlights" in the app, and resetCountOnOff prepares for the next automated trigger or manual "turn on" of "Spotlights" in the app to trigger the first switch (i.e. Button 1).

Jumping to a specific step

Set statefulSwitches: true on a light to let an automation or scene pick which step runs next, instead of always advancing to "whatever's next". For example, with a 3-switch "Kitchen Scene" light:

  1. Enable statefulSwitches for "Kitchen Scene".
  2. Create an automation: some condition (e.g. a specific wall switch, time of day, or sensor) sets "Kitchen Scene Cycle 2" to on — it's its own accessory, listed separately from "Kitchen Scene" itself and from the other switches. This also turns "Kitchen Scene" itself on, the same as if the rotation had reached step 2 normally.
  3. The next time anything turns "Kitchen Scene" on — a different automation, or the Home app — it fires Cycle 3, not whatever the rotation would otherwise have been on. Triggering a switch directly always determines what the following "on" does.

This is useful when you want an external condition to steer the cycle (e.g. "if it's after sunset, the next toggle should jump straight to the night scene") without having to also fire that scene's own action a second time. Since the switch you jump to stays on until the rotation moves past it, the scene that set it reads back as staying on/active too — it won't flip back to "off" moments later the way a momentary trigger would.

Development

  • npm run watch — build, npm link the plugin, and run Homebridge with nodemon for live reload (requires a local Homebridge config; see homebridge-plugin-template's docs for setting up test/hbConfig).
  • npm run lint — lint with ESLint.