<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: fuxinsuo</title>
    <description>The latest articles on DEV Community by fuxinsuo (@249263431).</description>
    <link>https://dev.to/249263431</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4190046%2Ffc057b5f-aef1-4d96-b8a2-7862d1c6b7af.png</url>
      <title>DEV Community: fuxinsuo</title>
      <link>https://dev.to/249263431</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/249263431"/>
    <language>en</language>
    <item>
      <title>Delivered Is Not Seen: What a Fall Alert Needs After the Message Arrives</title>
      <dc:creator>fuxinsuo</dc:creator>
      <pubDate>Mon, 02 Nov 2026 00:38:10 +0000</pubDate>
      <link>https://dev.to/249263431/delivered-is-not-seen-what-a-fall-alert-needs-after-the-message-arrives-1a4l</link>
      <guid>https://dev.to/249263431/delivered-is-not-seen-what-a-fall-alert-needs-after-the-message-arrives-1a4l</guid>
      <description>&lt;p&gt;The previous article in this series was about getting a fall alert onto the right phone: why the fallback layer should be SMS and a placed call rather than a push notification, and what each channel guarantees on the way there.&lt;/p&gt;

&lt;p&gt;This one picks up where that left off, at the moment the message has already arrived. Because arriving is not the same as being noticed, and being noticed is not the same as someone taking responsibility. Most alert systems that fail in the field do not fail at delivery. They fail here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three states, and only two of them are observable
&lt;/h2&gt;

&lt;p&gt;A single alert passes through three states:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Delivered&lt;/strong&gt; — the message reached the device.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seen&lt;/strong&gt; — a person actually looked at it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Answered&lt;/strong&gt; — a person took responsibility for it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first and the third can be observed. The middle one cannot, and this is the single most important thing to accept before designing anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delivered&lt;/strong&gt; is observable because the operator network reports it: a delivery report for SMS, an answered or unanswered state for a call. Both come from outside your system and both are real.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Answered&lt;/strong&gt; is observable only because you built a way to record it — an acknowledge action, a digit pressed during a call, a reply. It exists because you decided it should.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seen&lt;/strong&gt; has no signal at all. Nothing in any messaging channel tells you a human being read a thing. Read receipts are a partial exception and a weak one: they report that an app was opened, not that a person understood anything, and they are not available on the channels a fall alert actually uses.&lt;/p&gt;

&lt;p&gt;So the practical consequence is this: &lt;strong&gt;you cannot measure attention, so you must design for a proxy.&lt;/strong&gt; The only trustworthy proxy is an acknowledge action. Everything in the rest of this article follows from that one sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "seen" fails
&lt;/h2&gt;

&lt;p&gt;It is worth being concrete about the ways an alert arrives and is not noticed, because they are ordinary and they are not exotic failures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Notification grouping.&lt;/strong&gt; Smartphones sort messages into categories. A message that is not from a person tends to land somewhere other than the place its owner looks first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus and quiet modes.&lt;/strong&gt; Scheduled automatically overnight — which is a large fraction of the hours a fall happens in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The phone is not with the person.&lt;/strong&gt; Charging in another room, face down, on silent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The alert went to the wrong member of the household.&lt;/strong&gt; One device was enrolled, several people live there, and the one who was enrolled is not the one who is available.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Several people were notified, and each assumed another was handling it.&lt;/strong&gt; When a group is told, responsibility spreads across the group and can end up nowhere. This is a well-documented pattern in team coordination, and a family is not immune to it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these is a channel problem. Every one of them happens &lt;em&gt;after&lt;/em&gt; a perfectly successful delivery. That is why a better channel does not fix them, and why they need to be designed against rather than engineered away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alert fatigue: the one failure that disables every channel at once
&lt;/h2&gt;

&lt;p&gt;Every channel discussed in the previous article — SMS, a placed call, push — works on the assumption that the recipient treats an incoming alert as worth attention. That assumption is the load-bearing part, and it is the part that breaks.&lt;/p&gt;

&lt;p&gt;If alerts arrive often enough that they stop meaning anything, the recipient stops looking. Once that happens, &lt;strong&gt;every channel fails simultaneously.&lt;/strong&gt; It does not matter that the SMS was delivered by a carrier-grade store-and-forward network, or that the call was placed by a retry queue you tuned carefully. A message nobody opens is indistinguishable from a message that never arrived.&lt;/p&gt;

&lt;p&gt;This is the reason to treat false alarms as an alerting problem and not only a sensing problem. The sources of false alarms, and what has been done about them at the algorithm layer, are covered in &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/19/fall-alarm-market-demand-false-alarms.html" rel="noopener noreferrer"&gt;Real Demand, Hard Physics&lt;/a&gt;. This article is about the other half: what a false alarm does to the delivery chain downstream.&lt;/p&gt;

&lt;p&gt;Two design rules follow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The value of an alert is its rarity, not its volume.&lt;/strong&gt; An alert that fires ten times a day carries almost no information on the eleventh. An alert that fires once a month is read immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Severity should control intrusiveness, not the reverse.&lt;/strong&gt; Not every event deserves a phone call. A static-dwell or out-of-bed notice is not the same class of event as a fall. If everything arrives at maximum urgency, urgency stops being a signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cancel path: an alert needs a way to be withdrawn
&lt;/h2&gt;

&lt;p&gt;An alert that cannot be withdrawn is an alert that will eventually be muted.&lt;/p&gt;

&lt;p&gt;There are two places a cancellation can come from, and a complete design has both:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From the person on the floor.&lt;/strong&gt; If the resident is fine, they should be able to say so before anyone is disturbed. On the finished fall radar this already exists: the unit speaks after a fall and accepts a spoken reply, and a spoken confirmation stands the local alarm down — see the &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/product/2026/10/17/fall-radar-guide.html" rel="noopener noreferrer"&gt;fall radar guide&lt;/a&gt; for the phrase set and its limits. If you build on a module instead, the local cancel has to be your own hardware or firmware.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From a person who received it.&lt;/strong&gt; Whoever gets the message needs a way to say "I am handling this" — a digit pressed during the call, a reply, an acknowledge in whatever interface you ship.&lt;/p&gt;

&lt;p&gt;Two things are easy to get wrong here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A cancellation that nobody else hears about is not a cancellation.&lt;/strong&gt; If four people were notified and one acknowledges, the other three are still worried. Notify the rest that it is handled. This is cheap and it is the difference between a system people trust and one they find exhausting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The window has to be long enough to be usable and short enough to matter.&lt;/strong&gt; Too short and nobody can react before the escalation fires. Too long and the escalation is pointless. This is a product decision that belongs in configuration, visible to whoever commissions the installation — not a constant buried in your service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Escalation parameters: what actually decides whether someone acts
&lt;/h2&gt;

&lt;p&gt;Escalation is usually described as a sequence: notify, wait, escalate. The interesting decisions are all in the parameters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Escalate on the absence of an acknowledge, not on elapsed time alone.&lt;/strong&gt; Elapsed time is a fallback for when your acknowledge mechanism is unavailable. If you have an acknowledge mechanism, the absence of one within the window is the real trigger, because it is the one signal that means "nobody has taken this".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parallel notification spreads responsibility; serial escalation concentrates it.&lt;/strong&gt; Telling everyone at once is fast, and it is the right choice for the first tier — cheap channels, low intrusion, everybody informed. But if nobody responds, continuing to tell everyone at once rarely helps. Moving to one person at a time, in a defined order, means someone specific is being asked. Stop at the first answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Intrusiveness should climb one step at a time.&lt;/strong&gt; A text that nobody reads is not a reason to immediately place a call; the call is the step after the text has had a fair chance. Escalating straight to the most intrusive channel burns the channel's effectiveness for the genuine emergency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decide in advance where escalation stops.&lt;/strong&gt; Every loop needs a terminus: a maximum number of attempts, a maximum number of people, or both. Without a ceiling, a misconfigured sensor turns into a repeated interruption, and repeated interruption is exactly what produces the fatigue described above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Say who is responsible when nobody answers.&lt;/strong&gt; If the chain reaches its terminus with no acknowledge, that state needs to be visible somewhere — a record, an entry, an unresolved item — rather than simply ending. An alert that nobody saw and an alert that nobody acknowledged are the same outcome, and both should leave a trace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three kinds of receipt, and which one you can trust
&lt;/h2&gt;

&lt;p&gt;Worth separating clearly, because they get conflated and the conflation produces bad logic:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Receipt&lt;/th&gt;
&lt;th&gt;Where it comes from&lt;/th&gt;
&lt;th&gt;What it actually proves&lt;/th&gt;
&lt;th&gt;Trust it?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Delivery receipt&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The operator network&lt;/td&gt;
&lt;td&gt;The message reached the handset&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Read receipt&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The receiving application, when one exists&lt;/td&gt;
&lt;td&gt;An app was opened&lt;/td&gt;
&lt;td&gt;No — not evidence of comprehension, and not available on SMS or calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Acknowledge&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your own design&lt;/td&gt;
&lt;td&gt;A person acted&lt;/td&gt;
&lt;td&gt;Yes — the only one you control&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The delivery receipt tells you your half worked. The acknowledge tells you the whole loop worked. The read receipt tells you very little, and on the channels that matter for a fall alert it does not exist at all.&lt;/p&gt;

&lt;p&gt;The practical rule: &lt;strong&gt;build your logic on delivery receipts and acknowledges.&lt;/strong&gt; Anything that depends on inferring attention from silence is guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring the chain itself
&lt;/h2&gt;

&lt;p&gt;One alert failing is a small problem. The chain failing silently is a large one, because it looks identical to a quiet day.&lt;/p&gt;

&lt;p&gt;The counters worth keeping, and they are all simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alerts emitted&lt;/strong&gt; — how many events your system decided to tell someone about&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delivered&lt;/strong&gt; — how many the network confirmed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acknowledged&lt;/strong&gt; — how many a person took responsibility for&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time to acknowledge&lt;/strong&gt; — the distribution, not the average&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unresolved&lt;/strong&gt; — reached the end of the chain with nobody answering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A collapsing acknowledge rate is a much earlier warning than a complaint from a customer. It is the number that tells you the system is on its way to becoming background noise.&lt;/p&gt;

&lt;p&gt;Two related traps, both covered in detail elsewhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Do not conclude anything from silence.&lt;/strong&gt; Absence of messages is the normal state of a working installation, because these radars report on change. See &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/25/reading-radar-data-your-own-app.html" rel="noopener noreferrer"&gt;Reading Radar Data in Your Own Application&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The alerting service is itself a single point of failure.&lt;/strong&gt; A device that has lost its network link cannot tell you, and neither can a forwarding service that has stopped. Treat the health of the chain as its own monitored object, with the same discipline described in &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/27/online-heartbeat-radar-health.html" rel="noopener noreferrer"&gt;Online and HeartBeat&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The other half of the chain, choosing the channel: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/11/01/sms-voice-alert-delivery-fall-radar.html" rel="noopener noreferrer"&gt;What SMS and Voice Calls Give a Fall Alert That Push Notifications Cannot&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Where false alarms come from, and what has been fixed: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/19/fall-alarm-market-demand-false-alarms.html" rel="noopener noreferrer"&gt;Real Demand, Hard Physics&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The hand-off itself, including the local voice confirmation: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/product/2026/10/17/fall-radar-guide.html" rel="noopener noreferrer"&gt;the fall radar guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Escalation across three radars in one home: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/23/aging-in-place-care-system-three-radars.html" rel="noopener noreferrer"&gt;Building an Aging-in-Place Care System&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Device health before you alert on it: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/27/online-heartbeat-radar-health.html" rel="noopener noreferrer"&gt;Online and HeartBeat&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;More questions answered: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/faq.html" rel="noopener noreferrer"&gt;the FAQ&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A note on what this is
&lt;/h2&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 what happens after a reported event reaches a person. Good alert design makes sure a real event is noticed and owned. It cannot make an undetected event appear, and no amount of escalation logic improves the sensing underneath it.&lt;/p&gt;

&lt;p&gt;Evaluation units and modules are on &lt;a href="https://www.tindie.com/stores/fuxinsuo/" rel="noopener noreferrer"&gt;Tindie&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.tindie.com/stores/fuxinsuo/" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Check out our 60 GHz and 24 GHz mmWave radar modules for fall detection, sleep sensing, and presence sensing.&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>alerting</category>
      <category>iot</category>
      <category>radar</category>
      <category>escalation</category>
    </item>
    <item>
      <title>What SMS and Voice Calls Give a Fall Alert That Push Notifications Cannot</title>
      <dc:creator>fuxinsuo</dc:creator>
      <pubDate>Sun, 01 Nov 2026 04:33:43 +0000</pubDate>
      <link>https://dev.to/249263431/what-sms-and-voice-calls-give-a-fall-alert-that-push-notifications-cannot-31k5</link>
      <guid>https://dev.to/249263431/what-sms-and-voice-calls-give-a-fall-alert-that-push-notifications-cannot-31k5</guid>
      <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;h2&gt;
  
  
  Step five is a chain, not a message
&lt;/h2&gt;

&lt;p&gt;"Escalation" 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. "The radar missed it" is very often "the radar reported correctly and nobody found out."&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 "will our users install our app" — it is whether the person who must be reachable has done their part before the one day it counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two networks, not two vendors
&lt;/h2&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;div class="table-wrapper-paragraph"&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;/div&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;h2&gt;
  
  
  What the telecom path gives you
&lt;/h2&gt;

&lt;p&gt;Six properties, roughly in order of how much they matter here.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Nothing is required of the recipient
&lt;/h3&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;h3&gt;
  
  
  2. It does not depend on a data connection
&lt;/h3&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;h3&gt;
  
  
  3. The retry belongs to someone else
&lt;/h3&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;h3&gt;
  
  
  4. An answered call is an acknowledgment
&lt;/h3&gt;

&lt;p&gt;Push and SMS can both report "delivered". 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;h3&gt;
  
  
  5. Delivery you can measure
&lt;/h3&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&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;/div&gt;



&lt;p&gt;Every branch above is triggered by something arriving. That is worth contrasting with the alternative, where "probably delivered" is the strongest signal you get and silence has to be interpreted.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. The alerting logic stays yours
&lt;/h3&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;h2&gt;
  
  
  What it looks like assembled
&lt;/h2&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 "I am OK" stands the local alarm down — see the phrase set and its limits in the &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/product/2026/10/17/fall-radar-guide.html" rel="noopener noreferrer"&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 href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/gpio-vs-uart-fall-alarm-without-parser.html" rel="noopener noreferrer"&gt;GPIO vs UART&lt;/a&gt; and &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/fall-alarm-relay-from-gp1.html" rel="noopener noreferrer"&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 "I am handling it", 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;h2&gt;
  
  
  The first half of the chain is not immune
&lt;/h2&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 href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/25/reading-radar-data-your-own-app.html" rel="noopener noreferrer"&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 href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/27/online-heartbeat-radar-health.html" rel="noopener noreferrer"&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;h2&gt;
  
  
  What telecom channels do not solve
&lt;/h2&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 href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/19/fall-alarm-market-demand-false-alarms.html" rel="noopener noreferrer"&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 "yes, call me for fall alarms". 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 "installs are still a barrier". 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;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Step five across three radars in one home: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/23/aging-in-place-care-system-three-radars.html" rel="noopener noreferrer"&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 href="https://249263431.github.io/60GHz-Radar-SDK/product/2026/10/17/fall-radar-guide.html" rel="noopener noreferrer"&gt;the fall radar guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;What genuinely defeats detection: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/fall-detection-failure-modes.html" rel="noopener noreferrer"&gt;fall detection failure modes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Getting events reliably to your backend: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/25/reading-radar-data-your-own-app.html" rel="noopener noreferrer"&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 href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/27/online-heartbeat-radar-health.html" rel="noopener noreferrer"&gt;Online and HeartBeat&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The broker underneath: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/23/apache-artemis-mqtt-broker-radar.html" rel="noopener noreferrer"&gt;Apache Artemis setup&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;More questions answered: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/faq.html" rel="noopener noreferrer"&gt;the FAQ&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A note on what this is
&lt;/h2&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 href="https://www.tindie.com/stores/fuxinsuo/" rel="noopener noreferrer"&gt;Tindie&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.tindie.com/stores/fuxinsuo/" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Check out our 60 GHz and 24 GHz mmWave radar modules for fall detection, sleep sensing, and presence sensing.&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>radar</category>
      <category>sms</category>
      <category>iot</category>
      <category>alerting</category>
    </item>
    <item>
      <title>What Data-Driven Algorithms Changed in Contactless Sleep Sensing, and What They Could Not</title>
      <dc:creator>fuxinsuo</dc:creator>
      <pubDate>Fri, 30 Oct 2026 22:39:18 +0000</pubDate>
      <link>https://dev.to/249263431/what-data-driven-algorithms-changed-in-contactless-sleep-sensing-and-what-they-could-not-5fe7</link>
      <guid>https://dev.to/249263431/what-data-driven-algorithms-changed-in-contactless-sleep-sensing-and-what-they-could-not-5fe7</guid>
      <description>&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;h2&gt;
  
  
  The hand-tuned era
&lt;/h2&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;h2&gt;
  
  
  What learning from data changed
&lt;/h2&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;h2&gt;
  
  
  What did not change
&lt;/h2&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 href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/19/sleep-sensing-ring-watch-radar-module-infrared.html" rel="noopener noreferrer"&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;h2&gt;
  
  
  What the radar actually publishes
&lt;/h2&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;div class="table-wrapper-paragraph"&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;/div&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;h2&gt;
  
  
  How to read the output
&lt;/h2&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 href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/26/storing-radar-data-retention-trends.html" rel="noopener noreferrer"&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;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The comparison across four sensing approaches, and where polysomnography sits: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/19/sleep-sensing-ring-watch-radar-module-infrared.html" rel="noopener noreferrer"&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 href="https://249263431.github.io/60GHz-Radar-SDK/product/2026/10/17/sleep-radar-guide.html" rel="noopener noreferrer"&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 href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/26/storing-radar-data-retention-trends.html" rel="noopener noreferrer"&gt;Storing Radar Data&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Reading the stream directly: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/25/reading-radar-data-your-own-app.html" rel="noopener noreferrer"&gt;Reading Radar Data in Your Own Application&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Treating quoted specifications with care: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/radar-spec-limits.html" rel="noopener noreferrer"&gt;Three mmWave Radar Specs That Are Almost Always Exaggerated&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&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;&lt;a href="https://www.tindie.com/stores/fuxinsuo/" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Check out our mmWave radar modules for sleep and presence sensing&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>radar</category>
      <category>iot</category>
      <category>sleep</category>
      <category>health</category>
    </item>
    <item>
      <title>Antenna Channels, Virtual Aperture, and What More of Them Actually Buys You</title>
      <dc:creator>fuxinsuo</dc:creator>
      <pubDate>Thu, 29 Oct 2026 22:52:27 +0000</pubDate>
      <link>https://dev.to/249263431/antenna-channels-virtual-aperture-and-what-more-of-them-actually-buys-you-302d</link>
      <guid>https://dev.to/249263431/antenna-channels-virtual-aperture-and-what-more-of-them-actually-buys-you-302d</guid>
      <description>&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;h2&gt;
  
  
  What a channel count means
&lt;/h2&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;N = T x R
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;h3&gt;
  
  
  Angular resolution
&lt;/h3&gt;

&lt;p&gt;With elements at half-wavelength spacing, the angular resolution at broadside is approximately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;theta_res ≈ 2 / N  radians  =  114.6 / N  degrees
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&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;/div&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;h3&gt;
  
  
  Coherent combining gain
&lt;/h3&gt;

&lt;p&gt;Processing across &lt;code&gt;N&lt;/code&gt; channels gives a combining gain of about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;G = 10 x log10(N)  dB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&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;/div&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;h3&gt;
  
  
  Elevation
&lt;/h3&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;h2&gt;
  
  
  What a channel count does not decide
&lt;/h2&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Δd = c / (2B)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Displacement sensitivity&lt;/strong&gt; depends on wavelength and phase, not on channels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Δφ = 4π x Δx / λ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;h2&gt;
  
  
  What the three jobs actually require
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&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;/div&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;h2&gt;
  
  
  Why more channels are not automatically better for home monitoring
&lt;/h2&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 href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/28/distance-speed-two-equations.html" rel="noopener noreferrer"&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;h2&gt;
  
  
  When more channels are genuinely needed
&lt;/h2&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;h2&gt;
  
  
  Choosing for the job
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&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;/div&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&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;h2&gt;
  
  
  Summary
&lt;/h2&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;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The equations behind range and velocity: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/28/distance-speed-two-equations.html" rel="noopener noreferrer"&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 href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/29/beam-coverage-installation-geometry.html" rel="noopener noreferrer"&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 href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/60ghz-24ghz-radar-modules-oem-integration.html" rel="noopener noreferrer"&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 href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/radar-spec-limits.html" rel="noopener noreferrer"&gt;Three mmWave Radar Specs That Are Almost Always Exaggerated&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Angular and range figures here follow from the standard relations for a virtual array at half-wavelength spacing, and are theoretical bounds rather than measured performance.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.tindie.com/stores/fuxinsuo/" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Check out our mmWave radar modules for sleep and presence sensing&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>radar</category>
      <category>iot</category>
      <category>antenna</category>
      <category>mimo</category>
    </item>
    <item>
      <title>Where the Beam Actually Lands: Coverage from Beam Angle and Mounting Height</title>
      <dc:creator>fuxinsuo</dc:creator>
      <pubDate>Wed, 28 Oct 2026 23:41:04 +0000</pubDate>
      <link>https://dev.to/249263431/where-the-beam-actually-lands-coverage-from-beam-angle-and-mounting-height-4cam</link>
      <guid>https://dev.to/249263431/where-the-beam-actually-lands-coverage-from-beam-angle-and-mounting-height-4cam</guid>
      <description>&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;h2&gt;
  
  
  Two quantities, not one
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&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;/div&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;h2&gt;
  
  
  The geometry
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Axis pointing straight down
&lt;/h3&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;r = h x tan(θ / 2)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;h3&gt;
  
  
  Axis tilted
&lt;/h3&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&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;/div&gt;



&lt;p&gt;The useful angles:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&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;/div&gt;

&lt;h2&gt;
  
  
  Presence module: R24AVD1, ceiling
&lt;/h2&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&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;/div&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;h2&gt;
  
  
  Fall module: R60AFD1-V, ceiling
&lt;/h2&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;div class="table-wrapper-paragraph"&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;/div&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;h2&gt;
  
  
  Sleep module: R60ABD1, tilted above the bed
&lt;/h2&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&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;/div&gt;



&lt;p&gt;At 30°:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&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;/div&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;h2&gt;
  
  
  Why the two numbers diverge
&lt;/h2&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 "something is there" — 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;h2&gt;
  
  
  What geometry does not tell you
&lt;/h2&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 href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/19/ghost-targets-multipath-interference.html" rel="noopener noreferrer"&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;h2&gt;
  
  
  Verifying it on site
&lt;/h2&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 href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/24/distance-position-debug-installation.html" rel="noopener noreferrer"&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;h2&gt;
  
  
  Checklist
&lt;/h2&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;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Where to put a sleep radar, and what to avoid: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/16/sleep-radar-installation-position.html" rel="noopener noreferrer"&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 href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/60ghz-24ghz-radar-modules-oem-integration.html" rel="noopener noreferrer"&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 href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/radar-spec-limits.html" rel="noopener noreferrer"&gt;Three mmWave Radar Specs That Are Almost Always Exaggerated&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Commissioning the result: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/24/distance-position-debug-installation.html" rel="noopener noreferrer"&gt;Using Distance and Position Output to Debug a Radar Installation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Computed footprints follow from published beam angles and published mounting heights. Detection ranges are measured figures; where geometry and a quoted range disagree, verify on site rather than assuming either.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.tindie.com/stores/fuxinsuo/" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Check out our mmWave radar modules for sleep and presence sensing&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>radar</category>
      <category>iot</category>
      <category>geometry</category>
      <category>installation</category>
    </item>
    <item>
      <title>Distance and Speed from a Radar Echo: Two Equations, and What the Module Datasheets Publish</title>
      <dc:creator>fuxinsuo</dc:creator>
      <pubDate>Tue, 27 Oct 2026 23:22:44 +0000</pubDate>
      <link>https://dev.to/249263431/distance-and-speed-from-a-radar-echo-two-equations-and-what-the-module-datasheets-publish-25p9</link>
      <guid>https://dev.to/249263431/distance-and-speed-from-a-radar-echo-two-equations-and-what-the-module-datasheets-publish-25p9</guid>
      <description>&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 href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/how-mmwave-radar-works.html" rel="noopener noreferrer"&gt;How mmWave Radar Works: From FMCW to Breathing Detection&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Range
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The underlying idea: round-trip time
&lt;/h3&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;d = c · t / 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The division by two accounts for the outbound and return legs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the time is not measured directly
&lt;/h3&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;h3&gt;
  
  
  How it is done instead: FMCW
&lt;/h3&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;h3&gt;
  
  
  What sets how finely two targets can be separated
&lt;/h3&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Δ d = c / 2B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Δd = c / (2B) = 3x10^8 / (2 x 1.5x10^9) = 0.10 m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;h2&gt;
  
  
  Velocity
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Doppler effect
&lt;/h3&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;h3&gt;
  
  
  The equation
&lt;/h3&gt;

&lt;p&gt;With wavelength &lt;code&gt;λ&lt;/code&gt; and Doppler shift &lt;code&gt;Δf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v = Δ f · λ / 2, Δ f = 2v / λ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The factor of two again comes from the two-way path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wavelength from the published frequency
&lt;/h3&gt;

&lt;p&gt;Wavelength follows from the carrier: &lt;code&gt;λ = c / f&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&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;/div&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;h3&gt;
  
  
  Shift magnitudes in practice
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&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;/div&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;h3&gt;
  
  
  Where sub-millimetre sensitivity comes from
&lt;/h3&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tex"&gt;&lt;code&gt;Δ φ = 4&lt;span class="k"&gt;\pi&lt;/span&gt; · Δ x / λ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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 href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/how-mmwave-radar-works.html" rel="noopener noreferrer"&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;h2&gt;
  
  
  Three parameters that set precision and span
&lt;/h2&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Δv = λ / (2 · T_obs)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v_max = λ / 4 · T_c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;h2&gt;
  
  
  Published output cadences
&lt;/h2&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;div class="table-wrapper-paragraph"&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;/div&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 href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/radar-spec-limits.html" rel="noopener noreferrer"&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 href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/24/distance-position-debug-installation.html" rel="noopener noreferrer"&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;h2&gt;
  
  
  The two bands side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&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;/div&gt;

&lt;h2&gt;
  
  
  What this accounts for
&lt;/h2&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 href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/radar-spec-limits.html" rel="noopener noreferrer"&gt;Three mmWave Radar Specs That Are Almost Always Exaggerated&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&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;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The conceptual version without arithmetic: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/how-mmwave-radar-works.html" rel="noopener noreferrer"&gt;How mmWave Radar Works&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Practical limits on angle, distance and speed: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/radar-spec-limits.html" rel="noopener noreferrer"&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 href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/60ghz-24ghz-radar-modules-oem-integration.html" rel="noopener noreferrer"&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 href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/24/distance-position-debug-installation.html" rel="noopener noreferrer"&gt;Using Distance and Position Output to Debug a Radar Installation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Derived figures in this article follow from the equations and the published transmit bands. Where a datasheet does not publish a parameter, the equation is given and the absence is stated, rather than filled in.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.tindie.com/stores/fuxinsuo/" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Check out our mmWave radar modules for sleep and presence sensing&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>radar</category>
      <category>iot</category>
      <category>physics</category>
      <category>fmcw</category>
    </item>
    <item>
      <title>Updating Radar Firmware Without a Cloud: Self-Hosted OTA over MQTT</title>
      <dc:creator>fuxinsuo</dc:creator>
      <pubDate>Tue, 27 Oct 2026 22:49:27 +0000</pubDate>
      <link>https://dev.to/249263431/updating-radar-firmware-without-a-cloud-self-hosted-ota-over-mqtt-224g</link>
      <guid>https://dev.to/249263431/updating-radar-firmware-without-a-cloud-self-hosted-ota-over-mqtt-224g</guid>
      <description>&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 article 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 article says so rather than filling the gap.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Scope first
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Over-the-air 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/&amp;lt;DeviceID&amp;gt;/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/&amp;lt;DeviceID&amp;gt;/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;&lt;strong&gt;No such command appears in its protocol document&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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;h2&gt;
  
  
  Three commands
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Select the target module&lt;/strong&gt; — required before every update.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ota_set"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ota_set"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"otaInform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"firmwareUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://192.168.31.88/app.rbl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8090"&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The device fetches the firmware from that URL and port and starts updating. A failure comes back as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"opt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"otaInform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"res"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fail"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"msg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ota failed!"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Observe progress.&lt;/strong&gt; The report is described as an active report during update, sent with method &lt;code&gt;ota_post&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ota_post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"downloadProgress"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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 particular 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;h2&gt;
  
  
  Confirming the result
&lt;/h2&gt;

&lt;p&gt;The point of verification is that "the transfer finished" and "the unit now runs the new code" 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/&amp;lt;DeviceID&amp;gt;/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 documented as read-only — they cannot be written, only read. There is also a request command for soliciting it on demand: publish &lt;code&gt;{"version":"1.0","method":"get_version","params":{}}&lt;/code&gt; to &lt;code&gt;/RadarHP/&amp;lt;DeviceID&amp;gt;/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;h2&gt;
  
  
  Running an update
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;paho.mqtt.client&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mqtt&lt;/span&gt;

&lt;span class="n"&gt;BROKER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;192.168.1.2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1883&lt;/span&gt;
&lt;span class="n"&gt;DEV&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;DeviceID&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;                        &lt;span class="c1"&gt;# the MAC, printed on the label
&lt;/span&gt;&lt;span class="n"&gt;SET_TOPIC&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/Radar60SP/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DEV&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/sys/property/set&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;POST_TOPIC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/Radar60SP/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DEV&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/sys/property/post&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;IMAGE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IMAGE_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://192.168.1.50:8090/app.rbl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8090&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;TARGET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;                              &lt;span class="c1"&gt;# "0" Wi-Fi module, "1" radar module
&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SET_TOPIC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;method&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;params&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
                   &lt;span class="n"&gt;qos&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userdata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;params&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;downloadProgress&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transfer:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;downloadProgress&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;opt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;otaInform&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;res&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fail&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;update failed:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;msg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;firmwareVersion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reported version after restart:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;firmwareVersion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;


&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mqtt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;username_pw_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;backend&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;on_message&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BROKER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keepalive&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;POST_TOPIC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qos&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loop_start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ota_set&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;module&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TARGET&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;          &lt;span class="c1"&gt;# select the target first
&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ota_set&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;otaInform&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;firmwareUrl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IMAGE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IMAGE_PORT&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loop_forever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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. Your monitoring will interpret that as a failure unless you suppress it — see &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/27/online-heartbeat-radar-health.html" rel="noopener noreferrer"&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 a different behaviour at 03:00 is discovered by nobody until morning.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A minimal image host
&lt;/h2&gt;

&lt;p&gt;You need nothing more than a static file server on the management network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 8090 &lt;span class="nt"&gt;--directory&lt;/span&gt; ./firmware
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which makes &lt;code&gt;http://&amp;lt;host&amp;gt;: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;h2&gt;
  
  
  What the documents do not say
&lt;/h2&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 a procedural rule rather than a technical one: update when someone is present to observe, one unit first, with the previous image still served.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&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;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Not alarming yourself during the restart: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/27/online-heartbeat-radar-health.html" rel="noopener noreferrer"&gt;Online Says It's Connected, HeartBeat Says It Works&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Reading the stream these commands travel on: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/25/reading-radar-data-your-own-app.html" rel="noopener noreferrer"&gt;Reading Radar Data in Your Own Application&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Keeping device identities straight at scale: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/24/running-several-radars-one-home.html" rel="noopener noreferrer"&gt;Running Several Radars in One Home&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The broker underneath it: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/23/apache-artemis-mqtt-broker-radar.html" rel="noopener noreferrer"&gt;Apache Artemis setup&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Updating firmware is a maintenance operation, not a feature. Verify every unit before you rely on it, and keep the previous image served until you have.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.tindie.com/stores/fuxinsuo/" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Check out our mmWave radar modules for sleep and presence sensing&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>ota</category>
      <category>mqtt</category>
      <category>radar</category>
      <category>firmware</category>
    </item>
    <item>
      <title>Online Says It's Connected, HeartBeat Says It Works: Reading Two Status Signals Instead of Guessing From Silence</title>
      <dc:creator>fuxinsuo</dc:creator>
      <pubDate>Tue, 27 Oct 2026 00:04:27 +0000</pubDate>
      <link>https://dev.to/249263431/online-says-its-connected-heartbeat-says-it-works-reading-two-status-signals-instead-of-guessing-gpf</link>
      <guid>https://dev.to/249263431/online-says-its-connected-heartbeat-says-it-works-reading-two-status-signals-instead-of-guessing-gpf</guid>
      <description>&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 "is the link up?"&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;heartBeat&lt;/code&gt; answers "is the radar itself working?"&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&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;"0"&lt;/code&gt; offline, &lt;code&gt;"1"&lt;/code&gt; online&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;"0"&lt;/code&gt; abnormal, &lt;code&gt;"1"&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;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"online"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"heartBeat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why this distinction is not academic
&lt;/h2&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: "0"&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 "connected" 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;h2&gt;
  
  
  The trap: silence is not evidence
&lt;/h2&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 "not yet available" 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 — "if I have not heard from the device in 60 seconds, it must be gone" — has no foundation here. Absence of messages is the normal state of a working installation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can do instead
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&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;/div&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;h2&gt;
  
  
  Two rules you must not break
&lt;/h2&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 "suppress this attribute" and &lt;code&gt;0&lt;/code&gt; means "allow it". 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 "this attribute supports querying" 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 "is reporting suppressed for this attribute", not "what is its current value". 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;h2&gt;
  
  
  A minimal health monitor
&lt;/h2&gt;

&lt;p&gt;Subscribe, merge the sparse payload, and raise the two classes separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;paho.mqtt.client&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mqtt&lt;/span&gt;

&lt;span class="n"&gt;TOPIC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/Radar60SP/&amp;lt;MAC&amp;gt;/#&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;last_any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userdata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;params&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="c1"&gt;# Only the fields that changed are present.
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;online&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heartBeat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;initFinish&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;last_any&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Two different failures, two different severities.
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;online&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;raise_alarm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;link&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;infra&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heartBeat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;raise_alarm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sensor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;h2&gt;
  
  
  Measure these at commissioning
&lt;/h2&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: "1"&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: "1"&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 "still online" 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 href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/24/distance-position-debug-installation.html" rel="noopener noreferrer"&gt;Using Distance and Position Output to Debug a Radar Installation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Maintenance looks like a failure
&lt;/h2&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;h2&gt;
  
  
  Two controls worth knowing about
&lt;/h2&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;h2&gt;
  
  
  Checklist
&lt;/h2&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;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Getting the stream in the first place: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/25/reading-radar-data-your-own-app.html" rel="noopener noreferrer"&gt;Reading Radar Data in Your Own Application&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The broker underneath it: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/23/apache-artemis-mqtt-broker-radar.html" rel="noopener noreferrer"&gt;Apache Artemis setup&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Keeping several devices straight: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/24/running-several-radars-one-home.html" rel="noopener noreferrer"&gt;Running Several Radars in One Home&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Keeping the history: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/26/storing-radar-data-retention-trends.html" rel="noopener noreferrer"&gt;Storing Radar Data&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&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;

&lt;p&gt;&lt;a href="https://www.tindie.com/stores/fuxinsuo/" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Check out our mmWave radar modules for sleep and presence sensing&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>iot</category>
      <category>mqtt</category>
      <category>radar</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Keeping Months of Radar Data: Retention, Downsampling, and Why Trends Beat Instant Values</title>
      <dc:creator>fuxinsuo</dc:creator>
      <pubDate>Mon, 26 Oct 2026 07:44:53 +0000</pubDate>
      <link>https://dev.to/249263431/keeping-months-of-radar-data-retention-downsampling-and-why-trends-beat-instant-values-4hod</link>
      <guid>https://dev.to/249263431/keeping-months-of-radar-data-retention-downsampling-and-why-trends-beat-instant-values-4hod</guid>
      <description>&lt;p&gt;The &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/25/reading-radar-data-your-own-app.html" rel="noopener noreferrer"&gt;previous article&lt;/a&gt; ends with a dict full of the latest values. That is a working state, not a record: restart the process and everything before the restart is gone. This article is the step after — deciding what to keep, for how long, at what resolution, and which numbers actually answer a question anyone cares about.&lt;/p&gt;

&lt;p&gt;It matters more than it sounds, because a single instant reading is nearly useless. "Occupied, still, right now" is a fact you could get by looking into the room. The facts that are worth keeping are the ones nobody can observe: how long she slept, how often she got up, whether tonight's activity looks like last month's. Those only exist if you stored the stream.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start from what actually arrives
&lt;/h2&gt;

&lt;p&gt;Recapping &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/25/reading-radar-data-your-own-app.html" rel="noopener noreferrer"&gt;the JSON contract&lt;/a&gt;: finished units publish &lt;code&gt;{"version":"1.0","method":"post","params":{...}}&lt;/code&gt; where values are strings and &lt;strong&gt;only changed fields are present&lt;/strong&gt;, while a UART module behind your own gateway publishes whatever flat shape your firmware chose.&lt;/p&gt;

&lt;p&gt;Three properties of that stream decide your storage design:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It is sparse.&lt;/strong&gt; Most messages carry one or two fields. A row-per-field and a row-per-message schema are very different things.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is heterogeneous.&lt;/strong&gt; Presence changes on a human timescale; breathing updates every few seconds; a fall event happens once in a blue moon. One retention rule cannot serve all three.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is a stream of states, not a stream of samples.&lt;/strong&gt; Between two messages nothing changed. Storing the interval matters more than storing the moment.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Four questions before you write a line of code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How long do you need each kind of data?&lt;/strong&gt; Presence events might be worth two years; breathing rate might be worth thirty days. A fall alarm is an incident record — keep it as long as you keep any incident record.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At what resolution?&lt;/strong&gt; Nobody reads three-second breathing samples from four months ago. They read the nightly average.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who is allowed to see it?&lt;/strong&gt; This is intimate data, and a great deal of detail is not needed to answer any of the questions above. The safe default is: aggregate early, keep raw shortest, and store it where you own it — see the &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/23/apache-artemis-mqtt-broker-radar.html" rel="noopener noreferrer"&gt;self-hosted broker&lt;/a&gt; rather than a cloud queue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which fields are trustworthy?&lt;/strong&gt; Distance and position are documented as &lt;strong&gt;non-critical functions with limited accuracy&lt;/strong&gt;, on the sleep and presence radars alike — see &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/24/distance-position-debug-installation.html" rel="noopener noreferrer"&gt;Using Distance and Position Output to Debug a Radar Installation&lt;/a&gt;. If you store them, store them as commissioning notes, not as a data product.&lt;/p&gt;

&lt;h2&gt;
  
  
  A retention layout that matches cadence
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Data&lt;/th&gt;
&lt;th&gt;Raw window&lt;/th&gt;
&lt;th&gt;Then keep&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Incidents&lt;/td&gt;
&lt;td&gt;Fall events, alarms, alarm acknowledgements&lt;/td&gt;
&lt;td&gt;Forever&lt;/td&gt;
&lt;td&gt;Forever — this is an audit trail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sessions&lt;/td&gt;
&lt;td&gt;Sleep session start/end, score, stage changes&lt;/td&gt;
&lt;td&gt;90 days&lt;/td&gt;
&lt;td&gt;Daily summary, indefinitely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Presence&lt;/td&gt;
&lt;td&gt;In-bed / occupancy state changes&lt;/td&gt;
&lt;td&gt;90 days&lt;/td&gt;
&lt;td&gt;Per-day occupancy seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vitals&lt;/td&gt;
&lt;td&gt;Breathing rate, heart rate&lt;/td&gt;
&lt;td&gt;30 days&lt;/td&gt;
&lt;td&gt;Per-minute average, then per-night average&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diagnostics&lt;/td&gt;
&lt;td&gt;Distance, position, motion level&lt;/td&gt;
&lt;td&gt;7 days&lt;/td&gt;
&lt;td&gt;Nothing, except the install-day baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those numbers are a starting pattern, not a rule; the point is that one retention setting for everything guarantees either a sprawling database or a useless one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cast before you store
&lt;/h2&gt;

&lt;p&gt;The finished units send everything as strings — &lt;code&gt;{"someoneExists": "1"}&lt;/code&gt;. Insert that into a numeric column and you will discover it later, at the worst possible moment. Normalise on the way in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;INT_FIELDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;someoneExists&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;getIntoBed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heartRateValue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;breathValue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sleepStatus&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;INT_FIELDS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;          &lt;span class="c1"&gt;# the sensor sometimes reports an unusable value
&lt;/span&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;continue&lt;/code&gt;: a breathing sensor reports "no value" states when it cannot resolve a rate. Storing those as 0 invents a reading that did not happen. Skipping them, and letting your last-known value stand, keeps the database honest — the same rule as in memory, applied one layer down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Merge, then write
&lt;/h2&gt;

&lt;p&gt;Because payloads are sparse, write only what changed and treat it as valid until something else arrives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userdata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;params&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;insert_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# sparse: only the fields in this message
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reading back is then an "as of" query — the most recent value of each field at or before a timestamp — which most column stores handle with &lt;code&gt;last_value(...) OVER (PARTITION BY field ORDER BY ts)&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Downsample to what someone will actually read
&lt;/h2&gt;

&lt;p&gt;A per-night row is worth more than the 28,800 three-second samples it came from. Keep a rollup table alongside the raw one and build it once an hour:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;night_of&lt;/span&gt;      &lt;span class="nb"&gt;DATE&lt;/span&gt;       &lt;span class="c1"&gt;-- the night, not the timestamp: nights do not start at 00:00&lt;/span&gt;
&lt;span class="n"&gt;device_id&lt;/span&gt;     &lt;span class="nb"&gt;TEXT&lt;/span&gt;
&lt;span class="n"&gt;in_bed_at&lt;/span&gt;     &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;  &lt;span class="c1"&gt;-- first sustained in-bed reading&lt;/span&gt;
&lt;span class="n"&gt;out_of_bed_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;
&lt;span class="n"&gt;bed_exits&lt;/span&gt;     &lt;span class="nb"&gt;INT&lt;/span&gt;        &lt;span class="c1"&gt;-- count of in-bed -&amp;gt; not-in-bed transitions&lt;/span&gt;
&lt;span class="n"&gt;away_seconds&lt;/span&gt;  &lt;span class="nb"&gt;INT&lt;/span&gt;        &lt;span class="c1"&gt;-- total time out of bed&lt;/span&gt;
&lt;span class="n"&gt;avg_breath&lt;/span&gt;    &lt;span class="nb"&gt;NUMERIC&lt;/span&gt;    &lt;span class="c1"&gt;-- median, not mean: it resists outliers&lt;/span&gt;
&lt;span class="n"&gt;min_breath&lt;/span&gt;    &lt;span class="nb"&gt;NUMERIC&lt;/span&gt;
&lt;span class="n"&gt;max_breath&lt;/span&gt;    &lt;span class="nb"&gt;NUMERIC&lt;/span&gt;
&lt;span class="n"&gt;still_ratio&lt;/span&gt;   &lt;span class="nb"&gt;NUMERIC&lt;/span&gt;    &lt;span class="c1"&gt;-- fraction of samples with body movement at 1 %&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every one of those columns came from values the radars already publish; none of them required a new sensor or a new field. That is the whole argument for keeping history: the interesting features are computed from ordinary state transitions.&lt;/p&gt;

&lt;p&gt;Use &lt;strong&gt;medians and counts, not means&lt;/strong&gt;, for vitals. One garbage reading in ninety thousand will drag a mean and leave a median untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much space, roughly
&lt;/h2&gt;

&lt;p&gt;Worth doing once so nobody is surprised. A sleep radar publishing breathing every 3 s produces about 20 messages a minute, roughly 1,200 an hour, roughly 29,000 a day. At a sparse payload of well under 200 bytes before indexing, that is single-digit megabytes per device per day in a naive row-oriented store — and an order of magnitude less once you drop the raw stream on schedule and keep only the nightly row.&lt;/p&gt;

&lt;p&gt;Do the arithmetic with your own payload sizes rather than trusting that figure; it is arithmetic on documented cadences, not a benchmark.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trends worth computing
&lt;/h2&gt;

&lt;p&gt;Genuinely useful, and derivable from nothing but occupancy and breathing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nightly time in bed and number of exits.&lt;/strong&gt; The classic sleep-quality proxy, and the change in it matters more than its value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Baseline activity.&lt;/strong&gt; Once you have weeks, "unusually still today" becomes a computable statement rather than a feeling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Room usage by hour.&lt;/strong&gt; An occupancy heat map tells you whether a room is used the way the household thinks it is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow drift over months.&lt;/strong&gt; Some apparent change is the sensor, not the person — see &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/18/radar-position-drift.html" rel="noopener noreferrer"&gt;why radar position drifts&lt;/a&gt;. Keep an install-day baseline precisely so you can tell the two apart.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an elderly-care setting these roll-up rows are also what a carer can digest: not a live graph, a weekly summary. The escalation logic that turns a summary into a call is described in &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/23/aging-in-place-care-system-three-radars.html" rel="noopener noreferrer"&gt;Building an Aging-in-Place Care System with Three 60GHz Radars&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Empty-restart test: does anything survive an application restart?&lt;/li&gt;
&lt;li&gt;[ ] Every numeric field cast explicitly; unusable values dropped rather than stored as 0&lt;/li&gt;
&lt;li&gt;[ ] Sparse writes, with an "as of" read path that fills from the last known value&lt;/li&gt;
&lt;li&gt;[ ] One retention window per tier, not one for all data&lt;/li&gt;
&lt;li&gt;[ ] Rollup job scheduled, and verified to produce the same answer as yesterday's data would&lt;/li&gt;
&lt;li&gt;[ ] Distance and position marked as diagnostic, pruned aggressively&lt;/li&gt;
&lt;li&gt;[ ] Data stored where you control it, and not sent anywhere it does not need to go&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Getting the data in the first place: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/25/reading-radar-data-your-own-app.html" rel="noopener noreferrer"&gt;Reading Radar Data in Your Own Application&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;What each field means: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/product/2026/10/17/presence-radar-guide.html" rel="noopener noreferrer"&gt;presence&lt;/a&gt;, &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/product/2026/10/17/sleep-radar-guide.html" rel="noopener noreferrer"&gt;sleep&lt;/a&gt; and &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/product/2026/10/17/fall-radar-guide.html" rel="noopener noreferrer"&gt;fall&lt;/a&gt; guides&lt;/li&gt;
&lt;li&gt;Why some readings take seconds: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/radar-latency-explained.html" rel="noopener noreferrer"&gt;Radar Latency Explained&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The broker underneath it: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/23/apache-artemis-mqtt-broker-radar.html" rel="noopener noreferrer"&gt;Apache Artemis setup&lt;/a&gt; and &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/24/running-several-radars-one-home.html" rel="noopener noreferrer"&gt;Running Several Radars in One Home&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Storing occupancy and breathing history is not the same as measuring anything clinical. These are trends from a contactless sensor, useful for spotting change, and not a diagnosis.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.tindie.com/stores/fuxinsuo/" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Check out our mmWave radar modules for sleep and presence sensing&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>iot</category>
      <category>database</category>
      <category>radar</category>
      <category>retention</category>
    </item>
    <item>
      <title>Running Several Radars in One Home: Two Kinds of ID, One Broker, and How to Group Them</title>
      <dc:creator>fuxinsuo</dc:creator>
      <pubDate>Sun, 25 Oct 2026 04:37:17 +0000</pubDate>
      <link>https://dev.to/249263431/running-several-radars-in-one-home-two-kinds-of-id-one-broker-and-how-to-group-them-1ao6</link>
      <guid>https://dev.to/249263431/running-several-radars-in-one-home-two-kinds-of-id-one-broker-and-how-to-group-them-1ao6</guid>
      <description>&lt;p&gt;Setting up one radar is a single sequence in the &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/17/radar-setup-guide.html" rel="noopener noreferrer"&gt;setup guide&lt;/a&gt;. Setting up five is not the same sequence five times — it is a small design exercise. Almost every multi-radar failure we see traces back to one of four things: a duplicated ClientID, two devices sharing one broker account, topics that cannot be told apart, or two radars looking at the same square metre of floor.&lt;/p&gt;

&lt;p&gt;This article is the deployment view: which identifier does what, how to keep them from colliding, where room identity actually lives, and the order to commission in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two kinds of ID, and they are not the same thing
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;DeviceID&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;ClientID&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it is&lt;/td&gt;
&lt;td&gt;The device's &lt;strong&gt;MAC address&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;The MQTT client identifier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where you see it&lt;/td&gt;
&lt;td&gt;Printed on the device label; shown on the configuration page at &lt;code&gt;192.168.0.1&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Entered on the same configuration page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can you change it&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes — it defaults to the MAC&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where it appears&lt;/td&gt;
&lt;td&gt;In the topic path: &lt;code&gt;/Radar60SP/{DeviceID}/sys/property/post&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Nowhere visible; it identifies the connection to the broker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uniqueness rule&lt;/td&gt;
&lt;td&gt;Unique by construction&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Must be unique.&lt;/strong&gt; Two devices sharing one ClientID keep kicking each other off the broker&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row is the whole reason this article exists. A shared ClientID does not fail loudly — it produces two devices that connect, disconnect, and reconnect in a loop, with data arriving in bursts and gaps that look like a radio problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  One broker, many clients
&lt;/h2&gt;

&lt;p&gt;Point every device at the same broker, then give each one its own credentials. The &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/23/apache-artemis-mqtt-broker-radar.html" rel="noopener noreferrer"&gt;Artemis setup guide&lt;/a&gt; shows the broker side: one account per client, anonymous access off. From the radar side it is the same four values as always — server address, port 1883, username, password — entered once per device on its own configuration page.&lt;/p&gt;

&lt;p&gt;Before you power anything on, build the table. Fill it in from the labels:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Device&lt;/th&gt;
&lt;th&gt;DeviceID (MAC, from label)&lt;/th&gt;
&lt;th&gt;ClientID (default = MAC)&lt;/th&gt;
&lt;th&gt;Account&lt;/th&gt;
&lt;th&gt;Room&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fall radar&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AA:BB:...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;default&lt;/td&gt;
&lt;td&gt;&lt;code&gt;radar&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bathroom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sleep radar&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CC:DD:...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;default&lt;/td&gt;
&lt;td&gt;&lt;code&gt;radar&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bedroom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Presence radar&lt;/td&gt;
&lt;td&gt;&lt;code&gt;EE:FF:...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;default&lt;/td&gt;
&lt;td&gt;&lt;code&gt;radar&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Living room&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two habits that save an evening:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Leave the ClientID at its default.&lt;/strong&gt; The default is already unique, and a custom value is one more thing to get wrong. If you do set one, make it encode the room (&lt;code&gt;bathroom-fall-01&lt;/code&gt;) — but then the &lt;em&gt;label&lt;/em&gt; is the only place the MAC is written, so record both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One account per device, or one shared account with a deliberate decision.&lt;/strong&gt; A shared account is fine for a home; separate accounts matter when you want to revoke one device, or to restrict which topics each one can publish to.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Topics are fixed, so put room identity in Home Assistant
&lt;/h2&gt;

&lt;p&gt;Because the DeviceID is a MAC address, the topic path cannot be renamed per room:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/Radar60FL/&amp;lt;MAC&amp;gt;/sys/property/post      ← fall radar, bathroom
/Radar60SP/&amp;lt;MAC&amp;gt;/sys/property/post      ← sleep radar, bedroom
/RadarHP/&amp;lt;MAC&amp;gt;/sys/property/post        ← presence radar, living room
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a feature, not a limitation: the MAC is unique forever, so nothing you do later can break the path. The room name belongs in the layer above. In Home Assistant:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Assign each device to the matching &lt;strong&gt;area&lt;/strong&gt; (Settings → Devices &amp;amp; Services → Devices → area).&lt;/li&gt;
&lt;li&gt;Rename entities to &lt;code&gt;Bathroom Fall Status&lt;/code&gt;, &lt;code&gt;Bedroom Breathing Rate&lt;/code&gt; — not &lt;code&gt;Sensor 1&lt;/code&gt;, &lt;code&gt;Sensor 2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use the device ID to correlate: when a dashboard shows a fall in the bathroom, the entity name and area already say which bathroom.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result is that a topic full of MAC addresses is still readable to a human, because nobody ever reads the topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gateway is the exception
&lt;/h2&gt;

&lt;p&gt;Everything above applies to finished units, which are configured from their own web page. The &lt;strong&gt;ESP32 gateway&lt;/strong&gt; for UART-only modules is different: it is your firmware. Both the ClientID and the topic — &lt;code&gt;radar/R60ABD1/state&lt;/code&gt; — are whatever you set in code, so you are free to name them. See the &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/22/r60abd1-esp32-s3-sip-s-mqtt.html" rel="noopener noreferrer"&gt;ESP32-S3 gateway guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That freedom deserves a rule, not a free-for-all:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep a naming scheme in one place (a comment block, a README, or a spreadsheet) so the next person can decode it.&lt;/li&gt;
&lt;li&gt;If you run several gateways, give each a unique ClientID in firmware and a topic that encodes the room, e.g. &lt;code&gt;radar/&amp;lt;room&amp;gt;/state&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Write the scheme on the enclosure label next to the MAC. Six months from now, that label is the only documentation anyone will read.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Stop the radars from arguing with each other
&lt;/h2&gt;

&lt;p&gt;Two radars in adjacent rooms can interfere in ways that look like faulty hardware. The mechanisms are covered in &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/19/ghost-targets-multipath-interference.html" rel="noopener noreferrer"&gt;Ghost Targets and Multipath Interference&lt;/a&gt; and &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/18/radar-position-drift.html" rel="noopener noreferrer"&gt;Radar Position Drift&lt;/a&gt;; the practical rules are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Check coverage before mounting.&lt;/strong&gt; A finished presence radar covers roughly a 2.7 m radius; the 24 GHz presence &lt;em&gt;module&lt;/em&gt; reaches up to about 12 m for a moving person. Two modules in a small apartment will absolutely see each other's rooms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shrink the trigger range first.&lt;/strong&gt; Choosing the smallest scene mode that still covers the room is the single most effective change — see the &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/product/2026/10/17/presence-radar-guide.html" rel="noopener noreferrer"&gt;presence radar guide&lt;/a&gt; for the mode table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep metal out of the beam path.&lt;/strong&gt; Metal doors, mirrors, and appliance housings in the line of sight create reflections that appear as targets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aim away from fans, air-conditioner motors, swaying curtains, and pets.&lt;/strong&gt; These are the usual false-alarm sources in every room.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Commission in this order
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Broker first.&lt;/strong&gt; It has to be running before any radar is configured — that is the prerequisite the setup guide opens with. Confirm with a test client that it accepts a connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One radar at a time.&lt;/strong&gt; Configure it, then verify it on its own: subscribe to &lt;code&gt;/prefix/&amp;lt;MAC&amp;gt;/#&lt;/code&gt; with any MQTT client and watch data arrive. Do not add the next device until the first is clean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fill in the table&lt;/strong&gt; (DeviceID, ClientID, account, room) as each device comes online — not afterwards from memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then Home Assistant.&lt;/strong&gt; Add the MQTT integration, confirm entities appear, assign areas, rename.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only then build automations.&lt;/strong&gt; Cross-room logic written before the entities are stable is logic you will rewrite.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Acceptance checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Every device's ClientID is unique (defaults are)&lt;/li&gt;
&lt;li&gt;[ ] Every device has credentials that work, and anonymous access is off&lt;/li&gt;
&lt;li&gt;[ ] Each device's topic is subscribed and reporting, verified against the MAC on its label&lt;/li&gt;
&lt;li&gt;[ ] Two devices left alone for an hour produce no disconnection loop on the broker&lt;/li&gt;
&lt;li&gt;[ ] Each room's entities are named and assigned to the right area&lt;/li&gt;
&lt;li&gt;[ ] Walk the home once and confirm occupancy, presence and in-bed states change as expected&lt;/li&gt;
&lt;li&gt;[ ] Note the mounting position and sensitivity of each unit in the same table&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Single-unit setup: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/17/radar-setup-guide.html" rel="noopener noreferrer"&gt;Wi-Fi Provisioning &amp;amp; MQTT Setup Guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The broker: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/23/apache-artemis-mqtt-broker-radar.html" rel="noopener noreferrer"&gt;Apache Artemis setup&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A worked three-radar example: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/23/aging-in-place-care-system-three-radars.html" rel="noopener noreferrer"&gt;Aging-in-Place Care System&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hardware wiring without a parser: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/gpio-vs-uart-fall-alarm-without-parser.html" rel="noopener noreferrer"&gt;GPIO vs UART&lt;/a&gt; and &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/presence-s1-relay-occupancy.html" rel="noopener noreferrer"&gt;presence relay wiring&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Module specs: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/60ghz-24ghz-radar-modules-oem-integration.html" rel="noopener noreferrer"&gt;60 GHz / 24 GHz module spec sheet&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finished radars and modules are on &lt;a href="https://www.tindie.com/stores/fuxinsuo/" rel="noopener noreferrer"&gt;Tindie&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>iot</category>
      <category>mqtt</category>
      <category>radar</category>
      <category>smarthome</category>
    </item>
    <item>
      <title>Powering and Enclosing a 60GHz Radar Module: Ripple, Grounding, and Antenna Keep-Out</title>
      <dc:creator>fuxinsuo</dc:creator>
      <pubDate>Fri, 23 Oct 2026 23:39:31 +0000</pubDate>
      <link>https://dev.to/249263431/powering-and-enclosing-a-60ghz-radar-module-ripple-grounding-and-antenna-keep-out-5a6h</link>
      <guid>https://dev.to/249263431/powering-and-enclosing-a-60ghz-radar-module-ripple-grounding-and-antenna-keep-out-5a6h</guid>
      <description>&lt;p&gt;The &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/60ghz-24ghz-radar-modules-oem-integration.html" rel="noopener noreferrer"&gt;module spec sheet&lt;/a&gt; tells you the electrical and mechanical numbers. This article is about what to do with them, because in our experience the first time a radar product fails a test lab, the cause is almost never the sensor — it is the supply, the ground, or the enclosure sitting too close to the antenna.&lt;/p&gt;

&lt;p&gt;Everything below applies to the three bare modules we sell. Where a value differs per module it is stated; where the datasheet is the authority, it says so.&lt;/p&gt;

&lt;h2&gt;
  
  
  Power: the window, the current, and the ripple
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Module&lt;/th&gt;
&lt;th&gt;Supply&lt;/th&gt;
&lt;th&gt;Current&lt;/th&gt;
&lt;th&gt;Ripple&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;R60AFD1-V (fall, 60 GHz)&lt;/td&gt;
&lt;td&gt;4.5–5.5 V, typ. 5.0 V&lt;/td&gt;
&lt;td&gt;≤ 100 mA avg, ≤ 300 mA peak&lt;/td&gt;
&lt;td&gt;≤ 100 mV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R60ABD1 (sleep &amp;amp; breathing, 60 GHz)&lt;/td&gt;
&lt;td&gt;4.5–5.5 V, typ. 5.0 V&lt;/td&gt;
&lt;td&gt;≥ 200 mA&lt;/td&gt;
&lt;td&gt;≤ 100 mV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R24AVD1 (presence, 24 GHz)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;see datasheet&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;see datasheet&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;see datasheet&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three things to take from that table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The 3.3 V figure on the pinout is the I/O level, not the supply.&lt;/strong&gt; UART, S1/S2 and the GP pins are 3.3 V TTL logic. Powering the module from 3.3 V because "the pins say 3.3 V" is a classic mistake.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ripple ≤ 100 mV is a real constraint.&lt;/strong&gt; A bare switching regulator at the edge of its tolerance will not meet it. Use an LDO after the switcher, or a linear supply where heat allows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget for peaks, not averages.&lt;/strong&gt; The fall module draws up to 300 mA in peaks while averaging 100 mA; the sleep module needs 200 mA available. A supply that sags on the peak is a supply that resets the module at the worst moment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Practical layout order: bulk reservoir → LDO → module VCC, with the decoupling capacitor as close to the module pins as the layout allows. Keep the supply trace short and direct; do not route it past a relay coil or a motor driver.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two power domains, and they stay separate
&lt;/h2&gt;

&lt;p&gt;If your product switches a load — a light, an HVAC contactor, a buzzer — the module and the load live in &lt;strong&gt;separate power domains&lt;/strong&gt;, as shown in the &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/presence-s1-relay-occupancy.html" rel="noopener noreferrer"&gt;presence relay article&lt;/a&gt; and the &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/fall-gp1-plc-dry-contact.html" rel="noopener noreferrer"&gt;PLC dry-contact article&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   Module domain                 Load domain
   5 V, clean  ──► radar          5/12 V ──► relay ──► load
        │                              │
        └──────── single-point ────────┘
                  ground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One ground point where the two domains meet.&lt;/strong&gt; Two grounds joined in two places make a loop, and the loop current rides on your ground reference exactly where the sensor is most sensitive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The GPIO pins are weak.&lt;/strong&gt; S1, S2 and GP1/GP2 are 3.3 V logic at milliamp level. They drive a transistor gate, never a relay coil or a lamp directly — with a flyback diode across any coil you switch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opto-isolation is available when the load side is nasty.&lt;/strong&gt; A high-voltage or long-cable load is worth an optocoupler; the GPIO then only has to drive an LED.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The enclosure is part of the antenna
&lt;/h2&gt;

&lt;p&gt;The 60 GHz modules radiate through their own PCB. The housing around them is part of the RF path, which is why the mounting notes in the guides read like RF instructions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Non-metallic over the antenna face.&lt;/strong&gt; Plastic, not aluminium. A metal face in front of the antenna changes the beam and can detune it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flat, not curved.&lt;/strong&gt; A curved surface over the antenna is not supported; keep the face flat and parallel to the board.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3–5 mm from the antenna face to the inner shell surface.&lt;/strong&gt; Closer loads the antenna; further away wastes the space you needed for the lens or window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the radar component at least 1 mm above the surrounding PCB parts.&lt;/strong&gt; The antenna must not be shadowed by a taller component or a ground pour cut that was not planned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing metallic directly behind or beside the antenna&lt;/strong&gt; — mounting screws, brackets, and heatsinks all count.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your product has a lens or radome over the antenna, treat its material and thickness as a design variable and confirm the effect with your own measurements rather than assuming.&lt;/p&gt;

&lt;h2&gt;
  
  
  EMC basics that matter for this sensor
&lt;/h2&gt;

&lt;p&gt;The radar is a sensitive receiver sitting next to whatever else your product contains. In order of impact:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Distance from switching power.&lt;/strong&gt; Keep the switcher, its inductor, and its loop area away from the antenna end of the board. This is the single biggest source of broadband noise in a compact product.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Motors and solenoids.&lt;/strong&gt; A buzzer, a lock, or a fan in the same enclosure couples straight into the measurement. If they must share a board, keep them at the far end and give them their own return path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cable routing.&lt;/strong&gt; Keep the UART pair and any GPIO wires away from the supply wires and from anything switching current. Twist the UART pair if the run is long.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Antenna area is keep-out.&lt;/strong&gt; No traces, no vias, no pours under or beside the antenna unless your design rules explicitly allow it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ground discipline.&lt;/strong&gt; One solid ground reference for the analog/sensor domain; noisy return currents kept off it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why your lab will ask about all of this
&lt;/h2&gt;

&lt;p&gt;These are the same items that decide whether the product passes. The enclosure and the supply determine radiated emissions and band-edge behaviour, and the lab will want your technical file to reflect the configuration you actually ship. The full picture is in &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/23/certification-support-ce-fcc-own-brand.html" rel="noopener noreferrer"&gt;Certification Support: Get CE and FCC Made Out to Your Own Name&lt;/a&gt; — including what we supply and what stays yours.&lt;/p&gt;

&lt;p&gt;Two items from this article are worth telling your lab up front, because they are the ones most often discovered late: the &lt;strong&gt;enclosure must be non-metallic and flat over the antenna face&lt;/strong&gt;, and the modules specify &lt;strong&gt;power-supply ripple limits&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pre-layout checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Supply inside the module's voltage window, with ripple ≤ 100 mV at the module pins&lt;/li&gt;
&lt;li&gt;[ ] Peak current budgeted, with a reservoir for the fall module's 300 mA peaks&lt;/li&gt;
&lt;li&gt;[ ] LDO after any switcher, decoupling close to the pins&lt;/li&gt;
&lt;li&gt;[ ] Module domain and load domain separately supplied, single-point ground&lt;/li&gt;
&lt;li&gt;[ ] GPIO driving a transistor, never a coil directly; flyback diode on every coil&lt;/li&gt;
&lt;li&gt;[ ] Antenna face over non-metallic, flat material, 3–5 mm from the inner shell&lt;/li&gt;
&lt;li&gt;[ ] Radar component ≥ 1 mm above surrounding PCB parts; no metal behind the antenna&lt;/li&gt;
&lt;li&gt;[ ] Switcher, inductor and motors at the opposite end of the board&lt;/li&gt;
&lt;li&gt;[ ] Mounting height and tilt per the product guide for the variant you chose&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;All three modules' numbers: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/60ghz-24ghz-radar-modules-oem-integration.html" rel="noopener noreferrer"&gt;60 GHz / 24 GHz module spec sheet&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Wiring a load without writing a parser: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/presence-s1-relay-occupancy.html" rel="noopener noreferrer"&gt;presence relay wiring&lt;/a&gt;, &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/fall-alarm-relay-from-gp1.html" rel="noopener noreferrer"&gt;fall alarm relay&lt;/a&gt;, &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/21/fall-gp1-plc-dry-contact.html" rel="noopener noreferrer"&gt;PLC dry contact&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Certification path: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/23/certification-support-ce-fcc-own-brand.html" rel="noopener noreferrer"&gt;CE / FCC support&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Getting first frames: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/22/radar-module-bring-up-checklist.html" rel="noopener noreferrer"&gt;Bring-Up Checklist&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Modules and evaluation units are on &lt;a href="https://www.tindie.com/stores/fuxinsuo/" rel="noopener noreferrer"&gt;Tindie&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>hardware</category>
      <category>iot</category>
      <category>embedded</category>
      <category>electronics</category>
    </item>
    <item>
      <title>Using Distance and Position Output to Debug a Radar Installation</title>
      <dc:creator>fuxinsuo</dc:creator>
      <pubDate>Fri, 23 Oct 2026 23:37:32 +0000</pubDate>
      <link>https://dev.to/249263431/using-distance-and-position-output-to-debug-a-radar-installation-20j4</link>
      <guid>https://dev.to/249263431/using-distance-and-position-output-to-debug-a-radar-installation-20j4</guid>
      <description>&lt;p&gt;Our product guides flag the distance and position outputs the same way every time: they are &lt;strong&gt;non-critical functions with limited accuracy&lt;/strong&gt;. That sentence is often read as "ignore these fields". It means the opposite of what integrators need — these are the most useful numbers you have while &lt;em&gt;installing&lt;/em&gt; the sensor, precisely because they are raw and unsmoothed.&lt;/p&gt;

&lt;p&gt;This article is the method: what to subscribe to, what a healthy reading looks like, and how to decide between moving the sensor, changing a setting, or removing an interference source.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;These values are diagnostic only. Do not build a product feature on them — the documentation is explicit that their accuracy is limited, and a customer-visible feature needs the primary outputs (presence, motion, fall state, breathing) instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What you can subscribe to
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Range / cadence&lt;/th&gt;
&lt;th&gt;What it tells you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Human distance&lt;/td&gt;
&lt;td&gt;0–65535 cm, about every 2 s&lt;/td&gt;
&lt;td&gt;Roughly how far the detected target is&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human position&lt;/td&gt;
&lt;td&gt;x, y, z, about every 2 s&lt;/td&gt;
&lt;td&gt;Where in the beam the target sits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion status&lt;/td&gt;
&lt;td&gt;0 none / 1 still / 2 active&lt;/td&gt;
&lt;td&gt;Whether the target is moving at all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Body-movement level&lt;/td&gt;
&lt;td&gt;0–100 (5 s module, 1 s finished)&lt;/td&gt;
&lt;td&gt;How much movement, in fixed bands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Approach / leave&lt;/td&gt;
&lt;td&gt;1–5, about every 2 s (presence module)&lt;/td&gt;
&lt;td&gt;Direction of travel relative to the radar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Presence energy &amp;amp; threshold&lt;/td&gt;
&lt;td&gt;fall radar, switchable&lt;/td&gt;
&lt;td&gt;How strong the presence detection is firing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The body-movement bands are worth memorising, because they turn a number into a sentence:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0 %&lt;/td&gt;
&lt;td&gt;No person in range&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 %&lt;/td&gt;
&lt;td&gt;Still — breathing only, no limb movement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2–30 %&lt;/td&gt;
&lt;td&gt;Micro movement: small head or limb motion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;31–60 %&lt;/td&gt;
&lt;td&gt;Walking or fast limb movement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;61–100 %&lt;/td&gt;
&lt;td&gt;Running or large close-range movement&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A correctly aimed radar on a person lying still should sit at &lt;strong&gt;1 %&lt;/strong&gt;. That single number answers the most common installation question — "is it actually seeing the person?" — without any interpretation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The method
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Empty-room baseline
&lt;/h3&gt;

&lt;p&gt;Before anyone is in the room, subscribe and watch for a minute. A healthy sensor in an empty room reports &lt;strong&gt;no target&lt;/strong&gt;: distance absent, body movement at 0 %, motion status 0. If you see a distance, a position, or movement above 0 % with nobody present, you have interference — a fan, an air-conditioner motor, a curtain, or a reflective surface. Find it now, not after the customer calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Walk the room
&lt;/h3&gt;

&lt;p&gt;Walk slowly from the door to the far wall, then back. Watch three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The distance trace&lt;/strong&gt; should rise and fall smoothly and end near the far wall's real distance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Body movement&lt;/strong&gt; should read 31–60 % while walking and drop to 1 % when you stand still.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Motion status&lt;/strong&gt; should follow: 2 while walking, 1 while standing still.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the distance stops changing while you keep walking, you have left the beam — that is your coverage edge.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Map the coverage edge
&lt;/h3&gt;

&lt;p&gt;Repeat the walk along different paths and mark where the sensor stops reporting. Compare with the documented coverage for your variant: the finished presence radar covers roughly a 2.7 m radius; the 24 GHz presence module reaches about 12 m for a moving person, 6 m for someone sitting, 3 m for a sleeping person; the sleep radar's breathing detection degrades beyond about 1.5 m and disappears before presence detection does.&lt;/p&gt;

&lt;p&gt;If the edge is short of where people actually are, that is a mounting or scene-mode problem, not a fault.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Verify the aim
&lt;/h3&gt;

&lt;p&gt;For a bed installation, the beam must hit the chest, not the wall: the sleep radar is side-wall mounted at 1.8–2.2 m, or above the bed head tilted 30°–45°, with 45° recommended. Position x/y/z and the body-movement value together tell you whether you got it right — a target that only ever reports 1 % at the edge of the range is a target you are barely seeing.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Confirm the empty transition
&lt;/h3&gt;

&lt;p&gt;Leave the room and time it. Declaring a room empty takes about &lt;strong&gt;40 s&lt;/strong&gt; of confirmed absence, by design; declaring it occupied takes under 0.5 s. If your "empty" transition is much faster, something is being missed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Red flags and what they usually mean
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you see&lt;/th&gt;
&lt;th&gt;Likely cause&lt;/th&gt;
&lt;th&gt;First move&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Target reported in an empty room&lt;/td&gt;
&lt;td&gt;Fan, AC motor, curtain, pet, reflective metal&lt;/td&gt;
&lt;td&gt;Remove or re-aim away from the source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distance jumps around with nobody moving&lt;/td&gt;
&lt;td&gt;Multipath from a metal surface or a second radar&lt;/td&gt;
&lt;td&gt;Check line of sight; see &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/19/ghost-targets-multipath-interference.html" rel="noopener noreferrer"&gt;ghost targets&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distance pinned at a round number (0 or max)&lt;/td&gt;
&lt;td&gt;Out of range or field saturated&lt;/td&gt;
&lt;td&gt;Move the sensor closer / reduce range&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Body movement never drops below 30 %&lt;/td&gt;
&lt;td&gt;Beam too wide for the room, or interference&lt;/td&gt;
&lt;td&gt;Use a smaller scene mode; lower sensitivity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Still person reads 0 %&lt;/td&gt;
&lt;td&gt;Beam missing the chest, or sensitivity too low&lt;/td&gt;
&lt;td&gt;Re-aim; raise sensitivity one step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Readings change over weeks with nothing moved&lt;/td&gt;
&lt;td&gt;Position drift — new furniture, hanging objects&lt;/td&gt;
&lt;td&gt;See &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/18/radar-position-drift.html" rel="noopener noreferrer"&gt;radar position drift&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Decide in this order
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Can I remove the interference?&lt;/strong&gt; Cheapest fix. Fans and air-conditioner motors in the beam cause most false readings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can I move the sensor?&lt;/strong&gt; Coverage that misses the occupied area is usually a position problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can I shrink the range?&lt;/strong&gt; The smallest scene mode that still covers the room is the most effective single change — the mode table is in the &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/product/2026/10/17/presence-radar-guide.html" rel="noopener noreferrer"&gt;presence radar guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only then change sensitivity.&lt;/strong&gt; One step at a time, and note the change. Higher sensitivity catches micro-movement and also picks up curtains and fans.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Room-size adjustment.&lt;/strong&gt; Below about 15 m², drop sensitivity one step; above about 40 m², raise it one step — small rooms strengthen reflections, large rooms weaken them.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Keep the baseline
&lt;/h2&gt;

&lt;p&gt;Save the empty-room reading and the walk-through trace for every installation. It is the only objective record of what the sensor saw on day one, and it turns "it worked when you left" into a comparison you can actually make.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The fields in full: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/product/2026/10/17/presence-radar-guide.html" rel="noopener noreferrer"&gt;presence radar guide&lt;/a&gt;, &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/product/2026/10/17/sleep-radar-guide.html" rel="noopener noreferrer"&gt;sleep radar guide&lt;/a&gt;, &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/product/2026/10/17/fall-radar-guide.html" rel="noopener noreferrer"&gt;fall radar guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Why it behaves this way: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/how-mmwave-radar-works.html" rel="noopener noreferrer"&gt;How mmWave Radar Works&lt;/a&gt; and &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/radar-latency-explained.html" rel="noopener noreferrer"&gt;Radar Latency Explained&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Setting expectations on the numbers: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/fundamentals/2026/10/17/radar-spec-limits.html" rel="noopener noreferrer"&gt;Three mmWave Radar Specs That Are Almost Always Exaggerated&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Mounting geometry: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/16/sleep-radar-installation-position.html" rel="noopener noreferrer"&gt;Sleep Radar Mounting Position&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Multiple radars in one home: &lt;a href="https://249263431.github.io/60GHz-Radar-SDK/guide/2026/10/24/running-several-radars-one-home.html" rel="noopener noreferrer"&gt;Running Several Radars in One Home&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finished radars and modules are on &lt;a href="https://www.tindie.com/stores/fuxinsuo/" rel="noopener noreferrer"&gt;Tindie&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>iot</category>
      <category>radar</category>
      <category>tutorial</category>
      <category>smarthome</category>
    </item>
  </channel>
</rss>
