Data sources and channels

Added: after 2026-10-R1

The Data sources window shows a card for each source, built from sections like the device panel. The standard sections (js/base/sources.js) work for every source; a phy module adds its own for the capture drivers it knows, such as the Wi-Fi channel layout or a cell modem’s DIAG capture (js/phy/cell.js).

Picking sources

The data source registries pick the sources each entry applies to with either:

  • drivers: ['linuxwifi', ...]: the capture driver types, from the kismet.datasource.driver.type field, or
  • match(source): any test of the source record.

With both, a source must have one of the drivers and pass the test. kismet.ui.driverOf(record) reads the driver type from a source or an interface record.

Source sections

kismet.ui.sourceSections holds the sections of a source’s card.

Option
id, title, priorityThe section
drivers or matchThe sources it’s for
fieldsRows, as field entries
render(source, el, ctx)Or draw the section yourself; called again about once a second with fresh data, so update in place
openStart expanded (default true)
runningOnly show it while the source is open and running
when(source, type)A further test, given the source’s sourceTypes entry

A field entry’s draw(cell, value, source, state, ctx) gets the window’s ctx last, for controls:

ctx
ctx.sourcesEvery source, as last read
ctx.busy(promise, text)Covers the card with text until promise settles
ctx.refresh()Reads the sources again now
ctx.message(text, kind)A note under the card’s title ('error', 'ok', or plain)

The cell module’s section for DIAG capture, shortened:

const diagField = (s, k) => s[`${diag}.${k}`];
const isDisabled = (s) => diagField(s, 'mask_preset') === 'disabled';

ui.sourceSections.add({
    id: 'celldiag', title: 'DIAG capture', priority: 5, drivers: ['celldiag'],
    fields: [
        { title: 'Bring-up', value: (s) => (isDisabled(s) ? null : bringupText(s)),
            help: 'Opening a modem takes several steps; this says how far it got, and why it failed.' },
        { title: 'Last observation', value: (s) => (isDisabled(s) ? null :
            (diagField(s, 'last_obs_epoch') ? ago(diagField(s, 'last_obs_epoch')) : muted('None yet'))) },
        { title: 'DIAG read', value: (s) => (isDisabled(s) ? null : format.size(diagField(s, 'bytes_read') ?? 0)) },
        // ...
    ],
});

kismet.ui.sourceFields adds rows to a section someone else owns, the same way deviceDetailFields does for the device panel: an id, the section, a priority, and the field entry.

Source types

kismet.ui.sourceTypes says what a kind of source is, for the window’s titles and lists of interfaces. The first match, by priority, is used.

Option
id, priority, drivers or matchThe entry
iconThe icon for the card
subtitle(source, ctx)Short text after the source’s name, such as a modem’s model
channels: falseHides the standard channel section, for sources whose channels aren’t radio channels
interfaceTitle(intf), interfaceSubtitle(intf)The title, and text after it, for an available interface of this driver
enableForm(intf, ctx)Returns a node with the options asked for before enabling an interface, in place of a plain Enable button
interfaces(list, ctx)Adds to or changes the listed interfaces, for drivers whose lister can’t see a busy device
sortKey(intf)Orders the interfaces (default: by interface name)

The cell module’s entry, shortened:

ui.sourceTypes.add({
    id: 'cell', drivers: cellDrivers, icon: 'tower', channels: false,
    subtitle: (s) => {
        const parsed = parseLabel(sourceLabel(s));
        return parsed && (parsed.name || parsed.firmware) ? rowTitle(parsed, driverOf(s)) : captureLabel[driverOf(s)];
    },
    interfaceTitle: (intf) => rowTitle(parseLabel(intf['kismet.datasource.probed.hardware']), driverOf(intf)),
    interfaces: (list, ctx) => augmentInterfaces(list, ctx.sources),
    sortKey: (intf) => sortKey(intf['kismet.datasource.probed.interface']),
    enableForm: (intf, ctx) => enableForm(intf, ctx),
});

Channel pickers

kismet.ui.channelPickers decides how a source’s channels are shown and chosen in the standard channel section. The first match, by priority, is used; the generic picker, a button for each channel, matches every source. The Wi-Fi module’s picker lays channels out by band (js/phy/dot11_channels.js).

Option
id, priority, drivers or matchThe entry
create(el, ctx)Builds the picker in el; returns { update(source, state), destroy() }

state is the section’s shared hop and lock state:

state
state.mode'hop' or 'lock'
state.channelsThe source’s channels
state.currentThe channel a locked source is on
state.selectedA Set of the channels to hop on
state.pendingTrue while a change is waiting to be sent
state.toggle(channel), state.lock(channel), state.setSelected(list)Change them

The generic picker, shortened:

ui.channelPickers.add({
    id: 'generic', priority: 1000,
    create(node) {
        const buttons = new Map();
        const grid = el('div', { class: 'ds-chan-grid' });
        node.append(grid);
        let st = null;
        return {
            update(s, state) {
                st = state;
                if (state.channels.join('\n') !== grid._key) {
                    grid._key = state.channels.join('\n');
                    buttons.clear();
                    grid.replaceChildren(...state.channels.map((c) => {
                        const b = el('button', { type: 'button', class: 'ds-chan', onclick: () => st.toggle(c) }, c);
                        buttons.set(c, b);
                        return b;
                    }));
                }
                for (const [c, b] of buttons) {
                    b.classList.toggle('on', state.mode === 'hop' ? state.selected.has(c) : c === state.current);
                    b.classList.toggle('pending', state.pending);
                }
            },
        };
    },
});

Channel names

Frequencies are shown by channel name wherever the UI can, such as in the channel coverage window, from kismet.ui.channelNames: one entry for each phy which names its channels.

Option
idThe phy name, such as 'IEEE802.11'
titleA short name, such as 'Wi-Fi'
name(khz)The channel at a frequency, or null when it isn’t one of this phy’s
frequency(channel)Optional: the frequency of a channel, in kHz, or null

Where channel plans overlap, as Wi-Fi and Bluetooth LE do at 2.4 GHz, the lowest priority names the frequency and the others are listed with it. The Bluetooth LE module names its 40 channels:

// BLE channels: 37, 38, and 39 advertise; 0-36 carry connections
const bleFrequency = (channel) => {
    const n = Number(channel);
    if (!Number.isInteger(n) || n < 0 || n > 39)
        return null;
    const mhz = n === 37 ? 2402 : n === 38 ? 2426 : n === 39 ? 2480 :
        (n <= 10 ? 2404 + n * 2 : 2428 + (n - 11) * 2);
    return mhz * 1000;
};

ui.channelNames.add({ id: 'BTLE', title: 'BLE', priority: 10, name: bleChannel, frequency: bleFrequency });

Source channels

Data sources report their channels in their own terms (6, 36HT40+, 5W6e, 37), so kismet.ui.sourceChannels says how to turn a source’s channels into frequencies.

Option
id, priorityThe entry
match(source)Whether it applies to a source record
frequency(channel)The channel’s frequency, in kHz, or null
span(channel)Optional: { low, high } in kHz, for a wide channel
titleOptional: a short name for the channels, or title(channel) returning one; the channel coverage window labels the frequencies these sources listen on with it and their own channel, such as 802.15.4 12 rather than the Bluetooth LE channel at the same frequency
ui.sourceChannels.add({
    id: 'btle',
    match: (src) => /nrf|cc2540|sniffle|ble|wch/i.test(src['kismet.datasource.driver.type'] ?? ''),
    frequency: bleFrequency,
});

The 802.15.4 entry (js/phy/802154.js) has priority -10, so it comes ahead of the Bluetooth LE entry, which also matches some of the same sources, and names its channels with title:

const bleAdvertising = { 37: 2402000, 38: 2426000, 39: 2480000 };
ui.sourceChannels.add({
    id: '802154', priority: -10,
    title: (channel) => (bleAdvertising[Number(channel)] ? 'BLE' : '802.15.4'),
    match: (src) => /^(ticc2531|nrf52840|rzkillerbee|catsniffer_zigbee|freaklabszigbee|nxp_kw41z|zigbee900sdr)$/i
        .test(src['kismet.datasource.driver.type'] ?? ''),
    frequency: (channel) => channelFrequency(channel) ?? bleAdvertising[Number(channel)] ?? null,
});

Channels which are plain frequencies (2412, 433.92MHz) work without an entry.