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

iobroker.xibo

v0.4.1

Published

Control Xibo digital signage displays: change the layout playing on a display group, revert to schedule, and read display status.

Readme

ioBroker.xibo

NPM version Downloads Number of Installations Current version in stable repository

Tests: Test and Release

Play a layout on a Xibo display group, and put it back to its schedule.

Anything in ioBroker that can write a state — a button, a script, a scene, a VIS widget — can put a layout on a display group, and hand it back to the schedule afterwards.

Xibo is an open-source digital signage platform, of which this drives the CMS.

What it does

  • Inventory — 23 CMS collections mirrored on a timer, from display groups and layouts to datasets, sync groups and player versions
  • Control — change layout, overlay layout, revert to schedule, collect now
  • Status — how many displays in a group are online, and what they report playing
  • Anything else — any of the CMS's 263 API operations, via sendTo

Layouts are the unit of control. Media in the CMS library cannot be scheduled on its own; a layout is what a display can be told to show.

Calling the rest of the API

The state tree covers everyday control of what displays show. The CMS exposes far more — dataset editing, widget and region layout, user administration — and modelling all of it as states would be thousands of states for jobs better done in the Xibo UI. So anything not modelled is still one call away:

// Returns the response body.
const res = await sendToAsync("xibo.0", "api", {
    method: "GET", path: "/layout", params: { retired: 0 },
});
if (res.ok) log(`${res.result.length} live layouts`);

// Arrays become the repeated key[] form the schedule endpoints require.
await sendToAsync("xibo.0", "api", {
    method: "GET", path: "/schedule", params: { displayGroupIds: [3, 4] },
});

commands.api does the same from a state — write {"method":"POST","path":"/tag","params":{"name":"reception"}} — but a state cannot hand a response back to whoever wrote it, so the body lands in commands.lastResult. Prefer sendTo when you need the answer.

Paths are resolved and checked to stay under /api, and methods are limited to GET, POST, PUT and DELETE. Beyond that the passthrough is as powerful as the credentials it is given — a DELETE /layout/{id} really does delete a layout — so scope the CMS application accordingly.

One thing the CMS gets wrong

GET /campaign returns an empty array on a CMS holding campaigns, because every layout's own single-layout campaign is excluded by the default filter. Ask for ?isLayoutSpecific=-1 — the adapter's own campaigns collection does.

Configuration

| Field | Notes | |---|---| | CMS URL | The CMS root, without /api | | Client ID / Secret | Administration → Applications in the CMS. The application must allow the client_credentials grant. | | Layout folder | Only offer layouts in this folder and below. Leave empty for the whole CMS. | | Default duration | Seconds a layout stays before the display returns to its schedule. 0 means until reverted. |

The adapter authenticates as an application, not as a person, so CMS folder permissions do not constrain it — those govern people using the Xibo UI.

State tree (xibo.0.*)

info

  • info.connection — the CMS is reachable and the credentials work. Driven by the status poll, and needs two consecutive failures before it goes false, so a single timed-out request is not reported as a disconnection.
  • info.lastError — the outstanding error, whichever poll produced it
  • info.lastSync, info.cmsUrl

inventory

One inventory.<name>Json state and one count per mirrored collection, chosen under Mirrored CMS collections. Seventeen are on by default:

displayGroups, displays, layouts, campaigns, playlists, datasets, templates, tags, resolutions, displayProfiles, dayParts, folders, cmsCommands, syncGroups, menuBoards, notifications, playerVersions

Six are available but off: library and modules are large, venues is a fixed lookup list, and users, userGroups and sessions hold personal data — mirroring those copies names, email addresses and who is signed in out of the CMS's access control into states any script can read.

The first three keep the state ids they had in 0.2.0 (displayGroupCount, not displayGroupsCount), so an upgrade neither loses them nor orphans them. Unticking a collection deletes its states rather than leaving a stale value behind, and unticking everything mirrors nothing — display groups and layouts are still read, because the object tree needs them.

Per display group

xibo.0.displayGroups.<sanitised name>, with native.displayGroupId carrying the CMS id. Display-specific groups — Xibo's internal one-per-display groups — are omitted, because they are never what an operator picks.

| State | Notes | |---|---| | id, name | CMS identity | | displayCount, displaysOnline | How many displays, and how many are logged in | | currentLayout | What the first display in the group reports playing | | playLayoutId | Writable. Write a layout id to play it on this group. | | revert | Writable. Return this group to its schedule. |

playLayoutId is the state to bind a button or script to.

Commands

Callers write JSON to commands.<name> un-acked; the adapter executes it and clears the state, so an identical follow-up request still triggers.

| Command | Payload | |---|---| | refresh | boolean button | | changeLayout | {displayGroupId, layoutId, duration?} | | overlayLayout | {displayGroupId, layoutId, duration?} — refused in schedule mode | | revertToSchedule | {displayGroupId} | | collectNow | {displayGroupId} | | pushCriteria | {displayGroupId, metric, value, ttl?}, or {displayGroupId, updates:[…]} |

commands.lastResult records {ok, command, payload, error?, ts}.

Schedule Criteria

pushCriteria sends metric values to a display group, which is the other way to change what a display shows. Instead of naming a layout, the CMS schedule holds several standing entries each gated on a <criteria> condition, and the player picks the one whose condition its current metric values satisfy.

The point is that nothing outside the CMS names a layout. A button pushes screen1=3; which content that is lives in the schedule. So replacing a layout with a new one cannot leave a button pointing at something that no longer exists.

Three things to know:

  • The CMS stores no value. It only forwards the push to the players over XMR, so there is no readback. What was last sent is a weaker claim than what a display is showing.
  • A player that does not implement criteria fails open, not closed. It sees every gated entry as unconditional, so four entries gated eq 1..4 all match and the display rotates through all four. Nothing errors.
  • ttl is a floor, not a deadline. A push re-evaluates the schedule the moment it lands, but an expiry is only noticed at the player's next minute tick. To take content off at a definite time, push a new value rather than let one lapse. The default ttl is 12 hours, so a display holds what an operator chose until they choose again.

How a layout reaches the player

Setting: "How a layout reaches the player" — schedule (default) or action.

The CMS's own changeLayout action is delivered over XMR and applied instantly — by a player that implements that message. Arexibo and gaxibo do not. The action arrives, is logged as an unsupported XMR action and dropped, while the CMS reports success. Nothing moves and nothing fails, which is the worst way for this to break.

So schedule mode does not use the action. It:

  1. resolves the layout's campaign (the schedule names a campaign, never a layout);
  2. deletes the event it created last time on that display group;
  3. creates one "always" layout event at schedulePriority, which outranks the group's ordinary schedule;
  4. sends collectNow — the one XMR action these players do implement, which is what makes the change land in seconds rather than at the next poll.

A duration becomes a custom day part bounded by toDt, which the player enforces locally, so the layout comes down on time without hearing from the CMS again.

revertToSchedule in this mode deletes that event rather than posting the XMR revert action, because the event is the thing overriding the schedule.

Two constraints follow:

  • schedulePriority must be a priority nothing else uses. The CMS gives a schedule event no name or tag to stamp, so the priority is the only marker available for "the adapter owns this one and may replace it".
  • overlayLayout is refused in schedule mode, rather than attempted. These players render no overlay by either route, so posting the action would report success and show nothing.

Pick action only for the official Xibo player, where it is instant.

currentLayout lags

displayGroups.<group>.currentLayout is what the player last reported, and gaxibo reports it from a field its GUI thread updates asynchronously — so the collect that applies a new layout usually still reports the previous one. Measured at anything from 4 seconds to 5 minutes behind. It is a status field, not a confirmation that a command worked, and binding a button's active indicator to it will light the wrong button.

Notes

A layout change replaces the schedule and stays until something else changes it or the group is reverted — changeMode: replace with no duration, so what was pressed stays on screen rather than expiring unexpectedly. Set a default duration if you want the opposite.

downloadRequired is set, so the player fetches the layout before showing it rather than flashing an empty screen while it downloads.

Folder scoping covers the subtree. The CMS folderId filter matches one folder exactly, so scoping to a root folder alone finds nothing when layouts sit in subfolders below it. The adapter walks the folder tree and filters against it.

Changelog

0.4.1 (2026-10-01)

  • Published through CI with npm provenance.
  • The 0.4.0 release notes shown in admin are corrected in every language. The machine translation had changed state and setting ids such as commands.lastResult and schedulePriority, and cut some sentences short.

0.4.0 (2026-10-01)

Behaviour changes — read these before upgrading.

  • Command payloads are checked strictly. Ids must be positive integers, given as a number or a numeric string. true, null and "" used to be turned into 1 or 0, so {"displayGroupId": true} targeted display group 1; they are now refused and commands.lastResult records ok:false. A duration of true or "" is refused too, while a missing or null one still means the configured default. A criteria ttl must be a positive number — a blank ttl used to expire the push at the next minute tick instead of using the 12-hour default.
  • schedulePriority is rounded to a whole number. A fractional priority meant the adapter could never find its own schedule events again, so they piled up instead of being replaced.
  • defaultChangeDuration is capped at 86400 seconds in code as well as in admin, since a larger value sent the CMS an invalid date.

Fixes

  • Display-group states created by an earlier version now receive updated definitions, and one deleted by hand is recreated on the next start.
  • A command cut short by the instance stopping — saving the config restarts it — no longer logs an error or writes commands.lastResult from a stopping adapter.
  • Two display groups whose names fold to the same object id can no longer end up sharing a branch.
  • The collection picker in admin is translated, and several admin strings that had been mistranslated are corrected.
  • A new icon that reads as a screen at small sizes.

0.3.0 (2026-09-03)

  • Schedule Criteria (CMS 4.1+). commands.pushCriteria sends metric values to a display group — {displayGroupId, metric, value, ttl?}, or {displayGroupId, updates:[…]} to batch several into one request. Standing, criteria-gated schedule entries in the CMS then decide which content a value selects, so nothing outside the CMS names a layout and republishing cannot invalidate a button. Note that the CMS stores no value and the player reports none, so there is no readback; and a ttl is a floor, not a deadline — expiry is only noticed at the player's next minute tick.

Behaviour changes — read these before upgrading.

  • A duration that is not a number is now refused instead of ignored. Number("30s") is NaN and Number("") is 0, and both used to mean "no duration", so {"layoutId":41,"duration":"30s"} booked an indefinite play and still recorded ok:true. It now throws, commands.lastResult records ok:false, and the error names the field. If a button or script sends a duration with units in it, fix the payload — that button was not doing what it appeared to do before.

  • info.connection means something slightly different. It now stays false until a status poll has actually succeeded, and needs two consecutive status-poll failures to go false again. A single timed-out request no longer reports a disconnection, and a failing inventory refresh no longer does either — that goes to info.lastError and the log instead. Watchdogs gating on this state will see a slightly longer window after a restart, and far fewer spurious disconnects.

  • Scheduled layouts survive a DST change. The CMS's UTC offset was read once and kept for the life of the instance, so an adapter running since summer booked every event an hour out after the October change — the display changing an hour late, or a timed layout never appearing at all, with ok reported both times. It is now re-read hourly.

  • commands.lastResult now describes a failure the same way as a success. A failing write recorded command: "commands.changeLayout" with the raw string it was sent, where a working one recorded command: "changeLayout" with the parsed payload — so a script matching command === "changeLayout" to see whether its own press worked matched every success and no failure, and reported a failed press as still pending. Both paths now use the short name and a parsed payload.

  • Object names and roles now reach upgraded instances, not just fresh installs. Adapter-owned metadata is merged with extendObject instead of being written only when missing, so this release's renamed inventory channel and count labels appear on existing instances too. Your own history and InfluxDB settings on those states are left alone, and a display-group channel you have renamed keeps your name.

  • A display group renamed in the CMS keeps its branch. The branch id is folded from the group name, so a rename used to produce a second branch on the next restart while the old one stayed behind for ever, frozen at its last counts and looking live — and a button still writing the old displayGroups.<old name>.playLayoutId hit a state that existed and looked healthy while nothing happened. Branches are now matched on the CMS id, so the rename follows the name state and existing bindings keep working. A group deleted from the CMS has its states zeroed once and stops updating, and writing to it now fails visibly in commands.lastResult instead of only logging a warning.

  • A standing failure is logged once, not every poll. An application scoped without Layout access, or an estate that has never used menu boards, answers 403 for ever — which used to put the same line in the log 288 times a day and re-raise admin's "errors in the log" notice every five minutes. The first occurrence is logged, repeats go to debug, a different failure still reports, and recovery is logged so the log says when it stopped.

  • A display group you renamed in admin keeps your name. The previous fix compared the CMS's name against the channel's label, which made a rename in admin indistinguishable from a rename in Xibo — so the adapter reverted your name on the next restart and logged a CMS rename that had never happened. The CMS name is now recorded separately in native.displayGroup, and only a genuine CMS rename moves the label.

  • A duplicate branch left by 0.2.0 no longer wins. 0.2.0 created a second branch after a CMS rename, both carrying the same displayGroupId. The first fix adopted whichever the database returned first — deterministically the older, dead one — leaving the branch your bindings had been moved to unindexed, where every press failed with "not in the CMS any more". The branch whose recorded CMS name matches now wins, and any leftover is zeroed and named in a warning so you can delete it.

  • Unticking every collection now mirrors nothing. An empty list and an absent setting were treated the same, so clearing all 23 entries and saving left the adapter mirroring the 17 defaults anyway — 34 states recreated and 14 extra CMS requests every five minutes — while the config screen showed nothing selected. An empty list is now honoured; the adapter still drives display groups and layouts either way, since those are fetched for the object tree rather than for the mirror.

New

  • inventory.* now mirrors 23 CMS collections, not three: campaigns, playlists, datasets, templates, tags, resolutions, display profiles, day parts, folders, CMS commands, sync groups, menu boards, notifications and player versions are on by default, and the media library, widget modules, venues, users, user groups and sessions can be turned on. Pick them under "Mirrored CMS collections". Unticking one deletes its states rather than leaving a stale value behind.

  • Any CMS operation can now be called, for the parts of the platform the state tree does not model:

    const layouts = await sendToAsync("xibo.0", "api", {
        method: "GET", path: "/layout", params: { retired: 0 },
    });

    commands.api does the same from a state, with the response in commands.lastResult. Paths are held to /api and methods to GET/POST/PUT/DELETE.

  • Release tooling: npm run release now drives the version bump, so package.json, io-package.json and this changelog cannot drift apart.

  • Integration test that starts the adapter under a real js-controller.

0.2.0

  • Layout changes now reach Arexibo and gaxibo players. Those players do not implement the CMS's changeLayout XMR action — it arrives, is logged as unsupported and dropped, while the CMS reports success — so a layout change did nothing and nothing failed. The new default schedule mode books a priority schedule event and sends collectNow instead, which every player honours. Verified through to a live display.
  • layoutPlayMode setting: schedule (default, works on any player) or action (instant, official Xibo player only).
  • schedulePriority setting, which is also the marker for the events the adapter owns and may replace.
  • revertToSchedule deletes the adapter's own schedule event in schedule mode, rather than posting an XMR action the player would ignore.
  • overlayLayout is refused in schedule mode rather than silently doing nothing, since those players render no overlay by either route.

0.1.0

  • Initial release: display group and layout inventory, and change layout / overlay layout / revert to schedule / collect now commands.

Older changes have been moved to CHANGELOG_OLD.md

Requirements

  • Node.js 22 or newer, js-controller 6.0.11 or newer and admin 7.6.20 or newer.
  • A Xibo CMS (developed against 4.5) reachable from ioBroker.
  • An Application in the CMS under Administration -> Applications, with the client_credentials grant enabled. The adapter authenticates as that application, not as a person, so scope it to what you want it to reach — the API passthrough is as powerful as the credentials it is given.
  • At least one layout and one display group. Media in the library cannot be scheduled on its own.

Player compatibility

schedule mode, the default, works with any player because every player honours its own schedule. Use it unless you have a reason not to.

action mode posts the CMS's changeLayout XMR action, which is instant but is silently ignored by players that do not implement it — the CMS still reports success. Arexibo and gaxibo are in that category. Gaxibo is a fork of Arexibo that plays video through GStreamer so the hardware decoder is used, built for Rockchip RK3399 players. Only choose action with the official Xibo player.

License

MIT — see LICENSE.

Copyright (c) 2026 Alan Paris [email protected]