Skip to content

night: switch day/night from the ISP gain when there is no light sensor - #55

Open
Jamp wants to merge 27 commits into
OpenIPC:masterfrom
Jamp:night-isp-gain
Open

Jamp wants to merge 27 commits into
OpenIPC:masterfrom
Jamp:night-isp-gain

Conversation

@Jamp

@Jamp Jamp commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Depends on #54 (motion detection), which itself depends on #53; this branch is on top of both.

Cameras like the ipc017 have neither an ADC nor a light sensor pin. Without one the night
thread only slept; it now reads the ISP gain (MI_ISP_AE_QueryExposureInfo, optional) and
switches to night at 8x held 15 s and back to day below 2x held 60 s (the IR LED brings the
gain down to ~6x, so the day threshold must stay under it), pausing motion detection while
the IR cut filter moves. The thresholds are night_mode settings; automatic switching stops
with a warning when the ISP gain cannot be read.

Also:

  • the night thread now stops on night_disable (it looped on keepRunning only, and
    /api/night?enable=false hung);
  • the IR cut filter is pulsed again every 10 minutes in its current position: once a pulse
    was missed and the filter stayed out by day, the picture went pink and the lower gain meant
    no crossing would ever correct it;
  • sysfs gpio_read read 0 bytes (only the read length here: the fd < 0 checks are left to
    hal: add Fullhan FH8852/FH8856 (V100) support #43, which fixes them in every sysfs helper);
  • MI_ISP_IQ_SetColorToGray takes a 4-byte boolean (a char left the picture purple on SSC323).

Measured on SSC323: 64x sensor and 3x ISP gain in the dark, ~6x with the IR LED; one clean
transition each way per night, plus the expected ones when the room light goes on and off.
With the filter forced out by day, the next periodic pulse put it back.

The HAL changes are kept inside src/hal/star/i6_* and conditioned on the series where they
are specific to SSC32x; glad to rebase once the HAL naming refactor you mentioned in #43 lands.

Jamp added 20 commits September 30, 2026 14:47
- ISO 8601 durations are parsed in 64 bits
- A failed PullMessages allocation is answered with an error
- RelatesTo has room for the longest message ID
Measured on SSC323: port 2 gives 320x180 NV12 frames with a 320 stride.
Port 3 returns buffers with fresh timestamps whose memory is never
written, even through an uncached mapping.
- Encoders no longer take the port the motion detector reads
- Frames smaller than the detector input are skipped
- More headroom for MI_SYS_BufInfo_t
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add ISP-gain night switching, motion detection, and ONVIF events

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Switch day and night modes using ISP gain when no light sensor is available.
• Detect motion from raw camera frames and publish ONVIF PullPoint events.
• Add host tests and fix night-thread shutdown, GPIO reads, and grayscale control.
Diagram

graph TD
  Config["Camera settings"] --> Night["Night controller"] --> ISP["ISP gain HAL"]
  Raw["Raw frame HAL"] --> Motion["Motion detector"] --> Events["Event subscriptions"] --> Server["ONVIF server"]
  Config --> Motion
  Night -->|pause during transition| Motion
  Server -->|pull and manage| Events
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use an XML/SOAP library for ONVIF parsing
  • ➕ Handles XML namespaces and structure more reliably than string searches.
  • ➕ Reduces protocol-parser security and maintenance risk.
  • ➖ Adds an embedded-target dependency and binary footprint.
  • ➖ Requires integration work across existing ONVIF handlers.

Recommendation: The bounded, in-process detector and subscription store fit the camera's resource constraints. Before extending the new hand-written SOAP helpers further, evaluate a small XML parser: host tests help, but local-name string searches do not provide full XML structural validation. Review the stacked ONVIF and motion work separately from the final ISP-gain change where possible.

Files changed (44) +1815 / -159

Enhancement (33) +1402 / -154
capabilities.xmlAdvertise PullPoint support +1/-1

Advertise PullPoint support

• Changes ONVIF event capabilities to report PullPoint support.

res/onvif/capabilities.xml

event.xmlAdd event SOAP envelope template +9/-0

Add event SOAP envelope template

• Provides an envelope with event namespaces, action header, and response body placeholders.

res/onvif/event.xml

eventcaps.xmlDescribe event service capabilities +3/-0

Describe event service capabilities

• Reports PullPoint support and the four-subscription limit.

res/onvif/eventcaps.xml

eventprops.xmlDescribe MotionAlarm event properties +22/-0

Describe MotionAlarm event properties

• Declares the motion topic, source and state fields, and supported event dialects.

res/onvif/eventprops.xml

fault.xmlAdd SOAP fault template +16/-0

Add SOAP fault template

• Defines a SOAP fault body for invalid requests, unknown subscriptions, and unsupported actions.

res/onvif/fault.xml

notification.xmlFormat motion notifications +13/-0

Format motion notifications

• Defines the ONVIF MotionAlarm notification with timestamp, operation, source, and state.

res/onvif/notification.xml

pullmessages.xmlFormat PullMessages responses +4/-0

Format PullMessages responses

• Provides response fields for current time, subscription termination, and queued notifications.

res/onvif/pullmessages.xml

pullpoint.xmlFormat subscription creation responses +7/-0

Format subscription creation responses

• Returns a subscription URL and its current and termination times.

res/onvif/pullpoint.xml

renew.xmlFormat subscription renewals +4/-0

Format subscription renewals

• Provides renewal response timestamps.

res/onvif/renew.xml

services.xmlList ONVIF service endpoints +22/-0

List ONVIF service endpoints

• Adds a GetServices response listing device, media, and event endpoints.

res/onvif/services.xml

syncpoint.xmlAcknowledge synchronization requests +1/-0

Acknowledge synchronization requests

• Adds the SetSynchronizationPoint response body.

res/onvif/syncpoint.xml

unsubscribe.xmlAcknowledge unsubscription +1/-0

Acknowledge unsubscription

• Adds the Unsubscribe response body.

res/onvif/unsubscribe.xml

i6_hal.cExpose raw VPE frames and ISP gain +79/-1

Expose raw VPE frames and ISP gain

• Adds raw-port setup, frame acquisition and release, and exposure-derived gain queries. Passes a four-byte value to grayscale control.

src/hal/star/i6_hal.c

i6_hal.hDeclare infinity6 raw and gain APIs +9/-0

Declare infinity6 raw and gain APIs

• Reserves VPE port 2 for raw frames and declares frame lifecycle and ISP gain functions.

src/hal/star/i6_hal.h

i6_isp.hAdd ISP exposure-query bindings +29/-2

Add ISP exposure-query bindings

• Defines the exposure-information layout, loads the optional query symbol, and corrects the grayscale function-pointer argument type.

src/hal/star/i6_isp.h

i6_sys.hBind infinity6 buffer functions +38/-0

Bind infinity6 buffer functions

• Adds the raw buffer metadata layout and optional system-library symbols for buffer access and cache invalidation.

src/hal/star/i6_sys.h

types.hDefine a shared raw-frame descriptor +7/-0

Define a shared raw-frame descriptor

• Adds luma pointer, stride, dimensions, and release handle for captured frames.

src/hal/types.h

main.cManage motion and subscription lifecycles +9/-0

Manage motion and subscription lifecycles

• Starts and stops configured motion detection and expires ONVIF subscriptions in the main loop.

src/main.c

media.cRoute raw frames and gain through media +60/-1

Route raw frames and gain through media

• Dispatches raw-frame and ISP-gain operations to the infinity6 HAL. Prevents encoder allocation of the reserved raw port.

src/media.c

media.hExpose raw-frame and gain media APIs +7/-0

Expose raw-frame and gain media APIs

• Declares portable entry points for raw-frame acquisition, release, and ISP gain reads.

src/media.h

motion.cRun camera motion detection +111/-0

Run camera motion detection

• Feeds raw luma frames to the detector at five frames per second and publishes state changes. Handles pauses, frame stalls, and shutdown.

src/motion.c

motion.hDeclare motion lifecycle controls +16/-0

Declare motion lifecycle controls

• Exposes start, stop, and temporary pause operations.

src/motion.h

motion_core.cImplement block-based motion detection +114/-0

Implement block-based motion detection

• Tracks a rolling luma background, scores changed blocks, and applies entry and exit holds. Suppresses global lighting changes and supports settling pauses.

src/motion_core.c

motion_core.hDefine motion detector state and API +23/-0

Define motion detector state and API

• Declares detector state, event values, tuning, feeding, pause, reset, and cleanup functions.

src/motion_core.h

night.cSwitch sensorless night mode using held ISP gain +43/-7

Switch sensorless night mode using held ISP gain

• Adds gain hysteresis, timed transitions, periodic IR-cut re-pulsing, and motion pauses during switching. Makes sensor loops stop on night_disable and handles thread-creation failure.

src/night.c

night.hConnect night control to motion pauses +1/-0

Connect night control to motion pauses

• Includes the motion interface used during IR-cut transitions.

src/night.h

onvif.cImplement ONVIF event responses +233/-138

Implement ONVIF event responses

• Adds service discovery, PullPoint operations, notifications, and SOAP faults using shared response formatting. Moves action and credential parsing to SOAP helpers and reports written response lengths.

src/onvif.c

onvif.hDeclare event response and pull interfaces +22/-1

Declare event response and pull interfaces

• Exposes event handlers, SOAP faults, and parsed long-poll request parameters.

src/onvif.h

onvif_event.cStore and deliver PullPoint events +187/-0

Store and deliver PullPoint events

• Implements bounded subscription queues, renewal, expiration, synchronization, and long polling. Uses monotonic lifetimes and protects active waiting pulls from idle-slot replacement.

src/onvif_event.c

onvif_event.hDefine subscription limits and event API +26/-0

Define subscription limits and event API

• Declares queue capacities, motion-event records, and subscription lifecycle operations.

src/onvif_event.h

server.cRoute ONVIF event and subscription requests +89/-3

Route ONVIF event and subscription requests

• Adds event-service and subscription actions, with detached workers for long polling. Returns SOAP faults for missing or unsupported actions.

src/server.c

soap.cAdd SOAP parsing and time helpers +179/-0

Add SOAP parsing and time helpers

• Extracts actions and credentials by local tag name, validates password digests, and parses durations and timezone-aware timestamps. Checks duration overflow before scaling.

src/soap.c

soap.hExpose reusable SOAP helpers +17/-0

Expose reusable SOAP helpers

• Declares tag, action, duration, datetime, and digest helpers for ONVIF handlers.

src/soap.h

Bug fix (2) +4 / -5
gpio.cRead a byte from sysfs GPIO +1/-2

Read a byte from sysfs GPIO

• Fixes gpio_read to request one byte rather than zero and checks the read length.

src/gpio.c

tools.cCorrect base64 decoding length +3/-3

Correct base64 decoding length

• Stops copying an extra byte from an incomplete final base64 group, supporting correctly sized decoded nonces.

src/hal/tools.c

Tests (5) +349 / -0
MakefileAdd host-side test runner +20/-0

Add host-side test runner

• Builds and runs standalone SOAP, subscription-store, and motion-core tests.

test/Makefile

check.hAdd lightweight test assertions +25/-0

Add lightweight test assertions

• Provides failure-counting checks and common test completion reporting.

test/check.h

motion_core_test.cTest motion-state behavior +101/-0

Test motion-state behavior

• Covers still frames, moving objects, exit holds, lighting changes, noise, pauses, and reset behavior.

test/motion_core_test.c

onvif_event_test.cTest subscription queues and waits +117/-0

Test subscription queues and waits

• Covers notification delivery, queue overflow, long-poll wakeups, lifecycle operations, expiration, and replacement of idle subscriptions.

test/onvif_event_test.c

soap_test.cTest SOAP and credential helpers +86/-0

Test SOAP and credential helpers

• Covers namespace variants, malformed and overflowing durations, timezone offsets, base64 padding, and password-digest vectors.

test/soap_test.c

Documentation (1) +13 / -0
config.mdDocument gain switching and motion settings +13/-0

Document gain switching and motion settings

• Explains the new night thresholds and hold periods, plus motion sensitivity, hold time, and ONVIF event availability.

doc/config.md

Other (3) +47 / -0
divinus.yamlProvide night-gain and motion defaults +9/-0

Provide night-gain and motion defaults

• Adds gain thresholds and hold times for sensorless night switching. Adds disabled-by-default motion detection settings.

divinus.yaml

app_config.cParse and persist new camera settings +29/-0

Parse and persist new camera settings

• Sets defaults and validation ranges for gain switching and motion detection. Writes both sections when saving configuration.

src/app_config.c

app_config.hExtend application configuration +9/-0

Extend application configuration

• Adds night gain and hold fields plus motion enable, sensitivity, and hold fields.

src/app_config.h

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (6) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Pull requests can exhaust server workers 🐞 Bug ⛨ Security
Description
start_pullmessages() creates a detached thread for every PullMessages request without limiting
outstanding workers. The four-wait limit applies only after thread creation, and dispatch releases
the request's normal server slot, so a request flood can consume thread and socket resources outside
that limit.
Code

src/server.c[R487-492]

+    if (pthread_create(&thread_id, &thread_attr, send_pullmessages_thread, (void *)task)) {
+        HAL_DANGER("onvif", "Can't create thread\n");
+        send_http_error(client_fd, 500);
+        free(task);
+    } else
+        pthread_detach(thread_id);
Evidence
Each routed pull starts and detaches a thread, whereas ONVIF_EVENT_MAX_WAITS is checked inside the
already-running worker's event pull; the server removes dispatched requests from its normal client
slots.

src/server.c[472-494]
src/server.c[734-739]
src/server.c[1825-1840]
src/onvif_event.h[6-8]
src/onvif_event.c[129-159]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The server creates an unbounded number of detached PullMessages workers despite its limited subscription waits.
## Fix Focus Areas
- src/server.c[472-494]
- src/server.c[734-739]
- src/onvif_event.c[129-153]
## Recommended Fix
Reserve a bounded worker slot before allocating or starting each pull worker; reject excess requests and release the slot on every worker exit path.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Motion detection fails when JPEG uses port 2 🐞 Bug ≡ Correctness
Description
i6_raw_create() fixes raw capture to port 2 and rejects that port when an encoder already uses it.
With MP4 and MJPEG occupying the first two channels, enabling JPEG assigns it channel 2 before
motion starts, so motion detection cannot start.
Code

src/hal/star/i6_hal.c[R389-390]

+    if (i6_state[I6_RAW_PORT].enable)
+        HAL_ERROR("i6_raw", "VPE port %d is already used by an encoder!\n", I6_RAW_PORT);
Evidence
JPEG uses the lowest free encoder channel, SDK setup creates MP4, MJPEG, and JPEG before starting
motion, and the new raw creation rejects an occupied port 2.

src/hal/star/i6_hal.h[41-47]
src/hal/star/i6_hal.c[383-390]
src/media.c[433-442]
src/jpeg.c[23-27]
src/media.c[994-1006]
src/main.c[93-100]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Raw capture conflicts with JPEG when the earlier encoders occupy channels 0 and 1.
## Fix Focus Areas
- src/hal/star/i6_hal.c[383-407]
- src/media.c[374-390]
- src/media.c[433-442]
## Recommended Fix
Reserve the raw port before encoder allocation when motion is enabled, or select an available supported raw port and carry that selection through capture and release.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. A second frame stall stops motion detection ✓ Resolved
Description
motion_thread() breaks after its one raw-port reopen fails to restore frames, but leaves
motionOn set. After a second ten-second stall, detection remains off for the rest of the run and
motion_start() would incorrectly report success instead of starting another thread.
Code

src/motion.c[R52-54]

+            if (reopened) {
+                HAL_DANGER("motion", "Frames did not come back, detection stopped!\n");
+                break;
Evidence
The second-stall branch exits the loop; the exit path does not change motionOn, while the start
and stop paths use that flag to determine whether a worker exists.

src/motion.c[48-79]
src/motion.c[81-110]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A second frame stall exits the motion worker while its running flag remains set.
## Fix Focus Areas
- src/motion.c[48-79]
- src/motion.c[81-110]
## Recommended Fix
Provide continuing bounded recovery for frame stalls, or record worker termination and make start and stop handle the stopped worker and raw port correctly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. IR-cut pauses can emit false motion events 🐞 Bug ≡ Correctness
Description
motion_core_feed() treats every paused frame as a no-motion frame and advances the normal
motion-ending counter. If motion is active when the night thread requests a three-second pause and
hold_s is set to 1, 2, or 3, the detector emits an end event during the pause and can emit a new
start event afterward.
Code

src/motion_core.c[R72-75]

+    if (core->settle) {
+        core->settle--;
+        core->primed = false;
+        return motion_core_step(core, false);
Evidence
The night transition schedules 3000 ms, which becomes 15 paused frames at 5 fps; paused feeds call
motion_core_step(core, false), and that function emits MOTION_END after the configured
hold-frame count.

src/night.c[103-107]
src/motion.c[3-5]
src/motion.c[13-16]
src/motion.c[35-36]
src/motion_core.c[48-75]
src/app_config.c[396-399]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Paused frames count as quiet frames and can end an active motion event.
## Fix Focus Areas
- src/motion_core.c[48-75]
- src/night.c[103-107]
## Recommended Fix
While paused, suppress analysis without advancing the motion-ending counter; resume from a rebuilt background without manufacturing an end/start pair.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View review recommended (4)
5. New subscribers can interrupt active pulls 🐞 Bug ☼ Reliability
Description
onvif_event_subscribe() selects a waiting subscription for replacement when all four slots have
waiting pulls. A fifth subscription then clears that active slot and wakes its client into an
unknown-subscription response, even though the client's subscription has not reached its termination
time.
Code

src/onvif_event.c[R77-83]

+    for (int i = 0; i < ONVIF_EVENT_MAX_SUBS && sub->id; i++)
+        if (!subs[i].id || (!subs[i].waiting && sub->waiting) ||
+            (!subs[i].waiting == !sub->waiting && subs[i].lastSeen < sub->lastSeen))
+            sub = &subs[i];
+    if (sub->id) pthread_cond_broadcast(&eventCond);
+
+    memset(sub, 0, sizeof(*sub));
Evidence
The selection loop can choose among waiting slots when none is free or idle; replacement broadcasts
and clears the chosen slot, and its waiting pull subsequently fails to find its ID.

src/onvif_event.c[70-87]
src/onvif_event.c[129-166]
src/onvif.c[399-415]
src/onvif.c[438-443]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Creating a fifth subscription evicts an existing client when every slot has a waiting pull.
## Fix Focus Areas
- src/onvif_event.c[70-90]
- src/onvif.c[399-415]
## Recommended Fix
If every slot is occupied by an active waiting subscription, return a capacity failure instead of replacing one, and turn that failure into an appropriate SOAP fault.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. A message ID can corrupt event replies 🐞 Bug ≡ Correctness
Description
onvif_reply_event() inserts the request's MessageID directly into a RelatesTo XML element
without escaping it. A request containing XML-significant characters in that field reaches the event
response handlers and produces malformed or injected SOAP header content.
Code

src/onvif.c[R366-368]

+    if (*messageId)
+        snprintf(relates, sizeof(relates), "\n    <wsa:RelatesTo>%s</wsa:RelatesTo>", messageId);
+    onvif_reply(response, respLen, "200 OK", eventxml, action, relates, body);
Evidence
The extraction helper copies the supplied text unchanged, onvif_reply_event places it between XML
tags, and the resulting string is included in the SOAP event envelope used by multiple response
handlers.

src/onvif.c[357-368]
src/onvif.c[383-396]
res/onvif/event.xml[2-5]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Request-controlled message IDs are copied into event-response XML without escaping.
## Fix Focus Areas
- src/onvif.c[357-368]
- res/onvif/event.xml[3-5]
## Recommended Fix
XML-escape the extracted message ID before formatting the RelatesTo element, and check the formatted output's bounds.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Night API calls undo the manual settings they just applied ✓ Resolved
Description
/api/night always runs night_disable() then night_enable(). Now that the 999/GPIO loops
actually stop on nightOn, the restarted night_thread begins with night_mode(night_mode_on()),
and it does so even when manual is set. A request that changes one control, such as
?manual=true&grayscale=true or ?ircut=false, leaves grayscale && !ircut && irled false, so the
new thread forces day mode at once and reverts the change, even though the API returns 200.
Code

src/night.c[R75-78]

+        bool night = night_mode_on();
+        unsigned int held = 0, since_pulse = 0, gain;
+
+        while (keepRunning && nightOn) {
Evidence
server.c applies each grayscale/ircut/irled/manual query parameter and then restarts the night
thread. night_thread starts with an unconditional night_mode(night_mode_on()), and night_mode_on()
is only true when all three outputs are in their night positions. Before this PR the 999/GPIO loops
ignored nightOn, so night_disable() blocked in pthread_join (the hang the PR fixes) and the thread
never restarted. Now the restart happens, and the initial night_mode() call overwrites the
single-output change the user just made.

src/server.c[1382-1422]
src/night.c[15-15]
src/night.c[46-50]
src/night.c[150-155]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Every `/api/night` request restarts the night thread. The thread's first `night_mode(night_mode_on())` then reverts any single grayscale/ircut/irled change, even in manual mode.
## Fix Focus Areas
- src/night.c[46-50]
- src/server.c[1420-1422]
## Recommended Fix
In `night_thread`, only call the initial `night_mode(...)` when `!manual`. Alternatively, in the `/api/night` handler, only restart the thread when `enable` or a pin/threshold setting changed, not when a single output or `manual` is toggled.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Light-sensor pin is switched to an output after each read ✓ Resolved
Description
With the read length fixed, the sysfs gpio_read now reads successfully and continues to
gpio_direction(pin, "out") and an unexport, so the photo-sensor input pin is driven as an output
after every sample. Before, the 0-byte read always hit the error return before this point. Now, on
pre-4.8 kernels using a GPIO light sensor, the camera fights the sensor's output every
check_interval_s, and night_mode(state) re-pulses the IR cut coil on every poll.
Code

src/gpio.c[121]

+    if (read(fd, &val, 1) != 1) {
Evidence
gpio_read() sets the direction to "in", reads one byte, then calls gpio_direction(pin, "out") and
gpio_export(pin, false). Previously read(fd,&val,0) left val at 0, so if (!val) always returned
through HAL_ERROR before the direction change. The night thread's GPIO branch calls gpio_read on the
sensor pin, then night_mode(state) on every interval.

src/gpio.c[109-132]
src/night.c[109-117]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The sysfs gpio_read now gets past the read and then sets the sensor pin's direction to "out", so the light-sensor input is driven as an output.
## Fix Focus Areas
- src/gpio.c[109-132]
- src/night.c[109-117]
## Recommended Fix
Remove the `gpio_direction(pin, "out")` call from `gpio_read` (leave the pin as an input). In the GPIO branch of `night_thread`, only call `night_mode(state)` when `state != night_mode_on()`, to avoid pulsing the IR cut filter every interval.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

9. Swapped gain thresholds make day/night flap ✓ Resolved
Description
app_config_parse checks night_gain and day_gain only against 1..1024 and never checks that
day_gain = night_gain, any gain in between is a crossing in both states, so the ISP-gain loop
switches every night_hold_s/day_hold_s seconds and moves the IR cut filter each time.
Code

src/app_config.c[R388-389]

+        parse_int(&ini, "night_mode", "night_gain", 1, 1024, &app_config.night_gain);
+        parse_int(&ini, "night_mode", "day_gain", 1, 1024, &app_config.day_gain);
Evidence
The crossing test is night ? gain = night_gain*1024. Take day_gain=8 and night_gain=4 with gain at
6x: by day it crosses to night, and by night it crosses back to day, with no gain range that holds
either state.

src/night.c[95-107]
src/app_config.c[388-391]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Nothing stops day_gain from being set at or above night_gain, which makes day/night switching oscillate.
## Fix Focus Areas
- src/app_config.c[388-391]
## Recommended Fix
After parsing, if `app_config.day_gain >= app_config.night_gain`, log a warning and reset both to their defaults (8 and 2), or set `day_gain = night_gain / 4` with a minimum of 1.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. One failed gain read ends automatic switching 🐞 Bug ☼ Reliability
Description
The ISP-gain loop breaks on the first non-zero return from get_isp_gain, and it logs "No ISP
gain on this platform" even when MI_ISP_AE_QueryExposureInfo exists and only failed once. That
single error stops day/night switching and the 10-minute IR cut re-pulse until the night thread is
restarted.
Code

src/night.c[R90-93]

+            if (get_isp_gain(&gain)) {
+                HAL_WARNING("night", "No ISP gain on this platform, automatic switching disabled!\n");
+                break;
+            }
Evidence
i6_isp_gain returns EXIT_FAILURE when the symbol is missing, but it also passes through any error
code from fnQueryExposureInfo. night_thread does not tell these cases apart and leaves the loop on
either one.

src/hal/star/i6_hal.c[559-571]
src/night.c[90-93]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A transient ISP query error permanently stops gain-based switching and the periodic IR cut pulse.
## Fix Focus Areas
- src/night.c[90-93]
- src/hal/star/i6_hal.c[559-571]
## Recommended Fix
On a gain read error, set `held = 0` and `continue` instead of breaking. Only stop with the warning when the platform has no gain support (missing symbol or unsupported platform) or after N consecutive failures.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can turn on the rule miner and Qodo learns your standards from review history

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/hal/star/i6_hal.c
Comment on lines +389 to +390
if (i6_state[I6_RAW_PORT].enable)
HAL_ERROR("i6_raw", "VPE port %d is already used by an encoder!\n", I6_RAW_PORT);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Motion detection fails when jpeg uses port 2 🐞 Bug ≡ Correctness

i6_raw_create() fixes raw capture to port 2 and rejects that port when an encoder already uses it.
With MP4 and MJPEG occupying the first two channels, enabling JPEG assigns it channel 2 before
motion starts, so motion detection cannot start.
Agent Prompt
## Issue description
Raw capture conflicts with JPEG when the earlier encoders occupy channels 0 and 1.
## Fix Focus Areas
- src/hal/star/i6_hal.c[383-407]
- src/media.c[374-390]
- src/media.c[433-442]
## Recommended Fix
Reserve the raw port before encoder allocation when motion is enabled, or select an available supported raw port and carry that selection through capture and release.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/motion.c Outdated
Comment thread src/motion_core.c
Comment on lines +72 to +75
if (core->settle) {
core->settle--;
core->primed = false;
return motion_core_step(core, false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

4. Ir-cut pauses can emit false motion events 🐞 Bug ≡ Correctness

motion_core_feed() treats every paused frame as a no-motion frame and advances the normal
motion-ending counter. If motion is active when the night thread requests a three-second pause and
hold_s is set to 1, 2, or 3, the detector emits an end event during the pause and can emit a new
start event afterward.
Agent Prompt
## Issue description
Paused frames count as quiet frames and can end an active motion event.
## Fix Focus Areas
- src/motion_core.c[48-75]
- src/night.c[103-107]
## Recommended Fix
While paused, suppress analysis without advancing the motion-ending counter; resume from a rebuilt background without manufacturing an end/start pair.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/server.c
Comment on lines +487 to +492
if (pthread_create(&thread_id, &thread_attr, send_pullmessages_thread, (void *)task)) {
HAL_DANGER("onvif", "Can't create thread\n");
send_http_error(client_fd, 500);
free(task);
} else
pthread_detach(thread_id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Pull requests can exhaust server workers 🐞 Bug ⛨ Security

start_pullmessages() creates a detached thread for every PullMessages request without limiting
outstanding workers. The four-wait limit applies only after thread creation, and dispatch releases
the request's normal server slot, so a request flood can consume thread and socket resources outside
that limit.
Agent Prompt
## Issue description
The server creates an unbounded number of detached PullMessages workers despite its limited subscription waits.
## Fix Focus Areas
- src/server.c[472-494]
- src/server.c[734-739]
- src/onvif_event.c[129-153]
## Recommended Fix
Reserve a bounded worker slot before allocating or starting each pull worker; reject excess requests and release the slot on every worker exit path.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/onvif_event.c
Comment on lines +77 to +83
for (int i = 0; i < ONVIF_EVENT_MAX_SUBS && sub->id; i++)
if (!subs[i].id || (!subs[i].waiting && sub->waiting) ||
(!subs[i].waiting == !sub->waiting && subs[i].lastSeen < sub->lastSeen))
sub = &subs[i];
if (sub->id) pthread_cond_broadcast(&eventCond);

memset(sub, 0, sizeof(*sub));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

5. New subscribers can interrupt active pulls 🐞 Bug ☼ Reliability

onvif_event_subscribe() selects a waiting subscription for replacement when all four slots have
waiting pulls. A fifth subscription then clears that active slot and wakes its client into an
unknown-subscription response, even though the client's subscription has not reached its termination
time.
Agent Prompt
## Issue description
Creating a fifth subscription evicts an existing client when every slot has a waiting pull.
## Fix Focus Areas
- src/onvif_event.c[70-90]
- src/onvif.c[399-415]
## Recommended Fix
If every slot is occupied by an active waiting subscription, return a capacity failure instead of replacing one, and turn that failure into an appropriate SOAP fault.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/onvif.c
Comment on lines +366 to +368
if (*messageId)
snprintf(relates, sizeof(relates), "\n <wsa:RelatesTo>%s</wsa:RelatesTo>", messageId);
onvif_reply(response, respLen, "200 OK", eventxml, action, relates, body);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

6. A message id can corrupt event replies 🐞 Bug ≡ Correctness

onvif_reply_event() inserts the request's MessageID directly into a RelatesTo XML element
without escaping it. A request containing XML-significant characters in that field reaches the event
response handlers and produces malformed or injected SOAP header content.
Agent Prompt
## Issue description
Request-controlled message IDs are copied into event-response XML without escaping.
## Fix Focus Areas
- src/onvif.c[357-368]
- res/onvif/event.xml[3-5]
## Recommended Fix
XML-escape the extracted message ID before formatting the RelatesTo element, and check the formatted output's bounds.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/night.c Outdated
Comment thread src/gpio.c
Comment thread src/app_config.c
Comment thread src/night.c
Comment on lines +90 to +93
if (get_isp_gain(&gain)) {
HAL_WARNING("night", "No ISP gain on this platform, automatic switching disabled!\n");
break;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Informational

10. One failed gain read ends automatic switching 🐞 Bug ☼ Reliability

The ISP-gain loop breaks on the first non-zero return from get_isp_gain, and it logs "No ISP
gain on this platform" even when MI_ISP_AE_QueryExposureInfo exists and only failed once. That
single error stops day/night switching and the 10-minute IR cut re-pulse until the night thread is
restarted.
Agent Prompt
## Issue description
A transient ISP query error permanently stops gain-based switching and the periodic IR cut pulse.

## Fix Focus Areas
- src/night.c[90-93]
- src/hal/star/i6_hal.c[559-571]

## Recommended Fix
On a gain read error, set `held = 0` and `continue` instead of breaking. Only stop with the warning when the platform has no gain support (missing symbol or unsupported platform) or after N consecutive failures.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4876b37. Whether the platform has an ISP gain is now checked once, before the loop. Inside the loop a failed read resets held and retries on the next second, so switching and the IR cut re-pulse keep running; it is logged once per run of failures.

Checked on an SSC323 with a test build that fails the gain read for 3 s every 30 s: the loop kept running through every failure.

Jamp added 7 commits October 5, 2026 11:07
On SSC323 a char left the picture purple with the IR cut filter off,
the ISP reads a full word.
MI_BOOL is 4 bytes wide in MI_ISP_AE_QueryExposureInfo on SSC323. In the
dark the ipc017 reaches 64x sensor and 3x ISP gain; with the IR LED on it
settles around 6x, above the default day threshold.
- The night mode flag is set before its thread starts
- Automatic switching stops with a warning when there is no ISP gain
@Jamp

Jamp commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Pushed 4876b37 (on top of the updated #54, 7109f2c) for findings 3, 7, 8, 9 and 10; details are in each thread. Findings 1, 2, 4, 5 and 6 are not addressed in this PR: 2 is the same port 2 limitation discussed in #54, and 4 only applies with hold_s of 3 s or less.

The image running here with these fixes has been streaming normally since the update (1080p at 20 fps, ONVIF events consumed by a PullPoint client).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant