Skip to content

Configuration Reference

github-actions[bot] edited this page Oct 25, 2026 · 34 revisions

Configuration Reference

timps reads a single flat key = value file (default /etc/timps.conf, overridable with -c <path>) — see timps.conf.example / scripts/camera.conf for worked examples. There is no INI-style [section] syntax; the "section" is simply the dotted prefix of the key (image., audio., video0., ...).

This page documents every user-facing key recognized by src/config.c, grouped by section, with type, default, valid range, and — importantly — whether the key can be changed live via POST /control (see HTTP /control API Reference) or is persist-only (the value is stored/echoed but only takes effect the next time timpsd restarts).

How to read the "Live?" column

Value Meaning
Live A /control POST applies the value to the running pipeline immediately (subject to per-SoC hardware support — see Platform & SDK Support) and persists it to the config file.
Live (next cycle) Not applied via a direct HAL call, but the owning thread (recorder, timelapse, day/night) re-reads the live in-memory config on every loop iteration, so the change takes effect within a few hundred ms to a few seconds without a restart.
Restart-only The key is only read once at startup (or at ISP/encoder init). A /control POST for one of these still updates the in-memory config and persists it to the file (when the section accepts POSTs at all — see below), but the running daemon keeps behaving as it did at boot; the new value takes effect on the next timpsd restart.
File-only The key has no /control POST path at all — it can only be changed by editing the config file (or via an offline tool that calls config_write_keys()) and restarting. Some of these are still included in GET /control's read-only dump; others are not readable back at all (noted individually).

This classification is derived directly from src/control.c's control_apply_json() (which sections/keys it accepts) and src/config.c's descriptor tables (which keys GET /control/change-detection can read back) — not guessed from names or comments.

Two important caveats:

  • image.* live-apply is per-SoC: every image.* key is accepted live regardless of platform, but the HAL only issues the underlying IMP_ISP_Tuning_* call when that platform's SDK actually exposes it (see the per-key notes below and caps.image in GET /control). On an unsupported platform the value is stored/echoed but has no visible effect.
  • videoN.rtsp_path is a documented exception: GET /control's caps.restart array lists the whole video section as restart-required, but config.h explicitly carves it out (and since v1.9.20 the POST reply no longer lists it under deferred_keys): RTSP DESCRIBE matches the request path against the live config on every request, not the boot snapshot, so a /control edit to rtsp_path actually changes which URL serves that stream immediately, with no restart. Every other video.N.* key (codec/dimensions/fps/bitrate/...) genuinely needs a restart because the encoder/FrameSource pipeline is never reconfigured live.

sensor.* — camera sensor

Restart-only; the sensor is probed and configured once at ISP init (config_sensor_finalize()). Not reachable via /control at all (no "sensor" handling in control_apply_json beyond being listed for completeness — actually it is accepted for persistence, see below). Note: config_sensor_finalize() auto-detects unset fields from /proc/jz/sensor/sensor0/* and a config value that disagrees with the loaded kernel sensor driver is overridden (with a warning) rather than trusted, because a mismatching name/i2c address would crash the ISP driver.

Key Type Default Range Live? Description
sensor.model string (unset → auto-detected, fallback gc2053) — Restart-only Sensor driver name; auto-filled from /proc/jz/sensor/sensor0/name if present.
sensor.i2c_addr (alias i2c_address) int (unset → auto-detected, fallback 0x37) 0–0x7F Restart-only Sensor I2C address.
sensor.fps int (unset → driver max_fps, capped at 30 or the fastest enabled video<N>.fps if higher; else video0.fps, else 25) 0–120 Restart-only Sensor capture frame rate, applied once at ISP init. The auto value is capped at 30, or at the fastest enabled video<N>.fps if that is higher; set it explicitly to go higher. The log line sensor fps: requested N, driver holds n/d shows what the driver accepted. Not the same as video<N>.fps.
sensor.width int (unset → auto/video0.width, fallback 1920) 0–8192 Restart-only Sensor native width.
sensor.height int (unset → auto/video0.height, fallback 1080) 0–8192 Restart-only Sensor native height.
sensor.i2c_adapter (alias i2c_adapter_id) int -1 (unset → 0) -1–15 File-only T40/T41 only. I2C adapter the sensor is wired to.
sensor.mclk int -1 (unset → 0) -1–2 File-only T40/T41 only. Sensor clock source, MCLK0/1/2.
sensor.video_interface int -1 (unset → 0) -1–2 File-only T40/T41 only. 0 = MIPI CSI0, 1 = MIPI CSI1 (T40 only, ignored with a WARN on T41), 2 = DVP.
sensor.default_boot int -1 (unset → 0) -1–15 File-only T40/T41 only. Sensor driver boot setting index.
sensor.rst_gpio int -1 (none) -1–255 File-only T40/T41 only. Sensor reset GPIO number.
sensor.pwdn_gpio int -1 (none) -1–255 File-only T40/T41 only. Sensor power-down GPIO number.

sensor.* is accepted by POST /control ({"sensor":{...}}) for persistence — it is listed in caps.restart alongside video, and a changed key comes back in the POST reply's deferred_keys — but has no live-apply path; the value is written to the config file and takes effect at the next ISP init. The six board-wiring keys (i2c_adapter … pwdn_gpio) are the exception: not POST-able, edit /etc/timps.conf and restart. Use them when IMP_ISP_AddSensor fails because the board wires the sensor off I2C adapter 0 / MCLK0 / CSI0 (e.g. Eufy S350: i2c_adapter = 1, mclk = 1, default_boot = 4, rst_gpio = 91). An invalid value is logged and dropped; other SoCs ignore them with a warning.

image.* — ISP tuning

All keys live-apply, gated per-SoC (see caps.image in GET /control and the ISP-capability column below; the underlying macros live in src/isp_caps.h). A value the platform can't apply is still stored and echoed back — it just has no visible effect until/unless a firmware update adds support. image.running_mode is additionally special: re-posting the same value still re-drives the ISP (skipping the flash write) because it is treated as a hardware-sync command, not just stored state — see Day/Night for why, including the fs_kick_chn0() latch-kick this triggers.

Key Type Default Range Live? ISP capability gate Description
image.brightness int 128 0–255 Live all SoCs ISP brightness.
image.contrast int 128 0–255 Live all SoCs ISP contrast.
image.saturation int 128 0–255 Live all SoCs ISP saturation.
image.sharpness int 128 0–255 Live all SoCs ISP sharpness.
image.hue int 128 0–255 Live ISP_HAS_HUE: T23/T31/T40/T41/C100 Hue (SetBcshHue).
image.hflip bool 0 0/1 Live all SoCs (via SetHVFLIP on T40/T41) Horizontal flip. Global (all streams). Use with vflip for a 180° flip on SoCs without a rotation path.
image.vflip bool 0 0/1 Live all SoCs Vertical flip. Global (all streams).
image.running_mode int 0 0 = day, 1 = night Live all SoCs ISP day/night running mode. Normally driven by the Day/Night thread's switch script, not set directly by a user.
image.anti_flicker int 1 (2 up to 1.9.33) 0 = off, 1 = 50 Hz, 2 = 60 Hz Live all SoCs Anti-flicker (banding) compensation. Set it to your mains frequency, see below.
image.ae_compensation int 128 0–255 Live ISP_HAS_AECOMP: all except T21 and T40; T41 only with OpenIMP (since 1.9.33) AE (auto-exposure) compensation/EV bias.
image.max_again int 160 0–255 Live ISP_HAS_GAINS: all except T40/T41 Max analog gain ceiling.
image.max_dgain int 80 0–255 Live ISP_HAS_GAINS Max digital gain ceiling.
image.sinter_strength int 128 0–255 Live ISP_HAS_NR: all except T40; T41 only with OpenIMP (since 1.9.33) Spatial noise reduction strength.
image.temper_strength int 128 0–255 Live ISP_HAS_NR: all except T40; T41 only with OpenIMP (since v1.10.0) Temporal noise reduction strength; on T41 visible only at high gain.
image.dpc_strength int 128 0–255 Live ISP_HAS_DPC: T23/T31/C100; OpenIMP also T10/T20/T21 and T41 (since v1.11.0) Defective pixel correction strength.
image.defog_strength int 128 0–255 Live ISP_HAS_DEFOG: T23/T31/C100; OpenIMP also T20 and T41 (since v1.11.0) Defog strength.
image.drc_strength int 128 0–255 Live ISP_HAS_DRC (WDR): T21/T23/T31/C100; OpenIMP also T20 and T41 (since v1.11.0) Dynamic range compression (WDR) strength.
image.highlight_depress int 0 0–10 Live ISP_HAS_HILIGHT: all except T40/T41 Highlight suppression.
image.colorfx int 0 0–9 Live Colour effect (0 none, 1 B/W, 2 sepia on T20, 3 negative, 9 vivid). Only with an OpenIMP build (USE_OPENIMP); not in caps.image otherwise.
image.scene int 0 0–14 Live Scene preset (SDK IMPISPSceneMode). Only with an OpenIMP build (USE_OPENIMP).
image.backlight_compensation int 0 0–10 Live ISP_HAS_BACKLIGHT: T23/T31/C100 Backlight compensation.
image.core_wb_mode int 0 0–9 (0–8 on T10/T20/T30) Live ISP_HAS_WB: all except T40/T41 White balance mode: 0 auto, 1 manual, 2 daylight, 3 cloudy, 4 incandescent, 5 fluorescent, 6 twilight, 7 shade, 8 warm fluorescent, 9 custom.
image.wb_rgain int 0 0–255 (T10/T20/T30), 0–4095 (T21/T23/T31), 0–65535 elsewhere Live ISP_HAS_WB Manual WB red gain (used when core_wb_mode selects manual). The scale differs per SoC: 128 = 1.0 on T10/T20/T30, 256 = 1.0 on T21/T23/T31. The per-SoC clamp is new in v1.10.0 (up to 1.9.33: 0–65535 everywhere, T10/T20/T30 wrapped values above 255).
image.wb_bgain int 0 same as wb_rgain Live ISP_HAS_WB Manual WB blue gain. Since v1.9.21, GET /control reports the gains in effect as read-only image.wb_live {"rgain","bgain"}, a start value for manual mode that matches auto.

Picking image.anti_flicker

Set it to the mains frequency where the camera hangs:

  • 1 (50 Hz, the default since v1.10.0): Europe, most of Asia and Africa, Australia.
  • 2 (60 Hz): most of the Americas, South Korea, Taiwan, the Philippines, Saudi Arabia, western Japan (eastern Japan is 50 Hz).

Lamps flicker at twice the mains frequency (100 or 120 Hz). Anti-flicker quantises the AE's exposure time to whole half-periods (10 ms at 50 Hz, 8.33 ms at 60 Hz), so every exposure collects the same light whatever the phase. The wrong value steps the exposure in the wrong unit and shows as horizontal bands under LED or fluorescent light, rolling at the beat between the light and the frame rate (5 Hz at 15 fps on 50 Hz mains). A T23 sc2336p on 50 Hz mains measured 1.15 % RMS row ripple at 2 and 0.09 % at 1.

Up to 1.9.33 the default was 2 (60 Hz, the vendor SDK convention that prudynt and raptor also follow), and thingino's shipped /etc/timps.conf writes the key explicitly. An upgraded camera whose config was ever saved therefore stays on 2. A fresh flash, a factory reset, a config that was never saved (it still comes from the image) or one without the key gets 1. In a 60 Hz region, set 2 after such an upgrade or reset:

curl -s -X POST http://127.0.0.1:8880/control -d '{"image":{"anti_flicker":2}}'

It applies live and persists to /etc/timps.conf.

Anti-flicker puts a floor under the exposure. Use 0 (off) for a very bright close-up scene whose exposure visibly pulses: there the AE needs an exposure below the first node, and it oscillates between honouring the node (overexposed) and breaking it.

At sensor.fps = 30 the GC2053 T31 driver publishes a 25 fps line model at probe time, so the ISP scales the nodes by 1.2× and neither 1 nor 2 lands on a true mains period; at sensor.fps = 25 the model is consistent and both settings are correct.

FrontCrop: image.fcrop_* (since v1.10.0)

Key Type Default Range Live? Description
fcrop_enable bool 0 0/1 Live where caps.fcrop.live; restart on T23 (OpenIMP) without the driver's fcrop_upscale_pct parameter ISP FrontCrop ("zoom"): only the window below reaches the streams; each stream scales it to its own size.
fcrop_x int 0 0–8192 as above Left edge, sensor-sized picture pixels (unrotated, as shown with image.hflip/vflip; OpenIMP T21/T23/T31 mirror x and y for the flips, T10/T20 only y).
fcrop_y int 0 0–8192 as above Top edge.
fcrop_w int 0 0–8192 as above Width; at least 64.
fcrop_h int 0 0–8192 as above Height; at least 64.
fcrop_upscale_pct int -1 -1–caps.fcrop.upscale_max (100) Live Since v1.11.0, T23/T31 OpenIMP with the driver parameter only. Writes the open-tx-isp fcrop_upscale_pct so the scaler may upscale the window by up to this many percent per axis (100 = a window down to half the stream size). -1 = leave the driver's value. Never above 100: T31 at 150 hard-hangs. Elsewhere a POST answers unsupported and a file value is ignored with one WARN.

x/y/w/h follow the FrameSource crop convention (left, top); the SDK struct is ordered top, left and the HAL maps it. timps evens the values, moves the window inside the sensor and refuses anything under 64×64; the SDK then refuses a window no stream can show (-EINVAL: caps.fcrop.state = rejected, one WARN; any other error: failed). Keep the stream aspect ratio or the picture is stretched. Device-tested (by the OpenIMP maintainer) on T10, T20 and T21 with OpenIMP — on T20 only the downscaled streams show the window. T23 and T31 with OpenIMP are effective and live when the driver has the open-tx-isp fcrop_upscale_pct parameter (checked at start; open-tx-isp b15ef235 has it). Without it T31 is experimental and T23 experimental and start-only: a POST persists and applies at the next /etc/init.d/S95timps restart; it needs open-tx-isp 3cf9bdac or newer (older drivers hung the pipeline on a crop change). A window smaller than a stream that is not scaled down is refused unless fcrop_upscale_pct allows it; lowering that key with such a window active returns to the full sensor. Vendor T23 refuses the keys. See Platform & SDK Support for all SoCs. Privacy masks, OSD and ROI regions stay in stream-frame coordinates: the scene moves under them.

audio.* — capture, encode, speaker defaults

Split between live-applicable knobs (real-time DSP/gain calls) and persist-only attribute keys (codec/format/pipeline-init settings that IMP_AI_SetPubAttr/encoder init need at bring-up time and cannot change on a running capture channel). The Restart-only keys are listed in GET /control's caps.restart as audio.<key>, and since v1.9.20 a changed one comes back in the POST reply's deferred_keys.

Key Type Default Range Live? Description
audio.volume int 80 0–100 Live Mic capture volume (IMP_AI_SetVol).
audio.gain int 25 0–31 Live Mic capture gain (IMP_AI_SetGain).
audio.alc_gain int 0 0–7 Live on T21/T31/C100 (AUDIO_HAS_ALC_GAIN); accepted/persisted elsewhere with no effect Analog PGA gain level (IMP_AI_SetAlcGain).
audio.mute bool 0 0/1 Live Live mic mute: captured frames are dropped before the encoder/hub (works on every SoC — it's a publish-gate in timps, not an SDK call).
audio.spk_volume int 80 0–100 Live (only if USE_PLAY or USE_BACKCHANNEL compiled in) Speaker (IMP_AO) output volume — applies to whichever producer (backchannel/play queue) currently holds the AO device, and persists as the default for the next one.
audio.spk_gain int 25 0–100 (effective 0–31: IMP_AO_SetGain's range, −39 dB … +6 dB in 1.5 dB steps; higher values are clamped to 31) Live (same gate as spk_volume) Speaker output gain.
audio.enabled bool 1 0/1 Restart-only Master audio capture enable.
audio.codec enum aac aac|pcmu|pcma|opus|none (aliases g711u/ulaw→pcmu, g711a/alaw→pcma, off→none) Restart-only Audio codec. AAC needs USE_FAAC; opus needs USE_STREAM_OPUS (RTSP-only, RFC 7587 — see Audio/Streaming Protocols) and is only an accepted token on such a build. Falls back to AAC at parse time for any unrecognized token.
audio.codec2 enum pcmu on USE_WEBRTC builds, none otherwise pcmu|off/none Restart-only Optional second, independent G.711u encode of the same captured PCM, published on its own hub source. It exists for WebRTC/WHEP, which carries G.711 and nothing else: without it a camera on the default audio.codec = aac gets a video-only WHEP session. RTSP, the fMP4 preview and recordings keep the AAC primary untouched. Ignored (and not encoded) when audio.codec is already pcmu/pcma. Needs an 8 kHz or 16 kHz capture (16 kHz is filtered and halved to G.711's 8 kHz clock); any other audio.samplerate logs a warning and disables the second encode.
audio.samplerate int 16000 8000–96000 Restart-only Capture sample rate in Hz. G.711 is pinned to 8000 Hz regardless of this value (see Audio).
audio.channels int 1 1–2 Restart-only 1 = mono (native). 2 = "simulated stereo" — the mono mic duplicated to L=R, AAC only.
audio.bitrate int 32 8–320 (kbps) Restart-only AAC encode bitrate.
audio.high_pass bool 1 0/1 Restart-only High-pass filter. Not live: libimp runs HPF/AGC/NS on its own record thread and frees state unlocked — a live toggle would race the vendor thread (use-after-free risk), so this is restart-required by design, not merely unimplemented.
audio.agc bool 0 0/1 Restart-only Automatic gain control on/off (same race-avoidance rationale as high_pass).
audio.agc_target_dbfs int 10 0–31 Restart-only AGC target level (dBFS).
audio.agc_compression_db int 0 0–90 Restart-only AGC compression gain (dB).
audio.ns int 0 0–3 Restart-only Noise suppression level (0 = off).
audio.force_stereo bool 0 0/1 Restart-only Separate companion field to channels=2 (not an alias — both are settable independently).
audio.spk_enabled bool 1 0/1 Live (at the next AO open; USE_PLAY/USE_BACKCHANNEL builds) Master speaker-output enable. Read by speaker.c every time it is about to open the IMP_AO device, so 0 silences the next backchannel/play session without a restart; a session already holding the speaker keeps it until it ends.
audio.backchannel bool 0 0/1 Restart-only Enable the ONVIF two-way audio backchannel (USE_BACKCHANNEL build).
audio.backchannel_codec enum pcmu (0) pcmu(0)|pcma(1)|aac(2) Restart-only Advertised/accepted backchannel codec.
audio.backchannel_rate int 16000 8000–48000 Restart-only Speaker sample rate fed by the backchannel decoder.
audio.talk_ws tri-state 0 0|1|2 (true/on/yes → 1) Live (next /talk request; the backchannel it rides on is still restart-only) Serve the browser push-to-talk WebSocket at /talk (USE_BC_WS builds, and only when the backchannel itself came up at boot). httpd.c reads the value on every /talk upgrade request, so a /control POST changes the answer for the next connection immediately — including switching from 1 to 2, which drops the TLS requirement; treat it with the same care as a hand edit. 0 = not served; 1 = served over TLS only, a plaintext port answers 426; 2 = also served over plain ws://, a deliberate hand edit since the microphone audio and its token then cross the network in the clear. GET /control reports the resolved verdict as caps.backchannel.talk_ws (1 on a plaintext port resolves to 0). See Audio.
audio.aec bool 0 0/1 Not live — persists now, engages at the next AO open (only if USE_PLAY or USE_BACKCHANNEL compiled in) Opt-in Acoustic Echo Cancellation (IMP_AI_EnableAec) for the backchannel — subtracts the speaker output from the mic capture. Engages only once both AI capture and AO output are actually live, applied at the next AO open (same timing contract as spk_volume/spk_gain). Off by default since AEC quality/latency varies per SoC/mic/speaker pairing.

See Audio for the backchannel/play-queue feature details.

jpeg.* — dedicated JPEG/MJPEG channel

Entirely file-only: there is no "jpeg" object in the /control POST schema, so these keys can only be changed by editing the config file and restarting. (Note this is distinct from the per-stream piggyback JPEG encoder configured under video.N.jpeg*, below — see Streaming Protocols for how the two JPEG sources relate.)

Key Type Default Range Live? Description
jpeg.enabled bool 0 0/1 File-only Enable the dedicated JPEG encoder channel (independent FrameSource).
jpeg.width int 640 64–4096 File-only JPEG channel width.
jpeg.height int 360 64–4096 File-only JPEG channel height.
jpeg.quality int 75 1–100 File-only JPEG quality.
jpeg.fps int 5 1–120 File-only Max snapshot/MJPEG publish rate for this channel.
jpeg.imp_chn int 2 0–7 File-only IMP encoder channel number (internal). (since v1.9.28) a value that collides with an enabled video stream's channel is moved to the lowest free channel at load, with an [ERR] line.
jpeg.snapshot_path string "" — File-only Optional path to periodically write the latest JPEG to disk ("" = disabled).

rtsp.* — RTSP server

File-only (no "rtsp" object accepted by /control). See Streaming Protocols for the RTSP server's runtime behavior.

Key Type Default Range Live? Description
rtsp.enabled bool 1 0/1 File-only Enable the RTSP server.
rtsp.port int 554 1–65535 File-only RTSP listener port.
rtsp.mtu int 1200 548–1472 File-only Max RTP packet size (header+payload) for UDP transport; 1200 leaves headroom for WireGuard/OpenVPN/PPPoE/IPv6 tunnel overhead — raise to ~1400 on LAN-only setups.
rtsp.user (alias username) string "" — File-only RTSP username. Empty = RTSP auth disabled.
rtsp.pass (alias password) string "" — File-only RTSP password.
rtsp.tls (alias tls_enabled) bool 0 0/1 File-only Also run an RTSPS (TLS) listener (USE_TLS builds).
rtsp.tls_port int 322 1–65535 File-only RTSPS port (RFC 7826 Annex C well-known port).

srt.* — SRT output

File-only. USE_SRT builds only (see Streaming Protocols).

Key Type Default Range Live? Description
srt.enabled bool 0 0/1 File-only Enable the SRT output.
srt.mode string listener listener|caller File-only listener waits for receivers to dial in; caller makes the camera dial srt.host:srt.port itself and reconnect forever with a doubling 1→30 s backoff (for cameras behind NAT). An unrecognized value warns and falls back to listener.
srt.host string "" — File-only Caller mode only: the receiver to dial. srt.mode=caller with this empty is a startup error and SRT stays off (it is not silently downgraded to a listener). Ignored in listener mode.
srt.port int 9000 1–65535 File-only Listener: local bind port. Caller: the remote port dialled on srt.host.
srt.channel int 0 — File-only Video stream served over SRT.
srt.latency_ms (alias latency) int 120 — File-only SRT latency (ms).
srt.streamid string "" — File-only Listener: required STREAMID for a connecting client ("" = not enforced). Caller: the STREAMID timps presents to the receiver.
srt.passphrase string "" — File-only AES passphrase for SRT encryption (10–79 chars if set; an invalid passphrase stops SRT — the listener never binds, the caller stops dialling — rather than running unencrypted). srt.streamid is plaintext, so without a passphrase a listener serves video to anyone regardless of rtsp.user/http.user; (since v1.9.28) that combination logs SRT port N has NO access control - set srt.passphrase (10-79 chars) at start.

http.* — HTTP server (fMP4/MJPEG/snapshot/control/events)

File-only. See HTTP /control API Reference and Streaming Protocols.

Key Type Default Range Live? Description
http.enabled bool 1 0/1 File-only Enable the HTTP server.
http.port int 8880 1–65535 File-only HTTP listener port.
http.preview_chn int 1 — File-only Default video stream for the built-in preview page.
http.adaptive_drop bool 1 0/1 File-only Per-client adaptive frame dropping on /stream.mp4 when a slow client's own queue backs up (freeze-and-resume-at-keyframe instead of forwarding a corrupt headless GOP).
http.user (alias username) string "" — File-only HTTP Basic/Digest username. Falls back to rtsp.user if empty.
http.pass (alias password) string "" — File-only HTTP password. Falls back to rtsp.pass if empty.
http.token string "" — File-only Optional persistent remote secret token for /control//events/media endpoints (never written to the token file).
http.token_file string /run/timps.token — File-only Where the random per-boot token is published (mode 0640); "" disables publishing.
http.https (alias tls) tri-state 0 0/1/2 File-only TLS on the HTTP port (USE_TLS builds). 0 = plaintext only. 1 = both schemes on the same port — each connection is classified by its first byte (0x16 = TLS handshake record, any HTTP method starts with a letter), so http:// and https:// both work; this is wider than the pre-v1.9.11 meaning of 1, which was TLS-only. 2 = TLS only: a plaintext request gets 426 Upgrade Required and is closed (the old meaning of 1). Either on-value still fails closed — a cert/key that cannot be loaded means the listener is not bound at all, never a silent downgrade to plaintext.
http.tls_cert (alias cert) string /etc/ssl/certs/timps.crt — File-only Certificate, PEM or DER (shared with RTSPS). On thingino, S95timps symlinks this to the WebUI's /etc/ssl/certs/uhttpd.crt when that exists, so both ports present the same cert; otherwise it generates a self-signed pair here.
http.tls_key (alias key) string /etc/ssl/private/timps.key — File-only Private key matching http.tls_cert (shared with RTSPS).

Security — empty credentials expose the media (by design). While both http.user and rtsp.user are empty (the shipped default), the media endpoints — the RTSP video/audio stream, /snapshot.jpg, /stream.mjpeg and /stream.mp4 — are reachable by anyone on the network with no authentication. /control and /events are the exception: they stay loopback-only (non-local requests get 403) even with empty credentials, so config/state is never exposed off-device without a token or configured credentials. Set rtsp.user/rtsp.pass and/or http.user/http.pass to require auth for the media too. See HTTP /control API Reference § Empty credentials.

webrtc.* — WebRTC/WHEP endpoint

File-only, and the whole section only exists on USE_WEBRTC builds (which imply USE_TLS + USE_CONTROL). GET /control reports the resolved state as caps.webrtc. The top-level README.md § "WebRTC / WHEP" carries the media contract and its deliberate limitations (LAN/VPN only, H.264 + G.711 only, no NACK/FEC/congestion control, 4 concurrent sessions).

Key Type Default Range Live? Description
webrtc.enabled tri-state 2 0/1/2 (true/on/yes → 1) File-only Serve POST /webrtc/whep. 0 = the endpoint answers 503. 1 = served, but a plaintext POST on a port that has TLS configured is refused with 426 Upgrade Required (same rule as audio.talk_ws=1) — the offer carries the fingerprint the peer is held to and the answer carries our ICE password, so a rewritable exchange hands over the whole session. 2 = served over either scheme. The default is 2 rather than 1 because most cameras have no http→https redirect, so 1 would silently 426 the feature; a port with no TLS configured at all behaves the same either way.
webrtc.port int 0 0–65535 File-only Bottom of the UDP media port range; 0 = one OS-picked ephemeral port per session. Every session binds its own socket, so a fixed value is a range start, not a single port.
webrtc.port_max int 0 0–65535 File-only Top of that range. 0 = webrtc.port + WEBRTC_MAX_SESSIONS - 1 (one port per session slot, so no extra configuration is needed). A range narrower than the 4 slots just answers 503 once every port in it is taken.
webrtc.channel int 0 0–(MS_MAX_VSTREAM-1) File-only Which video stream to send when the WHEP POST carries no ?chn=N (a client picks the stream per session with it). It must be H.264 — there is no H.265 or transcoding over WebRTC. Its video<N>.profile decides which browsers can play the result: Chromium accepts any profile, Firefox only Baseline (0).

Audio over WHEP needs G.711 on the wire: either audio.codec is already pcmu/pcma, or audio.codec2 = pcmu (the USE_WEBRTC default) arms the second encode. With neither, the answer rejects the audio m-section (m=audio 0 …) and the session is video-only.

events.* — Server-Sent-Events push stream

File-only startup settings (USE_CONTROL builds).

Key Type Default Range Live? Description
events.enabled bool 1 0/1 File-only 0 makes /events answer 404.
events.stats_ms int 2000 — File-only Period of the stats SSE event; 0 disables it.
events.max_clients int 8 — File-only Max concurrent /events connections; beyond this, /events answers 503.

osd.* — on-screen display (global settings)

The master switch and the shared OSD infrastructure settings. Per-item overlay fields are documented separately below (osd<S>.<N>.*).

Key Type Default Range Live? Description
osd.enabled bool 1 0/1 Restart-only Master OSD on/off switch, global across all streams. Settable via /control ({"osd":{"enabled":...}}) and persists, but the OSD groups are only ever built once at startup (imp_osd_setup), so the effect needs a restart.
osd.monitor_stream int 0 — Live Which stream's measured fps/bitrate feeds the {fps} and {bitrate} placeholders — for every OSD layer, on every stream. Use the numbered {fpsN}/{bitrateN} forms instead when a layer should show its own stream's figures. Settable via /control ({"osd":{"monitor_stream":...}}); read directly off g_cfg on every OSD text refresh, so a POST applies on the next render, no restart needed.
osd.font_path string /usr/share/fonts/default.ttf — Restart-only Default TTF font for text items without a per-item font_path override. Settable via /control, same restart-required class as osd.enabled.
osd.supersample int 2 1–4 Restart-only TTF rasterizer anti-aliasing quality (samples per axis per pixel); cost scales ~quadratically, 2 is visually close to 4 at typical OSD sizes for roughly a quarter of the CPU cost. Settable via /control, same restart-required class as osd.enabled.
osd.hinting bool 1 0/1 Restart-only (only if USE_OSD_HINTING compiled in) Opt-in lightweight geometric autohint for the TTF rasterizer. The rasterizer (msttf.c) does not execute the font's embedded TrueType hint bytecode (a real hint interpreter is real interpreter-writing work with a real correctness/security surface for an on-device, unsandboxed daemon); at small sizes (e.g. the substream OSD's default 12px) that shows up as visibly uneven stroke widths between glyphs. Enabling this snaps long, near-vertical/near-horizontal outline edges (typical letter stems/serifs) to the pixel grid before rasterizing, which measurably reduces that unevenness — it is a coarse heuristic, not real hinting, and does not preserve the font's authored hint intent. On by default in the runtime config; set it to 0 for byte-for-byte unhinted OSD bitmaps. Verified against the shipped UbuntuMono Regular (/usr/share/fonts/default.ttf); untested against other TTF files if a user swaps osd.font_path. Also gated at COMPILE time by USE_OSD_HINTING (BR2_PACKAGE_TIMPS_OSD_HINTING in the buildroot package, off by default — measured ~2.1KB smaller .text on T31/GCC 16.1.0/-Os when left off): on a build without it, setting this key is accepted but has no effect. Settable via /control, same restart-required class as osd.enabled.

Custom placeholders (show any value you want in the OSD)

Any {name} token in an OSD text template that isn't one of the built-in placeholders (hostname, ip, mac, fps, fpsN, bitrate, bitrateN, uptime, net/tx, cpu, mem, clients) is looked up in a fixed file instead - a plain key = value text file, one entry per line. This lets an external script drive OSD content with no timps code changes at all: temperature readings, a doorbell state, whatever.

Since v1.9.28 the path is /tmp/timps_osd.vars, fixed in src/hal/osd_vars.c (OSD_VARS_FILE) and not configurable - not even file-only via timps.conf. It used to be (osd.vars_file), but any settable path could point it at /etc/timps.conf itself and render its credentials into the video through a {placeholder}.

  1. Pick a name and use it in the OSD text, e.g. osd0.4.text = Temp: {room_temp}C.
  2. From any script/cron job with filesystem access, write matching lines to /tmp/timps_osd.vars:
    room_temp = 21.5
    doorbell = idle
    
  3. timps re-reads the file on every OSD refresh tick (~1x/s) - no signal or restart needed. There's no locking, so writing in-place (e.g. echo ... > file, which truncates then writes) has a small window where a concurrent read can land mid-write: either an empty file (blank placeholder for that tick) or a partially-written value line (a truncated-but-not-garbage value for that tick, e.g. 21 instead of 21.5) - never a crash or memory issue, since reads are line-bounded, just a possible one-tick display glitch that self-corrects on the next read. Avoid it entirely with an atomic replace: write the new content to a temp file in the same directory, then rename()/mv it onto the target path - rename() is atomic on Linux, so timps always sees either the complete old file or the complete new one, never a partial write.

Limits: each resolved value is capped at 127 bytes, and the whole expanded text string (all placeholders combined, before strftime() substitution) at 511 bytes - long enough for typical single-line status text, not a replacement for a full custom rendering pipeline. Unmatched names (typo, or /tmp/timps_osd.vars doesn't exist yet) resolve to an empty string rather than an error, so a missing file just shows blank text for that placeholder.

osd<S>.<N>.* — per-stream OSD overlay items

Canonical form: osd<S>.<N>.<field> where S is the video stream index (0/1) and N is the item slot (0–7, MS_MAX_OSD). A legacy shared form osd<N>.<field> (no stream index) is still parsed for backward compatibility and mirrors the value onto every stream's item N. Default layout: item 0 = timestamp (top-left), item 1 = {hostname} (top-center), item 2 = {uptime} (top-right), item 3 = thingino logo (bottom-right); items 4–7 are unused placeholders (disabled).

Key Type Default Range Live? Description
enabled bool item 0–3: 1, items 4–7: 0 0/1 Restart-only Enabling an item that started disabled has no IMP region to attach to — only takes effect on restart. Disabling a running item also only persists (no live hide).
type enum text (item 3: logo) text|logo Restart-only Overlay type. Settable via /control and persists, but imp_osd_apply()'s live re-render dispatch is fixed at region-creation time (rg->is_text), so switching an existing item between text and logo needs a restart to actually change what's drawn (same restart-required reasoning as enabled).
text string(128) per-item (see layout above) — Live (if the item had a region at startup) Text template: literal text, {placeholder} tokens ({hostname} {ip} {mac} {fps} {fpsN} {bitrate} {bitrateN} {uptime} {net} {cpu} {mem} {clients}), and strftime() codes. Posting "" clears it (a deliberate exception to the usual "empty value refused" rule — see HTTP /control API Reference); null/missing stays refused, since some clients post null for fields they merely didn't intend to touch.
logo (alias logo_path) string(128) /usr/share/images/thingino_100x30.bgra (item 3) — File-only Raw BGRA logo file path.
logo_w (alias logo_width) int 100 (item 3) 0–4096 File-only Logo width in px.
logo_h (alias logo_height) int 30 (item 3) 0–4096 File-only Logo height in px.
x int per-item -8192–8192 (since v1.9.28) Live X position: 0 = centered, >0 = pixels from the left edge, <0 = pixels from the right edge.
y int per-item -8192–8192 (since v1.9.28) Live Y position: same convention, top/bottom.
font_size int 32 (stream 0) / 12 (other streams) 8–128 Live Absolute pixel font size (no per-stream auto-scaling).
color (alias font_color) hex (0xAARRGGBB) 0xFFFFFFFF — Live Text fill color.
transparency int 255 0–255 Live Group alpha.
outline (alias stroke) int 1 0–64 Live Text outline/stroke width in px (0 = off). On by default so overlays stay legible on light backgrounds.
outline_color (alias stroke_color) hex (0xAARRGGBB) 0xFF000000 — Live Outline/stroke color.
font_path string(128) "" (inherits osd.font_path) — File-only Optional per-item TTF override.

privacy<S>.<N>.* — privacy cover masks

S = video stream index (0/1), N = region slot (0–3, MS_MAX_PRIVACY). Implemented as IMP OSD cover regions; live-appliable as long as an OSD group exists on that stream (i.e. OSD or a privacy region was enabled at boot — GET /control's caps.privacy.available reflects this).

Key Type Default Range Live? Description
enabled bool 0 0/1 Live* Show/hide this cover region.
x int 0 -8192–8192 (since v1.9.28) Live* Rectangle X origin, pixels, in the stream's frame.
y int 0 -8192–8192 (since v1.9.28) Live* Rectangle Y origin.
w (alias width) int 0 0–8192 (since v1.9.28) Live* Rectangle width.
h (alias height) int 0 0–8192 (since v1.9.28) Live* Rectangle height.
color (alias fill_color) hex (0xAARRGGBB) 0xFF000000 (opaque black) — Live* Fill color.

* Live only if an OSD group already exists for that stream at boot; otherwise the value persists but has no visible effect until restart.

roi<S>.<N>.* — encoder ROI (since v1.10.0)

S = video stream (0/1), N = region slot (0–3, MS_MAX_ROI; the SDK has 8, timps uses 4). Per-region QP for H.264 via IMP_Encoder_SetChnROI. POST as {"roi":{"0":{"1":{"enabled":1,"x":640,"y":320,"w":320,"h":240,"qp":-6}}}}.

Key Type Default Range Live? Description
enabled bool 0 0/1 Live Region on/off.
x, y int 0 0–8192 Live Top-left corner, pixels of the encoded stream.
w, h int 0 0–8192 Live Size; rounded outwards to 16×16 macroblocks.
qp int -5 -51–51 Live Delta -26..25 or absolute 0..51, clamped per qp_mode.
qp_mode int 1 0/1 Live 1 delta to the frame QP, 0 absolute QP.

The encode thread applies a change before its next frame; an idle stream gets its regions when it starts. The SDK rectangle holds two inclusive corners, not a width/height, and libimp clips nothing — timps converts and clamps. Only unrotated H.264 streams take regions (caps.roi.streams); on an H.265 or rotated stream a file value logs one WARN at start, while a POST is accepted and persisted without any effect or log line. Device-tested (by the OpenIMP maintainer, not timps) on T10, T20 and T21 with OpenIMP (T10: QP51 region -25 % bitrate; T21: on by default, OPENIMP_T21_ROI=0 in the environment turns it off and timps reports experimental). Vendor T10/T20/T21 are experimental (T21: no effect seen). T23 refuses the keys (the encoder only stores the region); T31 ROI exists in OpenIMP as SetChnRoiAttr but is not wired yet. Since v1.11.0 T40/T41 with OpenIMP take regions as QP deltas when the libimp exports OpenIMP_Cap_EncRoi (experimental; see Platform & SDK Support). Everything else refuses the keys. caps.roi.tier reports it.

motion.* — motion detection

See Motion Detection for the full grid model. enabled/sensitivity/cols/rows/monitor_stream are POST-able and Live; hold_ms/skip_frames are POST-able and Live too — they feed the IVS grid/hold logic only at create/resync time, so ing_control() routes them through the same deferred grid re-sync as the geometry keys, which runs once at the end of the POST. cooldown_ms/on_motion stay config-file-only by design (see below).

Key Type Default Range Live? Description
motion.enabled bool 0 0/1 Live Enable/disable IVS motion detection (stops/recreates the whole IVS grid).
motion.monitor_stream int (channel) 1 valid stream index Live Which video stream's frame the grid is computed over. Default is the sub stream (far less CPU than 1080p); stream 0 is used when video1 is not enabled.
motion.sensitivity int 128 0–255 Live UI-facing sensitivity; mapped to the SDK's native 0–4 range (v*4/255) applied uniformly to every cell. A change that maps to the same SDK level as before is treated as unchanged (no IVS rebuild).
motion.cols int 5 (or 2/1 on SDKs with a smaller ROI budget) ≥1, cols*rows clamped to MOTION_CELL_LIMIT Live Grid columns. Setting one axis clamps against the current value of the other, never the reverse, so re-applying the same pair is idempotent.
motion.rows int 5 (or 2/1) ≥1, same clamp Live Grid rows.
motion.cooldown_ms int 5000 250–INT_MAX (floor enforced) File-only Minimum gap between on_motion hook executions.
motion.hold_ms int 800 0–60000 Live How long a cell reports "active" after its last hit, so an async /events//control reader reliably observes single-frame motion instead of racing IVS's own immediate clear. 0 = no hold. Settable via /control; applied through the grid re-sync batched at the end of the request.
motion.skip_frames int 5 1–100 Live IMP_IVS_MoveParam.skipFrameCnt — analyze every Nth frame. Higher = cheaper/higher latency. Settable via /control; applied through the grid re-sync batched at the end of the request.
motion.on_motion string(128) "" — File-only, not GET-readable either Program launched by posix_spawn() on motion (no shell, no arguments, no PATH search — use an absolute path). "" = disabled. Receives MOTION_COLS/MOTION_ROWS/MOTION_CELLS/MOTION_TIME via the environment — see Motion Detection.
motion.roi_x/roi_y/roi_w/roi_h int 0 — File-only, deprecated Legacy single-ROI keys, still parsed for old configs but ignored — the grid replaced them. Setting a non-zero value logs a one-time warning.

record.* — local SD recording

See Recording & Timelapse. All settable keys are re-read live by the recorder thread on every loop pass, so changes apply within its ~300 ms poll cycle without a restart.

Key Type Default Range Live? Description
record.enabled bool 0 0/1 Live (next cycle) Master enable; also gates on-boot start.
record.channel int (channel) 0 valid stream index Live (next cycle) Video stream to record.
record.mode enum 1 (motion) continuous(0)|motion(1)|raw number Live (next cycle) Continuous vs. motion-triggered recording.
record.dir string(128) /mnt/mmcblk0p1 — Live (next cycle) SD base directory. Since v1.9.28 refused if it (or its nearest existing parent) is on the root filesystem — typically the mount point with no card — instead of recording into flash; /tmp is allowed.
record.name string(96) %Y%m%d/%H/%Y%m%dT%H%M%S strftime template Live (next cycle) Path template under <dir>/<hostname>/records/.
record.segment_s (alias segment) int 60 0–86400 Live (next cycle) Max segment length in seconds; rotation only happens at a video keyframe. 0 = single file, no rotation.
record.pre_roll_s (alias pre_roll) int 3 0–60 Live (next cycle) Motion mode: seconds of buffered video kept before the trigger (ring buffer).
record.post_roll_s (alias post_roll) int 10 1–300 Live (next cycle) Motion mode: keep recording this long after the last motion event.
record.min_free_mb int 200 0–1048576 Live (next cycle) Prune oldest segments until at least this much free space remains. Set it larger than the media can ever provide and the recorder refuses to record (saying so in the record status object's last_error) instead of emptying the archive chasing an unreachable target — see Recording & Timelapse.
record.audio bool 1 0/1 Live (next cycle) Mux AAC audio into the recording when available (G.711 cannot be muxed into fMP4).

{"record":{"active":1|0}} (not a config key) is a transient manual start/stop override, and {"record":{"clip":"<path>","seconds":N}} triggers an independent on-demand fMP4 clip capture — neither is persisted to the config file.

timelapse.* — periodic JPEG capture

See Recording & Timelapse. Like record.*, the running thread re-reads these live.

Key Type Default Range Live? Description
timelapse.enabled bool 0 0/1 Live (next cycle) Master enable; also gates on-boot start.
timelapse.channel int (channel) 0 valid stream index Live (next cycle) Video stream whose JPEG is captured.
timelapse.dir string(128) /mnt/mmcblk0p1 — Live (next cycle) Base directory (SD, NFS, any writable path; since v1.9.28: not on the root filesystem, same rule as record.dir).
timelapse.name string(96) %Y%m%d/%H/%Y%m%dT%H%M%S strftime template Live (next cycle) Path template under <dir>/<hostname>/timelapses/.
timelapse.interval_s (alias interval) int 60 ≥1 Live (next cycle) Seconds between shots.
timelapse.keep_days int 7 0–3650 (since v1.9.28) Live (next cycle) Delete shots older than this; 0 = keep forever. Shots with a pre-2025 (pre-NTP) mtime are never age-pruned (since v1.9.28).

daynight.* — automatic day/night

See Day/Night for the full decision logic. The short version: the metric is the exposure index (total_gain × integration_time / max_integration_time, higher = darker — equal to total_gain in a dark scene and far below it in a bright one); day→night is a direct measurement on the honest day pipeline, night→day only ever happens via a check of the day pipeline, asked for by a look (IR illuminator off 8 s, silent); the calendar (time_*, sun_*) is used by mode = schedule only.

switch_cmd, isp_path, trace_path and irprobe_cmd are not GET-readable even though they are settable via the config file — each names a path or program the daemon acts on as root.

Renamed 2026-08-17: total_gain_day_threshold → day_gain and total_gain_night_threshold → night_gain. The old names still work as aliases and the units and calibration are unchanged. Removed 2026-08-17 (parsed, ignored, warned about once): day_gain_pct, baseline_delay_s, boot_settle_max_s, boot_stable_pct, night_reconfirm_s, probe_max_skip_s, threshold_low, threshold_high, hysteresis.

Removed 2026-08-22 (config consolidation — parsed, ignored, warned about once): the whole learn/state_path learning subsystem is gone outright.

Retired in v1.10.1 (the simple engine, dev_notes/dn-redesign.md; parsed, ignored with one warning each, … is retired and IGNORED): probe_min_gap_s, probe_confirm_s, boot_probe, diagnose_thresholds, and the eight constants of the 2026-08-22 consolidation listed at the end of this section, which are no longer echoed in GET /control.

Key Type Default Range Live? Description
daynight.enabled bool 1 0/1 Live Auto-detection on/off; 0 = manual (thread still samples but forces nothing).
daynight.mode enum auto auto|schedule Live auto = the sensor automaton (the calendar is not used). schedule = the calendar decides outright, no sensor and no looks. The pre-2026-08-17 tokens still parse: sensor→auto, time/sun→schedule.
daynight.time_night_start string(6) "" "HH:MM" Live Calendar: local time night begins. Set together with time_day_start; takes precedence over the sun calendar. (since v1.9.28) a window with only one edge set, a malformed value or two equal edges is ignored with a startup warning (and the sun calendar used if a location is set).
daynight.time_day_start string(6) "" "HH:MM" Live Calendar: local time day begins.
daynight.sun_latitude float 0.0 -90–90 Live Sun calendar: latitude. Used when no time window is set and either coordinate is non-zero.
daynight.sun_longitude float 0.0 -180–180 Live Sun calendar: longitude.
daynight.sun_sunrise_offset_min int 0 -1440–1440 Live Minutes added to computed sunrise. (since v1.9.28) an offset that pushes an edge across solar midnight is honoured; it used to be clipped to the current solar day.
daynight.sun_sunset_offset_min int 0 -1440–1440 Live Minutes added to computed sunset.
daynight.day_gain float 768 1–1,000,000 Live Exposure index below which the day pipeline confirms day. 768 = 3× gain: "day is confirmed when the day pipeline can hold the scene at ≤3×". Alias: total_gain_day_threshold.
daynight.night_gain float 4096 1–1,000,000 Live Exposure index above which day ends. 4096 = 16×, "colour is hopeless". Alias: total_gain_night_threshold.
daynight.jump_pct int 20 5–70 Live since v1.10.1. At night: the illuminator-lit level this far below the last look for 20 s starts a look at once (new light).
daynight.color_pct int 3 0–50 Live Since v1.11.0. At night: the colour of the illuminator-lit picture (global R/G or B/G of the ISP's AWB statistics, read every 5 s) this far from its value after the last look for 20 s starts a look at once — a weak lamp that hardly brightens the IR picture. At most every 5 min, the gap doubling up to 45 min until a timer look; 0 = off. Needs irprobe_cmd and a libimp with per-zone AWB statistics; status daynight.color_trigger (on/off/unavailable) and color_dev_pct.
daynight.day_confirm_s int 30 1–3600 Live How long the index must stay above night_gain (or the AE at its limit) before day→night; 6 s when very dark.
daynight.heartbeat_s int 14400 (4h) 300–604800 Live Check interval at night for cameras without a switchable illuminator; also caps the 45-min look interval of the others.
daynight.heartbeat_max_s int 43200 (12h) 300–604800 Live The longest night without a day-pipeline reading: then a check runs regardless of looks and lockouts.
daynight.interval_ms int 2000 100–60000 Live Sample interval.
daynight.ir_idle_off int 0 0/1 Live Since v1.10.0. Idle-Dark: at night, once no picture consumer has been there for ir_idle_linger_s, switch only the IR LED off (IR-cut and ISP stay in night) and decide at the next consumer from the reading taken meanwhile. Needs the FrameSource keepalive (general.fs_keepalive, auto on OpenIMP T23/T40/T41), auto mode and a timps-irprobe with the idle/state verbs. Off by default: with 850 nm LEDs the glow going out shows that nobody watches. Status daynight.ir_idle; see Day/Night.
daynight.ir_idle_linger_s int 60 0–3600 Live Since v1.10.0. How long after the last picture consumer the IR LED may go off. A poll slower than this costs one LED on/off pair and one held snapshot per poll; keep it longer than timelapse.interval_s (at 60/60 every shot can light the LED once).
daynight.history_s int 0 (off) 0–172800 (48h) Live Seconds of decision history kept in RAM for the WebUI tuning graph, served by GET /control?dn_history=1; 0 = off, and at 0 nothing is allocated at all. One 16-byte sample per 10 s, allocated on first push and resized in place when this changes, so 4 h costs 22.5 KB and the 48 h ceiling 270 KB. Anything above the ceiling is clamped (the POST response echoes the clamped value, and the daemon logs the effective size). Opt-in and decoupled from the tuning page's lifetime: the page's "collect in background" switch is the only thing that POSTs it, and it persists like any other key, so an explicit opt-in survives a reboot. With it off the page collects live into the tab over SSE instead; with it on the daemon owns the series, which is what lets the graph show the hours no tab was open (the WebUI is plain HTTP and therefore has no Service Worker to collect with).
daynight.irprobe_cmd string(64) timps-irprobe — File-only, not GET-readable Board script run as <cmd> on|off|idle|state to drive the IR illuminator alone, without moving the IR-cut filter (on/off for a look, idle/state for Idle-Dark). Empty = no looks: the camera checks like one without a switchable illuminator (night level halved, heartbeat_s). Two failures in a row turn looks off for the session. Run via fork+execlp, never a shell.
daynight.switch_cmd string(64) daynight — File-only, not GET-readable Board script run as <cmd> day|night on a switch.
daynight.isp_path string(128) /proc/jz/isp/isp-m0 — File-only, not GET-readable ISP exposure proc file (gain and integration time). Since v1.11.0 never read on vendor T21 (that driver corrupts the kernel heap on the proc read); exposure from IMP only there.
daynight.trace_path string(128) "" — File-only, not GET-readable Opt-in decision-trace CSV for the replay harness. tmpfs only; 256 KB cap (since 1.11.0, before 1 MB), rotated once to <path>.1.

Constants of the v1.10.0 engine (historical)

These eight were per-camera daynight.* fields until the 2026-08-22 consolidation, then fixed DN_* constants echoed in GET /control. Since v1.10.1 they are retired: a file value is ignored with one warning, and the status no longer echoes them. The table describes the v1.10.0 engine.

Key Was type Fixed value What it did
probe_jump_pct int 50 Probe when the index falls below this percentage of the night reference (the level at which night was last proven). 50 = one full stop of new brightening.
probe_settle_s int 8 AE settling on the day pipeline before the probe verdict is taken. One verdict, binary: below day_gain it sticks, otherwise it reverts immediately (bypassing transition_s).
ref_delay_s int 30 Wait after entering night before anchoring the night reference (IR LEDs settling). A floor — the anchor also waits for the reading to stop moving.
ir_ratio_night float 2.0 Silent probe: an IR ratio r at or above this means the illuminator is carrying the scene — stay night, no click. r = D(illuminator off) / D(illuminator on), so it is dimensionless and fleet-wide, unlike any absolute level.
ir_ratio_day float 2.0 …and at or below this the room supplies the light. Deliberately equal to ir_ratio_night, so every silent probe reaches a verdict; a reading between the two is inconclusive and escalates to the audible probe instead of deciding on thin evidence.
ir_min_headroom int 8 Minimum AE reserve, in log2 units (32 = one stop), for a ratio to mean anything. An AE with nothing left cannot respond to the illuminator going off and returns r ≈ 1, which looks exactly like daylight — measured 1.14 on a pitch-dark scene. Below this the verdict is night: being pegged at the dark end is itself proof.
boot_settle_s int 5 Minimum wait after thread start/re-enable before the first decision (also gated on the reading having settled).
transition_s int 5 Minimum dwell between switches. A failed probe's revert bypasses it.

general.* — daemon-wide settings

File-only except debug_modules, which is POST-able as {"general":{"debug_modules":"HAL_ING"}} and takes effect immediately. Everything else in this section is read at startup.

Two keys are deliberately left out of the table below: general.trace and general.trace_ms are a developer probe for send-pipeline tracing (see src/trace.h), handled directly in set_kv() rather than as table fields — not POST-able, not echoed, not part of /control?fields=1. They are intentionally undocumented here; edit /etc/timps.conf by hand if you need them.

Key Type Default Range Live? Description
general.loglevel int LOG_INFO (2) SDK log-level enum File-only Startup log verbosity (-v on the command line also forces debug).
general.debug_modules string(64) "" comma list of log tags Live Modules raised to DEBUG regardless of loglevel (HAL_ING,DAYNIGHT). Names rather than a bitmask: a mask in a config file silently means something else once a module is added, and nothing warns. It matters because the ring buffer is 64 KB and recycles in minutes under load - raising the global level to reach one subsystem loses the lines it was raised for. Case-insensitive, up to 8 names; over-long or surplus names are rejected with a WARN, an unknown (but well-formed) name simply never matches. A listed module passes at every level, not only its DEBUG lines. See Logging.
general.imp_polling_timeout int 500 — File-only IMP_Encoder_PollingStream timeout, ms.
general.osd_pool_size int 1024 — File-only Max OSD pool size (KB) reserved for small OSD regions.
general.fs_keepalive int -1 -1..1 File-only FrameSource keepalive. 1: every video FrameSource is enabled once at start and held until the daemon stops (encoders stay on-demand), so nothing disables a FrameSource at runtime. 0: FrameSources run only while something consumes them (the classic on-demand model). -1 (auto): on for OpenIMP builds on T23, T40 and T41 (T23/T41: a FrameSource disable/enable while the sensor streams can hang the SoC; T40, since v1.11.0: the AE stops without frames and day/night read a frozen exposure); off everywhere else, because the idle cost of a permanently running FrameSource (CPU, heat, ~6.3 MB rmem for a 1080p main stream) is unmeasured there. Also auto, on/true/yes, off/false/no; an unknown word logs a warning and stays auto. Effective value: caps.framesource in GET /control. See Architecture.
general.syslog bool on 0/1 File-only Also mirror log output to syslog (logread). A side-effecting key: applied immediately at config-load time via log_set_syslog(), but not stored as a field, so it cannot be echoed back by any getter.

sim.* — host simulation backend

Only meaningful for make sim builds (src/hal/hal_sim.c); ignored by a real-hardware (HAL_INGENIC) build. File-only.

Key Type Default Range Live? Description
sim.video0 string(256) "" file path File-only Annex-B H.264/H.265 file looped as the video0 source.
sim.video1 string(256) "" file path File-only Annex-B file looped as the video1 source.
sim.audio string(256) "" file path File-only ADTS AAC file looped as the audio source.
sim.jpeg string(256) "" file path File-only Still JPEG file republished as the JPEG source(s).

video<N>.* — per-stream encoder settings

N is the video stream index (0 = main, 1 = sub; MS_MAX_VSTREAM=2 streams total). Geometry/codec/identity keys (enabled/codec/width/ height/fps/gop/max_gop/profile/rotation/buffers) are persist-only / restart-required (the encoder and FrameSource are never reconfigured on the running pipeline); rtsp_path is honestly live — see the caveat at the top of this page. The rate-control knobs below (bitrate/rc_mode/qp/min_qp/max_qp/i_bias_lvl) are Mixed: a per-platform subset reaches the running encoder live as of 2026-08-21 — see Rate Control Parameters: Live vs. restart for the exact key × SoC table, or query the running build's own caps.video_live (never hardcode the table into a client — see HTTP Control API). GET /control still groups this whole section under caps.restart (entry "video") as the conservative default; a key that also appears in caps.video_live, and rtsp_path, are the per-key exceptions.

Key Type Default (video0 / video1) Range Live? Description
video<N>.enabled bool 1 / 1 0/1 Restart-only Enable this stream.
video<N>.codec enum h264 / h264 h264|h265 (aliases hevc) Restart-only Video codec. H.265 only where the platform's encoder API supports it — see Platform & SDK Support.
video<N>.width int 1920 / 640 64–4096 Restart-only Encode width (pre-rotation). With neither width nor height set, a known sensor size that fits inside the default is used instead, so nothing is upscaled (a 1280x720 sensor streams 1280x720; since v1.10.0).
video<N>.height int 1080 / 360 64–4096 Restart-only Encode height (pre-rotation). Unset: see width.
video<N>.fps int 25 / 25 1–120 Restart-only Encode frame rate of this stream only. It does not set the sensor rate; that is sensor.fps.
video<N>.bitrate int 3000 / 512 16–50000 (kbps) Mixed (live on every SoC as of 2026-08-21, see below) Target bitrate.
video<N>.rc_mode (alias mode) enum cbr / cbr cbr|vbr|fixqp|smart|capped_vbr|capped_quality Mixed (live on classic SoCs only, see below) Rate-control mode.
video<N>.gop int 50 / 50 1–1000 Restart-only GOP length (I-frame interval).
video<N>.max_gop int 60 / 60 1–1000 Restart-only RESERVED / no effect — parsed, clamped and persisted for compatibility but consumed by no HAL; the encoder's keyframe interval comes from video<N>.gop. Setting a non-zero value logs a one-shot warning.
video<N>.profile int 2 / 2 0 (baseline) – 2 (high) Restart-only H.264/H.265 encode profile. Matters for WebRTC: the WHEP answer's profile-level-id comes from this stream's live SPS, and Firefox offers (and accepts) H.264 as Baseline only — it refuses a Main/High answer at setRemoteDescription(), so the session never establishes. Chromium accepts any profile and decodes from the in-band SPS. Set 0 on the webrtc.channel stream if Firefox must work; the preview page detects the mismatch and disables its WebRTC option either way. See the top-level README.md § "WebRTC / WHEP".
video<N>.qp int 35 / 35 1–51 Mixed, see below Fixed QP, used only when video<N>.rc_mode=fixqp (passed as the encoder's initial/fixed QP: iInitialQP on the new API, attrH264FixQp.qp/attrH265FixQp.qp on the classic API). No effect under cbr/vbr/etc., which derive QP from rate control instead — use video<N>.min_qp/max_qp there.
video<N>.min_qp int 20 / 20 1–51 Mixed (live on every SoC as of 2026-08-21, see below) Minimum QP.
video<N>.max_qp int 45 / 45 1–51 Mixed (live on every SoC as of 2026-08-21, see below) Maximum QP.
video<N>.quality_lvl int 2 / 2 0–7 Restart-only Classic-SoC (T10..T30) VBR/Smart only: minimum quality floor. 0 = highest quality/largest stream, 7 = lowest. The vendor SDK derives minBitRate = bitrate * quality[lvl] from it (quality[]={0.8..0.1}), but that floor was not found to be binding in testing — see Rate Control Parameters. No equivalent field on the new encoder API (T31/C100/T40/T41); a non-default value there logs a one-shot warning and has no effect.
video<N>.change_pos int 80 / 80 50–100 Restart-only Classic-SoC VBR/Smart only: percentage of bitrate above which the controller starts raising QP. Measured to have no observable effect on a T23 (80/65/50 gave identical output) — treat as unverified rather than a reliable lever. No equivalent on the new API; same one-shot warning as quality_lvl.
video<N>.i_bias_lvl int 0 / 0 -3–3 Mixed (live on classic SoCs and T31/C100, see below) I-frame QP bias (negative = more bits on keyframes). Classic SoCs: both CBR and VBR/Smart. New API: wired on T31/C100 via IMP_Encoder_SetChnQpIPDelta (readable back as encoder.<n>.rc.ip_delta); T40/T41 have no such call and warn once instead.
video<N>.fluc_lvl int 0 / 0 0–4 Restart-only, H265 only Classic-SoC H265 CBR/VBR/Smart: bitrate fluctuation allowed relative to the average. No H264 equivalent field, and the T23 SW-rotate path is H264-only so never sees this key. No equivalent on the new API.
video<N>.rotation int 0 / 0 0|90|270 (+180 on T40/T41 only); legacy 1/2 accepted as raw rotTo90 values Restart-only, USE_ROTATE builds only Image rotation — see Platform & SDK Support and docs/rotation.md. Unsupported values on a given SoC are coerced to 0 with a warning. T10/T20/T21 (OpenIMP builds, since v1.10.0): sub streams only, 16-aligned, ≤704x576, and only with an OpenIMP that has T1x rotation (6b7f5ae); otherwise the stream starts unrotated with a warning. GET /control reports why a stream runs unrotated in video.<N>.rotation_state (since v1.10.1) and how it is rotated in video.<N>.rotate_path (off/sw/hw/band, since v1.11.0).
video<N>.buffers int 2 / 2 1–8 Restart-only IMP video buffer count (nrVBs). Explicitly setting this makes the HAL trust it as-is instead of applying its own safety clamp.
video<N>.rtsp_path string(64) /ch0 / /ch1 — Live (documented exception, see above) RTSP mount path for this stream.
video<N>.jpeg (alias jpeg_enabled) bool 1 / 1 0/1 File-only Enable a piggyback JPEG encoder sharing this stream's FrameSource (used by /snapshot.jpg//stream.mjpeg?chn=N and timelapse).
video<N>.jpeg_quality int 75 / 75 1–100 File-only Piggyback JPEG quality.
video<N>.jpeg_fps int 5 / 5 1–120 File-only Piggyback JPEG max publish rate.
video<N>.jpeg_chn int MS_MAX_VSTREAM+1+N 0–8 File-only IMP encoder channel number for the piggyback JPEG encoder (internal). Must be unique; (since v1.9.28) a collision is moved to the lowest free channel at load (or that JPEG encoder disabled if none is free), with an [ERR] line.
video<N>.imp_chn int N 0–7 File-only, internal IMP encoder channel number for the video stream itself. It doubles as the stream's hub slot, so it must equal N; (since v1.9.28) any other value is reset to N at load with an [ERR] line (before, it silently fed another stream's or the audio slot).

For the live-apply mechanics (how a POST maps onto these keys, JSON shapes, and the caps object a client can use to discover this classification programmatically) see HTTP /control API Reference.

Clone this wiki locally