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.
- Connect over Bluetooth LE or USB CDC.
- Send
{"cmd":"api.info"}to confirm the firmware responds and to readapi,fw_ver, anddev_type. - Send
{"cmd":"api.limits"}and use the returned limits for the current session. - Send
{"cmd":"dev.status"}to read the current ND, mode, lock state, battery, charging state, and LCminiFX percentage where supported. - Use
ctrl.set,ctrl.adjust,ctrl.mode.set, andlock.setfor user actions. - Use
cal.readfor 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
| Product | dev_type | Control value range | Percentage control |
|---|---|---|---|
| LCminiND | LCminiND | 0 to 240 | No |
| LCcineND | LCcineND | -24 to 144 | No |
| LCminiFX | LCminiFX | 0 to 240 | Yes, 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 + 96nd = 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
| Product | Public API value | Internal value | Displayed ND | Displayed stops |
|---|---|---|---|---|
| LCminiND / LCminiFX | nd:0 | 96 | ND 0.6 | 2 stops |
| LCminiND / LCminiFX | nd:48 | 144 | ND 0.9 | 3 stops |
| LCminiND / LCminiFX | nd:240 | 336 | ND 2.1 | 7 stops |
| LCcineND | nd:-24 | 72 | ND 0.45 | 1.5 stops |
| LCcineND | nd:0 | 96 | ND 0.6 | 2 stops |
| LCcineND | nd:144 | 240 | ND 1.5 | 5 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
| Item | UUID | Direction |
|---|---|---|
| Service | 4ed4587e-5172-429c-8505-8cbe9c4f231f | Primary service |
| RX characteristic | 523d6915-0ac2-40f6-a423-7d5b0a413d9b | App writes request |
| TX characteristic | ac4493cf-becf-42de-9e3b-3ea076249af7 | Device 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"}
| Reason | Meaning |
|---|---|
bad_json | Request did not parse as a supported JSON object. |
unknown_cmd | cmd was parsed but is not recognized. |
bad_value | A required field is missing or a value is invalid. |
range | A numeric value is outside product limits. |
not_supported | The command exists but is unsupported on this product. |
not_implemented | Reserved command name recognized but not implemented yet. |
storage | A persistent storage operation failed. |
busy | Reserved for busy-state handling. |
usb_mode_required | USB JSON command rejected because USB command mode is not active. |
btle_mode_required | Bluetooth 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 voltage | API battery value |
|---|---|
4170 mV or higher | 100 |
4136 mV | 95 |
4062 mV | 90 |
3980 mV | 85 |
3891 mV | 70 |
3829 mV | 60 |
3749 mV | 40 |
3691 mV | 25 |
3665 mV | 10 |
3633 mV | 7 |
3601 mV | 3 |
3550 mV | 1 |
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
| Command | Example request | Use |
|---|---|---|
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
| Command | Example request | Use |
|---|---|---|
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
| Command | Example request | Use |
|---|---|---|
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.
| Command | Example request | Use |
|---|---|---|
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 command | Example request | Response 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.
| Command | Format | Purpose |
|---|---|---|
| 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.limitsafter every connection. - Do not show percentage controls unless
api.limitsreturnspct:true. - Do not send another command until the previous response has been received.
- Do not treat
not_supportedas a broken connection. It may simply mean the connected product does not support that feature. - Do not use calibration values as control values.
cal.readis for diagnostics only. - Do not rely on legacy commands for new integrations.
Implementation checklist
- Call
api.info, thenapi.limits, thendev.statusafter connecting. - Store
dev_type,nd_min,nd_max, and percentage capability for the session. - Clamp UI controls locally, but still handle firmware
rangeerrors. - Hide percentage controls unless
api.limitsreturnspct:true. - Treat
not_supportedas 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 returnednextvalue untilmore: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.
