<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged radar</title>
<link>https://coderlegion.com/tag/radar</link>
<description>Powered by Question2Answer</description>
<item>
<title>What SMS and Voice Calls Give a Fall Alert That Push Notifications Cannot</title>
<link>https://coderlegion.com/33282/what-sms-and-voice-calls-give-a-fall-alert-that-push-notifications-cannot</link>
<description>&lt;p&gt;Every fall-alarm article on this site ends the same way: &lt;strong&gt;step five is yours.&lt;/strong&gt; The radar detects the collapse, waits out its configured delay, publishes &lt;code&gt;fallStatus&lt;/code&gt;, and on the finished units even speaks to the person on the floor. Then it stops. Who gets told, how, and what happens when nobody answers is not in any datasheet, because none of it belongs to the sensor.&lt;/p&gt;
&lt;p&gt;This article is about that fifth step — specifically the last leg of it, getting the alert onto the right phone. The argument is unfashionable but practical: &lt;strong&gt;the fallback layer of a fall alert should be SMS and a placed call, not a push notification.&lt;/strong&gt; Here is what each approach actually buys you, and what neither ever will.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;This is engineering guidance for integrators. It is not a medical claim and not a safety certification. See the closing note.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;step-five-is-a-chain-not-a-message&quot;&gt;Step five is a chain, not a message&lt;/h3&gt;
&lt;p&gt;&quot;Escalation&quot; gets drawn as one box in an architecture diagram, and then implemented as one call in code — &lt;code&gt;send_notification()&lt;/code&gt;. It is really three legs, and they fail differently:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Transport&lt;/strong&gt; — the alarm travels from the radar to your backend (MQTT).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reach&lt;/strong&gt; — your backend gets the alert onto the recipient's device.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Acknowledgment&lt;/strong&gt; — a human learns about it and takes responsibility.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Most engineering effort goes into leg 1, because it is the leg the datasheet describes. Most field complaints come from legs 2 and 3. &quot;The radar missed it&quot; is very often &quot;the radar reported correctly and nobody found out.&quot;&lt;/p&gt;
&lt;p&gt;And for our part of the chain, the detail that changes everything: &lt;strong&gt;the buyer is usually not the user.&lt;/strong&gt; An adult child installs this for a parent. The question is not &quot;will our users install our app&quot; — it is whether the person who must be reachable has done their part before the one day it counts.&lt;/p&gt;
&lt;h3 id=&quot;two-networks-not-two-vendors&quot;&gt;Two networks, not two vendors&lt;/h3&gt;
&lt;p&gt;The choice between push and telecom is usually framed as picking a service provider. It is not. It is picking which &lt;strong&gt;network&lt;/strong&gt; carries the last leg.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;App push &lt;/th&gt;&lt;th&gt;SMS / placed call&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Carried on &lt;/td&gt;&lt;td&gt;The internet — Wi-Fi or mobile data &lt;/td&gt;&lt;td&gt;The operator's signalling and voice networks&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Needs a working data connection &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Requires an app installed &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Requires notification permission &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Requires an account on the recipient's side &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Who retries when the device is unreachable &lt;/td&gt;&lt;td&gt;You, or nobody &lt;/td&gt;&lt;td&gt;The operator, for SMS&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Delivery evidence &lt;/td&gt;&lt;td&gt;Vendor receipt &lt;/td&gt;&lt;td&gt;Operator delivery report / answered-call state&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Two consequences follow directly, and they are the whole argument:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Push asks something of the recipient first.&lt;/strong&gt; There must be an installed app, a granted permission, and a live session with a push service before your alert has anywhere to land. Every one of those is a condition you do not control.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;An alert path that only works when everything else is also working is not a fallback.&lt;/strong&gt; It is a second copy of the same dependency.&lt;/p&gt;
&lt;h3 id=&quot;what-the-telecom-path-gives-you&quot;&gt;What the telecom path gives you&lt;/h3&gt;
&lt;p&gt;Six properties, roughly in order of how much they matter here.&lt;/p&gt;
&lt;h4 id=&quot;1-nothing-is-required-of-the-recipient&quot;&gt;1. Nothing is required of the recipient&lt;/h4&gt;
&lt;p&gt;A phone number is enough. No install step, no sign-up, no permission granted weeks earlier, no app version quietly gone stale. A number works on a decade-old handset and for a relative who visits once a year and appears in nobody's contact graph.&lt;/p&gt;
&lt;p&gt;It also removes the most expensive class of support ticket: the one where the alert never arrived because notifications were muted, an app was signed out, or an account was never created. None of those failure modes exist on a path that only needs a number.&lt;/p&gt;
&lt;h4 id=&quot;2-it-does-not-depend-on-a-data-connection&quot;&gt;2. It does not depend on a data connection&lt;/h4&gt;
&lt;p&gt;SMS travels on signalling; a call travels on the voice network. Neither needs an active IP session. The cases this covers are ordinary ones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The recipient has &lt;strong&gt;no data allowance left&lt;/strong&gt; this month.&lt;/li&gt;
&lt;li&gt;The recipient is somewhere with &lt;strong&gt;one bar of signal&lt;/strong&gt; — enough for a text and a call, not enough for anything else to load.&lt;/li&gt;
&lt;li&gt;The phone is in &lt;strong&gt;battery-saving mode&lt;/strong&gt;, which throttles background data and leaves calls alone.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;3-the-retry-belongs-to-someone-else&quot;&gt;3. The retry belongs to someone else&lt;/h4&gt;
&lt;p&gt;This is the one property no application-layer approach can replicate. SMS is &lt;strong&gt;store-and-forward&lt;/strong&gt;: if the handset is off, out of coverage, or underground, the message is held by the network and delivered when the device returns.&lt;/p&gt;
&lt;p&gt;It is time-limited — the retention window is set by the operator and by your provider's configuration, so &lt;strong&gt;confirm it rather than assume it&lt;/strong&gt;. But the retry loop runs inside the operator's infrastructure, unattended, whether or not your server is awake. With push, the same offline device is your problem, and whatever the platform holds, it holds briefly and on conditions you do not set.&lt;/p&gt;
&lt;p&gt;Know the asymmetry going in: &lt;strong&gt;a call has no store-and-forward.&lt;/strong&gt; An unanswered call is simply unanswered. You build the retry queue, you choose the intervals, and you decide how many contacts deep it goes.&lt;/p&gt;
&lt;h4 id=&quot;4-an-answered-call-is-an-acknowledgment&quot;&gt;4. An answered call is an acknowledgment&lt;/h4&gt;
&lt;p&gt;Push and SMS can both report &quot;delivered&quot;. Neither tells you a human engaged. An alert sitting unread in a notification tray and an alert nobody received are indistinguishable from your backend's point of view.&lt;/p&gt;
&lt;p&gt;An answered call is different in kind: a person picked up. That is the closest thing to proof-of-reach available without shipping an app, and it is why the call belongs at the &lt;em&gt;end&lt;/em&gt; of the chain rather than the start.&lt;/p&gt;
&lt;p&gt;You can extend that signal to everyone rather than only the first person to answer, and still ship no application: &lt;strong&gt;prompt for a digit press during the call and treat it as an acknowledge.&lt;/strong&gt; Works on any handset that can receive the call, needs no account, and gives your backend a real event to record.&lt;/p&gt;
&lt;h4 id=&quot;5-delivery-you-can-measure&quot;&gt;5. Delivery you can measure&lt;/h4&gt;
&lt;p&gt;SMS returns a delivery report; a call returns answered or unanswered. Both come from the operator's side. So your escalation logic can be built on &lt;strong&gt;evidence&lt;/strong&gt; instead of a timeout guess:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fall event arrives ──&amp;gt; SMS to all contacts in parallel
                  ──&amp;gt; read delivery reports
                  ──&amp;gt; wait a bounded window for an acknowledge
                  ──&amp;gt; none received: place calls in order, stop at first answer
                  ──&amp;gt; nobody answered: next contact, bounded retries
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every branch above is triggered by something arriving. That is worth contrasting with the alternative, where &quot;probably delivered&quot; is the strongest signal you get and silence has to be interpreted.&lt;/p&gt;
&lt;h4 id=&quot;6-the-alerting-logic-stays-yours&quot;&gt;6. The alerting logic stays yours&lt;/h4&gt;
&lt;p&gt;When the notification path is somebody else's app, the alert experience — who is told, in what order, what happens when nobody responds — is largely defined by that platform. When you place SMS and calls from your own service, the contact list, ordering, retry policy, and audit trail are software you own.&lt;/p&gt;
&lt;p&gt;For an integrator that is the difference between a feature you tune per customer and a feature you request. It is also the part that can be priced and supported, rather than inherited.&lt;/p&gt;
&lt;h3 id=&quot;what-it-looks-like-assembled&quot;&gt;What it looks like assembled&lt;/h3&gt;
&lt;p&gt;Cheapest and most reliable first, escalating only on the absence of an answer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer 0 — on site, with or without a network.&lt;/strong&gt; Part of this already exists in hardware. The finished fall radar speaks after a fall and accepts a spoken reply, so a resident who says &quot;I am OK&quot; stands the local alarm down — see the phrase set and its limits in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/product/2026/10/17/fall-radar-guide.html&quot;&gt;fall radar guide&lt;/a&gt;. If you are building on a module instead, &lt;strong&gt;R60AFD1-V&lt;/strong&gt; raises &lt;code&gt;GP1&lt;/code&gt; the moment a fall is concluded, which is enough to drive a local buzzer or relay with no parser at all — see &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/gpio-vs-uart-fall-alarm-without-parser.html&quot;&gt;GPIO vs UART&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/fall-alarm-relay-from-gp1.html&quot;&gt;driving a relay from GP1&lt;/a&gt;. Know the variant difference up front: the &lt;strong&gt;24 GHz fall module has no hardware alarm output&lt;/strong&gt; — its &lt;code&gt;S1&lt;/code&gt;/&lt;code&gt;S2&lt;/code&gt; are presence and motion only, so for that variant Layer 0 has to be your firmware's job.&lt;/p&gt;
&lt;p&gt;Keep this layer even though it looks redundant. It is the only one that still works when the home router has failed, which is not an exotic scenario.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Verify this one on the bench.&lt;/strong&gt; In the documented alarm chain, &lt;code&gt;fallStatus&lt;/code&gt; is published &lt;em&gt;before&lt;/em&gt; the local voice step. Confirm what your broker actually sees when a resident cancels locally — if that cancellation does not reach your system, then the stand-down decision lives entirely in your own logic, which is exactly why Layer 2 exists.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Layer 1 — reach.&lt;/strong&gt; SMS to every contact, in parallel.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer 2 — acknowledgment.&lt;/strong&gt; Wait a bounded window. No acknowledge → place calls, first contact first, stopping at the first answer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The acknowledge mechanism.&lt;/strong&gt; Do not skip it. Without a way for the receiving person to say &quot;I am handling it&quot;, carers learn to treat alerts as background noise, and that defeats every layer above. The digit press during the call is the cheapest version that needs no app; a reply keyword works too, if your number can receive.&lt;/p&gt;
&lt;p&gt;Set the wait window honestly: long enough that someone can read a text and respond, short enough to still be useful. It is a product decision and belongs in the configuration, visible to whoever commissions the installation.&lt;/p&gt;
&lt;h3 id=&quot;the-first-half-of-the-chain-is-not-immune&quot;&gt;The first half of the chain is not immune&lt;/h3&gt;
&lt;p&gt;Everything above assumes the fall reached your backend. Worth a short section, because a telecom fallback tends to get blamed for failures upstream of it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use QoS 1 on fall events&lt;/strong&gt; and a persistent session on your consumer, so a restarting backend does not miss the one message that mattered. The settings that actually help are listed in &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/25/reading-radar-data-your-own-app.html&quot;&gt;Reading Radar Data in Your Own Application&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Payloads are sparse by design.&lt;/strong&gt; Everything is reported on change, so no news is the normal state, not a fault.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Device health has a hard limit.&lt;/strong&gt; An offline device cannot report that it is offline. &lt;code&gt;online&lt;/code&gt; is generated by the broker when a session drops, which is real evidence; &lt;code&gt;heartBeat&lt;/code&gt; can only ever come from a radar that still has a link to send it with. Read &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/27/online-heartbeat-radar-health.html&quot;&gt;Online and HeartBeat&lt;/a&gt; before writing any timeout-based logic — it is the clearest treatment of why silence proves nothing, and it lists what to measure at commissioning instead.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A network outage removes all of it at once.&lt;/strong&gt; No choice of downstream channel recovers an alert that never left the house. Which is the argument for Layer 0.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;what-telecom-channels-do-not-solve&quot;&gt;What telecom channels do not solve&lt;/h3&gt;
&lt;p&gt;Stated plainly, because overselling this would be worse than not writing it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reach is not attention.&lt;/strong&gt; This article is about getting the alert onto the phone. Whether somebody notices within thirty seconds is a separate problem, solved by escalation design and by keeping alerts rare enough to still mean something — which is a false-alarm question at the sensor end, covered in &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/19/fall-alarm-market-demand-false-alarms.html&quot;&gt;Real Demand, Hard Physics&lt;/a&gt;. A perfect SMS path delivering constant noise is still a product nobody trusts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Numbers get filtered.&lt;/strong&gt; Operator spam filtering and unknown-call screening are real, particularly for numbers that have carried other traffic before. Use a consistent, registered sender identity, and ask recipients to save the number to contacts during setup. Plan for this rather than discovering it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Templates and consent are paperwork.&lt;/strong&gt; Signatures and message templates need registration and approval, and voice broadcasting is regulated more tightly than SMS in most places. Get explicit opt-in from the contact at binding time — not buried in terms, but an affirmative &quot;yes, call me for fall alarms&quot;. With someone's phone number, that is also simply the decent thing to do.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bound your retry policy.&lt;/strong&gt; Cost arrives per message and per call minute. Fine at true-fall frequency; not fine if a misconfigured dwell alarm starts placing calls every few minutes overnight. Put a ceiling on attempts per event.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One regional caveat, stated rather than buried.&lt;/strong&gt; How wide the gap really is depends on your market. Where handsets carry a system-level push service that is exempt from power management, an app path can perform well and the argument narrows to &quot;installs are still a barrier&quot;. Where handsets ship without one — which covers a large share of Android devices — app push depends on per-manufacturer channels and per-device background policy, and the gap is wide. &lt;strong&gt;Verify this for the markets you actually ship to&lt;/strong&gt; rather than taking either sentence from us.&lt;/p&gt;
&lt;h3 id=&quot;where-to-go-next&quot;&gt;Where to go next&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Step five across three radars in one home: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/23/aging-in-place-care-system-three-radars.html&quot;&gt;Building an Aging-in-Place Care System&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The hand-off itself, step by step: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/product/2026/10/17/fall-radar-guide.html&quot;&gt;the fall radar guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;What genuinely defeats detection: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/fall-detection-failure-modes.html&quot;&gt;fall detection failure modes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Getting events reliably to your backend: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/25/reading-radar-data-your-own-app.html&quot;&gt;Reading Radar Data in Your Own Application&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Device health before you alert on it: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/27/online-heartbeat-radar-health.html&quot;&gt;Online and HeartBeat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The broker underneath: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/23/apache-artemis-mqtt-broker-radar.html&quot;&gt;Apache Artemis setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;More questions answered: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/faq.html&quot;&gt;the FAQ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;a-note-on-what-this-is&quot;&gt;A note on what this is&lt;/h3&gt;
&lt;p&gt;A fall radar is a sensing device. It does not diagnose, does not treat, does not summon emergency services, and does not replace human checks or a call system. It detects motion it cannot promise to interpret correctly in every room, and it will produce false alarms.&lt;/p&gt;
&lt;p&gt;Everything above concerns making sure a reported event reaches a person. That is a communication problem, one layer removed from whether any given fall was caught at all — and no receiving channel, however reliable, improves the detection underneath it.&lt;/p&gt;
&lt;p&gt;Evaluation units and modules are on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.tindie.com/stores/fuxinsuo/&quot;&gt;Tindie&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Evaluation units and modules are on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.tindie.com/stores/fuxinsuo/&quot;&gt;Tindie&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33282/what-sms-and-voice-calls-give-a-fall-alert-that-push-notifications-cannot</guid>
<pubDate>Sun, 01 Nov 2026 04:34:41 +0000</pubDate>
</item>
<item>
<title>What Data-Driven Algorithms Changed in Contactless Sleep Sensing, and What They Could Not</title>
<link>https://coderlegion.com/33155/what-data-driven-algorithms-changed-in-contactless-sleep-sensing-and-what-they-could-not</link>
<description>&lt;hr&gt;
&lt;p&gt;title: &quot;What Data-Driven Algorithms Changed in Contactless Sleep Sensing, and What They Could Not&quot;&lt;br&gt;
published: true&lt;br&gt;
tags: radar, iot, sleep, health&lt;br&gt;
canonical_url: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/31/ai-sleep-staging-radar-limits.html&quot;&gt;https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/31/ai-sleep-staging-radar-limits.html&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;description-sleep-staging-on-a-contactless-radar-went-from-hand-tuned-thresholds-to-learned-models-here-is-what-that-migration-actually-improved-and-the-part-of-the-problem-it-does-not-touch-&quot;&gt;description: &quot;Sleep staging on a contactless radar went from hand-tuned thresholds to learned models. Here is what that migration actually improved, and the part of the problem it does not touch.&quot;&lt;/h3&gt;
&lt;p&gt;The largest change in contactless sleep sensing over the last few years has not been in the radio. The same bands, similar front-ends, comparable antenna layouts — what changed is the layer that turns motion, respiration and heart rate into a statement about sleep.&lt;/p&gt;
&lt;p&gt;That layer used to be written by hand. It is increasingly learned from data. This article is about what that migration actually bought, and — more importantly — about the part of the problem it does not touch.&lt;/p&gt;
&lt;h3 id=&quot;the-hand-tuned-era&quot;&gt;The hand-tuned era&lt;/h3&gt;
&lt;p&gt;A rule-based sleep stage classifier looks like a set of thresholds. Movement below a value for a number of minutes means light sleep; a longer quiet interval with a slower, more regular breathing rate means deep sleep; a burst of movement followed by stillness means a transition. Each threshold is chosen by a person, tested on a handful of nights, and adjusted.&lt;/p&gt;
&lt;p&gt;The weakness is not that the rules are wrong. It is that the variables they would need are not expressible as constants:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Individual differences.&lt;/strong&gt; Resting heart rate, breathing depth and the amount of movement during sleep vary substantially between people, and between an eighty-year-old and a thirty-year-old.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bedding and season.&lt;/strong&gt; A thick winter quilt changes both the movement signature reaching the radar and the thermal environment. So does a fan running in August.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One person or two.&lt;/strong&gt; Two bodies in the same bed produce a composite signal no single-person rule describes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Body position.&lt;/strong&gt; Lying on the back, on the side, or curled up changes which parts of the torso move and how much of that motion reaches the antenna.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of these would need its own branch, and the branches interact. A system built this way works in the conditions it was tuned in and degrades quietly outside them.&lt;/p&gt;
&lt;h3 id=&quot;what-learning-from-data-changed&quot;&gt;What learning from data changed&lt;/h3&gt;
&lt;p&gt;The difference is not that a model is smarter than a person. It is that the decision boundary is fitted rather than guessed.&lt;/p&gt;
&lt;p&gt;Instead of one person choosing where to put a threshold, a large number of labelled nights are used to find which combinations of features actually separate the states. The features are the same ones the hand-tuned rules used — movement over time, breathing rate and its regularity, heart rate and its variability, time since getting into bed, transitions between positions — but they are considered together, as a pattern over a window rather than as independent conditions.&lt;/p&gt;
&lt;p&gt;Three practical consequences follow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stability improved first.&lt;/strong&gt; The most visible change is not a more accurate stage label but fewer of them. A hand-tuned classifier can flicker between light and deep several times in a minute when a value sits near a boundary. A fitted boundary produces a smoother sequence, which is what makes a night's output readable at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The edges got better.&lt;/strong&gt; Falling asleep and waking up are transitions with recognisable signatures — breathing rate changes, the movement pattern changes, and then posture settles. Learned models handle these boundaries better than a rule that waits for a fixed number of quiet minutes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Individual baselines became usable.&lt;/strong&gt; Once the model is fitted against many nights from many people, it can adapt to a particular person's typical pattern rather than comparing everyone against one absolute threshold. This is where most of the practical gain has come from.&lt;/p&gt;
&lt;h3 id=&quot;what-did-not-change&quot;&gt;What did not change&lt;/h3&gt;
&lt;p&gt;This is the part worth being precise about, because the improvement in stability is easily read as an improvement in accuracy against ground truth.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The inputs did not change.&lt;/strong&gt; A contactless radar observes body movement, respiration, heart rate, and — depending on the configuration — position and posture. It does not observe brain activity, eye movement, or muscle tone. Those are the signals that sleep stages are defined by.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The clinical definition did not change.&lt;/strong&gt; Sleep staging comes from polysomnography: EEG for cortical activity, EOG for eye movement, EMG for muscle tone. A stage label produced without any of those three is an inference from correlates, and it stays an inference no matter how the inference is computed. The reasoning is set out in &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/19/sleep-sensing-ring-watch-radar-module-infrared.html&quot;&gt;Choosing a Sleep Sensing Approach&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Agreement with a wearable is not accuracy against reality.&lt;/strong&gt; This distinction matters more as models become better. If a model is trained or tuned so that its output matches a wristband or a ring, what improves is &lt;strong&gt;agreement with that device&lt;/strong&gt;. The wearable is itself estimating stages from an accelerometer and an optical pulse sensor, and its own agreement with polysomnography is limited. Two devices agreeing with each other tells you they are consistent. It does not tell you either is right.&lt;/p&gt;
&lt;p&gt;That is not an argument against the practice — comparing against a wearable is often the only large-scale source of labels available outside a sleep laboratory. It is an argument for keeping the two statements separate when describing what a device does.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The population in the training data is still the population it works on.&lt;/strong&gt; A model fitted on one set of people, in one set of beds, with one range of body types, carries those conditions with it. The failure mode is not random error; it is systematic error on whoever was under-represented.&lt;/p&gt;
&lt;h3 id=&quot;what-the-radar-actually-publishes&quot;&gt;What the radar actually publishes&lt;/h3&gt;
&lt;p&gt;The published output for the sleep radar makes the boundary visible, which is useful: the limits are on the surface rather than buried in an opaque score.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Field &lt;/th&gt;&lt;th&gt;Content&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;sleepStatus&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Sleep state&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;getIntoBed&lt;/code&gt; &lt;/td&gt;&lt;td&gt;In bed / out of bed&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;awakeDuration&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Time awake&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;lightSleepDuration&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Time in light sleep&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;deepSleepDuration&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Time in deep sleep&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;sleepScore&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Sleep score&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;sleepComprehensiveStatus&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Composite sleep state&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;sleepQualityAnalysis&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Sleep quality analysis&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;abnormalSleep&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Sleep abnormality flag&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Awake, light and deep are present. &lt;strong&gt;REM is not.&lt;/strong&gt; There is no rapid-eye-movement stage in the published protocol, which is the honest outcome of the constraint described above: eye movement is not among the measurable inputs, so a REM label would have to be inferred from heart-rate and movement patterns alone.&lt;/p&gt;
&lt;p&gt;The supporting measurements arrive at their own cadences — breathing rate and heart rate roughly every three seconds, the body-movement index about once a second on the finished units — which is why staging is computed over windows rather than from a moment.&lt;/p&gt;
&lt;h3 id=&quot;how-to-read-the-output&quot;&gt;How to read the output&lt;/h3&gt;
&lt;p&gt;Given all of the above, the output is best used as it was built to be used:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;As a trend, not a measurement.&lt;/strong&gt; Nightly time in bed, number of times out of bed, and the distribution across states are meaningful when compared against the same person's previous weeks. A single night's absolute number is much weaker. This is the same principle as &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/26/storing-radar-data-retention-trends.html&quot;&gt;Storing Radar Data: Retention, Downsampling, and Why Trends Beat Instant Values&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;As a prompt, not a diagnosis.&lt;/strong&gt; &lt;code&gt;abnormalSleep&lt;/code&gt; and &lt;code&gt;sleepScore&lt;/code&gt; are flags that something in the night differed from the usual pattern. What follows from a flag is a person checking on a person.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;With the stage labels coarse.&lt;/strong&gt; Three states plus an awake duration describe a night at a resolution that is defensible. Finer claims are not supported by the inputs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Contactless sensing is useful precisely because nobody has to wear or charge anything, and because it works night after night without anyone remembering to enable it. That is worth a great deal. It is a different thing from a sleep study, and it is better for the comparison to be stated than left implied.&lt;/p&gt;
&lt;h3 id=&quot;where-to-go-next-1&quot;&gt;Where to go next&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The comparison across four sensing approaches, and where polysomnography sits: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/19/sleep-sensing-ring-watch-radar-module-infrared.html&quot;&gt;Choosing a Sleep Sensing Approach&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;What the sleep radar publishes, field by field: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/product/2026/10/17/sleep-radar-guide.html&quot;&gt;Sleep Radar Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Keeping months of it, and why the trend is the signal: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/26/storing-radar-data-retention-trends.html&quot;&gt;Storing Radar Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Reading the stream directly: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/25/reading-radar-data-your-own-app.html&quot;&gt;Reading Radar Data in Your Own Application&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Treating quoted specifications with care: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/radar-spec-limits.html&quot;&gt;Three mmWave Radar Specs That Are Almost Always Exaggerated&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Contactless sensing observes movement, respiration and heart rate. It does not observe sleep physiology, and a stage label derived from it is an inference.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;{% cta &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.tindie.com/stores/fuxinsuo/&quot;&gt;https://www.tindie.com/stores/fuxinsuo/&lt;/a&gt; %} Check out our mmWave radar modules for sleep and presence sensing {% endcta %}&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33155/what-data-driven-algorithms-changed-in-contactless-sleep-sensing-and-what-they-could-not</guid>
<pubDate>Fri, 30 Oct 2026 22:40:58 +0000</pubDate>
</item>
<item>
<title>Antenna Channels, Virtual Aperture, and What More of Them Actually Buys You</title>
<link>https://coderlegion.com/33026/antenna-channels-virtual-aperture-and-what-more-of-them-actually-buys-you</link>
<description>&lt;p&gt;Antenna Channels, Virtual Aperture, and What More of Them Actually Buys You&lt;/p&gt;
&lt;p&gt;Channel counts are the specification most often quoted when radar modules are compared: one transmit and three receive, four and four, six and six. The comparison is meaningful, but it is often read as a linear quality scale, and it is not one. What a channel buys depends entirely on what the radar is being asked to work out.&lt;/p&gt;
&lt;p&gt;This article sets out what channel count determines, what it does not, and how that maps onto the three jobs these modules are built for — presence, breathing and sleep, and fall detection.&lt;/p&gt;
&lt;h3 id=&quot;what-a-channel-count-means&quot;&gt;What a channel count means&lt;/h3&gt;
&lt;p&gt;With &lt;code&gt;T&lt;/code&gt; transmit antennas and &lt;code&gt;R&lt;/code&gt; receive antennas, time-division MIMO produces a &lt;strong&gt;virtual array&lt;/strong&gt; of:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;N = T x R
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;virtual channels. The virtual array, not the physical one, sets the angular capability. Two consequences follow directly, and a third follows from how the elements are placed.&lt;/p&gt;
&lt;h4 id=&quot;angular-resolution&quot;&gt;Angular resolution&lt;/h4&gt;
&lt;p&gt;With elements at half-wavelength spacing, the angular resolution at broadside is approximately:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;theta_res ≈ 2 / N  radians  =  114.6 / N  degrees
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Configuration &lt;/th&gt;&lt;th&gt;Virtual channels &lt;/th&gt;&lt;th&gt;Approximate angular resolution&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1T1R &lt;/td&gt;&lt;td&gt;1 &lt;/td&gt;&lt;td&gt;none — no angle at all&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1T3R &lt;/td&gt;&lt;td&gt;3 &lt;/td&gt;&lt;td&gt;≈ 38°&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2T4R &lt;/td&gt;&lt;td&gt;8 &lt;/td&gt;&lt;td&gt;≈ 14°&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4T4R &lt;/td&gt;&lt;td&gt;16 &lt;/td&gt;&lt;td&gt;≈ 7.2°&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;6T6R &lt;/td&gt;&lt;td&gt;36 &lt;/td&gt;&lt;td&gt;≈ 3.2°&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;These are theoretical figures, at broadside and with good signal-to-noise ratio. Real performance is worse, and it degrades further away from the antenna boresight.&lt;/p&gt;
&lt;h4 id=&quot;coherent-combining-gain&quot;&gt;Coherent combining gain&lt;/h4&gt;
&lt;p&gt;Processing across &lt;code&gt;N&lt;/code&gt; channels gives a combining gain of about:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;G = 10 x log10(N)  dB
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Configuration &lt;/th&gt;&lt;th&gt;Combining gain&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1T3R &lt;/td&gt;&lt;td&gt;≈ 4.8 dB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2T4R &lt;/td&gt;&lt;td&gt;≈ 9 dB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4T4R &lt;/td&gt;&lt;td&gt;≈ 12 dB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;6T6R &lt;/td&gt;&lt;td&gt;≈ 15.6 dB&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Because detection range scales as the fourth root of signal-to-noise ratio, a gain difference of &lt;code&gt;G&lt;/code&gt; dB changes range by a factor of &lt;code&gt;10^(G/40)&lt;/code&gt;. Going from three virtual channels to sixteen — about 7 dB — extends range by roughly 1.5 times. That is a real improvement, and it is much smaller than the ratio of channel counts suggests.&lt;/p&gt;
&lt;h4 id=&quot;elevation&quot;&gt;Elevation&lt;/h4&gt;
&lt;p&gt;Angular resolution in azimuth is not enough when the question is about height. Estimating elevation requires receive elements distributed in the &lt;strong&gt;vertical&lt;/strong&gt; direction. A single row of elements gives azimuth only, no matter how many there are. An L-shaped or triangular arrangement gives both, at lower resolution than either.&lt;/p&gt;
&lt;p&gt;This is the one place where the physical layout matters more than the count. Three elements in a triangle can say something about height; eight elements in a straight line cannot.&lt;/p&gt;
&lt;h3 id=&quot;what-a-channel-count-does-not-decide&quot;&gt;What a channel count does not decide&lt;/h3&gt;
&lt;p&gt;Two of the most important capabilities in these applications are set by other parameters entirely.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Range resolution&lt;/strong&gt; depends on swept bandwidth, not on channels:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Δd = c / (2B)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Displacement sensitivity&lt;/strong&gt; depends on wavelength and phase, not on channels:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Δφ = 4π x Δx / λ
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At 61–62.5 GHz the wavelength is about 4.9 mm, which gives roughly 144° of phase change per millimetre of displacement. That is what lets breathing be measured, and adding channels does not change it.&lt;/p&gt;
&lt;p&gt;A worked example makes the boundary clear. Separating two fingers about 2 cm apart at a distance of 0.5 m needs an angular resolution near 2.3°, which means roughly 50 virtual channels — and it also needs a range resolution better than 2 cm, which needs about 7.5 GHz of bandwidth. Neither is available in a 60 GHz module. Fine spatial detail of that kind is imaging-radar territory, not the territory of a four-by-four or a six-by-six.&lt;/p&gt;
&lt;h3 id=&quot;what-the-three-jobs-actually-require&quot;&gt;What the three jobs actually require&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Job &lt;/th&gt;&lt;th&gt;Question being answered &lt;/th&gt;&lt;th&gt;Capability it actually rests on &lt;/th&gt;&lt;th&gt;Does it need angular resolution?&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Presence (24 GHz) &lt;/td&gt;&lt;td&gt;Is anyone in the room, including someone sitting still? &lt;/td&gt;&lt;td&gt;Sensitivity, beam coverage, detection of small motion &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Breathing and sleep (60 GHz) &lt;/td&gt;&lt;td&gt;Breathing rate, heart rate, sleep stage &lt;/td&gt;&lt;td&gt;Phase sensitivity at a short wavelength, signal-to-noise ratio &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Fall detection (60 GHz) &lt;/td&gt;&lt;td&gt;Is this person upright, seated, or lying down? &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Change in height&lt;/strong&gt; — the elevation dimension &lt;/td&gt;&lt;td&gt;Yes, and it can be coarse&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Two of the three do not need angle at all. They need sensitivity and phase, which come from wavelength, bandwidth and combining gain.&lt;/p&gt;
&lt;p&gt;The third is the exception. Distinguishing lying from standing is a height question, so fall detection is the only one of the three that requires the elevation dimension — and it requires it only well enough to place a body in one of a few postures, not to resolve it in detail.&lt;/p&gt;
&lt;p&gt;This is also visible in the published figures for the fall module: a fall and static-dwell radius of about 1.5 m against a beam of 100° × 100°. The decision radius is far smaller than the illuminated area. The decision is made from signal quality and height change within that radius, not from angular precision across the whole beam.&lt;/p&gt;
&lt;h3 id=&quot;why-more-channels-are-not-automatically-better-for-home-monitoring&quot;&gt;Why more channels are not automatically better for home monitoring&lt;/h3&gt;
&lt;p&gt;The classification being asked for is coarse: unoccupied, lying, seated, standing, moving. That is a small number of classes, and it does not require a dense point cloud to separate them.&lt;/p&gt;
&lt;p&gt;Adding channels past the point where those classes are separable buys detail that the application cannot use, and it costs in four places:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cost and die area.&lt;/strong&gt; Every channel carries its own RF front-end circuitry.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Power.&lt;/strong&gt; More active channels per frame.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Frame duration.&lt;/strong&gt; In time-division MIMO the transmit antennas take turns, so the frame grows roughly linearly with the number of transmit channels. Using the relation from &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/28/distance-speed-two-equations.html&quot;&gt;Distance and Speed from a Radar Echo&lt;/a&gt;, &lt;code&gt;v_max = λ / (4 · T_c)&lt;/code&gt;, a longer per-antenna repetition interval lowers the unambiguous velocity. A configuration with four transmit channels reaches one quarter of the unambiguous velocity that one transmit channel would, at the same chirp duration. Fast motion then aliases, and a moving target that crosses resolution cells within one coherent interval produces a smeared result.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Array layout constraints.&lt;/strong&gt; Packing many elements into a small board pushes spacing towards or beyond half a wavelength, which produces grating lobes — false angular responses — and increases mutual coupling between elements. Both degrade the point cloud rather than improving it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is also a processing cost: more channels means more data per frame and more work per frame, on hardware that in a domestic product is expected to be inexpensive.&lt;/p&gt;
&lt;h3 id=&quot;when-more-channels-are-genuinely-needed&quot;&gt;When more channels are genuinely needed&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Separating several people&lt;/strong&gt; in the same space, and tracking each one&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Finer posture work&lt;/strong&gt; than lying, seated and standing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Imaging applications&lt;/strong&gt;, where the output is a shape rather than a class&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Longer range&lt;/strong&gt;, where the combining gain is what buys the link budget&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these is required by unattended presence, breathing monitoring, or fall detection in a single-occupant room.&lt;/p&gt;
&lt;h3 id=&quot;choosing-for-the-job&quot;&gt;Choosing for the job&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;If the product must… &lt;/th&gt;&lt;th&gt;Then the design pressure is on…&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Say whether a room is occupied, including a still person &lt;/td&gt;&lt;td&gt;Sensitivity and beam coverage, not channel count&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Report breathing rate and sleep stage &lt;/td&gt;&lt;td&gt;Wavelength, phase stability and signal quality&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Decide whether someone has fallen &lt;/td&gt;&lt;td&gt;An elevation dimension, of modest resolution&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Separate and track several people &lt;/td&gt;&lt;td&gt;Angular resolution — genuinely more channels&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Produce a shape or a fine posture estimate &lt;/td&gt;&lt;td&gt;Many channels, and bandwidth to match&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;checklist&quot;&gt;Checklist&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] The question the radar answers written down before the channel count is chosen&lt;/li&gt;
&lt;li&gt;[ ] Whether an elevation dimension is needed at all, checked first&lt;/li&gt;
&lt;li&gt;[ ] Angular resolution computed as &lt;code&gt;114.6 / N&lt;/code&gt; degrees, and compared against the actual separation required&lt;/li&gt;
&lt;li&gt;[ ] Range resolution computed as &lt;code&gt;c / (2B)&lt;/code&gt; separately — channels do not affect it&lt;/li&gt;
&lt;li&gt;[ ] Combining gain converted into range, not read as a raw dB figure&lt;/li&gt;
&lt;li&gt;[ ] Frame duration checked against the fastest motion that must be captured&lt;/li&gt;
&lt;li&gt;[ ] Element spacing checked against half a wavelength&lt;/li&gt;
&lt;li&gt;[ ] Cost, power and processing budget checked at the higher count&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Channel count sets the virtual aperture, and therefore angular resolution, combining gain, and — if the elements are placed in two dimensions — the ability to estimate height. It does not set range resolution, which is bandwidth, and it does not set displacement sensitivity, which is wavelength and phase.&lt;/p&gt;
&lt;p&gt;For presence, breathing and fall detection in a home, the decisions being made are a small number of coarse classes. Two of the three do not use angle at all, and the third needs it only coarsely. Beyond that point, additional channels cost frame duration, area, power and processing, and can degrade the result through grating lobes and motion within the coherent interval.&lt;/p&gt;
&lt;h3 id=&quot;where-to-go-next-2&quot;&gt;Where to go next&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The equations behind range and velocity: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/28/distance-speed-two-equations.html&quot;&gt;Distance and Speed from a Radar Echo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Turning beam angle and mounting height into floor coverage: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/29/beam-coverage-installation-geometry.html&quot;&gt;Where the Beam Actually Lands&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Beam angles, ranges and mounting heights in full: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/60ghz-24ghz-radar-modules-oem-integration.html&quot;&gt;60 GHz &amp;amp; 24 GHz Radar Modules for OEM Integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Practical limits on quoted specifications: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/radar-spec-limits.html&quot;&gt;Three mmWave Radar Specs That Are Almost Always Exaggerated&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Full documentation: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/&quot;&gt;https://249263431.github.io/60GHz-Radar-SDK/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Modules and finished units: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.tindie.com/stores/fuxinsuo/&quot;&gt;https://www.tindie.com/stores/fuxinsuo/&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33026/antenna-channels-virtual-aperture-and-what-more-of-them-actually-buys-you</guid>
<pubDate>Thu, 29 Oct 2026 22:55:08 +0000</pubDate>
</item>
<item>
<title>Where the Beam Actually Lands: Coverage from Beam Angle and Mounting Height</title>
<link>https://coderlegion.com/32904/where-the-beam-actually-lands-coverage-from-beam-angle-and-mounting-height</link>
<description>&lt;p&gt;Where the Beam Actually Lands: Coverage from Beam Angle and Mounting Height&lt;/p&gt;
&lt;p&gt;A datasheet gives you a beam angle and a mounting height. What you actually need to know is how much floor the radar covers, and how much of that footprint is usable. Those are two different questions, and the published figures answer them separately.&lt;/p&gt;
&lt;p&gt;This article works the geometry out for the three modules we ship, using published beam angles and published mounting heights. It also deals with the gap that appears when you put the two published numbers next to each other — the geometric footprint and the quoted detection range often do not agree, and when they disagree, one of them is the harder bound.&lt;/p&gt;
&lt;h3 id=&quot;two-quantities-not-one&quot;&gt;Two quantities, not one&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Quantity &lt;/th&gt;&lt;th&gt;Set by &lt;/th&gt;&lt;th&gt;Nature&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Geometric footprint&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Beam angle and mounting height &lt;/td&gt;&lt;td&gt;Geometry. Calculable, and a hard bound.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Detection range&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Signal-to-noise ratio, target size, and the conditions of the measurement &lt;/td&gt;&lt;td&gt;Measured under stated conditions.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The beam decides where energy can reach. Detection range decides whether what comes back is enough to make a decision. A target inside the beam but beyond the detection range is illuminated and still not reported.&lt;/p&gt;
&lt;h3 id=&quot;the-geometry&quot;&gt;The geometry&lt;/h3&gt;
&lt;h4 id=&quot;axis-pointing-straight-down&quot;&gt;Axis pointing straight down&lt;/h4&gt;
&lt;p&gt;For a ceiling-mounted unit at height &lt;code&gt;h&lt;/code&gt; with a full beam angle &lt;code&gt;θ&lt;/code&gt; on the axis of interest, the footprint half-width on the floor is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;r = h x tan(θ / 2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The full width is &lt;code&gt;2r&lt;/code&gt;. &lt;code&gt;θ&lt;/code&gt; is the &lt;strong&gt;full&lt;/strong&gt; angle as published, not the half angle.&lt;/p&gt;
&lt;h4 id=&quot;axis-tilted&quot;&gt;Axis tilted&lt;/h4&gt;
&lt;p&gt;For a wall-mounted unit, the geometry is easier in terms of the depression angle &lt;code&gt;φ&lt;/code&gt; below horizontal. With height &lt;code&gt;h&lt;/code&gt; above the reference plane and beam angle &lt;code&gt;θ&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;near edge   = h / tan(φ + θ/2)
beam centre = h / tan(φ)
far edge    = h / tan(φ - θ/2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The useful angles:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Angle &lt;/th&gt;&lt;th&gt;tan&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;10° &lt;/td&gt;&lt;td&gt;0.176&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;25° &lt;/td&gt;&lt;td&gt;0.466&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;30° &lt;/td&gt;&lt;td&gt;0.577&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;45° &lt;/td&gt;&lt;td&gt;1.000&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;50° &lt;/td&gt;&lt;td&gt;1.192&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;65° &lt;/td&gt;&lt;td&gt;2.145&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;presence-module-r24avd1-ceiling&quot;&gt;Presence module: R24AVD1, ceiling&lt;/h3&gt;
&lt;p&gt;Published: beam &lt;strong&gt;90° horizontal × 60° vertical&lt;/strong&gt;, ceiling mounting to &lt;strong&gt;2.75 m&lt;/strong&gt;, horizontal tilt ≤ 3°.&lt;/p&gt;
&lt;p&gt;At 2.75 m:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;width  = 2 x 2.75 x tan(45°) = 5.5 m
depth  = 2 x 2.75 x tan(30°) = 3.2 m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The footprint is therefore roughly &lt;strong&gt;5.5 m × 3.2 m&lt;/strong&gt;, and it is not square — the vertical beam is narrower, so the depth of coverage is smaller than the width. Orienting the board with the long side toward the door, as the integration notes recommend, puts the wide axis where it is wanted.&lt;/p&gt;
&lt;p&gt;The published detection figure for this module is much larger — a moving person at up to about 12 m across. That figure is measured for a moving target under stated conditions. The 5.5 × 3.2 m footprint is where the beam puts energy at 2.75 m. Both are real; they are not the same statement, and the geometry is the one you can rely on without qualification.&lt;/p&gt;
&lt;h3 id=&quot;fall-module-r60afd1-v-ceiling&quot;&gt;Fall module: R60AFD1-V, ceiling&lt;/h3&gt;
&lt;p&gt;Published: beam &lt;strong&gt;100° × 100°&lt;/strong&gt;, mounting &lt;strong&gt;2.2–2.8 m&lt;/strong&gt;, fall / static-dwell / motion radius ≈ &lt;strong&gt;1.5 m&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;At the ends and middle of the published mounting range:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Mounting height &lt;/th&gt;&lt;th&gt;Footprint (square)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;2.2 m &lt;/td&gt;&lt;td&gt;5.2 m × 5.2 m&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2.5 m &lt;/td&gt;&lt;td&gt;6.0 m × 6.0 m&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2.8 m &lt;/td&gt;&lt;td&gt;6.7 m × 6.7 m&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Against a detection radius of 1.5 m, the beam covers a floor area several times larger than the area in which a fall can be decided. Raising the unit from 2.2 m to 2.8 m widens the footprint from about 5.2 m to about 6.7 m without extending the radius in which the decision is possible — which is why the mounting height is a configured parameter (DP6, default 2.3–2.4 m) rather than a free choice.&lt;/p&gt;
&lt;p&gt;The practical reading: size the room against the 1.5 m radius, not against the beam. A bathroom wider than about 3 m across will have corners the radar illuminates and cannot decide on.&lt;/p&gt;
&lt;h3 id=&quot;sleep-module-r60abd1-tilted-above-the-bed&quot;&gt;Sleep module: R60ABD1, tilted above the bed&lt;/h3&gt;
&lt;p&gt;Published: beam &lt;strong&gt;40° × 40°&lt;/strong&gt;, mounted above the bed at &lt;strong&gt;0.8–1.0 m&lt;/strong&gt;, tilt &lt;strong&gt;30°–45°&lt;/strong&gt;, with &lt;strong&gt;45°&lt;/strong&gt; given as the recommended value. Respiration is specified to &lt;strong&gt;1.5 m&lt;/strong&gt;, presence and sleep to &lt;strong&gt;2.5 m&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Take &lt;code&gt;h = 1.0 m&lt;/code&gt; and &lt;code&gt;θ = 40°&lt;/code&gt;, so &lt;code&gt;θ/2 = 20°&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At the recommended 45°:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;near edge   = 1.0 / tan(65°) = 0.47 m
beam centre = 1.0 / tan(45°) = 1.00 m
far edge    = 1.0 / tan(25°) = 2.14 m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At 30°:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;near edge   = 1.0 / tan(50°) = 0.84 m
beam centre = 1.0 / tan(30°) = 1.73 m
far edge    = 1.0 / tan(10°) = 5.67 m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The geometry explains the published recommendation. At 45° the far edge sits at 2.14 m, inside the 2.5 m sleep range, so essentially all of the illuminated area is usable. At 30° the far edge runs out to 5.67 m, well past the 2.5 m figure — most of the beam is falling on floor that the radar cannot report on, and the beam centre has moved to 1.73 m, away from the chest.&lt;/p&gt;
&lt;p&gt;Working the same numbers at the lower mounting height, &lt;code&gt;h = 0.8 m&lt;/code&gt; with a 45° tilt, gives a near edge of about 0.37 m and a far edge of about 1.72 m — a tighter footprint concentrated on the upper body, which is what breathing detection wants.&lt;/p&gt;
&lt;p&gt;For respiration specifically, the 1.5 m limit means the reliably measured band on the bed runs from about 0.47 m to 1.5 m from the wall at &lt;code&gt;h = 1.0 m&lt;/code&gt; and 45°. That is the part of the bed to aim at.&lt;/p&gt;
&lt;h3 id=&quot;why-the-two-numbers-diverge&quot;&gt;Why the two numbers diverge&lt;/h3&gt;
&lt;p&gt;Beam edge energy is lower than on-axis energy, and off-axis targets return a weaker echo at a worse incidence angle. Decisions that need more than &quot;something is there&quot; — is this person lying down, is this a fall — need more signal than simple presence does, so their usable radius is smaller even inside the same beam.&lt;/p&gt;
&lt;p&gt;Published range figures are measured against a defined target under defined conditions. Comparing them directly against a geometric footprint is a category error in both directions: the footprint can be larger than the usable area, and a quoted range can be larger than the footprint at a low mounting height. When the two disagree, treat the geometry as the bound you can compute, and the quoted range as a figure you verify on site.&lt;/p&gt;
&lt;h3 id=&quot;what-geometry-does-not-tell-you&quot;&gt;What geometry does not tell you&lt;/h3&gt;
&lt;p&gt;The calculation assumes a clear line of sight. It says nothing about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Occlusion&lt;/strong&gt; — a cabinet, a shower screen, a plant between the radar and the target&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reflections&lt;/strong&gt; — metal and glass surfaces that route energy elsewhere and produce returns from places no one is standing; see &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/19/ghost-targets-multipath-interference.html&quot;&gt;Ghost Targets and Glass Walls&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Target size&lt;/strong&gt; — a person standing returns more than a person lying down at the same distance&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interference&lt;/strong&gt; — moving non-living objects inside the beam, such as fans and curtain rails&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;verifying-it-on-site&quot;&gt;Verifying it on site&lt;/h3&gt;
&lt;p&gt;The finished units report distance and position, both about every 2 s and both documented as non-critical with limited accuracy. Walk the edge of the computed footprint and watch where the target is picked up and where it is lost; that gives you the real boundary, including occlusions the calculation cannot see. The method is set out in &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/24/distance-position-debug-installation.html&quot;&gt;Using Distance and Position Output to Debug a Radar Installation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Record the result. It is the number your next installation of the same model starts from.&lt;/p&gt;
&lt;h3 id=&quot;checklist-1&quot;&gt;Checklist&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Beam angle read as the &lt;strong&gt;full&lt;/strong&gt; angle, and halved before use&lt;/li&gt;
&lt;li&gt;[ ] Footprint computed at the actual mounting height, not at the nominal one&lt;/li&gt;
&lt;li&gt;[ ] For ceiling units: width and depth computed separately when the beam is not square&lt;/li&gt;
&lt;li&gt;[ ] For tilted units: near edge, centre and far edge all computed, and the far edge checked against the published range&lt;/li&gt;
&lt;li&gt;[ ] Room sized against the &lt;strong&gt;detection radius&lt;/strong&gt;, not the beam footprint&lt;/li&gt;
&lt;li&gt;[ ] Occlusions and reflective surfaces checked separately — the calculation assumes clear line of sight&lt;/li&gt;
&lt;li&gt;[ ] Boundary confirmed on site with the distance output, and recorded&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;where-to-go-next-3&quot;&gt;Where to go next&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Where to put a sleep radar, and what to avoid: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/16/sleep-radar-installation-position.html&quot;&gt;Sleep Radar Installation — Finding the Right Position&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Beam angles, mounting heights and ranges in full: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/60ghz-24ghz-radar-modules-oem-integration.html&quot;&gt;60 GHz &amp;amp; 24 GHz Radar Modules for OEM Integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Why published ranges should be treated with care: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/radar-spec-limits.html&quot;&gt;Three mmWave Radar Specs That Are Almost Always Exaggerated&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Commissioning the result: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/24/distance-position-debug-installation.html&quot;&gt;Using Distance and Position Output to Debug a Radar Installation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Full documentation: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/&quot;&gt;https://249263431.github.io/60GHz-Radar-SDK/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Modules and finished units: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.tindie.com/stores/fuxinsuo/&quot;&gt;https://www.tindie.com/stores/fuxinsuo/&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32904/where-the-beam-actually-lands-coverage-from-beam-angle-and-mounting-height</guid>
<pubDate>Wed, 28 Oct 2026 23:41:43 +0000</pubDate>
</item>
<item>
<title>Distance and Speed from a Radar Echo: Two Equations, and What the Module Datasheets Publish</title>
<link>https://coderlegion.com/32754/distance-and-speed-from-a-radar-echo-two-equations-and-what-the-module-datasheets-publish</link>
<description>&lt;p&gt;Distance and Speed from a Radar Echo: Two Equations, and What the Module Datasheets Publish&lt;/p&gt;
&lt;p&gt;A radar echo carries two kinematic quantities: &lt;strong&gt;range&lt;/strong&gt; — how far away the reflecting target is — and &lt;strong&gt;radial velocity&lt;/strong&gt;, the component of the target's motion along the line between the radar and the target. Both are recovered from the same received signal, by two different routes. Range comes from timing, expressed through frequency. Velocity comes from the Doppler effect, expressed through frequency shift.&lt;/p&gt;
&lt;p&gt;This article sets out the two equations, derives the quantities that follow from them for the two bands we ship — &lt;strong&gt;24 GHz&lt;/strong&gt; and &lt;strong&gt;61–62.5 GHz&lt;/strong&gt; — and states which parameters the module datasheets publish and which they do not.&lt;/p&gt;
&lt;p&gt;The conceptual overview, without arithmetic, is in &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/how-mmwave-radar-works.html&quot;&gt;How mmWave Radar Works: From FMCW to Breathing Detection&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;range&quot;&gt;Range&lt;/h3&gt;
&lt;h4 id=&quot;the-underlying-idea-round-trip-time&quot;&gt;The underlying idea: round-trip time&lt;/h4&gt;
&lt;p&gt;The radar transmits, the wave travels to the target, part of the energy returns, and the receiver sees it. Electromagnetic waves propagate at &lt;code&gt;c ≈ 3×10⁸ m/s&lt;/code&gt;, and the wave covers the distance twice, so the round-trip time &lt;code&gt;t&lt;/code&gt; gives the range:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;d = c · t / 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The division by two accounts for the outbound and return legs.&lt;/p&gt;
&lt;h4 id=&quot;why-the-time-is-not-measured-directly&quot;&gt;Why the time is not measured directly&lt;/h4&gt;
&lt;p&gt;For a target at 1 m the round trip is 2 m, which takes about &lt;strong&gt;6.7 ns&lt;/strong&gt;. Measuring delay at nanosecond resolution directly is not practical in a module of this cost and size.&lt;/p&gt;
&lt;h4 id=&quot;how-it-is-done-instead-fmcw&quot;&gt;How it is done instead: FMCW&lt;/h4&gt;
&lt;p&gt;The modules use &lt;strong&gt;FMCW&lt;/strong&gt; (frequency-modulated continuous wave). The transmit frequency sweeps linearly with time, so the echo received at any instant is a delayed copy of an earlier transmit frequency. Mixing the received signal with the current transmit signal produces a &lt;strong&gt;beat frequency&lt;/strong&gt; proportional to that delay, and therefore to range.&lt;/p&gt;
&lt;p&gt;The published radar type confirms this: the fall module and the sleep module are both specified as &lt;strong&gt;60 GHz FMCW&lt;/strong&gt;, and the fall module as &lt;strong&gt;1T3R&lt;/strong&gt; (one transmit, three receive channels).&lt;/p&gt;
&lt;p&gt;The round-trip-time equation remains the correct way to understand where range comes from. The beat frequency is the engineering route to the same number.&lt;/p&gt;
&lt;h4 id=&quot;what-sets-how-finely-two-targets-can-be-separated&quot;&gt;What sets how finely two targets can be separated&lt;/h4&gt;
&lt;p&gt;Range resolution is set by the swept bandwidth &lt;code&gt;B&lt;/code&gt;, not by the carrier frequency:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Δ d = c / 2B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where a datasheet publishes the transmit frequency band, a figure follows. The fall module publishes a transmit frequency of &lt;strong&gt;61–62.5 GHz&lt;/strong&gt;, a span of 1.5 GHz:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Δd = c / (2B) = 3x10^8 / (2 x 1.5x10^9) = 0.10 m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So about &lt;strong&gt;10 cm&lt;/strong&gt;, on the assumption that the published operating band is also the swept span. Two notes on that figure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is the separation at which two targets appear as two. The accuracy on a single target is finer, because a single return is localised by interpolation and by phase, not by which resolution cell it lands in.&lt;/li&gt;
&lt;li&gt;The 24 GHz datasheet we supply does not publish a swept bandwidth, so no equivalent figure is stated here.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This explains the distinction that is often blurred: a resolution cell of roughly 10 cm, and the detection of chest-wall displacements below 1 mm, are not the same measurement. The second comes from &lt;strong&gt;phase&lt;/strong&gt; within a cell, covered below.&lt;/p&gt;
&lt;h3 id=&quot;velocity&quot;&gt;Velocity&lt;/h3&gt;
&lt;h4 id=&quot;the-doppler-effect&quot;&gt;The Doppler effect&lt;/h4&gt;
&lt;p&gt;When target and radar move relative to each other, the echo returns at a frequency offset from the transmitted one. Approaching shifts it upward, receding shifts it downward, and the sign gives the direction.&lt;/p&gt;
&lt;h4 id=&quot;the-equation&quot;&gt;The equation&lt;/h4&gt;
&lt;p&gt;With wavelength &lt;code&gt;λ&lt;/code&gt; and Doppler shift &lt;code&gt;Δf&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;v = Δ f · λ / 2, Δ f = 2v / λ
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The factor of two again comes from the two-way path.&lt;/p&gt;
&lt;h4 id=&quot;wavelength-from-the-published-frequency&quot;&gt;Wavelength from the published frequency&lt;/h4&gt;
&lt;p&gt;Wavelength follows from the carrier: &lt;code&gt;λ = c / f&lt;/code&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Module &lt;/th&gt;&lt;th&gt;Published band &lt;/th&gt;&lt;th&gt;Wavelength&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;R24AVD1 &lt;/td&gt;&lt;td&gt;24 GHz &lt;/td&gt;&lt;td&gt;12.5 mm&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;R60ABD1, R60AFD1-V, R60ABD2-H &lt;/td&gt;&lt;td&gt;61–62.5 GHz &lt;/td&gt;&lt;td&gt;4.80–4.92 mm&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For every 1 m/s of radial velocity, the shift is therefore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;24 GHz&lt;/strong&gt;: &lt;code&gt;2 / 0.0125&lt;/code&gt; ≈ &lt;strong&gt;160 Hz per m/s&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;61–62.5 GHz&lt;/strong&gt;: ≈ &lt;strong&gt;410 Hz per m/s&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;shift-magnitudes-in-practice&quot;&gt;Shift magnitudes in practice&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Target &lt;/th&gt;&lt;th&gt;Radial velocity &lt;/th&gt;&lt;th&gt;Δf at 24 GHz &lt;/th&gt;&lt;th&gt;Δf at 61–62.5 GHz&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Chest wall during breathing &lt;/td&gt;&lt;td&gt;≈ 0.0075 m/s &lt;/td&gt;&lt;td&gt;≈ 1.2 Hz &lt;/td&gt;&lt;td&gt;≈ 3 Hz&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Walking &lt;/td&gt;&lt;td&gt;1.5 m/s &lt;/td&gt;&lt;td&gt;≈ 240 Hz &lt;/td&gt;&lt;td&gt;≈ 610 Hz&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Running &lt;/td&gt;&lt;td&gt;5 m/s &lt;/td&gt;&lt;td&gt;≈ 800 Hz &lt;/td&gt;&lt;td&gt;≈ 2.0 kHz&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The shifts fall in the hertz to kilohertz range. They are small fractions of the carrier, and they are recovered by coherent processing rather than by comparing nominal frequencies.&lt;/p&gt;
&lt;h4 id=&quot;where-sub-millimetre-sensitivity-comes-from&quot;&gt;Where sub-millimetre sensitivity comes from&lt;/h4&gt;
&lt;p&gt;Phase is the more sensitive quantity. A displacement &lt;code&gt;Δx&lt;/code&gt; produces a round-trip phase change of:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Δ φ = 4\pi · Δ x / λ
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a 1 mm displacement this is about &lt;strong&gt;58°&lt;/strong&gt; at 24 GHz and about &lt;strong&gt;144°&lt;/strong&gt; at 61–62.5 GHz. The shorter wavelength gives roughly 2.5 times more phase per millimetre, which is the quantitative form of the statement made in &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/how-mmwave-radar-works.html&quot;&gt;How mmWave Radar Works&lt;/a&gt; — that 60 GHz is the band to use when breathing or heart rate is required.&lt;/p&gt;
&lt;h3 id=&quot;three-parameters-that-set-precision-and-span&quot;&gt;Three parameters that set precision and span&lt;/h3&gt;
&lt;p&gt;Each of the following is a sampling constraint, so each is a trade rather than a setting that can be widened independently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Range resolution&lt;/strong&gt; follows the swept bandwidth, &lt;code&gt;Δd = c / 2B&lt;/code&gt;. More span gives finer separation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Velocity resolution&lt;/strong&gt; follows the observation time &lt;code&gt;T_obs&lt;/code&gt; over which the phase history is collected:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Δv = λ / (2 · T_obs)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a one-second observation this gives about 6.3 mm/s at 24 GHz and about 2.4 mm/s at 61–62.5 GHz. Longer observation gives finer velocity resolution, which is why slow periodic motion — breathing — is measured over intervals rather than from single frames.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Unambiguous velocity&lt;/strong&gt; follows the chirp repetition interval &lt;code&gt;T_c&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;v_max = λ / 4 · T_c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the one quantity where the shorter wavelength works in the opposite direction. At the same chirp interval, the 61–62.5 GHz band reaches a smaller unambiguous velocity than 24 GHz. A radar tuned to resolve sub-millimetre chest motion and a radar built to measure vehicle speed are therefore sampling the same physics with incompatible timing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not published:&lt;/strong&gt; the modules' internal chirp timing and coherent processing interval do not appear in the datasheets, so no &lt;code&gt;T_c&lt;/code&gt; or &lt;code&gt;T_obs&lt;/code&gt; value is given here, and the worked figures above are labelled as illustrations of the equations.&lt;/p&gt;
&lt;h3 id=&quot;published-output-cadences&quot;&gt;Published output cadences&lt;/h3&gt;
&lt;p&gt;Separate from the radio parameters above are the intervals at which results are reported. These are published, and they are output cadences, not integration times:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Report &lt;/th&gt;&lt;th&gt;R24AVD1 (24 GHz) &lt;/th&gt;&lt;th&gt;60 GHz finished units&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Occupied → unoccupied &lt;/td&gt;&lt;td&gt;40 s &lt;/td&gt;&lt;td&gt;about 40 s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Unoccupied → occupied &lt;/td&gt;&lt;td&gt;&amp;lt; 0.5 s &lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Static / active change &lt;/td&gt;&lt;td&gt;&amp;lt; 0.5 s &lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Approach / leave / no lateral movement &lt;/td&gt;&lt;td&gt;2 s &lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Body-movement amplitude 0–100 &lt;/td&gt;&lt;td&gt;5 s &lt;/td&gt;&lt;td&gt;1 s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Breathing rate, heart rate &lt;/td&gt;&lt;td&gt;— &lt;/td&gt;&lt;td&gt;about 3 s&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The asymmetry — under half a second to declare a room occupied, about 40 seconds to declare it empty — is a deliberate stability choice, described in &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/radar-spec-limits.html&quot;&gt;Three mmWave Radar Specs That Are Almost Always Exaggerated&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Distance and position are reported by the finished units in centimetres and as x/y/z, both about every 2 s, and both documented as &lt;strong&gt;non-critical functions with limited accuracy&lt;/strong&gt;. They are useful while commissioning, as set out in &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/24/distance-position-debug-installation.html&quot;&gt;Using Distance and Position Output to Debug a Radar Installation&lt;/a&gt;, and are not a basis for a product feature.&lt;/p&gt;
&lt;p&gt;The body-movement amplitude is a dimensionless index from 0 to 100 with defined bands, not a velocity. It is not converted to metres per second.&lt;/p&gt;
&lt;h3 id=&quot;the-two-bands-side-by-side&quot;&gt;The two bands side by side&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Quantity &lt;/th&gt;&lt;th&gt;24 GHz &lt;/th&gt;&lt;th&gt;61–62.5 GHz&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Wavelength &lt;/td&gt;&lt;td&gt;12.5 mm &lt;/td&gt;&lt;td&gt;4.80–4.92 mm&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Doppler shift per 1 m/s &lt;/td&gt;&lt;td&gt;≈ 160 Hz &lt;/td&gt;&lt;td&gt;≈ 410 Hz&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Phase change per 1 mm displacement &lt;/td&gt;&lt;td&gt;≈ 58° &lt;/td&gt;&lt;td&gt;≈ 144°&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Phase change per 1 mm, relative &lt;/td&gt;&lt;td&gt;1× &lt;/td&gt;&lt;td&gt;≈ 2.5×&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Velocity resolution at 1 s observation &lt;/td&gt;&lt;td&gt;≈ 6.3 mm/s &lt;/td&gt;&lt;td&gt;≈ 2.4 mm/s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Unambiguous velocity at equal chirp interval &lt;/td&gt;&lt;td&gt;larger &lt;/td&gt;&lt;td&gt;smaller&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Swept bandwidth published &lt;/td&gt;&lt;td&gt;no &lt;/td&gt;&lt;td&gt;61–62.5 GHz (1.5 GHz span)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Derived range resolution &lt;/td&gt;&lt;td&gt;not stated &lt;/td&gt;&lt;td&gt;≈ 10 cm&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;what-this-accounts-for&quot;&gt;What this accounts for&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Respiration and heart rate use 60 GHz.&lt;/strong&gt; Displacement sensitivity scales inversely with wavelength, so the shorter band returns about 2.5 times more phase per millimetre.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Presence over a wide area uses 24 GHz.&lt;/strong&gt; The longer wavelength and the wider beam cover a larger volume; the published beam for R24AVD1 is 90° horizontal by 60° vertical.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A posture radar is not a speed instrument.&lt;/strong&gt; The unambiguous velocity limit moves against the higher band, and the same processing budget is spent on deciding what the target is doing. The practical figure for a home posture radar is around 3 m/s, with the reasoning in &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/radar-spec-limits.html&quot;&gt;Three mmWave Radar Specs That Are Almost Always Exaggerated&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;summary-1&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Range comes from round-trip time, recovered in practice from an FMCW beat frequency, and its resolution follows the swept bandwidth. Velocity comes from the Doppler shift, and its resolution and span follow observation time and chirp interval respectively.&lt;/p&gt;
&lt;p&gt;The two bands differ by a factor of about 2.5 in wavelength, and that one ratio propagates through every sensitivity figure — phase per millimetre, Doppler shift per metre per second, velocity resolution — while moving the unambiguous velocity limit in the opposite direction.&lt;/p&gt;
&lt;p&gt;Where a datasheet publishes a value, that value is used here. Where it does not, the equation is given and the absence is stated.&lt;/p&gt;
&lt;h3 id=&quot;where-to-go-next-4&quot;&gt;Where to go next&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The conceptual version without arithmetic: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/how-mmwave-radar-works.html&quot;&gt;How mmWave Radar Works&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Practical limits on angle, distance and speed: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/radar-spec-limits.html&quot;&gt;Three mmWave Radar Specs That Are Almost Always Exaggerated&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Module specifications in full: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/60ghz-24ghz-radar-modules-oem-integration.html&quot;&gt;60 GHz &amp;amp; 24 GHz Radar Modules for OEM Integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Using distance and position while commissioning: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/24/distance-position-debug-installation.html&quot;&gt;Using Distance and Position Output to Debug a Radar Installation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Full documentation: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/&quot;&gt;https://249263431.github.io/60GHz-Radar-SDK/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Modules and finished units: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.tindie.com/stores/fuxinsuo/&quot;&gt;https://www.tindie.com/stores/fuxinsuo/&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32754/distance-and-speed-from-a-radar-echo-two-equations-and-what-the-module-datasheets-publish</guid>
<pubDate>Tue, 27 Oct 2026 23:24:14 +0000</pubDate>
</item>
<item>
<title>Updating Radar Firmware Without a Cloud: Self-Hosted OTA over MQTT</title>
<link>https://coderlegion.com/32750/updating-radar-firmware-without-a-cloud-self-hosted-ota-over-mqtt</link>
<description>&lt;p&gt;Updating Radar Firmware Without a Cloud: Self-Hosted OTA over MQTT&lt;/p&gt;
&lt;p&gt;Most connected devices want you to push firmware through somebody's cloud. These do not ask for that. The sleep and fall radars fetch an image from a URL you hand them, and the command arrives on the same MQTT connection the telemetry already uses. Nothing has to leave your network, and there is no vendor account in the path.&lt;/p&gt;
&lt;p&gt;This is the practical shape of that: which units support it, the three commands involved, how to confirm an update actually landed, and the operational precautions the protocol document leaves to you.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Everything below comes from the Wi-Fi protocol documents for the sleep and fall units. Where they do not specify something, this says so rather than filling the gap.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;scope-first&quot;&gt;Scope first&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Unit &lt;/th&gt;&lt;th&gt;OTA update via this protocol&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Sleep radar (60 GHz, Wi-Fi) &lt;/td&gt;&lt;td&gt;Yes — topic family &lt;code&gt;/Radar60SP/{DeviceID}/sys/property/{post,set}&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Fall radar (60 GHz, Wi-Fi) &lt;/td&gt;&lt;td&gt;Yes — topic family &lt;code&gt;/Radar60FL/{DeviceID}/sys/property/{post,set}&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Presence radar (24 GHz, Wi-Fi) &lt;/td&gt;&lt;td&gt;No such command appears in its protocol document&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That last row matters. The presence unit's document specifies &lt;code&gt;online&lt;/code&gt;, &lt;code&gt;version&lt;/code&gt;, &lt;code&gt;active&lt;/code&gt; and &lt;code&gt;passive&lt;/code&gt; report topics plus a &lt;code&gt;property/set&lt;/code&gt; command topic — and no update command at all. If you need to update those, that is a question for the hardware, not for this protocol.&lt;/p&gt;
&lt;p&gt;Commands go to the unit's &lt;code&gt;.../sys/property/set&lt;/code&gt; topic; everything it reports arrives on &lt;code&gt;.../sys/property/post&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;three-commands&quot;&gt;Three commands&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Select the target module&lt;/strong&gt; — required before every update.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;version&quot;: &quot;1.0&quot;, &quot;method&quot;: &quot;ota_set&quot;, &quot;params&quot;: {&quot;module&quot;: &quot;0&quot;}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;0&lt;/code&gt; is the Wi-Fi module, &lt;code&gt;1&lt;/code&gt; is the radar module; the default is &lt;code&gt;0&lt;/code&gt;. The documentation is explicit that this control command has to be sent before each update — it is not a one-time setting you configure and forget.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Hand over the image location.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;version&quot;: &quot;1.0&quot;, &quot;method&quot;: &quot;ota_set&quot;,
 &quot;params&quot;: {&quot;otaInform&quot;: {&quot;firmwareUrl&quot;: &quot;http://192.168.31.88/app.rbl&quot;, &quot;port&quot;: &quot;8090&quot;}}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The device fetches the firmware from that URL and port and starts updating. A failure comes back as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;version&quot;: &quot;1.0&quot;, &quot;opt&quot;: &quot;otaInform&quot;, &quot;res&quot;: &quot;fail&quot;, &quot;msg&quot;: &quot;ota failed!&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. Observe progress.&lt;/strong&gt; Described as an active report during update, sent with method &lt;code&gt;ota_post&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;version&quot;: &quot;1.0&quot;, &quot;method&quot;: &quot;ota_post&quot;, &quot;params&quot;: {&quot;downloadProgress&quot;: &quot;0&quot;}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Values run &lt;code&gt;0&lt;/code&gt;–&lt;code&gt;100&lt;/code&gt;. Subscribe to the unit's report topic before you start; if your unit reports it somewhere else, record that during commissioning rather than assuming.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After a successful update&lt;/strong&gt;, the module restarts and then reports its version number. Treat that report — not the arrival of &lt;code&gt;100&lt;/code&gt; — as the completion signal.&lt;/p&gt;
&lt;h3 id=&quot;confirming-the-result&quot;&gt;Confirming the result&lt;/h3&gt;
&lt;p&gt;&quot;Transfer finished&quot; and &quot;the unit now runs the new code&quot; are two different statements.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sleep and fall units&lt;/strong&gt; carry &lt;code&gt;firmwareVersion&lt;/code&gt; and &lt;code&gt;firmwareVersionWiFi&lt;/code&gt; in their property list, and the protocol states that the post-restart report carries the version number after updating. Compare against the value you recorded before starting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Presence units&lt;/strong&gt; publish a version snapshot on &lt;code&gt;/RadarHP/{DeviceID}/sys/version/post&lt;/code&gt; containing &lt;code&gt;radarID&lt;/code&gt;, &lt;code&gt;softwareVersion&lt;/code&gt;, &lt;code&gt;firmwareVersion&lt;/code&gt; and &lt;code&gt;protocolVersion&lt;/code&gt;. These are read-only — they cannot be written, only read. There is also a request command to solicit it on demand: publish &lt;code&gt;{&quot;version&quot;:&quot;1.0&quot;,&quot;method&quot;:&quot;get_version&quot;,&quot;params&quot;:{}}&lt;/code&gt; to &lt;code&gt;/RadarHP/{DeviceID}/sys/property/set&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keep the before-and-after version pair in the same commissioning record you keep for everything else. It is the only evidence you have later.&lt;/p&gt;
&lt;h3 id=&quot;running-an-update&quot;&gt;Running an update&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import json
import paho.mqtt.client as mqtt

BROKER, PORT = &quot;192.168.1.2&quot;, 1883
DEV = &quot;&amp;lt;DeviceID&amp;gt;&quot;                        # the MAC, printed on the label
SET_TOPIC  = f&quot;/Radar60SP/{DEV}/sys/property/set&quot;
POST_TOPIC = f&quot;/Radar60SP/{DEV}/sys/property/post&quot;

IMAGE_URL, IMAGE_PORT = &quot;http://192.168.1.50:8090/app.rbl&quot;, &quot;8090&quot;
TARGET = &quot;0&quot;                              # &quot;0&quot; Wi-Fi module, &quot;1&quot; radar module


def command(client, method, params):
    client.publish(SET_TOPIC,
                   json.dumps({&quot;version&quot;: &quot;1.0&quot;, &quot;method&quot;: method, &quot;params&quot;: params}),
                   qos=1)


def on_message(client, userdata, msg):
    payload = json.loads(msg.payload.decode())
    params = payload.get(&quot;params&quot;, {})
    if &quot;downloadProgress&quot; in params:
        print(&quot;transfer:&quot;, params[&quot;downloadProgress&quot;], &quot;%&quot;)
    if payload.get(&quot;opt&quot;) == &quot;otaInform&quot; and payload.get(&quot;res&quot;) == &quot;fail&quot;:
        print(&quot;update failed:&quot;, payload.get(&quot;msg&quot;))
    if &quot;firmwareVersion&quot; in params:
        print(&quot;reported version after restart:&quot;, params[&quot;firmwareVersion&quot;])


client = mqtt.Client()
client.username_pw_set(&quot;backend&quot;, &quot;...&quot;)
client.on_message = on_message
client.connect(BROKER, PORT, keepalive=60)
client.subscribe(POST_TOPIC, qos=1)
client.loop_start()

command(client, &quot;ota_set&quot;, {&quot;module&quot;: TARGET})          # select the target first
command(client, &quot;ota_set&quot;, {&quot;otaInform&quot;: {&quot;firmwareUrl&quot;: IMAGE_URL, &quot;port&quot;: IMAGE_PORT}})
client.loop_forever()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is the whole mechanism — two publishes and a subscription. The rest is procedure.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Declare a maintenance window.&lt;/strong&gt; The unit is unreachable while it rewrites and restarts. Monitoring will read that as a failure unless you suppress it — see &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/27/online-heartbeat-radar-health.html&quot;&gt;Online Says It's Connected, HeartBeat Says It Works&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Select the target deliberately.&lt;/strong&gt; &lt;code&gt;module&lt;/code&gt; defaults to the Wi-Fi module. Sending a radar image to it is a mistake the command will not catch for you.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make sure the URL is reachable from the device.&lt;/strong&gt; Same subnet or routable, firewall permits that port, and never &lt;code&gt;localhost&lt;/code&gt; or &lt;code&gt;127.0.0.1&lt;/code&gt; — the address is resolved by the radar, not by the machine issuing the command.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stage it.&lt;/strong&gt; One unit first. Watch &lt;code&gt;downloadProgress&lt;/code&gt; reach &lt;code&gt;100&lt;/code&gt;, wait for the restart, confirm the version changed, then proceed with the rest.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep the previous image served.&lt;/strong&gt; Given what is not documented below, this is your only fallback.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do not batch-update overnight in a care deployment.&lt;/strong&gt; A unit that comes back with different behaviour at 03:00 is discovered by nobody until morning.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;a-minimal-image-host&quot;&gt;A minimal image host&lt;/h3&gt;
&lt;p&gt;Nothing more than a static file server on the management network:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python -m http.server 8090 --directory ./firmware
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which makes &lt;code&gt;http://{host}:8090/app.rbl&lt;/code&gt; available to the units. Use it on a segregated network or behind whatever reverse proxy you already trust; it is plain HTTP with directory listing enabled, and it is a build artifact service, not a public endpoint.&lt;/p&gt;
&lt;h3 id=&quot;what-the-documents-do-not-say&quot;&gt;What the documents do not say&lt;/h3&gt;
&lt;p&gt;Stated plainly, because guessing here costs hardware:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Transport.&lt;/strong&gt; Every example uses &lt;code&gt;http://&lt;/code&gt;. HTTPS support is not stated. Plan for plain HTTP on a network you control.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rollback.&lt;/strong&gt; No rollback command appears. Assume you cannot command a unit back to the previous revision — which is why keeping the old image available and piloting one unit first are not optional.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Duration and interruption.&lt;/strong&gt; No update duration is given, and the documents describe failure only as the &lt;code&gt;ota failed!&lt;/code&gt; response. Behaviour after power loss mid-update is undocumented.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The consequence is procedural rather than technical: update when someone is present to observe, one unit first, with the previous image still served.&lt;/p&gt;
&lt;h3 id=&quot;checklist-2&quot;&gt;Checklist&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Unit confirmed to be one of the two that document this command&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;module&lt;/code&gt; selected explicitly before every update — Wi-Fi module vs radar module&lt;/li&gt;
&lt;li&gt;[ ] Image URL and port reachable from the device's network, not from yours only&lt;/li&gt;
&lt;li&gt;[ ] Subscribed to the report topic &lt;em&gt;before&lt;/em&gt; publishing either command&lt;/li&gt;
&lt;li&gt;[ ] Monitoring suppression window opened, and closed only after the version report arrives&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;downloadProgress&lt;/code&gt; observed to &lt;code&gt;100&lt;/code&gt;, restart observed, version compared against the pre-update record&lt;/li&gt;
&lt;li&gt;[ ] Previous firmware image still available at a known URL&lt;/li&gt;
&lt;li&gt;[ ] Staged: one unit, verified, then the cohort&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;related-reading&quot;&gt;Related reading&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Reading the stream these commands travel on: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/25/reading-radar-data-your-own-app.html&quot;&gt;Reading Radar Data in Your Own Application&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Managing many units: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/24/running-several-radars-one-home.html&quot;&gt;Running Several Radars in One Home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The broker underneath it: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/23/apache-artemis-mqtt-broker-radar.html&quot;&gt;Apache Artemis setup&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Full documentation: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/&quot;&gt;https://249263431.github.io/60GHz-Radar-SDK/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Modules and finished units: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.tindie.com/stores/fuxinsuo/&quot;&gt;https://www.tindie.com/stores/fuxinsuo/&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32750/updating-radar-firmware-without-a-cloud-self-hosted-ota-over-mqtt</guid>
<pubDate>Tue, 27 Oct 2026 23:05:42 +0000</pubDate>
</item>
<item>
<title>Online Says It's Connected, HeartBeat Says It Works: Reading Two Status Signals Instead of Guessing</title>
<link>https://coderlegion.com/32601/online-says-its-connected-heartbeat-says-works-reading-status-signals-instead-guessing</link>
<description>&lt;hr&gt;
&lt;p&gt;title: &quot;Online Says It's Connected, HeartBeat Says It Works: Reading Two Status Signals Instead of Guessing From Silence&quot;&lt;br&gt;
published: true&lt;br&gt;
tags: iot, mqtt, radar, monitoring&lt;br&gt;
canonical_url: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/27/online-heartbeat-radar-health.html&quot;&gt;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/27/online-heartbeat-radar-health.html&lt;/a&gt;&lt;br&gt;
description: &quot;Two fields in a mmWave radar's MQTT output — online and heartBeat — look like the same health check and are not. One proves the link; the other proves the sensor. And neither supports a timeout.&quot;&lt;/p&gt;
&lt;h3 id=&quot;cover-image-&quot;&gt;cover_image:&lt;/h3&gt;
&lt;p&gt;Two fields in a radar's MQTT output get mistaken for each other constantly, and mistaking them is how a health monitor ends up trusted when it should not be.&lt;/p&gt;
&lt;p&gt;They answer different questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;online&lt;/code&gt; answers &quot;is the link up?&quot;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;heartBeat&lt;/code&gt; answers &quot;is the radar itself working?&quot;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;&lt;code&gt;online&lt;/code&gt; &lt;/th&gt;&lt;th&gt;&lt;code&gt;heartBeat&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Question answered &lt;/td&gt;&lt;td&gt;Is this device reachable? &lt;/td&gt;&lt;td&gt;Is this device operating correctly?&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Reported values &lt;/td&gt;&lt;td&gt;&lt;code&gt;&quot;0&quot;&lt;/code&gt; offline, &lt;code&gt;&quot;1&quot;&lt;/code&gt; online &lt;/td&gt;&lt;td&gt;&lt;code&gt;&quot;0&quot;&lt;/code&gt; abnormal, &lt;code&gt;&quot;1&quot;&lt;/code&gt; normal&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Layer &lt;/td&gt;&lt;td&gt;MQTT connection and broker session &lt;/td&gt;&lt;td&gt;The radar's own operating state&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Reported when &lt;/td&gt;&lt;td&gt;On state change only &lt;/td&gt;&lt;td&gt;On state change only&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Available on &lt;/td&gt;&lt;td&gt;Sleep radar, fall radar, presence radar &lt;/td&gt;&lt;td&gt;Sleep radar, fall radar&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The presence radar publishes its online state on a dedicated topic — &lt;code&gt;/RadarHP/&amp;lt;DeviceID&amp;gt;/sys/online/post&lt;/code&gt;; the sleep and fall radars publish &lt;code&gt;online&lt;/code&gt; as an attribute in the same property payload as everything else. Note the asymmetry: &lt;strong&gt;there is no &lt;code&gt;heartBeat&lt;/code&gt; in the presence radar's protocol documentation.&lt;/strong&gt; The other two report it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;version&quot;: &quot;1.0&quot;, &quot;method&quot;: &quot;post&quot;, &quot;params&quot;: {&quot;online&quot;: &quot;0&quot;}}
{&quot;version&quot;: &quot;1.0&quot;, &quot;method&quot;: &quot;post&quot;, &quot;params&quot;: {&quot;heartBeat&quot;: &quot;0&quot;}}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;why-this-distinction-is-not-academic&quot;&gt;Why this distinction is not academic&lt;/h3&gt;
&lt;p&gt;Consider what has to be true for any &lt;code&gt;heartBeat&lt;/code&gt; message to reach your code: the radar has power, it is associated with the Wi-Fi access point, its MQTT session is established, and you are subscribed to its topic. &lt;strong&gt;A delivered &lt;code&gt;heartBeat&lt;/code&gt; therefore already proves the link is working.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;From that, one conclusion follows immediately:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;heartBeat&lt;/code&gt; cannot report a connectivity failure. Physically and logically, it cannot — a radar that has lost its link has no way to deliver that news.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The reverse holds for &lt;code&gt;online&lt;/code&gt;. An &lt;code&gt;online: &quot;0&quot;&lt;/code&gt; message is not written by the radar that went offline; it is generated &lt;strong&gt;by the broker&lt;/strong&gt; when the session it was holding stops answering. The radar contributes nothing to that judgement.&lt;/p&gt;
&lt;p&gt;So neither field overlaps with the other, and neither can substitute for the other:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you only watch &lt;code&gt;online&lt;/code&gt;, you will see &quot;connected&quot; while the radar sits there reporting a fault about itself.&lt;/li&gt;
&lt;li&gt;If you only watch &lt;code&gt;heartBeat&lt;/code&gt;, every network outage looks like nothing at all.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-trap-silence-is-not-evidence&quot;&gt;The trap: silence is not evidence&lt;/h3&gt;
&lt;p&gt;Both fields are documented as &lt;strong&gt;reported on state change&lt;/strong&gt;, not on a timer. That single sentence is where most monitoring code goes wrong.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Occupancy.&lt;/strong&gt; The presence state is event-driven. A room whose state does not change for three hours produces no occupancy messages, and that is correct behaviour — not a fault.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Heartbeat.&lt;/strong&gt; A radar that boots normally and stays normal has no reason to send &lt;code&gt;heartBeat&lt;/code&gt; at all, because no state change occurred.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The one periodic attribute.&lt;/strong&gt; In the sleep and fall protocols, the only attribute the documentation marks as periodically reported is &lt;code&gt;workDuration&lt;/code&gt; (4 bytes, value range &lt;code&gt;0x3C&lt;/code&gt;–&lt;code&gt;0xffffffff&lt;/code&gt;, unit seconds) — and it is marked &quot;not yet available&quot; in the same line. &lt;code&gt;workDurationReportFrequency&lt;/code&gt; carries the same note. &lt;strong&gt;Do not build a keepalive on either of them.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the tempting pattern — &quot;if I have not heard from the device in 60 seconds, it must be gone&quot; — has no foundation here. Absence of messages is the normal state of a working installation.&lt;/p&gt;
&lt;h3 id=&quot;what-you-can-do-instead&quot;&gt;What you can do instead&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;code&gt;online&lt;/code&gt; &lt;/th&gt;&lt;th&gt;&lt;code&gt;heartBeat&lt;/code&gt; &lt;/th&gt;&lt;th&gt;Conclusion &lt;/th&gt;&lt;th&gt;Action&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1 &lt;/td&gt;&lt;td&gt;1 &lt;/td&gt;&lt;td&gt;Link up, radar reports normal &lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1 &lt;/td&gt;&lt;td&gt;0 &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Link up, radar reports a fault&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Raise the higher-severity alarm; this is the case only &lt;code&gt;heartBeat&lt;/code&gt; can surface&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;0 &lt;/td&gt;&lt;td&gt;— &lt;/td&gt;&lt;td&gt;Session dropped, as judged by the broker &lt;/td&gt;&lt;td&gt;Investigate the link; the radar's own state is unknown&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;no messages &lt;/td&gt;&lt;td&gt;no messages &lt;/td&gt;&lt;td&gt;Most likely nothing changed &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Nothing can be concluded&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Treat the two as different classes of event with different severities. A lost link is an infrastructure ticket; a radar reporting itself abnormal is a hardware ticket, and it is the one that should wake someone up.&lt;/p&gt;
&lt;h3 id=&quot;two-rules-you-must-not-break&quot;&gt;Two rules you must not break&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Do not disable &lt;code&gt;online&lt;/code&gt; or &lt;code&gt;heartBeat&lt;/code&gt; with &lt;code&gt;limit_set&lt;/code&gt;.&lt;/strong&gt; The Wi-Fi protocols expose a reporting-suppression command: &lt;code&gt;limit_set&lt;/code&gt; / &lt;code&gt;limit_get&lt;/code&gt;, where &lt;code&gt;1&lt;/code&gt; means &quot;suppress this attribute&quot; and &lt;code&gt;0&lt;/code&gt; means &quot;allow it&quot;. It exists for the attributes that report frequently — &lt;code&gt;someoneExists&lt;/code&gt;, &lt;code&gt;motionStatus&lt;/code&gt;, &lt;code&gt;movementSigns&lt;/code&gt;, &lt;code&gt;humanDistance&lt;/code&gt;, &lt;code&gt;humanPosition&lt;/code&gt; — and it is a legitimate way to cut traffic and noise. Both &lt;code&gt;online&lt;/code&gt; and &lt;code&gt;heartBeat&lt;/code&gt; appear in that list. Suppress them and you remove the only two signals that stand between you and a timeout guess.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Do not try to poll &lt;code&gt;heartBeat&lt;/code&gt;.&lt;/strong&gt; Every neighbouring attribute in those documents — &lt;code&gt;initFinish&lt;/code&gt;, &lt;code&gt;workDuration&lt;/code&gt;, &lt;code&gt;productMode&lt;/code&gt; — carries the line &quot;this attribute supports querying&quot; followed by a &lt;code&gt;get&lt;/code&gt; example. &lt;code&gt;heartBeat&lt;/code&gt; does not. The only &lt;code&gt;?&lt;/code&gt; it ever appears with is inside &lt;code&gt;limit_get&lt;/code&gt;, and that question means &quot;is reporting suppressed for this attribute&quot;, not &quot;what is its current value&quot;. There is no documented way to ask the radar for that value on demand.&lt;/p&gt;
&lt;p&gt;That leaves one workable approach for the things you genuinely need on demand: &lt;strong&gt;query the attributes that do support it.&lt;/strong&gt; If you must confirm a radar has finished starting up, &lt;code&gt;initFinish&lt;/code&gt; (&lt;code&gt;0&lt;/code&gt; not complete, &lt;code&gt;1&lt;/code&gt; complete) is queryable. &lt;code&gt;heartBeat&lt;/code&gt; is not, and no amount of polling will change that.&lt;/p&gt;
&lt;h3 id=&quot;a-minimal-health-monitor&quot;&gt;A minimal health monitor&lt;/h3&gt;
&lt;p&gt;Subscribe, merge the sparse payload, and raise the two classes separately:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import json, time
import paho.mqtt.client as mqtt

TOPIC = &quot;/Radar60SP/&amp;lt;MAC&amp;gt;/#&quot;

state = {}
last_any = {}


def on_message(client, userdata, msg):
    try:
        payload = json.loads(msg.payload.decode())
    except ValueError:
        return
    params = payload.get(&quot;params&quot;, {})
    if not params:
        return

    # Only the fields that changed are present.
    for k in (&quot;online&quot;, &quot;heartBeat&quot;, &quot;initFinish&quot;):
        if k in params:
            state[k] = params[k]

    last_any[&quot;ts&quot;] = time.time()

    # Two different failures, two different severities.
    if params.get(&quot;online&quot;) == &quot;0&quot;:
        raise_alarm(&quot;link&quot;, device=msg.topic, severity=&quot;infra&quot;)
    if params.get(&quot;heartBeat&quot;) == &quot;0&quot;:
        raise_alarm(&quot;sensor&quot;, device=msg.topic, severity=&quot;high&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the shape: nothing here fires on elapsed time. Every action is triggered by an arriving value.&lt;/p&gt;
&lt;p&gt;If you still want a timeout as a last resort, the threshold must come from measurement, not habit. Log the longest interval your real installation goes without any message during normal operation, then add margin to that. A number you made up will either fire constantly or never fire.&lt;/p&gt;
&lt;h3 id=&quot;measure-these-at-commissioning&quot;&gt;Measure these at commissioning&lt;/h3&gt;
&lt;p&gt;Two things the documentation does not state, and that you should record per installation rather than assume:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Does the radar announce &lt;code&gt;heartBeat: &quot;1&quot;&lt;/code&gt; on power-up?&lt;/strong&gt; A device that boots healthy may never have a reason to report it. Power one up on the bench with a subscriber attached and write down what actually arrives, and how long after power-up it arrives.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Does &lt;code&gt;online: &quot;1&quot;&lt;/code&gt; get republished after a reconnect?&lt;/strong&gt; If it does not, your consumer must treat the absence of a change as &quot;still online&quot; rather than as unknown.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both belong in the same commissioning note as the empty-room baseline from &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/24/distance-position-debug-installation.html&quot;&gt;Using Distance and Position Output to Debug a Radar Installation&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;maintenance-looks-like-a-failure&quot;&gt;Maintenance looks like a failure&lt;/h3&gt;
&lt;p&gt;One consequence of all this that catches people out: the sleep and fall protocols support firmware update over the air — &lt;code&gt;ota_set&lt;/code&gt; with &lt;code&gt;otaModule&lt;/code&gt; (&lt;code&gt;0&lt;/code&gt; Wi-Fi module, &lt;code&gt;1&lt;/code&gt; radar module, and the command must be sent before every update), &lt;code&gt;otaInform&lt;/code&gt; giving a firmware URL and port, and &lt;code&gt;downloadProgress&lt;/code&gt; reporting &lt;code&gt;0&lt;/code&gt;–&lt;code&gt;100&lt;/code&gt;. On success the module restarts and then reports its new version number.&lt;/p&gt;
&lt;p&gt;During that window the device is legitimately unreachable. If your monitoring treats a missing device as an alarm, a firmware update will page someone. Put planned maintenance in a suppression window, and use the version report afterwards — the presence radar's &lt;code&gt;/RadarSP/&amp;lt;DeviceID&amp;gt;/sys/version/post&lt;/code&gt; topic, or the &lt;code&gt;firmwareVersion&lt;/code&gt; attribute — to confirm the update actually landed.&lt;/p&gt;
&lt;p&gt;The update server is one you host yourself; the documentation states plainly that you build the web server the firmware is fetched from.&lt;/p&gt;
&lt;h3 id=&quot;two-controls-worth-knowing-about&quot;&gt;Two controls worth knowing about&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reporting suppression&lt;/strong&gt; (&lt;code&gt;limit_set&lt;/code&gt; / &lt;code&gt;limit_get&lt;/code&gt;): set &lt;code&gt;1&lt;/code&gt; to suppress an attribute, &lt;code&gt;0&lt;/code&gt; to allow it. Use it on the high-frequency attributes to cut traffic and clutter. Leave &lt;code&gt;online&lt;/code&gt; and &lt;code&gt;heartBeat&lt;/code&gt; alone.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Per-attribute QoS&lt;/strong&gt; (&lt;code&gt;control_qos&lt;/code&gt; in the presence protocol, &lt;code&gt;qos_set&lt;/code&gt; / &lt;code&gt;qos_get&lt;/code&gt; with &lt;code&gt;Qos_post&lt;/code&gt; in the sleep and fall protocols): the default is QoS 1, where &lt;code&gt;0&lt;/code&gt; is QoS 0, &lt;code&gt;1&lt;/code&gt; is QoS 1 and &lt;code&gt;2&lt;/code&gt; is QoS 2. The presence protocol additionally exposes per-attribute keys — &lt;code&gt;Qos_online&lt;/code&gt;, &lt;code&gt;Qos_movementSigns&lt;/code&gt;, &lt;code&gt;Qos_breathRate&lt;/code&gt;, and so on. This is worth knowing because it contradicts the usual assumption that QoS is decided only by the subscriber.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;checklist-3&quot;&gt;Checklist&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;online&lt;/code&gt; and &lt;code&gt;heartBeat&lt;/code&gt; handled as two separate signals with two separate severities&lt;/li&gt;
&lt;li&gt;[ ] No logic concludes anything from the absence of messages&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;limit_set&lt;/code&gt; used, if at all, only on high-frequency attributes — never on &lt;code&gt;online&lt;/code&gt; or &lt;code&gt;heartBeat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;[ ] No polling of &lt;code&gt;heartBeat&lt;/code&gt;; anything needed on demand read from attributes that document query support&lt;/li&gt;
&lt;li&gt;[ ] Power-up behaviour of &lt;code&gt;heartBeat&lt;/code&gt; measured and recorded&lt;/li&gt;
&lt;li&gt;[ ] Reconnect behaviour of &lt;code&gt;online&lt;/code&gt; measured and recorded&lt;/li&gt;
&lt;li&gt;[ ] Any timeout threshold derived from measured worst-case silence, not assumed&lt;/li&gt;
&lt;li&gt;[ ] Firmware updates placed in a suppression window, and verified afterwards by version report&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;where-to-go-next-5&quot;&gt;Where to go next&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Getting the stream in the first place: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/25/reading-radar-data-your-own-app.html&quot;&gt;Reading Radar Data in Your Own Application&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The broker underneath it: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/23/apache-artemis-mqtt-broker-radar.html&quot;&gt;Apache Artemis setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Keeping several devices straight: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/24/running-several-radars-one-home.html&quot;&gt;Running Several Radars in One Home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Keeping the history: &lt;a rel=&quot;nofollow&quot; href=&quot;https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/26/storing-radar-data-retention-trends.html&quot;&gt;Storing Radar Data&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This is not a medical device. A status field tells you what a radar reports about itself; it does not guarantee what the radar detects. For anyone at real risk, monitoring is an addition to a care routine, never a substitute for one.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32601/online-says-its-connected-heartbeat-says-works-reading-status-signals-instead-guessing</guid>
<pubDate>Tue, 27 Oct 2026 01:03:42 +0000</pubDate>
</item>
<item>
<title>Radar: A New Kubernetes IDE Worth Knowing About (vs OpenLens, FreeLens)</title>
<link>https://coderlegion.com/23255/radar-a-new-kubernetes-ide-worth-knowing-about-vs-openlens-freelens</link>
<description>
&lt;a href=&quot;https://alexandre-vazquez.com/radar-kubernetes-ide-vs-openlens-freelens/&quot; title=&quot;Radar: A New Kubernetes IDE Worth Knowing About (vs OpenLens, FreeLens)&quot; rel=&quot;nofollow&quot;&gt;&lt;img width=&quot;1536&quot; height=&quot;1024&quot; src=&quot;https://mlk5tkvgfeoa.i.optimole.com/cb:s8WW.68/w:1536/h:1024/q:mauto/f:best/https://alexandre-vazquez.com/wp-content/uploads/2026/05/radar-kubernetes-ide.avif&quot; class=&quot;webfeedsFeaturedVisual wp-post-image&quot; alt=&quot;Radar sweep showing OpenLens, FreeLens and Radar as a Kubernetes IDE comparison&quot; style=&quot;display: block; margin: auto; margin-bottom: 5px;max-width: 100%;&quot; title=&quot;Radar: A New Kubernetes IDE Worth Knowing About (vs OpenLens, FreeLens) 14&quot;&gt;&lt;/a&gt;&lt;p&gt;&lt;em&gt;Related reading: &lt;a rel=&quot;nofollow&quot; href=&quot;https://alexandre-vazquez.com/freelens-vs-openlens-vs-lens-kubernetes-ide/&quot;&gt;FreeLens vs OpenLens vs Lens: which one to standardize on&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve been following Kubernetes tooling, you&amp;rsquo;ve probably already been through the Lens saga: Lens went commercial, &lt;a rel=&quot;nofollow&quot; href=&quot;https://alexandre-vazquez.com/openlens-vs-lens/&quot;&gt;OpenLens&lt;/a&gt; emerged as the community fork, then &lt;a rel=&quot;nofollow&quot; href=&quot;https://alexandre-vazquez.com/freelens-vs-openlens-vs-lens/&quot;&gt;FreeLens&lt;/a&gt; appeared when OpenLens maintenance slowed. The pattern is familiar &amp;mdash; a useful desktop tool, a licensing decision, a fork, another fork.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://radarhq.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;Radar&lt;/a&gt; is not a fork. It&amp;rsquo;s a different approach to the same problem: giving engineers a useful interface for Kubernetes clusters without the friction of kubectl for every task. Built by &lt;a href=&quot;https://skyhook.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;Skyhook&lt;/a&gt; (YC-backed, Google Cloud Partner), it&amp;rsquo;s been live since 2025, has 1.7k+ GitHub stars, releases weekly, and the founder reaches out to the community directly. That&amp;rsquo;s usually a good signal that someone is genuinely building in public.&lt;/p&gt;
&lt;p&gt;This article covers what Radar actually does, where it pulls ahead of OpenLens and FreeLens, and when those tools are still the right choice.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-state-of-kubernetes-desktop-tooling-in-2026&quot;&gt;The State of Kubernetes Desktop Tooling in 2026&lt;/h2&gt;
&lt;p&gt;Before getting into Radar specifically, it&amp;rsquo;s worth naming the landscape clearly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lens&lt;/strong&gt; &amp;mdash; the original. Electron-based, polished, now commercial (Mirantis). The free Personal tier is non-commercial only. Pro is ~$22-35/user/month.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenLens&lt;/strong&gt; &amp;mdash; the community fork of Lens before Mirantis closed exec/logs/shell in v6.3 (January 2023). Maintenance has slowed significantly. No active release cadence.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FreeLens&lt;/strong&gt; &amp;mdash; a more active community fork, filling the gap left by OpenLens&amp;rsquo; decline. Restores the missing features. No commercial backing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;k9s&lt;/strong&gt; &amp;mdash; terminal TUI, fast, keyboard-driven, single-cluster. Different audience.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Headlamp&lt;/strong&gt; &amp;mdash; CNCF Sandbox project, plugin-extensible, web-based.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Radar&lt;/strong&gt; &amp;mdash; Go binary, Apache 2.0, team-oriented, topology and event timeline focused.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The problem with OpenLens and FreeLens is not that they&amp;rsquo;re bad tools &amp;mdash; they&amp;rsquo;re genuinely useful for the solo developer with one or two clusters. The problem is that they&amp;rsquo;re single-cluster-at-a-time desktop apps with no concept of team, no persistent state, and no awareness of the modern Kubernetes ecosystem (ArgoCD, Flux, Karpenter, KEDA). As your infrastructure grows, you outgrow them.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-radar-actually-is&quot;&gt;What Radar Actually Is&lt;/h2&gt;
&lt;p&gt;Radar is available in two forms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Radar OSS&lt;/strong&gt; &amp;mdash; a single ~30MB Go binary, Apache 2.0, free forever. Can run locally (desktop app) or deployed in-cluster via Helm. No sidecars, no feature gates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Radar Cloud&lt;/strong&gt; &amp;mdash; same binary, adds a hosted control plane with fleet aggregation, 30-day event retention, SSO/SCIM, scoped RBAC, and shared URLs for team incident response. Priced per cluster ($99/cluster/month for Team), not per user.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The per-cluster pricing is a deliberate design decision &amp;mdash; teams don&amp;rsquo;t pay more as they add engineers, only as they add clusters. For a 20-person platform engineering team managing 5 clusters, Radar Cloud runs $495/month. The equivalent Lens Pro seats would cost $2,200-4,200/month.&lt;/p&gt;
&lt;p&gt;For most self-hosted environments, the OSS version is sufficient and costs nothing.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;key-features&quot;&gt;Key Features&lt;/h2&gt;
&lt;h3 id=&quot;topology-view&quot;&gt;Topology View&lt;/h3&gt;
&lt;p&gt;This is the most visually distinctive feature. Radar renders a live service graph for your cluster: deployments, services, ingresses, cross-namespace dependencies, and east-west traffic flows &amp;mdash; all in a single view without running &lt;code&gt;kubectl get all -A&lt;/code&gt; and stitching the output together mentally.&lt;/p&gt;
&lt;p&gt;OpenLens and FreeLens have resource list views. They show you what exists. Radar shows you how things connect &amp;mdash; which is what you actually need when debugging why Service A can&amp;rsquo;t reach Service B.&lt;/p&gt;
&lt;h3 id=&quot;persistent-event-timeline&quot;&gt;Persistent Event Timeline&lt;/h3&gt;
&lt;p&gt;Kubernetes events are ephemeral by default &amp;mdash; they expire after approximately one hour. When something breaks at 2am and you&amp;rsquo;re looking at it at 9am, the events that explain what happened are gone. Logs may still be there if you&amp;rsquo;re running a log aggregator, but the Kubernetes-level events (pod restarts, scheduling failures, node pressure events, probe failures) are gone.&lt;/p&gt;
&lt;p&gt;Radar retains events. The OSS version extends this beyond the default 1-hour cluster retention. The Cloud version retains 30 days. You can rewind the timeline to any point and reconstruct what the cluster looked like at that moment.&lt;/p&gt;
&lt;p&gt;Neither OpenLens nor FreeLens have any event retention beyond what the cluster itself provides.&lt;/p&gt;
&lt;h3 id=&quot;gitops-integration-argocd-flux&quot;&gt;GitOps Integration (ArgoCD + Flux)&lt;/h3&gt;
&lt;p&gt;Radar auto-detects ArgoCD and Flux and surfaces sync state, drift, and health directly in the UI. You can see whether a deployment is in sync, when it last synced, and whether it drifted from the desired state in Git.&lt;/p&gt;
&lt;p&gt;In OpenLens and FreeLens, ArgoCD resources appear as generic Kubernetes custom resources. You can see the CRDs, but there&amp;rsquo;s no purpose-built understanding of what they mean &amp;mdash; no sync status visualization, no diff view, no rollback trigger.&lt;/p&gt;
&lt;h3 id=&quot;helm-management&quot;&gt;Helm Management&lt;/h3&gt;
&lt;p&gt;Radar tracks Helm releases with full revision history and supports one-click rollbacks from the UI. This is similar to what OpenLens/FreeLens offer via the Helm releases view, but Radar adds revision diffing &amp;mdash; you can see what changed between release 5 and release 6 before deciding to roll back.&lt;/p&gt;
&lt;h3 id=&quot;image-filesystem&quot;&gt;Image Filesystem&lt;/h3&gt;
&lt;p&gt;You can browse container image filesystems through Radar without needing &lt;code&gt;kubectl exec&lt;/code&gt; into a running pod or access to the container registry. Useful for security audits and debugging &amp;mdash; you can verify what&amp;rsquo;s actually in an image at rest.&lt;/p&gt;
&lt;h3 id=&quot;mcp-server-ai-integration&quot;&gt;MCP Server (AI Integration)&lt;/h3&gt;
&lt;p&gt;Radar ships with an &lt;a rel=&quot;nofollow&quot; href=&quot;https://alexandre-vazquez.com/mcp-kubernetes/&quot;&gt;MCP (Model Context Protocol)&lt;/a&gt; server, which means you can connect Claude, Cursor, or GitHub Copilot directly to your cluster context and ask questions about it in natural language. The MCP server is token-optimized &amp;mdash; it doesn&amp;rsquo;t dump raw YAML at the model, it structures cluster state into meaningful context.&lt;/p&gt;
&lt;p&gt;This is something neither OpenLens nor FreeLens have. It&amp;rsquo;s also something that&amp;rsquo;s genuinely useful if you&amp;rsquo;re already using AI assistants for development work.&lt;/p&gt;
&lt;h3 id=&quot;cluster-audit&quot;&gt;Cluster Audit&lt;/h3&gt;
&lt;p&gt;30 built-in best-practice checks &amp;mdash; resource requests/limits, RBAC permissions, image pinning, network policies, security contexts. The checks are labeled by compliance framework. This is not a replacement for dedicated security tooling (Trivy, Falco, Polaris), but it&amp;rsquo;s a useful first-pass audit without leaving the tool you&amp;rsquo;re already using.&lt;/p&gt;
&lt;h3 id=&quot;multi-cluster-support-cloud&quot;&gt;Multi-Cluster Support (Cloud)&lt;/h3&gt;
&lt;p&gt;The Cloud tier adds fleet-level visibility: a single view across all clusters, cross-cluster search, and drift detection between environments (e.g., staging vs. production). This is the feature that changes the calculus for platform engineering teams managing 5+ clusters.&lt;/p&gt;
&lt;p&gt;OpenLens and FreeLens require you to switch cluster context manually. There is no fleet view.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;architecture-why-a-go-binary-matters&quot;&gt;Architecture: Why a Go Binary Matters&lt;/h2&gt;
&lt;p&gt;OpenLens and FreeLens are Electron apps &amp;mdash; Chromium + Node.js wrapped in a desktop shell. This means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;200-500MB install size&lt;/li&gt;
&lt;li&gt;1-2 second startup time on a fast machine, more on slower ones&lt;/li&gt;
&lt;li&gt;Memory footprint in the hundreds of megabytes&lt;/li&gt;
&lt;li&gt;Local kubeconfig required on each engineer&amp;rsquo;s machine&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Radar&amp;rsquo;s in-cluster deployment is a single Go binary (~30MB) that runs as a Pod with a ServiceAccount. It connects to the hosted control plane over outbound WebSocket + TLS. No inbound firewall rules, no kubeconfig distribution, no per-engineer setup.&lt;/p&gt;
&lt;p&gt;The local desktop app is also a lightweight Go binary &amp;mdash; 65-second startup was demonstrated on a 322-node cluster. That&amp;rsquo;s not a typo.&lt;/p&gt;
&lt;p&gt;For in-cluster deployment, the architecture means security is handled at the ServiceAccount level, not by distributing kubeconfigs to engineer laptops. That matters for teams with security requirements around credential management.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;feature-comparison&quot;&gt;Feature Comparison&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Radar OSS&lt;/th&gt;
&lt;th&gt;Radar Cloud&lt;/th&gt;
&lt;th&gt;OpenLens&lt;/th&gt;
&lt;th&gt;FreeLens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;License&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;td&gt;Proprietary (hosted)&lt;/td&gt;
&lt;td&gt;MIT/GPL&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maintenance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Active (weekly releases)&lt;/td&gt;
&lt;td&gt;Active&lt;/td&gt;
&lt;td&gt;Stalled&lt;/td&gt;
&lt;td&gt;Active (community)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Go binary / in-cluster&lt;/td&gt;
&lt;td&gt;In-cluster + hosted&lt;/td&gt;
&lt;td&gt;Electron&lt;/td&gt;
&lt;td&gt;Electron&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-cluster&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;Fleet view&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png&quot; alt=&quot;&amp;#10060;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png&quot; alt=&quot;&amp;#10060;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Event retention&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Extended&lt;/td&gt;
&lt;td&gt;30 days&lt;/td&gt;
&lt;td&gt;Cluster default (~1h)&lt;/td&gt;
&lt;td&gt;Cluster default (~1h)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Topology view&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png&quot; alt=&quot;&amp;#10060;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png&quot; alt=&quot;&amp;#10060;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitOps (ArgoCD/Flux)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;CRDs only&lt;/td&gt;
&lt;td&gt;CRDs only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Helm management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;kubectl exec / logs / shell&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt; (restored)&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP / AI integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png&quot; alt=&quot;&amp;#10060;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png&quot; alt=&quot;&amp;#10060;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cluster audit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png&quot; alt=&quot;&amp;#10060;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png&quot; alt=&quot;&amp;#10060;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SSO / SCIM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png&quot; alt=&quot;&amp;#10060;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png&quot; alt=&quot;&amp;#10060;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png&quot; alt=&quot;&amp;#10060;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shared incident URLs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png&quot; alt=&quot;&amp;#10060;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png&quot; alt=&quot;&amp;#10060;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png&quot; alt=&quot;&amp;#10060;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Image filesystem browser&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png&quot; alt=&quot;&amp;#10060;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png&quot; alt=&quot;&amp;#10060;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost tracking&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt; (OpenCost)&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png&quot; alt=&quot;&amp;#10060;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png&quot; alt=&quot;&amp;#10060;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Price&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;$99/cluster/month&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;when-radar-makes-sense&quot;&gt;When Radar Makes Sense&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;You&amp;rsquo;re managing multiple clusters.&lt;/strong&gt; Even with the OSS version, the topology view and event timeline make Radar more useful than OpenLens/FreeLens at 3+ clusters. The Cloud fleet view is the compelling option at 5+.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Your team uses GitOps.&lt;/strong&gt; If ArgoCD or Flux is part of your workflow, Radar&amp;rsquo;s native understanding of sync state and drift is meaningfully better than seeing CRDs in a generic list view.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You need post-mortem capability.&lt;/strong&gt; If your incident review process involves looking at what the cluster was doing when the alert fired, you need event retention. Radar has it; OpenLens and FreeLens don&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You&amp;rsquo;re adopting AI tooling.&lt;/strong&gt; The MCP server is the most forward-looking feature here. If you use Claude Code, Cursor, or Copilot for your infrastructure work, having cluster context available to those tools without copy-pasting YAML is a genuine productivity improvement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You have a platform engineering team.&lt;/strong&gt; Per-cluster pricing, SSO, SCIM, and shared incident URLs are features that only matter if you have more than one person managing infrastructure.&lt;/p&gt;
&lt;h2 id=&quot;when-openlens-or-freelens-still-makes-sense&quot;&gt;When OpenLens or FreeLens Still Makes Sense&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;You&amp;rsquo;re a solo developer with one or two clusters.&lt;/strong&gt; OpenLens and FreeLens are familiar, local, and have zero setup overhead. If you don&amp;rsquo;t need team features, event retention, or topology views, they remain perfectly functional tools.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You&amp;rsquo;re deeply invested in the Lens UX.&lt;/strong&gt; The resource tree, the terminal integration, the way Lens presents namespace-scoped resources &amp;mdash; if your muscle memory is built around that interface, switching has a real cost. Radar is different, not just better.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You need maximum customi&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/23255/radar-a-new-kubernetes-ide-worth-knowing-about-vs-openlens-freelens</guid>
<pubDate>Wed, 29 Jul 2026 10:00:00 +0000</pubDate>
</item>
</channel>
</rss>