Make the thermal camera's frame rate hold on the wire
Setting Framerate_Hz while grabbing only thinned out what the SDK handed over; the camera went on sending 10 fps (and 50 from power-up). Set while the grab is stopped, once the stream has run, it holds. So the grab is restarted with the rate set when the first frame is in, and the packet rate of the camera's port is counted: if it still carries more than 1.5x the rate's frames, the restart is repeated (up to three times). On tower 111: 5.3 fps, 2 224 packets a second instead of 22 234, on the second attempt; system load 18 % down from about 29 %. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0076e623d0
commit
a326a758c4
|
|
@ -173,9 +173,10 @@ scripts for this are worth keeping; note that wrapping them in `timeout` SIGTERM
|
|||
were the camera's own NUC every 30 s (a 0.7 s pause in its stream); since 2026-10-15 the program runs the
|
||||
NUC at the start of a move instead. A NIR unit would be a third driver
|
||||
behind the same key.
|
||||
- The InfraTec SDK costs about a quarter of one core on the 3 Delta even between pictures: three threads
|
||||
it starts in `dll_init` and one per connection poll the raw socket in `nanosleep` loops (measured on
|
||||
tower 111, 2026-10-14). The frame rate does not change it. In the first minutes after deploying, the
|
||||
- The InfraTec SDK keeps the CPU awake: about 15 of its threads sleep 1 ms at a time, some 15 000 wake-ups
|
||||
a second, so the cores never settle and the CPU clocks them at turbo (2.8 GHz on the N5105) whenever they
|
||||
run. Until 2026-10-15 the camera also sent 50 fps (31 MB/s) because the frame rate was not taken; at the
|
||||
intended 5 fps the system load fell from about 29 % to 18 %, but the wake-ups remain. In the first minutes after deploying, the
|
||||
CPU guard slowed the scan once (86 °C, back to normal after 2 min). Watch Health `cpu_c` over a warm
|
||||
day before a tower without air flow gets a thermal camera.
|
||||
- Tower 111's colour profile does not apply: every start since 2026-10-07 logs `applying profile 'standard'
|
||||
|
|
|
|||
|
|
@ -950,8 +950,14 @@ Measured on tower 111 (2026-10-14, SDK 4.3.44, SN 2017744):
|
|||
- A profile switch takes about 3 s; frames keep the old scale until
|
||||
`Calib_ChangeInProgress` reaches 0, and the program takes none meanwhile.
|
||||
- The camera's frame counter (`ImageNum`) is always 0; `MissedPackets` was
|
||||
always 0 on the direct link. It delivers about 50 fps whatever `Framerate_Hz`
|
||||
says; the program polls it at 10 Hz.
|
||||
always 0 on the direct link.
|
||||
- Frame rate (2026-10-15): from power-up the camera sends 50 fps, 31 MB/s and
|
||||
22 000 packets a second. `Framerate_Hz` set right after connecting answers ok
|
||||
and changes nothing; set while grabbing it only thins out what the SDK hands
|
||||
over; set while the grab is stopped, after the stream has run, it holds on the
|
||||
wire. The program does that once the first frame is in and checks the packet
|
||||
rate of `[Camera.tir] interface` (5 Hz: about 2 200 packets a second); the log
|
||||
says `5.3 fps on enp1s0 (2224 packets/s), set on attempt 2`.
|
||||
- NUC (2026-10-15): the camera runs one every 30 s (`NUC_Interval`) and sends no
|
||||
frame for 650-760 ms meanwhile. `NUC_Interval = 0` stops that; `NUC_Perform`
|
||||
(1-point) runs one and returns when it is done, after about 0.67 s.
|
||||
|
|
|
|||
|
|
@ -138,6 +138,14 @@ struct TarisCameraSource::Impl {
|
|||
double last_cam_ts = -1.0; // its MemInfo time, ms
|
||||
long long polls_since_frame = 0;
|
||||
std::map<Ret, int> codes_since_frame; // getdata answers other than a new frame
|
||||
// The frame rate (see setFrameRate()): set by restarting the grab once the
|
||||
// first frame is in, then checked on the wire.
|
||||
int rate_attempts = 0;
|
||||
bool rate_pending = false; // restart the grab with it at the next frame
|
||||
long long wire_check_ms = 0; // steady: when to count packets; 0 = none
|
||||
long long wire_packets0 = -1;
|
||||
long long wire_start_ms = 0;
|
||||
size_t frame_bytes = 0;
|
||||
// NUC run by the program (Options::nuc_interval_ms).
|
||||
int nuc_interval_found = -1; // the camera's own timer, put back on close
|
||||
bool nuc_ours = false; // the camera's timer is off and we run it
|
||||
|
|
@ -418,15 +426,9 @@ struct TarisCameraSource::Impl {
|
|||
}
|
||||
setInt32(param::kPollingEnable, 1);
|
||||
|
||||
// The frame rate only once grabbing: set before the first startgrab after
|
||||
// connecting, the camera answers ok and keeps sending what it sent before
|
||||
// - 50 fps from power-up, 31 MB/s and 22 000 packets a second for the SDK
|
||||
// and the CPU to take in (measured on tower 111). Set now, it holds.
|
||||
float rate = static_cast<float>(o.frame_rate_hz);
|
||||
r = api->dev_setparam(dev, param::kFramerateHz, &rate, kTypeSingle);
|
||||
if (!ok(r))
|
||||
LOG_WARN << "thermal camera: cannot set " << o.frame_rate_hz
|
||||
<< " Hz: " << describe(r) << "; it runs at its own rate";
|
||||
rate_attempts = 0;
|
||||
rate_pending = true;
|
||||
wire_check_ms = 0;
|
||||
|
||||
std::string model;
|
||||
getString(param::kCameraName, model);
|
||||
|
|
@ -486,6 +488,7 @@ struct TarisCameraSource::Impl {
|
|||
return;
|
||||
const size_t need = static_cast<size_t>(w) * h * 2;
|
||||
if (bytes < 0 || static_cast<size_t>(bytes) < need) return;
|
||||
frame_bytes = need;
|
||||
|
||||
// Polling hands out the newest frame again until a newer one arrives. The
|
||||
// camera's frame counter stays 0 (TarisIR mini, SDK 4.3.44), so a new
|
||||
|
|
@ -584,6 +587,84 @@ struct TarisCameraSource::Impl {
|
|||
}
|
||||
}
|
||||
|
||||
// ---- frame rate ----------------------------------------------------------
|
||||
// The camera sends 50 fps from power-up: 31 MB/s and 22 000 packets a second
|
||||
// for the SDK and the CPU to take in, for one picture every few seconds. Set
|
||||
// right after connecting, Framerate_Hz answers ok and changes nothing; set
|
||||
// while grabbing, it thins out what the SDK hands over but the camera keeps
|
||||
// sending. Set while the grab is stopped, after the stream has run, it holds
|
||||
// on the wire (tower 111: 5 Hz, 2 100 packets a second). So that is what is
|
||||
// done once the first frame is in, and checked on the port's packet count.
|
||||
void setFrameRate() {
|
||||
float rate = static_cast<float>(o.frame_rate_hz);
|
||||
const Ret r = api->dev_setparam(dev, param::kFramerateHz, &rate, kTypeSingle);
|
||||
++rate_attempts;
|
||||
if (!ok(r))
|
||||
LOG_WARN << "thermal camera: cannot set " << o.frame_rate_hz << " Hz: " << describe(r)
|
||||
<< "; it runs at its own rate";
|
||||
}
|
||||
|
||||
static long long rxPackets(const std::string& interface) {
|
||||
if (interface.empty()) return -1;
|
||||
std::ifstream f("/sys/class/net/" + interface + "/statistics/rx_packets");
|
||||
long long v = -1;
|
||||
f >> v;
|
||||
return f ? v : -1;
|
||||
}
|
||||
|
||||
void restartForRate() {
|
||||
rate_pending = false;
|
||||
api->dev_stopgrab(dev, 0);
|
||||
setFrameRate();
|
||||
setInt32(param::kPollingEnable, 1);
|
||||
const Ret r = api->dev_startgrab(dev, 0);
|
||||
setInt32(param::kPollingEnable, 1);
|
||||
last_frame_steady = steadyMs(); // grace for the restart
|
||||
if (!ok(r)) {
|
||||
setOffline("cannot restart grabbing: " + describe(r));
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
wire_check_ms = steadyMs() + 1000; // count packets once the stream is up again
|
||||
wire_packets0 = -1;
|
||||
}
|
||||
|
||||
// Packets per second on the camera's port against what the rate should send.
|
||||
void checkWire() {
|
||||
if (wire_check_ms == 0 || steadyMs() < wire_check_ms) return;
|
||||
if (o.interface.empty()) {
|
||||
wire_check_ms = 0;
|
||||
LOG_INFO << "thermal camera: " << o.frame_rate_hz
|
||||
<< " fps set; not checked on the wire (no [Camera.tir] interface)";
|
||||
return;
|
||||
}
|
||||
const long long now = steadyMs(), pk = rxPackets(o.interface);
|
||||
if (pk < 0) { wire_check_ms = 0; return; }
|
||||
if (wire_packets0 < 0) { // start counting
|
||||
wire_packets0 = pk;
|
||||
wire_start_ms = now;
|
||||
wire_check_ms = now + 3000;
|
||||
return;
|
||||
}
|
||||
wire_check_ms = 0;
|
||||
const double pps = (pk - wire_packets0) * 1000.0 / std::max<long long>(1, now - wire_start_ms);
|
||||
const double per_fr = frame_bytes > 0 ? frame_bytes / 1464.0 : 420.0; // GVSP payload
|
||||
const double fps_wire = pps / per_fr;
|
||||
if (fps_wire <= o.frame_rate_hz * 1.5) {
|
||||
LOG_INFO << "thermal camera: " << std::lround(fps_wire * 10) / 10.0 << " fps on "
|
||||
<< o.interface << " (" << std::lround(pps) << " packets/s), set on attempt "
|
||||
<< rate_attempts;
|
||||
} else if (rate_attempts >= 3) {
|
||||
LOG_WARN << "thermal camera: still " << std::lround(fps_wire) << " fps on " << o.interface
|
||||
<< " (" << std::lround(pps) << " packets/s), not " << o.frame_rate_hz
|
||||
<< "; frame rate not taken";
|
||||
} else {
|
||||
LOG_INFO << "thermal camera: " << std::lround(fps_wire) << " fps on " << o.interface
|
||||
<< ", not " << o.frame_rate_hz << "; restarting the grab again";
|
||||
rate_pending = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- NUC ---------------------------------------------------------------
|
||||
// Switch the camera's own NUC timer off: from now on the program runs the
|
||||
// NUC, while the gimbal moves. The timer's value goes back on close. (Read
|
||||
|
|
@ -787,6 +868,9 @@ struct TarisCameraSource::Impl {
|
|||
if (!connected) continue;
|
||||
serviceNuc();
|
||||
poll();
|
||||
if (rate_pending && last_cam_ts >= 0.0) restartForRate(); // once a frame is in
|
||||
if (!connected) continue;
|
||||
checkWire();
|
||||
|
||||
const long long now = steadyMs();
|
||||
if (now - last_frame_steady > o.stall_ms) {
|
||||
|
|
|
|||
|
|
@ -233,6 +233,8 @@ TEST_CASE("the thermal camera connects, trying adapters until one answers") {
|
|||
t.cam.start();
|
||||
t.sdk.push(29315, 57315);
|
||||
REQUIRE(t.online());
|
||||
// The rate is set by restarting the grab once the first frame is in.
|
||||
CHECK(waitFor([&] { std::lock_guard<std::mutex> l(t.sdk.m); return t.sdk.rate > 49.f; }));
|
||||
std::lock_guard<std::mutex> l(t.sdk.m);
|
||||
REQUIRE(t.sdk.connect_attempts.size() >= 4);
|
||||
CHECK(t.sdk.connect_attempts.front() == "SN:2017744|00:0e:c6:aa:bb:cc|192.168.0.200#0");
|
||||
|
|
|
|||
Loading…
Reference in New Issue