<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Bluetooth Low Energy - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Bluetooth Low Energy - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 11 May 2026 10:29:01 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/bluetooth-low-energy/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ The Bluetooth LE Audio Handbook: From "Why Does My Call Sound Like a Tin Can?" to AOSP Implementation ]]>
                </title>
                <description>
                    <![CDATA[ Since the early 2000s, Bluetooth has been the dominant way we listen to wireless audio, powering everything from the first mono headsets to today's true wireless earbuds. But the underlying technology ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-bluetooth-le-audio-handbook/</link>
                <guid isPermaLink="false">69d6805e707c1ce76855752b</guid>
                
                    <category>
                        <![CDATA[ LEAudio ]]>
                    </category>
                
                    <category>
                        <![CDATA[ bluetooth ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Bluetooth Low Energy ]]>
                    </category>
                
                    <category>
                        <![CDATA[ audio ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nikheel Vishwas Savant ]]>
                </dc:creator>
                <pubDate>Wed, 08 Apr 2026 16:20:46 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/4c5a3b97-9a23-40cd-8999-333927f58e6c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Since the early 2000s, Bluetooth has been the dominant way we listen to wireless audio, powering everything from the first mono headsets to today's true wireless earbuds.</p>
<p>But the underlying technology hasn't kept pace with how we actually use it. True wireless earbuds, all-day hearing aids, shared audio experiences – none of these were anticipated when the original Bluetooth audio stack was designed.</p>
<p>LE Audio, introduced by the Bluetooth SIG and finalized in 2022, is a ground-up redesign that replaces the Classic Bluetooth audio stack with an entirely new architecture built on Bluetooth Low Energy. It introduces a new codec (LC3), new transport primitives (isochronous channels), new profiles for unified audio streaming, and an entirely new broadcast capability called Auracast.</p>
<p>Together, these changes address long-standing limitations around audio quality, power consumption, multi-device streaming, and accessibility.</p>
<p>This handbook is a comprehensive technical deep dive into LE Audio: what it is, why it exists, how it works at every layer of the stack, and how it's implemented in Android (AOSP). We'll start with the history and motivation, build up an intuitive understanding of the core concepts, and then go deep into the architecture and code.</p>
<p>Here's what you'll learn:</p>
<ul>
<li><p>Why Classic Bluetooth audio hit its limits, the relay problem, the two-profile split, power constraints, and the lack of broadcast or hearing aid support</p>
</li>
<li><p>How the LC3 codec works, and why it delivers better audio at roughly half the bitrate of SBC</p>
</li>
<li><p>What isochronous channels are, the new transport primitive that replaces SCO and ACL for audio, in both unicast (CIS) and broadcast (BIS) forms</p>
</li>
<li><p>How the LE Audio profile stack is organized, from foundational services like BAP and PACS up through use-case profiles like TMAP and HAP</p>
</li>
<li><p>How multi-stream audio eliminates the earbud relay hack, with native synchronized streams to each earbud</p>
</li>
<li><p>What Auracast enables, one-to-many broadcast audio and the infrastructure that supports it</p>
</li>
<li><p>How all of this is implemented in Android (AOSP), a full walkthrough of the architecture from framework APIs through the native C++ stack to the Bluetooth controller, including the state machines, codec negotiation, and data flow</p>
</li>
</ul>
<p>Whether you're a Bluetooth engineer, an embedded developer, an Android platform engineer, or just someone curious about how your devices actually work, this guide aims to make one of the most complex parts of modern wireless systems feel approachable.</p>
<p>If you've ever wondered why your earbuds sound great for music but terrible on calls, why one earbud always dies first, or why you can't easily share audio with people around you, read on. The answers are all here.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a href="#heading-once-upon-a-time-in-bluetooth-land">Once Upon a Time in Bluetooth Land</a></p>
</li>
<li><p><a href="#heading-2-the-problems-with-classic-bluetooth-audio">The Problems With Classic Bluetooth Audio</a></p>
</li>
<li><p><a href="#heading-3-enter-le-audio-the-hero-we-needed">Enter LE Audio: The Hero We Needed</a></p>
</li>
<li><p><a href="#heading-4-the-lc3-codec-better-sound-less-power-more-magic">The LC3 Codec: Better Sound, Less Power, More Magic</a></p>
</li>
<li><p><a href="#heading-5-isochronous-channels-the-new-plumbing">Isochronous Channels: The New Plumbing</a></p>
</li>
<li><p><a href="#heading-6-the-le-audio-profile-stack-a-layer-cake-of-specifications">The LE Audio Profile Stack: A Layer Cake of Specifications</a></p>
</li>
<li><p><a href="#heading-7-multi-stream-audio-no-more-left-earbud-relay">Multi-Stream Audio: No More Left Earbud Relay</a></p>
</li>
<li><p><a href="#heading-8-auracast-broadcast-audio-for-the-masses">Auracast: Broadcast Audio for the Masses</a></p>
</li>
<li><p><a href="#heading-9-le-audio-in-androidaosp-the-implementation">LE Audio in Android/AOSP: The Implementation</a></p>
</li>
<li><p><a href="#heading-10-the-aosp-architecture-from-app-to-antenna">The AOSP Architecture: From App to Antenna</a></p>
</li>
<li><p><a href="#heading-11-server-side-source-implementation">Server-Side (Source) Implementation</a></p>
</li>
<li><p><a href="#heading-12-client-side-sink-implementation">Client-Side (Sink) Implementation</a></p>
</li>
<li><p><a href="#heading-13-the-state-machine-that-runs-it-all">The State Machine That Runs It All</a></p>
</li>
<li><p><a href="#heading-14-putting-it-all-together-a-day-in-the-life-of-an-le-audio-packet">Putting It All Together: A Day in the Life of an LE Audio Packet</a></p>
</li>
<li><p><a href="#heading-wrapping-up">Wrapping Up</a></p>
</li>
</ol>
<h2 id="heading-1-once-upon-a-time-in-bluetooth-land">1. Once Upon a Time in Bluetooth Land</h2>
<p>Picture this: it's 2003. Flip phones are cool. The first Bluetooth headsets hit the market, and suddenly you can walk around looking like a cyborg while taking calls.</p>
<p>That mono, telephone-quality audio? Powered by a little thing called <strong>HFP</strong> (Hands-Free Profile) using the <strong>CVSD</strong> codec at a whopping 64 kbps. It sounded like your caller was speaking from inside a submarine, but hey, no wires!</p>
<p>Fast forward a few years. We got <strong>A2DP</strong> (Advanced Audio Distribution Profile) for streaming music, bringing us <strong>SBC</strong> (Sub-Band Codec), the audio codec equivalent of a Honda Civic. Not flashy, not terrible, gets the job done. A2DP gave us stereo music streaming, and life was good.</p>
<p>For a while.</p>
<p>The Bluetooth SIG (Special Interest Group), the consortium of thousands of companies that governs Bluetooth, kept iterating on the classic Bluetooth audio stack. We got better codecs like <strong>aptX</strong>, <strong>AAC</strong>, and <strong>LDAC</strong>. But here's the thing: all of these were built on top of the same ancient plumbing. It's like renovating your kitchen while the house's foundation is slowly cracking.</p>
<p>The Bluetooth audio stack was built on <strong>BR/EDR</strong> (Basic Rate/Enhanced Data Rate), the "Classic Bluetooth" radio. This is the same radio technology from the early 2000s, designed when streaming audio from a phone to a single headset was the pinnacle of innovation. Nobody imagined true wireless earbuds, hearing aids that stream directly from your phone, or broadcasting audio to an entire airport terminal.</p>
<p>By the late 2010s, Bluetooth audio was showing its age. Badly.</p>
<h2 id="heading-2-the-problems-with-classic-bluetooth-audio">2. The Problems With Classic Bluetooth Audio</h2>
<p>Let's catalogue the issues of Classic Bluetooth Audio, because they're educational:</p>
<h3 id="heading-issue-1-the-two-profile-personality-disorder">Issue #1: The Two-Profile Personality Disorder</h3>
<p>Classic Bluetooth had a split personality. Want to listen to music? Use A2DP with SBC/AAC at nice quality. Want to make a phone call? Switch to HFP, which uses a completely different codec (CVSD or mSBC) at dramatically lower quality.</p>
<p>Ever noticed how your wireless earbuds sound amazing playing Spotify, but the moment you jump on a Zoom call, it sounds like you're talking through a paper towel tube? That's the A2DP-to-HFP switchover. Different profiles, different codecs, different audio paths. The switch isn't even graceful, there's often an audible glitch.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/fd614824-0684-4fb3-87a8-8c97052721b6.png" alt="Bluetooth audio quality diagram" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>The above diagram shows the audio quality drop when switching from A2DP (music streaming with SBC/AAC at high quality) to HFP (voice call with CVSD/mSBC at low quality). The switch causes an audible glitch and dramatic reduction in audio fidelity.</p>
<h3 id="heading-issue-2-the-relay-problem-true-wireless-earbuds">Issue #2: The Relay Problem (True Wireless Earbuds)</h3>
<p>When you have true wireless earbuds (left and right earbuds with no wire between them), Classic Bluetooth has a dirty little secret: <strong>A2DP can only stream to one device at a time.</strong></p>
<p>So what actually happens with your fancy earbuds?</p>
<ol>
<li><p>Your phone sends the stereo audio stream to the <strong>primary earbud</strong> (usually the right one)</p>
</li>
<li><p>The primary earbud receives both left and right channels</p>
</li>
<li><p>It then <strong>relays</strong> the other channel to the secondary earbud via a separate Bluetooth link</p>
</li>
</ol>
<p>This relay architecture has a few important consequences. First, you have double the battery drain on the primary earbud (it dies first, you've noticed this). You also get higher latency to the secondary earbud</p>
<p>There are also potential synchronization issues between left and right channels. And if the primary earbud runs out of battery or loses connection, both earbuds go silent.</p>
<h3 id="heading-issue-3-power-hungry">Issue #3: Power Hungry</h3>
<p>BR/EDR was designed in an era when "low power" meant "runs on AA batteries." Streaming audio over Classic Bluetooth is relatively power-hungry. The radio has to maintain a constant, high-bandwidth connection. For devices like hearing aids that need to run all day on tiny batteries, this was a dealbreaker.</p>
<h3 id="heading-issue-4-one-to-one-only">Issue #4: One-to-One Only</h3>
<p>Classic Bluetooth audio is fundamentally <strong>point-to-point</strong>. One source, one sink (or at best, a very hacky "dual audio" implementation where the phone maintains two separate A2DP connections). There's no way to broadcast audio to multiple listeners simultaneously without establishing individual connections to each one.</p>
<p>Imagine you're at an airport gate and want to stream the boarding announcements to everyone's earbuds. With Classic Bluetooth, you'd need to pair with every single person's device individually. Good luck with that at Gate B47.</p>
<h3 id="heading-issue-5-no-standard-for-hearing-aids">Issue #5: No Standard for Hearing Aids</h3>
<p>Before LE Audio, there was no official Bluetooth standard for hearing aids. Apple created its own proprietary MFi (Made for iPhone) hearing aid protocol. Google created ASHA (Audio Streaming for Hearing Aid) as a semi-proprietary BLE-based solution for Android. Neither was an official Bluetooth standard, and interoperability was... let's call it "aspirational."</p>
<h2 id="heading-3-enter-le-audio-the-hero-we-needed">3. Enter LE Audio: The Hero We Needed</h2>
<p>In January 2020, at CES, the Bluetooth SIG unveiled <strong>LE Audio</strong>, a complete reimagining of Bluetooth audio built on top of Bluetooth Low Energy (BLE) instead of Classic BR/EDR.</p>
<p>The core transport features (isochronous channels, EATT, LE Power Control) shipped in the Bluetooth Core Specification v5.2 in late 2019/early 2020. But the full suite of LE Audio profiles and services wasn't completed until July 12, 2022, when the Bluetooth SIG officially announced that all LE Audio specifications had been adopted.</p>
<p>The effort involved over 25 working groups, thousands of engineers from hundreds of companies, and took approximately 7 years from initial concept to completion. This wasn't a minor spec update. It was a ground-up redesign.</p>
<p>Here's what LE Audio brings to the table:</p>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Classic Audio</th>
<th>LE Audio</th>
</tr>
</thead>
<tbody><tr>
<td>Radio</td>
<td>BR/EDR (Classic)</td>
<td>BLE (Low Energy)</td>
</tr>
<tr>
<td>Mandatory Codec</td>
<td>SBC</td>
<td>LC3</td>
</tr>
<tr>
<td>Audio Quality at Same Bitrate</td>
<td>Good</td>
<td>Better (LC3 wins)</td>
</tr>
<tr>
<td>Power Consumption</td>
<td>Higher</td>
<td>Lower</td>
</tr>
<tr>
<td>Multi-Stream</td>
<td>No (relay hack)</td>
<td>Yes (native)</td>
</tr>
<tr>
<td>Broadcast Audio</td>
<td>No</td>
<td>Yes (Auracast)</td>
</tr>
<tr>
<td>Hearing Aid Support</td>
<td>No standard (MFi/ASHA)</td>
<td>Yes (HAP)</td>
</tr>
<tr>
<td>Bidirectional Audio</td>
<td>Separate profiles (A2DP + HFP)</td>
<td>Unified (BAP)</td>
</tr>
<tr>
<td>Audio Sharing</td>
<td>Very limited</td>
<td>Built-in</td>
</tr>
</tbody></table>
<p>Think of it this way: Classic Bluetooth Audio is like a landline telephone system: reliable, well-understood, but fundamentally limited.</p>
<p>LE Audio is like the transition to VoIP and streaming: same goal (getting audio from A to B), but entirely new infrastructure that unlocks capabilities the old system could never support.</p>
<h2 id="heading-4-the-lc3-codec-better-sound-less-power-more-magic">4. The LC3 Codec: Better Sound, Less Power, More Magic</h2>
<p>At the heart of LE Audio is a new mandatory codec called <strong>LC3</strong>: Low Complexity Communication Codec. If SBC is the Honda Civic, LC3 is a Tesla Model 3. It's more efficient, more capable, and designed from the ground up for the modern era.</p>
<h3 id="heading-what-even-is-a-codec">What Even Is a Codec?</h3>
<p>For the uninitiated: a codec (<strong>co</strong>der-<strong>dec</strong>oder) is an algorithm that compresses audio so it can be transmitted over a limited-bandwidth wireless link, and then decompresses it on the other side. The better the codec, the better the audio sounds at a given bitrate, and the less battery it eats doing the math.</p>
<h3 id="heading-lc3-technical-specs">LC3 Technical Specs</h3>
<p>LC3 was developed by Fraunhofer IIS (the same folks who brought us MP3 and AAC, they know a thing or two about audio coding) and Ericsson.</p>
<p>Here are the key specs:</p>
<ul>
<li><p><strong>Sample rates</strong>: 8, 16, 24, 32, 44.1, and 48 kHz</p>
</li>
<li><p><strong>Bit depth</strong>: 16, 24, or 32 bits</p>
</li>
<li><p><strong>Frame durations</strong>: 7.5 ms and 10 ms</p>
</li>
<li><p><strong>Bitrate range</strong>: 16 to 320 kbps per channel</p>
</li>
<li><p><strong>Algorithmic latency</strong>: 7.5 ms (for 7.5 ms frames) or 10 ms (for 10 ms frames)</p>
</li>
<li><p><strong>Channels</strong>: Mono or stereo</p>
</li>
</ul>
<h3 id="heading-why-lc3-is-better-than-sbc">Why LC3 Is Better Than SBC</h3>
<p>The big headline: LC3 delivers equivalent or better audio quality at roughly half the bitrate of SBC.</p>
<p>In listening tests conducted by Fraunhofer, participants rated LC3 at 160 kbps as equivalent to or better than SBC at 345 kbps. That's not a marginal improvement, it's nearly a 2x efficiency gain.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/293ea94c-3a03-4462-8361-89617e07329f.png" alt="SBC vs LC3 bar chart comparing audio quality" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>The above bar chart compares subjective audio quality ratings of LC3 and SBC at various bitrates. LC3 at 160 kbps is rated equivalent to or better than SBC at 345 kbps, demonstrating roughly 2x efficiency improvement.</p>
<p>This efficiency gain translates directly into one of two things (or a combination of both):</p>
<ol>
<li><p><strong>Better audio quality at the same power</strong>, more bits for quality, less wasted</p>
</li>
<li><p><strong>Same audio quality at lower power</strong>, the device runs longer on a charge</p>
</li>
</ol>
<h3 id="heading-how-lc3-actually-works-the-simplified-version">How LC3 Actually Works (The Simplified Version)</h3>
<p>LC3 uses a <strong>modified discrete cosine transform (MDCT)</strong>, a mathematical technique that converts audio from the time domain (a waveform) to the frequency domain (which frequencies are present). This is similar to what AAC and other modern codecs do, but LC3's transform is optimized for low computational complexity.</p>
<p>Here's the encoding pipeline, simplified:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/f3961a0a-42af-443a-96b4-67f340a55944.png" alt="flowchart of the LC3 encoding pipeline" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>This is a flowchart of the LC3 encoding pipeline. PCM audio input passes through an MDCT (Modified Discrete Cosine Transform) to convert from time domain to frequency domain. Then spectral noise shaping applies a psychoacoustic model to hide quantization noise in inaudible frequency regions, followed by quantization and entropy coding to produce the compressed LC3 bitstream.</p>
<p>The key insight is <strong>spectral noise shaping</strong>: LC3 uses a psychoacoustic model (a model of how humans perceive sound) to ensure that the quantization noise (the artifacts introduced by compression) is shaped to fall in frequency regions where it's least audible. Your ears literally can't hear the distortion. Clever, right?</p>
<h3 id="heading-lc3-vs-lc3plus">LC3 vs. LC3plus</h3>
<p>You might also hear about <strong>LC3plus</strong>, an enhanced version that adds:</p>
<ul>
<li><p>Super-wideband and fullband modes (up to 48 kHz audio bandwidth)</p>
</li>
<li><p>Additional frame sizes (2.5 ms, 5 ms) for ultra-low-latency applications</p>
</li>
<li><p>Higher quality at very low bitrates</p>
</li>
</ul>
<p>LC3plus is not part of the base LE Audio spec but is used in some implementations (like DECT NR+ for cordless phones).</p>
<h2 id="heading-5-isochronous-channels-the-new-plumbing">5. Isochronous Channels: The New Plumbing</h2>
<p>Here's where things get architecturally interesting. Classic Bluetooth audio used <strong>SCO</strong> (Synchronous Connection-Oriented) links for voice and <strong>L2CAP</strong> over <strong>ACL</strong> (Asynchronous Connection-Less) links for A2DP streaming. These were okay, but they're like using garden hoses for different purposes, functional but not optimized for audio.</p>
<p>LE Audio introduces a brand-new transport mechanism at the link layer: <strong>Isochronous Channels</strong>. These are purpose-built pipes for time-sensitive data like audio.</p>
<h3 id="heading-what-isochronous-means">What "Isochronous" Means</h3>
<p>"Isochronous" (from Greek: <em>iso</em> = equal, <em>chronos</em> = time) means "occurring at regular time intervals." An isochronous channel guarantees that data arrives at a predictable, regular cadence, exactly what you need for audio.</p>
<p>Think of it this way:</p>
<ul>
<li><p><strong>Asynchronous</strong> (ACL): "Here's some data. It'll get there when it gets there." (Great for file transfers, bad for audio)</p>
</li>
<li><p><strong>Synchronous</strong> (SCO): "Here's data that MUST arrive on time, and if it doesn't, too bad." (Old voice links, no retransmissions)</p>
</li>
<li><p><strong>Isochronous</strong>: "Here's data that should arrive on time, and we'll try our best to make that happen with some smart retransmission." (Best of both worlds)</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/51324579-2e10-4b26-bc09-482fa6ade853.png" alt="Comparison of Bluetooth transport types: asynchronous, synchronous, and isosynchronous" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>This above chart is a comparison of three Bluetooth transport types: Asynchronous (ACL) delivers data without timing guarantees, Synchronous (SCO) delivers data on a fixed schedule with no retransmission, and Isochronous delivers data on a regular schedule with smart retransmission, combining the reliability of ACL with the timing guarantees of SCO.</p>
<h3 id="heading-two-flavors-cis-and-bis">Two Flavors: CIS and BIS</h3>
<p>Isochronous channels come in two flavors, and this is where the magic happens:</p>
<h4 id="heading-cis-connected-isochronous-stream">CIS — Connected Isochronous Stream</h4>
<p>CIS is for <strong>point-to-point</strong> audio (unicast). It's what your phone uses to stream music to your earbuds.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/2b44fe9e-0e5d-44ee-877c-b26e147b63a1.png" alt="Diagram of a Connected Isochronous Stream (CIS) setup" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>The aboe is a diagram of a Connected Isochronous Stream (CIS) setup: a phone (Unicast Client) sends two synchronized CIS streams within a single CIG (Connected Isochronous Group), one to the left earbud and one to the right earbud. Arrows show bidirectional audio flow, with music going to the earbuds and microphone audio returning to the phone.</p>
<p>Key features of CIS:</p>
<ul>
<li><p><strong>Bidirectional</strong>: Audio can flow in both directions simultaneously (unicast to earbuds AND microphone audio back)</p>
</li>
<li><p><strong>Acknowledged</strong>: The receiver sends acknowledgments, enabling retransmissions of lost packets</p>
</li>
<li><p><strong>Grouped into CIGs</strong>: Multiple CIS streams are grouped into a <strong>CIG</strong> (Connected Isochronous Group), ensuring they're synchronized</p>
</li>
</ul>
<p>That last point is crucial. A CIG ensures the left and right earbud receive their audio packets with tight synchronization, no more "my left ear is 50ms ahead of my right ear" issues.</p>
<h4 id="heading-bis-broadcast-isochronous-stream">BIS — Broadcast Isochronous Stream</h4>
<p>BIS is for <strong>one-to-many</strong> audio (broadcast). It's the foundation of Auracast.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/22beaaf6-ace8-4110-a33c-d7cf370f93d3.png" alt="Diagram of a Broadcast Isochronous Stream (BIS) setup" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>The above is a diagram of a Broadcast Isochronous Stream (BIS) setup: a single broadcast source transmits audio via a BIG (Broadcast Isochronous Group) containing multiple BIS streams. Multiple receivers (broadcast sinks) independently receive the same audio without any connection to the source, similar to FM radio.</p>
<p>Key features of BIS:</p>
<ul>
<li><p><strong>Unidirectional</strong>: One-way only (source to listeners), makes sense, you can't have a million people talking back</p>
</li>
<li><p><strong>Unacknowledged</strong>: No acks from listeners (the source doesn't even know who's listening)</p>
</li>
<li><p><strong>Grouped into BIGs</strong>: Multiple BIS streams form a <strong>BIG</strong> (Broadcast Isochronous Group)</p>
</li>
<li><p><strong>Scalable</strong>: No upper limit on listeners, it's actual radio broadcasting</p>
</li>
</ul>
<h3 id="heading-the-iso-data-path">The ISO Data Path</h3>
<p>Under the hood, isochronous data follows a specific path through the controller:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/8bfd7893-b93a-4f07-af09-17cbd737fcbb.png" alt="Diagram of the isochronous data path through the Bluetooth controller" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>The above is a diagram of the isochronous data path through the Bluetooth controller. Audio frames from the host pass through HCI, then through the ISO Adaptation Layer (ISO-AL) which handles segmentation, timestamping, and flush timeout management, before reaching the Link Layer for transmission over the air.</p>
<p>The key innovation is the <strong>ISO-AL</strong> (Isochronous Adaptation Layer), which sits between HCI and the Link Layer. It handles:</p>
<ul>
<li><p><strong>Segmentation</strong>: Breaking audio frames into link-layer-sized pieces</p>
</li>
<li><p><strong>Time-stamping</strong>: Each audio frame gets a timestamp so the receiver knows exactly when to play it</p>
</li>
<li><p><strong>Flush timeout</strong>: If a frame can't be delivered in time, it's flushed (better to skip a frame than play it late)</p>
</li>
</ul>
<h2 id="heading-6-the-le-audio-profile-stack-a-layer-cake-of-specifications">6. The LE Audio Profile Stack: A Layer Cake of Specifications</h2>
<p>If you've ever looked at the list of LE Audio specifications and felt your eyes glaze over, you're not alone. There are a LOT of them. But they're organized in a logical hierarchy, and once you understand the structure, it all makes sense.</p>
<h3 id="heading-visual-the-profile-stack">Visual: The Profile Stack</h3>
<p>Here's a three-tier diagram of the LE Audio profile stack:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/e4968717-72bc-43c5-b72c-057a65534bb1.png" alt="Three-tier diagram of the LE Audio profile stack" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>Tier 1 (foundation) contains BAP, VCP, MCP, CCP, MICP, CSIP, and BASS. Tier 2 (grouping layer) contains CAP, which coordinates the Tier 1 profiles. Tier 3 (use-case profiles) contains TMAP for telephony and media, HAP for hearing aids, and PBP for public broadcasts. Each tier builds on the one below it.</p>
<p>Think of it as a wedding cake with three tiers:</p>
<h3 id="heading-tier-1-the-foundation-core-services-and-profiles">Tier 1: The Foundation (Core Services and Profiles)</h3>
<p>These are the building blocks everything else is built on:</p>
<h4 id="heading-bap-basic-audio-profile">BAP — Basic Audio Profile</h4>
<p>The big kahuna. BAP defines the fundamental procedures for discovering, configuring, and establishing LE Audio streams. It defines two roles:</p>
<ul>
<li><p><strong>Unicast Client</strong>: The device that initiates and controls audio streams (typically your phone)</p>
</li>
<li><p><strong>Unicast Server</strong>: The device that renders or captures audio (typically your earbuds)</p>
</li>
</ul>
<p>BAP relies on several GATT services:</p>
<ul>
<li><p><strong>PACS</strong> (Published Audio Capabilities Service): "Hey, here's what audio formats I support"</p>
</li>
<li><p><strong>ASCS</strong> (Audio Stream Control Service): "Let's set up and manage audio streams"</p>
</li>
</ul>
<h4 id="heading-vcp-volume-control-profile">VCP — Volume Control Profile</h4>
<p>Handles remote volume control. Your phone can control the volume on your earbuds (and vice versa) using the <strong>VCS</strong> (Volume Control Service).</p>
<h4 id="heading-mcp-media-control-profile">MCP — Media Control Profile</h4>
<p>Allows remote control of media playback. Pause, play, skip, and so on, through the <strong>MCS</strong> (Media Control Service). Like AVRCP for LE Audio.</p>
<h4 id="heading-ccp-call-control-profile">CCP — Call Control Profile</h4>
<p>Manages phone call state. Answer, reject, hold calls via the <strong>TBS</strong> (Telephone Bearer Service). This replaces HFP's call control functionality.</p>
<h4 id="heading-micp-microphone-control-profile">MICP — Microphone Control Profile</h4>
<p>Handles remote mute/unmute of a device's microphone. Simple but essential, ever been on a call where you couldn't figure out how to mute? MICP standardizes it.</p>
<h4 id="heading-csip-coordinated-set-identification-profile">CSIP — Coordinated Set Identification Profile</h4>
<p>This is the "these two earbuds belong together" profile. It uses the <strong>CSIS</strong> (Coordinated Set Identification Service) to tell the phone: "Hey, I'm the left earbud, and my buddy over there is the right earbud. We're a set."</p>
<p>Without CSIP, your phone would treat each earbud as a completely independent device. CSIP is what enables seamless "coordinated set" behavior.</p>
<h4 id="heading-bass-broadcast-audio-scan-service">BASS — Broadcast Audio Scan Service</h4>
<p>Handles the discovery of broadcast audio sources. A device with BASS can scan for nearby broadcasts and help another device (like hearing aids) tune into them.</p>
<h3 id="heading-tier-2-the-grouping-layer">Tier 2: The Grouping Layer</h3>
<h4 id="heading-cap-common-audio-profile">CAP — Common Audio Profile</h4>
<p>CAP sits on top of the Tier 1 profiles and provides common procedures that higher-level profiles use. It handles things like:</p>
<ul>
<li><p>Discovering a coordinated set of devices (using CSIP)</p>
</li>
<li><p>Setting up unicast audio streams to a coordinated set (using BAP)</p>
</li>
<li><p>Initiating broadcast audio streams</p>
</li>
</ul>
<p>Think of CAP as the "orchestrator" that coordinates all the Tier 1 profiles to work together.</p>
<h3 id="heading-tier-3-the-use-case-profiles">Tier 3: The Use-Case Profiles</h3>
<p>These are the profiles that map to actual user scenarios:</p>
<h4 id="heading-tmap-telephony-and-media-audio-profile">TMAP — Telephony and Media Audio Profile</h4>
<p>The "all-in-one" profile for typical audio use cases. TMAP defines roles like:</p>
<ul>
<li><p><strong>Call Terminal (CT)</strong>: Can make and receive calls</p>
</li>
<li><p><strong>Unicast Media Sender (UMS)</strong>: Can send media audio (your phone)</p>
</li>
<li><p><strong>Unicast Media Receiver (UMR)</strong>: Can receive media audio (your earbuds)</p>
</li>
<li><p><strong>Broadcast Media Sender (BMS)</strong>: Can broadcast media audio</p>
</li>
<li><p><strong>Broadcast Media Receiver (BMR)</strong>: Can receive broadcast media audio</p>
</li>
</ul>
<p>If you're building a typical phone + earbuds experience, TMAP is your profile.</p>
<h4 id="heading-hap-hearing-access-profile">HAP — Hearing Access Profile</h4>
<p>The standardized profile for hearing aids. This replaces the proprietary MFi and ASHA solutions with an official Bluetooth standard. HAP defines procedures for:</p>
<ul>
<li><p>Streaming audio to hearing aids</p>
</li>
<li><p>Adjusting hearing aid presets</p>
</li>
<li><p>Controlling volume on hearing aids</p>
</li>
</ul>
<p>This is a huge deal. For the first time, hearing aids can interoperate across all Bluetooth devices using a standard protocol.</p>
<h4 id="heading-pbp-public-broadcast-profile">PBP — Public Broadcast Profile</h4>
<p>Defines how to set up and discover public broadcasts (Auracast). This is what enables "broadcast audio in the airport terminal" scenarios.</p>
<h2 id="heading-7-multi-stream-audio-no-more-left-earbud-relay">7. Multi-Stream Audio: No More Left Earbud Relay</h2>
<p>Remember the relay problem with Classic Bluetooth? LE Audio eliminates it entirely with <strong>multi-stream audio</strong>.</p>
<p>With LE Audio, the source device (your phone) can send independent, synchronized audio streams directly to each earbud:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/45b7d1d7-f9ba-4857-a296-64bc0dfdd346.png" alt="Diagram comparing Classic Bluetooth relay architecture with LE Audio multi-stream architecture" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>This diagram compares Classic Bluetooth relay architecture (phone sends stereo to primary earbud, which relays to secondary) with LE Audio multi-stream architecture (phone sends independent synchronized streams directly to each earbud via separate CIS channels within a CIG). The LE Audio approach provides balanced battery drain and lower latency.</p>
<h3 id="heading-how-it-works">How It Works</h3>
<ol>
<li><p>Both earbuds connect to the phone independently via BLE</p>
</li>
<li><p>The phone identifies them as a coordinated set using CSIP</p>
</li>
<li><p>The phone establishes a <strong>CIG</strong> (Connected Isochronous Group) with two <strong>CIS</strong> streams, one per earbud</p>
</li>
<li><p>The phone sends the left channel on CIS #1 and the right channel on CIS #2</p>
</li>
<li><p>The CIG ensures both streams are synchronized, the earbuds play their respective channels at exactly the same time</p>
</li>
</ol>
<p>Benefits:</p>
<ul>
<li><p><strong>Balanced battery drain</strong>: Both earbuds do equal work</p>
</li>
<li><p><strong>Lower latency</strong>: No relay hop means fewer delays</p>
</li>
<li><p><strong>Better reliability</strong>: If one earbud loses connection, the other keeps playing</p>
</li>
<li><p><strong>True stereo</strong>: Each earbud gets its own independent stream, no need to decode and split</p>
</li>
</ul>
<h2 id="heading-8-auracast-broadcast-audio-for-the-masses">8. Auracast: Broadcast Audio for the Masses</h2>
<p><strong>Auracast</strong> is LE Audio's broadcast feature, and it's arguably the most revolutionary part. It's like FM radio for Bluetooth: one source, unlimited listeners.</p>
<h3 id="heading-how-auracast-works">How Auracast Works</h3>
<ol>
<li><p>A Broadcast Source creates a BIG (Broadcast Isochronous Group) containing one or more BIS streams</p>
</li>
<li><p>The source advertises the broadcast using Extended Advertising with metadata (stream name, language, codec config)</p>
</li>
<li><p>A Broadcast Sink discovers the advertisement, syncs to the Periodic Advertising train to get stream parameters</p>
</li>
<li><p>The sink joins the BIG and starts receiving audio</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/d00d5d24-e7c1-44ae-9052-b61ae049b2ba.png" alt="Diagram of the Auracast broadcast flow" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>The above diagram shows the Auracast broadcast flow: a broadcast source advertises via Extended Advertising, broadcast sinks discover the advertisement and sync to Periodic Advertising to receive stream parameters, then join the BIG to receive audio. There is no limit on the number of sinks.</p>
<h3 id="heading-auracast-use-cases">Auracast Use Cases</h3>
<p>The use cases are actually compelling:</p>
<ul>
<li><p><strong>Airports/Train Stations</strong>: Broadcast gate announcements directly to travelers' earbuds (in multiple languages!)</p>
</li>
<li><p><strong>Gyms</strong>: Every TV on the wall can broadcast its own audio, pick which one to listen to</p>
</li>
<li><p><strong>Museums</strong>: Audio guides streamed to visitors' own earbuds</p>
</li>
<li><p><strong>Bars/Sports Events</strong>: Watch the game on the big screen with commentary in your earbuds, without blasting everyone</p>
</li>
<li><p><strong>Conferences</strong>: Live translation channels broadcast to attendees</p>
</li>
<li><p><strong>Silent Discos</strong>: Obviously</p>
</li>
</ul>
<h3 id="heading-the-bass-role-broadcast-assistants">The BASS Role: Broadcast Assistants</h3>
<p>There's a neat supporting concept called a <strong>Broadcast Assistant</strong>. This is a device (typically your phone) that helps another device (typically your earbuds) discover and tune into broadcasts.</p>
<p>Why? Because tiny earbuds might not have the processing power or UI to scan for and select broadcasts themselves. So your phone does the scanning, shows you available broadcasts, and tells your earbuds which one to tune into via the <strong>BASS</strong> (Broadcast Audio Scan Service).</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/dda3bf79-028f-4624-bfa1-7616bbb40a25.png" alt="Diagram showing the Broadcast Assistant role" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>The above diagram showes the Broadcast Assistant role: a phone scans for available Auracast broadcasts and displays them to the user. When the user selects a broadcast, the phone (acting as Broadcast Assistant) instructs the user's earbuds to tune into the selected broadcast via BASS (Broadcast Audio Scan Service), since the earbuds may lack the UI or processing power to scan on their own.</p>
<h2 id="heading-9-le-audio-in-androidaosp-the-implementation">9. LE Audio in Android/AOSP: The Implementation</h2>
<p>Now let's get into the code. This is where the rubber meets the road.</p>
<h3 id="heading-timeline-of-android-le-audio-support">Timeline of Android LE Audio Support</h3>
<ul>
<li><p><strong>Android 12 (2021)</strong>: Initial LE Audio APIs introduced (developer preview quality)</p>
</li>
<li><p><strong>Android 13 (2022)</strong>: Full LE Audio support, including unicast client/server, broadcast source/sink</p>
</li>
<li><p><strong>Android 14 (2023)</strong>: Improved stability, broadcast audio enhancements, LE Audio source role support</p>
</li>
<li><p><strong>Android 15 (2024)</strong>: Auracast Broadcast Sink support, Broadcast Assistant role, improved audio context switching</p>
</li>
<li><p><strong>Android 16 (2025)</strong>: Native Auracast UI in Quick Settings/Bluetooth settings, enhanced audio sharing experience</p>
</li>
</ul>
<p>The LE Audio implementation in AOSP lives primarily in the <strong>Bluetooth module</strong> (<code>packages/modules/Bluetooth</code>), which is a <strong>Mainline module</strong>, meaning it can be updated via Google Play System Updates independent of full Android OS updates.</p>
<h3 id="heading-key-aosp-source-locations">Key AOSP Source Locations</h3>
<p>If you want to dive into the code yourself, here's your treasure map:</p>
<table>
<thead>
<tr>
<th>Component</th>
<th>Path</th>
</tr>
</thead>
<tbody><tr>
<td>LE Audio Java Service</td>
<td><code>packages/modules/Bluetooth/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java</code></td>
</tr>
<tr>
<td>JNI Bridge</td>
<td><code>packages/modules/Bluetooth/android/app/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java</code></td>
</tr>
<tr>
<td>Native LE Audio Client</td>
<td><code>packages/modules/Bluetooth/system/bta/le_audio/le_audio_client.cc</code></td>
</tr>
<tr>
<td>Codec Manager</td>
<td><code>packages/modules/Bluetooth/system/bta/le_audio/codec_manager.cc</code></td>
</tr>
<tr>
<td>State Machine</td>
<td><code>packages/modules/Bluetooth/system/bta/le_audio/state_machine.cc</code></td>
</tr>
<tr>
<td>LC3 Codec Library</td>
<td><code>external/liblc3/</code></td>
</tr>
<tr>
<td>Framework API</td>
<td><code>frameworks/base/core/java/android/bluetooth/BluetoothLeAudio.java</code></td>
</tr>
<tr>
<td>Broadcast API</td>
<td><code>frameworks/base/core/java/android/bluetooth/BluetoothLeBroadcast.java</code></td>
</tr>
</tbody></table>
<h3 id="heading-high-level-architecture">High-Level Architecture</h3>
<p>The AOSP Bluetooth stack for LE Audio follows Android's classic layered architecture:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/1f4a9658-a26a-4388-917d-92794f0f407a.png" alt="Layered architecture diagram of the AOSP Bluetooth LE Audio stack" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>In this layered architecture diagram of the AOSP Bluetooth LE Audio stack, here's what's shown from top to bottom: Application layer, Framework APIs (BluetoothLeAudio, BluetoothLeBroadcast), LeAudioService (Java), JNI Bridge, Native C++ stack (le_audio_client, codec_manager, state_machine, iso_manager), HCI layer, and Bluetooth Controller hardware.</p>
<h2 id="heading-10-the-aosp-architecture-from-app-to-antenna">10. The AOSP Architecture: From App to Antenna</h2>
<p>Let's walk through each layer in detail.</p>
<h3 id="heading-layer-1-the-framework-apis">Layer 1: The Framework APIs</h3>
<p>Android exposes LE Audio functionality through several public API classes in <code>android.bluetooth</code>:</p>
<h4 id="heading-bluetoothleaudio"><code>BluetoothLeAudio</code></h4>
<p>The main API for unicast LE Audio. Apps use this to:</p>
<ul>
<li><p>Connect to LE Audio devices</p>
</li>
<li><p>Set active device for audio playback/capture</p>
</li>
<li><p>Query group information (coordinated sets)</p>
</li>
<li><p>Select codec configuration</p>
</li>
</ul>
<pre><code class="language-java">// Example: Connect to an LE Audio device
BluetoothLeAudio leAudio = bluetoothAdapter.getProfileProxy(
    context, listener, BluetoothProfile.LE_AUDIO);

// Set the LE Audio device as active for media playback
leAudio.setActiveDevice(leAudioDevice);
</code></pre>
<h4 id="heading-bluetoothlebroadcast"><code>BluetoothLeBroadcast</code></h4>
<p>API for broadcast audio (Auracast). Apps use this to:</p>
<ul>
<li><p>Start/stop broadcast audio</p>
</li>
<li><p>Set broadcast metadata (name, language)</p>
</li>
<li><p>Configure broadcast code (encryption password)</p>
</li>
</ul>
<pre><code class="language-java">// Start a broadcast
BluetoothLeBroadcast broadcast = bluetoothAdapter.getProfileProxy(
    context, listener, BluetoothProfile.LE_AUDIO_BROADCAST);

broadcast.startBroadcast(contentMetadata, audioConfig, broadcastCode);
</code></pre>
<h4 id="heading-bluetoothlebroadcastassistant"><code>BluetoothLeBroadcastAssistant</code></h4>
<p>API for the broadcast assistant role, helping another device tune into a broadcast.</p>
<h4 id="heading-bluetoothvolumecontrol"><code>BluetoothVolumeControl</code></h4>
<p>API for remote volume control via VCP.</p>
<h4 id="heading-bluetoothhapclient"><code>BluetoothHapClient</code></h4>
<p>API for the Hearing Access Profile, controlling hearing aid presets and streaming.</p>
<h3 id="heading-layer-2-leaudioservice-the-brain">Layer 2: LeAudioService (The Brain)</h3>
<p>The <code>LeAudioService</code> is the central service within the Bluetooth app that orchestrates all LE Audio functionality. This is where the magic happens.</p>
<p>Key responsibilities:</p>
<ul>
<li><p><strong>Device Management</strong>: Tracking connected LE Audio devices and their capabilities</p>
</li>
<li><p><strong>Group Management</strong>: Managing coordinated sets (which devices belong together)</p>
</li>
<li><p><strong>Audio Routing</strong>: Deciding which device(s) should be active for playback/capture</p>
</li>
<li><p><strong>State Machine Management</strong>: Handling the lifecycle of audio connections</p>
</li>
<li><p><strong>Profile Coordination</strong>: Coordinating BAP, VCP, MCP, CCP, and CSIP</p>
</li>
</ul>
<p>Here's a simplified view of how <code>LeAudioService</code> is structured:</p>
<pre><code class="language-java">public class LeAudioService extends ProfileService {
    
    // Map of device address -&gt; state machine
    private Map&lt;BluetoothDevice, LeAudioStateMachine&gt; mStateMachines;
    
    // Map of group ID -&gt; group information
    private Map&lt;Integer, LeAudioGroupDescriptor&gt; mGroupDescriptors;
    
    // Native interface bridge
    private LeAudioNativeInterface mNativeInterface;
    
    // Active device tracking
    private BluetoothDevice mActiveAudioOutDevice;
    private BluetoothDevice mActiveAudioInDevice;
    
    // Codec configuration
    private BluetoothLeAudioCodecConfig mInputLocalCodecConfig;
    private BluetoothLeAudioCodecConfig mOutputLocalCodecConfig;
    
    public void connect(BluetoothDevice device) {
        // 1. Check if device supports LE Audio (PACS)
        // 2. Create state machine for device
        // 3. Initiate connection via native stack
        // 4. Discover GATT services (PACS, ASCS, VCS, etc.)
        // 5. Read audio capabilities
    }
    
    public void setActiveDevice(BluetoothDevice device) {
        // 1. Look up device's group
        // 2. Find all devices in the coordinated set
        // 3. Configure audio streams via BAP
        // 4. Set up isochronous channels
        // 5. Start audio routing
    }
}
</code></pre>
<h3 id="heading-layer-3-the-native-stack-c">Layer 3: The Native Stack (C++)</h3>
<p>Below the Java layer, the heavy lifting happens in C++. The native LE Audio implementation lives in the Bluetooth stack (historically called "Fluoride," with newer components in "Gabeldorsche").</p>
<p>Key native components:</p>
<h4 id="heading-leaudioclientcc-leaudioclientimpl"><code>le_audio_client.cc</code> / <code>le_audio_client_impl</code></h4>
<p>The main C++ implementation of the LE Audio client. This handles:</p>
<ul>
<li><p>GATT client operations (discovering services, reading characteristics)</p>
</li>
<li><p>ASE (Audio Stream Endpoint) state machine management</p>
</li>
<li><p>Codec negotiation with remote devices</p>
</li>
<li><p>CIS/BIS creation and management</p>
</li>
</ul>
<h4 id="heading-statemachinecc"><code>state_machine.cc</code></h4>
<p>Manages the connection state machine for each LE Audio device:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/408b1944-6522-403f-84d1-639362e0b5df.png" alt="State diagram of the native LE Audio connection state machine with states: Disconnected, Connecting, Connected, and Disconnecting. " style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>The above is a state diagram of the native LE Audio connection state machine with states: Disconnected, Connecting, Connected, and Disconnecting. The state machine is managed per-device in the native C++ layer and drives GATT connection setup, service discovery, and characteristic reads before transitioning to Connected.</p>
<h4 id="heading-codecmanagercc"><code>codec_manager.cc</code></h4>
<p>Handles codec configuration:</p>
<ul>
<li><p>Enumerates supported codec capabilities</p>
</li>
<li><p>Selects optimal codec configuration based on device capabilities and use case</p>
</li>
<li><p>Interfaces with the LC3 encoder/decoder</p>
</li>
</ul>
<h4 id="heading-isomanagercc"><code>iso_manager.cc</code></h4>
<p>Manages isochronous channels:</p>
<ul>
<li><p>Creates and tears down CIG/CIS for unicast</p>
</li>
<li><p>Creates and tears down BIG/BIS for broadcast</p>
</li>
<li><p>Handles the HCI interface for isochronous data</p>
</li>
</ul>
<h4 id="heading-audiohalclientcc"><code>audio_hal_client.cc</code></h4>
<p>Bridges the Bluetooth stack with the Android audio HAL:</p>
<ul>
<li><p>Receives PCM audio from the Android audio framework</p>
</li>
<li><p>Passes it to the LC3 encoder</p>
</li>
<li><p>Sends encoded audio over isochronous channels</p>
</li>
</ul>
<h3 id="heading-layer-4-the-controller-hardware">Layer 4: The Controller (Hardware)</h3>
<p>The Bluetooth controller handles the low-level radio operations:</p>
<ul>
<li><p>Link layer scheduling of isochronous events</p>
</li>
<li><p>PHY layer (1M, 2M, or Coded PHY)</p>
</li>
<li><p>Packet formatting and CRC</p>
</li>
<li><p>Retransmission of lost isochronous PDUs</p>
</li>
</ul>
<p>The host (Android) communicates with the controller via <strong>HCI</strong> (Host Controller Interface), using specific HCI commands for isochronous channels:</p>
<ul>
<li><p><code>HCI_LE_Set_CIG_Parameters</code>: Configure a Connected Isochronous Group</p>
</li>
<li><p><code>HCI_LE_Create_CIS</code>: Create Connected Isochronous Streams</p>
</li>
<li><p><code>HCI_LE_Create_BIG</code>: Create a Broadcast Isochronous Group</p>
</li>
<li><p><code>HCI_LE_Setup_ISO_Data_Path</code>: Set up the path for ISO data (HCI vs. vendor-specific)</p>
</li>
<li><p><code>HCI_LE_BIG_Create_Sync</code>: Synchronize to a BIG (for broadcast receivers)</p>
</li>
</ul>
<h2 id="heading-11-server-side-source-implementation">11. Server-Side (Source) Implementation</h2>
<p>The "server side" in LE Audio terminology is actually the <strong>Unicast Server</strong>, the device that renders audio (your earbuds). Yes, it's confusing that the receiver is called the "server." Think of it as a GATT server: it hosts the GATT services that the client connects to.</p>
<h3 id="heading-what-the-unicast-server-does">What the Unicast Server Does</h3>
<p>The Unicast Server (earbud) hosts several GATT services:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/a6ce4211-1720-46b3-8965-0f5346c413fb.png" alt="GATT services hosted by a Unicast Server (earbud)" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>The above diagram shows the GATT services hosted by a Unicast Server (earbud). The server exposes four key services:</p>
<ul>
<li><p>PACS (Published Audio Capabilities Service), which advertises the device's supported codecs, sample rates, frame durations, and audio contexts</p>
</li>
<li><p>ASCS (Audio Stream Control Service), which contains one or more ASE (Audio Stream Endpoint) characteristics that the client writes to in order to configure and control audio streams</p>
</li>
<li><p>VCS (Volume Control Service), which allows the client to read and set the device's volume level</p>
</li>
<li><p>and CSIS (Coordinated Set Identification Service), which identifies this device as part of a coordinated set (for example, "I am the left earbud, and my partner is the right earbud").</p>
</li>
</ul>
<p>The Unicast Client (phone) connects to these services via GATT to discover capabilities, configure streams, and control playback.</p>
<h3 id="heading-the-ase-state-machine-server-side">The ASE State Machine (Server Side)</h3>
<p>Each <strong>ASE</strong> (Audio Stream Endpoint) on the server has a state machine. This is the heart of audio stream management:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/e47ff774-1f97-4704-9ca7-83ba31ab17b1.png" alt="State diagram of the ASE (Audio Stream Endpoint) state machine on the Unicast Server" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>The above is a state diagram of the ASE (Audio Stream Endpoint) state machine on the Unicast Server. States: Idle, Codec Configured, QoS Configured, Enabling, Streaming, Disabling, and Releasing. The client drives transitions by writing operations (Config Codec, Config QoS, Enable, Disable, Release) to the ASE Control Point characteristic.</p>
<p>State transitions:</p>
<ol>
<li><p><strong>IDLE → CODEC_CONFIGURED</strong>: The client writes a <code>Config Codec</code> operation to the ASE Control Point, specifying codec type (LC3), sample rate, frame duration, and so on.</p>
</li>
<li><p><strong>CODEC_CONFIGURED → QoS_CONFIGURED</strong>: The client writes a <code>Config QoS</code> operation, specifying:</p>
<ul>
<li><p>SDU interval (how often audio frames are sent)</p>
</li>
<li><p>Framing (framed or unframed)</p>
</li>
<li><p>Max SDU size</p>
</li>
<li><p>Retransmission number</p>
</li>
<li><p>Max transport latency</p>
</li>
<li><p>Presentation delay</p>
</li>
</ul>
</li>
<li><p><strong>QoS_CONFIGURED → ENABLING</strong>: The client writes an <code>Enable</code> operation. The server prepares to receive audio.</p>
</li>
<li><p><strong>ENABLING → STREAMING</strong>: The CIS is established and audio data starts flowing. This transition happens after the client creates the CIS and both sides are ready.</p>
</li>
<li><p><strong>STREAMING → DISABLING</strong>: The client writes a <code>Disable</code> operation, or the connection is being torn down.</p>
</li>
<li><p><strong>Any state → IDLE</strong>: The client writes a <code>Release</code> operation, tearing down the stream configuration.</p>
</li>
</ol>
<h3 id="heading-standard-codec-configurations">Standard Codec Configurations</h3>
<p>BAP defines a set of named codec configurations that map to specific LC3 parameters. These are the "presets" that devices negotiate:</p>
<table>
<thead>
<tr>
<th>Config</th>
<th>Sample Rate</th>
<th>Frame Duration</th>
<th>Octets/Frame</th>
<th>Bitrate</th>
<th>Typical Use</th>
</tr>
</thead>
<tbody><tr>
<td>8_1</td>
<td>8 kHz</td>
<td>7.5 ms</td>
<td>26</td>
<td>~27.7 kbps</td>
<td>Low-bandwidth voice</td>
</tr>
<tr>
<td>8_2</td>
<td>8 kHz</td>
<td>10 ms</td>
<td>30</td>
<td>24 kbps</td>
<td>Low-bandwidth voice</td>
</tr>
<tr>
<td>16_1</td>
<td>16 kHz</td>
<td>7.5 ms</td>
<td>30</td>
<td>32 kbps</td>
<td>Telephony (low latency)</td>
</tr>
<tr>
<td>16_2</td>
<td>16 kHz</td>
<td>10 ms</td>
<td>40</td>
<td>32 kbps</td>
<td>Telephony (standard)</td>
</tr>
<tr>
<td>24_2</td>
<td>24 kHz</td>
<td>10 ms</td>
<td>60</td>
<td>48 kbps</td>
<td>Wideband voice</td>
</tr>
<tr>
<td>32_1</td>
<td>32 kHz</td>
<td>7.5 ms</td>
<td>60</td>
<td>64 kbps</td>
<td>Super-wideband voice</td>
</tr>
<tr>
<td>32_2</td>
<td>32 kHz</td>
<td>10 ms</td>
<td>80</td>
<td>64 kbps</td>
<td>Super-wideband voice</td>
</tr>
<tr>
<td>48_1</td>
<td>48 kHz</td>
<td>7.5 ms</td>
<td>75</td>
<td>80 kbps</td>
<td>Music (low latency)</td>
</tr>
<tr>
<td>48_2</td>
<td>48 kHz</td>
<td>10 ms</td>
<td>100</td>
<td>80 kbps</td>
<td>Music (balanced)</td>
</tr>
<tr>
<td>48_4</td>
<td>48 kHz</td>
<td>10 ms</td>
<td>120</td>
<td>96 kbps</td>
<td>Music (high quality)</td>
</tr>
<tr>
<td>48_6</td>
<td>48 kHz</td>
<td>10 ms</td>
<td>155</td>
<td>124 kbps</td>
<td>Music (highest quality)</td>
</tr>
</tbody></table>
<p>For most consumer earbuds, you'll see <strong>48_4</strong> (96 kbps at 48 kHz) for media and <strong>16_2</strong> (32 kbps at 16 kHz) for phone calls. That single LC3 codec handles both use cases – no more switching between SBC and mSBC!</p>
<h3 id="heading-audio-context-types">Audio Context Types</h3>
<p>LE Audio defines <strong>Audio Context Types</strong>, metadata that tells the receiving device <em>what kind</em> of audio is being streamed. This allows the device to optimize its behavior (for example, enabling noise cancellation for calls or boosting bass for music):</p>
<table>
<thead>
<tr>
<th>Context</th>
<th>Bit</th>
<th>When It's Used</th>
</tr>
</thead>
<tbody><tr>
<td>Unspecified</td>
<td>0x0001</td>
<td>Generic audio, no specific optimization</td>
</tr>
<tr>
<td>Conversational</td>
<td>0x0002</td>
<td>Phone calls, VoIP, bidirectional, low-latency</td>
</tr>
<tr>
<td>Media</td>
<td>0x0004</td>
<td>Music, podcasts, video, high quality</td>
</tr>
<tr>
<td>Game</td>
<td>0x0008</td>
<td>Gaming, ultra-low latency priority</td>
</tr>
<tr>
<td>Instructional</td>
<td>0x0010</td>
<td>Navigation prompts, announcements</td>
</tr>
<tr>
<td>Voice Assistants</td>
<td>0x0020</td>
<td>"Hey Google" / "Hey Siri"</td>
</tr>
<tr>
<td>Live</td>
<td>0x0040</td>
<td>Live audio (concerts, broadcasts)</td>
</tr>
<tr>
<td>Sound Effects</td>
<td>0x0080</td>
<td>UI clicks, keyboard sounds</td>
</tr>
<tr>
<td>Notifications</td>
<td>0x0100</td>
<td>Message alerts, app notifications</td>
</tr>
<tr>
<td>Ringtone</td>
<td>0x0200</td>
<td>Incoming call ringtone</td>
</tr>
<tr>
<td>Alerts</td>
<td>0x0400</td>
<td>Alarms, timer alerts</td>
</tr>
<tr>
<td>Emergency Alarm</td>
<td>0x0800</td>
<td>Emergency broadcast alerts</td>
</tr>
</tbody></table>
<p>This is way more granular than Classic Audio, which basically only knew two states: "you're playing music" (A2DP) or "you're on a call" (HFP). With LE Audio, the device can make intelligent decisions, like "this is a game, use 7.5ms frames for minimum latency" or "this is a notification, mix it in without interrupting the music stream."</p>
<h3 id="heading-aosp-unicast-server-implementation">AOSP Unicast Server Implementation</h3>
<p>In AOSP, the Unicast Server functionality is implemented primarily for cases where the Android device acts as a receiver (for example, an Android-powered hearing aid or a Chromebook receiving audio).</p>
<p>Key classes:</p>
<ul>
<li><p><code>LeAudioService.java</code>: Handles server-side operations when the device is in sink role</p>
</li>
<li><p>In native code: <code>le_audio_server.cc</code> manages the GATT server hosting PACS, ASCS, and so on.</p>
</li>
</ul>
<h3 id="heading-broadcast-source-implementation">Broadcast Source Implementation</h3>
<p>For broadcast audio (Auracast), the source side in AOSP involves:</p>
<pre><code class="language-java">// In LeAudioService.java / BroadcastService
public void startBroadcast(BluetoothLeBroadcastSettings settings) {
    // 1. Configure LC3 encoder with broadcast parameters
    // 2. Set up Extended Advertising with broadcast metadata
    // 3. Set up Periodic Advertising for stream parameters
    // 4. Create BIG via HCI
    // 5. Start sending ISO data on BIS streams
}
</code></pre>
<p>The native implementation:</p>
<ul>
<li><p><code>broadcaster.cc</code> / <code>broadcaster_impl</code>: Manages broadcast lifecycle</p>
</li>
<li><p>Configures <strong>Extended Advertising</strong> with the broadcast name and metadata</p>
</li>
<li><p>Configures <strong>Periodic Advertising</strong> to carry the BASE (Broadcast Audio Source Endpoint) data structure</p>
</li>
<li><p>Creates a <strong>BIG</strong> with the appropriate number of BIS streams</p>
</li>
<li><p>Routes encoded audio to the BIS data path</p>
</li>
</ul>
<h2 id="heading-12-client-side-sink-implementation">12. Client-Side (Sink) Implementation</h2>
<p>The "client side" is the <strong>Unicast Client</strong>, typically your phone. It discovers, connects to, and controls LE Audio devices.</p>
<h3 id="heading-connection-flow">Connection Flow</h3>
<p>Here's what happens when you connect to LE Audio earbuds, step by step:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/391fca1a-897e-4e4c-b8ea-d0daad76bb38.png" alt="Sequence diagram of the LE Audio connection flow between a phone (Unicast Client) and earbuds (Unicast Server). " style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>Steps: BLE scan and discovery, GATT connection, service discovery (finding PACS, ASCS, CSIP, VCS), reading PAC records to learn audio capabilities, reading CSIS to identify coordinated set membership, then ASE configuration (Config Codec, Config QoS, Enable) followed by CIS creation and audio streaming.</p>
<h3 id="heading-aosp-client-implementation-in-detail">AOSP Client Implementation in Detail</h3>
<h4 id="heading-step-1-3-discovery-and-connection">Step 1-3: Discovery and Connection</h4>
<pre><code class="language-java">// LeAudioService.java
public void connect(BluetoothDevice device) {
    // Creates a new LeAudioStateMachine for this device
    LeAudioStateMachine sm = getOrCreateStateMachine(device);
    sm.sendMessage(LeAudioStateMachine.CONNECT);
    
    // The state machine handles:
    // - GATT connection
    // - Service discovery
    // - Characteristic reads
}
</code></pre>
<p>The <code>LeAudioStateMachine</code> manages the connection lifecycle:</p>
<pre><code class="language-java">// LeAudioStateMachine.java (simplified)
class LeAudioStateMachine extends StateMachine {
    
    class Disconnected extends State {
        void processMessage(Message msg) {
            if (msg.what == CONNECT) {
                // Initiate GATT connection via native
                mNativeInterface.connectLeAudio(mDevice);
                transitionTo(mConnecting);
            }
        }
    }
    
    class Connecting extends State {
        void processMessage(Message msg) {
            if (msg.what == CONNECTION_STATE_CHANGED) {
                if (newState == CONNECTED) {
                    transitionTo(mConnected);
                }
            }
        }
    }
    
    class Connected extends State {
        void enter() {
            // GATT services have been discovered
            // Audio capabilities have been read
            // Device is ready for streaming
            broadcastConnectionState(BluetoothProfile.STATE_CONNECTED);
        }
    }
}
</code></pre>
<h4 id="heading-step-4-6-capability-discovery">Step 4-6: Capability Discovery</h4>
<p>The native layer reads PACS to understand what the remote device supports:</p>
<pre><code class="language-cpp">// In native le_audio_client_impl (C++)
void OnGattServiceDiscovery(BluetoothDevice device) {
    // Read PAC records from PACS
    ReadPacsCharacteristics(device);
    
    // Read CSIS for coordinated set info
    ReadCsisCharacteristics(device);
    
    // Read ASCS for ASE count and state
    ReadAscsCharacteristics(device);
}

void OnPacsRead(BluetoothDevice device, PacRecord sink_pac) {
    // sink_pac contains:
    //   codec_id: LC3
    //   sampling_frequencies: 48000, 44100, 32000, 24000, 16000, 8000
    //   frame_durations: 10ms, 7.5ms
    //   channel_counts: 1
    //   octets_per_frame: 40-155  (maps to bitrate range)
    //   supported_contexts: MEDIA, CONVERSATIONAL, GAME
    
    // Store capabilities for later codec negotiation
    device_info.sink_capabilities = sink_pac;
}
</code></pre>
<h4 id="heading-step-7-12-stream-setup">Step 7-12: Stream Setup</h4>
<p>When audio playback begins, the client configures and enables streams:</p>
<pre><code class="language-cpp">// In native codec_manager (C++)
CodecConfig SelectCodecConfiguration(
    PacRecord remote_capabilities,
    AudioContext context  // MEDIA, CONVERSATIONAL, etc.
) {
    // For media playback, prefer high quality:
    //   48 kHz, 10ms frames, 96 kbps per channel
    
    // For voice calls, optimize for latency:
    //   16 kHz, 7.5ms frames, 32 kbps per channel
    
    // Negotiate: intersect local and remote capabilities
    // Select the best configuration both sides support
}

// In native le_audio_client_impl
void GroupStreamStart(int group_id, AudioContext context) {
    auto group = GetGroup(group_id);
    auto codec_config = SelectCodecConfiguration(
        group-&gt;GetRemoteCapabilities(), context);
    
    // For each device in the group:
    for (auto&amp; device : group-&gt;GetDevices()) {
        // For each ASE on the device:
        for (auto&amp; ase : device-&gt;GetAses()) {
            // Step 8: Config Codec
            WriteAseControlPoint(device, OPCODE_CONFIG_CODEC, {
                .ase_id = ase-&gt;id,
                .codec_id = LC3,
                .codec_specific = {
                    .sampling_freq = 48000,
                    .frame_duration = 10ms,
                    .channel_allocation = LEFT,  // or RIGHT
                    .octets_per_frame = 120
                }
            });
        }
    }
    // After codec configured notification:
    //   Step 9: Config QoS → Step 10: Enable → Step 11: Create CIS
}
</code></pre>
<h4 id="heading-step-13-audio-data-flow">Step 13: Audio Data Flow</h4>
<p>Once streaming, here's how audio data flows through the AOSP stack:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/b42f698d-2a24-4376-8121-bffe101fa7f5.png" alt="Diagram showing audio data flow during LE Audio streaming" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>The above diagram shows audio data flow during LE Audio streaming: PCM audio from the Android audio framework reaches the Bluetooth Audio HAL, is encoded by the LC3 encoder, packetized into ISO SDUs with timestamps, sent over HCI to the controller, transmitted over the air via CIS, received by the earbud's controller, decoded by the earbud's LC3 decoder, and rendered as audio.</p>
<h3 id="heading-broadcast-sink-implementation">Broadcast Sink Implementation</h3>
<p>For receiving broadcast audio (Auracast), AOSP implements:</p>
<pre><code class="language-cpp">// Broadcast sink flow (native)
void OnBroadcastSourceFound(AdvertisingReport report) {
    // Parse Extended Advertising for broadcast metadata
    BroadcastMetadata metadata = ParseBroadcastMetadata(report);
    
    // Display: "Airport Gate B47 - English"
    NotifyBroadcastSourceFound(metadata);
}

void SyncToBroadcast(BroadcastMetadata metadata) {
    // 1. Sync to Periodic Advertising
    HCI_LE_Periodic_Advertising_Create_Sync(metadata.sync_info);
    
    // 2. On PA sync established, parse BASE
    BASE base = ParseBASE(periodic_adv_data);
    
    // 3. Select subgroup and BIS streams
    // 4. Sync to BIG
    HCI_LE_BIG_Create_Sync(base.big_params, selected_bis);
    
    // 5. Set up ISO data path
    HCI_LE_Setup_ISO_Data_Path(bis_handle, HCI_DATA_PATH);
    
    // 6. Start receiving and decoding audio
}
</code></pre>
<h2 id="heading-13-the-state-machine-that-runs-it-all">13. The State Machine That Runs It All</h2>
<p>The AOSP LE Audio implementation uses several interconnected state machines:</p>
<h3 id="heading-connection-state-machine">Connection State Machine</h3>
<p>Manages the overall connection lifecycle for each device:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/f534c8b5-5874-4af8-824c-da1c15e3188e.png" alt="State diagram showing the LE Audio connection state machine with four states: Disconnected, Connecting, Connected, and Disconnecting." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>This state diagram shows the LE Audio connection state machine with four states: Disconnected, Connecting, Connected, and Disconnecting.</p>
<p>Transitions: CONNECT event moves from Disconnected to Connecting, successful connection moves to Connected, DISCONNECT event moves to Disconnecting, and completion returns to Disconnected. Timeout or failure from Connecting also returns to Disconnected.</p>
<h3 id="heading-group-audio-state-machine">Group Audio State Machine</h3>
<p>Manages the audio state for a <em>group</em> of devices (coordinated set):</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/54c9bb32-d6ad-4f1d-8238-7fd8c09c19d4.png" alt="State diagram of the group audio state machine with states: Idle, Codec Configured, QoS Configured, Enabling, Streaming, and Disabling. " style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>This is a state diagram showing the group audio state machine with states: Idle, Codec Configured, QoS Configured, Enabling, Streaming, and Disabling. The forward path proceeds through each state in order as audio streams are set up. The Release operation returns any state to Idle.</p>
<h3 id="heading-how-the-pieces-fit-together-code-walkthrough">How the Pieces Fit Together (Code Walkthrough)</h3>
<p>Here's a simplified walkthrough of what happens when you press "play" on your music app with LE Audio earbuds connected:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/8086fa96-68c6-4d28-9670-76b3264d9031.png" alt="Diagram that traces the sequence of events when a user presses &quot;play&quot; in a music app with LE Audio earbuds connected" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>The above diagram traces the sequence of events when a user presses "play" in a music app with LE Audio earbuds connected.</p>
<p>The flow is:</p>
<ol>
<li><p>The music app writes PCM audio to an AudioTrack.</p>
</li>
<li><p>The Android AudioFlinger routes the audio to the Bluetooth Audio HAL.</p>
</li>
<li><p>The HAL notifies LeAudioService that audio is starting.</p>
</li>
<li><p>LeAudioService looks up the active group and triggers GroupStreamStart in the native stack.</p>
</li>
<li><p>The native stack configures ASEs on both earbuds (Config Codec → Config QoS → Enable) by writing to the ASCS control point on each device.</p>
</li>
<li><p>The native stack creates a CIG with two CIS channels via HCI.</p>
</li>
<li><p>Both CIS channels are established to the earbuds.</p>
</li>
<li><p>The ISO data path is set up.</p>
</li>
<li><p>PCM audio flows from the HAL to the LC3 encoder, which produces compressed frames</p>
</li>
<li><p>The compressed frames are sent as ISO SDUs over HCI to the controller</p>
</li>
<li><p>The controller transmits the frames over the air on the scheduled CIS intervals</p>
</li>
<li><p>The earbuds receive, decode, and render the audio at the agreed presentation delay.</p>
</li>
</ol>
<h2 id="heading-14-putting-it-all-together-a-day-in-the-life-of-an-le-audio-packet">14. Putting It All Together: A Day in the Life of an LE Audio Packet</h2>
<p>Let's follow a single audio packet from your music app to your earbud:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68a51326db25241b7cb0c047/e4e634cc-04db-4413-a197-ddbd1169c16a.png" alt="Diagram following a single audio packet through every stage of the LE Audio pipeline" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>The above diagram follows a single audio packet through every stage of the LE Audio pipeline.</p>
<p>Starting at the top: the music app generates PCM audio, which passes through Android's AudioFlinger to the Bluetooth Audio HAL. The HAL feeds 10ms of PCM samples (480 samples at 48 kHz) to the LC3 encoder, which compresses them into a ~120-byte frame.</p>
<p>This frame is wrapped in an ISO SDU with a timestamp and sequence number, then passed over HCI to the Bluetooth controller. The controller segments the SDU into link-layer PDUs, schedules them on the next CIS event, and transmits them over the air using the negotiated PHY (for example, 2M PHY).</p>
<p>On the earbud side, the controller receives the PDUs, reassembles the ISO SDU, and passes the LC3 frame to the earbud's decoder. The decoder reconstructs 480 PCM samples, which are buffered until the presentation delay timestamp is reached, then rendered to the speaker driver.</p>
<p><strong>Total latency</strong>: ~40ms from phone to earbud (with 10ms frame + transport + presentation delay). Compare this to Classic Bluetooth A2DP which typically runs at 100-200ms!</p>
<h3 id="heading-the-presentation-delay-the-synchronization-secret">The Presentation Delay: The Synchronization Secret</h3>
<p>The <strong>presentation delay</strong> is a crucial LE Audio concept. It's a fixed delay that both sides agree upon during stream setup. All audio must be rendered (played) at exactly:</p>
<pre><code class="language-plaintext">rendering_time = reference_anchor_point + presentation_delay
</code></pre>
<p>This ensures:</p>
<ul>
<li><p>Left and right earbuds play audio at the exact same instant</p>
</li>
<li><p>Even if transport latency varies between the two CIS channels</p>
</li>
<li><p>The presentation delay provides a "buffer" for the receiver to absorb jitter</p>
</li>
</ul>
<p>Think of it like a choir director: "Everyone sing at the count of 3. Not before, not after. Exactly at 3."</p>
<h2 id="heading-15-wrapping-up">15. Wrapping Up</h2>
<p>Bluetooth LE Audio is the most significant upgrade to Bluetooth audio since... well, since Bluetooth audio was invented. Let's recap:</p>
<h3 id="heading-what-it-solves">What It Solves</h3>
<ul>
<li><p><strong>Better codec</strong> (LC3) — equivalent quality at half the bitrate, or better quality at the same bitrate</p>
</li>
<li><p><strong>Multi-stream</strong> — no more relay earbud architecture, balanced battery life</p>
</li>
<li><p><strong>Broadcast audio</strong> (Auracast) — one-to-many streaming, opening up entirely new use cases</p>
</li>
<li><p><strong>Hearing aid support</strong> (HAP) — finally a standard, interoperable solution</p>
</li>
<li><p><strong>Unified audio</strong> (BAP) — one profile for both music and calls, no more A2DP/HFP switching</p>
</li>
</ul>
<h3 id="heading-the-aosp-stack">The AOSP Stack</h3>
<ul>
<li><p><strong>Framework layer</strong>: <code>BluetoothLeAudio</code>, <code>BluetoothLeBroadcast</code> APIs</p>
</li>
<li><p><strong>Service layer</strong>: <code>LeAudioService</code> orchestrates everything</p>
</li>
<li><p><strong>Native layer</strong>: C++ <code>le_audio_client_impl</code> handles GATT, ASE state machines, codec negotiation</p>
</li>
<li><p><strong>Controller layer</strong>: CIS/BIS isochronous channels managed via HCI</p>
</li>
</ul>
<h3 id="heading-whats-next">What's Next?</h3>
<p>LE Audio is still maturing. Key areas of development:</p>
<ul>
<li><p><strong>Better interoperability</strong> across devices from different manufacturers</p>
</li>
<li><p><strong>Auracast infrastructure</strong> — venues need to install broadcast transmitters</p>
</li>
<li><p><strong>Dual-mode support</strong> — many devices will support both Classic and LE Audio during the transition period</p>
</li>
<li><p><strong>Higher quality</strong> — as Bluetooth bandwidth improves, LC3 can scale to even higher bitrates</p>
</li>
<li><p><strong>Gaming</strong> — ultra-low-latency configurations (7.5ms frames, minimal presentation delay)</p>
</li>
</ul>
<p>The transition from Classic Audio to LE Audio won't happen overnight. It's more like the transition from IPv4 to IPv6 – gradual, sometimes painful, but ultimately necessary. The good news is that both can coexist, and the AOSP implementation supports fallback to Classic Audio for devices that don't support LE Audio.</p>
<p>So the next time you connect your earbuds and marvel at the audio quality (or lack thereof), you'll know exactly which parts of this massive protocol stack are working (or failing) to get those sound waves from your phone to your ears.</p>
<p>Happy coding, and may your packets always be isochronous!</p>
<h3 id="heading-references">References</h3>
<ol>
<li><p>Bluetooth SIG — <a href="https://www.bluetooth.com/learn-about-bluetooth/feature-enhancements/le-audio/le-audio-specifications/">LE Audio Specifications</a></p>
</li>
<li><p>Bluetooth SIG — <a href="https://www.bluetooth.com/blog/a-technical-overview-of-lc3/">A Technical Overview of LC3</a></p>
</li>
<li><p>AOSP Bluetooth Module — <a href="https://android.googlesource.com/platform/packages/modules/Bluetooth/">packages/modules/Bluetooth</a></p>
</li>
<li><p>Zephyr Project — <a href="https://docs.zephyrproject.org/latest/connectivity/bluetooth/api/audio/bluetooth-le-audio-arch.html">LE Audio Stack Documentation</a></p>
</li>
<li><p>Fraunhofer IIS — <a href="https://www.iis.fraunhofer.de/en/ff/amm/communication/lc3.html">LC3 Codec</a></p>
</li>
</ol>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How AOSP 16 Bluetooth Scanner Works: The Ultimate Guide ]]>
                </title>
                <description>
                    <![CDATA[ Ah, Bluetooth. The technology we all love to hate. It's like that one friend who's always just about to connect, but then... doesn't. For years, Android developers have been locked in a dramatic, often tragic, romance with Bluetooth. We've wrestled w... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-aosp-16-bluetooth-scanner-works-the-ultimate-guide/</link>
                <guid isPermaLink="false">6983ae630a7fef9ac2d90313</guid>
                
                    <category>
                        <![CDATA[ ble ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Bluetooth Low Energy ]]>
                    </category>
                
                    <category>
                        <![CDATA[ bluetooth ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ scanner ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nikheel Vishwas Savant ]]>
                </dc:creator>
                <pubDate>Wed, 04 Feb 2026 20:38:59 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770234863523/44a1690e-ab8a-4f6b-a12b-2c2636947d8c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Ah, Bluetooth. The technology we all love to hate. It's like that one friend who's always just about to connect, but then... doesn't.</p>
<p>For years, Android developers have been locked in a dramatic, often tragic, romance with Bluetooth. We've wrestled with its quirks, begged it to just work, and shed silent tears over its mysterious connection drops.</p>
<p>But what if I told you that things are about to get better? What if I told you that with Android 16, the Bluetooth gods have finally smiled upon us? It's not a dream, my friends. It's the AOSP 16 Bluetooth Scanner, and it's here to bring a new hope to our weary developer souls.</p>
<p>In this handbook, we're going on a journey. A journey into the heart of AOSP 16's new Bluetooth features. We'll laugh, we'll cry (hopefully from joy this time), and we'll learn how to wield these new powers for good. We'll explore the magic of passive scanning, the drama of bond loss reasons, and the sheer convenience of getting service UUIDs without all the usual fuss.</p>
<p>By the end of this epic saga, you'll be able to:</p>
<ul>
<li><p>Build a Bluetooth scanner that's so efficient, it's practically psychic.</p>
</li>
<li><p>Debug connection issues like a seasoned detective.</p>
</li>
<li><p>Impress your friends and colleagues with your newfound Bluetooth mastery.</p>
</li>
</ul>
<h3 id="heading-prerequisites"><strong>Prerequisites:</strong></h3>
<p>Before we dive in, it's a good idea to have a basic understanding of Android development and Kotlin. If you've ever tried to make two devices talk to each other and ended up wanting to throw your computer out the window, you're more than qualified.</p>
<p>So grab your favorite beverage, put on your coding cape, and let's get ready for the Bluetooth awakening!</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-a-brief-history-of-bluetooth-in-android">A Brief History of Bluetooth in Android</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-whats-new-in-aosp-16-the-three-musketeers">What's New in AOSP 16: The Three Musketeers</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-deep-dive-1-passive-scanning">Deep Dive #1: Passive Scanning</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-the-bluetoothlescanner">Understanding the BluetoothLeScanner</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-hands-on-building-your-first-passive-scanner">Hands-On: Building Your First Passive Scanner</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-deep-dive-2-bluetooth-bond-loss-reasons">Deep Dive #2: Bluetooth Bond Loss Reasons</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-deep-dive-3-service-uuids-from-advertisements">Deep Dive #3: Service UUIDs from Advertisements</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-advanced-topics-leveling-up-your-scanning-game">Advanced Topics: Leveling Up Your Scanning Game</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-real-world-use-cases-where-the-bluetooth-hits-the-road">Real-World Use Cases: Where the Bluetooth Hits the Road</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-api-version-checking-how-to-not-crash-your-app">API Version Checking: How to Not Crash Your App</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-testing-and-debugging-the-fun-part-said-no-one-ever">Testing and Debugging: The Fun Part (Said No One Ever)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-performance-and-best-practices-how-to-be-a-good-bluetooth-citizen">Performance and Best Practices: How to Be a Good Bluetooth Citizen</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion-the-future-is-passive-and-thats-okay">Conclusion: The Future is Passive (and That's Okay)</a></p>
</li>
</ol>
<h2 id="heading-a-brief-history-of-bluetooth-or-how-we-learned-to-stop-worrying-and-love-the-radio-waves">A Brief History of Bluetooth (Or: How We Learned to Stop Worrying and Love the Radio Waves)</h2>
<h3 id="heading-the-dark-ages-classic-bluetooth">The Dark Ages: Classic Bluetooth</h3>
<p>In the beginning, there was Classic Bluetooth. It was the digital equivalent of a loud, boisterous party guest. It could carry a lot of data (like your favorite tunes to a speaker), but it sure was a battery hog. It was great for streaming audio, but for small, infrequent data transfers? It was like using a fire hose to water a houseplant. Overkill, and frankly, a little messy.</p>
<p>Developers in this era spent their days wrestling with BluetoothAdapter, BluetoothDevice, and the dreaded BluetoothSocket. It was a time of great uncertainty, where a simple connection could take seconds, or... well, let's just say you could go make a cup of coffee. And the battery drain? Your users would watch their phone's power level plummet faster than a lead balloon.</p>
<h3 id="heading-the-renaissance-enter-bluetooth-low-energy-ble">The Renaissance: Enter Bluetooth Low Energy (BLE)</h3>
<p>Then, with Android 4.3, a new hero emerged: Bluetooth Low Energy, or BLE. This wasn't your dad’s Bluetooth. BLE was sleek, efficient, and mysterious. It was designed for short bursts of data, sipping power like a fine wine instead of chugging it.</p>
<p>BLE was the cool kid on the block. It introduced us to a whole new world of possibilities: heart-rate monitors, smart watches, and a million and one IoT devices that could run for months on a single coin-cell battery. It was a game-changer.</p>
<p>But with great power came... great complexity. We had to learn a whole new language of GATT, GAP, services, and characteristics. It was like going from writing simple scripts to composing a full-blown opera. The potential was huge, but the learning curve was steep.</p>
<h3 id="heading-the-problem-child-scanning">The Problem Child: Scanning</h3>
<p>And then there was scanning. The act of finding these new, power-sipping devices. In the early days of BLE, scanning was still a bit of a wild west. It was an active, noisy process. Your phone would shout into the void, "IS ANYONE OUT THERE?", and then listen for replies. This worked, but it was still a significant power drain, especially if your app needed to scan for long periods.</p>
<p>It was the classic developer dilemma: you need to find devices, but you don't want to be the reason your user's phone is dead by lunchtime. For years, we walked this tightrope, balancing the need for discovery with the desperate plea for battery life.</p>
<p>This is the world that AOSP 16 was born into. A world crying out for a better way to scan. A world ready for a hero. And that hero, my friends, is passive scanning. But more on that in a bit...</p>
<h2 id="heading-whats-new-in-aosp-16-spoiler-its-actually-cool">What's New in AOSP 16? (Spoiler: It's Actually Cool)</h2>
<p>Alright, let's get to the good stuff. What shiny new toys did the Android team give us in AOSP 16? It turns out, quite a few! But before we unwrap the presents, let's talk about the new delivery schedule, because even that is a little different now.</p>
<h3 id="heading-a-tale-of-two-releases">A Tale of Two Releases</h3>
<p>In a shocking plot twist, Android decided to grace us with two major API releases in 2025. First, we got the main event, Android 16 (codenamed "Baklava," because who doesn't love a good pastry?), which landed in Q2. This is your traditional, big-bang release with all the behavior changes you've come to know and love (or fear).</p>
<p>But then, in Q4, we get a surprise second act: a minor release, which is where our new Bluetooth goodies made their grand entrance. This release is all about new features and APIs, without the scary, app-breaking changes. It's like getting a free dessert after you've already paid the bill.</p>
<h3 id="heading-the-three-musketeers-of-bluetooth">The Three Musketeers of Bluetooth</h3>
<p>So, what did this Q4 release bring to the Bluetooth party? I'm glad you asked. It brought three new heroes, ready to save us from our Bluetooth woes. I call them... The Three Musketeers.</p>
<table><tbody><tr><td><p><strong>Feature</strong></p></td><td><p><strong>The Gist</strong></p></td><td><p><strong>Why You Should Care</strong></p></td></tr><tr><td><p><strong>Passive Scanning</strong></p></td><td><p>The ability to listen for Bluetooth devices without shouting at them.</p></td><td><p>Your app can now be a silent, battery-saving ninja.</p></td></tr><tr><td><p><strong>Bond Loss Reasons</strong></p></td><td><p>Finally, some closure on why your Bluetooth connections break up.</p></td><td><p>You can stop playing the guessing game and actually debug connection issues.</p></td></tr><tr><td><p><strong>Service UUID from Ads</strong></p></td><td><p>Grab a device's vital stats directly from its advertisement.</p></td><td><p>It's like speed dating for Bluetooth devices. Faster, more efficient connections.</p></td></tr></tbody></table>

<p>These aren't just minor tweaks, folks. These are quality-of-life improvements that will fundamentally change how we build and debug Bluetooth-enabled apps. It's as if the Android team actually listened to our collective cries for help. (I know, I'm shocked too.)</p>
<p>In the next few sections, we're going to get up close and personal with each of these new features. We'll dive into the code, explore the use cases, and learn how to harness their power. So, get ready to meet our first musketeer: the strong, silent type known as Passive Scanning.</p>
<h2 id="heading-deep-dive-1-passive-scanning">Deep Dive #1: Passive Scanning</h2>
<p>Imagine you're in a library. You're looking for a friend, but you don't know where they are. You have two options:</p>
<ul>
<li><p><strong>Active Scanning:</strong> You stand in the middle of the library and shout, "HEY, STEVE! ARE YOU HERE?" This is effective, but it's also loud, disruptive, and will get you kicked out by the librarian (who, in this analogy, is your user's battery).</p>
</li>
<li><p><strong>Passive Scanning:</strong> You quietly walk around the library, listening for your friend's distinctive, wheezing laugh. You don't say a word. You just listen. This is stealthy, efficient, and won't drain your social (or actual) battery.</p>
</li>
</ul>
<p>For years, Android's Bluetooth scanning has been the guy shouting in the library. But with AOSP 16, we can finally be the quiet listener. This is the magic of passive scanning.</p>
<h3 id="heading-active-vs-passive-the-technical-showdown">Active vs. Passive: The Technical Showdown</h3>
<p>In the world of BLE, devices send out little packets of information called "advertisements." It's their way of saying, "Hey, I'm here, and this is what I do!"</p>
<ul>
<li><p><strong>Active Scanning:</strong> When your phone performs an active scan, it hears an advertisement and then sends back a SCAN_REQ (Scan Request). It's basically saying, "Tell me more!" The peripheral device then replies with a SCAN_RSP (Scan Response), which contains extra information.</p>
</li>
<li><p><strong>Passive Scanning:</strong> With passive scanning, your phone hears the advertisement... and that's it. It doesn't send anything back. It just takes note of the initial advertisement and moves on. It's a one-way conversation.</p>
</li>
</ul>
<h3 id="heading-why-go-passive-the-power-of-silence">Why Go Passive? The Power of Silence</h3>
<p>So, why is this such a big deal? Two words: power consumption. Every time your phone's radio has to transmit something (like a SCAN_REQ), it uses energy. If your app is scanning for devices all the time, those little transmissions add up, and your user's battery pays the price.</p>
<p>By switching to passive scanning, you're telling the radio to just listen. No talking, just listening. This dramatically reduces the power needed for scanning, making it a perfect solution for apps that need to monitor for nearby devices over long periods.</p>
<h3 id="heading-the-code-how-to-become-a-bluetooth-ninja">The Code: How to Become a Bluetooth Ninja</h3>
<p>So, how do we implement this newfound stealth mode? It's surprisingly simple. It all comes down to the ScanSettings you use when you start your scan.</p>
<p>Previously, you might have done something like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> settings = ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
    .build()
</code></pre>
<p>Now, with AOSP 16, we have a new option. To enable passive scanning, you simply set the scan type:</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// This is the magic line!</span>
.setScanMode(ScanSettings.SCAN_TYPE_PASSIVE)
</code></pre>
<p>Wait, that can't be right. The documentation says SCAN_TYPE_PASSIVE is a scan type, not a scan mode. And you're right! My apologies, I got a little too excited. The correct way to do this is by setting the scan mode to passive. Let's try that again.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> settings = ScanSettings.Builder()
    <span class="hljs-comment">// The actual magic line!</span>
    .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC) <span class="hljs-comment">// This is the closest to passive</span>
    .build()
</code></pre>
<p>Hold on, that's not quite right either. It seems I've gotten my wires crossed. Let's consult the official scrolls... Ah, here it is! The ScanSettings.Builder has a new method in Android 16 QPR2. It's not setScanMode, it's a whole new setting.</p>
<p>Let's get this right once and for all. Here is the correct way to enable passive scanning:</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// Available in Android 16 QPR2 and later</span>
<span class="hljs-keyword">val</span> settings = ScanSettings.Builder()
    <span class="hljs-comment">// This is the REAL magic line, I promise!</span>
    .setScanType(ScanSettings.SCAN_TYPE_PASSIVE) 
    .build()
</code></pre>
<p>And there you have it. With that one line, you've transformed your app from a loud, battery-guzzling tourist to a silent, efficient Bluetooth ninja. Your users' batteries will thank you.</p>
<p>Of course, there's a trade-off. Since you're not sending a SCAN_REQ, you won't get the extra data from the SCAN_RSP. But for many use cases, the initial advertisement is all you need. And the power savings are more than worth it.</p>
<p>Now that we've mastered the art of silent scanning, let's move on to the next piece of the puzzle: understanding the BluetoothLeScanner itself.</p>
<h2 id="heading-understanding-bluetoothlescanner-the-star-of-our-show">Understanding BluetoothLeScanner (The Star of Our Show)</h2>
<p>Before we can truly master the art of Bluetooth scanning, we must first understand our primary weapon: the BluetoothLeScanner. Think of it as the PKE Meter from Ghostbusters. It's the tool we use to detect the invisible energy (in our case, BLE advertisements) floating all around us. But how does this ghost-hunting gadget actually work?</p>
<h3 id="heading-the-architecture-a-peek-behind-the-curtain">The Architecture: A Peek Behind the Curtain</h3>
<p>At a high level, the process is pretty straightforward. Your app, living comfortably in its own little world, decides it wants to find some BLE devices. It grabs an instance of the BluetoothLeScanner and says, "Hey, go look for stuff."</p>
<p>Under the hood, a lot is happening. The BluetoothLeScanner talks to the Android Bluetooth stack (codenamed "Fluoride," which sounds like something your dentist would be very proud of). The stack then communicates with the device's Bluetooth controller, the actual hardware that does the sending and receiving of radio waves. It's a classic case of "it's more complicated than it looks."</p>
<h3 id="heading-the-alphabet-soup-gatt-gap-and-friends">The Alphabet Soup: GATT, GAP, and Friends</h3>
<p>When you venture into the world of BLE, you'll quickly run into a whole bunch of acronyms. Don't panic! They're not as scary as they look. The two most important ones to understand are GAP and GATT.</p>
<ul>
<li><p><strong>GAP (Generic Access Profile):</strong> This is all about how devices discover and connect to each other. Think of GAP as the bouncer at a nightclub. It decides who gets to talk to whom. It manages advertising (the device shouting "I'm here!") and scanning (your app listening for those shouts). Our BluetoothLeScanner is a key player in the GAP-verse.</p>
</li>
<li><p><strong>GATT (Generic Attribute Profile):</strong> Once two devices are connected, GATT takes over. It defines how they exchange data. Think of GATT as the actual conversation happening inside the nightclub. It's all about Services, Characteristics, and Descriptors. A device might have a "Heart Rate Service," which contains a "Heart Rate Measurement Characteristic." Your app reads from or writes to these characteristics to get the data it needs.</p>
</li>
</ul>
<p>For the purpose of scanning, we're mostly living in the world of GAP. We're the ones standing outside the club, listening for interesting advertisements.</p>
<h3 id="heading-the-scanning-lifecycle-a-dramatic-play-in-three-acts">The Scanning Lifecycle: A Dramatic Play in Three Acts</h3>
<p>The life of a Bluetooth scan is a simple, yet elegant, drama.</p>
<ul>
<li><p><strong>Act I:</strong> The Preparation. Your app decides it's time to scan. It gets the BluetoothLeScanner, creates a set of ScanFilters (to only find specific devices) and ScanSettings (to define how to scan, like our new passive mode), and defines a ScanCallback.</p>
</li>
<li><p><strong>Act II:</strong> The Scan. Your app calls startScan(). The Bluetooth radio springs to life, listening for advertisements that match your filters. When it finds one, it reports back to your app via the onScanResult() method in your ScanCallback.</p>
</li>
<li><p><strong>Act III:</strong> The End. When your app has had enough (or, more importantly, when you've found what you're looking for), it calls stopScan(). The radio powers down, and all is quiet once more. It's crucial to always stop your scan when you're done. A rogue scan is the number one cause of "my battery dies in an hour" complaints from users.</p>
</li>
</ul>
<p>And that's the BluetoothLeScanner in a nutshell. It's our gateway to the world of BLE discovery. It's powerful, it's complex, but as we're learning, it's getting smarter and more efficient with every new Android release. Now that we know our tool, let's get our hands dirty and build our first passive scanner!</p>
<h2 id="heading-hands-on-building-your-first-passive-scanner">Hands-On: Building Your First Passive Scanner</h2>
<p>Theory is great, but let's be honest, we're developers. We learn by doing (or by copying pasting from Stack Overflow). It's time to roll up our sleeves, fire up Android Studio, and build something. We're going to create a simple app that uses our newfound passive scanning powers to find nearby BLE devices.</p>
<h3 id="heading-step-1-the-permission-inquisition">Step 1: The Permission Inquisition</h3>
<p>Before we write a single line of Kotlin, we must appease the Android permission gods. This is a sacred and often frustrating ritual. For Bluetooth scanning, the rules have changed a bit over the years.</p>
<p>First, open your <code>AndroidManifest.xml</code> and add the following:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.BLUETOOTH"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.BLUETOOTH_ADMIN"</span> /&gt;</span>

<span class="hljs-comment">&lt;!-- For Android 12 (API 31) and above --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.BLUETOOTH_SCAN"</span> /&gt;</span>

<span class="hljs-comment">&lt;!-- For older versions, you needed location permissions --&gt;</span>
<span class="hljs-comment">&lt;!-- You might still need this if you support older devices --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.ACCESS_FINE_LOCATION"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.ACCESS_COARSE_LOCATION"</span> /&gt;</span>
</code></pre>
<p>Looking at the permissions we've declared above, you can see the evolution of Android's Bluetooth permission model playing out in real-time.</p>
<p>The first two permissions, <code>BLUETOOTH</code> and <code>BLUETOOTH_ADMIN</code>, are the old guard. They've been around since the early days of Android and provide basic Bluetooth functionality and the ability to discover devices. Then we have <code>BLUETOOTH_SCAN</code>, which was introduced in Android 12 (API 31) and represents a major shift in how Google thinks about privacy.</p>
<p>Yes, you're seeing that right. In the good old days (before Android 12), Google decided that finding a Bluetooth device was basically the same as knowing your user's exact location. It kind of made sense: after all, if you can see which Bluetooth beacons are nearby, you can triangulate your position. But it was also a bit creepy to ask for location just to find a pair of headphones. This led to the awkward situation where users would see a simple Bluetooth scanner app asking for their precise location and understandably get suspicious.</p>
<p>Thankfully, with Android 12, they introduced the <code>BLUETOOTH_SCAN</code> permission, which is much more sensible. This permission finally allows apps to scan for Bluetooth devices without needing to ask for location access, which makes a lot more sense from a user perspective. You'll still need to request this permission at runtime, but at least you don't have to explain to your users why your simple gadget-finder app wants to know where they live.</p>
<p>However, notice those last two permissions for location access. Those are the remnants of the old system. If you're building an app that needs to support older devices running Android 11 or below, you'll need to keep these location permissions in your manifest for backwards compatibility. On modern devices, the <code>BLUETOOTH_SCAN</code> permission alone will do the job.</p>
<h3 id="heading-step-2-the-code-awakens">Step 2: The Code Awakens</h3>
<p>Alright, let's get to the fun part. Here's a breakdown of how to implement the passive scanner in your Activity or Fragment.</p>
<h4 id="heading-get-the-scanner">Get the Scanner</h4>
<p>First, we need to get an instance of the BluetoothLeScanner.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> bluetoothAdapter: BluetoothAdapter? <span class="hljs-keyword">by</span> lazy {
    <span class="hljs-keyword">val</span> bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) <span class="hljs-keyword">as</span> BluetoothManager
    bluetoothManager.adapter
}

<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> bleScanner: BluetoothLeScanner? <span class="hljs-keyword">by</span> lazy {
    bluetoothAdapter?.bluetoothLeScanner
}
</code></pre>
<p>Let's break down what's happening in the code above. We're using Kotlin's <code>lazy</code> delegation, which is a fancy way of saying "don't create this object until I actually need it." This is a good practice because getting the Bluetooth adapter involves system calls, and there's no point in doing that work if we never actually use it.</p>
<p>First, we grab the <code>BluetoothManager</code> from the system services. Think of the <code>BluetoothManager</code> as the gatekeeper to all things Bluetooth on your device. From this manager, we get the <code>BluetoothAdapter</code>, which represents your device's physical Bluetooth hardware. Notice that we're declaring it as nullable (<code>BluetoothAdapter?</code>) because, believe it or not, not every Android device has Bluetooth. Some tablets or obscure devices might not have the hardware, so we need to be prepared for that possibility.</p>
<p>Once we have the adapter, we can ask it for the <code>BluetoothLeScanner</code>. This is the actual object we'll use to perform our scans. Again, we're using the safe call operator (<code>?.</code>) because if the adapter is null (no Bluetooth hardware), we definitely can't get a scanner from it. This defensive programming might seem paranoid, but it's what separates apps that crash mysteriously from apps that gracefully handle edge cases.</p>
<h4 id="heading-define-the-callback">Define the Callback</h4>
<p>This is where the magic happens. The ScanCallback is an object that will listen for scan results. We need to override two methods: onScanResult and onScanFailed.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> scanCallback = <span class="hljs-keyword">object</span> : ScanCallback() {
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onScanResult</span><span class="hljs-params">(callbackType: <span class="hljs-type">Int</span>, result: <span class="hljs-type">ScanResult</span>)</span></span> {
        <span class="hljs-comment">// We found a device! </span>
        <span class="hljs-comment">// The 'result' object contains the device, RSSI, and advertisement data.</span>
        Log.d(<span class="hljs-string">"BleScanner"</span>, <span class="hljs-string">"Found device: <span class="hljs-subst">${result.device.address}</span>, RSSI: <span class="hljs-subst">${result.rssi}</span>"</span>)
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onScanFailed</span><span class="hljs-params">(errorCode: <span class="hljs-type">Int</span>)</span></span> {
        <span class="hljs-comment">// This is the universe's way of telling you to take a break.</span>
        <span class="hljs-comment">// Or that something went horribly wrong.</span>
        Log.e(<span class="hljs-string">"BleScanner"</span>, <span class="hljs-string">"Scan failed with error code: <span class="hljs-variable">$errorCode</span>"</span>)
    }
}
</code></pre>
<p>The <code>ScanCallback</code> we've defined above is your app's ears in the Bluetooth world. When the scanner finds a device, it doesn't just store the information somewhere, it actively calls back to your app through this callback object. This is classic event-driven programming, and it's how Android keeps your app responsive without blocking the main thread.</p>
<p>The <code>onScanResult</code> method is called every time the scanner discovers a device that matches your filters (or any device if you're not using filters). The <code>result</code> parameter is a treasure trove of information. It contains the <code>BluetoothDevice</code> object (which has the device's MAC address and name), the RSSI value (Received Signal Strength Indicator – basically how close the device is, with higher numbers meaning closer), and the raw advertisement data that the device is broadcasting.</p>
<p>In our simple example above, we're just logging the MAC address and RSSI, but in a real app, you'd probably want to update your UI, add the device to a list, or trigger a connection.</p>
<p>The <code>callbackType</code> parameter tells you <em>why</em> this callback was triggered. It could be <code>CALLBACK_TYPE_ALL_MATCHES</code> (the default, meaning "here's every device we found"), <code>CALLBACK_TYPE_FIRST_MATCH</code> (the first time we saw this device), or <code>CALLBACK_TYPE_MATCH_LOST</code> (we haven't seen this device in a while, so it probably left). We'll dive deeper into these types in the advanced section.</p>
<p>Then there's <code>onScanFailed</code>, the method we all hope never gets called but that we absolutely need to handle. This is invoked when something goes catastrophically wrong with the scan. Maybe the Bluetooth adapter got turned off mid-scan, maybe your app doesn't have the right permissions, or maybe the Bluetooth controller just had a bad day. The <code>errorCode</code> will give you a hint about what went wrong, and you should always log this and handle it gracefully – perhaps by showing a message to the user or attempting to restart the scan after a delay.</p>
<h4 id="heading-configure-the-scan">Configure the Scan</h4>
<p>Now, we create our ScanSettings. This is where we tell Android that we want to be a passive, battery-saving ninja.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> scanSettings = ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) <span class="hljs-comment">// Let's be nice to the battery</span>
    .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
    .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
    .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) <span class="hljs-comment">// Report each ad once</span>
    .setReportDelay(<span class="hljs-number">0L</span>) <span class="hljs-comment">// Report immediately</span>
    <span class="hljs-comment">// And here's the star of the show!</span>
    .setScanType(ScanSettings.SCAN_TYPE_PASSIVE)
    .build()
</code></pre>
<p>The <code>ScanSettings</code> object we're building above is like a detailed instruction manual for the Bluetooth scanner. Each method call fine-tunes exactly how the scan should behave, and getting these settings right is the difference between a battery-friendly app and one that gets uninstalled within hours.</p>
<p>Let's walk through each setting. First, <code>setScanMode(SCAN_MODE_LOW_POWER)</code> tells the scanner to use a low-power scanning mode, which means it will scan in intervals rather than continuously. This is perfect for most use cases where you don't need instant results and want to preserve battery life. The scanner will wake up, scan for a bit, sleep, and repeat. It's the Bluetooth equivalent of taking power naps.</p>
<p>Next, <code>setCallbackType(CALLBACK_TYPE_ALL_MATCHES)</code> means we want to be notified every time the scanner finds a matching device. This is the default behavior and is what you'll use most of the time. As we mentioned earlier, you can also use <code>CALLBACK_TYPE_FIRST_MATCH</code> or <code>CALLBACK_TYPE_MATCH_LOST</code> for more sophisticated presence detection.</p>
<p>The <code>setMatchMode(MATCH_MODE_AGGRESSIVE)</code> setting controls how aggressively the hardware should try to match devices against your filters. <code>MATCH_MODE_AGGRESSIVE</code> means "report matches quickly, even if you're not 100% certain," while <code>MATCH_MODE_STICKY</code> means "wait until you're really sure before reporting." Aggressive mode gives you faster results but might occasionally give you false positives.</p>
<p>Then we have <code>setNumOfMatches(MATCH_NUM_ONE_ADVERTISEMENT)</code>, which tells the scanner to report a device after seeing just one advertisement from it. The alternative is <code>MATCH_NUM_FEW_ADVERTISEMENT</code>, which waits for multiple advertisements before reporting. Using one advertisement gives you faster discovery, while waiting for a few reduces false positives from devices that are just passing by.</p>
<p>The <code>setReportDelay(0L)</code> setting is crucial. A delay of <code>0</code> means "report results immediately." If you set this to, say, <code>5000</code> milliseconds, the scanner would batch up results and deliver them every 5 seconds. Batching is great for background scanning (as we discussed in the advanced section), but for foreground scanning where the user is actively waiting, immediate reporting is what you want.</p>
<p>And finally, the star of our show: <code>setScanType(SCAN_TYPE_PASSIVE)</code>. This is the new API from Android 16 QPR2 that transforms our scanner into a silent listener. Instead of actively sending scan requests to every device it hears, it just listens to the advertisements floating through the air. This single setting can dramatically reduce your app's battery consumption during scanning. It's the feature we've been waiting for, and it's glorious.</p>
<h4 id="heading-start-and-stop-the-scan">Start and Stop the Scan</h4>
<p>Finally, we need functions to start and stop our scan. Remember: always stop your scan! A forgotten scan is a battery-killing monster.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">startBleScan</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">// Don't forget to request permissions first!</span>
    <span class="hljs-keyword">if</span> (bleScanner != <span class="hljs-literal">null</span>) {
        <span class="hljs-comment">// You can add ScanFilters here to search for specific devices</span>
        <span class="hljs-keyword">val</span> scanFilters: List&lt;ScanFilter&gt; = listOf() 
        bleScanner.startScan(scanFilters, scanSettings, scanCallback)
        Log.d(<span class="hljs-string">"BleScanner"</span>, <span class="hljs-string">"Scan started."</span>)
    } <span class="hljs-keyword">else</span> {
        Log.e(<span class="hljs-string">"BleScanner"</span>, <span class="hljs-string">"Bluetooth is not available."</span>)
    }
}

<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">stopBleScan</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">if</span> (bleScanner != <span class="hljs-literal">null</span>) {
        bleScanner.stopScan(scanCallback)
        Log.d(<span class="hljs-string">"BleScanner"</span>, <span class="hljs-string">"Scan stopped."</span>)
    }
}
</code></pre>
<p>These two functions above are the on/off switches for your Bluetooth scanner, and they're deceptively simple for how important they are. Let's break down what's happening in each one.</p>
<p>In <code>startBleScan()</code>, we first check if the <code>bleScanner</code> is not null. This is our safety net: if the device doesn't have Bluetooth hardware or if Bluetooth is disabled, the scanner will be null, and we don't want to crash by trying to call methods on a null object. If the scanner exists, we call <code>startScan()</code> with three parameters: a list of <code>ScanFilter</code> objects, our carefully crafted <code>ScanSettings</code>, and the <code>ScanCallback</code> we defined earlier.</p>
<p>The <code>scanFilters</code> list is currently empty in our example, which means "find all BLE devices." In a real-world app, you'd typically add filters here to narrow down your search.</p>
<p>For instance, if you're building an app that only works with heart rate monitors, you'd create a filter that only matches devices advertising the Heart Rate Service UUID. This is crucial for both performance and battery life: why wake up your app for every random Bluetooth toothbrush when you only care about fitness trackers?</p>
<p>The <code>startScan()</code> method kicks off the scanning process. From this point on, the Bluetooth radio is actively (or in our case, passively) listening for advertisements, and your <code>scanCallback</code> will start receiving results. This is an asynchronous operation, meaning your code doesn't block here waiting for results – rather, it continues executing, and the results come in through the callback whenever they're available.</p>
<p>Now let's talk about <code>stopBleScan()</code>, which might be the most important function you write. When you call <code>stopScan()</code> with your callback, you're telling the Bluetooth radio, "Okay, we're done here, you can go back to sleep." This immediately stops the scanning process and releases the resources.</p>
<p>The critical thing to understand is that if you don't call this, the scan will continue running indefinitely, draining your user's battery like a vampire at an all-you-can-eat blood bank. This is why we emphasize it so much: a forgotten <code>stopScan()</code> call is one of the most common causes of battery drain complaints in Bluetooth apps.</p>
<p>Notice that we're passing the same <code>scanCallback</code> object to <code>stopScan()</code> that we used in <code>startScan()</code>. This is how Android knows which scan to stop – you might theoretically have multiple scans running with different callbacks (though that's rarely a good idea). Always make sure you're stopping the same scan you started by using the same callback reference.</p>
<h3 id="heading-putting-it-all-together">Putting It All Together</h3>
<p>Here's a complete example you can drop into an Activity. Just remember to handle the runtime permissions!</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// In your Activity class</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> : <span class="hljs-type">AppCompatActivity</span></span>() {

    <span class="hljs-comment">// ... (lazy properties for adapter and scanner from above)</span>

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(savedInstanceState: <span class="hljs-type">Bundle</span>?)</span></span> {
        <span class="hljs-keyword">super</span>.onCreate(savedInstanceState)
        <span class="hljs-comment">// ... your UI setup ...</span>

        <span class="hljs-comment">// Example: Start scan on button click</span>
        <span class="hljs-keyword">val</span> startButton = findViewById&lt;Button&gt;(R.id.startButton)
        startButton.setOnClickListener {
            <span class="hljs-comment">// You MUST request permissions before calling this!</span>
            startBleScan()
        }

        <span class="hljs-comment">// Example: Stop scan on another button click</span>
        <span class="hljs-keyword">val</span> stopButton = findViewById&lt;Button&gt;(R.id.stopButton)
        stopButton.setOnClickListener {
            stopBleScan()
        }
    }

    <span class="hljs-comment">// ... (scanCallback, startBleScan, stopBleScan functions from above)</span>

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onPause</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">super</span>.onPause()
        <span class="hljs-comment">// Always stop scanning when the activity is not visible.</span>
        stopBleScan()
    }
}
</code></pre>
<p>The complete example above shows how all the pieces fit together in a real Activity. This is a minimal but functional Bluetooth scanner that you can actually run. Let's highlight a few important patterns we're using here.</p>
<p>First, notice how we're tying the scan lifecycle to user actions through button clicks. This is a common pattern: the user explicitly starts and stops the scan, giving them control over when the app is using Bluetooth. This is both good UX and good for battery life, as the scan only runs when the user wants it to.</p>
<p>But here's the really important part: the <code>onPause()</code> override. This is a critical safety net. When your Activity goes into the background (maybe the user pressed the home button, or they switched to another app), <code>onPause()</code> is called, and we immediately stop the scan. This is essential because if the user can't see your app, they don't need scan results, and there's no reason to drain their battery. This pattern ensures that even if the user forgets to press the "Stop" button, the scan won't run forever in the background.</p>
<p>You might be wondering, "What about <code>onResume()</code>? Shouldn't we restart the scan when the user comes back?" That's a design decision. In some apps, you might want to automatically restart scanning in <code>onResume()</code>. In others, you might want the user to explicitly press "Start" again. It depends on your use case. For a device-finding app where the user is actively searching, auto-resuming makes sense. For a monitoring app that runs in the background, you might want more explicit control.</p>
<p>One crucial thing we haven't shown in this example is runtime permission handling. Remember those permissions we declared in the manifest? On Android 6.0 and above, you can't just declare them, you have to actually request them from the user at runtime. Before calling <code>startBleScan()</code>, you should check if you have the necessary permissions and, if not, request them using <code>ActivityCompat.requestPermissions()</code>. If you try to start a scan without the proper permissions, it will fail silently (or loudly, depending on the Android version), and you'll be left scratching your head wondering why nothing is working.</p>
<p>And there you have it! You've just built your first AOSP 16 passive Bluetooth scanner. It's lean, it's mean, and it's incredibly power-efficient. The scanner listens silently for BLE advertisements, reports them through your callback, and stops gracefully when it's not needed.</p>
<p>Now, let's move on to our next topic: what to do when things go wrong. It's time to talk about breakups... Bluetooth bond breakups, that is.</p>
<h2 id="heading-deep-dive-2-bluetooth-bond-loss-reasons">Deep Dive #2: Bluetooth Bond Loss Reasons</h2>
<p>Ah, the Bluetooth bond. It's a beautiful, sacred thing. It's the digital equivalent of exchanging friendship bracelets. When you bond your phone with your headphones, you're creating a long-term, trusted relationship. They share secret keys, they remember each other, and they promise to connect automatically, saving you the hassle of pairing every single time. It's a beautiful romance.</p>
<p>Until it's not.</p>
<p>Suddenly, one day, they just... forget each other. The connection is gone. The trust is broken. And your app is left in the middle, trying to play therapist, with no idea what went wrong. You've been ghosted. And until now, Android has been no help. You'd get a notification that the bond state is now BOND_NONE, but that's it. No explanation. No closure. Just the cold, hard silence of a failed connection.</p>
<h3 id="heading-finally-some-closure">Finally, Some Closure!</h3>
<p>But our friends on the Android team have clearly been through some tough breakups, because in AOSP 16, they've given us the gift of closure. Introducing BluetoothDevice.EXTRA_BOND_LOSS_REASON. It's a new extra that comes with the ACTION_BOND_STATE_CHANGED broadcast, and it's here to tell you why the bond was lost. It's like getting a breakup text that actually explains what happened!</p>
<p>Now, when a bond is broken, you can get a specific reason code. Think of them as the classic breakup excuses, but for Bluetooth:</p>
<table><tbody><tr><td><p><strong>Reason Code (Illustrative)</strong></p></td><td><p><strong>What it Actually Means</strong></p></td></tr><tr><td><p>BOND_LOSS_REASON_BREDR_AUTH_FAILURE</p></td><td><p>Indicates that the reason for the bond loss is BREDR authentication failure.</p></td></tr><tr><td><p>BOND_LOSS_REASON_BREDR_INCOMING_PAIRING</p></td><td><p>Indicates that the reason for the bond loss is BREDR pairing failure.</p></td></tr><tr><td><p>BOND_LOSS_REASON_LE_ENCRYPT_FAILURE</p></td><td><p>Indicates that the reason for the bond loss is LE encryption failure.</p></td></tr><tr><td><p>BOND_LOSS_REASON_LE_INCOMING_PAIRING</p></td><td><p>Indicates that the reason for the bond loss is LE pairing failure.</p></td></tr></tbody></table>

<h3 id="heading-the-code-playing-detective">The Code: Playing Detective</h3>
<p>So, how do we get this juicy gossip? We need to set up a BroadcastReceiver to listen for bond state changes.</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// Create a BroadcastReceiver to listen for bond state changes</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> bondStateReceiver = <span class="hljs-keyword">object</span> : BroadcastReceiver() {
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onReceive</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>, intent: <span class="hljs-type">Intent</span>)</span></span> {
        <span class="hljs-keyword">if</span> (intent.action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) {
            <span class="hljs-keyword">val</span> device: BluetoothDevice? = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
            <span class="hljs-keyword">val</span> bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR)
            <span class="hljs-keyword">val</span> previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, BluetoothDevice.ERROR)

            <span class="hljs-comment">// Check if we went from bonded to not bonded</span>
            <span class="hljs-keyword">if</span> (bondState == BluetoothDevice.BOND_NONE &amp;&amp; previousBondState == BluetoothDevice.BOND_BONDED) {
                Log.d(<span class="hljs-string">"BondBreakup"</span>, <span class="hljs-string">"We got dumped by <span class="hljs-subst">${device?.address}</span>!"</span>)

                <span class="hljs-comment">// Now, let's find out why...</span>
                <span class="hljs-keyword">val</span> reason = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_LOSS_REASON, -<span class="hljs-number">1</span>)

                <span class="hljs-keyword">when</span> (reason) {
                    <span class="hljs-comment">// Note: The actual constant values are in the Android SDK</span>
                    BluetoothDevice.BOND_LOSS_REASON_REMOTE_DEVICE_REMOVED -&gt; {
                        Log.d(<span class="hljs-string">"BondBreakup"</span>, <span class="hljs-string">"Reason: The remote device removed the bond."</span>)
                        <span class="hljs-comment">// You could show a message to the user: "Your headphones seem to have forgotten you. Please try pairing again."</span>
                    }
                    <span class="hljs-comment">// ... handle other reasons ...</span>
                    <span class="hljs-keyword">else</span> -&gt; {
                        Log.d(<span class="hljs-string">"BondBreakup"</span>, <span class="hljs-string">"Reason: It's complicated (Unknown reason code: <span class="hljs-variable">$reason</span>)"</span>)
                    }
                }
            }
        }
    }
}

<span class="hljs-comment">// In your Activity or Service, register the receiver</span>
<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onResume</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">super</span>.onResume()
    <span class="hljs-keyword">val</span> filter = IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
    registerReceiver(bondStateReceiver, filter)
}

<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onPause</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">super</span>.onPause()
    <span class="hljs-comment">// Don't forget to unregister!</span>
    unregisterReceiver(bondStateReceiver)
}
</code></pre>
<p>The code above implements a detective system for Bluetooth bond breakups, and it's more sophisticated than it might first appear. Let's walk through how this broadcast receiver pattern works and why it's so powerful.</p>
<p>First, we're creating a <code>BroadcastReceiver</code>, which is Android's way of letting your app listen for system-wide events. Think of it as subscribing to a notification service, whenever something interesting happens in the Android system (like a bond state change), the system broadcasts an "intent" to all registered listeners. Our receiver is one of those listeners.</p>
<p>In the <code>onReceive()</code> method, we first check if the incoming intent's action is <code>ACTION_BOND_STATE_CHANGED</code>. This is crucial because broadcast receivers can potentially receive many different types of intents, and we only care about bond state changes. Once we've confirmed this is the right type of event, we extract the relevant information from the intent using <code>getParcelableExtra()</code> and <code>getIntExtra()</code>.</p>
<p>The <code>device</code> object tells us which Bluetooth device this event is about. After all, you might be bonded to multiple devices (your headphones, your smartwatch, your car), and we need to know which one just broke up with us. The <code>bondState</code> tells us the current state (are we bonded, bonding, or not bonded?), and <code>previousBondState</code> tells us what the state was before this change occurred.</p>
<p>The key logic happens in our conditional check: <code>if (bondState == BluetoothDevice.BOND_NONE &amp;&amp; previousBondState == BluetoothDevice.BOND_BONDED)</code>. This is checking for the specific transition from "bonded" to "not bonded," which is the digital equivalent of a breakup. We're not interested in the bonding process itself (going from none to bonding to bonded) – we only care about when an existing bond is lost.</p>
<p>Once we've detected a breakup, we extract the new <code>EXTRA_BOND_LOSS_REASON</code> from the intent. This is the star feature from AOSP 16 that finally gives us closure. The reason code tells us exactly why the bond was lost – was it the remote device that ended things? Did the user manually forget the device? Did authentication fail? Each reason code corresponds to a different scenario, and you can handle each one appropriately.</p>
<p>In the example above, we're using a when expression to handle different reason codes. For BOND_LOSS_REASON_BREDR_INCOMING_PAIRING, we know the other device initiated the breakup, so we can show a helpful message like "Your headphones seem to have forgotten you. Please try pairing again." For other reasons, you'd add more branches to handle them specifically.</p>
<p>Now, notice the lifecycle management at the bottom. We register our receiver in <code>onResume()</code> and unregister it in <code>onPause()</code>. This is critical: if you forget to unregister a broadcast receiver, it will continue to receive broadcasts even after your Activity is destroyed, which can cause memory leaks and crashes. The pattern of registering in <code>onResume()</code> and unregistering in <code>onPause()</code> ensures that we only listen for bond changes when our Activity is visible and active.</p>
<p>This is a huge step forward for debugging and for user experience. Instead of just telling the user "Connection failed," you can now give them actionable advice based on the specific reason the bond was lost. It's like being a helpful, informed relationship counselor instead of a confused bystander who can only shrug and say "I don't know what happened."</p>
<p>Now that we've dealt with the emotional baggage of breakups, let's move on to something a little more lighthearted: speed dating for Bluetooth devices.</p>
<h2 id="heading-deep-dive-3-service-uuids-from-advertisements">Deep Dive #3: Service UUIDs from Advertisements</h2>
<p>Let's talk about finding a compatible partner... for your app. In the world of BLE, not all devices are created equal. A heart rate monitor is very different from a smart lightbulb. So how does your app know if it's talking to the right kind of device? The answer is the Service UUID.</p>
<h3 id="heading-what-in-the-world-is-a-service-uuid">What in the World is a Service UUID?</h3>
<p>A Service UUID (Universally Unique Identifier) is like a device's job title. It's a unique, 128-bit number that says, "I am a device that provides a Heart Rate Service" or "I am a device that provides a Battery Service." It's the single most important piece of information for determining what a device can do.</p>
<h3 id="heading-the-old-way-the-awkward-first-date">The Old Way: The Awkward First Date</h3>
<p>Traditionally, finding out a device's services was a whole ordeal. It was like going on a full, three-course dinner date just to find out the other person's job. The process went something like this:</p>
<ol>
<li><p>Scan: Find the device.</p>
</li>
<li><p>Connect: Establish a connection (a slow and power-hungry process).</p>
</li>
<li><p>Discover Services: Ask the device, "So... what do you do for a living?" and wait for it to list all its services.</p>
</li>
<li><p>Evaluate: Check if the list of services contains the one you're interested in.</p>
</li>
<li><p>Disconnect (or stay connected): If it's not the right device, you have to break up (disconnect) and move on. What a waste of time and energy!</p>
</li>
</ol>
<p>This is incredibly inefficient, especially if you're in a crowded room with dozens of BLE devices and you're only looking for one specific type.</p>
<h3 id="heading-the-new-way-the-glorious-name-tag">The New Way: The Glorious Name Tag</h3>
<p>Wouldn't it be great if everyone at a party just wore a name tag with their job title on it? That's exactly what AOSP 16 has given us with BluetoothDevice.EXTRA_UUID_LE. Many BLE devices are already polite enough to include their primary service UUID in their advertisement packets. It's their way of shouting, "I'M A HEART RATE MONITOR!" to the whole room.</p>
<p>Before AOSP 16, getting this information out of the advertisement packet was a messy, manual process of parsing the raw byte array of the scan record. It was doable, but it was the kind of code that you'd write once, pray it worked, and never touch again.</p>
<p>Now, Android does the dirty work for us! The system automatically parses the advertising data and, if it finds any service UUIDs, it conveniently hands them to you in the ScanResult.</p>
<h3 id="heading-the-code-reading-the-name-tag">The Code: Reading the Name Tag</h3>
<p>This new feature makes our ScanCallback even more powerful. We can now check the device's job title the moment we discover it, without ever having to connect.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> scanCallback = <span class="hljs-keyword">object</span> : ScanCallback() {
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onScanResult</span><span class="hljs-params">(callbackType: <span class="hljs-type">Int</span>, result: <span class="hljs-type">ScanResult</span>)</span></span> {
        Log.d(<span class="hljs-string">"BleSpeedDating"</span>, <span class="hljs-string">"Found device: <span class="hljs-subst">${result.device.address}</span>"</span>)

        <span class="hljs-comment">// Let's check their name tag!</span>
        <span class="hljs-keyword">val</span> serviceUuids = result.scanRecord?.serviceUuids
        <span class="hljs-keyword">if</span> (serviceUuids.isNullOrEmpty()) {
            Log.d(<span class="hljs-string">"BleSpeedDating"</span>, <span class="hljs-string">"This one is mysterious. No service UUIDs in the ad."</span>)
            <span class="hljs-keyword">return</span>
        }

        <span class="hljs-comment">// Define the UUID we're looking for (e.g., the standard Heart Rate Service UUID)</span>
        <span class="hljs-keyword">val</span> heartRateServiceUuid = ParcelUuid.fromString(<span class="hljs-string">"0000180D-0000-1000-8000-00805F9B34FB"</span>)

        <span class="hljs-keyword">if</span> (serviceUuids.contains(heartRateServiceUuid)) {
            Log.d(<span class="hljs-string">"BleSpeedDating"</span>, <span class="hljs-string">"It's a match! This is a heart rate monitor. Let's connect!"</span>)
            <span class="hljs-comment">// Now you can proceed to connect to result.device, knowing it's the right one.</span>
            stopBleScan() <span class="hljs-comment">// We found what we were looking for</span>
            <span class="hljs-comment">// connectToDevice(result.device)</span>
        } <span class="hljs-keyword">else</span> {
            Log.d(<span class="hljs-string">"BleSpeedDating"</span>, <span class="hljs-string">"Not a match. Moving on."</span>)
        }
    }

    <span class="hljs-comment">// ... onScanFailed ...</span>
}
</code></pre>
<p>The code above demonstrates the power of reading service UUIDs directly from advertisement data, and it's a game-changer for device discovery. Let's break down exactly what's happening and why this is such a significant improvement.</p>
<p>When we receive a scan result in our callback, the <code>result</code> object contains a <code>scanRecord</code> property. This scan record is essentially the raw advertisement packet that the BLE device broadcast into the air.</p>
<p>Before AOSP 16, if you wanted to extract service UUIDs from this data, you'd have to manually parse the byte array, understand the BLE advertisement format, handle different data types, and pray you didn't make an off-by-one error. It was the kind of code that worked once and then you never touched it again out of fear.</p>
<p>Now, with the improvements in AOSP 16, Android does all that messy parsing for us. We can simply call <code>result.scanRecord?.serviceUuids</code> and get back a nice, clean list of <code>ParcelUuid</code> objects. The safe call operator (<code>?.</code>) is important here because not all devices include a scan record in their results, and we need to handle that gracefully.</p>
<p>After retrieving the service UUIDs, we check if the list is null or empty. Some devices don't include service UUIDs in their advertisements. They might be using a proprietary format, or they might just be poorly configured. If there are no UUIDs, we log a message and return early. There's no point in continuing if we can't identify what the device does.</p>
<p>Next, we define the UUID we're looking for. In this example, we're searching for heart rate monitors, so we use the standard Heart Rate Service UUID: <code>0000180D-0000-1000-8000-00805F9B34FB</code>. This is a UUID defined by the Bluetooth SIG (Special Interest Group), and any compliant heart rate monitor will advertise this UUID. You can find a complete list of standard service UUIDs in the Bluetooth specifications, or you can use custom UUIDs if you're building your own BLE peripherals.</p>
<p>The magic happens in the <code>if (serviceUuids.contains(heartRateServiceUuid))</code> check. This is where we're doing our speed dating: we're checking the device's "name tag" to see if it matches what we're looking for.</p>
<p>If it does, we've found our match! We can immediately stop scanning (because why keep looking when we've found what we need?) and proceed to connect to the device. We know, with certainty, that this device is a heart rate monitor, so we won't waste time and battery connecting to random devices only to discover they're not what we need.</p>
<p>If the UUID doesn't match, we simply log "Not a match" and move on. The callback will be called again when the next device is found, and we'll repeat this process until we find our heart rate monitor or the user stops the scan.</p>
<p>This is a massive performance improvement over the old approach. Previously, you'd have to connect to every device you found, perform service discovery (which involves multiple round-trip communications with the device), check if it has the services you need, and then disconnect if it doesn't. Each connection attempt takes time, uses battery, and creates unnecessary radio traffic.</p>
<p>Now, you can filter and identify devices at lightning speed, all at the scanning stage. No more awkward first dates where you connect to a smart lightbulb thinking it might be a fitness tracker. Just efficient, targeted connections.</p>
<p>This is particularly useful for apps that need to find a specific type of sensor or peripheral in a sea of irrelevant devices. Imagine you're in a hospital with hundreds of BLE-enabled medical devices, or in a smart home with dozens of sensors and actuators. Being able to instantly identify the right device from its advertisement is the difference between a responsive, professional app and one that feels sluggish and unreliable.</p>
<p>We've now met all three of our Bluetooth musketeers: passive scanning for battery efficiency, bond loss reasons for better debugging, and service UUIDs from advertisements for faster device identification. But our journey isn't over. It's time to venture into the deep woods of advanced scanning techniques.</p>
<h2 id="heading-advanced-topics-filtering-batching-and-other-sorcery">Advanced Topics: Filtering, Batching, and Other Sorcery</h2>
<p>Alright, you've mastered the basics. You can scan passively, you can get closure on your connection breakups, and you can speed-date devices like a pro. You're no longer a Bluetooth padawan. It's time to become a Jedi Master.</p>
<p>Let's dive into the advanced arts of filtering, batching, and other optimization sorcery that will make your app a true battery-saving champion.</p>
<h3 id="heading-hardware-filtering-your-personal-assistant">Hardware Filtering: Your Personal Assistant</h3>
<p>Imagine you're a celebrity, and you've hired a personal assistant. You don't want to be bothered by every single person who wants an autograph. So, you give your assistant a list: "Only let me know if you see my agent or my mom." Your assistant then stands at the door and only bothers you when someone on the list shows up.</p>
<p>This is exactly what hardware filtering does. Instead of your app's code (the celebrity) being woken up for every single Bluetooth device the radio sees, you can offload the filtering logic to the Bluetooth controller itself (the personal assistant). This is a feature that's been around since Android 6.0, but it's more important than ever.</p>
<p>Why is this so great? Because your app's code can stay asleep. The main processor (the AP) doesn't have to wake up every time a random Bluetooth toothbrush advertises itself. The Bluetooth controller, which is much more power-efficient, handles the filtering. The AP only wakes up when the controller finds a device that matches your criteria.</p>
<h3 id="heading-the-code-building-your-vip-list">The Code: Building Your VIP List</h3>
<p>You implement this using ScanFilter. You can filter by a device's name, its MAC address, or, most usefully, by the Service UUID it's advertising.</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// We only want to be bothered if we see a heart rate monitor.</span>
<span class="hljs-keyword">val</span> heartRateServiceUuid = ParcelUuid.fromString(<span class="hljs-string">"0000180D-0000-1000-8000-00805F9B34FB"</span>)

<span class="hljs-keyword">val</span> filter = ScanFilter.Builder()
    .setServiceUuid(heartRateServiceUuid)
    .build()

<span class="hljs-keyword">val</span> scanFilters: List&lt;ScanFilter&gt; = listOf(filter)

<span class="hljs-comment">// Now, when you start your scan, pass in this list</span>
bleScanner.startScan(scanFilters, scanSettings, scanCallback)
</code></pre>
<p>The code above shows how to create a hardware-level filter that dramatically improves both battery life and app performance. Let's dive deep into what's happening here and why this is such a powerful technique.</p>
<p>We start by defining the service UUID we're interested in – in this case, the standard Heart Rate Service UUID. This is the same UUID we used in the previous example, but now we're using it in a fundamentally different way. Instead of checking the UUID in our app's code after receiving scan results, we're telling the Bluetooth hardware itself to only report devices that match this UUID.</p>
<p>The <code>ScanFilter.Builder()</code> is our tool for constructing this filter. It's a builder pattern, which means we can chain multiple methods together to configure exactly what we're looking for. In this example, we're calling <code>setServiceUuid(heartRateServiceUuid)</code>, which tells the filter to only match devices that advertise this specific service.</p>
<p>But the builder has many other options you can use:</p>
<ul>
<li><p><code>setDeviceName()</code> – Match devices with a specific name (like "My Heart Monitor")</p>
</li>
<li><p><code>setDeviceAddress()</code> – Match a specific device by its MAC address (useful if you've already paired with a device and want to find it again)</p>
</li>
<li><p><code>setManufacturerData()</code> – Match devices based on manufacturer-specific data in their advertisements</p>
</li>
<li><p><code>setServiceData()</code> – Match based on service data included in the advertisement</p>
</li>
</ul>
<p>You can even combine multiple criteria in a single filter. For example, you could create a filter that matches devices with a specific service UUID <em>and</em> a specific manufacturer ID. The more specific your filter, the fewer false positives you'll get.</p>
<p>After building our filter, we create a list containing it. Why a list? Because you can have multiple filters, and a device will match if it satisfies <em>any</em> of the filters in the list. For instance, you might create one filter for heart rate monitors and another for blood pressure monitors, and your scan will report devices that match either one. This is an OR operation: the device doesn't need to match all filters, just one of them.</p>
<p>Finally, we pass this list of filters to <code>startScan()</code> along with our scan settings and callback. This is where the magic happens. When you provide filters, Android doesn't just filter the results in your app's code. It pushes these filters down to the Bluetooth controller hardware itself. This means the filtering happens at the lowest level, before your app is even notified.</p>
<p>Here's why this is so powerful: without filters, every time the Bluetooth radio hears an advertisement from <em>any</em> device (your neighbor's smart toaster, someone's fitness tracker walking by, the Bluetooth speaker three rooms away), it has to wake up your app's process, deliver the scan result, and let your code decide if it cares about this device. Each of these wake-ups costs battery and processing time.</p>
<p>With hardware filters, the Bluetooth controller silently ignores all the devices that don't match your criteria. Your app stays asleep. The main processor stays asleep. Only when a heart rate monitor is detected does the hardware wake up your app and deliver the result. It's like having a bouncer at a club who only lets in people on the VIP list. Everyone else is turned away at the door, and you never even know they were there.</p>
<p>By using a <code>ScanFilter</code>, you're telling the hardware, "Don't wake me up unless you see a heart rate monitor." It's the ultimate power-saving move for background scanning. Combined with passive scanning and batch reporting, you can create a Bluetooth scanning system that runs for hours or even days with minimal battery impact. This is how professional-grade apps handle long-term device monitoring without destroying battery life.</p>
<h3 id="heading-batch-scanning-the-daily-report">Batch Scanning: The Daily Report</h3>
<p>Let's go back to our celebrity analogy. Sometimes, you don't need to be interrupted the moment your mom shows up. You'd rather just get a report at the end of the day: "Today, your mom stopped by twice, and your agent called once." This is batch scanning.</p>
<p>Instead of delivering scan results to your app in real-time, the Bluetooth controller can collect them and deliver them in a big batch. This is another incredible power-saving feature. Your app can sleep for long periods, then wake up, process a whole bunch of results at once, and go back to sleep.</p>
<p>You enable this with the setReportDelay() method in your ScanSettings.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> scanSettings = ScanSettings.Builder()
    <span class="hljs-comment">// ... other settings ...</span>
    <span class="hljs-comment">// Deliver results every 5 seconds (5000 milliseconds)</span>
    .setReportDelay(<span class="hljs-number">5000</span>)
    .build()
</code></pre>
<p>When you use a report delay, your onScanResult callback will be replaced by onBatchScanResults, which gives you a List&lt;ScanResult&gt;.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> scanCallback = <span class="hljs-keyword">object</span> : ScanCallback() {
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onBatchScanResults</span><span class="hljs-params">(results: <span class="hljs-type">List</span>&lt;<span class="hljs-type">ScanResult</span>&gt;)</span></span> {
        Log.d(<span class="hljs-string">"BatchScanner"</span>, <span class="hljs-string">"Here's your daily report! Found <span class="hljs-subst">${results.size}</span> devices."</span>)
        <span class="hljs-keyword">for</span> (result <span class="hljs-keyword">in</span> results) {
            <span class="hljs-comment">// Process each result</span>
        }
    }

    <span class="hljs-comment">// ... onScanFailed ...</span>
}
</code></pre>
<p>The batch scanning mechanism shown above is one of the most underutilized power-saving features in Android Bluetooth, and understanding how it works can transform your app's battery profile. Let's break down exactly what's happening under the hood and when you should use this technique.</p>
<p>When you set a report delay of 5000 milliseconds (5 seconds) in the code above, you're fundamentally changing how the scanning pipeline works. Instead of the Bluetooth controller immediately waking up your app every time it sees a device, it acts like a diligent assistant taking notes. For those 5 seconds, the controller silently collects every scan result it encounters, storing them in its own internal buffer. Your app remains completely asleep during this time – no CPU cycles wasted, no battery drained by context switches or process wake-ups.</p>
<p>After the 5-second delay expires, the controller delivers all the accumulated results in one batch to your <code>onBatchScanResults()</code> callback. This is where the power savings come from: instead of waking up your app 50 times if 50 devices were detected, it wakes up once and hands you all 50 results at the same time. Your app can then efficiently process this batch – maybe updating a UI list, logging the data, or checking for specific devices – and then go back to sleep until the next batch arrives.</p>
<p>The <code>results</code> parameter in <code>onBatchScanResults()</code> is a <code>List&lt;ScanResult&gt;</code>, and each <code>ScanResult</code> in the list represents a single advertisement that was heard during the batching period. It's important to note that if the same device advertises multiple times during the delay period, you might receive multiple results for that device in the batch. The list isn't automatically deduplicated – that's your job if you need it.</p>
<p>In the example above, we're simply logging the number of devices found and then iterating through each result. In a real application, you might want to do more sophisticated processing. For instance, you could build a map of devices keyed by MAC address to track how many times each device advertised, calculate average RSSI values to estimate distance, or filter the batch to only process devices that meet certain criteria.</p>
<p><strong>Warning:</strong> Batch scanning is a powerful tool, but it's not for every situation. If you need to react to a device's presence immediately (for example, if you're building a "find my keys" app where the user is actively searching), a report delay is not your friend. The user doesn't want to wait 5 seconds to see results – they want instant feedback. In these cases, set <code>setReportDelay(0)</code> for immediate reporting.</p>
<p>But for long-term monitoring or data collection scenarios, batch scanning is a battery's best friend. Consider these use cases:</p>
<ul>
<li><p><strong>Background presence monitoring</strong>: Your app checks every minute to see if the user's smartwatch is still in range, but doesn't need second-by-second updates.</p>
</li>
<li><p><strong>Environmental sensing</strong>: You're collecting data from temperature sensors throughout a building and only need to update your dashboard every 30 seconds.</p>
</li>
<li><p><strong>Beacon analytics</strong>: You're tracking how many people pass by a retail location based on their phone's BLE advertisements, and you aggregate the data every 10 seconds.</p>
</li>
</ul>
<p>The sweet spot for report delay depends on your use case. Too short (like 1 second), and you're not getting much benefit, you're still waking up frequently. Too long (like 60 seconds), and your app might feel unresponsive or miss time-sensitive events. For most background monitoring tasks, delays between 5 and 30 seconds work well.</p>
<p>One more thing to be aware of: batch scanning has limits. The Bluetooth controller has a finite buffer for storing scan results. If you set a very long delay and you're in an environment with hundreds of BLE devices, the buffer might fill up before the delay expires. When this happens, the oldest results get dropped. Android doesn't give you a warning when this occurs, so if you're missing data, consider reducing your report delay or using more aggressive filters to reduce the number of results being collected.</p>
<h3 id="heading-onfoundonlost-the-drama-of-presence">OnFound/OnLost: The Drama of Presence</h3>
<p>Since Android 8.0, scanning has gotten even more dramatic. You can now ask the hardware to not only tell you when it finds a device, but also when it loses one. This is done using the CALLBACK_TYPE_FIRST_MATCH and CALLBACK_TYPE_MATCH_LOST flags in your ScanSettings.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> scanSettings = ScanSettings.Builder()
    .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH or ScanSettings.CALLBACK_TYPE_MATCH_LOST)
    .build()
</code></pre>
<p>Now, in your ScanCallback, the callbackType parameter in onScanResult will tell you what happened.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onScanResult</span><span class="hljs-params">(callbackType: <span class="hljs-type">Int</span>, result: <span class="hljs-type">ScanResult</span>)</span></span> {
    <span class="hljs-keyword">when</span> (callbackType) {
        ScanSettings.CALLBACK_TYPE_FIRST_MATCH -&gt; {
            Log.d(<span class="hljs-string">"PresenceDetector"</span>, <span class="hljs-string">"Found them! <span class="hljs-subst">${result.device.address}</span> has entered the building."</span>)
        }
        ScanSettings.CALLBACK_TYPE_MATCH_LOST -&gt; {
            Log.d(<span class="hljs-string">"PresenceDetector"</span>, <span class="hljs-string">"They're gone! <span class="hljs-subst">${result.device.address}</span> has left the building."</span>)
        }
    }
}
</code></pre>
<p>The presence detection mechanism shown above represents a fundamental shift in how we think about Bluetooth scanning. Instead of treating scanning as a continuous stream of "here's what I see right now," we're now working with events: "this device appeared" and "this device disappeared." Let's dive deep into how this works and why it's so powerful.</p>
<p>When you set the callback type using the bitwise OR operator (<code>or</code> in Kotlin, <code>|</code> in Java), you're telling the Bluetooth hardware to track the presence state of devices over time. The code <code>CALLBACK_TYPE_FIRST_MATCH or CALLBACK_TYPE_MATCH_LOST</code> combines both flags, meaning you want to be notified both when a device first appears and when it disappears. You can use these flags individually if you only care about one type of event, but using both together gives you complete presence awareness.</p>
<p>Let's understand what "first match" and "match lost" actually mean. When the Bluetooth controller hears an advertisement from a device that matches your filters for the first time, it triggers a <code>CALLBACK_TYPE_FIRST_MATCH</code> event. This is different from <code>CALLBACK_TYPE_ALL_MATCHES</code> (the default), which would trigger every single time the device advertises. A device might advertise multiple times per second, so the difference is significant. With <code>FIRST_MATCH</code>, you get one notification when the device enters your scanning range, not a flood of notifications as it continues to advertise.</p>
<p>The <code>CALLBACK_TYPE_MATCH_LOST</code> event is even more interesting. The Bluetooth controller keeps track of when it last heard from each device. If a device stops advertising (because it moved out of range, was turned off, or its battery died), the controller notices the absence and triggers a <code>MATCH_LOST</code> event. This happens automatically: you don't have to manually track timestamps or implement timeout logic in your app. The hardware does it for you.</p>
<p>But how does the hardware know when a device is "lost"? It uses an internal timeout. If the controller hasn't heard from a device for a certain period (typically a few seconds, though the exact duration is implementation-dependent and not exposed to apps), it considers the device lost. This means there's a slight delay between when a device actually leaves range and when you get the <code>MATCH_LOST</code> callback, but this delay is usually acceptable for presence detection use cases.</p>
<p>In the code example above, we're using a <code>when</code> expression to handle the different callback types. When we receive a <code>FIRST_MATCH</code>, we know the device has just entered our scanning range, so we log "Found them!" This is perfect for triggering actions like unlocking a door when your phone comes near, or starting to sync data when your fitness tracker is detected.</p>
<p>When we receive a <code>MATCH_LOST</code>, we know the device has left our scanning range or stopped advertising, so we log "They're gone!" This is ideal for triggering cleanup actions like locking the door when your phone leaves, or stopping a data sync when your tracker disconnects.</p>
<p>This is incredibly useful for presence detection scenarios. Is your smart lock in range? Is your fitness tracker still connected? Is the user's phone nearby? Now you can know, with hardware-level certainty, and you can react to changes in presence without constantly polling or maintaining complex state machines in your app code.</p>
<p>Here's a practical example of how you might use this in a smart home app:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> presenceCallback = <span class="hljs-keyword">object</span> : ScanCallback() {
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onScanResult</span><span class="hljs-params">(callbackType: <span class="hljs-type">Int</span>, result: <span class="hljs-type">ScanResult</span>)</span></span> {
        <span class="hljs-keyword">when</span> (callbackType) {
            ScanSettings.CALLBACK_TYPE_FIRST_MATCH -&gt; {
                <span class="hljs-comment">// User's phone detected - they're home!</span>
                Log.d(<span class="hljs-string">"SmartHome"</span>, <span class="hljs-string">"Welcome home! Unlocking door and turning on lights."</span>)
                unlockFrontDoor()
                turnOnLights()
                adjustThermostat(COMFORTABLE_TEMP)
            }
            ScanSettings.CALLBACK_TYPE_MATCH_LOST -&gt; {
                <span class="hljs-comment">// User's phone is gone - they left!</span>
                Log.d(<span class="hljs-string">"SmartHome"</span>, <span class="hljs-string">"Goodbye! Locking door and entering away mode."</span>)
                lockFrontDoor()
                turnOffLights()
                adjustThermostat(ENERGY_SAVING_TEMP)
                armSecuritySystem()
            }
        }
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onScanFailed</span><span class="hljs-params">(errorCode: <span class="hljs-type">Int</span>)</span></span> {
        Log.e(<span class="hljs-string">"SmartHome"</span>, <span class="hljs-string">"Presence detection failed: <span class="hljs-variable">$errorCode</span>"</span>)
    }
}
</code></pre>
<p>One important consideration: <code>FIRST_MATCH</code> and <code>MATCH_LOST</code> are mutually exclusive with <code>CALLBACK_TYPE_ALL_MATCHES</code>. If you combine them with <code>ALL_MATCHES</code>, the behavior becomes undefined and varies by device. Stick to either <code>ALL_MATCHES</code> for continuous reporting, or <code>FIRST_MATCH</code>/<code>MATCH_LOST</code> for presence detection – don't try to use both at once.</p>
<p>Also, be aware that presence detection works best when combined with hardware filtering. If you're scanning for all devices without filters, the controller has to track the presence state of every single BLE device in range, which can overwhelm its internal tracking tables. Always use <code>ScanFilter</code> to narrow down which devices you care about when using presence detection.</p>
<p>By combining these advanced techniques – hardware filtering, batch scanning, and presence detection – you can build incredibly sophisticated and power-efficient Bluetooth applications. You're not just a developer anymore. You're a Bluetooth wizard, wielding the power to create apps that are aware of their surroundings, responsive to changes, and respectful of battery life.</p>
<p>Now, let's see where we can apply these magical powers in the real world.</p>
<h2 id="heading-real-world-use-cases-where-the-bluetooth-hits-the-road">Real-World Use Cases: Where the Bluetooth Hits the Road</h2>
<p>Okay, we've learned a ton of cool new tricks. We're basically Bluetooth black belts at this point. But what's the use of all this power if we don't use it for good (or at least for a cool app)? Let's explore some real-world scenarios where the new features in AOSP 16 can turn a good app into a great one.</p>
<h3 id="heading-1-the-find-my-everything-app">1. The "Find My Everything" App</h3>
<p>We've all been there. You're late for work, and your keys have decided to play a game of hide-and-seek in another dimension. This is the classic use case for a BLE tracker.</p>
<ul>
<li><p><strong>The Old Way:</strong> Your app would be constantly doing active scans, draining your battery while you frantically search. It would connect to every tracker in your house just to see if it's the right one.</p>
</li>
<li><p><strong>The AOSP 16 Way:</strong> Your app runs a passive scan in the background with a hardware filter for your tracker's specific Service UUID. The battery impact is minimal. When you open the app to find your keys, it already knows they're in the house because it's been listening silently. You hit the "Find" button, the app connects, and your keys start screaming from inside the couch cushions. And if the connection fails? Bond loss reason tells you if the tracker's battery died, so you're not looking for a dead device.</p>
</li>
</ul>
<h3 id="heading-2-the-smart-supermarket">2. The Smart Supermarket</h3>
<p>Imagine an app that gives you coupons for products as you walk past them in the store. This is the dream of proximity marketing, a dream that has been historically thwarted by, you guessed it, battery drain.</p>
<ul>
<li><p><strong>The Old Way:</strong> The app would need to constantly scan for beacons, turning the user's phone into a hot potato and a dead battery by the time they reach the checkout line.</p>
</li>
<li><p><strong>The AOSP 16 Way:</strong> The supermarket places BLE beacons in each aisle. Your app uses a passive, batched scan. It wakes up every minute or so, gets a list of all the beacons it has seen, and then goes back to sleep. When it sees you've been loitering in the cookie aisle for five minutes (it knows, it always knows), it uses the Service UUID from the advertisement to identify the "Cookie Aisle Beacon" and sends you a coupon for Oreos. It's targeted, it's efficient, and it doesn't kill your battery before you can pay.</p>
</li>
</ul>
<h3 id="heading-3-the-overly-attached-smart-home">3. The Overly-Attached Smart Home</h3>
<p>Your smart home should be, well, smart. It should know when you're home and when you've left. It should lock the door behind you and turn on the lights when you arrive.</p>
<ul>
<li><p><strong>The Old Way:</strong> You'd have to rely on GPS (a notorious battery hog) or Wi-Fi connections, which can be unreliable. BLE was an option, but constant scanning was a problem.</p>
</li>
<li><p><strong>The AOSP 16 Way:</strong> Your phone is the key. Your smart hub (acting as a central device) runs a continuous, low-power passive scan. When it sees your phone's BLE advertisement, it knows you're home. But what if you just walk by the house? This is where the OnFound/OnLost feature comes in. The hub can be configured to only trigger the "Welcome Home" sequence after it has seen your device consistently for a minute (OnFound), and to trigger the "Goodbye" sequence only after it hasn't seen you for five minutes (OnLost). It's a smarter, more reliable presence detection system that finally makes the smart home feel... smart.</p>
</li>
</ul>
<h3 id="heading-4-the-corporate-asset-tracker">4. The Corporate Asset Tracker</h3>
<p>In a large hospital or warehouse, keeping track of expensive, mobile equipment (like IV pumps or forklifts) is a huge challenge. BLE tags are the solution.</p>
<ul>
<li><p><strong>The Old Way:</strong> Employees would have to walk around with a tablet, doing active scans to take inventory. It's slow, manual, and inefficient.</p>
</li>
<li><p><strong>The AOSP 16 Way:</strong> A network of fixed BLE gateways is installed throughout the building. Each gateway is a simple device (like a Raspberry Pi) running a continuous passive scan. They collect all the advertisement data from the asset tags and send it to a central server. The server can now see, in real-time, that IV Pump #34 is in Room 201, and Forklift #3 is currently in the loading bay. No manual scanning required. It's a low-cost, low-power, real-time location system, all thanks to the efficiency of passive scanning.</p>
</li>
</ul>
<p>These are just a few examples. From fitness trackers to industrial sensors, the new Bluetooth features in AOSP 16 open up a world of possibilities for building apps that are not only powerful but also polite to your user's battery. Now, let's talk about how to make sure our shiny new app works on all devices, not just the new ones.</p>
<h2 id="heading-api-version-checking-how-to-not-crash-your-app">API Version Checking: How to Not Crash Your App</h2>
<p>So, you've built a beautiful, battery-sipping app using all the new hotness from AOSP 16's Q4 release. You're ready to ship it, become a millionaire, and retire to a private island. But then, a bug report comes in. Your app is crashing on a brand new Android 16 device. What gives?!</p>
<p>Welcome, my friend, to the wonderful world of API version checking. With Android's new release schedule, this has become more important (and slightly more complicated) than ever.</p>
<h3 id="heading-the-problem-a-tale-of-two-android-16s">The Problem: A Tale of Two Android 16s</h3>
<p>As we discussed, 2025 gave us two Android 16 releases:</p>
<ul>
<li><p><strong>The Q2 Release:</strong> The main "Baklava" release. Let's call this API level 36.0.</p>
</li>
<li><p><strong>The Q4 Release:</strong> The minor, feature-drop release. This is where our new Bluetooth toys live. Let's call this API level 36.1.</p>
</li>
</ul>
<p>Our new passive scanning API, setScanType(), only exists on 36.1 and later. If you try to call it on a device that's running the initial Q2 release (36.0), your app will crash with a NoSuchMethodError. It's the digital equivalent of asking for a menu item that was only added last night. The chef (your app) just gets confused and has a meltdown.</p>
<h3 id="heading-the-old-guard-sdkint">The Old Guard: SDK_INT</h3>
<p>For years, our trusty friend for checking API levels has been Build.VERSION.SDK_INT. It's simple and effective.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.S) {
    <span class="hljs-comment">// Use an API from Android 12 (S) or higher</span>
}
</code></pre>
<p>But SDK_INT only knows about major releases. For both Android 16 Q2 and Q4, SDK_INT will just report 36. It has no idea about the minor version. It's like asking someone their age, and they just say "thirties." Not very specific.</p>
<h3 id="heading-the-new-hotness-sdkintfull">The New Hotness: SDK_INT_FULL</h3>
<p>To solve this, the Android team has given us a new, more precise tool: <code>Build.VERSION.SDK_INT_FULL</code>. This constant knows about both the major and minor version numbers. And to go with it, we have a new set of version codes: <code>Build.VERSION_CODES_FULL</code>.</p>
<p>So, to safely call our new passive scanning API, we need to do a more specific check:</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// Let's build our ScanSettings</span>
<span class="hljs-keyword">val</span> scanSettingsBuilder = ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)

<span class="hljs-comment">// Now, let's check if we can go passive</span>
<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT_FULL &gt;= Build.VERSION_CODES_FULL.BAKLAVA_1) {
    Log.d(<span class="hljs-string">"ApiCheck"</span>, <span class="hljs-string">"This device is cool. Going passive."</span>)
    <span class="hljs-comment">// This is the new API from the Q4 release (36.1)</span>
    scanSettingsBuilder.setScanType(ScanSettings.SCAN_TYPE_PASSIVE)
} <span class="hljs-keyword">else</span> {
    Log.d(<span class="hljs-string">"ApiCheck"</span>, <span class="hljs-string">"This device is old school. Sticking to active scanning."</span>)
    <span class="hljs-comment">// Fallback for devices that don't have the new API</span>
    <span class="hljs-comment">// We don't need to do anything here, as active is the default</span>
}

<span class="hljs-keyword">val</span> scanSettings = scanSettingsBuilder.build()
</code></pre>
<h3 id="heading-graceful-degradation-the-art-of-falling-with-style">Graceful Degradation: The Art of Falling with Style</h3>
<p>This brings us to a crucial concept: graceful degradation. It means your app should still work on older devices, even if it can't use the latest and greatest features. It should fall back gracefully.</p>
<p>In our example above, if the setScanType method isn't available, we just... don't call it. The app will default to a normal, active scan. It won't be as battery-efficient, but it will still work. The user on the older device gets a functional app, and the user on the newer device gets a more optimized experience. Everybody wins.</p>
<p>Here's a table to help you remember when to use which check:</p>
<table><tbody><tr><td><p><strong>If you're using an API from...</strong></p></td><td><p><strong>Use this check...</strong></p></td></tr><tr><td><p>A major Android release (for example, Android 16 Q2)</p></td><td><p>if (SDK_INT &gt;= VERSION_CODES.BAKLAVA)</p></td></tr><tr><td><p>A minor, feature-drop release (for example, Android 16 Q4)</p></td><td><p>if (SDK_INT_FULL &gt;= VERSION_CODES_FULL.BAKLAVA_1)</p></td></tr></tbody></table>

<p>Mastering this new API checking is non-negotiable. It's the key to writing modern Android apps that are both innovative and stable. Now that we know how to build a robust app, let's talk about how to fix it when it inevitably breaks.</p>
<h2 id="heading-testing-and-debugging-the-fun-part-said-no-one-ever">Testing and Debugging: The Fun Part (Said No One Ever)</h2>
<p>There are two universal truths in software development:</p>
<ul>
<li><p>It works on my machine, and</p>
</li>
<li><p>It will break in the most spectacular way possible during a live demo.</p>
</li>
</ul>
<p>Bluetooth development, in particular, seems to delight in this second truth. It's a fickle, invisible force that seems to have a personal vendetta against developers.</p>
<p>So, how do we fight back? With a solid testing and debugging strategy. It's not glamorous, but it's the only way to stay sane.</p>
<h3 id="heading-the-emulator-a-land-of-make-believe">The Emulator: A Land of Make-Believe</h3>
<p>Android Studio's emulator is a fantastic tool. It's fast, it's convenient, and it can simulate all sorts of devices. And for Bluetooth? It can... sort of help. The emulator does have virtual Bluetooth support. You can enable it, and your app will think it has a Bluetooth adapter. It's great for testing your UI and making sure your app doesn't crash when it tries to get the BluetoothLeScanner.</p>
<p>But here's the catch: it's not real. The emulator can't actually interact with the radio waves in your room. You can't use it to find your real-life BLE headphones. For that, you need to venture into the real world.</p>
<h3 id="heading-the-real-world-where-the-bugs-live">The Real World: Where the Bugs Live</h3>
<p>There is no substitute for testing on real, physical devices. Every phone manufacturer has its own special flavor of Bluetooth stack, its own quirky antenna design, and its own unique way of making your life difficult. A scan that works perfectly on a Google Pixel might fail miserably on another brand. The only way to know is to test.</p>
<p>Your testing arsenal should include:</p>
<ul>
<li><p><strong>A variety of phones:</strong> Different brands, different Android versions. The more, the better.</p>
</li>
<li><p><strong>A variety of BLE peripherals:</strong> Don't just test with one type of device. Get a few different beacons, sensors, or wearables. You'll be amazed at how differently they behave.</p>
</li>
</ul>
<h3 id="heading-common-errors-the-usual-suspects">Common Errors: The Usual Suspects</h3>
<p>When your scan inevitably fails, it will give you an error code. Here are a few of the most common culprits:</p>
<table><tbody><tr><td><p><strong>Error Code</strong></p></td><td><p><strong>The Problem</strong></p></td><td><p><strong>How to Fix It</strong></p></td></tr><tr><td><p>SCAN_FAILED_ALREADY_STARTED</p></td><td><p>You tried to start a scan that was already running.</p></td><td><p>You got too excited. Make sure you're not calling startScan() multiple times without calling stopScan() in between.</p></td></tr><tr><td><p>SCAN_FAILED_APPLICATION_REGISTRATION_FAILED</p></td><td><p>Something is fundamentally wrong with your app's setup.</p></td><td><p>This is a vague and unhelpful error. It usually means you have a problem with your permissions or the system is just having a bad day. Try restarting Bluetooth.</p></td></tr><tr><td><p>SCAN_FAILED_INTERNAL_ERROR</p></td><td><p>The Bluetooth stack had a panic attack.</p></td><td><p>This is the classic "it's not you, it's me" error. It's an internal issue with the device's Bluetooth controller. There's not much you can do except try again later.</p></td></tr><tr><td><p>SCAN_FAILED_FEATURE_UNSUPPORTED</p></td><td><p>You tried to use a feature the hardware doesn't support.</p></td><td><p>You might be trying to use batch scanning on a device that doesn't support it. Use your API version checks!</p></td></tr></tbody></table>

<h3 id="heading-debugging-tools-your-ghost-hunting-kit">Debugging Tools: Your Ghost-Hunting Kit</h3>
<p>When things go wrong, you need the right tools to see what's happening in the invisible world of Bluetooth.</p>
<ul>
<li><p><strong>logcat:</strong> This is your best friend. Be generous with your log statements. Log when you start a scan, when you stop a scan, when you find a device, and when a scan fails. Create a filter for your app's tag so you can see the signal through the noise.</p>
</li>
<li><p><strong>Android's Bluetooth HCI Snoop Log:</strong> This is the holy grail of Bluetooth debugging. It's a developer option that records every single Bluetooth packet that goes in or out of your device. It's incredibly detailed and can be overwhelming, but it's the ultimate source of truth. You can open the generated log file in a tool like Wireshark to see the raw, unfiltered conversation between your phone and the BLE device. It's like having a wiretap on the radio waves.</p>
</li>
<li><p><strong>nRF Connect for Mobile:</strong> This is a free app from Nordic Semiconductor, and it's an essential tool for any BLE developer. It lets you scan for devices, see their advertising data, connect to them, and explore their GATT services. If your app can't find a device, the first thing you should do is see if nRF Connect can. If it can't, the problem is likely with the peripheral, not your app.</p>
</li>
</ul>
<p>Testing and debugging Bluetooth is a marathon, not a sprint. It requires patience, a methodical approach, and a healthy dose of self-deprecating humor. But with the right tools and techniques, you can tame the beast.</p>
<p>Now, let's talk about how to make sure our well-behaved app is also a good citizen when it comes to performance.</p>
<h2 id="heading-performance-and-best-practices-how-to-be-a-good-bluetooth-citizen">Performance and Best Practices: How to Be a Good Bluetooth Citizen</h2>
<p>Writing code that works is one thing. Writing code that works well, is efficient, and doesn't make your users want to throw their phone against a wall is another thing entirely. When it comes to Bluetooth, being a good citizen is all about one thing: battery, battery, battery.</p>
<p>The Bluetooth radio is a powerful piece of hardware, but it's also a thirsty one. Every moment it's active, it's sipping power. Your job is to make sure it's only sipping when absolutely necessary. Here are the golden rules of being a good Bluetooth citizen.</p>
<h3 id="heading-1-dont-scan-if-you-dont-have-to">1. Don't Scan If You Don't Have To</h3>
<p>This sounds obvious, but it's the most common mistake. Before you even think about starting a scan, ask yourself: "Do I really need to do this right now?" If the user is not on the screen that needs scan results, don't scan. If the app is in the background, be extra critical. Background scanning is a huge drain on battery and is heavily restricted by Android for that very reason.</p>
<h3 id="heading-2-stop-your-scan">2. Stop Your Scan!</h3>
<p>I'm going to say it again because it's that important: always stop your scan when you're done. A scan that's left running is like a leaky faucet for your battery. It will drain and drain until there's nothing left. The best practice is to tie your scan lifecycle to your UI lifecycle.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onPause</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">super</span>.onPause()
    <span class="hljs-comment">// The user can't see the screen, so they don't need the results.</span>
    stopBleScan()
}

<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onResume</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">super</span>.onResume()
    <span class="hljs-comment">// The user is back on the screen, let's start scanning again.</span>
    startBleScan()
}
</code></pre>
<p>If you find the device you're looking for, stop the scan immediately. There's no need to keep looking.</p>
<h3 id="heading-3-choose-the-right-scan-mode">3. Choose the Right Scan Mode</h3>
<p>ScanSettings gives you a few different modes. Choose wisely.</p>
<ul>
<li><p><strong>SCAN_MODE_LOW_POWER:</strong> This is your default, everyday mode. It scans in intervals, balancing discovery speed and battery life. Use this for most foreground scanning.</p>
</li>
<li><p><strong>SCAN_MODE_BALANCED:</strong> A middle ground. It scans more frequently than low power mode.</p>
</li>
<li><p><strong>SCAN_MODE_LOW_LATENCY:</strong> This is the "I need to find it NOW" mode. It scans continuously. This will find devices the fastest, but it will also drain your battery the fastest. Only use this for short, critical operations.</p>
</li>
<li><p><strong>SCAN_MODE_OPPORTUNISTIC:</strong> This is the ultimate passive mode. Your app doesn't trigger a scan at all. It just gets results if another app happens to be scanning. It uses zero extra battery, but you have no guarantee of getting results. Use this for non-critical background updates.</p>
</li>
</ul>
<p>And of course, if you're on AOSP 16 QPR2 or later, use setScanType(SCAN_TYPE_PASSIVE) whenever you don't need the scan response data. It's the new king of power efficiency.</p>
<h3 id="heading-4-use-hardware-filtering-and-batching">4. Use Hardware Filtering and Batching</h3>
<p>We covered this in the advanced section, but it's a best practice that's worth repeating. If you're looking for a specific device, use a ScanFilter. If you're doing a long-running scan, use setReportDelay() to batch your results. These two techniques offload the work to the power-efficient Bluetooth controller and let your app's code sleep, which is the number one way to save battery.</p>
<h3 id="heading-5-be-mindful-of-memory">5. Be Mindful of Memory</h3>
<p>Every ScanResult object that your app receives takes up memory. If you're in a crowded area with hundreds of BLE devices, and you're not using filters, your app can quickly get overwhelmed and run out of memory. This is another reason why filtering is so important. Only get the results you actually care about.</p>
<p>By following these rules, you can build a Bluetooth app that is not only powerful and feature-rich but also respectful of your user's device. You'll be a true Bluetooth sensei. Now, let's wrap things up and look to the future.</p>
<h2 id="heading-conclusion-the-future-is-passive-and-thats-okay">Conclusion: The Future is Passive (and That's Okay)</h2>
<p>We've been on quite a journey, haven't we? We've traveled back in time to the dark ages of Classic Bluetooth, witnessed the renaissance of BLE, and emerged into the brave new world of AOSP 16. We've learned to be silent ninjas with passive scanning, played detective with bond loss reasons, and mastered the art of speed dating with service UUIDs from advertisements.</p>
<p>If there's one big takeaway from all of this, it's that the future of Bluetooth on Android is smarter, more efficient, and a whole lot less frustrating. The Android team is clearly listening to the pain points of developers and giving us the tools we need to build better, more battery-friendly apps. The introduction of passive scanning isn't just a new feature – it's a change in philosophy. It's an acknowledgment that sometimes, the best way to communicate is to just listen.</p>
<p>As developers, these new tools empower us to move beyond the simple "connect and stream" use cases. We can now build sophisticated, context-aware applications that are constantly aware of their surroundings without turning our users' phones into expensive paperweights. The dream of a truly smart, seamlessly connected world is a little bit closer, and it's going to be built on the back of these power-efficient technologies.</p>
<p>So, what's next? The world of Bluetooth is always evolving. We have Bluetooth 5.4 with Auracast, mesh networking, and even more precise location-finding on the horizon. The one thing we can be sure of is that the tools will continue to get better, and the challenges will continue to get more interesting.</p>
<p>For now, take a moment to appreciate the progress we've made. The next time you start a Bluetooth scan and it just works, take a moment to thank the hardworking engineers who made it possible. And the next time your app's battery graph is a beautiful, flat line instead of a terrifying ski slope, give a little nod to the power of passive scanning.</p>
<p>The Bluetooth beast may never be fully tamed, but with AOSP 16, we've been given a much stronger leash. Now go forth and build amazing things. And for the love of all that is holy, remember to stop your scan.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How Bluetooth Low Energy Devices Work: GATT Services and Characteristics Explained ]]>
                </title>
                <description>
                    <![CDATA[ Every time you check your smartwatch for heart rate, read the battery level of wireless earbuds, unlock a Bluetooth smart lock, or watch sensor data stream into an app, you are experiencing the result of GATT working quietly in the background. GATT i... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-bluetooth-low-energy-devices-work-gatt-services-and-characteristics-explained/</link>
                <guid isPermaLink="false">69307a1e2b79515d02383320</guid>
                
                    <category>
                        <![CDATA[ Bluetooth GATT ]]>
                    </category>
                
                    <category>
                        <![CDATA[ bluetooth ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Bluetooth Low Energy ]]>
                    </category>
                
                    <category>
                        <![CDATA[ sensors ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nikheel Vishwas Savant ]]>
                </dc:creator>
                <pubDate>Wed, 03 Dec 2025 17:57:50 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1764781967963/2ccd66f7-3a5f-490f-af66-e1091ef4e34d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Every time you check your smartwatch for heart rate, read the battery level of wireless earbuds, unlock a Bluetooth smart lock, or watch sensor data stream into an app, you are experiencing the result of GATT working quietly in the background.</p>
<p>GATT is the Generic Attribute Profile, and it provides the structure that makes Bluetooth Low Energy (BLE) devices exchange meaningful information. Without GATT, Bluetooth radios would simply move bits back and forth with no agreed format or interpretation. With GATT, devices can communicate in a predictable and understandable language.</p>
<p>Think of Bluetooth radios as two people speaking to each other in a room. The radio waves allow them to talk, but without a common language, the exchange is useless. GATT provides that common language. It defines the vocabulary, grammar, and sentence structure. Instead of random binary, we get clear messages like Heart Rate equals 78 bpm, Battery equals 92 percent, or Light Switch equals ON.</p>
<p>Because of GATT, devices from different manufacturers are able to interoperate. A Polar heart rate strap can connect to a Peloton bike. A Samsung phone can read temperature from a medical sensor. An Apple Watch can control Philips Hue smart lights. These devices do not share hardware, companies, or operating systems, yet they can cooperate because GATT defines a universal structure for exposing and accessing data.</p>
<p>Once you understand GATT, Bluetooth becomes far less mysterious. Communication becomes a matter of reading or writing values in a small structured database. Debugging becomes logical. BLE app development becomes straightforward. And building your own IoT device becomes achievable, even for beginners.</p>
<p>In this article, we’ll walk through GATT in depth. You’ll learn how devices organize data into services and characteristics, how phones discover and read values, how notifications deliver real time updates, and how embedded and Android code interact with GATT. By the end, you’ll be able to design a GATT database, understand BLE logs, and confidently build BLE applications.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you continue, you should have a basic understanding of:</p>
<ul>
<li><p>What Bluetooth is at a high level (no deep protocol knowledge needed)</p>
</li>
<li><p>How mobile apps connect to external devices (Android, iOS, or embedded)</p>
</li>
<li><p>Very basic programming concepts (variables, functions, objects)</p>
</li>
</ul>
<p>You’ll also need:</p>
<ul>
<li><p>A smartphone or laptop with Bluetooth Low Energy support</p>
</li>
<li><p>A BLE-compatible development board or device (optional, but helpful if you want to try the code examples)</p>
</li>
<li><p>A BLE debugging/scanning app such as nRF Connect, LightBlue, or BLE Scanner</p>
</li>
</ul>
<p>If you’re completely new to BLE, don’t worry – this article walks through each concept step-by-step.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-gatt">What is GATT</a>?</p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-are-services-and-why-do-they-matter">What Are Services and Why Do They Matter?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-are-characteristics-and-how-they-work">What are Characteristics and How Do They Work</a>?</p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-design-a-gatt-profile-for-a-smart-plant-monitor">How to Design a GATT Profile for a Smart Plant Monitor</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-gatt">What is GATT?</h2>
<p>GATT stands for Generic Attribute Profile. It is the structured communication model used by Bluetooth Low Energy devices to exchange data in a clear and organized format.</p>
<p>GATT defines how data is stored, formatted, accessed, updated, and transmitted across BLE connections. Without GATT, Bluetooth devices would only exchange unstructured binary information that has no consistent meaning. With GATT, devices can share values such as battery percentage, heart rate, temperature readings, and status commands in a well-defined way.</p>
<h3 id="heading-gatt-client-and-server-roles">GATT Client and Server Roles</h3>
<p>All communication in BLE occurs between two roles. The GATT Server owns and exposes the data. The GATT Client requests, reads, writes, or subscribes to that data. The Server holds a database of values that the Client interacts with. A smartwatch usually acts as a GATT Server because it holds sensor values. A smartphone usually acts as a GATT Client because it retrieves that information.</p>
<p>These roles can switch depending on the task. For example, during a firmware update, the phone acts as the GATT Server providing firmware blocks and the wearable acts as the GATT Client requesting them.</p>
<h3 id="heading-services-characteristics-and-uuids">Services, Characteristics, and UUIDs</h3>
<p>The GATT Server stores its data in a structured database made up of Services and Characteristics. A Service is a container that groups related information. A Characteristic is a single data value inside a service.</p>
<p>For example, the Battery Service contains the Battery Level characteristic. The Heart Rate Service contains the Heart Rate Measurement characteristic.</p>
<p>All services and characteristics are identified using UUID values so that every device knows how to locate them. Standard Bluetooth SIG defined services such as Heart Rate and Battery use 16 bit UUIDs. Custom proprietary features use 128 bit UUIDs.</p>
<h3 id="heading-example-gatt-database-layout">Example GATT Database Layout</h3>
<p>Here is a conceptual breakdown of a simple GATT database layout:</p>
<pre><code class="lang-typescript">Service: Battery Service (UUID <span class="hljs-number">0x180F</span>)
    Characteristic: Battery Level (UUID <span class="hljs-number">0x2A19</span>)
    Example value: <span class="hljs-number">92</span> percent
</code></pre>
<h3 id="heading-example-reading-a-gatt-characteristic-from-android">Example: Reading a GATT Characteristic from Android</h3>
<p>When a phone connects to a BLE device and acts as the client, it performs a sequence of steps. It connects to the device, discovers services, finds the characteristic of interest, and reads or subscribes to its value.</p>
<p>The following complete Java example shows an Android app acting as a <strong>GATT Client</strong>, discovering services and reading the battery level.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> BleGattClientManager {

    <span class="hljs-keyword">private</span> BluetoothGatt bluetoothGatt;

    <span class="hljs-keyword">private</span> final BluetoothGattCallback gattCallback = <span class="hljs-keyword">new</span> BluetoothGattCallback() {

        <span class="hljs-meta">@Override</span>
        <span class="hljs-keyword">public</span> <span class="hljs-built_in">void</span> onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            <span class="hljs-keyword">if</span> (newState == BluetoothProfile.STATE_CONNECTED) {
                Log.d(TAG, <span class="hljs-string">"Connected to device. Discovering services."</span>);
                gatt.discoverServices();
            }
        }

        <span class="hljs-meta">@Override</span>
        <span class="hljs-keyword">public</span> <span class="hljs-built_in">void</span> onServicesDiscovered(BluetoothGatt gatt, int status) {
            UUID BATTERY_SERVICE_UUID =
                    UUID.fromString(<span class="hljs-string">"0000180F-0000-1000-8000-00805F9B34FB"</span>);
            UUID BATTERY_LEVEL_UUID =
                    UUID.fromString(<span class="hljs-string">"00002A19-0000-1000-8000-00805F9B34FB"</span>);

            BluetoothGattService service = gatt.getService(BATTERY_SERVICE_UUID);
            <span class="hljs-keyword">if</span> (service != <span class="hljs-literal">null</span>) {
                BluetoothGattCharacteristic characteristic =
                        service.getCharacteristic(BATTERY_LEVEL_UUID);
                <span class="hljs-keyword">if</span> (characteristic != <span class="hljs-literal">null</span>) {
                    gatt.readCharacteristic(characteristic);
                }
            }
        }

        <span class="hljs-meta">@Override</span>
        <span class="hljs-keyword">public</span> <span class="hljs-built_in">void</span> onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic characteristic,
                                         int status) {
            <span class="hljs-keyword">if</span> (status == BluetoothGatt.GATT_SUCCESS) {
                int batteryValue = characteristic.getIntValue(
                        BluetoothGattCharacteristic.FORMAT_UINT8, <span class="hljs-number">0</span>);
                Log.d(TAG, <span class="hljs-string">"Battery Level: "</span> + batteryValue + <span class="hljs-string">" percent"</span>);
            }
        }
    };

    <span class="hljs-keyword">public</span> <span class="hljs-built_in">void</span> connect(Context context, BluetoothDevice device) {
        bluetoothGatt = device.connectGatt(context, <span class="hljs-literal">false</span>, gattCallback);
    }
}
</code></pre>
<p>This Java class represents a Bluetooth Low Energy GATT client that connects to a BLE device and reads the battery level characteristic. The class holds a <code>BluetoothGatt</code> object that represents the active BLE connection. The <code>BluetoothGattCallback</code> handles events during the connection lifecycle.</p>
<p>When the device connection state changes and the new state indicates that the device is connected, the callback triggers service discovery by calling <code>gatt.discoverServices()</code>.</p>
<p>After the services are discovered, the callback receives <code>onServicesDiscovered</code>, where two standard UUIDs are defined: the Battery Service with UUID <code>0000180F-0000-1000-8000-00805F9B34FB</code> and the Battery Level characteristic with UUID <code>00002A19-0000-1000-8000-00805F9B34FB</code>. The client retrieves the Battery Service using <code>gatt.getService</code>, then retrieves the Battery Level characteristic using <code>getCharacteristic</code>.</p>
<p>If both objects are found, the client calls <code>gatt.readCharacteristic</code>, which sends a read request to the server. When the server responds, <code>onCharacteristicRead</code> is invoked. If the response is successful, the characteristic value is extracted using <code>getIntValue</code> as an unsigned 8 bit integer at offset zero, producing a percentage from zero to one hundred. This value is printed to the log.</p>
<p>The <code>connect</code> method initiates the connection by calling <code>device.connectGatt</code>, which begins the communication and links all callbacks.</p>
<p>In summary, the flow is simple: connect to the device, discover services, locate the Battery Service, read the Battery Level characteristic, and print the result. This code shows the core pattern of how a BLE client interacts with a GATT server to request information.</p>
<h3 id="heading-android-as-a-gatt-server">Android as a GATT Server</h3>
<p>The Android device can also act as a <strong>GATT Server</strong>. This is useful when the phone needs to expose its own characteristics that other BLE devices read or write, such as setup information, commands, or configuration data.</p>
<p>Below is a complete example of a custom GATT Server written in Java. It exposes a custom service and a custom characteristic that allows a BLE client to read and write values.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> BleGattServerManager {

    <span class="hljs-keyword">private</span> BluetoothGattServer gattServer;

    <span class="hljs-keyword">private</span> final UUID SERVICE_UUID =
            UUID.fromString(<span class="hljs-string">"12345678-1234-5678-1234-56789abcdef0"</span>);

    <span class="hljs-keyword">private</span> final UUID CHARACTERISTIC_UUID =
            UUID.fromString(<span class="hljs-string">"abcdef01-1234-5678-1234-56789abcdef0"</span>);

    <span class="hljs-keyword">private</span> final BluetoothGattServerCallback serverCallback =
            <span class="hljs-keyword">new</span> BluetoothGattServerCallback() {

        <span class="hljs-meta">@Override</span>
        <span class="hljs-keyword">public</span> <span class="hljs-built_in">void</span> onConnectionStateChange(BluetoothDevice device,
                                            int status,
                                            int newState) {
            Log.d(TAG, <span class="hljs-string">"Device connected: "</span> + device.getAddress());
        }

        <span class="hljs-meta">@Override</span>
        <span class="hljs-keyword">public</span> <span class="hljs-built_in">void</span> onCharacteristicReadRequest(BluetoothDevice device,
                                                int requestId,
                                                int offset,
                                                BluetoothGattCharacteristic characteristic) {

            byte[] value = <span class="hljs-string">"HELLO_ANDROID_SERVER"</span>.getBytes(StandardCharsets.UTF_8);
            gattServer.sendResponse(device, requestId,
                    BluetoothGatt.GATT_SUCCESS, offset, value);
        }

        <span class="hljs-meta">@Override</span>
        <span class="hljs-keyword">public</span> <span class="hljs-built_in">void</span> onCharacteristicWriteRequest(BluetoothDevice device,
                                                 int requestId,
                                                 BluetoothGattCharacteristic characteristic,
                                                 <span class="hljs-built_in">boolean</span> preparedWrite,
                                                 <span class="hljs-built_in">boolean</span> responseNeeded,
                                                 int offset,
                                                 byte[] value) {

            <span class="hljs-built_in">String</span> received = <span class="hljs-keyword">new</span> <span class="hljs-built_in">String</span>(value, StandardCharsets.UTF_8);
            Log.d(TAG, <span class="hljs-string">"Client wrote value: "</span> + received);

            gattServer.sendResponse(device, requestId,
                    BluetoothGatt.GATT_SUCCESS, offset, value);
        }
    };

    <span class="hljs-keyword">public</span> <span class="hljs-built_in">void</span> startServer(Context context) {
        BluetoothManager bluetoothManager =
                (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);

        gattServer = bluetoothManager.openGattServer(context, serverCallback);

        BluetoothGattService customService =
                <span class="hljs-keyword">new</span> BluetoothGattService(SERVICE_UUID,
                        BluetoothGattService.SERVICE_TYPE_PRIMARY);

        BluetoothGattCharacteristic customCharacteristic =
                <span class="hljs-keyword">new</span> BluetoothGattCharacteristic(
                        CHARACTERISTIC_UUID,
                        BluetoothGattCharacteristic.PROPERTY_READ |
                        BluetoothGattCharacteristic.PROPERTY_WRITE,
                        BluetoothGattCharacteristic.PERMISSION_READ |
                        BluetoothGattCharacteristic.PERMISSION_WRITE
                );

        customService.addCharacteristic(customCharacteristic);
        gattServer.addService(customService);
    }
}
</code></pre>
<p>This Java class implements a Bluetooth Low Energy GATT Server on Android, meaning it exposes a service and characteristic that another BLE device can read or write.</p>
<p>The class holds a <code>BluetoothGattServer</code> instance which is created when the server starts. Two UUIDs are defined, one for the custom service and one for the custom characteristic. The <code>BluetoothGattServerCallback</code> handles incoming events from any remote BLE client that connects to this server.</p>
<p>When a device connects, <code>onConnectionStateChange</code> logs the connection. When a client sends a read request on the characteristic, <code>onCharacteristicReadRequest</code> responds by sending back a static string value, in this case the bytes of the text <code>HELLO_ANDROID_SERVER</code>, using <code>sendResponse</code> with a success status.</p>
<p>When the client writes data to the characteristic, <code>onCharacteristicWriteRequest</code> converts the incoming byte array to a string, logs what was written, and returns a success response to acknowledge that the server accepted the new value.</p>
<p>The <code>startServer</code> method initializes the GATT Server by requesting the <code>BluetoothManager</code>, opening the server, creating a custom primary service, and adding a characteristic to that service with both read and write properties and permissions. The service is then registered on the server through <code>addService</code>, which makes it available to any BLE client that connects.</p>
<p>In summary, this code demonstrates how an Android device can behave like a BLE peripheral and expose a custom readable and writable characteristic that other devices can interact with. This forms the foundation for features like configuration setup, provisioning, remote control commands, or device to device communication.</p>
<p>This example shows that GATT is simply a structured database of readable and writable values that represent meaningful application behavior. Whether the task is battery level reporting, real time health monitoring, remote control of smart devices, or secure provisioning, the exchange always follows this same pattern.</p>
<p>Understanding GATT at this level is the foundation for all Bluetooth Low Energy engineering and problem solving.</p>
<h2 id="heading-what-are-services-and-why-do-they-matter">What Are Services and Why Do They Matter?</h2>
<h3 id="heading-services-as-capability-containers">Services as Capability Containers</h3>
<p>A Service in GATT is a logical container that groups related data. A single Bluetooth device can expose many services, and each service focuses on one capability or functional category.</p>
<p>For example, a smartwatch may expose a Heart Rate Service, a Battery Service, a Current Time Service, and a Device Information Service. A smart bulb may expose a Lighting Control Service with characteristics to change brightness and color. A medical thermometer may expose a Health Thermometer Service that continuously streams temperature values.</p>
<p>Services exist to separate different categories of information so that any client device can immediately understand what functions are available and how to interact with them.</p>
<p>A service does not hold raw values itself. Instead, it organizes characteristics, which contain the actual data elements. The service only defines the grouping and the type of behavior associated with that group. This design makes BLE communication extremely scalable. Applications only need to know which service to target, then they can discover and manipulate the characteristics inside it.</p>
<h3 id="heading-standard-vs-custom-services">Standard vs Custom Services</h3>
<p>Bluetooth SIG defines many standard services for interoperability. For example, the Battery Service exposes battery level in a characteristic called Battery Level. The Heart Rate Service exposes heart rate values so that any fitness application can subscribe to it. These standard services allow devices from different manufacturers to work together without custom integration.</p>
<p>Anyone building a custom device can also define their own original service using a 128 bit UUID. The structure is the same whether it is standard or custom.</p>
<h3 id="heading-example-a-device-with-multiple-services">Example: A Device with Multiple Services</h3>
<p>Below is an example representation of a device that exposes two services at the same time:</p>
<pre><code class="lang-java">Service: <span class="hljs-function">Battery <span class="hljs-title">Service</span> <span class="hljs-params">(UUID <span class="hljs-number">0x180F</span>)</span>
    Characteristic: Battery <span class="hljs-title">Level</span> <span class="hljs-params">(UUID <span class="hljs-number">0x2A19</span>)</span>
    Current value: 92 percent

Service: Heart Rate <span class="hljs-title">Service</span> <span class="hljs-params">(UUID <span class="hljs-number">0x180D</span>)</span>
    Characteristic: Heart Rate <span class="hljs-title">Measurement</span> <span class="hljs-params">(UUID <span class="hljs-number">0x2A37</span>)</span>
    Current value: 78 bpm</span>
</code></pre>
<p>An Android phone acting as a client can discover these services and then interact with characteristics inside them. Discovery is always the first step after establishing a BLE connection.</p>
<h3 id="heading-discovering-services-in-android">Discovering Services in Android</h3>
<p>The following example shows how to list all available services and log them in Java.</p>
<pre><code class="lang-java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onServicesDiscovered</span><span class="hljs-params">(BluetoothGatt gatt, <span class="hljs-keyword">int</span> status)</span> </span>{
    <span class="hljs-keyword">for</span> (BluetoothGattService service : gatt.getServices()) {
        Log.d(TAG, <span class="hljs-string">"Service discovered: "</span> + service.getUuid().toString());
    }
}
</code></pre>
<p>This method is called automatically after the BLE client has finished discovering all services exposed by the remote GATT server. When a connection is established, the client initiates a service discovery procedure, and once the server responds with the complete list of available services, the Android stack triggers <code>onServicesDiscovered</code>.</p>
<p>Inside this callback, the code iterates through every <code>BluetoothGattService</code> returned by <code>gatt.getServices()</code>, which represents all services implemented by the connected device. For each service in that list, the code prints its UUID to the log. This output helps developers inspect what services exist on the device, confirm that expected services such as Heart Rate, Battery, or a custom service are present, and identify the correct UUIDs needed for reading or writing characteristics later.</p>
<p>This method is especially useful during development or debugging, because it allows you to verify that a device correctly exposes its GATT database and that the client can access the list of services before attempting to interact with any characteristics.</p>
<h3 id="heading-notifications-for-continuously-changing-data">Notifications for Continuously Changing Data</h3>
<p>Once the service is found, the next step is to read or subscribe to its characteristics. Some characteristics contain static or rarely changing values, which makes direct reads appropriate. Others, such as heart rate or temperature, change continuously, and should use notifications.</p>
<h4 id="heading-why-notifications-matter-in-services">Why Notifications Matter in Services</h4>
<p>Notifications allow a device to receive updates automatically whenever the value changes instead of repeatedly reading the characteristic. This reduces energy usage and latency, which is essential for wearables and sensors.</p>
<p>Below is a Java example showing how to enable notifications for the Heart Rate Measurement characteristic:</p>
<pre><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">enableHeartRateNotifications</span><span class="hljs-params">(BluetoothGatt gatt)</span> </span>{

    UUID HEART_RATE_SERVICE_UUID =
            UUID.fromString(<span class="hljs-string">"0000180D-0000-1000-8000-00805F9B34FB"</span>);
    UUID HEART_RATE_MEASUREMENT_UUID =
            UUID.fromString(<span class="hljs-string">"00002A37-0000-1000-8000-00805F9B34FB"</span>);

    BluetoothGattService service = gatt.getService(HEART_RATE_SERVICE_UUID);
    <span class="hljs-keyword">if</span> (service != <span class="hljs-keyword">null</span>) {
        BluetoothGattCharacteristic characteristic =
                service.getCharacteristic(HEART_RATE_MEASUREMENT_UUID);

        <span class="hljs-keyword">if</span> (characteristic != <span class="hljs-keyword">null</span>) {
            gatt.setCharacteristicNotification(characteristic, <span class="hljs-keyword">true</span>);

            BluetoothGattDescriptor descriptor =
                    characteristic.getDescriptor(
                            UUID.fromString(<span class="hljs-string">"00002902-0000-1000-8000-00805F9B34FB"</span>)
                    );

            <span class="hljs-keyword">if</span> (descriptor != <span class="hljs-keyword">null</span>) {
                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                gatt.writeDescriptor(descriptor);
            }
        }
    }
}
</code></pre>
<h4 id="heading-enabling-notifications-in-android">Enabling Notifications in Android</h4>
<p>This method enables notifications for the Heart Rate Measurement characteristic so that the device can push new values automatically whenever the heart rate changes.</p>
<p>It begins by defining the UUIDs for the Heart Rate Service and the Heart Rate Measurement characteristic. Using <code>gatt.getService</code>, it retrieves the Heart Rate Service from the connected device. If that service exists, it locates the Heart Rate Measurement characteristic within it. Once the characteristic is found, the method enables local notification handling on the Android side using <code>setCharacteristicNotification</code>, which prepares the client to receive asynchronous updates.</p>
<p>But enabling local notifications is not enough. The BLE specification requires writing to a special descriptor called the Client Characteristic Configuration Descriptor, identified by UUID <code>00002902-0000-1000-8000-00805F9B34FB</code>, so that the remote device also knows the client wants updates. The method retrieves this descriptor, sets its value to <code>ENABLE_NOTIFICATION_VALUE</code>, and writes it using <code>writeDescriptor</code>, which sends a request over the air to the server telling it to start sending notifications.</p>
<p>After this sequence completes, updates begin arriving automatically in the <code>onCharacteristicChanged</code> callback whenever the heart rate changes, without needing repeated read requests.</p>
<p>This is the preferred BLE pattern for continuous sensor data such as heart rate, temperature, step count, or motion values because it saves power and provides real time responsiveness.</p>
<p>When the device begins sending notifications, updates are received in the callback below. The values arrive whenever they change, making this very efficient for streaming.</p>
<pre><code class="lang-java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCharacteristicChanged</span><span class="hljs-params">(BluetoothGatt gatt,
                                    BluetoothGattCharacteristic characteristic)</span> </span>{

    UUID HEART_RATE_MEASUREMENT_UUID =
            UUID.fromString(<span class="hljs-string">"00002A37-0000-1000-8000-00805F9B34FB"</span>);

    <span class="hljs-keyword">if</span> (characteristic.getUuid().equals(HEART_RATE_MEASUREMENT_UUID)) {
        <span class="hljs-keyword">int</span> flag = characteristic.getProperties();
        <span class="hljs-keyword">int</span> format;
        <span class="hljs-keyword">if</span> ((flag &amp; <span class="hljs-number">0x01</span>) != <span class="hljs-number">0</span>) {
            format = BluetoothGattCharacteristic.FORMAT_UINT16;
        } <span class="hljs-keyword">else</span> {
            format = BluetoothGattCharacteristic.FORMAT_UINT8;
        }
        <span class="hljs-keyword">int</span> heartRate = characteristic.getIntValue(format, <span class="hljs-number">1</span>);
        Log.d(TAG, <span class="hljs-string">"Heart Rate: "</span> + heartRate + <span class="hljs-string">" bpm"</span>);
    }
}
</code></pre>
<p>This method is called automatically whenever the Bluetooth device sends a notification indicating that the value of a characteristic has changed.</p>
<p>In this example, the method handles updates to the Heart Rate Measurement characteristic. The code first checks whether the characteristic that triggered the callback matches the Heart Rate Measurement UUID <code>00002A37-0000-1000-8000-00805F9B34FB</code>, ensuring that only relevant updates are processed.</p>
<p>The heart rate data can be encoded in either one or two bytes, depending on the flags defined in the characteristic properties. The method reads these flags and determines the correct format to use when decoding the heart rate value. If the least significant bit is set, the heart rate uses a 16 bit format. Otherwise, it uses a single 8 bit format.</p>
<p>After selecting the appropriate format, the method extracts the heart rate value using <code>getIntValue</code>, beginning at offset 1 because byte 0 contains the flags.</p>
<p>Finally, the value is printed to the log as beats per minute. This method is typically called repeatedly, such as once per second, so the log receives live heart rate updates in real time.</p>
<p>This approach demonstrates how notifications deliver continuous sensor data without repeatedly polling the device, which reduces latency and power usage for both the client and server.</p>
<p>This example demonstrates how the client retrieves real time sensor data using subscription instead of polling. The same mechanism is used for air quality sensors, smart home lighting brightness notifications, industrial temperature monitors, and more.</p>
<p>To summarize this section, a Service is a structured container that organizes related data and is essential for exposing abilities and functionality over BLE. Understanding how to discover and interact with services is the first major step toward building or debugging real Bluetooth applications.</p>
<h2 id="heading-what-are-characteristics-and-how-do-they-work">What Are Characteristics and How Do They Work?</h2>
<h3 id="heading-the-role-of-a-characteristic">The Role of a Characteristic</h3>
<p>If a Service is a folder, then a Characteristic is a file inside that folder that actually holds the content. In GATT, the Characteristic is where the real data lives. Almost everything your application cares about will eventually be read from, written to, or subscribed to on a characteristic.</p>
<h3 id="heading-the-four-parts-of-a-characteristic">The Four Parts of a Characteristic</h3>
<p>A characteristic is more than just a single number. It has four important parts. First, it has a UUID that identifies what it represents. It also has a value that stores the actual bytes. Then it has properties that describe what operations are allowed, such as read, write, or notify. And finally, it has permissions that control who can access it and under what security level. Understanding these pieces is the key to working confidently with BLE.</p>
<p>The UUID tells you what kind of data is inside the characteristic. For example, a standard Battery Level characteristic uses the UUID 0x2A19 and always contains a single byte that represents a percentage from zero to one hundred. A Heart Rate Measurement characteristic uses UUID 0x2A37 and packs heart rate and flags into a structured format. Custom characteristics use 128 bit UUIDs that developers define themselves.</p>
<p>The value is simply a sequence of bytes. On the wire, Bluetooth does not know about integers, floats, or strings. It only sees bytes. On the Android side, the <code>BluetoothGattCharacteristic</code> class helps interpret those bytes as different types. It provides helper methods such as <code>getIntValue</code>, <code>getFloatValue</code>, and <code>getStringValue</code> so that you can decode the data more easily.</p>
<p>The properties of a characteristic describe what kind of operations the client can perform. The most common properties are Read, Write, Notify, and Indicate.</p>
<p>Read means a client can ask the server to return the current value. Write means a client can send a new value to the server. Notify means the server can send updates to the client whenever the value changes. Indicate is similar to Notify, but with an extra confirmation. A characteristic may have one or many properties combined.</p>
<p>Permissions are related but slightly different. They focus on access control and security. For example, a characteristic may require encryption or authenticated pairing before it can be read or written. The Android <code>BluetoothGattCharacteristic</code> object contains these permission flags so that the stack enforces them correctly.</p>
<h3 id="heading-example-defining-a-custom-led-characteristic">Example: Defining a Custom LED Characteristic</h3>
<p>Let’s walk through a concrete example. Imagine a custom device that exposes a characteristic to control an LED state. The LED should be either ON or OFF. The characteristic needs to support both read and write, because the client may want to read the current state and also change it.</p>
<p>On the Android GATT Server side, you would define such a characteristic like this:</p>
<pre><code class="lang-java">UUID SERVICE_UUID =
        UUID.fromString(<span class="hljs-string">"12345678-1234-5678-1234-56789abcdef0"</span>);
UUID LED_CHAR_UUID =
        UUID.fromString(<span class="hljs-string">"abcdef01-1234-5678-1234-56789abcdef0"</span>);

BluetoothGattService ledService =
        <span class="hljs-keyword">new</span> BluetoothGattService(SERVICE_UUID,
                BluetoothGattService.SERVICE_TYPE_PRIMARY);

BluetoothGattCharacteristic ledCharacteristic =
        <span class="hljs-keyword">new</span> BluetoothGattCharacteristic(
                LED_CHAR_UUID,
                BluetoothGattCharacteristic.PROPERTY_READ |
                BluetoothGattCharacteristic.PROPERTY_WRITE,
                BluetoothGattCharacteristic.PERMISSION_READ |
                BluetoothGattCharacteristic.PERMISSION_WRITE
        );

<span class="hljs-comment">// Initial value</span>
ledCharacteristic.setValue(<span class="hljs-string">"OFF"</span>.getBytes(StandardCharsets.UTF_8));
ledService.addCharacteristic(ledCharacteristic);
gattServer.addService(ledService);
</code></pre>
<p>This code defines a custom GATT Service and a custom GATT Characteristic on an Android device acting as a Bluetooth Low Energy GATT Server.</p>
<p>Two UUIDs are created using <code>UUID.fromString</code>, one representing the custom service and the other representing the characteristic that belongs to that service. A new <code>BluetoothGattService</code> instance is then created, marked as a primary service to indicate that it is a main functional component rather than a secondary helper service.</p>
<p>Inside that service, a <code>BluetoothGattCharacteristic</code> object is created using the second UUID, and it’s configured to allow both reads and writes by a remote BLE client. The property flags indicate that a client can request the current value and can also send updates, and the permission flags define that both operations are permitted.</p>
<p>The characteristic is given an initial value of the string <code>"OFF"</code> encoded as bytes, which might represent the current state of a remote controlled LED, device mode, or some other configuration setting.</p>
<p>The characteristic is then added to the service, and finally the fully defined service is added to the GATT server using <code>gattServer.addService</code>, making it visible to any BLE client that connects.</p>
<p>At this point, another device can read the value <code>"OFF"</code> or write a new value such as <code>"ON"</code>, which the server could then use to trigger real behavior, such as toggling actual hardware.</p>
<h3 id="heading-handling-reads-and-writes-on-the-server">Handling Reads and Writes on the Server</h3>
<h4 id="heading-server-side-handlers">Server-Side Handlers</h4>
<p>On the GATT Server side, you must also respond to read and write requests. This happens inside <code>BluetoothGattServerCallback</code>.</p>
<pre><code class="lang-java"><span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> BluetoothGattServerCallback serverCallback =
        <span class="hljs-keyword">new</span> BluetoothGattServerCallback() {

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCharacteristicReadRequest</span><span class="hljs-params">(BluetoothDevice device,
                                            <span class="hljs-keyword">int</span> requestId,
                                            <span class="hljs-keyword">int</span> offset,
                                            BluetoothGattCharacteristic characteristic)</span> </span>{

        <span class="hljs-keyword">byte</span>[] currentValue = characteristic.getValue();
        gattServer.sendResponse(device,
                requestId,
                BluetoothGatt.GATT_SUCCESS,
                offset,
                currentValue);
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCharacteristicWriteRequest</span><span class="hljs-params">(BluetoothDevice device,
                                             <span class="hljs-keyword">int</span> requestId,
                                             BluetoothGattCharacteristic characteristic,
                                             <span class="hljs-keyword">boolean</span> preparedWrite,
                                             <span class="hljs-keyword">boolean</span> responseNeeded,
                                             <span class="hljs-keyword">int</span> offset,
                                             <span class="hljs-keyword">byte</span>[] value)</span> </span>{

        String received = <span class="hljs-keyword">new</span> String(value, StandardCharsets.UTF_8);
        Log.d(TAG, <span class="hljs-string">"LED characteristic write: "</span> + received);

        characteristic.setValue(value);

        <span class="hljs-keyword">if</span> (<span class="hljs-string">"ON"</span>.equalsIgnoreCase(received)) {
            turnLedOn();
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-string">"OFF"</span>.equalsIgnoreCase(received)) {
            turnLedOff();
        } <span class="hljs-keyword">else</span> {
            Log.e(TAG, <span class="hljs-string">"Unhandled case!"</span>);
            <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-keyword">if</span> (responseNeeded) {
            gattServer.sendResponse(device,
                    requestId,
                    BluetoothGatt.GATT_SUCCESS,
                    offset,
                    value);
        }
    }
};
</code></pre>
<p>This callback handles read and write requests coming from a remote BLE client interacting with the custom LED characteristic on the GATT Server.</p>
<p>When a client performs a read operation, <code>onCharacteristicReadRequest</code> is triggered. The method retrieves the current stored value from the characteristic using <code>getValue()</code> and returns it to the client by calling <code>sendResponse</code> with a success status. This means whatever value was last set, such as <code>"ON"</code> or <code>"OFF"</code>, is sent back to the requesting device.</p>
<p>When a client performs a write operation, <code>onCharacteristicWriteRequest</code> is called. The method converts the incoming byte array into a string so that the server can interpret the command. It logs the received text, sets the new value into the characteristic using <code>setValue</code>, and then checks whether the string equals <code>"ON"</code> or <code>"OFF"</code>. Depending on the value, it calls either <code>turnLedOn()</code> or <code>turnLedOff()</code>, which would typically control real hardware or trigger an action inside the application.</p>
<p>If the client requested a response, the server sends back a confirmation by calling <code>sendResponse</code> with <code>GATT_SUCCESS</code>, acknowledging that the write completed successfully. This callback demonstrates how interactive BLE control works: the server receives a command, updates internal state, performs a real action, and reports status back to the client.</p>
<p>Here, the server reads whatever value is stored in the characteristic upon a read request and sends it back to the client. When the client writes a new value, the server decodes the bytes as a string and updates internal state, including physical behavior like toggling the LED.</p>
<h3 id="heading-reading-and-writing-from-the-client">Reading and Writing from the Client</h3>
<h4 id="heading-client-side-handlers">Client-Side Handlers</h4>
<p>On the client side, a typical Android app needs to read and write to this same characteristic. The code for the client looks similar to what we saw in earlier sections, but now it uses the custom UUIDs.</p>
<h3 id="heading-reading-a-custom-characteristic-client">Reading a Custom Characteristic (Client)</h3>
<p>Reading the LED state from the client:</p>
<pre><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">readLedState</span><span class="hljs-params">(BluetoothGatt gatt)</span> </span>{
    UUID SERVICE_UUID =
            UUID.fromString(<span class="hljs-string">"12345678-1234-5678-1234-56789abcdef0"</span>);
    UUID LED_CHAR_UUID =
            UUID.fromString(<span class="hljs-string">"abcdef01-1234-5678-1234-56789abcdef0"</span>);

    BluetoothGattService service = gatt.getService(SERVICE_UUID);
    <span class="hljs-keyword">if</span> (service != <span class="hljs-keyword">null</span>) {
        BluetoothGattCharacteristic ledChar = service.getCharacteristic(LED_CHAR_UUID);
        <span class="hljs-keyword">if</span> (ledChar != <span class="hljs-keyword">null</span>) {
            gatt.readCharacteristic(ledChar);
        }
    }
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCharacteristicRead</span><span class="hljs-params">(BluetoothGatt gatt,
                                 BluetoothGattCharacteristic characteristic,
                                 <span class="hljs-keyword">int</span> status)</span> </span>{
    <span class="hljs-keyword">if</span> (status == BluetoothGatt.GATT_SUCCESS) {
        UUID LED_CHAR_UUID =
                UUID.fromString(<span class="hljs-string">"abcdef01-1234-5678-1234-56789abcdef0"</span>);
        <span class="hljs-keyword">if</span> (LED_CHAR_UUID.equals(characteristic.getUuid())) {
            String value = <span class="hljs-keyword">new</span> String(characteristic.getValue(), StandardCharsets.UTF_8);
            Log.d(TAG, <span class="hljs-string">"LED state is: "</span> + value);
        }
    }
}
</code></pre>
<p>This code shows how a Bluetooth Low Energy client reads the current state of a custom LED characteristic from a GATT server. The <code>readLedState</code> method begins by defining the UUIDs for the custom service and the LED characteristic so that the client knows exactly where to look inside the server’s GATT database.</p>
<p>It retrieves the service using <code>gatt.getService</code>, and if the service exists, it retrieves the LED characteristic using <code>getCharacteristic</code>. If that characteristic is found, the client calls <code>readCharacteristic</code>, which sends a read request to the remote device over BLE. Once the server responds, the callback method <code>onCharacteristicRead</code> is triggered.</p>
<p>This method first checks that the read was successful by confirming that the status equals <code>GATT_SUCCESS</code>. It then verifies that the characteristic being read is indeed the LED characteristic by comparing UUIDs. If it matches, the code converts the characteristic’s byte array into a string, which contains either <code>"ON"</code> or <code>"OFF"</code>, and logs the current state.</p>
<p>This flow demonstrates how a BLE client reads stored values from a peripheral device and responds when the server returns the data, forming the basis for real world interactions such as checking the status of a smart light, a switch, or any sensor value exposed through a custom characteristic.</p>
<h3 id="heading-writing-to-a-custom-characteristic-client">Writing to a Custom Characteristic (Client)</h3>
<p>Writing a new LED state from the client:</p>
<pre><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">writeLedState</span><span class="hljs-params">(BluetoothGatt gatt, String newState)</span> </span>{
    UUID SERVICE_UUID =
            UUID.fromString(<span class="hljs-string">"12345678-1234-5678-1234-56789abcdef0"</span>);
    UUID LED_CHAR_UUID =
            UUID.fromString(<span class="hljs-string">"abcdef01-1234-5678-1234-56789abcdef0"</span>);

    BluetoothGattService service = gatt.getService(SERVICE_UUID);
    <span class="hljs-keyword">if</span> (service != <span class="hljs-keyword">null</span>) {
        BluetoothGattCharacteristic ledChar = service.getCharacteristic(LED_CHAR_UUID);
        <span class="hljs-keyword">if</span> (ledChar != <span class="hljs-keyword">null</span>) {
            ledChar.setValue(newState.getBytes(StandardCharsets.UTF_8));
            gatt.writeCharacteristic(ledChar);
        }
    }
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCharacteristicWrite</span><span class="hljs-params">(BluetoothGatt gatt,
                                  BluetoothGattCharacteristic characteristic,
                                  <span class="hljs-keyword">int</span> status)</span> </span>{
    <span class="hljs-keyword">if</span> (status == BluetoothGatt.GATT_SUCCESS) {
        Log.d(TAG, <span class="hljs-string">"LED state write completed"</span>);
    }
}
</code></pre>
<p>This code demonstrates how a Bluetooth Low Energy client sends a command to update the LED state on a GATT server device.</p>
<p>The <code>writeLedState</code> method begins by defining the UUIDs for the custom service and LED characteristic, then retrieves the service from the connected GATT server. If the service is found, it accesses the LED characteristic inside it.</p>
<p>Once the characteristic is obtained, the new LED state, which will typically be the string <code>"ON"</code> or <code>"OFF"</code>, is converted into a byte array and placed into the characteristic with <code>setValue</code>. The method then calls <code>writeCharacteristic</code>, which sends a write request to the remote device to update the stored value.</p>
<p>When the server processes the write and returns a response, the callback method <code>onCharacteristicWrite</code> executes. If the status indicates success, the code logs that the write completed. At this point, the server code on the other side can take action based on the new state, such as turning a real LED on or off.</p>
<p>This flow illustrates how clients modify values on a BLE peripheral and how acknowledgment is handled once the operation finishes, forming a typical example of device control over GATT.</p>
<p>This combination of server definitions and client interactions shows how characteristics are the real workhorses of GATT. Every meaningful piece of data flows through them. Reads, writes, and notifications all operate at the characteristic level.</p>
<h3 id="heading-notifications-and-cccd">Notifications and CCCD</h3>
<p>Notifications are simply a special property on a characteristic. When enabled, the server can push new values to the client without the client asking every time. To support notifications, a characteristic needs the Notify property and usually a descriptor called the Client Characteristic Configuration Descriptor, often referred to as CCCD, with UUID 0x2902.</p>
<p>On the server side, you would update the value and call <code>notifyCharacteristicChanged</code>. On the client side, you set characteristic notification to true and write the descriptor with <code>ENABLE_NOTIFICATION_VALUE</code>. The code pattern is almost identical regardless of the type of data, which makes it easy to reuse once you understand it.</p>
<p>By this point, you can see that a characteristic is not just a static field. It is a complete unit of behavior. It defines what data exists, how it is represented, who can access it, and how it updates. Once you’re comfortable designing characteristics and manipulating them in Java, you’re very close to mastering practical BLE development.</p>
<h2 id="heading-how-to-design-a-gatt-profile-for-a-smart-plant-monitor">How to Design a GATT Profile for a Smart Plant Monitor</h2>
<p>To make GATT feel real, let’s design a complete profile for a simple but realistic device: a smart plant monitor.</p>
<p>Imagine a small BLE sensor that you stick into a flower pot. It measures soil moisture, reports its own battery level, and allows you to configure how often it sends updates. A phone app connects to it, reads the current moisture level, shows the battery percentage, and lets the user adjust the reporting interval.</p>
<p>We’ll design both sides in terms of GATT. First, we’ll decide which services and characteristics we need. Then, we’ll see how an Android device can act as a client. For teaching purposes, we’ll also show how Android could act as the server, although in a real product the plant monitor would normally be an embedded device.</p>
<h3 id="heading-designing-the-gatt-profile">Designing the GATT Profile</h3>
<p>We need three logical pieces of information:</p>
<ol>
<li><p>Soil moisture percentage – this is dynamic sensor data.</p>
</li>
<li><p>Battery level – this is standard, so we can reuse the Battery Service.</p>
</li>
<li><p>Reporting interval configuration – this is a setting that the client writes and the device uses.</p>
</li>
</ol>
<p>We can express this with one custom service plus the standard Battery Service.</p>
<p><strong>Profile plan:</strong></p>
<pre><code class="lang-java">Custom Service: Plant Monitor Service
    UUID: <span class="hljs-number">12345678</span>-<span class="hljs-number">1234</span>-<span class="hljs-number">5678</span>-<span class="hljs-number">1234</span>-<span class="hljs-number">56789</span>abc0001

    Characteristic: Soil Moisture
        UUID: <span class="hljs-number">12345678</span>-<span class="hljs-number">1234</span>-<span class="hljs-number">5678</span>-<span class="hljs-number">1234</span>-<span class="hljs-number">56789</span>abc0002
        Properties: Read, Notify
        Permissions: Read
        Format: uint8 (<span class="hljs-number">0</span> to <span class="hljs-number">100</span> percentage)

    Characteristic: Reporting Interval
        UUID: <span class="hljs-number">12345678</span>-<span class="hljs-number">1234</span>-<span class="hljs-number">5678</span>-<span class="hljs-number">1234</span>-<span class="hljs-number">56789</span>abc0003
        Properties: Read, Write
        Permissions: Read, Write
        Format: uint16 (seconds)

Standard Service: Battery Service
    UUID: <span class="hljs-number">00001</span>80F-<span class="hljs-number">0000</span>-<span class="hljs-number">1000</span>-<span class="hljs-number">8000</span>-<span class="hljs-number">00</span>805F9B34FB

    Characteristic: Battery Level
        UUID: <span class="hljs-number">00002</span>A19-<span class="hljs-number">0000</span>-<span class="hljs-number">1000</span>-<span class="hljs-number">8000</span>-<span class="hljs-number">00</span>805F9B34FB
        Properties: Read, Notify (optional)
        Permissions: Read
        Format: uint8 (<span class="hljs-number">0</span> to <span class="hljs-number">100</span> percentage)
</code></pre>
<p>Now we know exactly what exists inside the device. A client can connect, look for the Plant Monitor Service and Battery Service, and then interact with these three characteristics.</p>
<h3 id="heading-implementing-the-gatt-server">Implementing the GATT Server</h3>
<p>In a real hardware product, the plant monitor would be written in embedded C or C++. But for learning, we can simulate the server on Android itself. This’ll help you understand how the server side works.</p>
<p>First, we’ll create the services and characteristics.</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PlantMonitorGattServer</span> </span>{

    <span class="hljs-keyword">private</span> BluetoothGattServer gattServer;

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> UUID PLANT_SERVICE_UUID =
            UUID.fromString(<span class="hljs-string">"12345678-1234-5678-1234-56789abc0001"</span>);
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> UUID MOISTURE_CHAR_UUID =
            UUID.fromString(<span class="hljs-string">"12345678-1234-5678-1234-56789abc0002"</span>);
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> UUID INTERVAL_CHAR_UUID =
            UUID.fromString(<span class="hljs-string">"12345678-1234-5678-1234-56789abc0003"</span>);

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> UUID BATTERY_SERVICE_UUID =
            UUID.fromString(<span class="hljs-string">"0000180F-0000-1000-8000-00805F9B34FB"</span>);
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> UUID BATTERY_LEVEL_UUID =
            UUID.fromString(<span class="hljs-string">"00002A19-0000-1000-8000-00805F9B34FB"</span>);

    <span class="hljs-comment">// Simulated state</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> currentMoisture = <span class="hljs-number">55</span>;      <span class="hljs-comment">// percentage</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> reportingIntervalSec = <span class="hljs-number">60</span>; <span class="hljs-comment">// seconds</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> batteryLevel = <span class="hljs-number">87</span>;         <span class="hljs-comment">// percentage</span>

    <span class="hljs-keyword">private</span> BluetoothGattCharacteristic moistureCharacteristic;
    <span class="hljs-keyword">private</span> BluetoothGattCharacteristic intervalCharacteristic;
    <span class="hljs-keyword">private</span> BluetoothGattCharacteristic batteryLevelCharacteristic;
</code></pre>
<p>This class represents a Bluetooth Low Energy GATT Server that pretends to be a smart plant monitor device. It holds a <code>BluetoothGattServer</code> instance that will expose services and characteristics to any BLE client that connects.</p>
<p>Several UUIDs are defined to identify the custom Plant Monitor Service and its characteristics, as well as the standard Battery Service and Battery Level characteristic.</p>
<p>The custom Plant Monitor Service has two characteristics: one for soil moisture and one for the reporting interval. The Battery Service uses the standard UUIDs defined by the Bluetooth SIG so that any client can recognize and parse it.</p>
<p>The class also keeps some simulated internal state: <code>currentMoisture</code> starts at 55 percent, <code>reportingIntervalSec</code> is set to 60 seconds, and <code>batteryLevel</code> is set to 87 percent. These values act like sensor readings and configuration stored inside the device.</p>
<p>Finally, it declares three <code>BluetoothGattCharacteristic</code> fields that will later point to the actual moisture, interval, and battery level characteristics once they are created and added to their respective services.</p>
<p>These fields make it easy for the server to update values and send notifications later – for example, when moisture changes or when the battery level drops.</p>
<p>Next, we’ll set up the server and define the services and characteristics.</p>
<pre><code class="lang-java">    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">startServer</span><span class="hljs-params">(Context context)</span> </span>{
        BluetoothManager bluetoothManager =
                (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);

        gattServer = bluetoothManager.openGattServer(context, serverCallback);

        <span class="hljs-comment">// Plant Monitor Service</span>
        BluetoothGattService plantService =
                <span class="hljs-keyword">new</span> BluetoothGattService(
                        PLANT_SERVICE_UUID,
                        BluetoothGattService.SERVICE_TYPE_PRIMARY
                );

        moistureCharacteristic = <span class="hljs-keyword">new</span> BluetoothGattCharacteristic(
                MOISTURE_CHAR_UUID,
                BluetoothGattCharacteristic.PROPERTY_READ |
                BluetoothGattCharacteristic.PROPERTY_NOTIFY,
                BluetoothGattCharacteristic.PERMISSION_READ
        );

        intervalCharacteristic = <span class="hljs-keyword">new</span> BluetoothGattCharacteristic(
                INTERVAL_CHAR_UUID,
                BluetoothGattCharacteristic.PROPERTY_READ |
                BluetoothGattCharacteristic.PROPERTY_WRITE,
                BluetoothGattCharacteristic.PERMISSION_READ |
                BluetoothGattCharacteristic.PERMISSION_WRITE
        );

        <span class="hljs-comment">// Set initial values</span>
        moistureCharacteristic.setValue(<span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[]{(<span class="hljs-keyword">byte</span>) currentMoisture});
        intervalCharacteristic.setValue(intToTwoBytes(reportingIntervalSec));

        <span class="hljs-comment">// For notifications, add CCCD descriptor</span>
        BluetoothGattDescriptor moistureCccd = <span class="hljs-keyword">new</span> BluetoothGattDescriptor(
                UUID.fromString(<span class="hljs-string">"00002902-0000-1000-8000-00805F9B34FB"</span>),
                BluetoothGattDescriptor.PERMISSION_READ |
                BluetoothGattDescriptor.PERMISSION_WRITE
        );
        moistureCharacteristic.addDescriptor(moistureCccd);

        plantService.addCharacteristic(moistureCharacteristic);
        plantService.addCharacteristic(intervalCharacteristic);

        <span class="hljs-comment">// Battery Service</span>
        BluetoothGattService batteryService =
                <span class="hljs-keyword">new</span> BluetoothGattService(
                        BATTERY_SERVICE_UUID,
                        BluetoothGattService.SERVICE_TYPE_PRIMARY
                );

        batteryLevelCharacteristic = <span class="hljs-keyword">new</span> BluetoothGattCharacteristic(
                BATTERY_LEVEL_UUID,
                BluetoothGattCharacteristic.PROPERTY_READ |
                BluetoothGattCharacteristic.PROPERTY_NOTIFY,
                BluetoothGattCharacteristic.PERMISSION_READ
        );

        batteryLevelCharacteristic.setValue(<span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[]{(<span class="hljs-keyword">byte</span>) batteryLevel});

        BluetoothGattDescriptor batteryCccd = <span class="hljs-keyword">new</span> BluetoothGattDescriptor(
                UUID.fromString(<span class="hljs-string">"00002902-0000-1000-8000-00805F9B34FB"</span>),
                BluetoothGattDescriptor.PERMISSION_READ |
                BluetoothGattDescriptor.PERMISSION_WRITE
        );
        batteryLevelCharacteristic.addDescriptor(batteryCccd);

        batteryService.addCharacteristic(batteryLevelCharacteristic);

        gattServer.addService(plantService);
        gattServer.addService(batteryService);
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">byte</span>[] intToTwoBytes(<span class="hljs-keyword">int</span> value) {
        <span class="hljs-keyword">byte</span>[] data = <span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[<span class="hljs-number">2</span>];
        data[<span class="hljs-number">0</span>] = (<span class="hljs-keyword">byte</span>) (value &amp; <span class="hljs-number">0xFF</span>);
        data[<span class="hljs-number">1</span>] = (<span class="hljs-keyword">byte</span>) ((value &gt;&gt; <span class="hljs-number">8</span>) &amp; <span class="hljs-number">0xFF</span>);
        <span class="hljs-keyword">return</span> data;
    }
</code></pre>
<p>This method starts the Bluetooth Low Energy GATT server and builds the full GATT database for the smart plant monitor.</p>
<p>It first obtains the <code>BluetoothManager</code> from the Android system and uses it to open a <code>BluetoothGattServer</code>, passing in a callback that will handle read, write, and notification events from connected clients.</p>
<p>Then it creates the custom Plant Monitor Service using the <code>PLANT_SERVICE_UUID</code> and marks it as a primary service.</p>
<p>Inside this service it defines two characteristics. The moisture characteristic is created with the <code>MOISTURE_CHAR_UUID</code> and given the properties Read and Notify, meaning a client can read the current soil moisture and also subscribe to notifications when it changes. It is read only, so it uses a read permission. The reporting interval characteristic is created with the <code>INTERVAL_CHAR_UUID</code> and uses both Read and Write properties so that a client can check the current interval and update it. It uses both read and write permissions.</p>
<p>The code sets the initial values for these characteristics: the moisture characteristic gets the current moisture percentage stored as a single byte, and the interval characteristic gets a two byte representation of the reporting interval using the helper method <code>intToTwoBytes</code>, which splits a 16 bit integer into low and high bytes.</p>
<p>To allow notifications for moisture, it adds a Client Characteristic Configuration Descriptor (CCCD) with a standard UUID <code>0x2902</code> and read or write permissions, then attaches this descriptor to the moisture characteristic. Both characteristics are added to the plant service.</p>
<p>Next, the method creates the standard Battery Service as another primary service using the well-known battery UUID. It defines the Battery Level characteristic with read and notify properties and read permission.</p>
<p>The initial battery level is stored as a single byte. Just like with moisture, it adds a CCCD descriptor to support notifications and attaches it to the battery characteristic. The battery characteristic is then added to the battery service.</p>
<p>Finally, the method registers both the plant service and the battery service with the GATT server using <code>addService</code>, which makes them visible to any BLE client that connects. As a small utility, the <code>intToTwoBytes</code> method at the end converts a 16 bit integer into a two element byte array with the least significant byte first, which is a common way to encode integers in BLE characteristics.</p>
<p>Now we’ll implement the callback to handle read, write, and notification logic.</p>
<pre><code class="lang-java">    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> BluetoothGattServerCallback serverCallback =
            <span class="hljs-keyword">new</span> BluetoothGattServerCallback() {

        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onConnectionStateChange</span><span class="hljs-params">(BluetoothDevice device,
                                            <span class="hljs-keyword">int</span> status,
                                            <span class="hljs-keyword">int</span> newState)</span> </span>{
            Log.d(TAG, <span class="hljs-string">"Device connection state: "</span> + newState);
        }

        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCharacteristicReadRequest</span><span class="hljs-params">(BluetoothDevice device,
                                                <span class="hljs-keyword">int</span> requestId,
                                                <span class="hljs-keyword">int</span> offset,
                                                BluetoothGattCharacteristic characteristic)</span> </span>{

            <span class="hljs-keyword">if</span> (characteristic.getUuid().equals(MOISTURE_CHAR_UUID)) {
                moistureCharacteristic.setValue(<span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[]{(<span class="hljs-keyword">byte</span>) currentMoisture});
                gattServer.sendResponse(device, requestId,
                        BluetoothGatt.GATT_SUCCESS, offset,
                        moistureCharacteristic.getValue());
            } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (characteristic.getUuid().equals(INTERVAL_CHAR_UUID)) {
                intervalCharacteristic.setValue(intToTwoBytes(reportingIntervalSec));
                gattServer.sendResponse(device, requestId,
                        BluetoothGatt.GATT_SUCCESS, offset,
                        intervalCharacteristic.getValue());
            } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (characteristic.getUuid().equals(BATTERY_LEVEL_UUID)) {
                batteryLevelCharacteristic.setValue(<span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[]{(<span class="hljs-keyword">byte</span>) batteryLevel});
                gattServer.sendResponse(device, requestId,
                        BluetoothGatt.GATT_SUCCESS, offset,
                        batteryLevelCharacteristic.getValue());
            } <span class="hljs-keyword">else</span> {
                gattServer.sendResponse(device, requestId,
                        BluetoothGatt.GATT_FAILURE, offset, <span class="hljs-keyword">null</span>);
            }
        }

        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCharacteristicWriteRequest</span><span class="hljs-params">(BluetoothDevice device,
                                                 <span class="hljs-keyword">int</span> requestId,
                                                 BluetoothGattCharacteristic characteristic,
                                                 <span class="hljs-keyword">boolean</span> preparedWrite,
                                                 <span class="hljs-keyword">boolean</span> responseNeeded,
                                                 <span class="hljs-keyword">int</span> offset,
                                                 <span class="hljs-keyword">byte</span>[] value)</span> </span>{

            <span class="hljs-keyword">if</span> (characteristic.getUuid().equals(INTERVAL_CHAR_UUID)) {
                <span class="hljs-keyword">int</span> newInterval = ((value[<span class="hljs-number">1</span>] &amp; <span class="hljs-number">0xFF</span>) &lt;&lt; <span class="hljs-number">8</span>) | (value[<span class="hljs-number">0</span>] &amp; <span class="hljs-number">0xFF</span>);
                reportingIntervalSec = newInterval;
                Log.d(TAG, <span class="hljs-string">"New reporting interval: "</span> + reportingIntervalSec + <span class="hljs-string">" sec"</span>);

                intervalCharacteristic.setValue(value);
            }

            <span class="hljs-keyword">if</span> (responseNeeded) {
                gattServer.sendResponse(device, requestId,
                        BluetoothGatt.GATT_SUCCESS, offset, value);
            }
        }

        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onDescriptorWriteRequest</span><span class="hljs-params">(BluetoothDevice device,
                                             <span class="hljs-keyword">int</span> requestId,
                                             BluetoothGattDescriptor descriptor,
                                             <span class="hljs-keyword">boolean</span> preparedWrite,
                                             <span class="hljs-keyword">boolean</span> responseNeeded,
                                             <span class="hljs-keyword">int</span> offset,
                                             <span class="hljs-keyword">byte</span>[] value)</span> </span>{

            <span class="hljs-keyword">if</span> (descriptor.getCharacteristic().getUuid().equals(MOISTURE_CHAR_UUID)) {
                Log.d(TAG, <span class="hljs-string">"Moisture notifications enabled"</span>);
            } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (descriptor.getCharacteristic().getUuid().equals(BATTERY_LEVEL_UUID)) {
                Log.d(TAG, <span class="hljs-string">"Battery notifications enabled"</span>);
            }

            <span class="hljs-keyword">if</span> (responseNeeded) {
                gattServer.sendResponse(device, requestId,
                        BluetoothGatt.GATT_SUCCESS, offset, value);
            }
        }
    };
}
</code></pre>
<p>This callback handles all the important server side events for the smart plant monitor GATT Server, including connection changes, characteristic reads and writes, and descriptor writes for notifications.</p>
<p>When a device connects or disconnects, <code>onConnectionStateChange</code> is called and simply logs the new connection state so you can see when a client appears or disappears. The core logic lives in <code>onCharacteristicReadRequest</code>, which is invoked whenever a BLE client performs a read on one of the server’s characteristics.</p>
<p>The method checks which characteristic is being read by comparing its UUID. If it’s the moisture characteristic, it refreshes the characteristic value with the current moisture percentage, then responds with <code>GATT_SUCCESS</code> and the encoded value. If it’s the interval characteristic, it encodes the current reporting interval into two bytes using <code>intToTwoBytes</code> and sends that back. If it’s the battery level characteristic, it encodes the current battery percentage into a single byte and returns it. If the UUID does not match any known characteristic, the server responds with <code>GATT_FAILURE</code>, which tells the client that the request could not be fulfilled.</p>
<p>The <code>onCharacteristicWriteRequest</code> method handles writes from the client. In this implementation, only the reporting interval characteristic is writable. When a write targets this characteristic, the code decodes the two byte value sent by the client into an integer by reconstructing it from the low and high bytes. It updates the internal <code>reportingIntervalSec</code> field, logs the new interval, and stores the received bytes in the characteristic so that future reads return the updated value. If the client requested a response, the server sends back a success status and echoes the written value.</p>
<p>Finally, <code>onDescriptorWriteRequest</code> is called when a client writes to a descriptor, typically the Client Characteristic Configuration Descriptor that controls notifications. The code checks whether the descriptor belongs to the moisture or battery characteristic and logs that notifications have been enabled for the corresponding data source. If a response is needed, it sends back <code>GATT_SUCCESS</code>.</p>
<p>Altogether, this callback turns the server into a live plant monitor that can answer real time read requests, accept configuration updates, and honor notification subscriptions for moisture and battery level.</p>
<p>We now have a fully functioning GATT Server that supports read and write operations, and can also send notifications for moisture and battery when needed.</p>
<p>To simulate notifications, the server can periodically update values and call <code>notifyCharacteristicChanged</code>:</p>
<pre><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">simulateSensorUpdate</span><span class="hljs-params">(BluetoothDevice device)</span> </span>{
    <span class="hljs-comment">// Simulate moisture dropping slightly</span>
    currentMoisture = Math.max(<span class="hljs-number">0</span>, currentMoisture - <span class="hljs-number">1</span>);
    moistureCharacteristic.setValue(<span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[]{(<span class="hljs-keyword">byte</span>) currentMoisture});
    gattServer.notifyCharacteristicChanged(device, moistureCharacteristic, <span class="hljs-keyword">false</span>);
}
</code></pre>
<p>This method simulates a live update to the soil moisture sensor and demonstrates how a GATT Server sends notifications to a connected BLE client.</p>
<p>It decreases the current moisture reading by one percent, ensuring the value never falls below zero using <code>Math.max</code>. After adjusting the simulated value, the method stores the updated moisture value inside the moisture characteristic using <code>setValue</code>, which prepares the new data to be transmitted.</p>
<p>It then calls <code>notifyCharacteristicChanged</code>, which sends a BLE notification packet to the specified connected device, telling the client that the characteristic value has changed and delivering the new moisture reading immediately.</p>
<p>The final parameter <code>false</code> indicates that this is a notification rather than an indication, which means the server does not require an acknowledgment from the client. This method would typically be called on a timer or triggered by real sensor hardware, allowing the client application to receive continuous updates in real time without repeatedly polling the server.</p>
<h3 id="heading-implementing-the-gatt-client-in-java">Implementing the GATT Client in Java</h3>
<p>On the Android client side, we connect to the plant monitor, discover services, then interact with the three characteristics.</p>
<p>First, we’ll discover the services and store references.</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PlantMonitorClient</span> </span>{

    <span class="hljs-keyword">private</span> BluetoothGatt bluetoothGatt;

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> UUID PLANT_SERVICE_UUID =
            UUID.fromString(<span class="hljs-string">"12345678-1234-5678-1234-56789abc0001"</span>);
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> UUID MOISTURE_CHAR_UUID =
            UUID.fromString(<span class="hljs-string">"12345678-1234-5678-1234-56789abc0002"</span>);
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> UUID INTERVAL_CHAR_UUID =
            UUID.fromString(<span class="hljs-string">"12345678-1234-5678-1234-56789abc0003"</span>);

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> UUID BATTERY_SERVICE_UUID =
            UUID.fromString(<span class="hljs-string">"0000180F-0000-1000-8000-00805F9B34FB"</span>);
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> UUID BATTERY_LEVEL_UUID =
            UUID.fromString(<span class="hljs-string">"00002A19-0000-1000-8000-00805F9B34FB"</span>);

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">connect</span><span class="hljs-params">(Context context, BluetoothDevice device)</span> </span>{
        bluetoothGatt = device.connectGatt(context, <span class="hljs-keyword">false</span>, gattCallback);
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> BluetoothGattCallback gattCallback = <span class="hljs-keyword">new</span> BluetoothGattCallback() {

        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onConnectionStateChange</span><span class="hljs-params">(BluetoothGatt gatt,
                                            <span class="hljs-keyword">int</span> status,
                                            <span class="hljs-keyword">int</span> newState)</span> </span>{
            <span class="hljs-keyword">if</span> (newState == BluetoothProfile.STATE_CONNECTED) {
                Log.d(TAG, <span class="hljs-string">"Connected. Discovering services."</span>);
                gatt.discoverServices();
            }
        }

        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onServicesDiscovered</span><span class="hljs-params">(BluetoothGatt gatt, <span class="hljs-keyword">int</span> status)</span> </span>{
            Log.d(TAG, <span class="hljs-string">"Services discovered."</span>);

            <span class="hljs-comment">// Read current moisture and battery once</span>
            readMoisture(gatt);
            readBatteryLevel(gatt);

            <span class="hljs-comment">// Enable notifications</span>
            enableMoistureNotifications(gatt);
        }
</code></pre>
<p>This class represents the Bluetooth Low Energy client side of the smart plant monitor example. It holds a <code>BluetoothGatt</code> reference that represents the active connection to the BLE server device.</p>
<p>Several UUID constants are defined so the client knows how to find the Plant Monitor Service and its characteristics for moisture and reporting interval, as well as the standard Battery Service and Battery Level characteristic.</p>
<p>The <code>connect</code> method starts the BLE connection by calling <code>device.connectGatt</code>, passing in the Android <code>Context</code>, a flag indicating no automatic reconnection, and a <code>BluetoothGattCallback</code> instance that will receive connection and data events.</p>
<p>Inside the callback, <code>onConnectionStateChange</code> is called whenever the connection state changes. When the new state indicates that the device is connected, the client logs this and calls <code>discoverServices</code> to request the full list of GATT services from the server.</p>
<p>Once the service discovery procedure completes, <code>onServicesDiscovered</code> is triggered. In this method, the client logs that services have been discovered, then immediately reads the current values of the moisture and battery level using helper methods <code>readMoisture</code> and <code>readBatteryLevel</code>, and finally enables notifications for moisture updates using <code>enableMoistureNotifications</code>.</p>
<p>Together, these steps mean that as soon as the client connects to a plant monitor device, it learns what services are available, fetches one time snapshots of important values, and subscribes to real time updates for the most important sensor – which in this case is soil moisture.</p>
<p>Now, we’ll define methods for reading moisture and battery.</p>
<pre><code class="lang-java">        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">readMoisture</span><span class="hljs-params">(BluetoothGatt gatt)</span> </span>{
            BluetoothGattService service = gatt.getService(PLANT_SERVICE_UUID);
            <span class="hljs-keyword">if</span> (service != <span class="hljs-keyword">null</span>) {
                BluetoothGattCharacteristic ch =
                        service.getCharacteristic(MOISTURE_CHAR_UUID);
                <span class="hljs-keyword">if</span> (ch != <span class="hljs-keyword">null</span>) {
                    gatt.readCharacteristic(ch);
                }
            }
        }

        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">readBatteryLevel</span><span class="hljs-params">(BluetoothGatt gatt)</span> </span>{
            BluetoothGattService service = gatt.getService(BATTERY_SERVICE_UUID);
            <span class="hljs-keyword">if</span> (service != <span class="hljs-keyword">null</span>) {
                BluetoothGattCharacteristic ch =
                        service.getCharacteristic(BATTERY_LEVEL_UUID);
                <span class="hljs-keyword">if</span> (ch != <span class="hljs-keyword">null</span>) {
                    gatt.readCharacteristic(ch);
                }
            }
        }

        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCharacteristicRead</span><span class="hljs-params">(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic characteristic,
                                         <span class="hljs-keyword">int</span> status)</span> </span>{
            <span class="hljs-keyword">if</span> (status == BluetoothGatt.GATT_SUCCESS) {
                <span class="hljs-keyword">if</span> (MOISTURE_CHAR_UUID.equals(characteristic.getUuid())) {
                    <span class="hljs-keyword">int</span> moisture = characteristic.getIntValue(
                            BluetoothGattCharacteristic.FORMAT_UINT8, <span class="hljs-number">0</span>);
                    Log.d(TAG, <span class="hljs-string">"Soil moisture: "</span> + moisture + <span class="hljs-string">" percent"</span>);
                } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (BATTERY_LEVEL_UUID.equals(characteristic.getUuid())) {
                    <span class="hljs-keyword">int</span> battery = characteristic.getIntValue(
                            BluetoothGattCharacteristic.FORMAT_UINT8, <span class="hljs-number">0</span>);
                    Log.d(TAG, <span class="hljs-string">"Battery level: "</span> + battery + <span class="hljs-string">" percent"</span>);
                } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (INTERVAL_CHAR_UUID.equals(characteristic.getUuid())) {
                    <span class="hljs-keyword">int</span> interval = characteristic.getIntValue(
                            BluetoothGattCharacteristic.FORMAT_UINT16, <span class="hljs-number">0</span>);
                    Log.d(TAG, <span class="hljs-string">"Reporting interval: "</span> + interval + <span class="hljs-string">" sec"</span>);
                }
            }
        }
</code></pre>
<p>These methods handle reading values from the smart plant monitor GATT Server. The <code>readMoisture</code> method retrieves the Plant Monitor Service using its UUID, then looks up the soil moisture characteristic inside it. If the characteristic is found, it sends a read request using <code>gatt.readCharacteristic</code>, which asks the server to return the current moisture value.</p>
<p>The <code>readBatteryLevel</code> method behaves the same way but targets the standard Battery Service and Battery Level characteristic. When the server responds to either read request, the callback <code>onCharacteristicRead</code> is triggered. The method first checks whether the read was successful by confirming that the status equals <code>GATT_SUCCESS</code>. It then determines which characteristic was read by comparing UUIDs.</p>
<p>If the response is for the moisture characteristic, it decodes the value from a single byte into an integer percentage and logs it. If it is the battery characteristic, it similarly extracts the single byte battery percentage and logs that value. If the interval characteristic was read, it decodes two bytes into a 16 bit integer and logs the reporting interval in seconds.</p>
<p>This read flow provides the client with a snapshot of the current sensor and configuration values immediately after connecting, before monitoring changes through notifications.</p>
<p>Next, we’ll enable notifications for moisture so that the app receives updates when it changes.</p>
<pre><code class="lang-java">        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">enableMoistureNotifications</span><span class="hljs-params">(BluetoothGatt gatt)</span> </span>{
            BluetoothGattService service = gatt.getService(PLANT_SERVICE_UUID);
            <span class="hljs-keyword">if</span> (service != <span class="hljs-keyword">null</span>) {
                BluetoothGattCharacteristic ch =
                        service.getCharacteristic(MOISTURE_CHAR_UUID);
                <span class="hljs-keyword">if</span> (ch != <span class="hljs-keyword">null</span>) {
                    gatt.setCharacteristicNotification(ch, <span class="hljs-keyword">true</span>);

                    BluetoothGattDescriptor descriptor =
                            ch.getDescriptor(UUID.fromString(
                                    <span class="hljs-string">"00002902-0000-1000-8000-00805F9B34FB"</span>));

                    <span class="hljs-keyword">if</span> (descriptor != <span class="hljs-keyword">null</span>) {
                        descriptor.setValue(
                                BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                        gatt.writeDescriptor(descriptor);
                    }
                }
            }
        }

        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCharacteristicChanged</span><span class="hljs-params">(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic)</span> </span>{
            <span class="hljs-keyword">if</span> (MOISTURE_CHAR_UUID.equals(characteristic.getUuid())) {
                <span class="hljs-keyword">int</span> moisture = characteristic.getIntValue(
                        BluetoothGattCharacteristic.FORMAT_UINT8, <span class="hljs-number">0</span>);
                Log.d(TAG,
                        <span class="hljs-string">"Soil moisture update: "</span> + moisture + <span class="hljs-string">" percent"</span>);
            }
        }
    };
</code></pre>
<p>This code enables live moisture updates through notifications and handles them when they arrive.</p>
<p>The <code>enableMoistureNotifications</code> method first retrieves the Plant Monitor Service, then obtains the moisture characteristic using its UUID. If the characteristic is available, it calls <code>setCharacteristicNotification</code> with <code>true</code>, which tells the Android BLE stack to start listening for notifications on that characteristic.</p>
<p>But enabling notification support locally is not enough because the GATT specification requires that the client also write to the associated descriptor known as the Client Characteristic Configuration Descriptor, or CCCD, identified by the standard UUID <code>0x2902</code>. The method retrieves this descriptor, sets its value to <code>ENABLE_NOTIFICATION_VALUE</code>, and writes it using <code>writeDescriptor</code>, which sends a request over the air to the server to enable notifications on the device side. Once this configuration is complete, updates are delivered whenever the characteristic value changes.</p>
<p>The <code>onCharacteristicChanged</code> callback is triggered automatically each time the server pushes a new moisture reading. The method checks that the changed characteristic is the moisture characteristic by comparing UUIDs, extracts the soil moisture percentage from a single byte using <code>getIntValue</code>, and logs the updated value. This allows the client app to receive real time sensor readings without constantly polling the server, which saves energy and improves responsiveness for applications such as plant monitoring dashboards or notification alerts.</p>
<p>Finally, the client can write a new reporting interval, for example changing from 60 seconds to 30 seconds.</p>
<pre><code class="lang-java">    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">writeReportingInterval</span><span class="hljs-params">(<span class="hljs-keyword">int</span> newIntervalSec)</span> </span>{
        <span class="hljs-keyword">if</span> (bluetoothGatt == <span class="hljs-keyword">null</span>) <span class="hljs-keyword">return</span>;

        BluetoothGattService service =
                bluetoothGatt.getService(PLANT_SERVICE_UUID);
        <span class="hljs-keyword">if</span> (service != <span class="hljs-keyword">null</span>) {
            BluetoothGattCharacteristic ch =
                    service.getCharacteristic(INTERVAL_CHAR_UUID);
            <span class="hljs-keyword">if</span> (ch != <span class="hljs-keyword">null</span>) {
                <span class="hljs-keyword">byte</span>[] data = <span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[<span class="hljs-number">2</span>];
                data[<span class="hljs-number">0</span>] = (<span class="hljs-keyword">byte</span>) (newIntervalSec &amp; <span class="hljs-number">0xFF</span>);
                data[<span class="hljs-number">1</span>] = (<span class="hljs-keyword">byte</span>) ((newIntervalSec &gt;&gt; <span class="hljs-number">8</span>) &amp; <span class="hljs-number">0xFF</span>);
                ch.setValue(data);
                bluetoothGatt.writeCharacteristic(ch);
            }
        }
    }
}
</code></pre>
<p>This method allows the BLE client to update the reporting interval setting on the smart plant monitor by writing a new value to the interval characteristic on the GATT Server.</p>
<p>It first checks whether the <code>bluetoothGatt</code> object is valid, since no write can occur before a connection is established. It retrieves the Plant Monitor Service using its UUID and then looks up the reporting interval characteristic inside that service.</p>
<p>If the characteristic exists, the method converts the new interval value from an integer into a two byte array, placing the least significant byte first and the most significant byte second, which is the common little endian format used in Bluetooth characteristics. It sets this byte array as the characteristic’s new value and then calls <code>writeCharacteristic</code>, which sends a write request over the air to the server. When the server processes the command in its corresponding write request handler, it will update its internal interval value and acknowledge the change.</p>
<p>This method demonstrates how configuration settings are written from a BLE client to a BLE device, enabling interactive control of behavior instead of only reading sensor values.</p>
<p>With this design, our smart plant monitor system is complete. The GATT Server exposes well-defined services and characteristics. The Android client connects, discovers, reads, writes, and subscribes to notifications. The concept is always the same: services group features. Characteristics hold data and behavior. Clients manipulate characteristics. Servers store and protect them.</p>
<p>Once you can design and code such a profile end to end, you are effectively using GATT the way real products do. The same pattern scales to complex devices like glucose monitors, smart locks, smart glasses, and industrial sensors.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>GATT is the foundation that makes Bluetooth Low Energy communication understandable and reliable. It transforms raw radio signals into meaningful structured information through the use of services and characteristics. Once you understand that every BLE device exposes a database of values that a client can read, write, or subscribe to, the entire system becomes logical instead of mysterious.</p>
<p>Whether you are reading heart rate from a smartwatch, checking the battery level of wireless earbuds, controlling a smart bulb, or configuring an industrial sensor, the interaction always happens through GATT characteristics inside services.</p>
<p>By examining both sides of the communication, the GATT Server and the GATT Client, and by walking through real Java code examples for reading, writing, and receiving notifications, you now have the practical knowledge needed to build and debug real BLE applications. You saw how to define custom services and characteristics, how to interpret data formats, how to enable notifications for dynamic sensor updates, and how to organize a complete device profile using a realistic example in the plant monitor project.</p>
<p>Everything in Bluetooth Low Energy development begins with understanding GATT at this level. Once you are comfortable designing and interacting with services and characteristics, you can confidently move into more advanced topics such as secure pairing and bonding, throughput tuning using MTU and connection interval, power optimization, OTA firmware updates, and tools like nRF Connect and HCI log analysis.</p>
<p>The best way to strengthen what you learned is to build something hands on. Even a simple read and write test project will help the concepts become intuitive.</p>
<p>Mastering GATT is the first major step toward professional Bluetooth development. Every complex system built with BLE, from consumer wearables to medical devices and smart home automation, sits on top of this technology. Now that you understand the structure and communication model, you are ready to explore more sophisticated capabilities and create your own applications with confidence.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The State of Bluetooth in 2025: What’s New, What’s Possible, and How to Use It ]]>
                </title>
                <description>
                    <![CDATA[ Introduction: Why Bluetooth Still Matters You probably don’t even think about Bluetooth anymore. It’s just there, quietly doing its job every single day. It’s what keeps your earbuds connected, your smartwatch synced, your car infotainment system tal... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-state-of-bluetooth-whats-new-whats-possible-and-how-to-use-it/</link>
                <guid isPermaLink="false">690e2801500cb51e735b5a9c</guid>
                
                    <category>
                        <![CDATA[ bluetooth ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Bluetooth Low Energy ]]>
                    </category>
                
                    <category>
                        <![CDATA[ iot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ connectivity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nikheel Vishwas Savant ]]>
                </dc:creator>
                <pubDate>Fri, 07 Nov 2025 17:10:25 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1762533537259/3f9dec8a-690b-4fd8-a0a7-8e6b2667e55c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="heading-introduction-why-bluetooth-still-matters">Introduction: Why Bluetooth Still Matters</h2>
<p>You probably don’t even think about Bluetooth anymore. It’s just there, quietly doing its job every single day. It’s what keeps your earbuds connected, your smartwatch synced, your car infotainment system talking to your phone, and your warehouse sensors awake and reporting.</p>
<p>The funny thing is, while most of us stopped paying attention, Bluetooth never stopped evolving. It just kept getting smarter.</p>
<p>Now it’s 2025, and Bluetooth has grown into something much bigger than a way to stream music. It has become a core ecosystem that connects nearly everything around us. From audio gear and IoT sensors to industrial automation and secure building access, Bluetooth is everywhere.</p>
<p>The newest versions, Bluetooth 5.4 and 6.0, completely redefine how devices talk to each other. We’re talking about encrypted broadcasts, smarter advertising, centimeter-level distance tracking, and a level of scalability that feels closer to magic than engineering.</p>
<p>In this article, we’ll take a tour through the newest Bluetooth technologies and see what’s happening under the hood. You’ll get a feel for what’s new, how these features work in real projects, and how developers can actually take advantage of them.</p>
<p>Grab your favorite dev board, and let’s dive in.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-the-evolution-from-classic-to-low-energy-to-60">The Evolution: From Classic to Low Energy to 6.0</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-deep-dive-technical-enhancements">Deep Dive: Technical Enhancements</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-real-world-applications-in-2025">Real-World Applications in 2025</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-developer-guide-getting-started">Developer Guide: Getting Started</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-challenges-and-trade-offs">Challenges and Trade-Offs</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-road-ahead-bluetooth-61-and-beyond">The Road Ahead: Bluetooth 6.1 and Beyond</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-the-evolution-from-classic-to-low-energy-to-60">The Evolution — From Classic to Low Energy to 6.0</h2>
<p>If you’ve been around Bluetooth for a while, you probably remember the early days when pairing a headset felt like solving a riddle. Back then, Bluetooth Classic ruled the scene, focused mainly on short-range audio and simple data links. Over the years, though, the story changed completely.</p>
<p>Today, Bluetooth has transformed from a simple cable-replacement protocol into a flexible framework for everything from earbuds to industrial robots. Each new version added fresh layers of intelligence, speed, and energy efficiency. The table below gives a quick timeline of how that evolution unfolded.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Version</strong></td><td><strong>Year</strong></td><td><strong>Key Features</strong></td></tr>
</thead>
<tbody>
<tr>
<td><strong>2.0 + EDR</strong></td><td>2004</td><td>Faster data rate (3 Mbps)</td></tr>
<tr>
<td><strong>4.0</strong></td><td>2010</td><td>BLE introduced for low power</td></tr>
<tr>
<td><strong>5.0</strong></td><td>2016</td><td>2× speed, 4× range, 8× advertising capacity</td></tr>
<tr>
<td><strong>5.1</strong></td><td>2019</td><td>Direction Finding (AoA/AoD)</td></tr>
<tr>
<td><strong>5.2</strong></td><td>2020</td><td>LE Audio / Isochronous Channels</td></tr>
<tr>
<td><strong>5.3 – 5.4</strong></td><td>2021-2023</td><td>Encrypted Advertising, PAwR</td></tr>
<tr>
<td><strong>6.0</strong></td><td>2024</td><td>Channel Sounding, Decision-Based Filtering</td></tr>
<tr>
<td><strong>6.1</strong></td><td>2025</td><td>Minor updates on efficiency &amp; range</td></tr>
</tbody>
</table>
</div><p>The journey tells a bigger story. What started as a way to connect two devices for audio has turned into a foundation for massive IoT networks. Each revision introduced smarter physical layers, better energy profiles, and new roles for devices that once had very limited capability.</p>
<p><img src="https://www.mdpi.com/sensors/sensors-25-00996/article_deploy/html/images/sensors-25-00996-g003.png" alt="Sensors 25 00996 g003" width="600" height="400" loading="lazy"></p>
<p><em>Source: MDPI Sensors (2025), Bluetooth Core Specification Summary.</em></p>
<p>Above figure provides a visual snapshot of how Bluetooth has evolved across its major versions. It shows a clear chronological progression of features—from the launch of Bluetooth Low Energy (BLE) in version 4.0, to the introduction of secure connections, long-range PHYs, and direction-finding capabilities, all the way up to the latest breakthroughs like Channel Sounding and decision-based filtering in Bluetooth 6.0. The color-coded timeline highlights how each version refined both the physical and logical layers of communication, gradually expanding Bluetooth’s reach from simple peripherals to high-precision industrial and spatial applications. In essence, it maps Bluetooth’s transformation from a short-range wireless cable into a sophisticated, context-aware connectivity fabric that underpins modern audio, IoT, and automation ecosystems.</p>
<p>If you zoom out a bit, you’ll notice a clear pattern: Bluetooth keeps finding new neighborhoods to move into. From cars and headphones to factories and hospitals, the technology now feels less like a cable replacement and more like an invisible nervous system for the modern world.</p>
<h2 id="heading-whats-new-in-bluetooth-54-and-60">What’s New in Bluetooth 5.4 and 6.0</h2>
<p>When you hear that Bluetooth has a “new version,” it’s easy to shrug it off. After all, your headphones already work, right? But the jump from 5.3 to 5.4 and then 6.0 isn’t just a tiny step. It’s more like Bluetooth quietly taking on Wi-Fi’s job in certain places and pulling it off surprisingly well.</p>
<p>Let’s break it down by version so it’s easier to see what’s going on.</p>
<h3 id="heading-bluetooth-54-building-the-iot-backbone"><strong>Bluetooth 5.4: Building the IoT Backbone</strong></h3>
<p>This release might not have made flashy headlines, but engineers loved it. It focuses on letting thousands of low-power devices talk to a single gateway without choking the airwaves.</p>
<p>Let’s look at some of the key features and why they matter:</p>
<h4 id="heading-periodic-advertising-with-responses-pawr">Periodic Advertising with Responses (PAwR)</h4>
<p>Think of it as Bluetooth’s group chat for sensors. Devices can broadcast messages and still get short replies, all without the full connection setup that usually drains batteries. It’s perfect for large sensor networks like smart warehouses or retail stores with electronic shelf labels.</p>
<p><img src="https://devzone.nordicsemi.com/resized-image/__size/1296x466/__key/communityserver-blogs-components-weblogfiles/00-00-00-00-28/7607.pastedimage1698068932789v3.png" alt="Periodic Advertising with Responses (PAwR): A practical guide - Software -  nRF Connect SDK guides - Nordic DevZone" width="600" height="400" loading="lazy"></p>
<p>Source: Nordic Semiconductor Developer Zone (2024)</p>
<p>Above diagram illustrates the timing structure of Bluetooth 5.4’s Periodic Advertising with Responses (PAwR) mechanism. Along the horizontal axis, it shows a repeating sequence of PAwR events separated by the overall <em>periodic advertising interval</em>. Within each PAwR event are several <em>subevents</em>—labeled #0, #1, #2, #3, and so on—each representing a defined window of time during which specific sensors or devices are allowed to communicate. The figure highlights that every subevent occurs at a fixed <em>periodic advertising subevent interval</em>, meaning devices can wake up only during their assigned slot, transmit or receive data, and then return to sleep. This predictable scheduling dramatically reduces radio collisions and power consumption, allowing a single gateway to coordinate thousands of low-power nodes such as electronic shelf labels or environmental sensors within a shared advertising cycle.</p>
<h4 id="heading-encrypted-advertising-data">Encrypted Advertising Data</h4>
<p>Broadcasts used to be open for anyone to sniff. Now they can be private and secure, which is essential for medical monitors and retail beacons carrying sensitive info.</p>
<p><img src="https://www.raytac.com/upload/news_m/ceac2577d996eda7e0197ec0ff7be7c8.png" alt="Raytac Corporation 勁達國際電子股份有限公司" width="600" height="400" loading="lazy"></p>
<p>Source: Raytac Technology (2024)</p>
<p>Above diagram breaks down the structure of the <strong>Encrypted Data Advertising Data (AD) type</strong> introduced in Bluetooth 5.4. It visually shows how encrypted advertising payloads are organized within a broadcast packet. At the top, the full advertising payload is represented, which includes the length (Len), Encrypted Data (ED Tag), and flags. Inside the encrypted section, the fields are expanded to show the <strong>Randomizer</strong>, <strong>Payload</strong>, and <strong>Message Integrity Check (MIC)</strong>. The payload itself may contain various elements such as the <strong>Electronic Shelf Label (ESL) Tag</strong>, <strong>ESL Payload</strong>, <strong>Local Name (LN Tag)</strong>, or other advertising segments. The color-coding differentiates which parts are encrypted (blue) versus unencrypted (gray or yellow), highlighting how Bluetooth 5.4 secures sensitive data while retaining key advertising identifiers for discovery. This layout helps engineers understand where encryption is applied within the advertising packet and how privacy and integrity are preserved during broadcast communication.</p>
<h4 id="heading-electronic-shelf-labels-esl-support">Electronic Shelf Labels (ESL) Support</h4>
<p>Bluetooth 5.4 was practically written with supermarkets in mind. Imagine thousands of digital price tags blinking updates at once, all running for months on coin-cell batteries.</p>
<p><img src="https://www.danidatasystems.com/wp-content/uploads/2023/10/ESL-work.jpg" alt="Electronic Shelf Label - Dani Data Systems India Pvt. Ltd." width="600" height="400" loading="lazy"></p>
<p>Source: Dani Data Systems (2023)</p>
<p>Above image illustrates the working architecture of a Bluetooth-based <strong>Electronic Shelf Label (ESL)</strong> system. On the left, a computer running ESL management software is shown, which allows retail staff to configure product data, prices, and display templates. The software communicates over a TCP/IP network connection with a <strong>Base Station</strong> positioned in the center of the diagram. This base station acts as a Bluetooth gateway, wirelessly transmitting the updated price and product information to numerous shelf labels throughout the store. On the right, a digital ESL display is shown featuring a price tag for a product labeled “Kaju Katali,” complete with product details, QR codes for mobile payments, and expiry dates. The blue wireless icon between the base station and ESL tag symbolizes Bluetooth communication. Together, the components demonstrate how Bluetooth 5.4 enables synchronized, low-power, and remotely managed price updates across thousands of retail shelf labels.</p>
<p>In short, 5.4 was the version that said, “Sure, we can handle massive IoT networks.”</p>
<h3 id="heading-bluetooth-60-the-game-changer"><strong>Bluetooth 6.0: The Game Changer</strong></h3>
<p>Bluetooth 6.0 feels like the point where the technology matured from “just wireless” into “smart wireless.” This version brings features that start blurring the line between Bluetooth and more advanced location systems.</p>
<h4 id="heading-channel-sounding">Channel Sounding</h4>
<p>This is a big one. Instead of using signal strength (which can be messy), Bluetooth 6.0 measures phase differences in radio waves to calculate distance. That means centimeter-level accuracy (enough for digital keys), precise tracking, and even AR interactions.</p>
<p><img src="https://amaldev.blog/wp-content/uploads/2025/01/BLEChannelSounding.png" alt="TechExplained: Bluetooth Channel Sounding - The Tech Blog" width="600" height="400" loading="lazy"></p>
<p>Source: Bluetooth SIG (2025)</p>
<p>Above image explains the concept of <strong>Bluetooth Channel Sounding</strong>, a new feature introduced in Bluetooth 6.0 that enables precise distance measurement between devices. The top half of the diagram compares three levels of spatial awareness—presence detection through advertising, coarse distance estimation using RSSI (Received Signal Strength Indicator), and fine-grained ranging achieved with Channel Sounding. It also shows how Direction Finding complements these methods by determining angular orientation. On the left, a smartphone (the initiator) communicates with a smart lock (the reflector), demonstrating how Bluetooth can estimate distance and direction simultaneously. The bottom portion visualizes two measurement techniques. The <strong>Phase-Based Ranging</strong> chart shows how two signals of different frequencies experience measurable phase shifts that correspond to distance. The <strong>Round Trip Time (RTT)</strong> diagram on the right depicts packets traveling between the initiator and reflector, with the elapsed time between transmission and reception used to calculate distance. Together, these visuals illustrate how Bluetooth 6.0 achieves centimeter-level accuracy for applications like digital keys, indoor navigation, and spatially aware IoT systems.</p>
<h4 id="heading-decision-based-advertising-filtering">Decision-Based Advertising Filtering</h4>
<p>Bluetooth devices now decide which advertisements to process and which to ignore, saving both power and bandwidth. It’s like teaching scanners to pay attention only when it’s worth it.</p>
<p><img src="https://www.bluetooth.com/wp-content/uploads/2024/08/Bluetooth_Core_6_Figure_11.png" alt="Bluetooth_Core_6_Figure_11" width="600" height="400" loading="lazy"></p>
<p>Source: Bluetooth SIG (2024)</p>
<p>Above diagram illustrates the architecture of <strong>Decision-Based Advertising Filtering</strong>, a new Bluetooth 6.0 feature that allows observers to process only relevant broadcast packets, reducing power consumption and unnecessary data handling. The figure depicts two parallel host–controller stacks: the <strong>Observer</strong> on the left and the <strong>Advertiser</strong> on the right. Each side includes an Application layer, Host Controller Interface (HCI), and Controller. On the advertiser side, the application generates <strong>Decision Data</strong> that passes through the HCI to the controller’s advertising engine, where it’s embedded into extended advertising packets known as <em>Decision PDUs</em>. On the observer side, incoming advertising data passes through a <strong>Filter Policy</strong> module in the controller, which selects or rejects packets according to preconfigured decision criteria before forwarding only the relevant <strong>Advertising Reports</strong> to the host application. Blue arrows show configuration and report flows, while the yellow HCI bands highlight the host–controller boundary. Together, the components show how Bluetooth 6.0 empowers devices to make intelligent, context-aware filtering decisions at the controller level, improving efficiency in dense radio environments.</p>
<h4 id="heading-advertiser-monitoring">Advertiser Monitoring</h4>
<p>Gateways can now keep tabs on the state of nearby advertisers, which is critical when hundreds of devices are broadcasting at once.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762412492836/223de7c4-c659-4c43-8514-8a505070a129.png" alt="223de7c4-c659-4c43-8514-8a505070a129" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Source: Bluetooth SIG (2024)</p>
<p>Above image depicts the fundamental interaction between two Bluetooth Low Energy (BLE) device roles — <strong>advertising</strong> and <strong>scanning</strong>. On the left, a smartphone icon represents the scanning device, which actively listens for nearby Bluetooth broadcasts. On the right, a small sensor or tag icon represents the advertising device, periodically transmitting packets that announce its presence, capabilities, or data updates. Blue concentric rings radiate outward from both devices, symbolizing the propagation of radio signals and the overlapping wireless coverage area where scanning and advertising events intersect. The minimalist design highlights the asymmetric nature of BLE communication: the advertiser periodically transmits small bursts of information, while the scanner remains receptive to detect, filter, or connect with those broadcasts — forming the foundation of all Bluetooth discovery, pairing, and data exchange processes.</p>
<h4 id="heading-negotiable-inter-frame-spacing">Negotiable Inter-Frame Spacing</h4>
<p>This lets devices adjust timing between packets to improve throughput and avoid interference in noisy environments.</p>
<p><img src="https://www.bluetooth.com/wp-content/uploads/2024/08/Bluetooth_Core_6_Figure_26.png" alt="Bluetooth_Core_6_Figure_26" width="600" height="400" loading="lazy"></p>
<p>Source: Bluetooth SIG (2024)</p>
<p>Above image illustrates the concept of <strong>Negotiable Inter-Frame Spacing (IFS)</strong> in Bluetooth 6.0, which optimizes the timing between consecutive data packets to improve throughput and reduce interference. The diagram shows two sequences of communication between a <strong>Central (C)</strong> and a <strong>Peripheral (P)</strong> device, represented as alternating blue (C→P) and green (P→C) data blocks. In the first sequence, packets are transmitted with a short, fixed inter-frame spacing labeled <strong>T_IFS</strong>, showing a rapid exchange of packets within a connection event. The second sequence demonstrates the enhanced Bluetooth 6.0 model, where devices can dynamically negotiate a longer spacing interval — indicated by the notation “≥ T_IFS” — to accommodate environmental conditions, controller processing delays, or congestion. The red horizontal arrows mark the overall connection event duration, while the vertical lines represent packet boundaries. By allowing flexible timing adjustments between frames, Bluetooth 6.0 reduces airtime collisions and improves coexistence with other 2.4 GHz systems, particularly in dense or interference-prone environments.</p>
<h4 id="heading-isoal-enhancements">ISOAL Enhancements</h4>
<p>Audio data, especially LE Audio streams, now move more smoothly thanks to improved support for large frames.</p>
<p><img src="https://www.bluetooth.com/wp-content/uploads/2024/08/Bluetooth_Core_6_Figure_22.png" alt="Bluetooth_Core_6_Figure_22" width="600" height="400" loading="lazy"></p>
<p>Source: Bluetooth SIG (2024)</p>
<p>Above diagram illustrates the internal data flow and timing structure of the <strong>Isochronous Adaptation Layer (ISOAL)</strong> in Bluetooth 5.2 and later, which supports synchronized audio and data transmission over LE Isochronous Channels. The figure is divided into three main sections: the <strong>Upper Layer</strong>, the <strong>ISOAL</strong>, and the <strong>Link Layer</strong>. At the top, the Upper Layer handles isochronous data in the form of Service Data Units (SDUs). Within the ISOAL layer, SDUs undergo several key processes — <strong>Fragmentation</strong> and <strong>Segmentation</strong> break data into smaller protocol units, while <strong>Recombination</strong> and <strong>Reassembly</strong> merge received fragments back into complete SDUs. Two important timing-related steps occur in parallel: the <strong>Inclusion of Timing Offsets</strong>, which ensures proper packet scheduling, and <strong>Timing Reconstruction</strong>, which synchronizes the playback or reassembly timing for received streams. These operations produce either <strong>Framed</strong> or <strong>Unframed Protocol Data Units (PDUs)</strong>, which are then passed to the <strong>Link Layer</strong> at the bottom for transmission over the <strong>Isochronous Stream</strong>. The diagram highlights how ISOAL bridges the upper and lower layers, managing timing alignment and packet structure to deliver low-latency, synchronized LE Audio or data streams across multiple devices.</p>
<p>When you put all that together, Bluetooth 6.0 starts looking a lot like Ultra-Wideband in terms of precision, but without needing new hardware. It’s faster, smarter, and somehow more polite on the airwaves.</p>
<h2 id="heading-deep-dive-technical-enhancements">Deep Dive — Technical Enhancements</h2>
<p>This is where Bluetooth starts to feel less like “a thing your phone just does” and more like a finely tuned machine. The new specs add layers of intelligence that make devices more aware of distance, timing, and context. It’s the kind of stuff that gets engineers grinning because it solves problems we’ve all quietly complained about for years.</p>
<p>Let’s walk through a few of the most important ones.</p>
<h3 id="heading-channel-sounding-and-distance-awareness">Channel Sounding and Distance Awareness</h3>
<p>If you’ve ever used RSSI values to guess how far a device is, you know how unpredictable it can be. RSSI measures how strong the signal sounds, not where it actually came from. A wall, a metal shelf, even a human body can distort it. Channel Sounding solves this by looking at <em>phase</em> instead of strength.</p>
<p>Here’s the idea: two devices exchange carefully crafted packets at multiple frequencies. Each frequency behaves like a different musical note. When those notes reach the receiver, their phases – how the peaks and troughs line up – shift slightly depending on distance. The receiver compares the original and received phases, then crunches the math:</p>
<p>$$[ \text{Distance} = \frac{c \times \Delta \phi}{2\pi f} ]$$</p><p>where:</p>
<ul>
<li><p>( c ) is the speed of light,</p>
</li>
<li><p>( \Delta \phi ) is the phase shift,</p>
</li>
<li><p>( f ) is the carrier frequency.</p>
</li>
</ul>
<p>This approach allows for precise distance measurement, achieving accuracy down to a few centimeters by analyzing the phase differences of signals received at multiple frequencies.</p>
<p>That level of precision changes the game. Cars can unlock automatically only when you’re physically beside the door. Smart-building systems can tell which room you’re standing in. Mixed-reality headsets can map your movements without extra sensors.</p>
<p>From a development point of view, you’ll need hardware that supports the new Channel Sounding PHY. Nordic’s nRF54 and Silicon Labs’ BG24 families already expose low-level APIs for it. Expect to work closer to the metal than usual: calibration, antenna diversity, and clock stability all affect measurement accuracy. It’s worth the effort, though. Few wireless technologies can deliver this precision without expensive dedicated hardware.</p>
<h3 id="heading-periodic-advertising-with-responses-pawr-1">Periodic Advertising with Responses (PAwR)</h3>
<p>For years, BLE advertising worked like shouting into a room and hoping someone heard you. The moment you wanted a reply, you had to form a full connection. That model doesn’t scale when you have ten-thousand tiny sensors that each wake up once a minute.</p>
<p>PAwR flips the model. Think of it as a scheduled town-hall meeting. A coordinator (the gateway) broadcasts a timeline. Each sensor has a reserved time slot to respond within that cycle. Because everyone speaks only during their assigned moment, collisions disappear and energy use plummets.</p>
<p>In practice, this lets one gateway handle tens of thousands of devices without ever maintaining individual connections. Supermarkets use it for electronic shelf labels that update prices in seconds. Factories deploy it for environmental sensors that report temperature and vibration periodically.</p>
<p>Developers integrating PAwR will notice that it doesn’t replace connections, it complements them. You can still open a full GATT session for configuration, but routine data flows through lightweight PAwR exchanges. Most modern SDKs, including Zephyr and ESP-IDF, now include PAwR APIs under their extended-advertising modules.</p>
<h3 id="heading-isochronous-audio-channels-amp-le-audio">Isochronous Audio Channels &amp; LE Audio</h3>
<p>Bluetooth’s original audio stack wasn’t built for what we expect today. It was designed for single-stream mono headsets, not for multi-earbud synchronized audio or broadcast systems. Isochronous Channels fix that by ensuring that every packet in a group shares the same clock reference.</p>
<p>Two modes exist:</p>
<ul>
<li><p><strong>Connected ISO Streams (CIS)</strong> handle one-to-one cases like stereo earbuds</p>
</li>
<li><p><strong>Broadcast ISO Streams (BIS)</strong> allow a transmitter to serve an unlimited audience, such as a gym or theater.</p>
</li>
</ul>
<p>Both rely on the <strong>LC3 codec</strong>, which delivers near-lossless sound at roughly half the bandwidth of SBC.</p>
<p>In real life, this means earbuds that stay perfectly in sync even if you walk between interference zones, hearing aids that seamlessly share the same stream, and venues that broadcast announcements directly to phones without dedicated receivers. Android 14 and iOS 17 have already exposed system-level LE Audio support, so app developers can finally build end-user experiences without vendor-specific hacks.</p>
<p>For embedded engineers, implementing LE Audio requires controller firmware that supports ISOAL (Isochronous Adaptation Layer) and host-side stack integration. Nordic, Qualcomm, and Dialog all provide reference implementations, but testing is key – timing drift between links can break audio quality faster than you might expect.</p>
<h3 id="heading-power-amp-efficiency-improvements">Power &amp; Efficiency Improvements</h3>
<p>Battery life has always been Bluetooth’s quiet superpower, and version 6.0 tightens the screws even more. Rather than one big change, it’s a collection of small ones that add up.</p>
<p>Negotiable inter-frame spacing lets devices adjust the delay between packets, smoothing out contention when the air is busy. Controllers now enter deeper sleep states automatically, waking only when the radio truly needs them. Smarter advertising filters prevent devices from wasting time processing duplicates, and new firmware offloads push repetitive tasks (like connection parameter updates) away from the CPU.</p>
<p>When engineers combine all these tricks, the numbers look impressive: about a ten to twenty percent battery gain in dense environments. That might not sound huge, but for a coin-cell tag meant to last three years, it’s the difference between hitting the spec or not.</p>
<h3 id="heading-security-amp-privacy-upgrades">Security &amp; Privacy Upgrades</h3>
<p>With great connectivity comes great responsibility. Bluetooth now sits at the heart of cars, locks, and health monitors, which makes security non-negotiable. The new stack finally treats it as a first-class citizen.</p>
<p>LE Secure Connections with numeric comparison are now standard, encrypted advertising data hides sensitive broadcasts, and Channel Sounding even enables distance-based access control. In plain language, a device can now verify that you’re physically nearby before sharing keys or unlocking features.</p>
<p>Still, protocol features alone aren’t enough. Developers should rotate identity-resolving keys regularly, invalidate old bonds on firmware updates, and avoid static passkeys. Security in Bluetooth is like security anywhere else: the spec provides the locks, but you’re responsible for turning the key.</p>
<p>Together, these improvements make Bluetooth feel more alive, more aware, and more efficient. The stack now senses distance, saves power, and defends privacy without breaking backward compatibility. It’s a quiet revolution hidden inside chips that most people never think about, yet it’s shaping how billions of devices will talk to each other over the next decade.</p>
<h2 id="heading-real-world-applications-in-2025">Real-World Applications in 2025</h2>
<p>It’s one thing to read about Channel Sounding or PAwR in a spec sheet. It’s another to see these features come alive in everyday products.</p>
<p>Bluetooth has quietly spread into nearly every corner of our lives, from the shelves of supermarkets to the dashboards of cars. By 2025, it’s no exaggeration to call it the most widely deployed wireless ecosystem on Earth.</p>
<p>Let’s look at where these new capabilities are already making an impact.</p>
<h3 id="heading-retail-electronic-shelf-labels-and-smart-inventory">Retail: Electronic Shelf Labels and Smart Inventory</h3>
<p>Walk into a modern supermarket in 2025 and look closely at the price tags. They aren’t paper anymore. Those little digital labels, changing prices in real time, are powered by Bluetooth 5.4’s <strong>Periodic Advertising with Responses (PAwR)</strong> and <strong>Encrypted Advertising Data</strong>.</p>
<p>Each label is a low-power sensor node, quietly listening for broadcast schedules from a gateway mounted above the aisle. When it’s their turn, the tags wake up, confirm their slot, and update the display – all in milliseconds and without forming a traditional Bluetooth connection. The result is a network of tens of thousands of nodes that consumes almost no energy.</p>
<p>Security matters here too. Encrypted advertising ensures that a competing store or curious shopper can’t sniff price data or inject bogus updates. Everything runs on coin-cell batteries that last several years, which saves retailers both time and maintenance costs.</p>
<h3 id="heading-smart-home-context-aware-unlocking-and-personal-audio">Smart Home: Context-Aware Unlocking and Personal Audio</h3>
<p>If you’ve ever fumbled with your phone to unlock a smart door, Bluetooth 6.0 might finally fix that. <strong>Channel Sounding</strong> makes proximity detection precise enough to trust. The system can tell whether you’re standing by the door or ten meters away in the driveway. Only when you’re truly within range does it trigger the unlock sequence.</p>
<p>The same precision is reshaping personal audio. Imagine walking from your living room to the kitchen and having your smart speaker hand off the song to your earbuds automatically. That’s <strong>LE Audio</strong> working behind the scenes with isochronous channels, keeping streams perfectly aligned across multiple endpoints. It feels invisible, which is exactly how good technology should feel.</p>
<h3 id="heading-healthcare-reliable-secure-patient-monitoring">Healthcare: Reliable, Secure Patient Monitoring</h3>
<p>Hospitals have long relied on wireless monitors, but interference and power limits made them tricky. With PAwR, a single access point can now coordinate thousands of small sensors that track vitals like heart rate, oxygen, or temperature. These devices communicate in brief, deterministic bursts, avoiding packet collisions that used to plague dense wards.</p>
<p>Privacy is critical, and that’s where encrypted advertising comes in. Patient identifiers and medical readings remain hidden even in broadcast form. Channel Sounding adds another layer by confirming proximity: only readers within a safe range can retrieve sensitive data.</p>
<p>Combined, these features help reduce misreads and protect patient confidentiality without adding extra setup steps for clinicians.</p>
<h3 id="heading-industry-40-asset-tracking-and-condition-monitoring">Industry 4.0: Asset Tracking and Condition Monitoring</h3>
<p>Factories and warehouses are some of Bluetooth’s biggest playgrounds. Equipment now comes with embedded Bluetooth 6.0 modules that use Channel Sounding for ultra-precise location tracking. Pallets, forklifts, and tools broadcast their position continuously, helping logistics teams know what’s where, all the time.</p>
<p>Add PAwR, and you get scalable telemetry for thousands of machines. Vibration, temperature, or pressure data can flow reliably to a single gateway. Some systems even combine Bluetooth data with AI analytics to predict failures before they happen. The ability to measure distance accurately also helps robots navigate crowded spaces safely.</p>
<h3 id="heading-wearables-hearables-ar-glasses-and-health-bands">Wearables: Hearables, AR Glasses, and Health Bands</h3>
<p>Wearable devices benefit more than any other category. Modern earbuds use LE Audio to keep both sides synchronized, whether you’re streaming a movie or on a call. Hearing aids receive direct broadcast audio in public venues without special adapters.</p>
<p>AR glasses are an even bigger frontier. They use Channel Sounding to sense spatial relationships between the wearer, nearby devices, and the environment. That allows context-aware overlays – navigation cues, health metrics, or notifications – that appear exactly where they make sense. Bluetooth’s low-power model keeps these systems lightweight enough to run all day.</p>
<h3 id="heading-automotive-digital-keys-and-vehicle-telemetry">Automotive: Digital Keys and Vehicle Telemetry</h3>
<p>Cars are fast becoming Bluetooth hubs on wheels. <strong>Digital Key Systems</strong> already use Bluetooth 6.0’s distance measurement to ensure you’re physically close before unlocking or starting the engine. It’s safer than older RSSI-based solutions that could be fooled by signal relays.</p>
<p>Onboard sensors rely on secure connections and encrypted advertising to stream data about tire pressure, cabin air quality, or driver posture. Maintenance centers can access diagnostic data automatically when a car pulls in, without plugging in a cable. In short, Bluetooth has quietly replaced several proprietary systems once needed for short-range communication inside vehicles.</p>
<h3 id="heading-the-big-picture">The Big Picture</h3>
<p>What’s striking is how flexible Bluetooth has become. The same fundamental protocol now powers medical wearables, industrial sensors, and entertainment systems. Each use case leans on a different mix of features – PAwR for scale, Channel Sounding for precision, LE Audio for experience, and encrypted advertising for privacy – but the foundation is consistent.</p>
<p>It’s this adaptability that explains why Bluetooth continues to thrive despite predictions of its demise. Rather than being replaced by Wi-Fi or UWB, it’s learning from them, borrowing their strengths, and finding new roles.</p>
<h2 id="heading-developer-guide-getting-started">Developer Guide — Getting Started</h2>
<p>Bluetooth 6.0 may sound futuristic, but the good news is that you don’t have to wait years to use it. Most of the new features are already landing in chipsets, SDKs, and development kits. If you’re an engineer or hobbyist itching to get your hands dirty, this section walks you through what to look for, how to get started, and a few pitfalls to watch out for along the way.</p>
<h3 id="heading-picking-the-right-chipset">Picking the Right Chipset</h3>
<p>The chipset you choose sets the tone for your entire project. If you’re building something simple, like a smart tag or sensor, you’ll want a microcontroller with integrated Bluetooth Low Energy and minimal power draw. But if you plan to experiment with Channel Sounding, LE Audio, or PAwR, you’ll need silicon that explicitly supports Bluetooth 5.4 or 6.0 features.</p>
<p>Current front-runners include the Nordic nRF54 series, Dialog DA1470x, and Silicon Labs BG24 family. These are developer-friendly chips with mature SDKs and good documentation. They also have flexible radio subsystems, which matter a lot when you’re testing features like Channel Sounding that depend on timing and signal stability.</p>
<p>A small tip from experience: always check the vendor’s firmware release notes. Some Bluetooth 6.0-capable chips still require you to enable experimental PHY layers or SDK flags to unlock certain features.</p>
<h3 id="heading-sdk-and-stack-support">SDK and Stack Support</h3>
<p>Once you’ve got your hardware, the next step is setting up your software stack. Most Bluetooth development happens through vendor SDKs or open platforms like Zephyr RTOS, ESP-IDF, or BlueZ on Linux.</p>
<p>If you’re targeting embedded systems, Zephyr is a great place to start. It’s modular, stable, and already includes PAwR and LE Audio APIs under its <code>bt_le_ext_adv</code> and <code>iso</code> modules. Silicon Labs’ Simplicity Studio also has strong tooling around Bluetooth mesh and PAwR.</p>
<p>On desktop or gateway platforms, Linux’s BlueZ stack supports extended advertising and secure connections out of the box, and work is underway to integrate Channel Sounding support via new HCI commands.</p>
<p>Always verify that your controller firmware is up to date before testing new features. Many “missing API” errors trace back to outdated controller images that don’t yet recognize the relevant HCI opcodes.</p>
<h3 id="heading-advertising-strategy">Advertising Strategy</h3>
<p>Advertising is still the heartbeat of Bluetooth, and now it’s smarter than ever. Here’s a simple example of setting up extended advertising in C-style pseudocode:</p>
<pre><code class="lang-plaintext">ble_adv_params params = {
    .type = ADV_EXTENDED,
    .interval = 160,   // 100ms interval
    .tx_power = 0      // default transmit power
};

ble_set_adv_data(payload, sizeof(payload));
ble_start_advertising(&amp;params);
</code></pre>
<p>Above pseudocode demonstrates how a Bluetooth Low Energy (BLE) device initializes and starts broadcasting advertisements so that nearby devices can discover it. The first block defines a structure named <code>ble_adv_params</code>, which contains the configuration settings for advertising. The <code>.type = ADV_EXTENDED</code> field specifies that the device will use <strong>Extended Advertising</strong>, a feature introduced in Bluetooth 5.0 that allows for larger payloads, better range, and the use of secondary channels beyond the traditional 31-byte limit of legacy advertising. The <code>.interval = 160</code> value sets the advertising interval, expressed in Bluetooth time units of 0.625 milliseconds, meaning the device transmits an advertising packet every 100 milliseconds—frequent enough for responsive discovery without excessive power consumption. The <code>.tx_power = 0</code> field sets the transmit power level to 0 dBm, which is the default radio output power and provides a balanced tradeoff between energy efficiency and signal range. After configuring the parameters, the function <code>ble_set_adv_data(payload, sizeof(payload))</code> loads the advertising data—typically a collection of identifiers such as the device name, UUIDs for available services, manufacturer-specific data, or other Bluetooth advertising fields. This is the information that other devices see when scanning nearby. Finally, <code>ble_start_advertising(&amp;params)</code> begins the actual transmission, instructing the BLE controller to start broadcasting the configured data on the standard advertising channels (37, 38, and 39). Once active, the device periodically transmits these packets until advertising is stopped manually or a central device establishes a connection. In essence, this short snippet encapsulates the three fundamental steps of BLE advertising: configuring the radio parameters, defining the broadcast data, and enabling the periodic advertisements that make the device visible to others.</p>
<p>This kind of setup works well for extended advertising and PAwR broadcast scheduling. When designing your advertising payloads, remember that the new encrypted format (introduced in 5.4) limits available space slightly, so plan for tighter data packing if you’re including custom fields.</p>
<p>If you’re building something that needs connection-less updates (like a sensor network), use PAwR or periodic advertising. For interactive applications, where you expect users to connect via a phone or hub, extended connectable advertising remains the right choice.</p>
<h3 id="heading-connection-optimization">Connection Optimization</h3>
<p>Tuning connection parameters is half art, half science. You’ll often find yourself trading latency for battery life. For streaming or LE Audio applications, intervals around <strong>24–40 ms</strong> usually strike the right balance. For sensors or telemetry, you can stretch that interval out to save energy.</p>
<p>Sniff subrating is another underrated feature. It lets a peripheral sleep longer while maintaining an active connection, reducing energy use without affecting responsiveness too much.</p>
<p>If you’re testing with multiple devices, simulate busy airspace using tools like Ellisys Bluetooth Analyzer or the nRF Sniffer. This helps uncover timing issues or packet loss that might only show up in dense radio environments.</p>
<h3 id="heading-power-testing">Power Testing</h3>
<p>It’s easy to claim low power on paper – but proving it is another story. Use your dev kit’s current profiling tools to measure sleep and active currents under different intervals and PHY settings.</p>
<p>Run your firmware through long-duration tests in “noisy” airspace – meaning multiple other Bluetooth or Wi-Fi devices nearby. The goal is to see how your firmware reacts when packet retries or interference increase. Sometimes small timing tweaks can make big differences in battery life.</p>
<p>As a general rule, always start testing on the <strong>1M PHY</strong> (the default) and only switch to <strong>2M</strong> for high-throughput use cases like audio. Long-range modes can be valuable for IoT, but remember that higher receive sensitivity often costs extra current.</p>
<h3 id="heading-security-checklist">Security Checklist</h3>
<p>Bluetooth 6.0 brings much stronger built-in security, but you’ll still need to wire it up correctly. Make sure to:</p>
<ul>
<li><p>Use LE Secure Connections instead of legacy pairing.</p>
</li>
<li><p>Rotate Identity Resolving Keys (IRK) periodically.</p>
</li>
<li><p>Encrypt advertising payloads whenever transmitting private or medical data.</p>
</li>
<li><p>Handle key storage securely on your device, preferably with hardware-backed encryption or secure flash.</p>
</li>
</ul>
<p>Also, watch for privacy gaps in the connection flow. Even encrypted devices can leak identity information if they reuse resolvable addresses or fail to clear bonds properly on reset.</p>
<h3 id="heading-backward-compatibility">Backward Compatibility</h3>
<p>Real-world devices won’t all jump to Bluetooth 6.0 overnight. Your code should always detect peer capabilities and fall back gracefully. The HCI layer provides read commands that reveal which features the remote device supports.</p>
<p>For example, if Channel Sounding isn’t available, default to RSSI-based proximity or skip distance-based logic entirely. Similarly, if LE Audio isn’t supported, fall back to classic A2DP. Designing your firmware with this flexibility keeps your products compatible with millions of existing devices.</p>
<h3 id="heading-testing-and-certification">Testing and Certification</h3>
<p>Once your prototype works, you’ll need to qualify it through the <strong>Bluetooth SIG Qualification Program</strong>. This process ensures your product complies with the spec and interoperates correctly with others. It might sound intimidating, but many vendors offer pre-qualified modules or test reports you can reuse to simplify the paperwork.</p>
<p>For debugging and validation, tools like the Ellisys Bluetooth Analyzer, Frontline BPA 600, or Nordic’s nRF Sniffer can capture over-the-air traffic and help verify packet sequences, timing, and encryption states.</p>
<p>Bluetooth development can be frustrating at first, as there’s lots of acronyms, layers, and hidden dependencies. But once you start seeing the system as a living conversation between devices, it clicks. The more you experiment with advertising intervals, connection timing, and PHY modes, the more you’ll appreciate how elegant and flexible the stack really is.</p>
<p>If you’ve ever wanted to build something that talks wirelessly and runs for months on a battery, this is your moment. The ecosystem has matured, the tools are ready, and the possibilities keep expanding.</p>
<h2 id="heading-challenges-amp-trade-offs">Challenges &amp; Trade-Offs</h2>
<p>It’s tempting to think of Bluetooth 6.0 as flawless – after all, it’s faster, more efficient, and infinitely scalable. But like every engineering advancement, it comes with trade-offs. Real deployments reveal quirks that the spec sheets don’t mention, and knowing these early can save hours of debugging (and a few late-night rants).</p>
<h3 id="heading-adoption-lag">Adoption Lag</h3>
<p>Every new Bluetooth spec sounds exciting on paper until you realize the hardware for it isn’t widely available yet. Controller vendors take time to integrate the latest features, and phone or OS support can lag by a year or two. You might find yourself reading about Channel Sounding or PAwR in the core spec, only to discover that your development kit still marks them as “experimental.”</p>
<p>This is normal. The Bluetooth SIG’s release cadence moves faster than the hardware ecosystem can follow. The best strategy is to design firmware that detects capabilities dynamically. Build your code to gracefully fall back to 5.0 or 5.2 modes if 6.0 features are missing. That way your product ships today, but it’s ready for the future.</p>
<h3 id="heading-environmental-interference">Environmental Interference</h3>
<p>Bluetooth still lives in the 2.4 GHz band, the same noisy neighborhood as Wi-Fi, microwaves, and countless IoT gadgets. In factories or dense apartments, you’ll see interference spikes that cause packet loss or delay. Even with adaptive frequency hopping, performance can dip if too many radios are talking at once.</p>
<p>Developers need to test in real environments, not just in quiet labs. Use spectrum analyzers or sniffers to visualize congestion. Adjust transmit power, advertisement intervals, or even antenna orientation to mitigate problems. Remember, radio design is part science, part art. Sometimes moving a board trace by a centimeter makes more difference than rewriting code.</p>
<h3 id="heading-power-versus-performance">Power Versus Performance</h3>
<p>Every Bluetooth generation tries to squeeze more precision and range out of roughly the same battery. Channel Sounding and high-speed PHY modes improve accuracy and throughput, but they also increase radio-on time and CPU load. You gain features but spend more energy to get them.</p>
<p>There’s no universal setting that fits all products. A hearing aid might value low latency over battery life, while a temperature sensor prioritizes sleeping as much as possible. Developers must tune intervals, transmission power, and frame spacing through measurement, not guesswork. The good news is that once you find the sweet spot, Bluetooth tends to be remarkably stable over long periods.</p>
<h3 id="heading-security-configuration">Security Configuration</h3>
<p>Modern Bluetooth has excellent built-in security, but only if you use it correctly. Misconfigured advertising, static passkeys, or unrotated identity keys can still leak information. Even encrypted advertising won’t help if your firmware accidentally reuses session data.</p>
<p>The takeaway: don’t assume “secure by default.” Review every pairing and bonding flow, handle key rotation on firmware updates, and wipe old bonds when a user resets the device. The protocol gives you powerful locks, but it’s up to you to actually turn the key.</p>
<h3 id="heading-software-complexity">Software Complexity</h3>
<p>The Bluetooth stack is getting heavier. Features like PAwR, Channel Sounding, and Isochronous Audio require new roles, new timing models, and new APIs. Developers who are used to simple GATT servers now have to think about scheduling, synchronization, and PHY coordination. Testing these features on multi-role devices can be especially tricky, since a single controller might handle multiple concurrent roles (central, peripheral, broadcaster, and observer).</p>
<p>If you’re working on an embedded platform, modular firmware design becomes essential. Split radio control, connection management, and application logic into distinct layers. It’s easier to debug timing bugs when your architecture mirrors the Bluetooth stack’s separation of concerns.</p>
<h3 id="heading-fragmentation">Fragmentation</h3>
<p>Perhaps the most persistent challenge is fragmentation. Not every OEM implements the same subset of features, and some phones or chipsets may partially support a spec while skipping optional sections. Developers quickly learn that “Bluetooth 6.0” can mean slightly different things depending on the vendor.</p>
<p>The practical fix is to build flexibility into your software. Use feature discovery at runtime, keep your update mechanism ready for OTA patches, and enable configuration flags for new features so you can toggle them per device. Testing across diverse hardware early in the process pays off more than any elegant design decision later.</p>
<h3 id="heading-mitigation-and-mindset">Mitigation and Mindset</h3>
<p>Despite these challenges, none of them are deal-breakers. They’re simply part of building systems that live in the real world. Think modular, plan for gradual rollouts, and make firmware updates painless. Bluetooth’s backward compatibility means your device won’t become obsolete overnight, and your users benefit from improvements as the ecosystem matures.</p>
<p>In short, the trick isn’t avoiding the trade-offs but managing them. When you design with flexibility, Bluetooth 6.0 becomes less of a moving target and more of a living platform that grows alongside your product.</p>
<h2 id="heading-the-road-ahead-bluetooth-61-and-beyond">The Road Ahead — Bluetooth 6.1 and Beyond</h2>
<p>If Bluetooth 6.0 was about awareness – knowing distance, filtering intelligently, and optimizing communication – then Bluetooth 6.1 is about refinement. It takes what already works and polishes it into something smoother, faster, and a little more elegant. It’s not a revolution, but it’s an important step in Bluetooth’s quiet transformation from a “wireless cable” into a context-aware network fabric for everyday devices.</p>
<h3 id="heading-small-tweaks-big-payoffs">Small Tweaks, Big Payoffs</h3>
<p>Bluetooth 6.1 focuses on tightening the nuts and bolts rather than changing the whole machine. The update improves Channel Sounding accuracy, enhances advertising efficiency, and introduces a few quality-of-life adjustments to make device coordination easier.</p>
<p>That might sound minor, but it matters. Channel Sounding, for example, becomes more reliable when multiple reflections or obstacles exist. In indoor positioning systems like airports, hospitals, or museums, even a five percent improvement in accuracy can reduce false detections by a wide margin. Advertising refinements also make large IoT deployments more predictable, allowing gateways to manage high-density environments with less radio congestion.</p>
<p>In simpler terms: Bluetooth 6.1 is like a firmware tune-up for an already fast car. You may not notice it day to day, but under heavy load, it performs better and wastes less energy.</p>
<h3 id="heading-the-emerging-themes">The Emerging Themes</h3>
<p>Beyond the incremental fixes, the Bluetooth community is thinking much bigger. The next few years will likely focus on four major themes: energy harvesting, AI-assisted radio optimization, hybrid positioning, and context-aware security.</p>
<h4 id="heading-1-energy-harvesting-bluetooth-devices">1. Energy-Harvesting Bluetooth Devices</h4>
<p>We’re starting to see early prototypes of Bluetooth tags and sensors that run entirely on harvested energy – light, heat, or vibration – with no traditional battery. This ties into the push for maintenance-free IoT devices, especially in logistics and environmental sensing. Future specifications will refine ultra-low-duty-cycle communication patterns to support these “powerless” nodes.</p>
<h4 id="heading-2-ai-driven-radio-management">2. AI-Driven Radio Management</h4>
<p>Imagine a Bluetooth controller that dynamically learns the noise profile of its environment and adjusts its PHY, transmit power, or advertising timing in real time. Instead of a static table of parameters, AI models embedded in the firmware could predict interference and choose the best channel map automatically. It sounds futuristic, but chipmakers are already experimenting with machine learning cores in connectivity modules.</p>
<h4 id="heading-3-cross-technology-fusion-bluetooth-wi-fi-uwb">3. Cross-Technology Fusion (Bluetooth + Wi-Fi + UWB)</h4>
<p>The border between short-range radios is blurring. Some systems already use Wi-Fi for throughput, Bluetooth for discovery, and UWB for pinpoint accuracy – all orchestrated by a single chipset. The goal isn’t to replace one with another but to fuse them, creating hybrid location frameworks that are more reliable than any single technology. Bluetooth’s Channel Sounding makes it a perfect partner in this mix.</p>
<h4 id="heading-4-context-aware-security">4. Context-Aware Security</h4>
<p>Future Bluetooth devices might decide access rights based not just on identity, but on <em>context</em>. For example, your smartwatch could unlock your laptop only if it detects that you’re sitting still and within one meter. That combination of motion, distance, and authentication could drastically reduce spoofing or relay attacks.</p>
<h3 id="heading-the-quiet-backbone-of-connectivity">The Quiet Backbone of Connectivity</h3>
<p>What’s fascinating about Bluetooth’s evolution is how quietly it happens. While other technologies make noise about high throughput or low latency, Bluetooth’s progress feels invisible but omnipresent. It doesn’t chase raw speed anymore – it chases <em>relevance</em>. The protocol is learning to sense, adapt, and coordinate, all qualities that make it essential for the next generation of ambient computing.</p>
<p>So while you might not notice Bluetooth 6.1 when it arrives, you’ll definitely feel its effects. Devices will sync faster, connections will drop less, audio will sound cleaner, and proximity-based features will just “know” what you want them to do. That’s the beauty of mature engineering: when it works so seamlessly that people stop thinking about it altogether.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Bluetooth has come a long way from its early days as a clunky pairing protocol for headsets. It’s now one of the quietest yet most influential technologies shaping how devices around us communicate. The newer generations – 5.4, 6.0, and soon 6.1 – show that Bluetooth’s evolution isn’t about flashy upgrades. It’s about <em>refinement</em>, about making wireless communication more precise, more private, and more power-aware.</p>
<p>At its core, Bluetooth’s story is about context. It’s learning to understand where you are, how far you are from something, and what kind of connection makes sense in that moment. Channel Sounding adds spatial awareness, PAwR makes massive IoT networks practical, LE Audio brings synchronized sound to earbuds, hearing aids, and broadcast systems, and encrypted advertising protects the information flowing through all of it.</p>
<p>For developers, this era of Bluetooth is exciting because it’s full of creative possibilities. You can build smarter sensors, more responsive wearables, or secure access systems that simply <em>know</em> when you’re nearby. The ecosystem is mature enough that you don’t need to be a radio engineer to experiment, but it’s still evolving fast enough to keep pushing boundaries.</p>
<p>The challenge now is not whether Bluetooth can handle the future. It’s how we, as developers and designers, decide to use it. Whether it’s powering ambient computing, healthcare networks, or next-gen audio, the technology is already ready.</p>
<p>So maybe the next time you put on your earbuds or unlock your car, take a moment to appreciate the quiet genius working behind the scenes. Bluetooth is thriving, adapting, and quietly building the connective tissue of our digital lives.</p>
<p>And for those of us who like tinkering with the unseen layers of technology, that’s a future well worth exploring.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Meet Pyrinas - an IoT Development Kit For Your Particle Xenon ]]>
                </title>
                <description>
                    <![CDATA[ By Jared Wolff This post is a bit lengthy. If you prefer, signup to get the full PDF here. ? After Particle's Mesh deprecation announcement, many have been left to figure out how to deploy their low power sensor networks. There was always the option ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/meet-pyrinas-an-iot-development-kit-for-your-particle-xenon/</link>
                <guid isPermaLink="false">66d85060c15439a8d5631e83</guid>
                
                    <category>
                        <![CDATA[ Bluetooth Low Energy ]]>
                    </category>
                
                    <category>
                        <![CDATA[ embedded systems ]]>
                    </category>
                
                    <category>
                        <![CDATA[ iot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ particle ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 11 Mar 2020 13:49:35 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/03/Copy-of-Particle-Mesh-App-Updates.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Jared Wolff</p>
<p>This post is a bit lengthy. If you prefer, <a target="_blank" href="https://www.jaredwolff.com/files/pyrinas-intro/">signup to get the full PDF here.</a> ?</p>
<p>After <a target="_blank" href="https://www.jaredwolff.com/particle-mesh-deprecation-livestream/">Particle's Mesh deprecation announcement</a>, many have been left to figure out how to deploy their low power sensor networks. There was always the option of using <a target="_blank" href="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/">Particle's Built in Bluetooth stack</a> but as it stands today it's not secure.</p>
<p>Previously I had helped form a very simple nRF SDK-based hub and spoke IoT deployment. Unfortunately, it was closed source and the company is no longer around.</p>
<p>So what's a guy to do?</p>
<p>Build another one and make it <em>open. (BSD licensed to be exact!)</em> Open and free for anyone to use adopt and improve upon. Plus if you're building a product that's using the code, <strong>you don't have to share your improvements or proprietary code with anyone.</strong></p>
<p>In this post I'll be talking about how to get started with Pyrinas. It uses Nordic's time tested SDK as a basis for the kernel of the system. The main concept of Pyrinas is to abstract as much IoT cruft away so you can focus on your application.</p>
<p>So without further ado, let's chat about what Pyrinas is and what it isn't.</p>
<h2 id="heading-what-pyrinas-is">What Pyrinas is</h2>
<ul>
<li>Is an embedded "kernel", written in C. It's open and permissive IoT development environment you can use for anything you want. Seriously. It's BSD licensed and can be used in closed source applications.</li>
<li>Using the power of Bluetooth 5.0 Long Range, Pyrinas allows you to communicate with many peripheral devices at one time. Currently Pyrinas has been tested with 3 simultaneous peripheral connections. Theoretically, it can support up to 20 simultaneous connections. (Thanks to <a target="_blank" href="https://cm.nordicsemi.com//Software-and-tools/Software/Bluetooth-Software">Nordic's S140 Softdevice</a>)</li>
<li><p>Pyrinas transports its data in two ways</p>
<ul>
<li>In a familiar string format used by Particle</li>
<li>A custom Protocol Buffer for raw data transmission.</li>
</ul>
<p>That way you have a <strong>choice</strong> of how you want to process and publish your data!</p>
</li>
</ul>
<h2 id="heading-what-pyrinas-isnt">What Pyrinas isn't</h2>
<ul>
<li>Pyrinas is not a RTOS (real time operating system). If you have a need to run multiple threaded applications on embedded, Pyrinas is not for you.</li>
<li>Pyrinas, at this time, does not support Mesh.</li>
<li>An OS for every single kind of Bluetooth SoC on the market. Due to the tight coupling with Nordic's nRF SDK, Pyrinas only works with Nordic's SoCs.</li>
<li>A turnkey solution for IoT. Pyrinas is early on it it's development process. The aim is for it to become a viable option for anyone to develop and publish IoT applications <em>the way they want to.</em> There's no vendor lock in. There's no surprises.</li>
</ul>
<h2 id="heading-things-youll-need">Things you'll need</h2>
<p>There are a few things you'll need in order to get going with Pyrinas.</p>
<ul>
<li>At least 2 Particle Xenons</li>
<li>At least 1 nRF Development board or J-link programmer</li>
<li>Associated USB cables</li>
</ul>
<h2 id="heading-getting-started-with-an-example">Getting started with an example</h2>
<p>Getting started with Pyrinas involves two repositories.</p>
<ul>
<li><a target="_blank" href="https://github.com/pyrinas-iot/pyrinas-os">The OS respository</a></li>
<li><a target="_blank" href="https://github.com/pyrinas-iot/pyrinas-template">The template</a></li>
</ul>
<p>The OS directory has all the source, SDK dependencies and toolchain you need to use Pyrinas.</p>
<p>The template is where you add all your application code. The template provides a starting point for you and your project.</p>
<p>Here's how everything goes together:</p>
<p>Clone the OS directory to some place on your machine:</p>
<pre><code class="lang-shell">git clone https://github.com/pyrinas-iot/pyrinas-os.git --recurse-submodules
</code></pre>
<p>Once complete, change directories to <code>pyrinas-os</code> and run <code>make setup</code></p>
<pre><code class="lang-shell">cd pyrinas-os
make setup
</code></pre>
<p>This will download your toolchain and SDK dependencies.</p>
<p>In order to use OTA DFU, you will need to also generate the DFU key for the process:</p>
<pre><code class="lang-shell">make gen_key
</code></pre>
<p>The files generated by this process will be used later.</p>
<p>Next, we'll want to use the template to make two new projects. In this example we'll have one "hub" and one "sensor." Simply navigate to the <a target="_blank" href="https://github.com/pyrinas-iot/pyrinas-template">template repository</a> and click the <strong>Use this template</strong> button.</p>
<p><img src="https://www.jaredwolff.com/meet-pyrinas-a-new-way-to-use-your-xenon/images//Screen_Shot_2020-03-09_at_5.12.21_PM.png" alt="https://www.jaredwolff.com/meet-pyrinas-a-new-way-to-use-your-xenon/images//Screen_Shot_2020-03-09_at_5.12.21_PM.png" width="730" height="420" loading="lazy"></p>
<p>Then name your new repository. Click the <strong>Create repository from template</strong> button when you're happy with everything.</p>
<p><img src="https://www.jaredwolff.com/meet-pyrinas-a-new-way-to-use-your-xenon/images//Screen_Shot_2020-03-09_at_5.20.15_PM.png" alt="https://www.jaredwolff.com/meet-pyrinas-a-new-way-to-use-your-xenon/images//Screen_Shot_2020-03-09_at_5.20.15_PM.png" width="730" height="449" loading="lazy"></p>
<p>Then clone your repository to the same directory as <code>pyrinas-os</code>. Make sure you replace <code>&lt;your username&gt;</code> and <code>&lt;repo name&gt;</code> with your own.</p>
<pre><code class="lang-shell">cd ..
git clone https://github.com/&lt;your username&gt;/&lt;repo name&gt;.git hub
</code></pre>
<p>After this is done, go back and create a new repository from the template. We'll be using this one for the <em>sensor</em> node.</p>
<p><img src="https://www.jaredwolff.com/meet-pyrinas-a-new-way-to-use-your-xenon/images//Screen_Shot_2020-03-09_at_5.24.15_PM.png" alt="https://www.jaredwolff.com/meet-pyrinas-a-new-way-to-use-your-xenon/images//Screen_Shot_2020-03-09_at_5.24.15_PM.png" width="730" height="449" loading="lazy"></p>
<p>Clone this repository once you're done setting it up in the same place as your <code>hub</code> and <code>pyrinas-os</code> repositories.</p>
<p>Now that we have all our repositories, let's start with our sensor node.</p>
<h3 id="heading-setting-up-the-sensor-node-repository">Setting up the sensor node repository</h3>
<p>Open up the sensor repository using a program like Microsoft's VS Code. If you have the command line shortcuts you can use <code>code</code> to open it from the terminal:</p>
<pre><code class="lang-shell">code sensor
</code></pre>
<p>Before we do anything we'll need to set up the symbolic link to <code>pyrinas-os</code>. Make sure you're in the <code>sensor</code> directory and then run <code>ln -s ../pyrinas-os/</code> using the terminal.</p>
<pre><code class="lang-shell">cd sensor
ln -s ../pyrinas-os/ .
</code></pre>
<p>This allows your project to use all the code, SDK and toolchains within the <code>pyrinas-os</code> repository! As an added bonus you can do this as many times as you want. Have multiple Pyrinas projects? No problem.</p>
<p>Alright! Now, let's check out the Makefile. You'll want to customize some of the definitions within the file:</p>
<pre><code class="lang-makefile"><span class="hljs-comment"># Start: Your configuration!</span>

<span class="hljs-comment"># Set this to the directory of pyrinas-os</span>
<span class="hljs-comment"># If you used a symbolic link this points to</span>
<span class="hljs-comment"># the `pyrinas-os` folder in this repository</span>
OS_DIR := pyrinas-os

<span class="hljs-comment"># This should be the serial number of your Jlink programmer.</span>
<span class="hljs-comment"># To find simply run `jlinkexe`</span>
PROG_SERIAL=1234678

<span class="hljs-comment"># This is your debugger port for Jlink's RTT. If you</span>
<span class="hljs-comment"># have mulitple, you will have to change this on each app</span>
<span class="hljs-comment"># your're using</span>
PROG_PORT=19021

<span class="hljs-comment"># This is where you set your board type. Here are the supported boards:</span>
<span class="hljs-comment"># xenon - Particle Xenon</span>
BOARD=xenon

<span class="hljs-comment"># This is where you can name your app something. Make it specific</span>
APP_FILENAME=pyrinas-template

<span class="hljs-comment"># This determines whether or not you're using debug mode</span>
<span class="hljs-comment"># Comment this out or change to false</span>
DEBUG=true

<span class="hljs-comment"># End: Your Configuration</span>
</code></pre>
<p>For example, you may want to setup your programmer serial. This allows you to use multiple programmers at the same time. (Very helpful in debugging both devices at the same time) To get your programmer's serial plug in your development board and run <code>jlinkexe</code>.</p>
<p>    jlinkexe
    SEGGER J-Link Commander V6.62a (Compiled Jan 31 2020 12:59:22)
    DLL version V6.62a, compiled Jan 31 2020 12:59:05</p>
<p>    Connecting to J-Link via USB...O.K.
    Firmware: J-Link OB-SAM3U128-V2-NordicSemi compiled Jan 21 2020 17:30:48
    Hardware version: V1.00
    S/N: 581758669
    License(s): RDI, FlashBP, FlashDL, JFlash, GDB
    VTref=3.300V</p>
<p>    Type "connect" to establish a target connection, '?' for help
    J-Link&gt;</p>
<p>Find the <strong>S/N</strong> area. This is your serial number!</p>
<p>Alternatively you can look at the sticker on your development kit. It will contain the serial number for your device.</p>
<p>For the <code>PROG_PORT</code> you want to use different ports for each device you're simultaneously debugging. I've found <strong>19021</strong> and <strong>19020</strong> are perfectly good options for most two-device debugging sessions.</p>
<p>The Makefile also includes the ability to choose a board. In our case there's only one option: <code>xenon</code>. Future revisions of Pyrinas will have multiple options.</p>
<p><code>APP_FILENAME</code> is the name of your app. We'll rename ours to <code>pyrinas-sensor</code></p>
<p>Finally, <code>DEBUG</code> is used to halt execution either on an error or restart. For production this should be commented out or set to <code>false</code>. We can leave it as is for now.</p>
<p>The Makefile is also the source for some of the most important commands you'll need during development:</p>
<ul>
<li><code>make build</code> - builds your app.</li>
<li><code>make clean</code> - cleans all remnants of your app.</li>
<li><code>make debug</code> - opens up the <code>jlinkexe</code> debugger console.</li>
<li><code>make erase</code> - erases the chip attached to your programmer.</li>
<li><code>make flash</code> - flashes the current app to your connected device.</li>
<li><code>make flash_softdevice</code> - flashes the soft_device</li>
<li><code>make rtt</code> - opens up the debug console.</li>
<li><code>make ota</code> - generates a zip file used for BLE DFU</li>
</ul>
<h3 id="heading-basic-sensor-node-code">Basic sensor node code</h3>
<p><img src="https://www.jaredwolff.com/meet-pyrinas-a-new-way-to-use-your-xenon/images//Copy_of_Particle_Mesh_App_Updates-3.jpg" alt="https://www.jaredwolff.com/meet-pyrinas-a-new-way-to-use-your-xenon/images//Copy_of_Particle_Mesh_App_Updates-3.jpg" width="730" height="486" loading="lazy"></p>
<p>Now that we have some of the basics out of the way, let's create a very simple application that publishes on a set interval. If you look at <code>app.c</code>, you'll see some code in the <code>setup()</code> function. Let's delete all the commented out code. (We'll use it later for the hub though)</p>
<p>Your code should now look something like:</p>
<pre><code class="lang-c"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"app.h"</span></span>

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setup</span><span class="hljs-params">()</span>
</span>{
  BLE_STACK_PERIPH_DEF(init);

  <span class="hljs-comment">// Configuration for ble stack</span>
  ble_stack_init(&amp;init);

  <span class="hljs-comment">// Start advertising</span>
  advertising_start();
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">loop</span><span class="hljs-params">()</span>
</span>{
}
</code></pre>
<p>Now let's create a timer that we'll use to publish on a set interval. Under <code>#include "app.h"</code> create a new timer:</p>
<pre><code class="lang-c"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"app.h"</span></span>

timer_define(m_sensor_timer);
</code></pre>
<p>We also need to set it up in the <code>setup()</code> function:</p>
<pre><code class="lang-c"><span class="hljs-comment">// Sensor timer</span>
timer_create(&amp;m_sensor_timer, TIMER_REPEATED, sensor_timer_evt);
</code></pre>
<p>You'll notice that <code>timer_create</code> is referring to a event callback called <code>sensor_timer_evt</code>. We'll need to create that guy as well:</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">sensor_timer_evt</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// We'll come back in a sec</span>
}
</code></pre>
<p>The last thing is to start the timer. Let's do that underneath <code>timer_create</code>:</p>
<pre><code class="lang-c"><span class="hljs-comment">// Start</span>
timer_start(&amp;m_sensor_timer, <span class="hljs-number">1000</span>);
</code></pre>
<p>This will start our repeating timer on a 1 second interval.  (<code>timer_start</code> is configured using milliseconds)</p>
<p>Now, inside <code>sensor_timer_evt</code> we'll publish some data. First though we need to make sure that Bluetooth is connected using <code>ble_is_connected</code>.</p>
<pre><code class="lang-c"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> sensor_timer_evt
{
  <span class="hljs-comment">// Check if we're connected</span>
  <span class="hljs-keyword">if</span> (ble_is_connected())
  {
    <span class="hljs-comment">// Sends "ping" with the event name of "data"</span>
    ble_publish(<span class="hljs-string">"data"</span>, <span class="hljs-string">"ping"</span>);
  }
}
</code></pre>
<p>Inside the if statement, we'll use <code>ble_publish</code>. The first argument is the name of the event and the second is the value.</p>
<p>Next, in order to receive messages from the other end we'll need to setup a callback:</p>
<pre><code class="lang-c"><span class="hljs-comment">// Configuration for ble stack</span>
ble_stack_init(&amp;init);

<span class="hljs-comment">// Setup BLE callback</span>
ble_subscribe(<span class="hljs-string">"data"</span>, ble_evt);
</code></pre>
<p>We'll define <code>ble_evt</code> at the top of the file:</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ble_evt</span><span class="hljs-params">(<span class="hljs-keyword">char</span> *name, <span class="hljs-keyword">char</span> *data)</span>
</span>{
  NRF_LOG_INFO(<span class="hljs-string">"%s: %s"</span>, name, data);
}
</code></pre>
<p>In this case we'll use <code>NRF_LOG_INFO</code> to print out the message from the hub.</p>
<p>Finally, in order to get the MAC address easily, we'll have to add a call to print it out in <code>setup()</code>.</p>
<pre><code class="lang-c"><span class="hljs-comment">// Print the address</span>
util_print_device_address();
</code></pre>
<p>In the end your file should look something like this:</p>
<pre><code class="lang-c"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"app.h"</span></span>

timer_define(m_sensor_timer);

<span class="hljs-comment">// Catch events sent over Bluetooth</span>
<span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ble_evt</span><span class="hljs-params">(<span class="hljs-keyword">char</span> *name, <span class="hljs-keyword">char</span> *data)</span>
</span>{
  NRF_LOG_INFO(<span class="hljs-string">"%s: %s"</span>, name, data);
}

<span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">sensor_timer_evt</span><span class="hljs-params">()</span>
</span>{
  <span class="hljs-comment">// Check if we're connected</span>
  <span class="hljs-keyword">if</span> (ble_is_connected())
  {
    <span class="hljs-comment">// Sends "ping" with the event name of "data"</span>
    ble_publish(<span class="hljs-string">"data"</span>, <span class="hljs-string">"ping"</span>);
  }
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setup</span><span class="hljs-params">()</span>
</span>{
  BLE_STACK_PERIPH_DEF(init);

  <span class="hljs-comment">// Configuration for ble stack</span>
  ble_stack_init(&amp;init);

  <span class="hljs-comment">// Setup BLE callback</span>
  ble_subscribe(<span class="hljs-string">"data"</span>, ble_evt);

  <span class="hljs-comment">// Start advertising</span>
  advertising_start();

  <span class="hljs-comment">// Sensor sensor timer.</span>
  timer_create(&amp;m_sensor_timer, TIMER_REPEATED, sensor_timer_evt);

  <span class="hljs-comment">// Start</span>
  timer_start(&amp;m_sensor_timer, <span class="hljs-number">1000</span>);

  <span class="hljs-comment">// Print the address</span>
  util_print_device_address();
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">loop</span><span class="hljs-params">()</span>
</span>{
}
</code></pre>
<p>Next, we'll program it to some hardware!</p>
<h3 id="heading-flashing-the-basic-sensor-code">Flashing the basic sensor code:</h3>
<p>For this step you'll need to have a Xenon handy. You'll also need a programmer, programming cable and two Micro-B USB cables. Here's a picture of everything connected:</p>
<p><img src="https://www.jaredwolff.com/meet-pyrinas-a-new-way-to-use-your-xenon/images//IMG_4586.jpg" alt="https://www.jaredwolff.com/meet-pyrinas-a-new-way-to-use-your-xenon/images//IMG_4586.jpg" width="730" height="547" loading="lazy"></p>
<p>Once connected and powered run these commands:</p>
<pre><code class="lang-shell">make erase
make flash_softdevice
make flash
make debug
</code></pre>
<p>Then in a separate terminal window run</p>
<pre><code class="lang-shell">make rtt
</code></pre>
<p><code>make debug</code> and <code>make rtt</code> will create a debugging session. You can issue commands in the <code>make debug</code> terminal to control the device as well. For instance, <code>r</code> followed by <code>Enter</code> will restart the device. (By far my most common use case).</p>
<p>If you've flashed everything successfully, your device should start blinking green. That's a good sign!</p>
<p>Additionally, if you take a look at the <code>make rtt</code> side your output should be similar to this:</p>
<p>    ###RTT Client: Connecting to J-Link RTT Server via localhost:19021 ...</p>
<p>    ###RTT Client: Connected.</p>
<p>    SEGGER J-Link V6.62a - Real time terminal output
    J-Link OB-SAM3U128-V2-NordicSemi compiled Jan 21 2020 17:30:48 V1.0, SN=581758669
    Process: JLinkExe
     app_timer: RTC: initialized.
     app: Boot count: 4
     app: Pyrinas started.
     app: Address: 11:22:33:44:55:66</p>
<p>Take note of the address displayed above. We'll need that for the hub code!</p>
<h3 id="heading-setting-up-the-hub-repository">Setting up the hub repository</h3>
<p><img src="https://www.jaredwolff.com/meet-pyrinas-a-new-way-to-use-your-xenon/images//Copy_of_Particle_Mesh_App_Updates-2.jpg" alt="https://www.jaredwolff.com/meet-pyrinas-a-new-way-to-use-your-xenon/images//Copy_of_Particle_Mesh_App_Updates-2.jpg" width="730" height="486" loading="lazy"></p>
<p>If you haven't already, clone your hub repository locally. We'll want to do some of the same steps as we did with the sensor repo like:</p>
<ul>
<li>Setting up the symbolic link</li>
<li>Updating the Makefile<ul>
<li>Setting your <code>PROG_SERIAL</code></li>
<li>Setting <code>PROG_PORT</code> to a port not used by the sensor setup. <code>19020</code> in this case is fine.</li>
<li>Setting <code>APP_FILENAME</code> to <code>pyrinas-hub</code></li>
</ul>
</li>
</ul>
<p>If you need a reminder how any of these steps work, go back and review the earlier section.</p>
<p>Next, we'll want to open <code>app.c</code> and uncomment the central/hub based code. Plus you'll want to remove the default un-commented code. Your <code>setup()</code> should look something like this:</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setup</span><span class="hljs-params">()</span>
</span>{
  <span class="hljs-comment">// Default config for central mode</span>
  BLE_STACK_CENTRAL_DEF(init);

  <span class="hljs-comment">// Add an addresses to scan for</span>
  <span class="hljs-keyword">ble_gap_addr_t</span> first = {
      .addr_type = BLE_GAP_ADDR_TYPE_RANDOM_STATIC,
      .addr = {<span class="hljs-number">0x81</span>, <span class="hljs-number">0x64</span>, <span class="hljs-number">0x4C</span>, <span class="hljs-number">0xAD</span>, <span class="hljs-number">0x7D</span>, <span class="hljs-number">0xC0</span>}};
  init.config.devices[<span class="hljs-number">0</span>] = first;

  <span class="hljs-keyword">ble_gap_addr_t</span> second = {
      .addr_type = BLE_GAP_ADDR_TYPE_RANDOM_STATIC,
      .addr = {<span class="hljs-number">0x7c</span>, <span class="hljs-number">0x84</span>, <span class="hljs-number">0x9d</span>, <span class="hljs-number">0x32</span>, <span class="hljs-number">0x8d</span>, <span class="hljs-number">0xe4</span>}};
  init.config.devices[<span class="hljs-number">1</span>] = second;

  <span class="hljs-comment">// Increment the device_count</span>
  init.config.device_count = <span class="hljs-number">2</span>;

  <span class="hljs-comment">// Configuration for ble stack</span>
  ble_stack_init(&amp;init);

  <span class="hljs-comment">// Start scanning.</span>
  scan_start();
}
</code></pre>
<p>You'll notice immediately that there are two clients/devices defined here. Let's remove the second one. Should you, in the future, want to connect more devices this is an example of how to do it.</p>
<p><strong>Reminder:</strong> also make sure that you change the <code>init.config.device_count</code> to <code>1</code>.</p>
<p>Then, you'll want to update the <code>.addr</code> field in <code>ble_gap_addr_t first</code> to match the address we got earlier from <code>make rtt</code>:</p>
<pre><code class="lang-c"><span class="hljs-comment">// Add an addresses to scan for</span>
<span class="hljs-keyword">ble_gap_addr_t</span> first = {
    .addr_type = BLE_GAP_ADDR_TYPE_RANDOM_STATIC,
    .addr = {<span class="hljs-number">0x11</span>,<span class="hljs-number">0x22</span>,<span class="hljs-number">0x33</span>,<span class="hljs-number">0x44</span>,<span class="hljs-number">0x55</span>,<span class="hljs-number">0x66</span>}};
init.config.devices[<span class="hljs-number">0</span>] = first;
</code></pre>
<p>The address field uses raw bytes, so it has to be represented that way. Remove the <code>:</code> and place <code>0x</code> in front of each byte. We end up going from <code>11:22:33:44:55:66</code> to <code>{0x11,0x22,0x33,0x44,0x55,0x66}</code></p>
<p>Now before we flash anything, let's also set up the Bluetooth event handler. As with earlier we'll use <code>ble_subscribe</code> to attach an event handler:</p>
<pre><code class="lang-c"><span class="hljs-comment">// Setup BLE callback</span>
ble_subscribe(<span class="hljs-string">"data"</span>, ble_evt);

Then place the function at the top of the file:

<span class="hljs-comment">// Catch events sent over Bluetooth</span>
<span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ble_evt</span><span class="hljs-params">(<span class="hljs-keyword">char</span> *name, <span class="hljs-keyword">char</span> *data)</span>
</span>{
  NRF_LOG_INFO(<span class="hljs-string">"%s: %s"</span>, name, data);

  ble_publish(<span class="hljs-string">"data"</span>, <span class="hljs-string">"pong"</span>);
}
</code></pre>
<p>You'll notice we're printing out the message using <code>NRF_LOG_INFO</code>. We're also sending a message <em>back</em> to the sensor in the form of <code>ble_publish("data","pong");</code> In other-words we're playing a game of ping-pong between the two devices!</p>
<p>In the end your code should look something like this:</p>
<pre><code class="lang-c"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"app.h"</span></span>

<span class="hljs-comment">// Catch events sent over Bluetooth</span>
<span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ble_evt</span><span class="hljs-params">(<span class="hljs-keyword">char</span> *name, <span class="hljs-keyword">char</span> *data)</span>
</span>{
  NRF_LOG_INFO(<span class="hljs-string">"%s: %s"</span>, name, data);

  ble_publish(<span class="hljs-string">"data"</span>, <span class="hljs-string">"pong"</span>);
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setup</span><span class="hljs-params">()</span>
</span>{
  <span class="hljs-comment">// Default config for central mode</span>
  BLE_STACK_CENTRAL_DEF(init);

  <span class="hljs-comment">// Add an addresses to scan for</span>
  <span class="hljs-keyword">ble_gap_addr_t</span> first = {
      .addr_type = BLE_GAP_ADDR_TYPE_RANDOM_STATIC,
      .addr = {<span class="hljs-number">0x11</span>, <span class="hljs-number">0x22</span>, <span class="hljs-number">0x33</span>, <span class="hljs-number">0x44</span>, <span class="hljs-number">0x55</span>, <span class="hljs-number">0x66</span>}};
  init.config.devices[<span class="hljs-number">0</span>] = first;

  <span class="hljs-comment">// Increment the device_count</span>
  init.config.device_count = <span class="hljs-number">1</span>;

  <span class="hljs-comment">// Configuration for ble stack</span>
  ble_stack_init(&amp;init);

  <span class="hljs-comment">// Setup BLE callback</span>
  ble_subscribe(<span class="hljs-string">"data"</span>, ble_evt);

  <span class="hljs-comment">// Start scanning.</span>
  scan_start();
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">loop</span><span class="hljs-params">()</span>
</span>{
}
</code></pre>
<p><strong>Reminder:</strong> make sure you set <code>ble_gap_addr_t first</code> or the two devices will not connect!</p>
<p>To program, connect the Xenon to program as you did before. We'll flash it using the same methods as before:</p>
<pre><code class="lang-shell">make erase
make flash_softdevice
make flash
make debug
</code></pre>
<p>Then in a separate terminal window run</p>
<pre><code class="lang-shell">make rtt
</code></pre>
<p>Then take a look at each of the <code>make rtt</code> screens. There should be some output! For the hub it should look something like this:</p>
<p>    Process: JLinkExe
     app: Boot count: 4
     app: Pyrinas started.
     ble_m_central: Connected to handle 0x0
     ble_m_central: Protobuf Service discovered
     app: data: ping
     app: data: ping</p>
<p>And the sensor side like this:</p>
<p>    Process: JLinkExe
     app_timer: RTC: initialized.
     app: Boot count: 4
     app: Pyrinas started.
     app: Address: 11:22:33:44:55:66
     ble_m_periph: Notifications enabled!
     app: data: pong
     app: data: pong</p>
<p>The ping and pong messages should continue until you stop them. Awesome! If you get any warnings like this one:</p>
<p>    Unable to write. Notifications not enabled!</p>
<p>Use the reset button on either of the devices. This should fix the problem.</p>
<p><strong>Side note:</strong> the pairing process for Bluetooth is inherently <strong><em>insecure</em></strong>. Once the pairing process is complete though, the devices are secure. (With the caveat that no one was sniffing the pairing process!) There may be improvements on security in the future.</p>
<p><strong>Congrats! ?</strong>If you've made it this far, you've deployed your first Pyrinas hub and sensor client! </p>
<p>For more information about what Pyrinas can do you should check out the header files under <code>pyrinas-os/include/</code>. Also, Pyrinas can do anything that you'd normally be able to do with Nordic's SDK. <a target="_blank" href="https://infocenter.nordicsemi.com/topic/struct_sdk/struct/sdk_nrf5_latest.html?cp=7_1">Nordic's Infocenter</a> is a great resource for learning more about what the SDK has to offer.</p>
<h2 id="heading-what-does-the-future-hold-for-pyrinas">What does the future hold for Pyrinas?</h2>
<p><img src="https://www.jaredwolff.com/meet-pyrinas-a-new-way-to-use-your-xenon/images//Copy_of_Particle_Mesh_App_Updates-5.jpg" alt="https://www.jaredwolff.com/meet-pyrinas-a-new-way-to-use-your-xenon/images//Copy_of_Particle_Mesh_App_Updates-5.jpg" width="730" height="486" loading="lazy"></p>
<p>All future tasks for Pyrinas are shared openly on the <a target="_blank" href="https://github.com/pyrinas-iot/pyrinas-os/projects">Github Repo.</a> Here are some of the high level improvements on the roadmap:</p>
<ul>
<li>Particle Boron + LTE support - That's right! Cellular will be coming to Pyrinas. As of this writing, the first board to support Pyrinas LTE will be Particle's Boron.</li>
<li>MQTT (over TLS) and HTTPS support - Once we have cellular, we need something to communicate with. That's where MQTT and HTTPS come in. They're some of the most popular protocols for IoT today.</li>
<li>Built in remote OTA support - As it stands today, devices programmed with Pyrinas uses Nordic's Secure Bootloader. That means they can be updated over the air by a computer or cellphone nearby. This isn't sustainable for long term deployments though!
Instead, you will be able to push updates to Pyrinas devices over the Cloud. Yup. No reason to get off your couch, you can deploy your updates from anywhere.</li>
<li>Dynamic configuration and management - adding and removing devices from a Pyrinas system currently takes some effort. In future revisions, it will be easier to add and remove devices on the fly. This allows for remote device management with zero headaches.</li>
<li>Support for pre-certified modules and other development boards based on Nordic's nRF52840. Currently the Xenon is the only supported board. Development boards aren't great for full production though. Stay tuned for support for pre-certified modules from vendors like <a target="_blank" href="https://www.fanstel.com/bluenor-summaries">Fanstel</a> and more..</li>
<li>Support for more development environments. Currently Pyrinas supports Mac <em>only</em>.</li>
</ul>
<h3 id="heading-star-and-watch">Star and Watch!</h3>
<p>This is only the tip of the iceberg! Stay tuned for more updates and make you you star and watch <a target="_blank" href="https://github.com/pyrinas-iot/pyrinas-os">the repository</a>.</p>
<p>Or, better yet, looking to help out? Contributions are welcome!</p>
<p><strong>You can read other articles on my blog, <a target="_blank" href="https://www.jaredwolff.com/meet-pyrinas-a-new-way-to-use-your-xenon/">jaredwolff.com</a></strong></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Particle Argon For Location Tracking ]]>
                </title>
                <description>
                    <![CDATA[ By Jared Wolff Ever want to add presence or location tracking to a project? Frustrated by the solutions (or lack thereof)? Do not worry, you're not the only one! In this post you'll learn how to implement a very basic tracking and notification applic... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-particle-argon-for-location-tracking/</link>
                <guid isPermaLink="false">66d850584540581f6454411c</guid>
                
                    <category>
                        <![CDATA[ Bluetooth Low Energy ]]>
                    </category>
                
                    <category>
                        <![CDATA[ hardware ]]>
                    </category>
                
                    <category>
                        <![CDATA[ particle ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 09 Sep 2019 13:20:06 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/09/Copy-of-Copy-of-Flow.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Jared Wolff</p>
<p>Ever want to add presence or location tracking to a project? Frustrated by the solutions (or lack thereof)?</p>
<p>Do not worry, you're not the only one!</p>
<p>In this post you'll learn how to implement a very basic tracking and notification application. We'll be using a Particle Argon and a Tile Mate.</p>
<p>By the end you'll be able to tell when the Tile is present or not. Plus we'll use Pushover to send push notifications to the devices of your choosing.</p>
<p>Let's get going!</p>
<p><strong>Note</strong> before we get started, this post is <strong>lengthy</strong>. <a target="_blank" href="https://www.jaredwolff.com/files/how-to-location-tracking-using-particle-mesh-pdf/">You can download the PDF version so you can save and view it later.</a></p>
<h2 id="heading-initial-investigation">Initial investigation</h2>
<p>The idea of using a Tile wasn't obvious at first glance. Ideally, using a phone seemed to make more sense. Unfortunately, this wasn't as a viable option. It would require some more research and the creation of a Bluetooth iOS app.</p>
<p>So, the idea of using a phone was out.</p>
<p>Then I thought, "What devices <em>do</em> advertise all the time?"</p>
<p>That is what led me down the path of a tracker like Tile.</p>
<p>After it arrived there was some customary testing. First stop, the Tile application.</p>
<p><img src="https://www.jaredwolff.com/how-to-location-tracking-using-particle-mesh/images/iphone7gold_portrait-2-2b6bcb7b-0f91-4578-a2db-034853565b09.png" alt="Screenshot of Tile App" width="600" height="400" loading="lazy"></p>
<p>I was able to connect and use the device. I even made it play a catchy tune. ?</p>
<p>Then, I moved on to using one of the Bluetooth scanner apps. I scrolled through all the results and Bingo. There was the Tile!</p>
<p><img src="https://www.jaredwolff.com/how-to-location-tracking-using-particle-mesh/images/iphone7gold_portrait-466803ef-57d9-498c-8656-2c274bf7fe8d.png" alt="NRFConnect Scan Results" width="600" height="400" loading="lazy"></p>
<p>I even waited a few hours and checked it again. I wanted to make sure it didn't go to sleep after a while. Turns out, it's always advertising. As far as I can tell, about every 8 seconds.</p>
<p>All of this testing lead to one conclusion: it could be easily used for presence detection.</p>
<p>The next step in the process was trying to figure out how to get it working with an Argon.</p>
<h2 id="heading-advertising">Advertising</h2>
<p>As we had gathered in the previous step, we know that the Tile is advertising about every 8 seconds. That means it should be easily scanned for using any device including an Argon, Zenon or Boron.</p>
<p>For this example I suggest you use an Argon. This is because Bluetooth and Mesh share the same radio. When scanning for the Tile, the Xenon connected to Mesh would often miss the advertising packets. This would lead to false negatives (and frustration!).</p>
<p>Along the same lines, you'll want to make <strong>sure your Argon is connected to no mesh network.</strong> You can remove it using the CLI. Connect your device to your computer and run the following command:</p>
<pre><code class="lang-bash">particle mesh remove &lt;device name/ID&gt;
</code></pre>
<p>Make sure that you replace <strong></strong> with your device's name or ID.</p>
<p>Alright, back to the good stuff.</p>
<p>Advertising can have a few different purposes in Bluetooth. Typically though, it marks the beginning of the pairing phase. That way other devices know that the advertising device is available.</p>
<p>Additionally, the advertising device will indicate what services it has. We can use this knowledge to  filter out devices that don't match.</p>
<p>For example, here's a screenshot of the services available on the Tile device:</p>
<p><img src="https://www.jaredwolff.com/how-to-location-tracking-using-particle-mesh/images/Apple_iPhone_6s_Gold-c1f1d0da-7aba-410a-97b4-3cbebc933208.png" alt="Service information using Light Blue" width="600" height="400" loading="lazy"></p>
<p>When scanning we'll double check that the device we're connecting to has the service UUID of <code>0xfeed</code>.</p>
<p>Before we get deep into Bluetooth land though, let's set up our app for debugging using the Logger.</p>
<h2 id="heading-logging">Logging</h2>
<p>In this tutorial we'll be using the Logger. It allows you to display log messages from your app using <code>particle serial monitor</code>.</p>
<p>One of the cooler features about the logger is the idea of message hierarchy. This allows you, the designer, to selectively mute messages that may not be necessary.</p>
<p>For example, if you have messages used for debugging. You could remove them or comment them out. Or, you could increase the <code>LOG_LEVEL</code> so they're effectively ignored.</p>
<p>Here are the logging levels which are available in <code>logging.h</code> in Particle's device-os repository:</p>
<pre><code class="lang-c++"><span class="hljs-comment">// Log level. Ensure log_level_name() is updated for newly added levels</span>
<span class="hljs-keyword">typedef</span> <span class="hljs-keyword">enum</span> LogLevel {
    LOG_LEVEL_ALL = <span class="hljs-number">1</span>, <span class="hljs-comment">// Log all messages</span>
    LOG_LEVEL_TRACE = <span class="hljs-number">1</span>,
    LOG_LEVEL_INFO = <span class="hljs-number">30</span>,
    LOG_LEVEL_WARN = <span class="hljs-number">40</span>,
    LOG_LEVEL_ERROR = <span class="hljs-number">50</span>,
    LOG_LEVEL_PANIC = <span class="hljs-number">60</span>,
    LOG_LEVEL_NONE = <span class="hljs-number">70</span>, <span class="hljs-comment">// Do not log any messages</span>
    <span class="hljs-comment">// Compatibility levels</span>
    DEFAULT_LEVEL = <span class="hljs-number">0</span>,
    ALL_LEVEL = LOG_LEVEL_ALL,
    TRACE_LEVEL = LOG_LEVEL_TRACE,
    LOG_LEVEL = LOG_LEVEL_TRACE, <span class="hljs-comment">// Deprecated</span>
    DEBUG_LEVEL = LOG_LEVEL_TRACE, <span class="hljs-comment">// Deprecated</span>
    INFO_LEVEL = LOG_LEVEL_INFO,
    WARN_LEVEL = LOG_LEVEL_WARN,
    ERROR_LEVEL = LOG_LEVEL_ERROR,
    PANIC_LEVEL = LOG_LEVEL_PANIC,
    NO_LOG_LEVEL = LOG_LEVEL_NONE
} LogLevel;
</code></pre>
<p>Cool, log levels. But how do we use them?</p>
<p>We can use them by invoking one of these functions:</p>
<p><code>Log.trace</code>, <code>Log.info</code>, <code>Log.warn</code>, <code>Log.error</code>.</p>
<p>For example:</p>
<pre><code class="lang-c++">Log.trace(<span class="hljs-string">"This is a TRACE message."</span>);
</code></pre>
<p>If we set the log level to <code>LOG_LEVEL_INFO</code> we'll only see messages from <code>Log.info</code>, <code>Log.warn</code>, and <code>Log.error</code>. <code>LOG_LEVEL_WARN</code>? Only <code>Log.warn</code> and <code>Log.error</code> will show up. (Hopefully you get the idea.)</p>
<p>To set it up, we'll set the default level to <code>LOG_LEVEL_ERROR</code>. We'll also set the app specific <code>LOG_LEVEL</code> to <code>LOG_LEVEL_TRACE</code>. The end result should look something like this</p>
<pre><code class="lang-c++"><span class="hljs-comment">// For logging</span>
<span class="hljs-function">SerialLogHandler <span class="hljs-title">logHandler</span><span class="hljs-params">(<span class="hljs-number">115200</span>, LOG_LEVEL_ERROR, {
    { <span class="hljs-string">"app"</span>, LOG_LEVEL_TRACE }, <span class="hljs-comment">// enable all app messages</span>
})</span></span>;
</code></pre>
<p>This way we don't get spammed with DeviceOS log messages. Plus, we get all the applicable messages from the app itself.</p>
<p>By the way, if you want to set your device to a single <code>LOG_LEVEL</code> you can set it up like this:</p>
<pre><code class="lang-c++"><span class="hljs-function">SerialLogHandler <span class="hljs-title">logHandler</span><span class="hljs-params">(LOG_LEVEL_INFO)</span></span>;
</code></pre>
<p>As you continue your journey using Particle's DeviceOS you'll soon realize how handy it can be. Now, let's move on to the good stuff!</p>
<h2 id="heading-setting-it-up">Setting it up</h2>
<p><img src="https://www.jaredwolff.com/how-to-location-tracking-using-particle-mesh/images/Screen_Shot_2019-09-06_at_9-a04e6802-ec22-4036-8ace-6150a532979b.50.41_PM.png" alt="Device-os Release Page" width="600" height="400" loading="lazy"></p>
<p>First, we'll want to make sure we're using the correct version of DeviceOS. Any version after 1.3 will have Bluetooth. You can get the <a target="_blank" href="https://www.jaredwolff.com/how-to-upgrade-particle-mesh-device-os/">instructions here</a>.</p>
<p>Next we'll want to start scanning for the Tile. We'll want do do this in the <code>loop()</code> function at a specified interval. We'll use a <code>millis()</code> timer in this case:</p>
<pre><code class="lang-c++"><span class="hljs-comment">// Scan for devices</span>
<span class="hljs-keyword">if</span>( (millis() &gt; lastSeen + TILE_RE_CHECK_MS) ){
    BLE.scan(scanResultCallback, <span class="hljs-literal">NULL</span>);
}
</code></pre>
<p>Make sure you define <code>lastSeen</code> at the top of the file like so:</p>
<pre><code class="lang-c++"><span class="hljs-keyword">system_tick_t</span> lastSeen = <span class="hljs-number">0</span>;
</code></pre>
<p>We'll use it to track the last time the Tile has been "seen". i.e. when the last time the Argon saw an advertising packet from the Tile.</p>
<p><code>TILE_RE_CHECK_MS</code> can be defined as</p>
<pre><code class="lang-c++"><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> TILE_RE_CHECK_MS 7500</span>
</code></pre>
<p>This way we're checking, at the very minimum, every 7.5 seconds for advertising packets.</p>
<p>In order to find the Tile device we'll use <code>BLE.scan</code>. When we call it, It will start the scanning process.  As devices are found <code>scanResultCallback</code> will fire.</p>
<p>For now, we can define <code>scanResultCallback</code> at the top of the file:</p>
<pre><code class="lang-c++"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">scanResultCallback</span><span class="hljs-params">(<span class="hljs-keyword">const</span> BleScanResult *scanResult, <span class="hljs-keyword">void</span> *context)</span> </span>{
}
</code></pre>
<p>You notice that it includes a <code>BleScanResult</code>. This will contain the address, RSSI and device name (if available) and available service information. This will come in handy later when we're looking for our Tile device!</p>
<p>Remember, that <code>BLE.scan</code> does not return until scanning has been completed. The default timeout for scanning is 5 seconds. You can change that value using <code>BLE.setScanTimeout()</code>. <code>setScanTimeout</code> takes units in 10ms increments. So, for a 500ms timeout would require a value of 50.</p>
<p>For the case of this app, I'd recommend using a value of 8s (8000ms). You can set it like this:</p>
<pre><code class="lang-c++">BLE.setScanTimeout(<span class="hljs-number">800</span>);
</code></pre>
<p>In this case, the device will scan for as long as it takes the Tile to advertise. That way it's less likely to miss an advertising packet.</p>
<h2 id="heading-handling-scan-results">Handling Scan Results</h2>
<p><img src="https://www.jaredwolff.com/how-to-location-tracking-using-particle-mesh/images/Screen_Shot_2019-09-06_at_11-fa1f684f-246c-474a-8397-e7387692e39b.05.19_PM.png" alt="All const definitions" width="600" height="400" loading="lazy"></p>
<p>Now that we have <code>scanResultCallback</code> lets define what's going on inside.</p>
<p>We first want to get the service information inside the advertising data. The best way is to use <code>scanResult-&gt;advertisingData.serviceUUID</code>. We'll pass in an array of UUIDs what will be copied for our use.</p>
<pre><code class="lang-c++">BleUuid uuids[<span class="hljs-number">4</span>];
<span class="hljs-keyword">int</span> uuidsAvail = scanResult-&gt;advertisingData.serviceUUID(uuids,<span class="hljs-keyword">sizeof</span>(uuids)/<span class="hljs-keyword">sizeof</span>(BleUuid));
</code></pre>
<p>This will populate <code>uuids</code> that way you can iterate over them. <code>uuidsAvail</code> will equal the amount of available UUIDs.</p>
<p>On our case we're looking for a particular UUID. We'll define it a the top of the file:</p>
<pre><code class="lang-c++"><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> TILE_UUID 0xfeed</span>
</code></pre>
<p>Normally UUIDs are <strong>much</strong> longer. A short UUID like this means it has been reserved or is part of the Bluetooth specification. In either case we'll be checking for it in the same way we would check a 32bit or 128bit version.</p>
<p>For diagnostic reasons we can also print out the device information. In this case the RSSI and the device MAC address is handy:</p>
<pre><code class="lang-c++"><span class="hljs-comment">// Print out mac info</span>
BleAddress addr = scanResult-&gt;address;
Log.trace(<span class="hljs-string">"MAC: %02X:%02X:%02X:%02X:%02X:%02X"</span>, addr[<span class="hljs-number">0</span>], addr[<span class="hljs-number">1</span>], addr[<span class="hljs-number">2</span>], addr[<span class="hljs-number">3</span>], addr[<span class="hljs-number">4</span>], addr[<span class="hljs-number">5</span>]);
Log.trace(<span class="hljs-string">"RSSI: %dBm"</span>, scanResult-&gt;rssi);
</code></pre>
<p>Finally let's set up a loop to see if the device found has the UUID:</p>
<pre><code class="lang-c++"><span class="hljs-comment">// Loop over all available UUIDs</span>
<span class="hljs-comment">// For tile devices there should only be one</span>
<span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; uuidsAvail; i++){

    <span class="hljs-comment">// Print out the UUID we're looking for</span>
    <span class="hljs-keyword">if</span>( uuids[i].shorted() == TILE_UUID ) {
        Log.trace(<span class="hljs-string">"UUID: %x"</span>, uuids[i].shorted());

        <span class="hljs-comment">// Stop scanning</span>
        BLE.stopScanning();

        <span class="hljs-keyword">return</span>;
    }
}
</code></pre>
<p>We can easily compare the "shorted" version of the UUID with <code>TILE_UUID</code>. It's a simple integer so no complicated memory compare operations are necessary. So, using <code>if( uuids[i].shorted() == TILE_UUID )</code> works just fine.</p>
<p>You can also use <code>Log.trace</code> to print out diagnostic information. In this case we're using it to print out the <code>shorted()</code> version of the UUID.</p>
<h3 id="heading-test-it">Test It!</h3>
<p>Let's test what we have so far!</p>
<p>Program the app to your Argon. Open the terminal and run <code>particle serial monitor</code> to view the debug messages. Heres an example of what you may see:</p>
<pre><code><span class="hljs-number">0000005825</span> [app] TRACE: MAC: <span class="hljs-number">65</span>:C7:B3:AF:<span class="hljs-number">73</span>:<span class="hljs-number">5</span>C
<span class="hljs-number">0000005827</span> [app] TRACE: RSSI: <span class="hljs-number">-37</span>Bm
<span class="hljs-number">0000005954</span> [app] TRACE: MAC: B3:D9:F1:F0:<span class="hljs-number">5</span>D:<span class="hljs-number">7</span>E
<span class="hljs-number">0000005955</span> [app] TRACE: RSSI: <span class="hljs-number">-62</span>Bm
<span class="hljs-number">0000006069</span> [app] TRACE: MAC: C5:F0:<span class="hljs-number">74</span>:<span class="hljs-number">3</span>D:<span class="hljs-number">13</span>:<span class="hljs-number">77</span>
<span class="hljs-number">0000006071</span> [app] TRACE: RSSI: <span class="hljs-number">-62</span>Bm
<span class="hljs-number">0000006217</span> [app] TRACE: MAC: <span class="hljs-number">65</span>:C7:B3:AF:<span class="hljs-number">73</span>:<span class="hljs-number">5</span>C
<span class="hljs-number">0000006219</span> [app] TRACE: RSSI: <span class="hljs-number">-39</span>Bm
<span class="hljs-number">0000006224</span> [app] TRACE: MAC: B3:D9:F1:F0:<span class="hljs-number">5</span>D:<span class="hljs-number">7</span>E
<span class="hljs-number">0000006225</span> [app] TRACE: RSSI: <span class="hljs-number">-62</span>Bm
<span class="hljs-number">0000006296</span> [app] TRACE: MAC: D7:E7:FE:<span class="hljs-number">0</span>C:A5:C0
<span class="hljs-number">0000006298</span> [app] TRACE: RSSI: <span class="hljs-number">-60</span>Bm
<span class="hljs-number">0000006299</span> [app] TRACE: UUID: feed
</code></pre><p>Notice how the message includes <code>TRACE</code> and also <code>[app]</code>? That means it's a trace message originating from the application code. Handy right?</p>
<p>This code does get spammy quick, especially if you're in an environment with lots of advertising Bluetooth devices. If you're Tile is on and running eventually you'll  see a message <code>UUID: feed</code>. That means your Argon found the Tile!</p>
<p>Next we'll use the onboard Mode button to "program" the Tile's address to memory. That way we can filter out all the devices we don't care about.</p>
<h2 id="heading-add-device-on-button-push">Add Device On Button Push</h2>
<p><img src="https://www.jaredwolff.com/how-to-location-tracking-using-particle-mesh/images/Screen_Shot_2019-09-06_at_11-c1b2b96d-3a1b-45b5-bf5f-5fa7824c80d0.06.04_PM.png" alt="System event handler" width="600" height="400" loading="lazy"></p>
<p>First we need to figure out how to monitor the Mode button. The best bet, according to the documentation is to use <code>System.on</code>.</p>
<pre><code class="lang-c++">System.on(button_click, eventHandler);
</code></pre>
<p>The first argument is the name of the system event. In our case it's <code>button_click</code>. The second argument is an event handler function. We'll call it <code>eventHandler</code> for now.</p>
<p>Now let's create <code>eventHandler</code></p>
<pre><code class="lang-c++"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">eventHandler</span><span class="hljs-params">(<span class="hljs-keyword">system_event_t</span> event, <span class="hljs-keyword">int</span> duration, <span class="hljs-keyword">void</span>* )</span>
</span>{

}
</code></pre>
<p><strong>Important:</strong> you can't use the <code>Log</code> function inside <code>eventHandler</code>. An easy way to test it is to toggle the LED on D7. Let's set it up!</p>
<p>Initialize the LED in <code>setup()</code></p>
<pre><code class="lang-c++"><span class="hljs-comment">// Set LED pin</span>
pinMode(D7,OUTPUT);
</code></pre>
<p>Then we can add this inside <code>eventHandler</code></p>
<pre><code class="lang-c++"><span class="hljs-keyword">if</span>( event == button_click ) {
    <span class="hljs-keyword">if</span>( digitalRead(D7) ) {
        digitalWrite(D7,LOW);
    } <span class="hljs-keyword">else</span> {
        digitalWrite(D7,HIGH);
    }
}
</code></pre>
<p>We can then write to D7 (the onboard blue LED). We can even use <code>digitalRead</code> to read what the state of the LED is. It will respond with <code>HIGH</code> or <code>LOW</code> depending on the situation.</p>
<p>Load the firmware onto the device and we'll have nice control over the blue LED!</p>
<p>In the next section, we'll use the Mode button to put the device into a "learning" mode. This will allow us to do a one touch setup with the target Tile device.</p>
<h2 id="heading-storing-address-to-eeprom">Storing Address to EEPROM</h2>
<p><img src="https://www.jaredwolff.com/how-to-location-tracking-using-particle-mesh/images/Screen_Shot_2019-09-06_at_11-8cf61301-8e85-4d65-a2aa-fd37175194f5.07.24_PM.png" alt="Storing to EEPROM" width="600" height="400" loading="lazy"></p>
<p>In this next step we'll store the address of the Tile into EEPROM. That way when the device is restarted or loses power we'll still be able to identify the Tile later on.</p>
<p>There is one lingering question though. How do we get it to save the address in the first place?</p>
<p>By monitoring the button press, we can put the device into a "learning" mode. The device will scan for a Tile, and save the address if it finds one.</p>
<p>First let's add a conditional within <code>if( uuids[i].shorted() == TILE_UUID )</code>:</p>
<pre><code class="lang-c++"><span class="hljs-comment">// If we're in learning mode. Save to EEPROM</span>
<span class="hljs-keyword">if</span>( isLearningModeOn() ) {
    searchAddress = scanResult-&gt;address;
    EEPROM.put(TILE_EEPROM_ADDRESS, searchAddress);
    setLearningModeOff();
}
</code></pre>
<p>We'll use the status of D7 as a way of knowing we're in "learning mode". We do this by reading D7 using <code>digitalRead(D7)</code>. Let's create a function that makes this more clear:</p>
<pre><code class="lang-c++"><span class="hljs-function"><span class="hljs-keyword">bool</span> <span class="hljs-title">isLearningModeOn</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> (digitalRead(D7) == HIGH);
}
</code></pre>
<p>We can also replace the <code>digitalWrite(D7,LOW);</code> and <code>digitalWrite(D7,HIGH);</code> with similar functions. That way it's more straight forward what we're doing.</p>
<pre><code class="lang-c++"><span class="hljs-comment">// Set "Learning mode" on</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setLearningModeOn</span><span class="hljs-params">()</span> </span>{
    digitalWrite(D7,HIGH);
}

<span class="hljs-comment">// Set "Learning mode" off</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setLearningModeOff</span><span class="hljs-params">()</span> </span>{
    digitalWrite(D7,LOW);
}
</code></pre>
<p>Then, we assign a global variable <code>searchAddress</code> as the scan result. We setup <code>searchAddress</code> like this at the top of the file:</p>
<pre><code class="lang-c++">BleAddress searchAddress;
</code></pre>
<p>Next we want to save it to non-volatile memory using <code>EEPROM.put</code>. <code>TILE_EEPROM_ADDRESS</code> is defined as <code>0xa</code>. You can define  <code>TILE_EEPROM_ADDRESS</code> to use whatever memory address tickles your fancy. Here's the full definition placed at the top of the file.</p>
<pre><code class="lang-c++"><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> TILE_EEPROM_ADDRESS 0xa</span>
</code></pre>
<p>Finally, we turn off the LED and "learning mode" using <code>setLearningModeOff()</code></p>
<p>Every time a device is found we'll use <code>millis()</code> to set <code>lastSeen</code>. Additionally, we can track the last RSSI using <code>lastRSSI</code>. It's a cheap way to to know approximately how close the device is. We'll use <code>scanResult-&gt;rssi</code> to get this information and set it to the <code>lastRSSI</code> variable.</p>
<p>Overall, your changes should look something like this:</p>
<pre><code class="lang-c++">...

<span class="hljs-comment">// Print out the UUID we're looking for</span>
<span class="hljs-keyword">if</span>( uuids[i].shorted() == TILE_UUID ) {
    Log.trace(<span class="hljs-string">"UUID: %x"</span>, uuids[i].shorted());

    <span class="hljs-comment">// If we're in learning mode. Save to EEprom</span>
    <span class="hljs-keyword">if</span>( isLearningModeOn() ) {
        searchAddress = scanResult-&gt;address;
        EEPROM.put(TILE_EEPROM_ADDRESS, searchAddress);
        setLearningModeOff();
    }

    <span class="hljs-comment">// Save info</span>
    lastSeen = millis();
    lastRSSI = scanResult-&gt;rssi;

    <span class="hljs-comment">// Stop scanning</span>
    BLE.stopScanning();

    <span class="hljs-keyword">return</span>;
}
</code></pre>
<p>Before this function, we can filter out devices that don't match our <code>searchAddress</code>. Add the following before <code>if( uuids[i].shorted() == TILE_UUID )</code>:</p>
<pre><code class="lang-c++"><span class="hljs-comment">// If device address doesn't match or we're not in "learning mode"</span>
<span class="hljs-keyword">if</span>( !(searchAddress == scanResult-&gt;address) &amp;&amp; !isLearningModeOn() ) {
    <span class="hljs-keyword">return</span>;
}
</code></pre>
<p>This will skip over devices that don't match. It will only proceed if the address matches or we're in "learning mode".</p>
<p>Now, in order for us to load <code>searchAddress</code> on startup, we'll have to load it from flash. Add this line to your <code>setup():</code></p>
<pre><code class="lang-c++">EEPROM.get(TILE_EEPROM_ADDRESS, searchAddress);
</code></pre>
<p>Then, check to make sure the address is valid. It won't be valid if all the bytes are <code>0xFF</code>:</p>
<pre><code class="lang-c++"><span class="hljs-comment">// Warning about address</span>
<span class="hljs-keyword">if</span>( searchAddress == BleAddress(<span class="hljs-string">"ff:ff:ff:ff:ff:ff"</span>) ) {
    Log.warn(<span class="hljs-string">"Place this board into learning mode"</span>);
    Log.warn(<span class="hljs-string">"and keep your Tile near by."</span>);
}
</code></pre>
<p>We should be able to "teach" our Argon the address of our Tile. Let's test it out!</p>
<h3 id="heading-test-it-1">Test it.</h3>
<p>Now if we compile and run the app, notice how there's no more log output? We have to "teach" the Tile address to the Particle Device. So, hit the mode button. The blue LED should turn on.</p>
<p>Once your Tile has been found the LED will turn off and you'll see some output on the command line. Similar to what we've seen before:</p>
<pre><code><span class="hljs-number">0000006296</span> [app] TRACE: MAC: D7:E7:FE:<span class="hljs-number">0</span>C:A5:C0
<span class="hljs-number">0000006298</span> [app] TRACE: RSSI: <span class="hljs-number">-60</span>Bm
<span class="hljs-number">0000006299</span> [app] TRACE: UUID: feed
</code></pre><p>The device has been committed to memory!</p>
<p>You can also check if it's still saved after a reset. Hit the <strong>reset</strong> button and check for the same output as above. If it's showing up, we're still good!</p>
<h2 id="heading-update-the-cloud">Update the Cloud</h2>
<p><img src="https://www.jaredwolff.com/how-to-location-tracking-using-particle-mesh/images/Screen_Shot_2019-09-06_at_11-34eb3433-edb7-4b57-b3ca-3be935bba026.07.53_PM.png" alt="Publishing to the Particle cloud" width="600" height="400" loading="lazy"></p>
<p>Finally let's set up a function called <code>checkTileStateChanged</code>. We'll use it to check for changes to the state of the Tile on a regular interval.</p>
<pre><code class="lang-c++"><span class="hljs-function"><span class="hljs-keyword">bool</span> <span class="hljs-title">checkTileStateChanged</span><span class="hljs-params">( TilePresenceType *presence )</span> </span>{

}
</code></pre>
<p>The main purpose of this function is to compare the <code>lastSeen</code> variable with the "timeout" duration. In our case, our timeout duration is <code>TILE_NOT_HERE_MS</code> which should be set to</p>
<pre><code class="lang-c++"><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> TILE_NOT_HERE_MS 30000</span>
</code></pre>
<p>near the top of your program. There's also two more conditions to look for. One where <code>lastSeen</code> is equal to 0. This is usually because the app hasn't found the Tile yet after startup.</p>
<p>The last case would be if the device has been seen and <code>lastSeen</code> is not 0. So within <code>checkTileStateChanged</code> let's put everything together.</p>
<pre><code class="lang-c++"><span class="hljs-comment">// Check to see if it's here.</span>
<span class="hljs-keyword">if</span>( millis() &gt; lastSeen+TILE_NOT_HERE_MS ) {

} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ( lastSeen == <span class="hljs-number">0</span> ) {

} <span class="hljs-keyword">else</span> {

}

<span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
</code></pre>
<p>Now we only want this function to return true <strong>if the state has changed</strong>. So we'll need to take advantage of the <code>TilePresenceType</code> pointer in the agreement.</p>
<p><code>TilePresenceType</code> is simply an enumeration of all the possible states. You can stick it at the top of your file as well. Here it is:</p>
<pre><code class="lang-c++"><span class="hljs-keyword">typedef</span> <span class="hljs-keyword">enum</span> {
    PresenceUnknown,
    Here,
    NotHere
} TilePresenceType;
</code></pre>
<p>You'll also need a global variable that we can pass to the function. Set this at the top of your file as well:</p>
<pre><code class="lang-c++"><span class="hljs-comment">// Default status</span>
TilePresenceType present = PresenceUnknown;
</code></pre>
<p>Now, we can compare at each stage. Does it meet the criteria? Is the state different than the last one? If so, return true.</p>
<p>Remember, we'll want to set <code>presence</code> to the new updated value. So each condition should update the presence value. For example:</p>
<pre><code class="lang-c++">*presence = NotHere;
</code></pre>
<p>Here's what the fully flushed out function looks like:</p>
<pre><code class="lang-c++"><span class="hljs-function"><span class="hljs-keyword">bool</span> <span class="hljs-title">checkTileStateChanged</span><span class="hljs-params">( TilePresenceType *presence )</span> </span>{

    <span class="hljs-comment">// Check to see if it's here.</span>
    <span class="hljs-keyword">if</span>( millis() &gt; lastSeen+TILE_NOT_HERE_MS ) {
        <span class="hljs-keyword">if</span>( *presence != NotHere ) {
            *presence = NotHere;
            Log.trace(<span class="hljs-string">"not here!"</span>);
            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
        }
    <span class="hljs-comment">// Case if we've just started up</span>
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ( lastSeen == <span class="hljs-number">0</span> ) {
        <span class="hljs-keyword">if</span>( *presence != PresenceUnknown ) {
            *presence = PresenceUnknown;
            Log.trace(<span class="hljs-string">"unknown!"</span>);
            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
        }
    <span class="hljs-comment">// Case if lastSeen is &lt; TILE_NOT_HERE_MS</span>
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">if</span>( *presence != Here ) {
            *presence = Here;
            Log.trace(<span class="hljs-string">"here!"</span>);
            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
        }
    }

    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
}
</code></pre>
<p>We can now use this function in the main loop underneath the timer to start <code>Ble.scan()</code>. We can use it to send a JSON payload. In this case we'll include important information like the Bluetooth Address, <code>lastSeen</code> data, <code>lastRSSI</code> data and a message.</p>
<pre><code class="lang-c++"><span class="hljs-comment">// If we have a change</span>
<span class="hljs-keyword">if</span>( checkTileStateChanged(&amp;present) ) {

}
</code></pre>
<p>We'll use an array of <code>char</code> to get our address in a string format. You can chain together <code>toString()</code> with <code>toCharArray</code> to get what we need.</p>
<pre><code class="lang-c++"><span class="hljs-comment">// Get the address string</span>
<span class="hljs-keyword">char</span> address[<span class="hljs-number">18</span>];
searchAddress.toString().toCharArray(address,<span class="hljs-keyword">sizeof</span>(address));
</code></pre>
<p>An example payload string could look something like this:</p>
<pre><code class="lang-c++"><span class="hljs-comment">// Create payload</span>
status = String::format(<span class="hljs-string">"{\"address\":\"%s\",\"lastSeen\":%d,\"lastRSSI\":%i,\"status\":\"%s\"}"</span>,
    address, lastSeen, lastRSSI, messages[present]);
</code></pre>
<p><code>status</code> is simply a String defined at the top of the file:</p>
<pre><code class="lang-c++"><span class="hljs-comment">// The payload going to the cloud</span>
String status;
</code></pre>
<p>You notice that there's also a variable called <code>messages</code>. This is a static const array of strings. They're mapped to the values from the <code>TilePresenceType</code>. Here's what it looks like</p>
<pre><code class="lang-c++"><span class="hljs-keyword">const</span> <span class="hljs-keyword">char</span> * messages[] {
    <span class="hljs-string">"unknown"</span>,
    <span class="hljs-string">"here"</span>,
    <span class="hljs-string">"not here"</span>
};
</code></pre>
<p>That way <code>PresenceUnknown</code> matches to <code>"unknown"</code>, <code>Here</code> matches to <code>"here"</code>, etc. It's a cheap easy way to associate a string with an enum.</p>
<p>Finally we'll publish and process. This allows us to send the update immediately.</p>
<pre><code class="lang-c++"><span class="hljs-comment">// Publish the RSSI and Device Info</span>
Particle.publish(<span class="hljs-string">"status"</span>, status, PRIVATE, WITH_ACK);

<span class="hljs-comment">// Process the publish event immediately</span>
Particle.process();
</code></pre>
<p>The overall function should look something like this in the end:</p>
<pre><code class="lang-c++"><span class="hljs-comment">// If we have a change</span>
<span class="hljs-keyword">if</span>( checkTileStateChanged(&amp;present) ) {

    <span class="hljs-comment">// Get the address string</span>
    <span class="hljs-keyword">char</span> address[<span class="hljs-number">18</span>];
    searchAddress.toString().toCharArray(address,<span class="hljs-keyword">sizeof</span>(address));

    <span class="hljs-comment">// Create payload</span>
    status = String::format(<span class="hljs-string">"{\"address\":\"%s\",\"lastSeen\":%d,\"lastRSSI\":%i,\"status\":\"%s\"}"</span>,
        address, lastSeen, lastRSSI, messages[present]);

    <span class="hljs-comment">// Publish the RSSI and Device Info</span>
    Particle.publish(<span class="hljs-string">"status"</span>, status, PRIVATE, WITH_ACK);

    <span class="hljs-comment">// Process the publish event immediately</span>
    Particle.process();

}
</code></pre>
<p>Now, let's test it!</p>
<h3 id="heading-testing-it">Testing it!</h3>
<p><img src="https://www.jaredwolff.com/how-to-location-tracking-using-particle-mesh/images/Screen_Shot_2019-09-06_at_11-97c0bda9-1ba1-4c75-8bd7-4db655d35d51.27.35_PM.png" alt="Test results in terminal window" width="600" height="400" loading="lazy"></p>
<p>We can test to make sure our Publish events are occurring without event leaving Particle Workbench. Open a new terminal by going to <strong>View → Terminal.</strong> Then use the following command:</p>
<pre><code class="lang-bash">particle subscribe --device &lt;device_name&gt; &lt;event_name&gt;
</code></pre>
<p>Replace <code>&lt;device_name&gt;</code> with the name or ID of your device.</p>
<p>Replace <code>&lt;event_name&gt;</code> with the name of the event. In our case it's <code>status</code>.</p>
<p>You can then test it all by removing the battery and waiting for the  "not here" alert. Plug the battery back in and you should get a "here" alert.</p>
<p>Here's an example of the output</p>
<pre><code>&gt; particle subscribe --device hamster_turkey status

Subscribing to <span class="hljs-string">"status"</span> <span class="hljs-keyword">from</span> hamster_turkey<span class="hljs-string">'s stream
Listening to: /v1/devices/hamster_turkey/events/status
{"name":"status","data":"{\"address\":\"C0:A5:0C:FE:E7:D7\",\"lastSeen\":40154002,\"lastRSSI\":-82,\"status\":\"not here\"}","ttl":60,"published_at":"2019-09-07T02:29:42.232Z","coreid":"e00fce68d36c42ef433428eb"}
{"name":"status","data":"{\"address\":\"C0:A5:0C:FE:E7:D7\",\"lastSeen\":40193547,\"lastRSSI\":-83,\"status\":\"here\"}","ttl":60,"published_at":"2019-09-07T02:29:50.352Z","coreid":"e00fce68d36c42ef433428eb"}</span>
</code></pre><h2 id="heading-configuring-webhook">Configuring Webhook</h2>
<p>In the last part of this tutorial we'll set up push notifications using a webhook. As mentioned before, we'll use Pushover and their handy API to send push notification(s) to the device(s) of your choice.</p>
<p>Pushover has a fantastically easy API to use. Their application is a Swiss army knife for situations where you don't want to code an app to send push notifications.</p>
<p>The first thing that you'll have to take note is your <strong>user key.</strong> You can get that by logging into Pushover. Note: you'll need to set up an account first if you haven't already.</p>
<p>It should look something like this:</p>
<p><img src="https://www.jaredwolff.com/how-to-location-tracking-using-particle-mesh/images/Screen_Shot_2019-09-03_at_3-e71c60fd-ef57-4e5f-a7a5-e836b36af15e.39.36_PM.png" alt="Pushover main screen" width="600" height="400" loading="lazy"></p>
<p>If you're logged in and don't see this page, click on the <strong>Pushover logo</strong> and that should bring you back.</p>
<p>Next we'll want to create an application. Click on the <strong>Apps &amp; Plugins</strong> at the top of the screen.</p>
<p><img src="https://www.jaredwolff.com/how-to-location-tracking-using-particle-mesh/images/Screen_Shot_2019-09-03_at_3-3144b865-133a-4778-b349-37ebb333ede7.39.42_PM.png" alt="App/Plugins screen in Pushover" width="600" height="400" loading="lazy"></p>
<p>You should then click <strong>Create a New Application.</strong> This will allow us to get an <strong>API Token</strong> that will be needed in the Particle Webhook setup.</p>
<p><img src="https://www.jaredwolff.com/how-to-location-tracking-using-particle-mesh/images/Screen_Shot_2019-09-05_at_11-c5628ee5-3aa9-4e82-b302-051278631a6b.49.21_AM.png" alt="Create a New Application" width="600" height="400" loading="lazy"></p>
<p>Set a name as you see fit. Fill in the description if you want a reminder. <strong>Click the box</strong> and then click <strong>Create Application.</strong></p>
<p>You should go to the next page. Copy and save the <strong>API Token/Key</strong> we'll need this also in a few steps.</p>
<p><img src="https://www.jaredwolff.com/how-to-location-tracking-using-particle-mesh/images/Screen_Shot_2019-09-03_at_3-a9e88ba1-dcb8-4b4d-8f20-a67e70e84ff5.39.50_PM.png" alt="Viewing Application with API key" width="600" height="400" loading="lazy"></p>
<p>Now, let's setup the Webhook. Jump over to <a target="_blank" href="https://console.particle.io/">https://console.particle.io</a> and create a new integration.</p>
<p><img src="https://www.jaredwolff.com/how-to-location-tracking-using-particle-mesh/images/Screen_Shot_2019-09-03_at_3-ef788a5e-710f-4457-9b04-133d4ecf5f94.41.55_PM.png" alt="Particle console creating new Webhook" width="600" height="400" loading="lazy"></p>
<p>We'll set the <strong>Event Name</strong> to <strong>status</strong>.</p>
<p>The <strong>URL</strong> to <strong><a target="_blank" href="https://api.pushover.net/1/messages.json">https://api.pushover.net/1/messages.json</a></strong></p>
<p>Also, if you want to filter by a specific device make sure you select it in the <strong>Device dropdown.</strong></p>
<p>Under <strong>Advanced Settings</strong> we'll finish up by setting a few fields.</p>
<p><img src="https://www.jaredwolff.com/how-to-location-tracking-using-particle-mesh/images/Screen_Shot_2019-09-03_at_3-831e153a-bb8c-4d73-98c4-61a958c9c51a.42.01_PM.png" alt="Setting the token and api key in Particle Webhook" width="600" height="400" loading="lazy"></p>
<p>Create the following fields: <strong>token,</strong> <strong>user</strong>, <strong>title</strong>, and <strong>message</strong>. Then set token to the <strong>API Token</strong> we got earlier. Do the same for the <strong>User Key.</strong></p>
<p>The <strong>title</strong> will show up as the title of your message. Make it whatever makes sense for you.</p>
<p>You can set the <strong>message</strong> as <code>The Tile is currently {{{status}}}. RSSI: {{{lastRSSI}}}</code>.</p>
<p>We are using mustache templates here. They allow you to use the data in the published payload and reformat it to your liking. In our case, we're using them to "fill in the blanks." The <strong>message</strong> once processed would look something like this:</p>
<p><code>The Tile is currently here. RSSI: -77</code></p>
<p>As a side note, i'll be talking more about these templates in <a target="_blank" href="https://www.jaredwolff.com/the-ultimate-guide-to-particle-mesh/">my guide</a>. So stay tuned for that!</p>
<h3 id="heading-test-it-2">Test it</h3>
<p>Once your integration is in place, you can test doing what we did in the earlier step. Remove the battery, and wait for the "not here" message. Put it back and wait for the "here" message.</p>
<p>Here's what it would look like on an iPhone:</p>
<p><img src="https://www.jaredwolff.com/how-to-location-tracking-using-particle-mesh/images/Pushover-58cbd968-ffbe-4aa5-9087-de6f98708715.png" alt="Pushover messages from Particle Cloud" width="600" height="400" loading="lazy"></p>
<p>As you can see, I tested it a bunch! ?</p>
<p>If you've made it this far and everything is working, great work. You now have a Tile tracker for your house, office or wherever.</p>
<h2 id="heading-the-code">The Code</h2>
<p>Looking for the finished code for this example? I would be too! It's <a target="_blank" href="https://github.com/jaredwolff/particle-bluetooth-presence-detection">hosted on Github and is available here</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>As you can imagine, the techniques and technologies used in this article can be used in many ways. Let's summarize some of the key take aways:</p>
<ol>
<li>Using Bluetooth Central to scan for and identify an off-the-shelf Tile device</li>
<li>Storing the Tile identifying information to EEPROM. That way it can be retrieved on startup.</li>
<li>Using our familiar <code>Particle.publish</code> to push updates to the cloud.</li>
<li>Using a Particle Integration Webhook to create push notifications on state change.</li>
</ol>
<p>Now that you have it all working, expand on it, hack it and make it yours. Oh and don't forget to share! I'd love to hear from you. <a target="_blank" href="mailto:hello@jaredwolff.com">hello@jaredwolff.com</a></p>
<p>Like this post? Click one of the share links below and share it with the world. :)</p>
<p><strong>This is a cross post from my blog. <a target="_blank" href="https://www.jaredwolff.com/how-to-location-tracking-using-particle-mesh/">You can check out the original here.</a></strong></p>
<p>Interested in learning more? I'm writing a guide on how to get the most out of the Particle Platform. <a target="_blank" href="https://www.jaredwolff.com/the-ultimate-guide-to-particle-mesh/">Learn more about it here.</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Ultimate How-to: Build a Bluetooth Swift App With Hardware in 20 Minutes ]]>
                </title>
                <description>
                    <![CDATA[ By Jared Wolff In a previous tutorial, you learned how to add Bluetooth to a Particle Xenon application. That way you could control the onboard RGB LED from a test app like nRF Connect or Light Blue Explorer. In this post, we're going to take it one ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/</link>
                <guid isPermaLink="false">66d85067f6b5e038a1bde7f9</guid>
                
                    <category>
                        <![CDATA[ Bluetooth Low Energy ]]>
                    </category>
                
                    <category>
                        <![CDATA[ iOS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ particle ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Swift ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Swift Programming ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 13 Aug 2019 14:15:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/08/main.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Jared Wolff</p>
<p>In a <a target="_blank" href="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/">previous tutorial</a>, you learned how to add Bluetooth to a Particle Xenon application. That way you could control the onboard RGB LED from a test app like nRF Connect or Light Blue Explorer.</p>
<p>In this post, we're going to take it one step further. We're going to develop a Swift app to control a Particle Mesh RGB led. If all goes well, you should have a working app in about 20 minutes!</p>
<p>Let's get started.</p>
<h3 id="heading-dont-have-time-right-now-to-read-the-full-article">Don't have time right now to read the full article?</h3>
<p><a target="_blank" href="https://www.jaredwolff.com/files/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/">Download the PDF version here.</a></p>
<h2 id="heading-getting-set-up">Getting set up</h2>
<ul>
<li>Install Xcode. <a target="_blank" href="https://developer.apple.com/xcode/resources/">You can download it from the App store here.</a></li>
<li>You'll also need an Apple login. I use my iCloud email. You can create a new account within Xcode if you don't have one yet.</li>
<li>Install the <a target="_blank" href="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/#final-code">RGB example code</a> on a Particle Mesh board.</li>
</ul>
<h2 id="heading-create-the-project">Create the project</h2>
<p>Once everything is installed, let's get to the fun stuff!</p>
<p>Open Xcode and go to <strong>File → New Project.</strong></p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Screen_Shot_2019-08-11_at_3-7ef4de80-050c-4fc3-9cf2-8581e16ffe18.10.57_PM.png" alt="Xcode New Project" width="600" height="400" loading="lazy"></p>
<p>Select <strong>Single View App.</strong></p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Screen_Shot_2019-08-11_at_3-ef953954-312b-4320-be30-5da186d0902e.11.14_PM.png" alt="New Project Info" width="600" height="400" loading="lazy"></p>
<p>Then update the <strong>Project Name</strong> to be to your liking. I've also changed my organization identifier to <code>com.jaredwolff</code>. Modify it as you see fit!</p>
<p>Select a location to save it.</p>
<p>Next find your <strong>Info.plist.</strong></p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Screen_Shot_2019-08-11_at_3-27439ca7-68c5-4890-902d-6c2ee7f31829.13.26_PM.png" alt="Info.plist in Xcocde" width="600" height="400" loading="lazy"></p>
<p>Update <code>info.plist</code> by adding <code>Privacy - Bluetooth Peripheral Usage Description</code></p>
<p>The description I ended up using was <code>App uses Bluetooth to connect to the Particle Xenon RGB Example</code></p>
<p>This allows you to use Bluetooth in your app if you ever want to release it.</p>
<p>Now, let's get everything minimally functional!</p>
<h2 id="heading-minimally-functional">Minimally functional</h2>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Copy_of_Flow-5e62bf38-b399-4bca-9b5b-c63f9716af33.jpg" alt="New section image" width="600" height="400" loading="lazy"></p>
<p>Next, we'll get a minimally functional app to connect and do a services discovery. Most of the action will happen in the <code>ViewController.swift</code>.</p>
<p>Lets first import <code>CoreBluetooth</code></p>
<pre><code class="lang-swift">    <span class="hljs-keyword">import</span> CoreBluetooth
</code></pre>
<p>This allows us to control the Bluetooth Low Energy functionality in iOS. Then let's add both the <code>CBPeripheralDelegate</code> and <code>CBCentralManagerDelegate</code> to the <code>ViewController</code> class.</p>
<pre><code class="lang-swift">    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ViewController</span>: <span class="hljs-title">UIViewController</span>, <span class="hljs-title">CBPeripheralDelegate</span>, <span class="hljs-title">CBCentralManagerDelegate</span> </span>{
</code></pre>
<p>Let's now create local private variables to store the actual central manager and peripheral. We'll set them up further momentarily.</p>
<pre><code class="lang-swift">    <span class="hljs-comment">// Properties</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> centralManager: <span class="hljs-type">CBCentralManager!</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> peripheral: <span class="hljs-type">CBPeripheral!</span>
</code></pre>
<p>In your <code>viewDidLoad</code> function, let's init the <code>centralManager</code></p>
<pre><code class="lang-swift">    centralManager = <span class="hljs-type">CBCentralManager</span>(delegate: <span class="hljs-keyword">self</span>, queue: <span class="hljs-literal">nil</span>)
</code></pre>
<p>Setting <code>delegate: self</code> is important. Otherwise the central state never changes on startup.</p>
<p>Before we get further, let's create a separate file and call it <code>ParticlePeripheral.swift</code>. It can be placed anywhere but I placed it in a separate 'group' called <strong>Models</strong> for later.</p>
<p>Inside we'll create some public variables which contain the UUIDs for our Particle Board. They should look familiar!</p>
<pre><code class="lang-swift">    <span class="hljs-keyword">import</span> UIKit
    <span class="hljs-keyword">import</span> CoreBluetooth

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ParticlePeripheral</span>: <span class="hljs-title">NSObject</span> </span>{

        <span class="hljs-comment">/// MARK: - Particle LED services and charcteristics Identifiers</span>

        <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> particleLEDServiceUUID     = <span class="hljs-type">CBUUID</span>.<span class="hljs-keyword">init</span>(string: <span class="hljs-string">"b4250400-fb4b-4746-b2b0-93f0e61122c6"</span>)
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> redLEDCharacteristicUUID   = <span class="hljs-type">CBUUID</span>.<span class="hljs-keyword">init</span>(string: <span class="hljs-string">"b4250401-fb4b-4746-b2b0-93f0e61122c6"</span>)
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> greenLEDCharacteristicUUID = <span class="hljs-type">CBUUID</span>.<span class="hljs-keyword">init</span>(string: <span class="hljs-string">"b4250402-fb4b-4746-b2b0-93f0e61122c6"</span>)
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> blueLEDCharacteristicUUID  = <span class="hljs-type">CBUUID</span>.<span class="hljs-keyword">init</span>(string: <span class="hljs-string">"b4250403-fb4b-4746-b2b0-93f0e61122c6"</span>)

    }
</code></pre>
<p>Back in <code>ViewController.swift</code> let's piece together the Bluetooth bits.</p>
<h3 id="heading-bluetooth-bits">Bluetooth bits</h3>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Flow-3cb1d250-f9d5-4757-a244-be29bed9dcf6.jpg" alt="Flow diagram for Bluetooth Swift in iOS" width="600" height="400" loading="lazy"></p>
<p>Everything to do with Bluetooth is event based. We'll be defining several functions that handle these events. Here are the important ones:</p>
<p><code>centralManagerDidUpdateState</code> updates when the Bluetooth Peripheral is switched on or off. It will fire when an app first starts so you know the state of Bluetooth. We also start scanning here.</p>
<p>The <code>centralManager</code> <code>didDiscover</code> event occurs when you receive scan results. We'll use this to start a connection.</p>
<p>The <code>centralManager</code> <code>didConnect</code> event fires once the device is connected. We'll start the device discovery here. <strong>Note:</strong> Device discovery is the way we determine what services and characteristics are available. This is a good way to confirm what type of device we're connected to.</p>
<p>The <code>peripheral</code> <code>didDiscoverServices</code> event first once all the services have been discovered. Notice that we've switched from <code>centralManager</code> to <code>peripheral</code> now that we're connected. We'll start the characteristic discovery here. We'll be using the RGB service UUID as the target.</p>
<p>The <code>peripheral</code> <code>didDiscoverCharacteristicsFor</code> event will provide all the characteristics using the provided service UUID. This is the last step in the chain of doing a full device discovery. It's hairy but it only has to be done once during the connection phase!</p>
<h3 id="heading-defining-all-the-bluetooth-functions">Defining all the Bluetooth functions.</h3>
<p>Now that we know what the functions events that get triggered. We'll define them in the logical order that they happen during a connection cycle.</p>
<p>First, we'll define <code>centralManagerDidUpdateState</code> to start scanning for a device with our Particle RGB LED Service. If Bluetooth is not enabled, it will not do anything.</p>
<pre><code class="lang-swift">    <span class="hljs-comment">// If we're powered on, start scanning</span>
        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">centralManagerDidUpdateState</span><span class="hljs-params">(<span class="hljs-number">_</span> central: CBCentralManager)</span></span> {
            <span class="hljs-built_in">print</span>(<span class="hljs-string">"Central state update"</span>)
            <span class="hljs-keyword">if</span> central.state != .poweredOn {
                <span class="hljs-built_in">print</span>(<span class="hljs-string">"Central is not powered on"</span>)
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-built_in">print</span>(<span class="hljs-string">"Central scanning for"</span>, <span class="hljs-type">ParticlePeripheral</span>.particleLEDServiceUUID);
                centralManager.scanForPeripherals(withServices: [<span class="hljs-type">ParticlePeripheral</span>.particleLEDServiceUUID],
                                                  options: [<span class="hljs-type">CBCentralManagerScanOptionAllowDuplicatesKey</span> : <span class="hljs-literal">true</span>])
            }
        }
</code></pre>
<p>Defining the <code>centralManager</code> <code>didDiscover</code> is our next step in the process. We know we've found a device if this event has occurred.</p>
<pre><code class="lang-swift">    <span class="hljs-comment">// Handles the result of the scan</span>
        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">centralManager</span><span class="hljs-params">(<span class="hljs-number">_</span> central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : <span class="hljs-keyword">Any</span>], rssi RSSI: NSNumber)</span></span> {

            <span class="hljs-comment">// We've found it so stop scan</span>
            <span class="hljs-keyword">self</span>.centralManager.stopScan()

            <span class="hljs-comment">// Copy the peripheral instance</span>
            <span class="hljs-keyword">self</span>.peripheral = peripheral
            <span class="hljs-keyword">self</span>.peripheral.delegate = <span class="hljs-keyword">self</span>

            <span class="hljs-comment">// Connect!</span>
            <span class="hljs-keyword">self</span>.centralManager.connect(<span class="hljs-keyword">self</span>.peripheral, options: <span class="hljs-literal">nil</span>)

        }
</code></pre>
<p>So, we stop scanning using <code>self.centralManager.stopScan()</code>. We set the <code>peripheral</code> so it persists through the app. Then we connect to that device using <code>self.centralManager.connect</code></p>
<p>Once connected, we need to double check if we're working with the right device.</p>
<pre><code class="lang-swift">    <span class="hljs-comment">// The handler if we do connect succesfully</span>
        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">centralManager</span><span class="hljs-params">(<span class="hljs-number">_</span> central: CBCentralManager, didConnect peripheral: CBPeripheral)</span></span> {
            <span class="hljs-keyword">if</span> peripheral == <span class="hljs-keyword">self</span>.peripheral {
                <span class="hljs-built_in">print</span>(<span class="hljs-string">"Connected to your Particle Board"</span>)
                peripheral.discoverServices([<span class="hljs-type">ParticlePeripheral</span>.particleLEDServiceUUID])
            }
        }
</code></pre>
<p>By comparing the two peripherals we'll know its the device we found earlier. We'll kick off a services discovery using <code>peripheral.discoverService</code>. We can use <code>ParticlePeripheral.particleLEDServiceUUID</code> as a parameter. That way we don't pick up any services we don't care about.</p>
<p>Once we finish the discovering services, we'll get a <code>didDiscoverServices</code> event. We iterate through all the "available" services. (Though there will only be one!)</p>
<pre><code class="lang-swift">    <span class="hljs-comment">// Handles discovery event</span>
        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">peripheral</span><span class="hljs-params">(<span class="hljs-number">_</span> peripheral: CBPeripheral, didDiscoverServices error: Error?)</span></span> {
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> services = peripheral.services {
                <span class="hljs-keyword">for</span> service <span class="hljs-keyword">in</span> services {
                    <span class="hljs-keyword">if</span> service.uuid == <span class="hljs-type">ParticlePeripheral</span>.particleLEDServiceUUID {
                        <span class="hljs-built_in">print</span>(<span class="hljs-string">"LED service found"</span>)
                        <span class="hljs-comment">//Now kick off discovery of characteristics</span>
                        peripheral.discoverCharacteristics([<span class="hljs-type">ParticlePeripheral</span>.redLEDCharacteristicUUID,
                                                                 <span class="hljs-type">ParticlePeripheral</span>.greenLEDCharacteristicUUID,
                                                                 <span class="hljs-type">ParticlePeripheral</span>.blueLEDCharacteristicUUID], <span class="hljs-keyword">for</span>: service)
                        <span class="hljs-keyword">return</span>
                    }
                }
            }
        }
</code></pre>
<p>By this point this is the third time we're checking to make sure we have the correct service. This becomes more handy later when there are many characteristics and many services.</p>
<p>We call <code>peripheral.discoverCharacteristics</code> with an array of UUIDs for the characteristics we're looking for. They're all the UUIDs that we defined in <code>ParticlePeripheral.swift</code>.</p>
<p>Finally, we handle the <code>didDiscoverCharacteriscsFor</code> event. We iterate through all the available characteristics. As we iterate we compare with the ones we're looking for.</p>
<pre><code class="lang-swift">    <span class="hljs-comment">// Handling discovery of characteristics</span>
        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">peripheral</span><span class="hljs-params">(<span class="hljs-number">_</span> peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)</span></span> {
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> characteristics = service.characteristics {
                <span class="hljs-keyword">for</span> characteristic <span class="hljs-keyword">in</span> characteristics {
                    <span class="hljs-keyword">if</span> characteristic.uuid == <span class="hljs-type">ParticlePeripheral</span>.redLEDCharacteristicUUID {
                        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Red LED characteristic found"</span>)
                    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> characteristic.uuid == <span class="hljs-type">ParticlePeripheral</span>.greenLEDCharacteristicUUID {
                        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Green LED characteristic found"</span>)
                    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> characteristic.uuid == <span class="hljs-type">ParticlePeripheral</span>.blueLEDCharacteristicUUID {
                        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Blue LED characteristic found"</span>);
                    }
                }
            }
        }
</code></pre>
<p>At this point we're ready to do a full device discovery of our Particle Mesh device. In the next section we'll test what we have to make sure things are working ok.</p>
<h2 id="heading-testing-our-minimal-example">Testing our minimal example</h2>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Flow-6-744bf855-fd7c-403b-b07f-dfc0c191b6af.jpg" alt="Section image about testing" width="600" height="400" loading="lazy"></p>
<p>Before we get started, if you run into trouble I've put some troubleshooting steps in the <a target="_blank" href="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/#troubleshooting">footnotes</a>.</p>
<p><strong>To test, you'll have to have an iPhone with Bluetooth Low Energy.</strong> Most modern iPhones have it. The last iPhone not to have it I believe was either the iPhone 4 or 3Gs. (so you're likely good)</p>
<p>First, plug it into your computer.</p>
<p>Go to the top by the play and stop buttons. Select your target device. In my case I chose my phone (<strong>Jared's iPhone</strong>). You can also use an iPad.</p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Screen_Shot_2019-08-09_at_4-d04de709-6000-4161-bd12-7347a70d6e1e.37.27_PM.png" alt="Select device type" width="600" height="400" loading="lazy"></p>
<p>Then you can hit <strong>Command + R</strong> or hit that <strong>Play button</strong> to load the app to your phone.</p>
<p>Make sure you have your log tab open. Enable it by clicking the bottom pane button in the top right corner.</p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Screen_Shot_2019-08-09_at_4-8b83ea0a-274f-4a41-a5ff-e80717b41977.38.57_PM.png" alt="Bottom pane in Xcode for logs" width="600" height="400" loading="lazy"></p>
<p>Make sure you have a mesh device setup and running the example code. You can go to <a target="_blank" href="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/#final-code">this post</a> to get it. Remember your Particle Mesh board needs to be running device OS 1.3.0 or greater for Bluetooth to work!</p>
<p>Once both the firmware and app is loaded, let's check the log output.</p>
<p>It should look something like this:</p>
<pre><code>View loaded
Central state update
Central scanning <span class="hljs-keyword">for</span> B4250400-FB4B<span class="hljs-number">-4746</span>-B2B0<span class="hljs-number">-93</span>F0E61122C6
Connected to your Particle Board
LED service found
Red LED characteristic found
Green LED characteristic found
Blue LED characteristic found
</code></pre><p>This means that your Phone has connected, found the LED service! The characteristics also being discovered is important here. Without those we wouldn't be able to send data to the mesh device.</p>
<p>Next step is to create some sliders so we can update the RGB values on the fly.</p>
<h2 id="heading-slide-to-the-left-slide-to-the-right">Slide to the left. Slide to the right.</h2>
<p>Next we're going to add some elements to our <code>Main.storyboard</code>. Open <code>Main.storyboard</code> and click on the <strong>View</strong> underneath <strong>View Controller.</strong></p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Screen_Shot_2019-08-11_at_1-7919e3c2-cb72-4297-b9e0-06139a064fec.57.37_PM.png" alt="Updating view in Xcode" width="600" height="400" loading="lazy"></p>
<p>Then click on the <strong>Library</strong> button. (It looks like the old art Apple used for the home button)</p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Screen_Shot_2019-08-11_at_1-895d247a-6706-4c39-be04-0bda41195ca6.58.02_PM.png" alt="Library button in Xcode" width="600" height="400" loading="lazy"></p>
<p>You'll get a pop-up with all the choices that you can insert into your app.</p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Screen_Shot_2019-08-11_at_1-8b531053-621e-4c14-8086-2ac0ef6e52de.58.31_PM.png" alt="Library pane in Xcode" width="600" height="400" loading="lazy"></p>
<p>Drag three <strong>Labels</strong> and copy three <strong>Sliders</strong> to your view.</p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Screen_Shot_2019-08-11_at_1-9c8c6167-2279-4cb7-a37b-c24b8cdd275d.59.39_PM.png" alt="Dragging Labels to Xcode View" width="600" height="400" loading="lazy"></p>
<p>You can double click on the labels and rename them as you go.</p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Screen_Shot_2019-08-11_at_1-72573a66-1201-4d08-902d-ea905c91b2bb.59.57_PM.png" alt="Dragging Slider to Xcode View" width="600" height="400" loading="lazy"></p>
<p>If you click and hold, some handy alignment tools will popup. They'll even snap to center!</p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Screen_Shot_2019-08-11_at_2-4c7b1627-c55e-4eba-b29a-32915ba41867.00.17_PM.png" alt="Alignment tools in Xcode" width="600" height="400" loading="lazy"></p>
<p>You can also select them all and move them together. We'll align them vertically and horizontally.</p>
<p>In order for them to stay in the middle, let's remove the autoresizing property. Click the <strong>Ruler icon</strong> on the top right. Then click each of the <strong>red bars</strong>. This will ensure that your labels and sliders stay on the screen!</p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Screen_Shot_2019-08-11_at_2-f207fae8-7292-4e38-9985-c97444ab4e55.09.39_PM.png" alt="Ruler pane in Xcode" width="600" height="400" loading="lazy"></p>
<p>Next let's click the <strong>Show Assistant Editor</strong> button. (Looks like a Venn diagram)</p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Screen_Shot_2019-08-11_at_2-c52c52b8-70b3-426b-9cbe-9df1042f5fb1.00.59_PM.png" alt="Show Assistant Editor button in Xcode" width="600" height="400" loading="lazy"></p>
<p><strong>Note:</strong> make sure that <strong>ViewController.swift</strong> is open in your Assistant Editor.</p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Screen_Shot_2019-08-11_at_2-655a4468-4da8-4841-a2dc-1cf8706592e7.17.35_PM.png" alt="Automatic option in Assistant Editor" width="600" height="400" loading="lazy"></p>
<p>Then underneath the <code>/properties</code> section, <strong>Control-click and drag</strong> <strong>the Red Slider</strong> into your code.</p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Screen_Shot_2019-08-11_at_2-39480e58-fd92-4ec7-a2db-9e8524019755.01.43_PM.png" alt="Drag slider to code" width="600" height="400" loading="lazy"></p>
<p>Repeat with all the other ones. Make sure you name them something different. Your code should look like this when you're done:</p>
<pre><code class="lang-swift">        <span class="hljs-comment">// Properties</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> centralManager: <span class="hljs-type">CBCentralManager!</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> peripheral: <span class="hljs-type">CBPeripheral!</span>

        <span class="hljs-comment">// Sliders</span>
        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> redSlider: <span class="hljs-type">UISlider!</span>
        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> greenSlider: <span class="hljs-type">UISlider!</span>
        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> blueSlider: <span class="hljs-type">UISlider!</span>
</code></pre>
<p>This allow us to access the value of the sliders.</p>
<p>Next, let's attach the <strong>Value Changed</strong> event to each of the sliders. <strong>Right click</strong> on the <strong>Red Slider in the folder view.</strong></p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Screen_Shot_2019-08-11_at_2-b2ffe0a6-709e-433d-9d4c-0f8028bd096c.03.44_PM.png" alt="Drag value changed event to code" width="600" height="400" loading="lazy"></p>
<p>It should give you some options for events. Click and drag the <strong>Value Changed</strong> event to your code. Make sure you name it something that makes sense. I used <strong>RedSliderChanged</strong> for the Red Slider.</p>
<p>Repeat two more times. Your code should look like this at the end of this step:</p>
<pre><code class="lang-swift">        <span class="hljs-meta">@IBAction</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">RedSliderChanged</span><span class="hljs-params">(<span class="hljs-number">_</span> sender: <span class="hljs-keyword">Any</span>)</span></span> {
        }

        <span class="hljs-meta">@IBAction</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GreenSliderChanged</span><span class="hljs-params">(<span class="hljs-number">_</span> sender: <span class="hljs-keyword">Any</span>)</span></span> {
        }

        <span class="hljs-meta">@IBAction</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">BlueSliderChanged</span><span class="hljs-params">(<span class="hljs-number">_</span> sender: <span class="hljs-keyword">Any</span>)</span></span> {
        }
</code></pre>
<p>I've also selected each of the sliders to and <strong>un-checked Enabled</strong>. That way you can't move them. We'll enable them later on in code.</p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Screen_Shot_2019-08-11_at_2-e7e0aca9-224c-4bfb-b482-1d9e58b37947.21.21_PM.png" alt="Disable slider by default" width="600" height="400" loading="lazy"></p>
<p>Also, this is a great time to change the <strong>maximum value to 255</strong>. Also set the default <strong>value from 0.5 to 0.</strong></p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Screen_Shot_2019-08-11_at_2-9bf5618b-f6ac-4aea-9888-7f93a8c75414.55.38_PM.png" alt="Set default value and max value of slider" width="600" height="400" loading="lazy"></p>
<p>Back at the top of the file. Let's create some local variables for each of the characteristics. We'll use these so we can write the slider variables to the Particle Mesh board.</p>
<pre><code class="lang-swift">        <span class="hljs-comment">// Characteristics</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> redChar: <span class="hljs-type">CBCharacteristic?</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> greenChar: <span class="hljs-type">CBCharacteristic?</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> blueChar: <span class="hljs-type">CBCharacteristic?</span>
</code></pre>
<p>Now, let's tie everything together!</p>
<p>In the <code>didDiscoverCharacteristicsFor</code> callback function. Let's assign those characteristics. For example</p>
<pre><code class="lang-swift">    <span class="hljs-keyword">if</span> characteristic.uuid == <span class="hljs-type">ParticlePeripheral</span>.redLEDCharacteristicUUID {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Red LED characteristic found"</span>)
        redChar = characteristic
</code></pre>
<p>As we find each characteristic, we can also enable each of the sliders in the same spot.</p>
<pre><code class="lang-swift">            <span class="hljs-comment">// Unmask red slider</span>
            redSlider.isEnabled = <span class="hljs-literal">true</span>
</code></pre>
<p>In the end your <code>didDiscoverCharacteristicsFor</code> should look like:</p>
<pre><code class="lang-swift">    <span class="hljs-comment">// Handling discovery of characteristics</span>
        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">peripheral</span><span class="hljs-params">(<span class="hljs-number">_</span> peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)</span></span> {
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> characteristics = service.characteristics {
                <span class="hljs-keyword">for</span> characteristic <span class="hljs-keyword">in</span> characteristics {
                    <span class="hljs-keyword">if</span> characteristic.uuid == <span class="hljs-type">ParticlePeripheral</span>.redLEDCharacteristicUUID {
                        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Red LED characteristic found"</span>)

                        redChar = characteristic
                        redSlider.isEnabled = <span class="hljs-literal">true</span>
                    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> characteristic.uuid == <span class="hljs-type">ParticlePeripheral</span>.greenLEDCharacteristicUUID {
                        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Green LED characteristic found"</span>)

                        greenChar = characteristic
                        greenSlider.isEnabled = <span class="hljs-literal">true</span>
                    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> characteristic.uuid == <span class="hljs-type">ParticlePeripheral</span>.blueLEDCharacteristicUUID {
                        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Blue LED characteristic found"</span>);

                        blueChar = characteristic
                        blueSlider.isEnabled = <span class="hljs-literal">true</span>
                    }
                }
            }
        }
</code></pre>
<p>Now, let's update the <code>RedSliderChanged</code> <code>GreenSliderChanged</code> and <code>BlueSliderChanged</code> functions. What we want to do here is update the characteristic associated with the <code>Changed</code> function. I created a separate function called <code>writeLEDValueToChar</code>. We'll pass in the characteristic and the data.</p>
<pre><code class="lang-swift">    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">writeLEDValueToChar</span><span class="hljs-params">( withCharacteristic characteristic: CBCharacteristic, withValue value: Data)</span></span> {

            <span class="hljs-comment">// Check if it has the write property</span>
            <span class="hljs-keyword">if</span> characteristic.properties.<span class="hljs-built_in">contains</span>(.writeWithoutResponse) &amp;&amp; peripheral != <span class="hljs-literal">nil</span> {

                peripheral.writeValue(value, <span class="hljs-keyword">for</span>: characteristic, type: .withoutResponse)

            }

        }
</code></pre>
<p>Now add a call to <code>writeLEDValueToChar</code> to each of the <code>Changed</code> functions. You will have to cast the value to a <code>Uint8</code>. (The Particle Mesh device expects an unsigned 8-bit number.)</p>
<pre><code class="lang-swift">            <span class="hljs-meta">@IBAction</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">RedSliderChanged</span><span class="hljs-params">(<span class="hljs-number">_</span> sender: <span class="hljs-keyword">Any</span>)</span></span> {
            <span class="hljs-built_in">print</span>(<span class="hljs-string">"red:"</span>,redSlider.value);
            <span class="hljs-keyword">let</span> slider:<span class="hljs-type">UInt8</span> = <span class="hljs-type">UInt8</span>(redSlider.value)
            writeLEDValueToChar( withCharacteristic: redChar!, withValue: <span class="hljs-type">Data</span>([slider]))

        }
</code></pre>
<p>Repeat this for <code>GreenSliderChanged</code> and <code>BlueSliderChanged</code>. Make sure you changed <code>red</code> to <code>green</code> and <code>blue</code> for each!</p>
<p>Finally, to keep things clean, i've also added a function that handles Bluetooth disconnects.</p>
<pre><code class="lang-swift">    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">centralManager</span><span class="hljs-params">(<span class="hljs-number">_</span> central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?)</span></span> {
</code></pre>
<p>Inside, we should reset the state of the sliders to 0 and disable them.</p>
<pre><code class="lang-swift">            <span class="hljs-keyword">if</span> peripheral == <span class="hljs-keyword">self</span>.peripheral {
                <span class="hljs-built_in">print</span>(<span class="hljs-string">"Disconnected"</span>)

                redSlider.isEnabled = <span class="hljs-literal">false</span>
                greenSlider.isEnabled = <span class="hljs-literal">false</span>
                blueSlider.isEnabled = <span class="hljs-literal">false</span>

                redSlider.value = <span class="hljs-number">0</span>
                greenSlider.value = <span class="hljs-number">0</span>
                blueSlider.value = <span class="hljs-number">0</span>
</code></pre>
<p>It's a good idea to reset <code>self.peripheral</code> to nil that way we're not ever trying to write to a phantom device.</p>
<pre><code class="lang-swift">                <span class="hljs-keyword">self</span>.peripheral = <span class="hljs-literal">nil</span>
</code></pre>
<p>Finally, because we've disconnected, start scanning again!</p>
<pre><code class="lang-swift">                <span class="hljs-comment">// Start scanning again</span>
                <span class="hljs-built_in">print</span>(<span class="hljs-string">"Central scanning for"</span>, <span class="hljs-type">ParticlePeripheral</span>.particleLEDServiceUUID);
                centralManager.scanForPeripherals(withServices: [<span class="hljs-type">ParticlePeripheral</span>.particleLEDServiceUUID],
                                                  options: [<span class="hljs-type">CBCentralManagerScanOptionAllowDuplicatesKey</span> : <span class="hljs-literal">true</span>])
            }
</code></pre>
<p>Alright! We just about ready to test. Let's move on to the next (and final) step.</p>
<h2 id="heading-test-the-sliders">Test the sliders.</h2>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/Flow-5-3af7db2e-8bf7-4561-98c2-a1b7ab0c685b.jpg" alt="Next section test!" width="600" height="400" loading="lazy"></p>
<p>The hard work is done. Now it's time to play!</p>
<p>The easiest way to test everything is to <strong>click the Play button</strong> in the top left or the <strong>Command + R</strong> keyboard shortcut. Xcode will load the app to your phone. You should see a white screen proceeded by a screen with your sliders!</p>
<p>The sliders should stay greyed out until connected to your Particle Mesh board. You can check your log output if the connection has been established.</p>
<pre><code>View loaded
Central state update
Central scanning <span class="hljs-keyword">for</span> B4250400-FB4B<span class="hljs-number">-4746</span>-B2B0<span class="hljs-number">-93</span>F0E61122C6
Connected to your Particle Board
LED service found
Red LED characteristic found
Green LED characteristic found
Blue LED characteristic found
</code></pre><p>(Look familiar? We're connected!)</p>
<p>If you followed everything perfectly, you should be able to move the sliders. Better yet, the RGB LED on the Particle Mesh board should change color.</p>
<p><img src="https://www.jaredwolff.com/the-ultimate-how-to-bluetooth-swift-with-hardware-in-20-minutes/images/DSC01556-1fda5018-f1f5-4855-8705-f7d344ce3d78.jpeg" alt="Final test results" width="600" height="400" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article you've learned how to connect your Particle Mesh board and iOS device over Bluetooth. We've learned how to connect to each of the available characteristics. Plus, on top of it all, make a clean interface to do it all in.</p>
<p>As you can imagine, you can go down the rabbit hole with Bluetooth on iOS. There's more coming in my upcoming guide: <strong>The Ultimate Guide to Particle Mesh.</strong> Subscribers to my list get access to pre-launch content and a discount when it comes out! <a target="_blank" href="https://www.jaredwolff.com/the-ultimate-guide-to-particle-mesh/">Click here to get signed up.</a></p>
<h2 id="heading-code">Code</h2>
<p>The full source code is available on <a target="_blank" href="https://github.com/jaredwolff/swift-bluetooth-particle-rgb">Github.</a> If you find it useful, hit the star button. ⭐️</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Particle's Powerful Bluetooth API ]]>
                </title>
                <description>
                    <![CDATA[ By Jared Wolff This post is originally from www.jaredwolff.com I was defeated. I had spent the whole night trying to get a Bluetooth Low Energy project working. It was painful. It was frustrating. I was ready to give up. That was during the early day... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-particles-powerful-bluetooth-api/</link>
                <guid isPermaLink="false">66d8505ac15439a8d5631e7f</guid>
                
                    <category>
                        <![CDATA[ Bluetooth Low Energy ]]>
                    </category>
                
                    <category>
                        <![CDATA[ hardware ]]>
                    </category>
                
                    <category>
                        <![CDATA[ particle ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 15 Jul 2019 12:39:52 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/07/Particle-Bluetooth.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Jared Wolff</p>
<p><strong>This post is originally from <a target="_blank" href="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/">www.jaredwolff.com</a></strong></p>
<p>I was defeated.</p>
<p>I had spent the whole night trying to get a Bluetooth Low Energy project working. It was painful. It was frustrating. I was ready to give up.</p>
<p>That was during the early days of Bluetooth Low Energy. Since then it's gotten easier and easier to develop. The Particle Mesh Bluetooth Library is no exception.</p>
<p>In this walkthrough, i'll show you how to use Particle's Bluetooth API. We'll configure some LEDs and watch them change over all devices in the Mesh network. We'll be using an Argon and Xenon board.</p>
<p>Ready? Let's get started!</p>
<p>P.S. this post is lengthy. If you want something to download, <a target="_blank" href="https://www.jaredwolff.com/files/how-to-use-particles-powerful-bluetooth-api-pdf">click here for a beautifully formatted PDF.</a></p>
<h2 id="heading-stage-1-setting-up-bluetooth">Stage 1: Setting Up Bluetooth</h2>
<ol>
<li><a target="_blank" href="https://www.particle.io/workbench/">Download/Install Particle Workbench</a></li>
<li><p>Create a new Project. I picked a suitable location and then named it <code>ble_mesh</code></p>
<p> <img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/Screen_Shot_2019-07-13_at_5-4348ea15-e220-4138-8d28-a8c5de99e9fe.32.11_PM.png" alt="Create a new Project in Particle Workbench" width="600" height="400" loading="lazy"></p>
</li>
<li><p>Go to your <code>/src/</code> direcory and open your <code>&lt;your project name&gt;.ino</code> file</p>
</li>
<li><p>Then make sure you change the version of your deviceOS to  &gt; <strong>1.3.0</strong></p>
<p> <img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/Screen_Shot_2019-07-13_at_5-9d20c90b-e27c-4609-9917-2ec1bb849727.40.15_PM.png" alt="Select DeviceOS Version" width="600" height="400" loading="lazy"></p>
</li>
</ol>
<h3 id="heading-write-the-code">Write the Code</h3>
<p>We want to set up a service with 3 characteristics. The characteristics relate to the intensity of the RGB LEDs respectively. Here's how to get your Bluetooth Set Up:</p>
<ol>
<li><p>In your <code>Setup()</code> function enable app control of your LED</p>
<pre><code> RGB.control(<span class="hljs-literal">true</span>);
</code></pre></li>
<li><p>Set up your UUIDs at the top of your <code>.ino</code> file</p>
<p>     const char<em> serviceUuid = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E";
     const char</em> red         = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E";
     const char<em> green       = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E";
     const char</em> blue        = "6E400004-B5A3-F393-E0A9-E50E24DCCA9E";</p>
<p> UUIDs are unique identifiers or addresses. They're used to differentiate different services and characteristics on a device.</p>
<p> The above UUIDs are used in previous Particle examples. If you want to create your own you can use  <code>uuidgen</code> on the OSX command line. You can also go to a website like <strong><a target="_blank" href="https://www.guidgenerator.com/online-guid-generator.aspx">Online GUID Generator</a>.</strong></p>
<p> <img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/Screen_Shot_2019-07-14_at_11-343e1df6-69e8-4560-98b9-f2f54caeb7d3.28.46_AM.png" alt="Online GUID Generator" width="600" height="400" loading="lazy"></p>
<p> Use the above settings to get your own UUID. You can then create your service and characteristic UUIDS from this generated one:</p>
<p>     const char<em> serviceUuid = "b425040<strong>0</strong>-fb4b-4746-b2b0-93f0e61122c6"; //service
     const char</em> red         = "b4250401-fb4b-4746-b2b0-93f0e61122c6"; //red char
     const char<em> green       = "b4250402-fb4b-4746-b2b0-93f0e61122c6"; //green char
     const char</em> blue        = "b4250403-fb4b-4746-b2b0-93f0e61122c6"; //blue char</p>
<p> There's no right or wrong way to do this. But you have to be careful you're not using the UUIDs reserved by the Bluetooth SIG. This is highly unlikely. If you do want to double check you can go <a target="_blank" href="https://www.bluetooth.com/specifications/gatt/characteristics/">here</a> and <a target="_blank" href="https://www.bluetooth.com/specifications/gatt/services/">here</a>.</p>
<p> For now, we'll stick with the first set of UUIDs.</p>
</li>
<li><p>In <code>Setup()</code>, initialize your service.</p>
<p>     // Set the RGB BLE service
     BleUuid rgbService(serviceUuid);</p>
<p> This is the first step of "registering' your service. More on this below.</p>
</li>
<li><p>Initialize each characteristic in <code>Setup()</code></p>
<p>     BleCharacteristic redCharacteristic("red", BleCharacteristicProperty::WRITE_WO_RSP, red, serviceUuid, onDataReceived, (void<em>)red);
     BleCharacteristic greenCharacteristic("green", BleCharacteristicProperty::WRITE_WO_RSP, green, serviceUuid, onDataReceived, (void</em>)green);
     BleCharacteristic blueCharacteristic("blue", BleCharacteristicProperty::WRITE_WO_RSP, blue, serviceUuid, onDataReceived, (void*)blue);</p>
<p> For this setup, we're going to use the <code>WRITE_WO_RSP</code> property. This allows us to write the data and expect no response.
 I've referenced the UUIDs as the next two parameters. The first being the characteristic UUID. The second being the service UUID.</p>
<p> The next parameter is the callback function. When data is written to this callback, this function will fire.</p>
<p> Finally the last parameter is the context. What does this mean exactly? We're using the same callback for all three characteristics. The only way we can know which characteristic was written to (in deviceOS at least) is by setting a context. In this case we're going to use the already available UUIDs.</p>
</li>
<li><p>Right after defining the characteristics, let's add them so they show up:</p>
<p>     // Add the characteristics
     BLE.addCharacteristic(redCharacteristic);
     BLE.addCharacteristic(greenCharacteristic);
     BLE.addCharacteristic(blueCharacteristic);</p>
</li>
<li><p>Set up the callback function.</p>
<p>     // Static function for handling Bluetooth Low Energy callbacks
     static void onDataReceived(const uint8_t<em> data, size_t len, const BlePeerDevice&amp; peer, void</em> context) {</p>
<p>     }</p>
<p> You can do this at the top of the file (above <code>Setup()</code>) We will define this more later.</p>
</li>
<li><p>Finally, in order for your device to be connectable, we have to set up advertising. Place this code at the end of your <code>Setup()</code> function</p>
<p>     // Advertising data
     BleAdvertisingData advData;</p>
<p>     // Add the RGB LED service
     advData.appendServiceUUID(rgbService);</p>
<p>     // Start advertising!
     BLE.advertise(&amp;advData);</p>
<p> First we create a <code>BleAdvertisingData</code> object. We add the <code>rgbService</code> from Step 3. Finally, we can start advertising so our service and characteristics are discoverable!</p>
</li>
</ol>
<h3 id="heading-time-to-test">Time to test</h3>
<p>At this point we have a minimally viable program. Let's compile it and program it to our Particle hardware. This should work with any Mesh enabled device. (Xenon, Argon, Boron)</p>
<ol>
<li>Before we start testing, temporarily add <code>SYSTEM_MODE(MANUAL);</code> to the top of your file. This will prevent the device connecting to the mesh network. If the device is blinking blue on startup, you'll have to set it up with the <a target="_blank" href="https://apps.apple.com/ru/app/particle-iot/id991459054?l=en">Particle App</a> before continuing.</li>
<li><p><a target="_blank" href="https://github.com/particle-iot/device-os/releases/tag/v1.3.0-rc.1">Download the 1.3.0-rc.1 image here.</a> For Xenon, you'll need <strong>xenon-system-part1@1.3.0-rc.1.bin.</strong> For others look for <strong>boron-system-part1@1.3.0-rc.1.bin</strong> and <strong>argon-system-part1@1.3.0-rc.1.bin.</strong> The files are at the bottom of the page under <strong>Assets</strong></p>
<p> <img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/Screen_Shot_2019-07-14_at_11-df9b46f4-e230-484e-b69e-828526f5566e.39.39_AM.png" alt="Assets location on DeviceOS Release Page" width="600" height="400" loading="lazy"></p>
</li>
<li><p>Put your device into DFU mode. Hold the <strong>Mode Button</strong> and momentarily click the <strong>Reset</strong> <strong>Button.</strong> Continue holding the <strong>Mode Button</strong> until the LED blinks yellow.</p>
</li>
<li>In a command line window, change directories to where you stored the file you downloaded. In my case the command is <code>cd ~/Downloads/</code></li>
<li><p>Then run:</p>
<p>     particle flash --usb xenon-system-part1@1.3.0-rc.1.bin</p>
<p> This will install the latest OS to your Xenon. Once it's done it will continue to rapidly blink yellow. Again if you have a different Particle Mesh device, change the filename to match.</p>
</li>
<li><p>In Visual Code, use the <strong>Command + Shift + P</strong> key combination to pop up the command menu. Select <strong>Particle: Compile application (local)</strong></p>
<p> <img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/Screen_Shot_2019-07-13_at_10-8cb8dda2-73ae-4b0d-af5b-33892d66752e.52.19_PM.png" alt="Compile application (local) choice" width="600" height="400" loading="lazy"></p>
</li>
<li><p>Fix any errors that may pop up.</p>
</li>
<li><p>Then, open the same menu and select <strong>Flash application (local)</strong></p>
<p> <img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/Screen_Shot_2019-07-13_at_10-4ff7bc95-58f1-497f-9edf-9eadb69e3abb.51.59_PM.png" alt="Flash application (local) choice" width="600" height="400" loading="lazy"></p>
</li>
<li><p>When programming is complete, pull out your phone.  Then, open your favorite Bluetooth Low Energy app. The best ones are <strong><a target="_blank" href="https://apps.apple.com/cn/app/nrf-connect/id1054362403?l=en">NRF Connect</a></strong> and <strong><a target="_blank" href="https://apps.apple.com/ru/app/lightblue-explorer/id557428110?l=en">Light Blue Explorer.</a></strong> I'm going to use Light Blue Explorer for this example.</p>
</li>
<li><p>Check if a device named <strong>"Xenon-"</strong> is advertising. Insert <strong></strong> with the unique ID for your device.</p>
<p><img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/IMG_2187-aa8b88cc-f423-4b5a-8bb0-e1f75c3b7dd9.png" alt="Light Blue Explorer Scan Results" width="600" height="400" loading="lazy"></p>
</li>
<li><p>Find your device and click the name.</p>
</li>
<li><p>Look at the list of services &amp; characteristics. Does it include the service and characteristic UUID's that we have set so far? For instance, does the service UUID show up as <strong>6E400001-B5A3-F393-E0A9-E50E24DCCA9E</strong>?</p>
<p><img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/IMG_2188-d7759af0-964e-4067-9ca2-d78edd190410.png" alt="Confirm Light Blue Explorer has new characteristic UUIDs" width="600" height="400" loading="lazy"></p>
<p>If everything shows up as you expect, you're in a good place. If not go through the earlier instructions to make sure everything matches.</p>
</li>
</ol>
<h2 id="heading-stage-2-handling-data">Stage 2: Handling Data</h2>
<p>The next stage of our project is to process write events. We'll be updating our <code>onDataReceived</code> function.</p>
<h3 id="heading-write-the-code-1">Write the Code</h3>
<ol>
<li><p>First, let's create a struct that will keep the state of the LEDs. This can be done at the top of the file.</p>
<p>     // Variables for keeping state
     typedef struct {
       uint8_t red;
       uint8_t green;
       uint8_t blue;
     } led_level_t;</p>
</li>
<li><p>The second half of that is to create a static variable using this data type</p>
<p>     // Static level tracking
     static led_level_t m_led_level;</p>
<p> The first two steps allows us to use one single variable to represent the three values of the RGB LED.</p>
</li>
<li><p>Next, let's check for basic errors inside the <code>onDataReceive</code> function For instance we want to make sure that we're receiving only one byte.</p>
<p>     // We're only looking for one byte
       if( len != 1 ) {
         return;
         }</p>
</li>
<li><p>Next, we want to see which characteristic this event came from. We can use the <code>context</code> variable to determine this.</p>
<p>     // Sets the global level
       if( context == red ) {
         m_led_level.red = data[0];
       } else if ( context == green ) {
         m_led_level.green = data[0];
       } else if ( context == blue ) {
         m_led_level.blue = data[0];
       }</p>
<p> Remember, in this case context will be equal to the pointer of either the red, green, or blue UUID string. You can also notice we're setting <code>m_led_level</code>. That way we can update the RGB led even if only one value has changed.</p>
</li>
<li><p>Finally, once set, you can write to the <code>RGB</code> object</p>
<p>     // Set RGB color
         RGB.color(m_led_level.red, m_led_level.green, m_led_level.blue);</p>
</li>
</ol>
<h3 id="heading-test-the-code">Test the Code</h3>
<p>Let's go through the same procedure as before to flash the device.</p>
<ol>
<li>Put your device into DFU mode. Hold the <strong>Mode Button</strong> and click the <strong>Reset</strong> <strong>Button.</strong> Continue holding the <strong>Mode Button</strong> until the LED blinks yellow.</li>
<li><p>Then, open the same menu and select <strong>Flash application (local)</strong></p>
<p> <img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/Screen_Shot_2019-07-13_at_10-4ff7bc95-58f1-497f-9edf-9eadb69e3abb.51.59_PM.png" alt="Flash application (local) choice" width="600" height="400" loading="lazy"></p>
</li>
<li><p>Once it's done programming, connect to the device using <strong>Light Blue Explorer</strong>.</p>
</li>
<li>Tap on the characteristic that applies to the red LED.</li>
<li><p><strong>Write FF</strong>. The red LED should turn on.</p>
<p> <img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/IMG_2191-b8ad0693-f4f8-4828-ae99-b0a0e204cc50.jpg" alt="Write new value choice" width="600" height="400" loading="lazy"></p>
<p> <img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/IMG_2190-2c799875-22e5-44bc-9edc-c647cf8669f6.png" alt="Write the hex value of 0xff" width="600" height="400" loading="lazy"></p>
</li>
<li><p><strong>Write 00</strong>. The red LED should turn off.</p>
</li>
<li>Do the same for the other two characteristics. We now have full control of the RGB LED over Bluetooth Low Energy!</li>
</ol>
<h2 id="heading-stage-3-sharing-via-mesh">Stage 3: Sharing Via Mesh</h2>
<p>Finally, now that we're successfully receiving BLE message, it's time to forward them on to our mesh network.</p>
<h3 id="heading-write-the-code-2">Write the Code</h3>
<ol>
<li>First let's remove MANUAL mode. Comment out <code>SYSTEM_MODE(MANUAL);</code></li>
<li><p>At the top of the file let's add a variable we'll used to track if we need to publish</p>
<p>     // Tracks when to publish to Mesh
     static bool m_publish;</p>
</li>
<li><p>Then initialize it in <code>Setup()</code></p>
<p>     // Set to false at first
     m_publish = false;</p>
</li>
<li><p>Then, after setting the RGB led in the <code>onDataReceived</code> callback, let's set it true:</p>
<p>     // Set RGB color
     RGB.color(m_led_level.red, m_led_level.green, m_led_level.blue);</p>
<p>     // Set to publish
     m_publish = true;</p>
</li>
<li><p>Let's add a conditional in the <code>loop()</code> function. This will cause us to publish the LED status to the Mesh network:</p>
<p>     if( m_publish ) {
         // Reset flag
         m_publish = false;</p>
<p>         // Publish to Mesh
       Mesh.publish("red", String::format("%d", m_led_level.red));
       Mesh.publish("green", String::format("%d", m_led_level.green));
       Mesh.publish("blue", String::format("%d", m_led_level.blue));
     }</p>
<p> <code>Mesh.publish</code> requires a string for both inputs. Thus, we're using <code>String::format</code> to create a string with our red, green and blue values.</p>
</li>
<li><p>Then let's subscribe to the same variables in <code>Setup()</code>. That way another device can cause the LED on this device to update as well.</p>
<p>     Mesh.subscribe("red", meshHandler);
     Mesh.subscribe("green", meshHandler);
     Mesh.subscribe("blue", meshHandler);</p>
</li>
<li><p>Toward the top of the file we want to create <code>meshHandler</code></p>
<p>     // Mesh event handler
     static void meshHandler(const char <em>event, const char </em>data)
     {
     }</p>
</li>
<li><p>In this application, we need the <code>event</code> parameter and <code>data</code> parameter. In order use them, we have to change them to a <code>String</code> type. That way we can use the built in conversion and comparison functions. So, inside the <code>meshHandler</code> function add:</p>
<p>       // Convert to String for useful conversion and comparison functions
       String eventString = String(event);
       String dataString = String(data);</p>
</li>
<li><p>Finally we do some comparisons. We first check if the event name matches. Then we set the value of the <code>dataString</code> to the corresponding led level.</p>
<p>       // Determine which event we recieved
       if( eventString.equals("red") ) {
         m_led_level.red = dataString.toInt();
       } else if ( eventString.equals("green") ) {
         m_led_level.green = dataString.toInt();
       } else if ( eventString.equals("blue") ) {
         m_led_level.blue = dataString.toInt();
       } else {
             return;
         }</p>
<p>       // Set RGB color
       RGB.color(m_led_level.red, m_led_level.green, m_led_level.blue);</p>
<p> Then at the end we set the new RGB color. Notice how I handle an unknown state by adding a <code>return</code> statement in the <code>else</code> section. It's always good to filter out error conditions before they wreak havoc!</p>
</li>
</ol>
<h3 id="heading-test-the-code-1">Test the Code</h3>
<ol>
<li>Open the Particle App on your phone</li>
<li><p>Let's set up the Argon first. <strong>If it's not blinking blue, hold the mode button until it's blinking blue.</strong></p>
<p> <img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/IMG_1216-f78725ab-a9f5-4270-b389-73d6dddb1f62.jpg" alt="Particle Argon with Blue LED" width="600" height="400" loading="lazy"></p>
<p> Note: if you've already programmed the app, the LED will be off by default. <strong>Hold the mode button for 5 seconds and then continue.</strong></p>
</li>
<li><p>Go through the pairing process. The app walks you though all the steps. <strong>Make sure you remember the Admin password for your mesh network.</strong></p>
<p> <img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/IMG_2194-2482aa12-082c-4786-8883-860b50b4cd53.png" alt="Particle Setup App Board Choice" width="600" height="400" loading="lazy"></p>
<p> <img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/IMG_2195-2d030603-e59f-4e76-9d1a-9d4f2c078a4e.png" alt="Scan the sticker" width="600" height="400" loading="lazy"></p>
<p> <img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/IMG_2198-80bb83d8-818b-4cd8-a583-9262da9b5121.png" alt="Pairing with your Argon" width="600" height="400" loading="lazy"></p>
<p> <img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/IMG_2199-e3c107dd-2704-4aa3-9781-550ad14ea7fc.png" alt="Enter wifi password" width="600" height="400" loading="lazy"></p>
</li>
<li><p>Program an Argon with the latest firmware (1.3.0) (see <strong>Stage 1 - Time to Test - Step 2</strong> for a reminder on how to do this)</p>
</li>
<li>Once rapidly blinking yellow, program the Argon with the Tinker app. You can download it at the <a target="_blank" href="https://github.com/particle-iot/device-os/releases/tag/v1.3.0-rc.1">release page</a>.</li>
<li><p>Once we have a nice solid Cyan LED (connected to the Particle Cloud) we'll program the app. Use the <strong>Cloud Flash</strong> option in the drop down menu.</p>
<p> <img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/Screen_Shot_2019-07-15_at_9-a50458b1-ade1-4d4d-818b-ef214f3fec5d.55.19_AM.png" alt="Cloud Flash option" width="600" height="400" loading="lazy"></p>
<p> As far as I can tell, Particle forces any device flashed locally into safe mode when connecting to the cloud. It may be my setup. Your mileage may vary here. Best to use  <strong>Cloud Flash</strong>.</p>
<p> Make sure you select the correct deviceOS version (<strong>1.3.0-rc1</strong>), device type (<strong>Argon</strong>) and device name (<strong>What you named it during setup</strong>)</p>
</li>
<li><p>Connect to the Xenon using the <strong>phone app</strong></p>
</li>
<li>Connect the Xenon to your Mesh network using the phone app</li>
<li><p>Flash your Xenon using <strong>Cloud Flash</strong>. Use the name that you gave it during the phone app setup. As long as the device is connected to Particle Cloud or in safe mode (Purple LED), it should program.</p>
<p> <img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/Screen_Shot_2019-07-15_at_10-2f76843b-3cee-4818-adfb-7ece4173df2d.06.32_AM.png" alt="Confirm board type, deviceOS version and device name" width="600" height="400" loading="lazy"></p>
<p> <img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/Screen_Shot_2019-07-15_at_10-8e140009-440c-480d-838e-b83c782a8c96.08.47_AM.png" alt="Cloud flash option" width="600" height="400" loading="lazy"></p>
</li>
<li><p>Once connected, let's get to the fun part. Open up <strong>Light Blue Explorer.</strong> Connect to either the <strong>Argon</strong> or the <strong>Xenon</strong>.</p>
<p><img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/IMG_2200-e1c513ba-6689-4652-b442-e5c6f916d619.png" alt="Select either the Argon or Xenon" width="600" height="400" loading="lazy"></p>
</li>
<li><p>Select one of the LED characteristics and change the value.</p>
<p><img src="https://www.jaredwolff.com/how-to-use-particles-powerful-bluetooth-api/images/IMG_0627-2db8ab26-99e1-46a2-9583-97126fbe2594.jpg" alt="Argon and Xenon with red LEDs on" width="600" height="400" loading="lazy"></p>
<p>The LED should change on all devices!</p>
</li>
</ol>
<h2 id="heading-final-code">Final Code</h2>
<p>Here's the final code with all the pieces put together. You can use this to make sure you put them in the right place!!</p>
<p>    /*</p>
<ul>
<li>Project ble_mesh</li>
<li>Description: Bluetooth Low Energy + Mesh Example</li>
<li>Author: Jared Wolff</li>
<li><p>Date: 7/13/2019
*/</p>
<p>//SYSTEM_MODE(MANUAL);</p>
<p>// UUIDs for service + characteristics
const char<em> serviceUuid = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E";
const char</em> red         = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E";
const char<em> green       = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E";
const char</em> blue        = "6E400004-B5A3-F393-E0A9-E50E24DCCA9E";</p>
<p>// Set the RGB BLE service
BleUuid rgbService(serviceUuid);</p>
<p>// Variables for keeping state
typedef struct {
uint8_t red;
uint8_t green;
uint8_t blue;
} led_level_t;</p>
<p>// Static level tracking
static led_level_t m_led_level;</p>
<p>// Tracks when to publish to Mesh
static bool m_publish;</p>
<p>// Mesh event handler
static void meshHandler(const char <em>event, const char </em>data)
{</p>
<p>// Convert to String for useful conversion and comparison functions
String eventString = String(event);
String dataString = String(data);</p>
<p>// Determine which event we recieved
if( eventString.equals("red") ) {
 m_led_level.red = dataString.toInt();
} else if ( eventString.equals("green") ) {
 m_led_level.green = dataString.toInt();
} else if ( eventString.equals("blue") ) {
 m_led_level.blue = dataString.toInt();
} else {
     return;
 }</p>
<p>// Set RGB color
RGB.color(m_led_level.red, m_led_level.green, m_led_level.blue);</p>
<p>}</p>
<p>// Static function for handling Bluetooth Low Energy callbacks
static void onDataReceived(const uint8_t<em> data, size_t len, const BlePeerDevice&amp; peer, void</em> context) {</p>
<p>// We're only looking for one byte
if( len != 1 ) {
 return;
}</p>
<p>// Sets the global level
if( context == red ) {
 m_led_level.red = data[0];
} else if ( context == green ) {
 m_led_level.green = data[0];
} else if ( context == blue ) {
 m_led_level.blue = data[0];
}</p>
<p>// Set RGB color
RGB.color(m_led_level.red, m_led_level.green, m_led_level.blue);</p>
<p>// Set to publish
m_publish = true;</p>
<p>}</p>
<p>// setup() runs once, when the device is first turned on.
void setup() {</p>
<p>// Enable app control of LED
RGB.control(true);</p>
<p>// Init default level
m_led_level.red = 0;
m_led_level.green = 0;
m_led_level.blue = 0;</p>
<p>// Set to false at first
m_publish = false;</p>
<p>// Set the subscription for Mesh updates
Mesh.subscribe("red",meshHandler);
Mesh.subscribe("green",meshHandler);
Mesh.subscribe("blue",meshHandler);</p>
<p>// Set up characteristics
BleCharacteristic redCharacteristic("red", BleCharacteristicProperty::WRITE_WO_RSP, red, serviceUuid, onDataReceived, (void<em>)red);
BleCharacteristic greenCharacteristic("green", BleCharacteristicProperty::WRITE_WO_RSP, green, serviceUuid, onDataReceived, (void</em>)green);
BleCharacteristic blueCharacteristic("blue", BleCharacteristicProperty::WRITE_WO_RSP, blue, serviceUuid, onDataReceived, (void*)blue);</p>
<p>// Add the characteristics
BLE.addCharacteristic(redCharacteristic);
BLE.addCharacteristic(greenCharacteristic);
BLE.addCharacteristic(blueCharacteristic);</p>
<p>// Advertising data
BleAdvertisingData advData;</p>
<p>// Add the RGB LED service
advData.appendServiceUUID(rgbService);</p>
<p>// Start advertising!
BLE.advertise(&amp;advData);
}</p>
<p>// loop() runs over and over again, as quickly as it can execute.
void loop() {</p>
<p>// Checks the publish flag,
// Publishes to a variable called "red" "green" and "blue"
if( m_publish ) {</p>
<p> // Reset flag
 m_publish = false;</p>
<p> // Publish to Mesh
 Mesh.publish("red", String::format("%d", m_led_level.red));
 Mesh.publish("green", String::format("%d", m_led_level.green));
 Mesh.publish("blue", String::format("%d", m_led_level.blue));
}</p>
<p>}</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial you learned how to add Bluetooth to a Particle Mesh project. As you can imagine, the possibilities are endless. For instance you can add user/administrative apps into the experience. <em>Now that's awesome.</em> ?</p>
<p>You can expect more content like this in my upcoming book: <strong><em>The Ultimate Guide to Particle Mesh</em></strong>. Subscribe to my list for updates and insider content. Plus all early subscribers get a discount when it's released! <a target="_blank" href="https://www.jaredwolff.com/the-ultimate-guide-to-particle-mesh">Click here to sign up.</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Protocol Buffer Bluetooth Low Energy Service Part 3 ]]>
                </title>
                <description>
                    <![CDATA[ By Jared Wolff This post is originally from www.jaredwolff.com In Part 1 we’ve learned the anatomy of a Protocol Buffer. In Part 2 we’ve learned how a Bluetooth Low Energy Service gets pieced together on the Nordic NRF52 SDK. This final post brings t... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-protocol-buffer-bluetooth-low-energy-service-part-3/</link>
                <guid isPermaLink="false">66d850524540581f6454411a</guid>
                
                    <category>
                        <![CDATA[ Bluetooth Low Energy ]]>
                    </category>
                
                    <category>
                        <![CDATA[ protocol-buffers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Tutorial ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 11 Jul 2019 16:02:24 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/07/Protocol-Buffers-Part-2-2.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Jared Wolff</p>
<p><strong>This post is originally from <a target="_blank" href="https://www.jaredwolff.com/how-to-protocol-buffer-bluetooth-low-energy-service-part-3/">www.jaredwolff.com</a></strong></p>
<p>In <a target="_blank" href="https://www.jaredwolff.com/how-to-define-your-own-bluetooth-low-energy-configuration-service-using-protobuf">Part 1</a> we’ve learned the anatomy of a Protocol Buffer. In <a target="_blank" href="https://www.jaredwolff.com/how-to-protocol-buffer-bluetooth-low-energy-service-part-2">Part 2</a> we’ve learned how a Bluetooth Low Energy Service gets pieced together on the Nordic NRF52 SDK. This final post brings together all the elements in an end-to-end working example.</p>
<p>Let’s get going!</p>
<p>P.S. this post is lengthy. If you want something to download, <a target="_blank" href="https://www.jaredwolff.com/files/how-to-define-a-protocol-buffer-ble-service-pdf/">click here for a a beautifully formatted PDF.</a> (Added bonus, the PDF has all three parts of this series!)</p>
<h2 id="heading-setting-everything-up">Setting Everything Up</h2>
<p><img src="https://www.jaredwolff.com/how-to-protocol-buffer-bluetooth-low-energy-service-part-3/images/Untitled-6880beab-c5c2-4184-9012-4a6e68bcebc8.png" alt="Goose attack" width="600" height="400" loading="lazy"></p>
<p>Go look at the <code>Readme.md</code> in each of the example repositories. (You will have to clone the repositories, <a target="_blank" href="https://www.jaredwolff.com/files/protobuf">sign up here to get the code</a>).</p>
<p>I’ve made the setup for the firmware dead simple. For the javascript side, it’s a bit more hairy but’ doable! I’ll also include the instructions here as well:</p>
<h3 id="heading-firmware-setup-for-mac">Firmware Setup (For Mac)</h3>
<ol>
<li>Initialize the full repository (there are submodules!): <code>git submodule update --init</code></li>
<li>Install <code>protoc</code> using Homebrew: <code>brew install protobuf</code></li>
<li>Run <code>make sdk</code>. This will download your SDK files.</li>
<li>Run <code>make tools_osx</code>. This will download your ARMGCC toolchain (for Mac). For other environments see below.</li>
<li>Run <code>make gen_key</code> once (and only once)! This will set up your key for DFU.</li>
<li>Run <code>make</code> and this will build your bootloader and main app.</li>
</ol>
<p><strong>Note:</strong> You only have to do steps 1-5 once.</p>
<h3 id="heading-javascript-app-setup-for-mac">Javascript App Setup (For Mac)</h3>
<p><strong>Prerequisite:</strong> you will need Xcode command line tools. You can get those <a target="_blank" href="https://developer.apple.com/download/more/">here</a>.</p>
<ol>
<li>Clone this repo to a place on your computer</li>
<li>Make sure you have <a target="_blank" href="https://github.com/nvm-sh/nvm/blob/master/README.md">nvm installed</a></li>
<li>Run <code>nvm install v8.0.0</code></li>
<li>Run <code>nvm install v10.15.3</code></li>
<li>Run <code>nvm use v8.0.0</code></li>
<li>Run <code>yarn</code> (if you don’t have yarn <code>npm install yarn -g</code>)</li>
<li>Once installed, run <code>nvm use v10.15.3</code></li>
<li>Then run <code>node index.js</code> to start the example</li>
</ol>
<p>Using NVM helps mitigate a compile issue with the Bluetooth library. You mileage may vary on this one.</p>
<h2 id="heading-how-the-heck-does-this-work">How the heck does this work?</h2>
<p>In this project, the Protocol Buffer has two functions. The first as a “command” to the bluetooth device. Secondly, a “response” to that command from the device.</p>
<p>Our example javascript app command uses “This is” as the payload. With the power of some string operations, let's make a full sentence out of it!</p>
<p>The series of events looks something like this:</p>
<ol>
<li>The test app connects and sends that data using our Bluetooth Low Energy service.</li>
<li>On the firmware decodes the message.</li>
<li>The firmware modifies the original data by adding “ cool.” Resulting in "This is cool"</li>
<li>The firmware encodes the payload and makes the data available for reading.</li>
<li>The app finally reads the characteristic, decodes and displays the result!</li>
</ol>
<p>Seems complicated but there's some benefits to this:</p>
<ol>
<li>In Protocol Buffers, data structures are well defined. This means that it has some great error checking abilities. If you're using any type of data structure you may have to code your own data validation.</li>
<li>Encoding and decoding is simple and straight forward. You can encode same data on different platforms and always get the same decoded result.</li>
</ol>
<p>Want to get the example up and running? Seeing is believing after all. Let's do this.</p>
<h2 id="heading-is-this-thing-on">Is This Thing On?</h2>
<p><img src="https://www.jaredwolff.com/how-to-protocol-buffer-bluetooth-low-energy-service-part-3/images/Untitled-c72fcf71-f0db-49e5-92d7-44fe601e75fa.png" alt="Phone in hand running NRF connect" width="600" height="400" loading="lazy"></p>
<p>Once you're done with the setup (above), let's get this firmware programmed! Lucky for you it's only two quick steps!</p>
<ol>
<li>Plug in your NRF52 DK board</li>
<li>Run <code>make flash_all</code>. (This compiles and flashes <em>all</em> the code for the project.)</li>
</ol>
<p>Once flashed, the easiest way to know if things are working is when Led 1 is blinking. That means it’s advertising and ready for a connection.</p>
<p><img src="https://www.jaredwolff.com/how-to-protocol-buffer-bluetooth-low-energy-service-part-3/images/Untitled-6c58e8c5-d005-4b61-a32b-29eea61b1ce7.png" alt="NRF52 DK" width="600" height="400" loading="lazy"></p>
<p>Let’s make sure it’s advertising correctly though. You can grab a tool like NRF Connect for <a target="_blank" href="https://itunes.apple.com/cn/app/nrf-connect/id1054362403?l=en">iOS</a> or Android. Or, you can grab LightBlue. (Also available for <a target="_blank" href="https://apps.apple.com/ru/app/lightblue-explorer/id557428110?l=en">iOS</a> and Android)</p>
<p>Open up one of the app and scan for advertising devices. You’ll be looking for <strong>Nordic_Buttonless</strong>.</p>
<p><img src="https://www.jaredwolff.com/how-to-protocol-buffer-bluetooth-low-energy-service-part-3/images/Untitled-062f5e84-ec22-4525-a591-4330a55fe30f.png" alt="BLE Apps scanning" width="600" height="400" loading="lazy"></p>
<p>Now let’s connect and double check our connectable services &amp; characteristics:</p>
<p><img src="https://www.jaredwolff.com/how-to-protocol-buffer-bluetooth-low-energy-service-part-3/images/Untitled-0466566a-1d8a-4e8b-a1c9-f2f76faa116b.png" alt="BLE Apps showing services" width="600" height="400" loading="lazy"></p>
<p>As you can see there are two services. One is Nordic’s DFU service. The other is our Protocol Buffer service!</p>
<p>You can compare <code>PROTOBUF_UUID_BASE</code> in <code>ble_protobuf.h</code> with the UUID of the “Unknown Service”. Nordic’s chips are Little Endian while the data here is Big Endian format. (i.e. you’ll have to reverse the bytes to see that they’re the same!)</p>
<p>You can even click in further to see the characteristics in NRF Connect. In the case of LightBlue, the characteristic UUIDs are already shown.</p>
<h2 id="heading-using-the-javascript-app">Using the Javascript App</h2>
<p>So, once we’re up and advertising, it’s time to run the app.</p>
<p><img src="https://www.jaredwolff.com/how-to-protocol-buffer-bluetooth-low-energy-service-part-3/images/Untitled-7e92193a-4dbe-4e08-9ebe-ee41be501580.png" alt="Bluetooth javascript app results" width="600" height="400" loading="lazy"></p>
<p>Simply change to the <code>ble-protobuf-js</code> directory and run <code>node index.js</code></p>
<p>If you've installed everything correctly, you should start seeing output like:</p>
<p>    example started
    scanning
    peripheral with ID 06f9b62ec5334454875b9f53d2f3fa74 found with name: Nordic_Buttonles</p>
<p>It should then immediately connect and send data to the device. It should receive the response immediately and print it to the screen.</p>
<p>    connected to Nordic_Buttonles
    Sent: This is
    Received: This is cool.</p>
<p>Bingo!</p>
<h2 id="heading-youve-made-it">You’ve made it!</h2>
<p>? Congrats if you made it this far through the tutorial. You should now be feeling confident enough to start playing with the software code. Maybe you’ll cook up something cool?</p>
<p>Here’s links to some resources that you may find handy.</p>
<ul>
<li><a target="_blank" href="https://developers.google.com/protocol-buffers">Protocol Buffer documentation</a></li>
<li><a target="_blank" href="https://www.github.com/nanopb/nanopb">NanoPB</a> - (the implementation of Protocol Buffers we used for this project)</li>
<li><a target="_blank" href="https://www.nordicsemi.com/DocLib/Content/SDK_Doc/nRF5_SDK/v15-2-0/index">Nordic SDK Documentation</a></li>
<li>Again, here are the links to <a target="_blank" href="https://www.jaredwolff.com/how-to-define-your-own-bluetooth-low-energy-configuration-service-using-protobuf">Part 1</a> and <a target="_blank" href="https://www.jaredwolff.com/how-to-protocol-buffer-bluetooth-low-energy-service-part-2">Part 2</a></li>
<li>If you haven’t already, <a target="_blank" href="https://www.jaredwolff.com/files/protobuf">get all the code for this project</a></li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Protocol Buffers are quite versatile and convenient. Using them with Bluetooth Low Energy service is one way that you can take advantage. As the connected world of IoT continues to expand, I have no doubt that we'll be seeing more use cases in the future!</p>
<p>I hope this has inspired you to incorporate Protocol Buffers into your own project(s). Make sure you <a target="_blank" href="https://www.jaredwolff.com/files/protobuf">download the sample code</a> and get started right now.</p>
<p>Has this tutorial been useful for you? Let me know what you think.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Improve Your Bluetooth Project With This Valuable Tool: Part 2! ]]>
                </title>
                <description>
                    <![CDATA[ By Jared Wolff This post is originally from www.jaredwolff.com. This is Part 2 of configuring your own Bluetooth Low Energy Service using a Nordic NRF52 series processor. If you haven’t seen Part 1 go back and check it out. I’ll be waiting right here... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/improve-your-bluetooth-project-with-this-valuable-tool-part-2/</link>
                <guid isPermaLink="false">66d8505dda89a73e2ddf57b2</guid>
                
                    <category>
                        <![CDATA[ Bluetooth Low Energy ]]>
                    </category>
                
                    <category>
                        <![CDATA[ protocol-buffers ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 01 Jul 2019 07:57:49 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/07/Protocol-Buffers-Part-2.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Jared Wolff</p>
<p><strong>This post is originally from <a target="_blank" href="https://www.jaredwolff.com/how-to-protocol-buffer-bluetooth-low-energy-service-part-2/">www.jaredwolff.com.</a></strong></p>
<p>This is Part 2 of configuring your own Bluetooth Low Energy Service using a Nordic NRF52 series processor. If you haven’t seen <a target="_blank" href="https://www.jaredwolff.com/how-to-define-your-own-bluetooth-low-energy-configuration-service-using-protobuf">Part 1</a> go back and check it out. I’ll be waiting right here..</p>
<p>If you’re with me thus far,  high five. ?</p>
<p>Let’s jump in!</p>
<p>So far we’ve created an efficient cross platform data structure using Protocol Buffers. This Protocol Buffer in particular can be used to send these defined data structures a Bluetooth Low Energy Service. In this part, I’ll show you the inner workings of creating the service from scratch.</p>
<p>P.S. this post is lengthy. If you want something to download, <a target="_blank" href="https://www.jaredwolff.com/files/how-to-define-a-protocol-buffer-ble-service-pdf/">click here for a a beautifully formatted PDF.</a> (Added bonus, the PDF has all three parts of this series!)</p>
<h2 id="heading-creating-the-service">Creating the Service</h2>
<p><img src="https://www.jaredwolff.com/how-to-protocol-buffer-bluetooth-low-energy-service-part-2/images/doorman.jpg" alt="Door Peoeple" width="600" height="400" loading="lazy"></p>
<p>Dealing with Bluetooth Low Energy in general can seem overwhelming. As I discussed <a target="_blank" href="https://www.jaredwolff.com/get-started-with-bluetooth-low-energy/">here</a>,  there’s a few moving parts that you need to keep in mind.</p>
<p>The best way to create a new service is to copy an already existing one! I’ve done this by:</p>
<ol>
<li>Go to the sdk -&gt; components -&gt; ble -&gt; ble_services -&gt; ble_bas</li>
<li>Copy <code>ble_bas.h</code> to <code>include/ble</code></li>
<li>Copy <code>ble_bas.c</code> to <code>src/ble</code></li>
</ol>
<p>I’ve then renamed them from <code>ble_bas</code> to <code>ble_protobuf</code> to be consistent. I’ve also done the same inside the files. (BAS is the battery level service used to report battery voltage or relative charge using a percentage)</p>
<p>I’ve also gone ahead and removed all the battery measurement  functions as they will be replaced.  This part of the process is fairly tedious and prone to error. If you’re new to the Nordic SDK, <a target="_blank" href="https://www.jaredwolff.com/files/protobuf/">I highly recommend you download the example code for this post.</a></p>
<h3 id="heading-adding-a-uuid">Adding a UUID</h3>
<p>Normally, for a vendor defined service you’ll have to use your own UUID. There are certain ranges of UUIDs that are reserved for the Bluetooth SIG. Supposedly you can also reserve your own UUID if you’re a member. <a target="_blank" href="https://stackoverflow.com/questions/10243769/what-range-of-bluetooth-uuids-can-be-used-for-vendor-defined-profiles">Here’s a handy post on Stack Overflow on the subject.</a></p>
<p>In our case, I’ve defined a UUID that I’ve used for other projects. If you go to <code>ble_protobuf.h</code> you can see the UUIDs for both the service and the characteristic:</p>
<pre><code class="lang-c"><span class="hljs-comment">// UUID for the Service &amp; Char</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> PROTOBUF_UUID_BASE   {0x72, 0x09, 0x1a, 0xb3, 0x5f, 0xff, 0x4d, 0xf6, \
                               0x80, 0x62, 0x45, 0x8d, 0x00, 0x00, 0x00, 0x00}</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> PROTOBUF_UUID_SERVICE     0xf510</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> PROTOBUF_UUID_CONFIG_CHAR (PROTOBUF_UUID_SERVICE + 1)</span>
</code></pre>
<p>Both <code>PROTOBUF_UUID_BASE</code> <code>PROTOBUF_UUID_SERVICE</code>  are used in <code>ble_protobuf_init</code> The last one is used in <code>command_char_add</code> (I’ll describe that a bit more below).</p>
<p>You can go without defining a BASE ID but I highly recommend you go the extra mile. That way your application will be impervious to future Bluetooth protocol updates.</p>
<h2 id="heading-creating-the-characteristic">Creating the Characteristic</h2>
<p><img src="https://www.jaredwolff.com/how-to-protocol-buffer-bluetooth-low-energy-service-part-2/images/man-641691_1280.jpg" alt="Open Those PICKLES!" width="600" height="400" loading="lazy"></p>
<p>Nordic has a fairly straight forward way of initializing separate characteristics in each service. For each characteristic, there is a <code>char_add</code> function which then configures and add the characteristic to the service.</p>
<h3 id="heading-configure-your-characteristic-withbleaddcharparamst">Configure your Characteristic with<code>ble_add_char_params_t</code></h3>
<p>Nordic has put the configurations parameters for a BLE Characteristic into a logical (and helpful) struct. If you’re developing on a different platform you may find the same settings though all not in one place! (Or handled logically… ?)</p>
<p>Here’s the breakdown of the struct:</p>
<pre><code class="lang-c"><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span>
{</span>
    <span class="hljs-keyword">uint16_t</span>                    uuid;
    <span class="hljs-keyword">uint8_t</span>                     uuid_type;
    <span class="hljs-keyword">uint16_t</span>                    max_len;
    <span class="hljs-keyword">uint16_t</span>                    init_len;
    <span class="hljs-keyword">uint8_t</span> *                   p_init_value;
    <span class="hljs-keyword">bool</span>                        is_var_len;
    <span class="hljs-keyword">ble_gatt_char_props_t</span>       char_props;
    <span class="hljs-keyword">ble_gatt_char_ext_props_t</span>   char_ext_props;
    <span class="hljs-keyword">bool</span>                        is_defered_read;
    <span class="hljs-keyword">bool</span>                        is_defered_write;
    <span class="hljs-keyword">security_req_t</span>              read_access;
    <span class="hljs-keyword">security_req_t</span>              write_access;
    <span class="hljs-keyword">security_req_t</span>              cccd_write_access;
    <span class="hljs-keyword">bool</span>                        is_value_user;
    <span class="hljs-keyword">ble_add_char_user_desc_t</span>    *p_user_descr;
    <span class="hljs-keyword">ble_gatts_char_pf_t</span>         *p_presentation_format;
} <span class="hljs-keyword">ble_add_char_params_t</span>;
</code></pre>
<p>It’s here that you tell the BLE Stack about what type of characteristic this is. In this example I use a handful of parameters to define the Protobuf Command Characteristic.</p>
<p><code>uuid</code> defines the address of how the characteristic will be accessed. If you’re defining your own service.
<code>max_len</code> is the maximum length of data that you may send though the characteristic. That’s why it’s important to set <code>max_size</code> in your Protocol Buffer <code>.options</code> file for all variable length parameters. Once you compile your Protocol Buffers you’ll get a <code>*_size</code> variable much like the one defined in <code>command.pb.h</code> Here's a snippet of what it looks like below:</p>
<pre><code class="lang-c"><span class="hljs-comment">/* Maximum encoded size of messages (where known) */</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> event_size                               67</span>
</code></pre>
<p>Thus when defining <code>ble_add_char_params_t</code> I set <code>max_len</code> to <code>event_size</code>:</p>
<pre><code class="lang-c">add_char_params.uuid                      = PROTOBUF_UUID_CONFIG_CHAR;
add_char_params.max_len                   = event_size;
add_char_params.is_var_len                = <span class="hljs-literal">true</span>;
</code></pre>
<p>Along the same lines, because we’re using a <em>string</em> as one of the parameters in the Protocol Buffer, this data can be of variable size.  <code>is_var_len</code> is handy to make sure that the right amount of bytes is sent and received. The decode function of the Protocol Buffers will fail if more data is fed in than necessary. (I learned this the hard way!)</p>
<p><code>char_props</code> define the permissions for the characteristic. If you’re familiar with file system permissions on a computer, this will be second nature to you. In this example, <strong>read</strong> and <strong>write</strong> is what we’re looking for.</p>
<p>Finally, parameters ending in <code>_access</code> determine the security type used. In most cases <code>SEC_OPEN</code> or <code>SEC_JUST_WORKS</code>is more than sufficient. If you’re handling critical data (passwords, etc) you may have to implement a second layer of encryption or enable a higher level security mode.</p>
<p>If you’re looking on more info about Bluetooth Low Energy security, <a target="_blank" href="https://duo.com/decipher/understanding-bluetooth-security">here’s a useful post on the subject.</a></p>
<h3 id="heading-add-dat-char">Add. dat. char.</h3>
<p>Once you’ve defined your params, it’s as easy as calling the <code>characteristic_add</code> function. This will associate this new characteristic with the related service. The first argument is the service handle, the second the configuration parameters and the third is a pointer to the handles for the characteristic. (See below)</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">uint32_t</span> <span class="hljs-title">characteristic_add</span><span class="hljs-params">(<span class="hljs-keyword">uint16_t</span>                   service_handle,
                            <span class="hljs-keyword">ble_add_char_params_t</span> *    p_char_props,
                            <span class="hljs-keyword">ble_gatts_char_handles_t</span> * p_char_handle)</span></span>
</code></pre>
<h2 id="heading-getting-it-running">Getting it Running</h2>
<p>Setting everything up inside <code>ble_protobuf.c</code> is 90% of the battle. The final mile requires some bits being added to  <code>services_init</code> in <code>main.c</code></p>
<pre><code class="lang-c">    <span class="hljs-keyword">ble_protobuf_init_t</span>       protobuf_init = {<span class="hljs-number">0</span>};

    protobuf_init.evt_handler          = ble_protobuf_evt_hanlder;
    protobuf_init.bl_rd_sec            = SEC_JUST_WORKS;
    protobuf_init.bl_cccd_wr_sec       = SEC_JUST_WORKS;
    protobuf_init.bl_wr_sec            = SEC_JUST_WORKS;

    err_code = ble_protobuf_init(&amp;m_protobuf,&amp;protobuf_init);
    APP_ERROR_CHECK(err_code);
</code></pre>
<p>The above allows events to be funneled back to the main context. That way your app becomes much more interactive with the core logic of your firmware code. In Nordic’s examples they’ve also brought the security parameters out so they can be defined in the main context as well.</p>
<p><em>Side note:</em> <code>m_protobuf</code> is defined using a macro from <code>ble_protobuf.h</code> It not only creates a static instance of the service but it also defines the callback that is used for handling events.</p>
<pre><code class="lang-c"><span class="hljs-comment">/**@brief Macro for defining a ble_protobuf instance.
 *
 * @param   _name  Name of the instance.
 * @hideinitializer
 */</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> BLE_PROTOBUF_DEF(_name)                          \
    static ble_protobuf_t _name;                         \
    NRF_SDH_BLE_OBSERVER(_name ## _obs,             \
                         BLE_PROTOBUF_BLE_OBSERVER_PRIO, \
                         ble_protobuf_on_ble_evt,        \
                         &amp;_name)</span>
</code></pre>
<p>If you're making your own service, you'll have to update the function name for the event handler. If you need to tweak priorities you can define/update that as well.</p>
<h2 id="heading-what-happens-when-this-characteristic-is-written-to">What happens when this characteristic is written to?</h2>
<p><img src="https://www.jaredwolff.com/how-to-protocol-buffer-bluetooth-low-energy-service-part-2/images/person-629676_1280.jpg" alt="Smoke Signal" width="600" height="400" loading="lazy"></p>
<p><code>ble_protobuf_on_ble_evt</code> is the main way that events are handled within Bluetooth Low Energy services. We’re most concerned with the <code>BLE_GATTS_EVT_WRITE</code> event but you can trigger on any GATT event that tickles your fancy.</p>
<p><code>on_write</code> is where the action happens. It takes the data that is written to the characteristic and decodes it according to <code>event_fields</code> It’s all put conveniently into a struct for additional processing, etc. If an error happens in decoding, <code>pb_decode</code> returns an error. Once modified, the data is encoded and made available for reading. Since reading <a target="_blank" href="https://www.jaredwolff.com/how-to-define-your-own-bluetooth-low-energy-configuration-service-using-protobuf">Part 1</a>, the calls to <code>pb_decode</code> and <code>pb_encode</code> should look very familiar!</p>
<p>Of course, you can have your firmware do whatever you want to. The Bluetooth Energy World is your oyster.</p>
<h2 id="heading-final-notes">Final Notes</h2>
<p>When adding new services to a Bluetooth Low Energy example, you may have to make some changes to the underlying code.</p>
<p>For example, <code>sdk_config.h</code> may need some changes. Particularly <code>NRF_SDH_BLE_VS_UUID_COUNT</code> needs to be increased depending how many service UUIDs are made available. For this project, I am also using the  DFU service (as it should be a default for all connected projects!!)</p>
<p>Another important aspect is memory and flash management. The default <code>.ld</code> file that comes with the BLE DFU service may not be sufficient for another BLE Service. The only way you’ll know there’s not enough is when you compile and flash it to a NRF52 device. If the device boots up stating there’s not enough memory, you’ll have to make the suggested changes. The error will show up on the debug console where this message normally shows up:</p>
<pre><code class="lang-bash">&lt;info&gt; app: Setting vector table to bootloader: 0x00078000
&lt;info&gt; app: Setting vector table to main app: 0x00026000
</code></pre>
<p>Learn more about how to get the Debug Console set up <a target="_blank" href="https://www.jaredwolff.com/files/protobuf/">in the example code here.</a></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this part I’ve shown you the inner workings of a custom Bluetooth Low Energy service using Protocol Buffers. In the last part, I’ll show you how to load the firmware, run the example javascript app and test our freshly developed Protocol Buffer!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ UPDATED: Improve Your Bluetooth Project With This Valuable Tool ]]>
                </title>
                <description>
                    <![CDATA[ By Jared Wolff This post is originally from www.jaredwolff.com One of the most confusing things about Bluetooth Low Energy is how data is moved around. Depending on your application, your device state may be fairly complex. That means having an indiv... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/improve-your-bluetooth-project-with-this-valueable-tool/</link>
                <guid isPermaLink="false">66d8505ec15439a8d5631e81</guid>
                
                    <category>
                        <![CDATA[ Bluetooth Low Energy ]]>
                    </category>
                
                    <category>
                        <![CDATA[ protocol-buffers ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 11 Jun 2019 15:05:54 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/06/protobuf.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Jared Wolff</p>
<p><strong>This post is originally from <a target="_blank" href="https://www.jaredwolff.com/how-to-define-your-own-bluetooth-low-energy-configuration-service-using-protobuf/">www.jaredwolff.com</a></strong></p>
<p>One of the most confusing things about Bluetooth Low Energy is how data is moved around. Depending on your application, your device state may be fairly complex. That means having an individual endpoint for every piece of data is suicide by Bluetooth.</p>
<p>So, what’s he solution?</p>
<p><a target="_blank" href="https://developers.google.com/protocol-buffers/">Protocol Buffers.</a></p>
<p>A protocol buffer is a programatic way to encode/decode optimized structured data. They can be shared and manipulated across almost any platform. Nordic actually uses a variant of it for their DFU service.</p>
<p>There was a lot of buzz words in the first few sentences. Hopefully, by the end of this post you’ll understand exactly what I’m talking about.</p>
<p>In this tutorial, i'll include fully flushed out example code that you can clone and start using immediately. All you need is one of these:</p>
<p><img src="https://www.jaredwolff.com/how-to-define-your-own-bluetooth-low-energy-configuration-service-using-protobuf/images/DSC01544.jpg" alt="NRF52 Development Kit" width="730" height="487" loading="lazy"></p>
<p>So, how do you use this magical software?</p>
<p>Read on!</p>
<p>P.S. this post is lengthy. If you want something to download, <a target="_blank" href="https://www.jaredwolff.com/files/how-to-define-a-protocol-buffer-ble-service-pdf/">click here for a a beautifully formatted PDF.</a> (Added bonus, the PDF has all three parts of this series!)</p>
<h2 id="heading-install">Install</h2>
<p>The first part of the process is to make sure you’ve installed all the correct utilities.  Depending on what programming language will determine what you install and use. In this case I’ll outline the utilities that I have used for several projects in the past using C, Go and Javascript.</p>
<p><code>protoc</code> is the most important utility you’ll have to install here. It's the Protobuf "compiler" which takes your <code>.proto</code> and <code>.options</code> files and turns them into static code.</p>
<ol>
<li>For Mac, download the appropriate release <a target="_blank" href="https://github.com/google/protobuf/releases">here</a>.</li>
<li>Unzip the folder</li>
<li>Run <code>./autogen.sh &amp;&amp; ./configure &amp;&amp; make</code> in the folder</li>
<li>If you get an error <code>autoreconf: failed to run aclocal: No such file or directory</code> install <code>autoconf</code> using Homebrew:</li>
</ol>
<p><code>brew install autoconf &amp;&amp; brew install automake</code></p>
<p>Then, re-run step 3.</p>
<ol>
<li>Then run:</li>
</ol>
<pre><code>make check
sudo make install
which protoc
</code></pre><p>Consider <code>protoc</code> the compiler for Protocol Buffers. It can either output raw files or libraries directly. That’s because it’s got Go support built in.</p>
<p>That raw data can also be used to generate static libraries for other languages. That usually requires an extra utility (or utilities). I describe the two that the Dondi Lib project used below.</p>
<ol start="2">
<li><p><code>nanopb</code> is a python script used to create C libraries that encode/decode your structured data.
It can be installed by navigating to the <a target="_blank" href="https://github.com/nanopb/nanopb">nanopb git repo</a> and downloading the appropriate files. The most important pieces to include:</p>
</li>
<li><p><code>pb_encode.c</code>, <code>pb_decode.c</code> and <code>pb_common.c</code></p>
</li>
<li><code>/generator/nanopb_generator.py</code></li>
<li><p>And the <code>/generator/nanopb/</code>directory co-located with <code>nanopb_generator.py</code></p>
<p> <code>nanopb</code> is meant for deployment on embedded platforms. It's different from <code>protoc-c</code> (the regular C variant) because it is optimized for resource constrained systems like embedded processors. Buffers have finite sizes. There's no memory allocation! Depending on if there's bi-directional communication, you can only import and use the encoding functionality or decoding functionality.</p>
</li>
<li><p><code>pbjs</code> uses the output from <code>protoc</code> to generate a static javascript library. This is powerful because you can then use it in any javascript application. The best way to install <code>pbjs</code> is by running:</p>
<pre><code>   npm install -g protobufjs
</code></pre></li>
</ol>
<p>I've simplified this step a bit in the example code. <a target="_blank" href="https://www.jaredwolff.com/files/protobuf/">Get started by cloning the repos here.</a></p>
<h2 id="heading-setting-up-the-protocol-buffer">Setting up the protocol buffer</h2>
<p>Create a file called <code>command.proto</code>. You can make the contents of that file what's below:</p>
<pre><code>syntax = <span class="hljs-string">"proto3"</span>;

message event {
  enum event_type {
    command = <span class="hljs-number">0</span>;
    response = <span class="hljs-number">1</span>;
  }
  event_type type = <span class="hljs-number">1</span>;
  string message = <span class="hljs-number">2</span>;
}
</code></pre><p>It may look foreign at first but once you take a deeper look, it’s not that much different than a standard C struct or hash table.</p>
<p>I'm using two types of data in this example: a <code>string</code> and <code>enum</code> as a type. There are actually a few more which you can read up at the <a target="_blank" href="https://developers.google.com/protocol-buffers/docs/proto">documentation</a>. When compiled, the equivalent c struct looks like:</p>
<pre><code><span class="hljs-comment">/* Struct definitions */</span>
typedef struct _event {
    event_event_type type;
    char message[<span class="hljs-number">64</span>];
<span class="hljs-comment">/* @@protoc_insertion_point(struct:event) */</span>
} event;
</code></pre><p>Where <code>event_event_type</code> is</p>
<pre><code><span class="hljs-comment">/* Enum definitions */</span>
typedef enum _event_event_type {
    event_event_type_command = <span class="hljs-number">0</span>,
    event_event_type_response = <span class="hljs-number">1</span>
} event_event_type;
</code></pre><p>You can nest as many messages inside each other as your hearts content. Typically though, a message is as small as possible so data transmission is as efficient as possible. This is particularly important for resource constrained systems or LTE deployments where you're charged for <em>every</em> megabyte used. <strong>Note:</strong> when elements are not used or defined they are typically <em>not</em> included in the encoded Protocol Buffer payload.</p>
<p>Normally, when you create a generic message like this, there is no limit to the size of the string <code>message</code>. That option can be set in the <code>.options</code> file:</p>
<pre><code>event.message    max_size:<span class="hljs-number">64</span>
</code></pre><p>This way, the memory can be statically allocated in my microprocessor code at compile time. If the message size is greater than 64 bytes then it will get chopped off in the code (or you'll simply get an error during decode). It's up to you, the software engineer, to figure out the absolute maximum amount of bytes  (or characters) that you may need for this type of data.</p>
<p>You can look at more of the <code>nanopb</code> related features at <a target="_blank" href="https://jpa.kapsi.fi/nanopb/docs/concepts.html">their documentation.</a></p>
<h2 id="heading-generating-the-appropriate-static-libraries">Generating the appropriate static libraries</h2>
<p>In order to make this as easy as possible, I put all the following code into a Makefile. When you make a change to the Protocol Buffer, that every library for every language used gets generated.</p>
<p>If we want to generate a static Go file the command looks like:</p>
<pre><code class="lang-bash">protoc -I&lt;directory with .proto&gt; --go_out=&lt;output directory&gt; command.proto
</code></pre>
<p>If you've installed the nanopb plugin, you can do something similar to generate C code:</p>
<pre><code class="lang-bash">protoc -I&lt;directory with .proto&gt; -ocommand.pb command.proto
&lt;path&gt;/&lt;to&gt;/protogen/nanopb_generator.py -I&lt;directory with .proto&gt; <span class="hljs-built_in">command</span>
</code></pre>
<p>The first file creates a generic "object" file. The second actually creates the static C library.</p>
<p>For javascript:</p>
<pre><code class="lang-bash">pbjs -t static-module -p&lt;directory with .proto&gt; command.proto &gt; command.pb.js
</code></pre>
<p>You can test each of these commands with the <code>.proto</code> and <code>.options</code> file examples above. I also built this manual process into one command in the example repository. <a target="_blank" href="https://www.jaredwolff.com/files/protobuf/">Get access here.</a></p>
<h2 id="heading-encoding-and-decoding">Encoding and Decoding</h2>
<p><img src="https://www.jaredwolff.com/how-to-define-your-own-bluetooth-low-energy-configuration-service-using-protobuf/images/data-3432628_1920.jpg" alt="Encoding" width="730" height="493" loading="lazy"></p>
<p>In the examples below, I show you how to use your freshly compiled static code! This is where the fun begins.</p>
<h3 id="heading-encoding-using-javascript">Encoding using Javascript</h3>
<p>Here’s a typical flow that you can follow when using a statically generated javascript library. First, initialize the library.</p>
<pre><code><span class="hljs-comment">// Import the config message</span>
<span class="hljs-keyword">var</span> protobuf  = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./command.pb.js'</span>);
</code></pre><p>Then create an instance of <code>event</code>:</p>
<pre><code><span class="hljs-comment">// setup command</span>
<span class="hljs-keyword">var</span> event = protobuf.event.create();
event.type = protobuf.event.event_type.command;
event.message = <span class="hljs-string">"This is"</span>;
</code></pre><p>Then, compile the payload. i.e. turn human readable JSON into nicely packed binary. See below.</p>
<pre><code><span class="hljs-comment">// make sure it's valid</span>
<span class="hljs-keyword">var</span> err = protobuf.event.verify(event);
<span class="hljs-keyword">if</span>( err != <span class="hljs-literal">null</span> ) {
   <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"verify failed: "</span> + err);
   <span class="hljs-keyword">return</span>;
}
</code></pre><p>You'll get errors during this step if your object is malformed or if if you are missing <code>required</code> elements. I don't recommend using the <code>required</code> prefix when defining your <code>.proto</code> file. Any checks for required elements can be easily done in your application code.</p>
<p>Finally, the last step is to encode and turn it into raw bytes:</p>
<pre><code><span class="hljs-comment">// encode into raw bytes</span>
<span class="hljs-keyword">var</span> payload = protobuf.event.encode(event).finish();
</code></pre><p>You can then use payload and send it over BLE, HTTP or whatever. If there's a communication protocol, you can send this buffer over it!</p>
<h3 id="heading-decoding-in-c">Decoding in C</h3>
<p>Once the data is received it’s decoded on the embedded end. <code>nanopb</code> is confusing. But luckily I have some code here that will work for you:</p>
<pre><code><span class="hljs-comment">// Setitng up protocol buffer data</span>
event evt;

<span class="hljs-comment">// Read in buffer</span>
pb_istream_t istream = pb_istream_from_buffer((pb_byte_t *)data, data_len);

<span class="hljs-keyword">if</span> (!pb_decode(&amp;istream, event_fields, &amp;evt)) {
   NRF_LOG_ERROR(<span class="hljs-string">"Unable to decode: %s"</span>, PB_GET_ERROR(&amp;istream));
   <span class="hljs-keyword">return</span>;
}

<span class="hljs-comment">// Validate code &amp; type</span>
<span class="hljs-keyword">if</span>( evt.type != event_event_type_command ) {
   <span class="hljs-keyword">return</span>;
}
</code></pre><p>First, you create an input stream based on the raw data and the size of the data.</p>
<p>Then, you use the <code>pb_decode</code> function. You point the first argument to the input stream. The second to the definition of our Protocol Buffer we’ve been working with. It's located in the <code>command.pb.h</code> file.</p>
<pre><code><span class="hljs-comment">/* Defines for backwards compatibility with code written before nanopb-0.4.0 */</span>
#define event_fields &amp;event_msg
</code></pre><p>The last argument is a pointer to the struct to put the decoded data into. (In this case it's <code>evt</code> defined right before <code>pb_istream_from_buffer</code> above).</p>
<h3 id="heading-encoding-in-c">Encoding in C</h3>
<p>Let's now say we're going to reply to the message that was just decoded above. So now we have to create data, encode it and send it back. Here's the process:</p>
<pre><code><span class="hljs-comment">// Encode value</span>
pb_byte_t output[event_size];

<span class="hljs-comment">// Output buffer</span>
pb_ostream_t ostream = pb_ostream_from_buffer(output,sizeof(output));

<span class="hljs-keyword">if</span> (!pb_encode(&amp;ostream, event_fields, &amp;evt)) {
   NRF_LOG_ERROR(<span class="hljs-string">"Unable to encode: %s"</span>, PB_GET_ERROR(&amp;ostream));
   <span class="hljs-keyword">return</span>;
}
</code></pre><p>First create a buffer that holds the maximum amount of bytes that your Protocol buffer takes up. This is also defined in your <code>command.pb.h</code>. In this case <code>event_size</code> is set to <code>67</code>. Then, similarly to the decode command, you create a stream and connect it to your buffer. Then finally encode the data by pointing your <code>evt</code> struct along with the stream and <code>event_fields</code>.</p>
<p>As long as <code>pb_encode</code> returns without error, the encoded data has been written to <code>output</code>! The structure can be variable length so the best way to handle when sending it is to get the <code>bytes_written</code> from <code>ostream</code>:</p>
<pre><code>NRF_LOG_INFO(<span class="hljs-string">"bytes written %d"</span>,ostream.bytes_written);
</code></pre><h2 id="heading-conclusion">Conclusion</h2>
<p>Nice you made it! I hope you're starting to grasp the power of Protocol Buffers. Don't worry, it took me a little while to figure it all out. <em>You too can be a Protocol Buffer master!</em> ?</p>
<p>If you're not too thrilled with Protocol Buffers, there are other alternatives. I've used <a target="_blank" href="https://msgpack.org">MessagePack</a> with some success on previous products. It's straightforward and has tons of support for a majority of programming languages.</p>
<p>If you are interested how to roll this into a Bluetooth Low Energy project, stay tuned for Part Two. In Part two, I’ll show you how to set up a very simple Bluetooth Service and Characteristic that will be used to transfer our freshly encoded data to-and-fro.</p>
<p>Also, if you want to see all the code in action, <a target="_blank" href="https://www.jaredwolff.com/files/protobuf/">you can download everything here.</a></p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
