Skip to content

Latest commit

 

History

History
167 lines (128 loc) · 6.82 KB

File metadata and controls

167 lines (128 loc) · 6.82 KB

Pulse-Eight logo

libCEC — Node.js binding

A native N-API addon that binds libCEC through its C API (include/cecc.h), the same surface the .NET binding (src/dotnetlib) uses. It lets Node.js applications control CEC-capable HDMI devices — power a TV on/standby, become the active source, send remote keys, read device state — and receive bus events (log, key presses, commands, source changes, alerts).

It works anywhere libCEC and a C++ toolchain do: Linux, macOS, Raspberry Pi and Windows, over the Pulse-Eight USB-CEC adapter or a SoC-native CEC backend.

Requirements

  • libCEC installed with its development files. On Unix these are discovered via pkg-config (pkg-config --exists libcec) — on Debian/Ubuntu that is the libcec8-dev package; or build and make install this repository first.
  • Node.js ≥ 16 and a C++17 toolchain (node-gyp prerequisites: a compiler, make, and Python 3).

Install

npm install libcec

The package carries sources, not a prebuilt binary: installing runs node-gyp rebuild, which compiles src/addon.cc against the libCEC already on the machine. The requirements above have to be met first, or the install fails at that step. Being built on the consumer's machine is also what lets one package serve every platform and every Node ≥ 16.

Build from this repository

cd src/nodejs
npm install        # runs node-gyp rebuild, compiling src/addon.cc against libcec
node example/simple.js

The compiled addon lands at build/Release/cec_native.node.

Windows

Windows has no pkg-config, so tell node-gyp where libCEC's headers and its cec.lib import library are with two environment variables (defaults point at an installed USB-CEC Adapter SDK). From a repo build:

set LIBCEC_INCLUDE_DIR=..\..\include
set LIBCEC_LIB_DIR=..\..\build\Release\x64\lib
npm install

At runtime the addon needs cec.dll on the DLL search path — keep it next to cec_native.node (a .node resolves its dependencies from its own directory). The x64 Windows installer ships a prebuilt addon set up this way under its nodejs folder, so end users don't need a compiler; see docs/README.windows.md. The x86 installer does not — Node.js has had no 32-bit Windows build since v23, so there is no node.lib to link a 32-bit addon against.

Test client

client/cec-client.js is an interactive REPL modelled on the C++ cec-client: the Node counterpart for exercising the library against real hardware and a worked example of every API call. Run it with npm run client (or node client/cec-client.js [port], or the installed cec-client-node bin):

$ npm run client
opening /dev/ttyACM0 ...
connection opened. type 'h' for help, 'q' to quit.
scan
...
tx 1F:82:10:00      # transfer raw bytes
on 0                # power on the TV
pow 0               # query the TV's power status
q

node client/cec-client.js --help lists the command-line options (--list-devices, --info, --type, --monitor, --log-level, …).

Usage

const cec = require('libcec'); // or require('.') from this folder

const adapter = new cec.CecAdapter({
  deviceName: 'CECNode',
  deviceType: cec.CecDeviceType.RecordingDevice,
});

adapter.on('log', (m) => console.log(m.message));
adapter.on('keyPress', (k) => console.log('key', cec.userControlKeyToString(k.keycode)));
adapter.on('command', (c) => console.log('cmd', cec.opcodeToString(c.opcode)));

adapter.open(); // or adapter.open(port) with a path from detectAdapters()

adapter.powerOnDevices(cec.CecLogicalAddress.TV);
for (const addr of adapter.getActiveDevices())
  console.log(cec.logicalAddressToString(addr), adapter.getDeviceOSDName(addr));

adapter.close();

Events

CecAdapter is an EventEmitter. libCEC fires its callbacks from its own worker thread; the addon marshals each one onto the Node event loop via a ThreadSafeFunction, so handlers run on the main thread like any other event.

event argument(s) source callback
log { message, level, time } logMessage
keyPress { keycode, duration } keyPress
command { initiator, destination, opcode, parameters, ack, eom, opcodeSet, transmitTimeout } commandReceived
sourceActivated (logicalAddress, activated) sourceActivated
alert alert (a CecAlert) alert
configurationChanged { deviceName, deviceTypes, physicalAddress, logicalAddresses, cecVersion, adapterType, firmwareVersion, … } configurationChanged
menuStateChanged state (a CecMenuState) menuStateChanged
commandHandler same shape as command commandHandler (opt-in)

commandHandler is off by default — pass { enableCommandHandler: true } to the constructor to receive it. It routes every command through libCEC's blocking command-handler path and carries the same data as the cheaper command event, so it's only worth enabling if you specifically need that hook.

Methods

Lifecycle: open(port, timeout=10000), close().

Control: transmit(command), powerOnDevices(address), standbyDevices(address), setActiveSource(deviceType), setInactiveView(), volumeUp(), volumeDown(), muteAudio(), sendKeypress(destination, key, wait=true), sendKeyRelease(destination, wait=true), setOSDString(destination, duration, message).

Queries: getActiveSource(), isActiveSource(address), getDevicePowerStatus(address), getDeviceVendorId(address), getDevicePhysicalAddress(address), getDeviceCecVersion(address), getDeviceOSDName(address), getActiveDevices(), pollDevice(address), rescanDevices(), pingAdapters(), detectAdapters(), getLibInfo().

Enum helpers (module-level): cecVersionToString, powerStatusToString, logicalAddressToString, vendorIdToString, opcodeToString, userControlKeyToString, plus the enum tables (CecLogicalAddress, CecDeviceType, CecPowerStatus, CecUserControlCode, CecOpcode, …).

Notes / limitations

  • commandHandler and menuStateChanged cannot suppress libCEC's default handling from JavaScript. Both are dispatched on libCEC's callback thread and expect a synchronous "handled?" return; the addon always answers "not handled" so the library keeps its default behaviour. Honouring a JS return would mean blocking that thread on the Node event loop and racing libCEC's 1000ms timeout. They are therefore exposed as observe-only events.
  • Call close() when done. It stops libCEC's worker thread before releasing the thread-safe callbacks, so no event can fire against a torn-down adapter.

Licence

GPL-2.0-or-later, or a commercial licence from Pulse-Eight — the same dual licence as libCEC itself.