{"id":1055,"date":"2026-09-10T16:07:14","date_gmt":"2026-09-10T16:07:14","guid":{"rendered":"https:\/\/boochlin.com\/?p=1055"},"modified":"2026-09-11T09:17:41","modified_gmt":"2026-09-11T09:17:41","slug":"android-%e7%88%9b%e8%a3%9d%e7%bd%ae%e6%83%b3%e8%b7%91%e4%ba%ba%e8%87%89%e8%be%a8%e8%ad%9814-%e9%96%80%e7%a6%81%e6%a9%9f%e7%95%b6-ble-peripheral-%e7%9a%84%e7%8f%be%e5%a0%b4%e8%b8%a9%e5%9d%91","status":"publish","type":"post","link":"https:\/\/boochlin.com\/?p=1055","title":{"rendered":"Android \u721b\u88dd\u7f6e\u60f3\u8dd1\u4eba\u81c9\u8fa8\u8b5814 \u2013 \u9580\u7981\u6a5f\u7576 BLE Peripheral \u7684\u73fe\u5834\u8e29\u5751\u8a18"},"content":{"rendered":"\n<p>\u5728\u908a\u7de3\u5de5\u63a7\u5834\u666f\u4e2d\uff0c\u6709\u4e00\u6b3e\u578b\u865f\u53eb <strong>Mkspure<\/strong>\u3002\u9019\u662f\u4e00\u53f0\u5c08\u70ba\u56b4\u82db\u74b0\u5883\u8a2d\u8a08\u7684\u7d14\u786c\u9ad4\u8a2d\u5099\uff1a<strong>\u6c92\u6709\u87a2\u5e55\u3001\u6c92\u6709\u9375\u76e4\u3001\u96f6\u5be6\u9ad4\u6309\u9375<\/strong>\u3002<\/p>\n\n\n\n<p>\u8a2d\u5099\u88ab\u91d1\u5c6c\u652f\u67b6\u6b7b\u6b7b\u91d8\u5728\u5169\u516c\u5c3a\u9ad8\u7684\u7acb\u67f1\u4e0a\u3002\u4e00\u65e6\u5de5\u5ee0\u73fe\u5834\u66f4\u63db\u4e86 WiFi \u57fa\u5730\u53f0\u3001\u6539\u4e86\u5bc6\u78bc\uff0c\u6216\u8005\u9700\u8981\u8a2d\u5b9a\u56fa\u5b9a IP\uff0c\u9023\u64cd\u4f5c\u4ecb\u9762\u90fd\u6c92\u6709\uff0c\u7e3d\u4e0d\u53ef\u80fd\u53eb\u7dad\u8b77\u4eba\u54e1\u625b\u8457\u92c1\u68af\u62c6\u5916\u6bbc\u63a5\u4e32\u53e3\u7dda\u8abf\u8a66\u3002<\/p>\n\n\n\n<p>\u8b93\u9580\u7981\u6a5f\u4f5c\u70ba <strong>BLE Peripheral\uff08GATT Server\uff09<\/strong>\uff0c\u8b93\u73fe\u5834\u4eba\u54e1\u62ff\u624b\u6a5f\u900f\u904e\u85cd\u7259\u9023\u7dda\u5b8c\u6210\u914d\u7db2\uff0c\u662f\u9019\u53f0\u7d14\u786c\u9ad4\u76f2\u76d2\u6a5f\u5728\u73fe\u5834\u6700\u6838\u5fc3\u7684\u901a\u8a0a\u751f\u547d\u7dda\u3002<\/p>\n\n\n\n<p>\u9019\u7bc7\u4e0d\u8ac7\u82b1\u62f3\u7e61\u817f\uff0c\u76f4\u63a5\u5f9e <code>BleService.java<\/code> \u8207 <code>BleUtils.java<\/code> \u7684\u771f\u5be6\u4ee3\u78bc\u51fa\u767c\uff0c\u9084\u539f\u9019\u5957 BLE \u914d\u7db2\u901a\u9053\u7684\u771f\u5be6\u5354\u8b70\u5be6\u4f5c\u3001\u73fe\u5834\u7269\u7406\u908a\u754c\uff0c\u4ee5\u53ca\u5f9e\u300c\u73fe\u5834\u6551\u706b\u539f\u578b\u300d\u6f14\u9032\u81f3\u300c\u5546\u7528\u91cf\u7522\u9632\u79a6\u300d\u7684\u67b6\u69cb\u601d\u8003\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u6838\u5fc3\u4ea4\u4e92\u6642\u5e8f<\/h2>\n\n\n\n<p>\u624b\u6a5f\u8207\u9580\u7981\u6a5f\u7684\u914d\u7db2\u6d41\u7a0b\uff0c\u4f9d\u5e8f\u5206\u70ba\u300c\u8b80\u53d6\u8a2d\u5099\u8b58\u5225\u300d\u3001\u300c\u5beb\u5165\u7db2\u8def\u53c3\u6578\u300d\u3001\u300c\u8f2a\u8a62\u9023\u7dda\u72c0\u614b\u300d\u4e09\u500b\u968e\u6bb5\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/boochlin.com\/wp-content\/uploads\/2026\/09\/ble_provisioning_sequence_diagram_1788359237209.jpg\" alt=\"\u73fe\u5834\u914d\u7db2\u6642\u5e8f\u8207\u89d2\u8272\u5b9a\u7fa9\u67b6\u69cb\u5716\"\/><\/figure>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nsequenceDiagram\n    autonumber\n    participant M as \u624b\u6a5f\u7aef App (Central)\n    participant D as \u9580\u7981\u6a5f (Peripheral \/ GATT Server)\n    participant S as \u96f2\u7aef\u7ba1\u7406\u5f8c\u53f0\n\n    Note over D: \u958b\u6a5f\u5e38\u99d0\u767c\u5c04\u4e3b\u5ee3\u64ad (31B \u5167)\n    D--&gt;&gt;M: \u4e3b\u5ee3\u64ad ADV_IND (\u8a2d\u5099\u540d\u7a31-\u77ed\u5e8f\u865f, TxPower)\n    M-&gt;&gt;D: \u4e3b\u52d5\u6383\u63cf SCAN_REQ\n    D--&gt;&gt;M: \u6383\u63cf\u56de\u61c9 SCAN_RSP (128-bit \u5c08\u5c6c Service UUID)\n    \n    Note over M,D: \u5efa\u7acb\u9023\u7dda (\u56fa\u5b9a 20-Byte \u9810\u8a2d\u57fa\u7dda)\n    M-&gt;&gt;D: CONNECT_REQ \u5efa\u7acb\u9023\u7dda\u4e26 Discover Services\n\n    rect rgb(240, 245, 255)\n    Note over M,D: \u968e\u6bb5\u4e00\uff1a\u8b80\u8a2d\u5099 ID (\u5b9a\u9577\u5206\u7247\u7279\u5fb5\u503c)\n    M-&gt;&gt;D: Read CHAR_READ_DEVICE_ID_CHUNK_NUM\n    D--&gt;&gt;M: \u56de\u50b3\u7e3d\u7247\u6578 (0x04)\n    M-&gt;&gt;D: \u4f9d\u5e8f Read CHUNK_0 ~ CHUNK_3\n    D--&gt;&gt;M: \u56de\u50b3\u5404\u7247 20-Byte \u5e8f\u865f\u6578\u64da\n    end\n\n    rect rgb(245, 255, 245)\n    Note over M,D: \u968e\u6bb5\u4e8c\uff1a\u5beb\u5165\u7db2\u8def\u914d\u7f6e (Wi-Fi \u6216 \u4e59\u592a\u7db2\u975c\u614b IP)\n    alt Wi-Fi \u914d\u7db2\u60c5\u5883\n        M-&gt;&gt;D: Write SSID \u5206\u5305 &#x5B;Index, TotalLen, Payload]\n        D--&gt;&gt;M: GATT ACK\n        M-&gt;&gt;D: Write \u5bc6\u78bc\u5206\u5305 &#x5B;Index, TotalLen, Payload]\n        D--&gt;&gt;M: GATT ACK\n        M-&gt;&gt;D: Write \u5f8c\u53f0\u7db2\u5740\u5206\u5305 &#x5B;Index, TotalLen, Payload]\n        D--&gt;&gt;M: GATT ACK\n        M-&gt;&gt;D: \u767c\u9001 Commit \u4ea4\u6613\u6307\u4ee4 (Transaction Commit)\n        D--&gt;&gt;M: \u9a57\u8b49\u6b04\u4f4d\u5b8c\u6574\u6027\uff0c\u56de\u50b3 ACK \u4e26\u975e\u540c\u6b65\u555f\u52d5\u9023\u7dda\n    else \u4e59\u592a\u7db2\u8def\u56fa\u5b9a IP \u60c5\u5883\n        M-&gt;&gt;D: \u4f9d\u5e8f\u5beb\u5165 7-Byte \u5b9a\u9577\u5e40 (0x01=IP ~ 0x04=DNS1)\n        D--&gt;&gt;M: GATT ACK\n        M-&gt;&gt;D: \u5beb\u5165 0x05 (DNS2) \u4e26\u767c\u9001 Commit \u6307\u4ee4\n        D--&gt;&gt;M: GATT ACK\uff0c\u539f\u5b50\u5316\u751f\u6548\u914d\u7f6e\u4e26\u91cd\u555f\u7db2\u5361\n    end\n    end\n\n    rect rgb(255, 250, 240)\n    Note over M,D: \u968e\u6bb5\u4e09\uff1a\u4e3b\u52d5\u8f2a\u8a62\u8207\u72c0\u614b\u9589\u74b0\n    loop \u624b\u6a5f\u7aef\u6bcf\u79d2\u767c\u4e00\u6b21 Read \u7279\u5fb5\u503c\n        M-&gt;&gt;D: Read CHAR_READ_NOTIFY_SERVER_CONNECTED\n        D--&gt;&gt;M: \u56de\u50b3 6-Byte &#x5B;Status: 0x02(\u9023\u7dda\u4e2d), IPVer: 0x04, IP: 0.0.0.0]\n    end\n    D-&gt;&gt;S: \u7db2\u8def\u9023\u901a\uff0cWebSocket \u63e1\u624b\u6210\u529f (Status \u8b8a 0x01)\n    M-&gt;&gt;D: Read CHAR_READ_NOTIFY_SERVER_CONNECTED\n    D--&gt;&gt;M: \u56de\u50b3 6-Byte &#x5B;Status: 0x01(\u5df2\u9023\u7dda), IPVer: 0x04, IP: 192.168.1.100]\n    Note over M: \u624b\u6a5f UI \u4eae\u7da0\u71c8\uff0c\u914d\u7db2\u6210\u529f\n    M-&gt;&gt;D: \u4e3b\u52d5\u767c\u8d77 DISCONNECT \u65b7\u958b\u85cd\u7259\n    end\n    Note over D: \u9580\u7981\u6a5f\u56de\u5230\u88ab\u52d5\u5ee3\u64ad\uff0c\u5e38\u99d0\u5f85\u547d\n\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u4e00\u3001 \u5ee3\u64ad\u5c64\uff1a31-Byte \u9650\u5236\u8207\u524d\u7db4\u88c1\u526a<\/h2>\n\n\n\n<p>\u6a19\u6e96 BLE Legacy \u5ee3\u64ad\u5c01\u5305\u7684\u6de8\u8f09\u4e0a\u9650\u53ea\u6709 <strong>31 Bytes<\/strong>\u3002\u6263\u9664\u5ee3\u64ad Flags\uff083 Bytes\uff09\u8207\u9577\u5ea6\u958b\u92b7\uff0c\u7559\u7d66\u8a2d\u5099\u540d\u7a31\u8207\u5ee3\u64ad\u8cc7\u6599\u7684\u7a7a\u9593\u6975\u5176\u6709\u9650\u3002<\/p>\n\n\n\n<p>\u9580\u7981\u6a5f\u51fa\u5ee0\u5e8f\u865f\u81ea\u5e36 4 \u78bc\u5ee0\u5546\u8b58\u5225\u524d\u7db4\uff08\u4f8b\u5982 <code>PROD12345678<\/code>\uff09\u3002\u5982\u679c\u76f4\u63a5\u628a\u5b8c\u6574\u7684\u8a2d\u5099\u578b\u865f\u9023\u540c\u5e8f\u865f\u585e\u5165\u540d\u7a31\uff0c\u518d\u52a0\u4e0a 16 Bytes \u7684 128-bit \u670d\u52d9 UUID\uff0c\u5ee3\u64ad\u5c01\u5305\u7acb\u523b\u7a81\u7834 31 Bytes\uff0c<code>mBluetoothLeAdvertiser.startAdvertising()<\/code> \u6703\u76f4\u63a5\u62cb\u51fa <code>ADVERTISE_FAILED_DATA_TOO_LARGE<\/code>\uff08\u932f\u8aa4\u78bc 1\uff09\u3002<\/p>\n\n\n\n<p>\u73fe\u5834\u6551\u706b\u6642\u7684\u6700\u76f4\u63a5\u89e3\u6cd5\uff0c\u662f\u63a1\u7528\u96d9\u5c64\u5ee3\u64ad\u62c6\u5206\uff1a<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ File: BleService.java (\u884c 156-158)\n\/\/ \u88c1\u6389\u56fa\u5b9a 4 \u78bc\u5ee0\u5546\u524d\u7db4\uff0c\u78ba\u4fdd\u58d3\u5728 31-Byte \u9650\u5236\u5167\nuniqueSerial = DeviceCodeUtils.getSerialNum().substring(4);\nbluetoothAdapter.setName(deviceName + &quot;-&quot; + uniqueSerial);\n\n<\/pre><\/div>\n\n\n<ol>\n<li><strong>\u7b2c\u4e00\u5c64\u4e3b\u5ee3\u64ad\uff08ADV_IND\uff09<\/strong>\uff1a\u53ea\u653e\u88c1\u526a\u5f8c\u7684\u77ed\u6a5f\u865f\u8207\u767c\u5c04\u529f\u7387\uff0c\u58d3\u5728 31 Bytes \u5167\u5e38\u99d0\u5ee3\u64ad\u3002<\/li>\n\n\n\n<li><strong>\u7b2c\u4e8c\u5c64\u6383\u63cf\u56de\u61c9\uff08SCAN_RSP\uff09<\/strong>\uff1a\u628a\u9577\u9054 16 Bytes \u7684 128-bit Service UUID \u632a\u5230\u7b2c\u4e8c\u5c64\uff0c\u50c5\u5728\u624b\u6a5f\u767c\u8d77\u4e3b\u52d5\u6383\u63cf\uff08SCAN_REQ\uff09\u6642\u56de\u50b3\u3002<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">\u67b6\u69cb\u6df1\u601d\uff1a31-Byte \u7684\u908a\u754c\u8a08\u7b97\u9632\u7dda<\/h3>\n\n\n\n<p>\u5728\u6551\u706b\u539f\u578b\u4e2d\uff0c<code>substring(4)<\/code> \u5047\u8a2d\u4e86\u5e8f\u865f\u7d50\u69cb\u6c38\u9060\u56fa\u5b9a\u4e14\u70ba\u7d14 ASCII\u3002\u4f46\u5728\u5546\u7528\u91cf\u7522\u74b0\u5883\u4e2d\uff0c<strong>BLE \u5ee3\u64ad\u9577\u5ea6\u662f\u56b4\u683c\u4ee5 UTF-8 \u4f4d\u5143\u7d44\uff08Bytes\uff09\u8a08\u7b97\uff0c\u800c\u975e Java \u5b57\u5143\u6578<\/strong>\u3002Legacy Advertising \u7684 31 Bytes \u5305\u542b\u6bcf\u500b AD Structure \u7684\u9577\u5ea6\u8207\u6a19\u982d\u958b\u92b7\uff1a<\/p>\n\n\n\n<ul>\n<li><code>Flags<\/code>\uff1a3 Bytes\uff08\u9577\u5ea6 1B + \u985e\u578b 1B + \u6578\u64da 1B\uff09<\/li>\n\n\n\n<li><code>Tx Power<\/code>\uff1a3 Bytes<\/li>\n\n\n\n<li><code>Local Name<\/code> \u6a19\u982d\uff1a2 Bytes\uff08\u9577\u5ea6 1B + \u985e\u578b 1B\uff09<\/li>\n<\/ul>\n\n\n\n<p>\u82e5\u8981\u907f\u514d\u5728\u5404\u7a2e OEM \u85cd\u7259\u6676\u7247\u6216\u7570\u5e38\u5e8f\u865f\u4e0b\u5d29\u6f70\uff0c\u5546\u7528\u7d1a\u4ee3\u78bc\u61c9\u63a1 CodePoint \u7d1a\u7684 UTF-8 \u4f4d\u5143\u7d44\u9810\u7b97\u88c1\u526a\uff1a<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nprivate static final int LEGACY_ADV_MAX_BYTES = 31;\nprivate static final int FLAGS_AD_BYTES = 3;\nprivate static final int LOCAL_NAME_OVERHEAD = 2;\n\nstatic String buildSafeAdvertisedName(String deviceName, String serialSuffix, int reservedBytes) {\n    String desired = deviceName + &quot;-&quot; + serialSuffix;\n    \/\/ \u7cbe\u78ba\u6263\u9664\u5ee3\u64ad Flags \u8207\u6a19\u982d\u958b\u92b7\uff0c\u8a08\u7b97\u5269\u9918\u7684\u4f4d\u5143\u7d44\u9810\u7b97\n    int nameBudget = LEGACY_ADV_MAX_BYTES - reservedBytes - LOCAL_NAME_OVERHEAD;\n    if (nameBudget &amp;lt;= 0) return deviceName;\n\n    StringBuilder sb = new StringBuilder();\n    int used = 0;\n    for (int i = 0; i &amp;lt; desired.length(); ) {\n        int cp = desired.codePointAt(i);\n        int charBytes = new String(Character.toChars(cp)).getBytes(StandardCharsets.UTF_8).length;\n        if (used + charBytes &gt; nameBudget) break;\n        sb.appendCodePoint(cp);\n        used += charBytes;\n        i += Character.charCount(cp);\n    }\n    return sb.toString();\n}\n\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u4e8c\u3001 \u50b3\u8f38\u5c64\uff1a20-Byte \u9650\u5236\u4e0b\u7684\u4e09\u500b\u8cc7\u6599\u6d41\u5be6\u4f5c<\/h2>\n\n\n\n<p>BLE \u9810\u8a2d\u7684 ATT MTU \u55ae\u5305\u6de8\u8f09\u70ba <strong>20 Bytes<\/strong>\u3002\u914d\u7db2\u8cc7\u6599\u96d6\u7136\u91cf\u4e0d\u5927\uff0c\u4f46\u4ecd\u5305\u542b 64 \u78bc\u8a2d\u5099\u8b58\u5225\u78bc\u3001WiFi \u5e33\u5bc6\u4ee5\u53ca IP \u53c3\u6578\u3002\u4ee3\u78bc\u91dd\u5c0d\u4e0d\u540c\u8cc7\u6599\u5c6c\u6027\uff0c\u5be6\u4f5c\u4e86\u4e09\u7a2e\u50b3\u8f38\u65b9\u5f0f\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/boochlin.com\/wp-content\/uploads\/2026\/09\/ble_binary_packet_layouts_1788423952069.jpg\" alt=\"BLE \u4e09\u5927\u4e8c\u9032\u5236\u901a\u8a0a\u5c01\u5305\u4f48\u5c40\u5716\"\/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">2.1 \u8b80\u53d6 Device ID\uff1a\u5b9a\u9577\u5206\u7247\u7279\u5fb5\u503c<\/h3>\n\n\n\n<p>\u8a2d\u5099 ID \u70ba 64 \u78bc\uff08AES \u52a0\u5bc6\u6676\u7247\u8b58\u5225\u78bc\uff09\uff0c\u7121\u6cd5\u55ae\u5305\u50b3\u8f38\u3002\u4ee3\u78bc\u5728 GATT \u670d\u52d9\u4e2d\u52d5\u614b\u8a3b\u518a 4 \u500b 20-Byte \u7684\u5206\u7247\u7279\u5fb5\u503c\uff08CHUNK_0 ~ CHUNK_3\uff09\uff0c\u4e26\u5728\u8b80\u53d6\u56de\u8abf\u4e2d\u7528\u7279\u5fb5\u503c\u672b\u78bc\u8a08\u7b97\u504f\u79fb\uff1a<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ File: BleService.java (\u884c 286-291)\n\/\/ 64 \u78bc\u5e8f\u865f\u62c6 4 \u7247\u7279\u5fb5\u503c\uff0c\u4f9d UUID \u672b\u78bc\u5b57\u5143\u8a08\u7b97\u8cc7\u6599\u504f\u79fb\nchar&#x5B;] uuid = characteristic.getUuid().toString().toCharArray();\nint shift = 20 * (uuid&#x5B;uuid.length - 1] - '0');\nmBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0,\n    Arrays.copyOfRange(DeviceCodeUtils.getCode().getBytes(StandardCharsets.UTF_8), shift, shift + 20));\n\n<\/pre><\/div>\n\n\n<p>\u624b\u6a5f\u7aef\u5148\u8b80\u53d6 <code>CHAR_READ_DEVICE_ID_CHUNK_NUM<\/code> \u53d6\u5f97\u7e3d\u7247\u6578\uff08<code>0x04<\/code>\uff09\uff0c\u518d\u4f9d\u5e8f\u767c\u8d77 4 \u6b21\u8b80\u53d6\u62fc\u51fa\u5b8c\u6574 64 \u78bc\u3002\u975c\u614b\u7279\u5fb5\u503c\u6620\u5c04\u5fb9\u5e95\u7e5e\u904e\u4e86\u5354\u8b70\u68e7\u5167\u90e8\u7684\u52d5\u614b offset \u72c0\u614b\u7dad\u8b77\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2.2 \u5beb\u5165 WiFi \u5e33\u5bc6\uff1a2-Byte \u6a19\u982d\u6ed1\u52d5\u5206\u5305<\/h3>\n\n\n\n<p>\u5c0d\u65bc\u9577\u5ea6\u4e0d\u5b9a\u7684 SSID\u3001\u5bc6\u78bc\u8207\u5f8c\u53f0\u7db2\u5740\uff0c\u5354\u8b70\u5b9a\u7fa9\u4e86 2-Byte \u6a19\u982d\u7d50\u69cb\uff1a<code>[Index(1B), TotalLength(1B), Payload(10B)]<\/code>\u3002\u6bcf\u5305\u6de8\u8f09\u56fa\u5b9a 10 Bytes\uff0c\u52a0\u4e0a\u6a19\u982d\u5171 12 Bytes\u3002<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ File: BleUtils.java (\u884c 43-55)\n\/\/ bytes&#x5B;0]=Index, bytes&#x5B;1]=TotalLength\nint expectedLen = bytes&#x5B;0] * 10;\nif (expectedLen &amp;lt; sb.length()) {\n    sb.delete(0, sb.length()); \/\/ \u5e8f\u865f\u91cd\u7f6e\uff1a\u624b\u6a5f\u91cd\u50b3\uff0c\u6e05\u7a7a\u7de9\u885d\u5340\u91cd\u65b0\u63a5\u6536\n} else if (expectedLen != sb.length()) {\n    return false;              \/\/ \u5e8f\u865f\u4e0d\u5339\u914d\uff1a\u8868\u793a\u6389\u5305\uff0c\u62d2\u7d55\u62fc\u63a5\n}\n\n\/\/ \u95dc\u9375\u4fee\u6b63\uff1a\u622a\u65b7\u524d 2 Bytes \u6a19\u982d\uff0c\u53ea\u53d6\u5be6\u969b payload\nString payload = new String(bytes, 2, bytes.length - 2, StandardCharsets.UTF_8);\nsb.append(payload);\nreturn bytes&#x5B;1] == sb.length();  \/\/ \u7d2f\u7a4d\u9577\u5ea6\u9054\u5230\u9810\u671f\u7e3d\u9577\u6642\u8fd4\u56de true\n\n<\/pre><\/div>\n\n\n<h4 class=\"wp-block-heading\">\u5de5\u7a0b\u6f14\u9032\uff1a\u5f9e\u5171\u4eab\u7de9\u885d\u5230 Byte-oriented \u91cd\u7d44\u5668<\/h4>\n\n\n\n<p>\u5728\u73fe\u5834\u539f\u578b\u4e2d\uff0c\u900f\u904e <code>index * 10 == sb.length()<\/code> \u6821\u9a57\u5206\u5305\u9806\u5e8f\uff0c\u4e26\u5229\u7528\u975c\u614b\u7269\u4ef6\u8907\u7528\u58d3\u4f4e GC \u958b\u92b7\u3002\u7136\u800c\u5728\u66f4\u9ad8\u6a19\u6e96\u7684\u5546\u7528\u8a2d\u8a08\u4e2d\uff0c\u5c07\u5206\u5305\u76f4\u63a5\u7d2f\u7a4d\u70ba String \u5b58\u5728 UTF-8 \u591a\u4f4d\u5143\u7d44\u5b57\u5143\u8de8\u5305\u5272\u88c2\u7684\u98a8\u96aa\u3002<\/p>\n\n\n\n<p>\u66f4\u5065\u58ef\u7684\u89e3\u6cd5\u662f\u5148\u5728\u4e8c\u9032\u4f4d\u5c64\u7d1a\uff08<code>ByteArrayOutputStream<\/code>\uff09\u5b8c\u6210\u7d44\u5305\u8207 Sequence \u6821\u9a57\uff0c\u6536\u9f4a\u5f8c\u518d\u505a\u4e00\u6b21\u6027 UTF-8 \u89e3\u78bc\uff0c\u5fb9\u5e95\u9632\u7bc4\u5b57\u5143\u622a\u65b7\u8207\u4e26\u767c\u91cd\u5165\u6c61\u67d3\uff1a<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nfinal class FragmentAssembler {\n    private final int txId;\n    private final int totalBytes;\n    private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();\n    private int nextSeq = 0;\n\n    boolean append(int incomingTxId, int seq, byte&#x5B;] payload) {\n        if (incomingTxId != txId || seq != nextSeq) return false;\n        if (buffer.size() + payload.length &gt; totalBytes) return false;\n\n        buffer.write(payload, 0, payload.length);\n        nextSeq++;\n        return true;\n    }\n\n    String finishUtf8() throws CharacterCodingException {\n        return StandardCharsets.UTF_8.newDecoder()\n            .onMalformedInput(CodingErrorAction.REPORT)\n            .decode(ByteBuffer.wrap(buffer.toByteArray())).toString();\n    }\n}\n\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">2.3 \u5beb\u5165\u4e59\u592a\u7db2\u8def\u975c\u614b IP\uff1a7-Byte \u5b9a\u9577\u63a7\u5236\u5e40<\/h3>\n\n\n\n<p>\u4e59\u592a\u7db2\u8def\u53c3\u6578\uff08IP\u3001\u63a9\u78bc\u3001\u7db2\u95dc\u3001DNS\uff09\u7686\u70ba\u56fa\u5b9a 4 Bytes \u7684 IPv4 \u5730\u5740\u3002\u4ee3\u78bc\u76f4\u63a5\u63a1\u7528 7-Byte \u5b9a\u9577\u5e40\uff0c\u7121\u9700 Length \u6b04\u4f4d\uff1a <code>[Type(1B), Ver(1B), Key(1B), IPv4(4B)]<\/code><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ File: BleService.java (\u884c 424-437 \u7bc0\u9078)\ncase 0x05: \/\/ Key 0x05 (DNS2) \u4f5c\u70ba\u6700\u5f8c\u4e00\u7b46\u914d\u7f6e\uff0c\u89f8\u767c\u914d\u7f6e\u751f\u6548\n    staticIpConfig.dns2 = BleUtils.ipv4ToString(ipBytes);\n    ethernetManager.setStaticIp(staticIpConfig); \/\/ \u751f\u6548\u914d\u7f6e\u4e26\u91cd\u555f\u7db2\u5361\n    staticIpConfig = null;\n    break;\n\n<\/pre><\/div>\n\n\n<p>\u624b\u6a5f\u7aef\u4f9d\u5e8f\u5beb\u5165 IP \u81f3 DNS1\uff0c\u6536\u5230\u6700\u5f8c\u4e00\u7b46 DNS2\uff08Key <code>0x05<\/code>\uff09\u6642\uff0c\u9580\u7981\u6a5f\u5224\u5b9a\u6240\u6709\u975c\u614b\u53c3\u6578\u5df2\u9f4a\u5168\uff0c\u8abf\u7528\u7cfb\u7d71\u5c08\u5c6c\u7684 <code>EthernetManager<\/code> \u91cd\u555f\u7db2\u5361\u751f\u6548\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u4e09\u3001 \u72c0\u614b\u540c\u6b65\u8207\u6d41\u7a0b\u929c\u63a5\uff1a\u5f9e\u300c\u6642\u9593\u5de7\u5408\u300d\u5230\u300c\u986f\u5f0f Transaction\u300d<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">3.1 \u539f\u59cb\u4ee3\u78bc\u7684\u6642\u5e8f\u76f2\u9ede<\/h3>\n\n\n\n<p>\u5728\u65e9\u671f Wi-Fi \u914d\u7db2\u5be6\u4f5c\u4e2d\uff0c\u624b\u6a5f\u7aef\u6703\u9023\u7e8c\u767c\u9001 SSID\u3001\u5bc6\u78bc\u8207\u5f8c\u53f0\u7db2\u5740\u3002\u8a2d\u5099\u7aef\u7684\u929c\u63a5\u4ee3\u78bc\u4f4d\u65bc <code>connectToWifi<\/code>\uff1a<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ File: BleService.java (\u884c 528-542 \u7bc0\u9078)\n\/\/ Wi-Fi \u63e1\u624b\u6210\u529f\u5f8c\uff0c\u975e\u540c\u6b65\u7b49\u5f85\u5f8c\u53f0\u7db2\u5740\u63a5\u6536\u5b8c\u6210\nint count = 0;\nwhile (count &amp;lt; 10) {\n    if (serverHostDone) { \/\/ \u5f8c\u53f0\u7db2\u5740\u5df2\u6536\u9f4a\uff0c\u5beb\u5165\u914d\u7f6e\n        SettingServ.setSyncServerIp(sbServerHost.toString().replaceFirst(&quot;^(http&#x5B;s]?:\/\/)&quot;, &quot;&quot;));\n        serverHostDone = false;\n        return;\n    }\n    Thread.sleep(2000);\n    count++;\n}\n\n<\/pre><\/div>\n\n\n<ul>\n<li><strong>\u73fe\u5834\u6642\u5e8f\u4f9d\u8cf4<\/strong>\uff1a\u9019\u6bb5\u4ee3\u78bc\u96b1\u542b\u4e86\u4e00\u500b\u6642\u5e8f\u5047\u8a2d\u2014\u2014\u624b\u6a5f\u7aef\u6703\u5728 1 \u79d2\u5167\u9023\u7e8c\u767c\u9001\u5b8c\u7562\uff0c\u800c Wi-Fi \u9023\u7dda\u8207 DHCP \u5206\u914d\u9700\u8981 2~5 \u79d2\u3002\u56e0\u6b64\u7576\u9023\u7dda\u6210\u529f\u56de\u8abf\u89f8\u767c\u6642\uff0c\u5f8c\u53f0\u7db2\u5740\u300c\u901a\u5e38\u300d\u5df2\u6536\u9f4a\u3002<\/li>\n\n\n\n<li><strong>\u67b6\u69cb\u96b1\u60a3<\/strong>\uff1a\u4e00\u65e6 Wi-Fi \u6676\u7247\u5feb\u53d6\u4e86\u71b1\u9ede\u8cc7\u8a0a\u800c\u77ac\u9593\u79d2\u9023\uff0c\u6216\u624b\u6a5f\u7aef\u6392\u7a0b\u5ef6\u9072\uff0c\u8a2d\u5099\u5c31\u6703\u5957\u7528\u534a\u6b98\u7684\u8a2d\u5b9a\uff1b\u4e14\u8f2a\u8a62 20 \u79d2\u8d85\u6642\u5f8c\u7f3a\u4e4f\u6e05\u6670\u7684\u5931\u6557\u56de\u6efe\u8207\u8a3a\u65b7\u56de\u5831\u3002<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3.2 \u91cf\u7522\u7d1a\u6f14\u9032\uff1a\u986f\u5f0f Commit \u8207\u53ef\u8a3a\u65b7\u72c0\u614b\u6a5f\uff08FSM\uff09<\/h3>\n\n\n\n<p>\u6210\u719f\u7684\u5de5\u63a7\u901a\u8a0a\u5fc5\u9808\u6d88\u9664\u300c\u8ced\u6642\u5e8f\u300d\u7684\u5beb\u6cd5\u3002\u6b63\u78ba\u6a21\u578b\u662f<strong>\u986f\u5f0f\u4ea4\u6613\uff08Transaction Commit\uff09<\/strong>\uff1a<\/p>\n\n\n\n<ol>\n<li><strong>\u66ab\u5b58\u63a5\u6536<\/strong>\uff1a\u624b\u6a5f\u767c\u9001 SSID\u3001\u5bc6\u78bc\u8207\u5f8c\u53f0\u7db2\u5740\uff0c\u8a2d\u5099\u50c5\u5b58\u5165\u4ea4\u6613\u66ab\u5b58\u5340\u3002<\/li>\n\n\n\n<li><strong>\u986f\u5f0f Commit<\/strong>\uff1a\u624b\u6a5f\u6240\u6709\u8cc7\u6599\u5beb\u7562\u5f8c\uff0c\u767c\u9001 Commit \u6307\u4ee4\u3002<\/li>\n\n\n\n<li><strong>\u539f\u5b50\u6821\u9a57\u8207\u5957\u7528<\/strong>\uff1a\u8a2d\u5099\u9a57\u8b49\u5fc5\u586b\u6b04\u4f4d\u5b8c\u6574\u6027\uff0c\u6301\u4e45\u5316\u8a2d\u5b9a\u5f8c\u555f\u52d5\u7db2\u8def\u9023\u7dda\uff0c\u4e26\u56de\u5831\u8a3a\u65b7\u72c0\u614b\u78bc\u3002<\/li>\n<\/ol>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ \u73fe\u5834\u53ef\u8a3a\u65b7\u7684\u72c0\u614b\u78bc\u679a\u8209\npublic enum ProvisionStatus {\n    IDLE((byte) 0x00),\n    SUCCESS((byte) 0x01),                  \/\/ \u6210\u529f\uff08Wi-Fi + \u5f8c\u53f0\u63e1\u624b\u5168\u901a\uff09\n    CONNECTING((byte) 0x02),               \/\/ \u7db2\u8def\u95dc\u806f\u8207\u4ea4\u63e1\u4e2d\n    WIFI_AUTH_FAILED((byte) 0x03),         \/\/ \u5bc6\u78bc\u932f\u8aa4\n    SSID_NOT_FOUND((byte) 0x04),           \/\/ \u627e\u4e0d\u5230 Wi-Fi \u71b1\u9ede\n    BACKEND_HANDSHAKE_FAILED((byte) 0x05), \/\/ \u96f2\u7aef\u5f8c\u53f0\u9023\u7dda\u6216\u63e1\u624b\u5931\u6557\n    INVALID_CONFIG((byte) 0x06),           \/\/ \u53c3\u6578\u6821\u9a57\u5931\u6557\n    NETWORK_TIMEOUT((byte) 0x07);          \/\/ DHCP \u5206\u914d\u6216\u9023\u7dda\u8d85\u6642\n\n    public final byte code;\n    ProvisionStatus(byte code) { this.code = code; }\n}\n\npublic void commitProvision(int transactionId) {\n    ProvisionTransaction tx;\n    synchronized (lock) {\n        if (activeTransaction == null || activeTransaction.id != transactionId) {\n            publishStatus(ProvisionStatus.INVALID_CONFIG, (byte) 0x01);\n            return;\n        }\n        if (!activeTransaction.isComplete()) {\n            publishStatus(ProvisionStatus.INVALID_CONFIG, (byte) 0x02);\n            return;\n        }\n        tx = activeTransaction;\n        activeTransaction = null; \/\/ \u9632\u6b62\u91cd\u8907\u89f8\u767c\n    }\n\n    publishStatus(ProvisionStatus.CONNECTING, (byte) 0x00);\n    executor.execute(() -&gt; {\n        try {\n            \/\/ \u539f\u5b50\u5316\u5beb\u5165\u914d\u7f6e\u6a94\n            persistProvisionConfig(tx);\n\n            \/\/ \u9023\u7dda Wi-Fi\n            WifiConnectResult res = connectWifi(tx.ssid, tx.passwordBytes);\n            if (res == WifiConnectResult.AUTH_FAILED) {\n                publishStatus(ProvisionStatus.WIFI_AUTH_FAILED, (byte) 0);\n                return;\n            }\n\n            \/\/ \u9a57\u8b49\u5f8c\u53f0\u9023\u901a\u6027\n            if (!verifyBackendHandshake(tx.serverHost)) {\n                publishStatus(ProvisionStatus.BACKEND_HANDSHAKE_FAILED, (byte) 0);\n                return;\n            }\n\n            publishStatus(ProvisionStatus.SUCCESS, (byte) 0);\n        } finally {\n            Arrays.fill(tx.passwordBytes, (byte) 0); \/\/ \u5bc6\u78bc\u8a18\u61b6\u9ad4\u6b78\u96f6\u6e05\u9664\n        }\n    });\n}\n\n<\/pre><\/div>\n\n\n<p>\u624b\u6a5f\u7aef\u4e0d\u518d\u53ea\u80fd\u770b\u8457\u300c\u9023\u7dda\u4e2d\u300d\u4e7e\u7b49\uff0c\u4e00\u65e6\u5bc6\u78bc\u6253\u932f\u6216\u5f8c\u53f0\u7db2\u5740\u586b\u932f\uff0c\u72c0\u614b\u7279\u5fb5\u503c\u80fd\u7cbe\u6e96\u56de\u5831 <code>0x03<\/code> \u6216 <code>0x05<\/code>\uff0c\u73fe\u5834\u4eba\u54e1\u79d2\u77e5\u554f\u984c\u6240\u5728\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u56db\u3001 \u5546\u7528\u9580\u7981\u7684\u6700\u4f4e\u8cc7\u5b89\u9632\u7dda<\/h2>\n\n\n\n<p>\u5728\u5de5\u63a7\u8207\u516c\u5171\u5b89\u5168\u5834\u666f\u4e2d\uff0cBLE \u914d\u7db2\u627f\u8f09\u7684\u662f Wi-Fi \u91d1\u9470\u8207\u96f2\u7aef\u7ba1\u7406\u5f8c\u53f0\u7aef\u9ede\uff0c\u7d55\u4e0d\u80fd\u628a\u300c\u4eba\u5728\u8a2d\u5099\u65c1\u908a\u300d\u7576\u6210\u6388\u6b0a\u4f9d\u64da\u3002\u91cf\u7522\u7248\u672c\u5fc5\u9808\u5efa\u7acb\u6700\u4f4e\u9632\u79a6\u908a\u754c\uff1a<\/p>\n\n\n\n<ol>\n<li><strong>\u51fa\u5ee0\u9810\u5171\u4eab\u91d1\u9470\uff08PSK\uff09\u8207\u6311\u6230\u61c9\u7b54\uff08Challenge-Response\uff09<\/strong>\uff1a \u6bcf\u53f0\u8a2d\u5099\u51fa\u5ee0\u64c1\u6709\u5c08\u5c6c PSK\u3002\u624b\u6a5f\u900f\u904e BLE \u9023\u7dda\u5f8c\uff0c\u8a2d\u5099\u751f\u6210\u96a8\u6a5f Nonce \u767c\u7d66\u624b\u6a5f\uff0c\u624b\u6a5f\u4f7f\u7528 PSK \u8a08\u7b97 HMAC-SHA256 \u56de\u50b3\u3002\u9580\u7981\u6a5f\u6821\u9a57\u901a\u904e\u524d\uff0c<strong>\u62d2\u7d55\u4efb\u4f55\u7279\u5fb5\u503c\u7684\u5beb\u5165\u8207\u8b80\u53d6<\/strong>\uff0c\u9632\u6b62\u964c\u751f\u624b\u6a5f\u4efb\u610f\u8986\u5beb\u914d\u7db2\u53c3\u6578\u3002<\/li>\n\n\n\n<li><strong>\u9632\u91cd\u653e\u6a5f\u5236\uff08Anti-Replay\uff09<\/strong>\uff1a \u6240\u6709\u5beb\u5165\u5206\u5305\u5747\u7d81\u5b9a\u7576\u6b21\u9023\u7dda\u751f\u6210\u7684 <code>Transaction ID<\/code> \u8207\u56b4\u683c\u905e\u589e\u7684 <code>Sequence<\/code>\uff0c\u73fe\u5834\u6293\u5305\u7121\u6cd5\u9304\u88fd\u91cd\u653e\u3002<\/li>\n\n\n\n<li><strong>\u8a18\u61b6\u9ad4\u5b89\u5168\u885b\u751f\uff08Memory Hygiene\uff09<\/strong>\uff1a Wi-Fi \u5bc6\u78bc\u7b49\u9ad8\u5ea6\u654f\u611f\u8cc7\u8a0a\u4ee5 <code>byte[]<\/code> \u8655\u7406\uff0c\u4f7f\u7528\u5b8c\u7562\u5f8c\u7acb\u5373\u4ee5 <code>Arrays.fill(bytes, (byte) 0)<\/code> \u62b9\u9664\uff0c\u56b4\u7981\u8f49\u5316\u70ba\u4e0d\u53ef\u8b8a\u7684 Java <code>String<\/code> \u9577\u671f\u6eef\u7559\u65bc Dalvik Heap \u4e2d\u88ab Heap Dump \u6293\u53d6\u3002<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u7d50\u8a9e<\/h2>\n\n\n\n<p>\u5de5\u63a7\u5834\u666f\u7684\u908a\u7de3\u901a\u8a0a\uff0c\u4e0d\u9700\u8981\u82b1\u54e8\u7684\u52d5\u614b\u5354\u5546\uff0c\u4f46\u5fc5\u9808\u5177\u5099<strong>\u72c0\u614b\u53ef\u89c0\u6e2c\u6027\u8207\u5931\u6557\u9632\u79a6\u529b<\/strong>\u3002<\/p>\n\n\n\n<p>\u9762\u5c0d\u7121\u87a2\u5e55\u7684\u5de5\u63a7\u76f2\u76d2\u6a5f\uff0c\u4e00\u5957\u57fa\u65bc 31-Byte \u5ee3\u64ad\u62c6\u5206\u300120-Byte \u5b9a\u9577\u5206\u5305\u8207\u4e3b\u52d5\u8f2a\u8a62\u7684\u901a\u9053\uff0c\u5728\u91cf\u7522\u6551\u706b\u521d\u671f\u4ee5\u6975\u7cbe\u7c21\u7684\u4ee3\u78bc\u5b8c\u6210\u4e86\u5f9e\u786c\u9ad4\u8b58\u5225\u3001\u7db2\u8def\u914d\u7f6e\u5230\u72c0\u614b\u9a57\u8b49\u7684\u5168\u6d41\u7a0b\u9589\u74b0\u3002<\/p>\n\n\n\n<p>\u7136\u800c\uff0c\u4e00\u500b\u771f\u6b63\u6210\u719f\u7684\u5546\u7528\u7cfb\u7d71\uff0c\u7d55\u4e0d\u80fd\u4f9d\u8cf4\u300c\u525b\u597d\u9023\u4e0a\u300d\u7684\u6642\u9593\u5de7\u5408\uff1b\u552f\u6709\u900f\u904e<strong>\u986f\u5f0f Transaction Commit\u3001\u5b8c\u5099\u7684\u8a3a\u65b7\u72c0\u614b\u6a5f\uff08FSM\uff09\u3001\u4ee5\u53ca\u8a18\u61b6\u9ad4\u654f\u611f\u8cc7\u6599\u64e6\u9664<\/strong>\uff0c\u624d\u80fd\u5728\u60e1\u52a3\u7684\u96fb\u78c1\u74b0\u5883\u8207\u7db2\u8def\u6ce2\u52d5\u4e0b\uff0c\u63d0\u4f9b\u771f\u6b63\u5805\u56fa\u7684\u5de5\u696d\u7d1a\u53ef\u9760\u6027\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u5728\u908a\u7de3\u5de5\u63a7\u5834\u666f\u4e2d\uff0c\u6709\u4e00\u6b3e\u578b\u865f\u53eb Mkspure\u3002\u9019\u662f\u4e00\u53f0\u5c08\u70ba\u56b4\u82db\u74b0\u5883\u8a2d\u8a08\u7684\u7d14\u786c\u9ad4\u8a2d\u5099\uff1a\u6c92\u6709\u87a2\u5e55\u3001\u6c92\u6709\u9375\u76e4\u3001\u96f6\u5be6 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1057,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[50,3,80],"tags":[],"_links":{"self":[{"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/posts\/1055"}],"collection":[{"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/boochlin.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1055"}],"version-history":[{"count":3,"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/posts\/1055\/revisions"}],"predecessor-version":[{"id":1059,"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/posts\/1055\/revisions\/1059"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/media\/1057"}],"wp:attachment":[{"href":"https:\/\/boochlin.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1055"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/boochlin.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1055"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/boochlin.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1055"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}