LCcommand API

LCcommand API v1.0 – June 2026

The LCcommand API lets third-party software talk to compatible KipperTie LC electronic filter devices.

It can be used to read device status, set ND values, change fine/step mode, lock or unlock controls, read battery state, and access read-only calibration diagnostics where supported.

Apps can connect over Bluetooth LE, or over USB CDC on products where USB command mode is available. Commands are sent as small JSON messages, and the device replies with JSON.

This page is intended for developers building camera-control tools, browser apps, desktop software, or hardware controllers. It is not needed for normal use of the product.

Compatibility

Bluetooth LE connectivity is available on all LCminiND, LCminiFX, and LCcineND devices.

USB CDC is available on LCcineND devices as standard, and can be enabled by request on LCminiND and LCminiFX.

Firmware version 1.5.3 or later is required for the public v1 API described here. Legacy Bluetooth commands will work with prior versions but are not officially supported for third-party use.

Fast integration flow

Start here: For a new integration, do not begin by sending control commands. First identify the device, read its limits, then read its current state. This prevents your app from assuming the wrong ND range, showing unsupported controls, or sending values that only apply to another LC product.

  1. Connect over Bluetooth LE or USB CDC.
  2. Send {"cmd":"api.info"} to confirm the firmware responds and to read api, fw_ver, and dev_type.
  3. Send {"cmd":"api.limits"} and use the returned limits for the current session.
  4. Send {"cmd":"dev.status"} to read the current ND, mode, lock state, battery, charging state, and LCminiFX percentage where supported.
  5. Use ctrl.set, ctrl.adjust, ctrl.mode.set, and lock.set for user actions.
  6. Use cal.read for read-only calibration diagnostics where required.

Do not hard-code product limits in your app. Call api.limits after every connection and show percentage controls only when the returned pct field is true.

Supported products

Productdev_typeControl value rangePercentage control
LCminiNDLCminiND0 to 240No
LCcineNDLCcineND-24 to 144No
LCminiFXLCminiFX0 to 240Yes, pct 0 to 99

Product capabilities may differ between firmware versions. Always use api.limits after connecting rather than assuming these limits in your app.

How ND and diffusion values map to internal values

All products expose mode as fine or step. The nd value used by this API is the public control value shown to software, not a DAC value, calibration value, or raw motor position.

Internally, the firmware uses an offset of 96. To convert between public API values and internal calibration-table positions:

  • internal_value = nd + 96
  • nd = internal_value - 96

The current calibration table has 481 entries, indexed from 0 to 480. The product range is a supported window inside that table. For example, LCminiND uses public nd values from 0 to 240, which correspond to internal calibration positions 96 to 336. LCcineND uses public nd values from -24 to 144, which correspond to internal positions 72 to 240.

The public nd value is also the value used by the KipperTie app control scale. It is not the same number as the displayed optical density. One public nd code step represents 0.00625 optical-density units. The displayed optical density can be calculated as:

displayed_ND = (nd + 96) / 160

In stop terms, because 0.3 ND is one stop:

displayed_stops = (nd + 96) / 48
ProductPublic API valueInternal valueDisplayed NDDisplayed stops
LCminiND / LCminiFXnd:096ND 0.62 stops
LCminiND / LCminiFXnd:48144ND 0.93 stops
LCminiND / LCminiFXnd:240336ND 2.17 stops
LCcineNDnd:-2472ND 0.451.5 stops
LCcineNDnd:096ND 0.62 stops
LCcineNDnd:144240ND 1.55 stops

In fine mode, the device can move by one public nd code step at a time. In step mode, the device snaps to the product’s supported fractional-stop positions. For third-party software, the safest approach is still to use api.limits, show the public nd range returned by the connected device, and optionally display a derived ND or stops label using the formula above.

LCminiFX also exposes diffusion/FX control as pct. The public API range is 0 to 99. Internally, this percentage is mapped across the same 240-code control span used by LCminiFX:

internal_value = 96 + floor(pct * 240 / 99)
nd = internal_value - 96

If your user interface shows this as a 1 to 100 percent display, convert between the UI and API values explicitly:

api_pct = ui_percent - 1
ui_percent = api_pct + 1

For example, a UI value of 100% should be sent to the API as pct:99. Do not send pct:100; it is outside the public API range.

Calibration values returned by cal.read are diagnostic table values. They are not public ND values and should not be used directly as user-facing controls.


Transport options

Bluetooth LE

Use the custom GATT service. Write one JSON request to RX and read one JSON response from TX notification.

USB CDC

Send one JSON object over the serial command path. JSON responses are returned as one object followed by a newline.

Request size

Keep JSON requests below 240 bytes. Responses are designed for the same 240-byte firmware response buffer. Commands that return larger data sets, such as cal.read, use chunked responses.

Bluetooth LE UUIDs

ItemUUIDDirection
Service4ed4587e-5172-429c-8505-8cbe9c4f231fPrimary service
RX characteristic523d6915-0ac2-40f6-a423-7d5b0a413d9bApp writes request
TX characteristicac4493cf-becf-42de-9e3b-3ea076249af7Device notifies response

The RX and TX characteristics use authenticated access. Pairing may request the passkey engraved on the device or the default 123456.

USB CDC framing

For JSON commands, send the JSON object and read until newline. Whitespace is accepted, and semicolon terminators are tolerated. For browser apps, use Web Serial. For desktop tools, use a serial library such as pyserial. A baud value of 115200 is a safe default for tools that require one.


Request and response format

A request is a JSON object with a cmd string.

{"cmd":"api.info"}

Standard success

{"status":"ok","cmd":"api.info","api":1,"fw_ver":"1.5.3","dev_type":"LCminiND"}

Standard error

{"status":"err","cmd":"ctrl.set","reason":"range"}
ReasonMeaning
bad_jsonRequest did not parse as a supported JSON object.
unknown_cmdcmd was parsed but is not recognized.
bad_valueA required field is missing or a value is invalid.
rangeA numeric value is outside product limits.
not_supportedThe command exists but is unsupported on this product.
not_implementedReserved command name recognized but not implemented yet.
storageA persistent storage operation failed.
busyReserved for busy-state handling.
usb_mode_requiredUSB JSON command rejected because USB command mode is not active.
btle_mode_requiredBluetooth JSON command rejected because Bluetooth command mode is not active.

Battery percentage and device display

The battery field returned by dev.status is a calculated battery percentage. It is not the raw battery voltage. The firmware measures battery voltage, applies the board’s voltage-divider correction, then converts the result into a percentage for the API.

The API percentage is calculated from voltage thresholds, with integer interpolation between the points. At or above 4170 mV, the API reports 100. At or below the bottom of the mapped range, the API reports 1.

Battery voltageAPI battery value
4170 mV or higher100
4136 mV95
4062 mV90
3980 mV85
3891 mV70
3829 mV60
3749 mV40
3691 mV25
3665 mV10
3633 mV7
3601 mV3
3550 mV1

The device display uses a separate coarse battery indicator derived from the same voltage thresholds. This means the display icon and the API percentage are related, but they are not the same value. The display indicator is deliberately coarser and includes smoothing behaviour while discharging, so it may not change at exactly the same moment as the API percentage.

The charging field is reported separately as true or false. Do not infer charging state from the battery percentage alone.


Public v1 command catalogue

Commands fall into three groups:

  • Discovery and status commands read information from the device and do not change its state.
  • Control commands change ND, percentage, mode, lock state, or device name.
  • Service or reserved commands are not intended for normal third-party control apps unless KipperTie has specifically asked you to use them.

Discovery and status

CommandExample requestUse
api.info{"cmd":"api.info"}Read API version, firmware version, and device type.
api.limits{"cmd":"api.limits"}Read product ND range, step support, and percentage capability.
dev.status{"cmd":"dev.status"}Read ND, mode, lock, battery, charging, and LCminiFX percentage.
ctrl.get{"cmd":"ctrl.get"}Read current control state without battery fields.

Control

CommandExample requestUse
ctrl.set ND{"cmd":"ctrl.set","nd":48}Set ND directly. Returns range if outside limits.
ctrl.set with clamp{"cmd":"ctrl.set","nd":999,"clamp":true}Clamp ND to product min/max and return limited:true when clamped.
ctrl.set percent{"cmd":"ctrl.set","pct":50}Set LCminiFX percentage. Other products return not_supported.
ctrl.adjust ND{"cmd":"ctrl.adjust","unit":"nd","delta":1}Adjust ND by a positive or negative delta.
ctrl.adjust percent{"cmd":"ctrl.adjust","unit":"pct","delta":5}Adjust LCminiFX percentage. Other products return not_supported.

Mode, lock, configuration, and system

CommandExample requestUse
ctrl.mode.get{"cmd":"ctrl.mode.get"}Read fine or step.
ctrl.mode.set{"cmd":"ctrl.mode.set","mode":"fine"}Set mode to fine or step.
lock.get{"cmd":"lock.get"}Read lock state.
lock.set{"cmd":"lock.set","state":"toggle"}Set lock on, off, or toggle.
cfg.name.get{"cmd":"cfg.name.get"}Read stored Bluetooth name.
cfg.name.set{"cmd":"cfg.name.set","bt_name":"LCminiND"}Store Bluetooth name. Use 1 to 28 characters, with no quotes, backslashes, or control characters.
sys.reboot{"cmd":"sys.reboot"}Send response first, then reboot.

Calibration read

The cal.read command is a read-only diagnostic command for inspecting calibration data over Bluetooth LE or USB CDC. It does not write, restore, or modify calibration data.

CommandExample requestUse
cal.read{"cmd":"cal.read","table":"current","offset":0,"count":12}Read a chunk from the current calibration table.
cal.read legacy{"cmd":"cal.read","table":"legacy","offset":0,"count":12}Read a chunk from the legacy calibration table when available.

The table field is optional and defaults to current. Accepted values are current and legacy. The offset field is optional and defaults to 0. The count field is optional and defaults to 12.

Responses are capped at 12 values per request so they remain within the firmware response buffer. To read a full calibration table, keep calling cal.read with offset set to the returned next value until more is false.

Calibration read response

{
  "status":"ok",
  "cmd":"cal.read",
  "table":"current",
  "available":true,
  "entry_count":481,
  "offset":0,
  "count":12,
  "more":true,
  "next":12,
  "migrated":true,
  "values":[7913,7913,7913,7913,7913,7913,7913,7913,7913,7913,7913,7913]
}

The current calibration table contains 481 entries. The legacy table contains 241 entries when available. If no readable legacy table remains after an upgrade or migration, the command still returns status:"ok", with available:false, count:0, and an empty values array. Treat this as a normal condition rather than a transport or command failure.

Third-party tools should use api.info first to confirm API version and device type, use cal.read only for diagnostics or verification, avoid requesting more than 12 values at once, handle bad_value for unsupported table names, and handle range for invalid offsets.

Reserved v1 placeholders

These command names are recognized and return {"status":"err","reason":"not_implemented"}: preset.list, preset.save, preset.load, preset.delete, ramp.start, ramp.stop, ramp.status, cal.status, cal.write, cal.restore, event.subscribe, event.unsubscribe, cfg.passkey.set, cfg.usb.set, sys.factory_reset, and ota.status.

Legacy JSON compatibility

Legacy commands remain supported but use older response shapes. New integrations should prefer v1 commands.

Legacy commandExample requestResponse shape
get_nd{"cmd":"get_nd"}{"nd":N}
set_nd{"cmd":"set_nd","nd":48}{"status":"ok"} or {"status":"err"}
get_bt{"cmd":"get_bt"}Battery percentage and charging boolean.
get_fw_ver{"cmd":"get_fw_ver"}Firmware version and device type.
get_bt_name{"cmd":"get_bt_name"}Stored Bluetooth name.
set_bt_name{"cmd":"set_bt_name","bt_name":"LCminiND"}Status response, with Bluetooth name on success.

USB-only service commands

These commands are service and manufacturing commands, not normal public API commands. Do not expose them in third-party user interfaces unless KipperTie has specifically approved that use case. Incorrect use may change device configuration or calibration data.

CommandFormatPurpose
Port enable-cmd port enable;Enable the USB command port record.
Port disable-cmd port disable;Store disabled command-port state.
Set passkey-cmd passkey 123456;Store a fixed six-digit pairing passkey.
Set Bluetooth name-cmd set bt_name "LCminiND";Store Bluetooth name.
Write reference calibration-w,v0,v1,...,v160;Write a 161-value source calibration table.

Minimal examples

Browser Bluetooth

const SERVICE = '4ed4587e-5172-429c-8505-8cbe9c4f231f';
const RX = '523d6915-0ac2-40f6-a423-7d5b0a413d9b';
const TX = 'ac4493cf-becf-42de-9e3b-3ea076249af7';
const enc = new TextEncoder();
const dec = new TextDecoder();

const device = await navigator.bluetooth.requestDevice({
  filters: [{ services: [SERVICE] }],
  optionalServices: [SERVICE]
});
const server = await device.gatt.connect();
const service = await server.getPrimaryService(SERVICE);
const rx = await service.getCharacteristic(RX);
const tx = await service.getCharacteristic(TX);

await tx.startNotifications();
tx.addEventListener('characteristicvaluechanged', event => {
  console.log(JSON.parse(dec.decode(event.target.value)));
});

await rx.writeValueWithResponse(enc.encode(JSON.stringify({ cmd: 'api.info' })));

Browser USB CDC

const enc = new TextEncoder();
const dec = new TextDecoder();
const port = await navigator.serial.requestPort();
await port.open({ baudRate: 115200 });

const writer = port.writable.getWriter();
await writer.write(enc.encode(JSON.stringify({ cmd: 'api.info' }) + '\n'));

const reader = port.readable.getReader();
let buffer = '';
while (!buffer.includes('\n')) {
  const { value, done } = await reader.read();
  if (done) break;
  buffer += dec.decode(value, { stream: true });
}
console.log(JSON.parse(buffer.trim()));

Python USB CDC

import json
import serial

with serial.Serial('/dev/ttyACM0', 115200, timeout=2) as ser:
    ser.write(b'{"cmd":"api.info"}\n')
    response = json.loads(ser.readline().decode('utf-8'))
    print(response)

Reading a full calibration table

async function readCalibration(sendCommand) {
  const values = [];
  let offset = 0;

  while (true) {
    const response = await sendCommand({
      cmd: 'cal.read',
      table: 'current',
      offset,
      count: 12
    });

    if (response.status !== 'ok') {
      throw new Error(response.reason || 'cal.read failed');
    }

    if (!response.available) {
      return [];
    }

    values.push(...response.values);

    if (!response.more) {
      return values;
    }

    offset = response.next;
  }
}

Common mistakes to avoid

  • Do not hard-code ND ranges. Use api.limits after every connection.
  • Do not show percentage controls unless api.limits returns pct:true.
  • Do not send another command until the previous response has been received.
  • Do not treat not_supported as a broken connection. It may simply mean the connected product does not support that feature.
  • Do not use calibration values as control values. cal.read is for diagnostics only.
  • Do not rely on legacy commands for new integrations.

Implementation checklist

  • Call api.info, then api.limits, then dev.status after connecting.
  • Store dev_type, nd_min, nd_max, and percentage capability for the session.
  • Clamp UI controls locally, but still handle firmware range errors.
  • Hide percentage controls unless api.limits returns pct:true.
  • Treat not_supported as product capability feedback, not as a fatal connection error.
  • Send one request at a time and wait for one response before sending the next request.
  • For Bluetooth, subscribe to TX notifications before writing to RX.
  • For USB CDC JSON, read until newline.
  • For cal.read, iterate using the returned next value until more:false.
  • Gate persistent writes, reboot actions, and service commands behind clear user confirmation.

Disclaimer

This public API is provided for third-party integration, testing, and development use. It is supplied as-is and may be revised as KipperTie products and firmware develop.

Third-party software is responsible for validating user input, checking api.info and api.limits for each connected device, handling errors safely, and testing behaviour on the specific product and firmware version before use in production.

KipperTie is not responsible for loss of footage, production delays, equipment damage, configuration changes, calibration issues, connection failures, or other losses caused by third-party software, unsupported firmware, incorrect commands, misuse of service commands, or failure to follow this guide.

Do not use this API for safety-critical functions. Service, calibration-write, factory-reset, passkey, and reserved commands should not be exposed to end users unless KipperTie has specifically approved that integration.

For integration questions, firmware compatibility, or access to USB CDC on LCminiND or LCminiFX, contact KipperTie support.