<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: DMJ Jones</title>
    <description>The latest articles on DEV Community by DMJ Jones (@dmj_jones_8ec54a3564709a8).</description>
    <link>https://dev.to/dmj_jones_8ec54a3564709a8</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1928613%2F5241db88-d55d-4692-a5b5-a058ad3a6ccf.jpg</url>
      <title>DEV Community: DMJ Jones</title>
      <link>https://dev.to/dmj_jones_8ec54a3564709a8</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dmj_jones_8ec54a3564709a8"/>
    <language>en</language>
    <item>
      <title>I co-engineered a macOS Soundcore headphone controller with Claude over a weekend</title>
      <dc:creator>DMJ Jones</dc:creator>
      <pubDate>Mon, 14 Sep 2026 19:54:11 +0000</pubDate>
      <link>https://dev.to/dmj_jones_8ec54a3564709a8/i-co-engineered-a-macos-soundcore-headphone-controller-with-claude-over-a-weekend-216g</link>
      <guid>https://dev.to/dmj_jones_8ec54a3564709a8/i-co-engineered-a-macos-soundcore-headphone-controller-with-claude-over-a-weekend-216g</guid>
      <description>&lt;p&gt;My Soundcore Space 2 pairs with my MacBook and sounds exactly as it should. But&lt;br&gt;
the moment I want to turn noise cancelling off, or pull the bass back, I have to&lt;br&gt;
pick up my phone and open the Soundcore app — because Anker only ships that app&lt;br&gt;
for Android and iOS.&lt;/p&gt;

&lt;p&gt;The headphones are right there. The Mac is right there. The controls are on a&lt;br&gt;
third device.&lt;/p&gt;

&lt;p&gt;So over a single weekend, I decided to pair-program and co-engineer a solution with&lt;br&gt;
Claude to see if we could reverse-engineer the protocol and build&lt;br&gt;
&lt;a href="https://github.com/mervin008/soundcorebridge" rel="noopener noreferrer"&gt;SoundcoreBridge&lt;/a&gt;: a native macOS menu bar app&lt;br&gt;
and CLI that speaks the same protocol. And it actually worked.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwcdbcd75fti6uho72fjm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwcdbcd75fti6uho72fjm.png" alt="The SoundcoreBridge panel" width="800" height="924"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the story of how that weekend went, what co-engineering low-level hardware&lt;br&gt;
code with an LLM looks like in practice, and the two occasions I confidently&lt;br&gt;
concluded macOS was broken when the bug was mine.&lt;/p&gt;
&lt;h2&gt;
  
  
  Finding the door
&lt;/h2&gt;

&lt;p&gt;Bluetooth devices advertise their services over SDP. Dumping the Space 2's&lt;br&gt;
records shows the usual audio profiles — A2DP, AVRCP, handsfree — and then some&lt;br&gt;
more interesting entries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ch 30   PaulWang-Spp   0cf12d31-fac3-4553-bd80-d6832e7d1402   &amp;lt;- vendor control
ch 17   RFCOMM COM     df21fe2c-2515-4fdb-8886-f12c4d67927c
ch 12   TOTA           0x1101                                  &amp;lt;- firmware OTA
ch 13   BESOTA         66666666-...                            &amp;lt;- chipset OTA
ch 16   IOSSPP         00000000-deca-fade-deca-deafdecacaff     &amp;lt;- Apple iAP2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That first UUID shares its prefix (&lt;code&gt;0cf12d31-fac3-4553-bd80-&lt;/code&gt;) with the&lt;br&gt;
documented Soundcore control UUID; the suffix is per-model. Channel 30 is the&lt;br&gt;
door.&lt;/p&gt;

&lt;p&gt;Channels 12 and 13 flash firmware. Those are hard-blocked in my code by service&lt;br&gt;
identity, and I'd suggest anyone doing this does the same before writing a&lt;br&gt;
single byte.&lt;/p&gt;
&lt;h2&gt;
  
  
  The frame format
&lt;/h2&gt;

&lt;p&gt;Prior art from other Soundcore projects gave me the shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;08 EE 00 00 | 00 | 06 81 | 10 00 | 00 5F 02 00 00 01 | EF
└─ magic ─┘  seq  └ cmd ┘ └ len ┘ └───  payload  ──┘  checksum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two-byte command, little-endian total length, and a checksum that's just the sum&lt;br&gt;
of every preceding byte truncated to 8 bits. Device replies swap the magic to&lt;br&gt;
&lt;code&gt;09 FF 00 00&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Before touching hardware I implemented the codec and checked it against six&lt;br&gt;
packets documented for other models. All six reproduced byte-for-byte. That&lt;br&gt;
five-minute investment meant that later, when nothing worked, I could rule out&lt;br&gt;
the encoder completely.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two days of "macOS blocks this"
&lt;/h2&gt;

&lt;p&gt;Then I tried to open the channel, and nothing happened.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;openRFCOMMChannelAsync&lt;/code&gt; returned success and the delegate callback never fired.&lt;br&gt;
The synchronous variant returned &lt;code&gt;kIOReturnError&lt;/code&gt; immediately. I tried channel&lt;br&gt;
30, then 17, then channel 1 (handsfree — definitely exists), then channel 2&lt;br&gt;
(nothing advertised). Identical failure every time.&lt;/p&gt;

&lt;p&gt;So I ran what I thought was a decisive control experiment: I tried opening&lt;br&gt;
RFCOMM to a &lt;strong&gt;completely different device&lt;/strong&gt; — an Android phone, on standard&lt;br&gt;
services like OBEX and phonebook access. Same failure.&lt;/p&gt;

&lt;p&gt;Two devices, seven channels, standard services, from both a bare binary and a&lt;br&gt;
signed &lt;code&gt;.app&lt;/code&gt;. &lt;code&gt;bluetoothd&lt;/code&gt; logs showed no connect attempt ever reaching the air.&lt;br&gt;
I checked TCC (granted), App Sandbox (not sandboxed), the baseband link&lt;br&gt;
(connected). I wrote it up as a platform limitation and started designing a&lt;br&gt;
Raspberry Pi bridge.&lt;/p&gt;

&lt;p&gt;I was wrong.&lt;/p&gt;

&lt;p&gt;Claude — who I was pair-programming with throughout the weekend — pushed back on my&lt;br&gt;
theory and surfaced &lt;a href="https://github.com/AmitRajput-Dev/SonyBridge" rel="noopener noreferrer"&gt;SonyBridge&lt;/a&gt;, an&lt;br&gt;
open-source project doing exactly this on macOS. It exists. It works. That single&lt;br&gt;
pointer broke my confirmation bias. I read its implementation, reproduced its&lt;br&gt;
approach in a standalone spike with Claude, and the channel opened.&lt;/p&gt;

&lt;p&gt;Diffing my spike against my app turned up three things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The first &lt;code&gt;openRFCOMMChannelAsync&lt;/code&gt; in a process always fails.&lt;/strong&gt; It appears&lt;br&gt;
to lazily initialise IOBluetooth's run-loop source, and the delegate never fires&lt;br&gt;
for that attempt. A retry succeeds. My spike had accidentally made two throwaway&lt;br&gt;
attempts against the phone before the one that worked — which is also why my&lt;br&gt;
"control experiment" failed identically on both devices. It was measuring my own&lt;br&gt;
bug twice and I read it as corroboration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. IOBluetooth delivers delegate callbacks on the run loop of the thread that&lt;br&gt;
opened the channel&lt;/strong&gt; — not on a dispatch queue. I was awaiting a Swift&lt;br&gt;
continuation, which never sees them. You have to pump the run loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;pump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;TimeInterval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;until&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addingTimeInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;deadline&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;RunLoop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;until&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addingTimeInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;until&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is also why CoreBluetooth worked fine for me all along while IOBluetooth&lt;br&gt;
never did — CoreBluetooth uses a dispatch queue. I had that evidence and read it&lt;br&gt;
backwards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Never close a failed attempt.&lt;/strong&gt; The close lands asynchronously and tears&lt;br&gt;
down whichever channel succeeds next.&lt;/p&gt;
&lt;h2&gt;
  
  
  Reads work. Writes vanish.
&lt;/h2&gt;

&lt;p&gt;With the channel open, &lt;code&gt;01:01&lt;/code&gt; returns 103 bytes, and there's readable ASCII in&lt;br&gt;
there straight away — &lt;code&gt;01.59&lt;/code&gt;, &lt;code&gt;1402&lt;/code&gt;, and the headset's own MAC. Firmware,&lt;br&gt;
model, serial.&lt;/p&gt;

&lt;p&gt;Writes were another matter. I sent what I was sure was the noise-cancelling&lt;br&gt;
command and got nothing. No error. No NAK. No state change. The device&lt;br&gt;
acknowledged reads perfectly and acted as though writes had never arrived.&lt;/p&gt;

&lt;p&gt;I burned a lot of time guessing command bytes. What actually solved it was&lt;br&gt;
capturing the Android app's own traffic: enable &lt;strong&gt;Bluetooth HCI snoop log&lt;/strong&gt; in&lt;br&gt;
developer options, drive the app, pull a bug report, and scan the capture for&lt;br&gt;
the frame magic.&lt;/p&gt;

&lt;p&gt;The capture showed a handshake the app performs on connect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;01:01                    device info
05:01  payload 01        capability table
05:81  (no payload) x2
05:81  payload 01
05:81  (no payload)
18:85  payload 01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Until that runs, the device discards every write in silence. That's not&lt;br&gt;
documented anywhere I could find, and I don't think I'd ever have guessed it.&lt;/p&gt;

&lt;p&gt;The same capture caught something subtler. The sound-mode write is &lt;code&gt;06:81&lt;/code&gt; with&lt;br&gt;
payload &lt;code&gt;[mode, level, 02, 00, 00, 01]&lt;/code&gt;. I'd been building that payload by&lt;br&gt;
reading the current block back and modifying it — but the &lt;strong&gt;read returns &lt;code&gt;FF&lt;/code&gt; in&lt;br&gt;
byte 2 where the write requires &lt;code&gt;02&lt;/code&gt;&lt;/strong&gt;. Echoing back what you read gets you&lt;br&gt;
silently ignored. That single byte was most of a day.&lt;/p&gt;
&lt;h2&gt;
  
  
  Mapping the rest by diffing
&lt;/h2&gt;

&lt;p&gt;Once writes worked, the remaining fields fell out of a simple loop: hold the&lt;br&gt;
channel open from the Mac, poll the state blob, change one setting on the phone,&lt;br&gt;
and diff the bytes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CHANGED 01:01
  byte[71] (0x47): 02 → 01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the ANC mode byte. Same method found the EQ preset at byte 23, the eight&lt;br&gt;
band values at 25–32, and — after an embarrassing detour — the ANC strength in&lt;br&gt;
the &lt;strong&gt;high nibble of byte 72&lt;/strong&gt;, not byte 70 as I first assumed. Byte 70 is a&lt;br&gt;
constant &lt;code&gt;05&lt;/code&gt;: the number of levels supported, not the level selected.&lt;/p&gt;

&lt;p&gt;Battery is byte 0, and it reads 0–9 rather than a percentage. That's Apple's&lt;br&gt;
accessory convention (&lt;code&gt;AT+IPHONEACCEV&lt;/code&gt;): percent is &lt;code&gt;(level + 1) × 10&lt;/code&gt;. I only&lt;br&gt;
worked that out because the raw byte said &lt;code&gt;5&lt;/code&gt; while both macOS and the phone app&lt;br&gt;
said 60%.&lt;/p&gt;
&lt;h2&gt;
  
  
  The official app has a bug
&lt;/h2&gt;

&lt;p&gt;While verifying the strength levels I found something I didn't expect.&lt;/p&gt;

&lt;p&gt;I set level 4 from the Mac, reconnected the phone, and watched the exchange. The&lt;br&gt;
app asked the headset for its sound mode. The headset replied truthfully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;11:46:10  app  → 06:01
11:46:10  dev  → 00 4F FF 00 00 01     (level 4)
          app displays: 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It queries, receives the correct answer, and renders its own cached value&lt;br&gt;
instead. So if you change anything from another host, Anker's app lies to you&lt;br&gt;
about the current state. Mine reads it from the device.&lt;/p&gt;
&lt;h2&gt;
  
  
  Making it work for more than one model
&lt;/h2&gt;

&lt;p&gt;The frame format is identical across Soundcore devices. What differs is where&lt;br&gt;
fields sit in the state blob, which value means which sound mode, and how many&lt;br&gt;
EQ bands there are. So a device is a data structure, not new protocol code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;space2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DeviceProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;modelCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"1402"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Soundcore Space 2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;batteryOffset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ancModeOffset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;71&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ancLevelOffset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;eqPresetOffset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;eqBandsStart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;eqBandCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;battery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;soundMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ancLevel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;equaliser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customEQ&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unknown models resolve to a read-only profile: battery and firmware, no writes.&lt;br&gt;
I own exactly one Soundcore device, so that's the only one whose offsets I've&lt;br&gt;
confirmed. Shipping guessed offsets to someone else's headphones isn't a risk&lt;br&gt;
worth taking for the sake of a longer compatibility table.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell myself at the start
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Verify by reading back, not by absence of an error.&lt;/strong&gt; Every real bug in this&lt;br&gt;
project was silent. The device never once told me I was wrong — it just ignored&lt;br&gt;
me. &lt;code&gt;confirmed: byte[71] = 0x00&lt;/code&gt; is worth more than any number of successful&lt;br&gt;
writes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check that your control experiment isn't measuring the same bug.&lt;/strong&gt; Mine tested&lt;br&gt;
a second device and gave me false confidence, because the defect was in the code&lt;br&gt;
common to both paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prior art is the cheapest debugging tool there is.&lt;/strong&gt; I'd written off the whole&lt;br&gt;
platform. One link to a project that already did it turned two days of wrong&lt;br&gt;
conclusions around in an hour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ground your AI pairing in real packet captures.&lt;/strong&gt; Claude excelled at spotting&lt;br&gt;
byte patterns, generating Swift codecs, and writing offline tests, but only&lt;br&gt;
because every prompt was grounded in real hex dumps and HCI logs. Without ground-truth&lt;br&gt;
captures, pair-programming on hardware quickly turns into mutual guesswork.&lt;/p&gt;




&lt;p&gt;SoundcoreBridge is MIT, Swift, macOS 13+. The full protocol map — state blob&lt;br&gt;
layout, handshake, all 22 EQ presets with band values, and the IOBluetooth traps&lt;br&gt;
— is in the repo, so if you'd rather write your own client than use mine, the&lt;br&gt;
hard part is already done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/mervin008/soundcorebridge" rel="noopener noreferrer"&gt;github.com/mervin008/soundcorebridge&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not affiliated with Anker or Soundcore. Firmware-update channels are blocked in&lt;br&gt;
code; the protocol work is interoperability reverse engineering on hardware I own.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>swift</category>
      <category>ai</category>
      <category>bluetooth</category>
      <category>reverseengineering</category>
    </item>
    <item>
      <title>Ai apps at i3</title>
      <dc:creator>DMJ Jones</dc:creator>
      <pubDate>Fri, 06 Dec 2024 15:16:30 +0000</pubDate>
      <link>https://dev.to/dmj_jones_8ec54a3564709a8/ai-apps-at-i3-3bo</link>
      <guid>https://dev.to/dmj_jones_8ec54a3564709a8/ai-apps-at-i3-3bo</guid>
      <description></description>
      <category>ai</category>
    </item>
  </channel>
</rss>
