
CMF Watch Pro 2 के लिए रिवर्स-इंजीनियर्ड BLE प्रोटोकॉल, GATT लेआउट, AES-128-CBC एन्क्रिप्टेड कमांड फ्रेम्स, प्रमाणीकरण हैंडशेक और वैकल्पिक साथी ऐप विकास हेतु स्वास्थ्य डेटा सिंक का दस्तावेज़ीकरण।
अनौपचारिक। यह दस्तावेज़ CMF Watch Pro 2 (CMF by Nothing) के ब्लूटूथ लो एनर्जी (BLE) प्रोटोकॉल का वर्णन करता है, जिसे एक वैकल्पिक साथी ऐप के लिए रिवर्स इंजीनियरिंग द्वारा पुनर्निर्मित किया गया है। यह Nothing/CMF से संबद्ध या समर्थित नहीं है। अपने जोखिम पर उपयोग करें।
फ्रेम हेडर और ऑपकोड में सभी मल्टी-बाइट पूर्णांक बिग-एंडियन हैं। कमांड पेलोड के अंदर पूर्णांक लिटिल-एंडियन हैं जब तक कि अन्यथा न कहा गया हो (यह डिवाइस फर्मवेयर को दर्शाता है) — अपवादों पर ध्यान दें (GOALS_SET, GPS_PUSH, बल्क-ट्रांसफर ऑफसेट/लंबाई बिग-एंडियन हैं)।
नीचे प्रत्येक गैर-स्पष्ट दावे को टैग किया गया है कि इसे कैसे स्थापित किया गया था:
जहाँ बाद का अनुभाग पहले वाले को सुधारता है, वहाँ पहले वाला पाठ हटाने के बजाय एक पॉइंटर के साथ रखा जाता है — यह जानना कि कौन से रीडिंग आज़माए गए और खारिज किए गए, अगले व्यक्ति को वही चक्कर बचाता है।
सभी कैप्चर के लिए परीक्षण डिवाइस: CMF Watch Pro 2-5485, fw 1.0.0.73, सीरियल CI04102520008192, MCU Actions ATS3089C (Cortex-M4), स्क्रीन 466×360।
फ़ोन GATT क्लाइंट है; घड़ी पेरिफेरल है, जो CMF Watch Pro 2-XXXX (4 हेक्स वर्ण) के रूप में विज्ञापन करती है।
प्रत्येक CCCD (00002902-…) पर 01 00 लिखकर नोटिफिकेशन सक्षम करें। कमांड चैनल (fff1/fff2) नीचे दिए गए फ्रेम किए गए प्रोटोकॉल को ले जाता है। शेल चैनल (77d4…) सादा AT-शैली टेक्स्ट ले जाता है (जैसे AT GETSECRET; §14 देखें)। डेटा चैनल (02f0…) बड़े बाइनरी ब्लॉब्स (वॉचफेस, फर्मवेयर, AGPS) ले जाता है, जो कमांड चैनल पर नियंत्रण ऑपकोड द्वारा समन्वित होते हैं।
सेवा UUID — घड़ी ~10 प्राथमिक सेवाओं का विज्ञापन करती है। वास्तविक इकाई पर गणना की गई:
0xfff0 (कमांड), 0x180f (बैटरी), 0x180a (डिवाइस जानकारी), 0xefe7, 0xffd0,
02f00000-…ffe0 और 02f00000-…fe00 (डेटा), 77d4e67c-2fe2-2334-0d35-9ccd078f529c (शेल / पेयरिंग), e49a3001-f69a-11e8-8eb2-f2801f1b9fd1, f48a23c0-f69a-11e8-8eb2-f2801f1b9fd1।
⚠️ शेल सेवा UUID
77d4e67c-…है, न कि77d4ff00-…। इस दस्तावेज़ के पहले के संशोधनों ने मान लिया था कि सेवा अपनी विशेषताओं (77d4ff01/77d4ff02, §14) केff00उपसर्ग को साझा करती है — ऐसा नहीं है, कम से कम उस इकाई पर जिस पर यह जाँचा गया था (खोज freethinkel/fmc से, §स्रोत देखें)। विशेषता UUID अपरिवर्तित हैं। यह सत्यापित नहीं है कि77d4e67cइकाइयों में स्थिर है या नहीं — हार्डकोड करने के बजाय गणना करें।
🌐 वेब ब्लूटूथ कॉलआउट। क्रोमियम केवल उन सेवाओं को खोजता है जिन्हें पृष्ठ ने
optionalServicesमें सूचीबद्ध किया है, यहाँ तक कि एक अनफ़िल्टर्डgetPrimaryServices()कॉल के लिए भी — 3 सेवाओं को सूचीबद्ध करने वाला पृष्ठ 3 देखता है, जबकिchrome://bluetooth-internals(क्रोम का अपना C++ लेयर, अनस्कोप्ड) सभी 10 दिखाता है। यदि आप एक ब्राउज़र क्लाइंट लिखते हैं, तो ऊपर हर UUID को पहले से सूचीबद्ध करें या पेयरिंग उन सेवाओं के साथ विफल हो जाएगी जो स्पष्ट रूप से मौजूद हैं। फ़ायरफ़ॉक्स/सफारी में कोई वेब ब्लूटूथ नहीं; उपयोगकर्ता इशारा + HTTPS/localhost की आवश्यकता है।
✅ एक पूरा वास्तविक सत्र एकल कमांड चैनल पर चला — 160 s के भारी-उपयोग कैप्चर के दौरान डेटा/फर्मवेयर या शेल चैनलों पर कोई ट्रैफ़िक नहीं था, सिवाय एक स्पष्ट OTA/वॉचफेस ट्रांसफर के।
0xF5)प्रत्येक कमांड-चैनल संदेश एक या अधिक 11-बाइट-हेडर फ्रेम में लपेटा जाता है:``` +------+-----------+--------+-------------+-------------+--------+-------------------+ | 0xF5 | chunkLen | cmd1 | chunkCount | chunkIndex | cmd2 | chunk bytes … | | 1 B | 2 B (BE) | 2 B BE | 2 B BE | 2 B BE | 2 B BE | chunkLen bytes | +------+-----------+--------+-------------+-------------+--------+-------------------+ __________________________ 11-byte header ____________________________/
- `cmd1`/`cmd2` मिलकर **opcode** बनाते हैं (§6 देखें)। 🔎 आधिकारिक ऐप के फ्रेम बिल्डर (`C6117b.m30831g`) के विरुद्ध पुष्टि की गई।
- `chunkCount` = इस कमांड के लिए कुल चंक; `chunkIndex` **1-आधारित** है।
- `chunkLen` = इस फ्रेम में `chunk` के बाइट्स की संख्या।
- एक एकल BLE राइट लिंक MTU द्वारा खंडित हो सकती है; रिसीवर कच्चे बाइट्स को बफर करता है और पूर्ण फ्रेम्स को फिर से निकालता है। बड़े पेलोड को कई चंक्स में विभाजित किया जाता है (समान `cmd1/cmd2`, बढ़ता हुआ `chunkIndex`) और क्रम में पुनः जोड़ा जाता है।
### Opcode परंपरा (✅ वायर पर पुष्टि की गई)
- `cmd1 = 0xFFFF`: `cmd2` में `0x80xx`/`0x90xx` = फोन→घड़ी (अनुरोध/सेट); `0x00xx`/`0xa0xx` = घड़ी→फोन (उत्तर)। जोड़े निम्न बाइट द्वारा मेल खाते हैं (`0x9055`↔`0xa055`, `0x8051`↔`0x0051`)।
- फीचर-विशिष्ट `cmd1`: `cmd2` प्रत्यय = `0x0001` **SET**, `0x0002` **GET**, `0x0003` **ACK**।
### चंक बॉडी
प्रत्येक चंक के लिए, बॉडी है `payloadPiece ‖ CRC32_LE(payloadPiece)` (4-बाइट CRC, लिटिल-एंडियन, zlib/IEEE)। यदि कमांड **एन्क्रिप्टेड** है (§3 देखें), तो पूरा `payloadPiece ‖ CRC` फिर AES-128-CBC/PKCS7 से एन्क्रिप्ट किया जाता है और वह सिफरटेक्स्ट फ्रेम का `chunk` बन जाता है।
**प्लेनटेक्स्ट विचित्रता:** प्लेनटेक्स्ट opcodes के लिए घड़ी `chunkLen` में 4-बाइट CRC को *गिनती* है लेकिन उसे **प्रसारित** नहीं करती। इसलिए प्लेनटेक्स्ट फ्रेम को डिकोड करते समय, वास्तविक डेटा लंबाई `chunkLen − 4` होती है। (एन्क्रिप्टेड फ्रेम्स CRC को सिफरटेक्स्ट के अंदर सामान्य रूप से ले जाते हैं।)
चंक आकार (ताकि एन्क्रिप्टेड चंक्स AES ब्लॉक सीमाओं पर पहुँचें), `maxWrite = mtu − 3` के साथ:
- एन्क्रिप्टेड: `floor((maxWrite − 11) / 16) * 16 − 4 − 1`
- प्लेनटेक्स्ट: `maxWrite − 11 − 4 − 1`
✅ सभी देखे गए एन्क्रिप्टेड फ्रेम `chunkLen` मान 16 के गुणक थे (ब्लॉक संरेखण बना रहता है)।
---
## 3. क्रिप्टोग्राफिक प्रिमिटिव्स
- **AES-128-CBC** जिसमें **PKCS7** पैडिंग और एक **निश्चित IV** (फर्मवेयर `CmfCharacteristic.AES_IV` से):
`50 51 52 53 54 55 56 57 60 61 62 63 64 65 66 5A`।
- **CRC32** (zlib/IEEE), 4 लिटिल-एंडियन बाइट्स के रूप में उत्सर्जित।
- **SHA-256** भागों के संयोजन पर।
कुंजी व्युत्पत्ति:```
authkey = SHA256( rnd1 ‖ rnd2 ‖ secret )[0..16] // persisted across sessions
sessionKey = SHA256( nonce ‖ authkey )[0..16] // per connection
secret = 16-बाइट डिवाइस सीक्रेट (वॉच से शेल कमांड AT GETSECRET → GETSECRET:<32-hex>,OK के माध्यम से प्राप्त किया जा सकता है)।rnd1 = फोन द्वारा चुने गए 16 रैंडम बाइट्स; rnd2 = वॉच से 16 रैंडम बाइट्स।nonce = वॉच के nonce रिप्लाई से बाइट्स।कुंजी सेट होने के बाद, सभी कमांड-चैनल फ्रेम AES-एन्क्रिप्टेड होते हैं सिवाय §5 में सूचीबद्ध प्लेनटेक्स्ट ऑपकोड्स के।
✅ दोनों डेरिवेशन मान्य किए गए: रूट किए गए फोन के ntwatch.db से प्राप्त authkey कैप्चर किए गए rnd1/rnd2/secret से व्युत्पन्न मान से मेल खाता था; कैप्चर किए गए nonce से पुनरुत्पादित sessionKey लाइव फ्रेम को डिक्रिप्ट करता है।
दो एंट्री पथ एक ही nonce/confirm टेल साझा करते हैं।
phone → (shell) AT GETSECRET watch → (shell) GETSECRET:<32hex>,OK phone: rnd1 = random16 ; signed1 = SHA256(rnd1 ‖ secret) phone → AUTH_PAIR_REQUEST (plaintext) payload = rnd1(16) ‖ signed1(32) // 48 B watch → AUTH_PAIR_REPLY (plaintext) payload = rnd2(16) ‖ signed2(32) // 48 B phone verifies signed2 == SHA256(rnd2 ‖ secret) phone: authkey = SHA256(rnd1 ‖ rnd2 ‖ secret)[0..16] → set crypto key = authkey phone → AUTH_PHONE_NAME (encrypted) payload = 0xA5 ‖ model(UTF-8) // e.g. "CMF Watch Pro 2" watch → AUTH_WATCH_MAC (encrypted) phone → AUTH_NONCE_REQUEST (encrypted) payload = 0xA5 watch → AUTH_NONCE_REPLY (encrypted) payload = nonce phone: sessionKey = SHA256(nonce ‖ authkey)[0..16] → set crypto key = sessionKey phone → AUTHENTICATED_CONFIRM_REQUEST (encrypted) payload = 0xA5 watch → AUTHENTICATED_CONFIRM_REPLY (encrypted) → state = Initialized
`AUTH_FAILED (0xFFFF,0xA061)` या हस्ताक्षर बेमेल होने पर, प्रमाणीकरण विफल हो जाता है।
### 4.2 पुनः कनेक्ट (authkey पहले से ज्ञात)```
set crypto key = authkey (persisted)
phone → AUTH_PHONE_NAME (encrypted) payload = 0xA5 ‖ model
watch → AUTH_WATCH_MAC (encrypted)
phone → AUTH_NONCE_REQUEST (encrypted) payload = 0xA5
watch → AUTH_NONCE_REPLY (encrypted) payload = nonce
sessionKey = SHA256(nonce ‖ authkey)[0..16] → set crypto key = sessionKey
phone → AUTHENTICATED_CONFIRM_REQUEST (encrypted) payload = 0xA5
watch → AUTHENTICATED_CONFIRM_REPLY (encrypted) → Initialized
✅ पुनः कनेक्ट क्रम (बिना शेल ट्रैफ़िक) वास्तविक कैप्चर पर बरकरार देखा गया।
⚠️→✅ डेटा क्वेरी से पहले
TIMEअनिवार्य है।Initializedके बाद, वॉचBATTERY,SERIAL_NUMBER_GET, याACTIVITY_FETCH_*हैंडशेक का उत्तर नहीं देगी जब तक कि सत्र मेंTIME (FFFF 8004)न भेजा जाए — इसके बिना, केवल एक अनचाहाFIRMWARE_VERSION_RETआता है और बाकी सब टाइमआउट हो जाता है। ✅ लाइव पुष्टि (Pixel 8a): बिनाTIMEके तीनों GET भेजने पर → केवल फ़र्मवेयर उत्तर देता है; पहलेTIMEभेजने पर → बैटरी और सीरियल उत्तर देना शुरू करते हैं।
अनुशंसित चरण-2 क्रम: TIME → FIRMWARE_VERSION_GET → SERIAL_NUMBER_GET → BATTERY (0xA5) → कॉन्फ़िग पुश → हेल्थ सिंक (§8)।
अधिकांश सेटिंग्स के लिए कोई अलग "रीड" ऑपकोड नहीं है। *_GET (cmd2 = 0x0002, पेलोड 0xA5) भेजने पर वॉच SET ऑपकोड के साथ उत्तर देती है (cmd2 = 0x0001) जिसमें वर्तमान मान होता है। SET कमांड को cmd2 = 0x0003 और खाली बॉडी के साथ स्वीकार किया जाता है।
कुंजी सेट होने के बाद फ़्रेम AES-एन्क्रिप्टेड होते हैं, इन ऑपकोडों को छोड़कर, जो हमेशा प्लेनटेक्स्ट होते हैं:
AUTH_PAIR_REQUEST (FFFF 8047), AUTH_PAIR_REPLY (FFFF 0048)DATA_CHUNK_WRITE_WATCHFACE (FFFF 9064), DATA_CHUNK_WRITE_FIRMWARE (FFFF 9042), DATA_CHUNK_WRITE_AGPS (FFFF 905F)फ़्रेम हेडर (cmd1/cmd2) हमेशा स्पष्ट रूप में यात्रा करते हैं, इसलिए कमांड अनुक्रम किसी भी कैप्चर में बिना कुंजी के भी दिखाई देता है — केवल एन्क्रिप्टेड पेलोड को sessionKey की आवश्यकता होती है।
(cmd1, cmd2)GET/SET/REQUEST = फ़ोन→वॉच; RET/REPLY/ACK/RESPONSE/DATA = वॉच→फ़ोन।
| नाम | cmd1,cmd2 |
|---|---|
| MUSIC_INFO_SET / _ACK | FFFF 905C / FFFF A05C |
| MUSIC_BUTTON | FFFF A05D |
| नाम | cmd1,cmd2 |
|---|---|
| WEATHER_SET_1 (वह जो काम करता है) | FFFF 906B |
| WEATHER_SET_2 (Pro 2 पर अनदेखा — §9 देखें) | 0066 0001 |
केवल-JS ऑपकोड (
FFFF 8051,FFFF 0051,FFFF 90A2,FFFF 90C5,FFFF A056,FFFF 908A/908BChatGPT स्थिति/समर्थन) ऐप के Hermes बाइटकोड में संभाले जाते हैं, Java परत में नहीं। उनके हेडर कैप्चर में दिखाई देते हैं लेकिन पेलोड शब्दार्थ ⚠️ [अनिश्चित] हैं।
वॉचफेस / फ़र्मवेयर / AGPS एक init → चंक-अनुरोध/चंक-लिखें लूप → फिनिश-ack का उपयोग करते हैं:
(सभी cmd1 = FFFF।) वॉच DATA_CHUNK_REQUEST_*(offset, length) उत्सर्जित करके लूप चलाती है (offset/length = u32 बिग-एंडियन); फ़ोन डेटा विशेषता पर payload[offset..offset+length] लेकर DATA_CHUNK_WRITE_* के साथ उत्तर देता है। विवरण के लिए §11–§12 देखें।
TIME (FFFF 8004) पेलोड = epochSeconds(i32, BE) ‖ utcOffsetMillis(i32, BE)। प्रमाणीकरण के तुरंत बाद भेजा जाता है ताकि वॉच स्थानीय समय दिखाए (और डेटा क्वेरी अनब्लॉक करे — §4.3 देखें)।
⚠️ वॉच से स्वास्थ्य टाइमस्टैम्प UTC हैं। साथी ऐप को स्थानीय कैलेंडर दिन / दिन-का-समय प्राप्त करने से पहले स्थानीय UTC ऑफ़सेट जोड़ना चाहिए। (कच्चे UTC दिन द्वारा स्वास्थ्य को बकेट करने से दिन गलत स्थानीय समय पर बदल जाता है।)
TIME_FORMAT (005F 0001) पेलोड = 1 बाइट: 00 = 24 घंटे, 01 = 12 घंटे।
ACTIVITY_FETCH_1 भेजता है; वॉच ACTIVITY_FETCH_ACK_1 के साथ उत्तर देती है (पहला बाइट 01 ⇒ तैयार)।ACTIVITY_FETCH_2 भेजता है; फिर वॉच डेटा फ़्रेमों का एक बर्स्ट धकेलती है: ACTIVITY_DATA, HEART_RATE_*, SPO2, STRESS, SLEEP_DATA, WORKOUT_SUMMARY[_V3]।सिंक अनुक्रमिक है (TIME का पालन करना चाहिए; वॉच ACK_2 के बाद स्ट्रीम जारी करती है), एकल बर्स्ट नहीं। एक भारी सत्र ~160 सेकंड में ~170–210 सूचना फ़्रेम धकेलता है। ✅
ACTIVITY_DATA (प्रत्येक 32 बाइट, LE) ✅कैलोरी इकाई: गतिविधि कैलोरी cal (ग्राम-कैलोरी) में रिपोर्ट की जाती हैं। दैनिक योग को 1000 से विभाजित करके kcal प्राप्त करें। (वर्कआउट-सारांश कैलोरी, इसके विपरीत, पहले से ही kcal में हैं।)
timestamp(i32 LE) ‖ value(i32 LE) (value = bpm / SpO₂ % / तनाव सूचकांक)।00DA 0001) अलग है — 5 बाइट: timestamp(i32 LE) ‖ hr(u8)। ✅ लाइव उदाहरण 5e dc 29 6a 4e → ts, hr = 78 bpm। तनाव स्कोर श्रेणियाँ: 1–29 / 30–59 / 60–79 / 80–99।SLEEP_DATA (18-बाइट हेडर + N × 8-बाइट रिकॉर्ड) ✅एक SLEEP_DATA = एक नींद सत्र; एक रात में कई हो सकते हैं (माइक्रो-जागरण सत्र विभाजित करते हैं)।
हेडर:
प्रत्येक 8-बाइट रिकॉर्ड: timestamp(u32) ‖ duration_s(u16) ‖ stage(u16)।
स्टेज कोड: 1 = Deep, 2 = Core/light, 3 = REM, 4 = Awake। ✅ पूरी रात के विरुद्ध मान्य (दो सत्र, D/C/R/A योग मेल खाते हैं)।
WORKOUT_SUMMARY v1 (54 बाइट) / _V3 (0160 0001)v1: start(u32), end(u32), duration_s(u32), फिर type/calories/steps/distance/avg-HR और एक GPS/विस्तारित ब्लॉक। ✅ v1 लेआउट फ़र्मवेयर के विरुद्ध पुष्टि की गई। WORKOUT_SUMMARY_V3 उसी डेटा के लिए एक नया लेआउट है साथ ही एक ~40-बाइट विस्तारित ब्लॉक (exerciseLoad, aerobic/anaerobic, recoveryTime, VO₂max, cadence, PAI, सर्वश्रेष्ठ-रन समय…)। फ़ील्ड सेट ज्ञात है (ऐप के Room DB से) लेकिन उस 40-बाइट ब्लॉक के भीतर सटीक बाइट ऑफ़सेट ⚠️ [अनिश्चित] हैं — उन्हें बंद करने के लिए GPS वर्कआउट के एक कच्चे कैप्चर की आवश्यकता है।
स्ट्रिंग्स UTF-8 हैं, फ़ील्ड आकार के लिए बाइट-ट्रंकेटेड (ट्रंकेशन एक मल्टी-बाइट वर्ण को विभाजित कर सकता है, फ़र्मवेयर के s.encode()[:max] व्यवहार से मेल खाता है); छोटे फ़ील्ड दाईं ओर शून्य-पैडेड हैं।
0065 0001) ✅: iconCode(1) ‖ 0x00 ‖ when(u32 BE) ‖ titleLen(1) ‖ title ‖ body। iconCode ऐप आइकन चुनता है (WhatsApp=8, Telegram=12, Instagram=18, Gmail=27; अज्ञात=0xFF)। शीर्षक ≤ 20 बाइट, बॉडी ≤ 128 बाइट। क्लाइंट से भेजा गया → वॉच ने प्रदर्शित किया + ACK 0065 0003।005C 0001) ✅: उत्तर = level(1) ‖ charging(1) (जैसे 3b 00 = 59 %, चार्ज नहीं हो रहा)।00DE 0001) ✅: len(1) ‖ ASCII (जैसे 10 + "CI04102520008192")।0095 0001) ✅: height_cm(1) ‖ weight_kg(1) ‖ age(1) ‖ gender(1: 1=M) (जैसे = 172 सेमी / 73 किग्रा / 31 / पुरुष)।now/utc_offset को स्पष्ट पैरामीटर के रूप में लेते हैं (नियतात्मक, परीक्षण योग्य)। परिवहन वास्तविक समय प्रदान करता है।TIME सब कुछ गेट करता है (§4.3) — इसे पहले भेजें या वॉच डेटा क्वेरी पर मौन रहती है।GOALS_SET और GPS_PUSH बिग-एंडियन हैं, और बल्क-ट्रांसफर ऑफ़सेट/लंबाई बिग-एंडियन हैं।वॉच समर्थन करती है (a) फोटो/कस्टम डायल (एक पृष्ठभूमि छवि + एक फ़र्मवेयर-चित्रित डिजिटल घड़ी) और (b) संरचित डायल (अंतर्निहित / स्टोर फेस: एक पृष्ठभूमि प्लस स्थित स्प्राइट परतें, हाथ, और टेक्स्ट विजेट)। दोनों §6 में init → चंक लूप के माध्यम से डेटा चैनल पर स्थानांतरित होते हैं।
वास्तव में क्या काम करता है (✅ लाइव मान्य): किसी भी छवि से फोटो डायल बनाना और उसे स्थापित करना; 103 स्टोर डायल में से किसी को ऑफ़लाइन स्थापित करना; एक संरचित डायल को रीस्किन करना (पृष्ठभूमि या किसी गैर-पृष्ठभूमि स्प्राइट को स्वैप करना) और उसकी परतों को स्थानांतरित करना; सक्रिय फेस को पुनःक्रमित / स्विच करना; और शुरू से एक संरचित डायल बनाना —
0x20दृश्य लिफाफा``` INIT1 8052 (payload A5) → 0052 [0]=01 INIT2 9063 (photo, APPEND) | 9075 (structured, REPLACE) → A063 / A075 [0]=01 [ watch → DATA_CHUNK_REQUEST A064 (offset, length; u32 BE, +progress u8) phone → DATA_CHUNK_WRITE 9064 (bytes[offset..offset+length], plaintext) ] × N FINISH A065 → 9065 (payload A5)
Finish reply byte: `01` = सक्रिय और सहेजा गया; `0a` = संग्रहीत लेकिन **सक्रिय नहीं** / अस्वीकृत।
Android पर प्रत्येक `DATA_CHUNK_WRITE` को **प्रति फ्रेम एक BLE राइट** के रूप में भेजा जाना चाहिए —
कॉनकैटेनेट करना और MTU द्वारा फिर से स्लाइस करना हेडर को डीसिंक कर देता है और घड़ी ऑफसेट 0 के लिए लूप करती रहती है।
- **`9063` (फोटो) = APPEND।** डायल सूची बढ़ती है (6→7); `watchfaceId = 0xFFFFFFFF` (कस्टम
सेंटिनल) ताकि इसे डुप्लिकेट के रूप में कभी अस्वीकार न किया जाए, और घड़ी इसे स्वतः सक्रिय कर देती है।
- **`9075` (स्ट्रक्चर्ड) = REPLACE** `old_id` स्लॉट। `old_id` **पहले से ही** सूची में होना **चाहिए**
(अन्यथा `0a`)। पहले से मौजूद किसी id को फिर से इंस्टॉल करने के लिए, **पहले उसे डिलीट करें** (9055 लिस्ट-माइनस-id)
फिर "फ्रेश" अपलोड करें — किसी id को उसी स्थान पर पुनः उपयोग करने पर `0a` मिलता है।
### 11.3 फोटो / कस्टम डायल — ✅ एंड-टू-एंड पूर्ण रूप से मान्य
**कंटेनर** (बाइट-सत्यापित राउंड-ट्रिप; सभी फ़ील्ड लिटिल-एंडियन):```
0x00 magic 6c 8d c4 a5
0x04 count 12 00 00 00 (=18) [constant, NOT an element count]
0x08 00 × 8
0x10 lenFull u32 LE (length of the whole FULL block: tag+len+payload)
0x14 FULL tag 04 48 47 3a ‖ payloadLen(u32 LE) ‖ LZ4(RGB565-LE) → 466×466 [raw 434312 B]
THUMB tag 04 38 c4 21 ‖ payloadLen(u32 LE) ‖ LZ4(RGB565-LE) → 270×270 [raw 145800 B]
EOF-4 magic 6c 8d c4 a5 [trailer = magic repeated]
कोडेक = मानक LZ4 ब्लॉक, RGB565 लिटिल-एंडियन, टॉप-डाउन (payloadLen पहले LZ4 बाइट से गिना जाता है)। आधिकारिक ऐप LZ4-HC का उपयोग करता है और 21-बाइट LZ4-ब्लॉक हेडर/फुटर हटा देता है; एक सादा केवल-लिटरल्स LZ4 एन्कोडर भी काम करता है — घड़ी किसी भी मान्य LZ4 को स्वीकार करती है, बाइट-पहचान आवश्यक नहीं है। अंकित वृत्त (केंद्र 233,233, त्रिज्या 233) के बाहर के पिक्सेल 0x0000 पर सेट होते हैं।
9063 के लिए INIT_2 — सटीक हेडर (✅ यह वही है जो काम करता है):```
01 ‖ size(u32 BE) ‖ FF FF FF FF ‖ 01 01 01 ‖ styleId(u16 BE) ‖ posX(u16 BE) ‖ posY(u16 BE) ‖
color565(u16 BE) ‖ FF × 8
`size` = सटीक `.bin` लंबाई; `FFFFFFFF` = कस्टम `watchfaceId`; `styleId` 0–4 बिल्ट-इन
डिजिटल-क्लॉक लेआउट चुनता है (यह हमेशा खींचा जाता है — कोई "बंद" विकल्प नहीं है); `posX/posY` इसे स्थित करते हैं (ज्ञात-अच्छा
56 / 77); `color565` इसे रंग देता है (जैसे `FFFF` = सफेद)। ⚠️ छोटा `A5 ‖ size ‖ watchfaceId` रूप
finish `0a` के साथ **अस्वीकृत** है — ऊपर दिया गया पूरा हेडर उपयोग करें। (संदर्भ कार्यान्वयन:
`core-rust/engine.rs::build_wf_init2`, आधिकारिक ऐप में `C6135t.m31104u` को दर्शाता है।)
**विधि:** छवि को 466×466 (और एक 270×270 थंबनेल) में आकार बदलें, RGB565-LE टॉप-डाउन में बदलें,
वैकल्पिक रूप से सर्कल के बाहर के पिक्सेल शून्य करें, प्रत्येक को LZ4-संपीड़ित करें, ऊपर दिया गया कंटेनर इकट्ठा करें, और
`watchfaceId = 0xFFFFFFFF` के साथ `9063` पाइपलाइन के माध्यम से अपलोड करें। (संदर्भ कोडेक: `core-rust/watchface.rs`,
`work/codec_dfa.py`।)
### 11.4 संरचित / स्टोर डायल — कंटेनर और कोडेक ✅
**फ़ाइल लेआउट** — 36-बाइट हेडर को EOF पर **बाइट-समान रूप से 36-बाइट फुटर के रूप में दोहराया जाता है**
(✅ 15 डायल पर सत्यापित; एक पार्सर को ऐसी फ़ाइल को अस्वीकार करना चाहिए जहाँ वे भिन्न हों):```
[36-byte header][scene TLV (§11.7)][asset pool][36-byte header again]
हेडर (सभी 103 स्टोर डायल में समान संरचना; सभी फ़ील्ड लिटिल-एंडियन हैं):``` 0x00 crc_tree u32 LE [CRC32-raw of header[0x04:0x24] ‖ scene section] ✅ see below 0x04 magic 01 00 00 XX [XX = 0x00 or 0x02; both seen, meaning of 0x02 unknown] 0x08 name char[16] [NUL-terminated, e.g. "SlopeTime", "Metaball"; may carry a non-zero tail after the NUL (@0x17) — round-trip it verbatim] 0x18 size_a u32 LE [= filesize − 36 = footer offset = header+body] ✅ 103 dials 0x1c size_b u32 LE [asset-pool length, exactly] ✅ 15 dials 0x20 crc_assets u32 LE [CRC32-raw of the asset pool] ✅ see below 0x24 … [body starts here: the 0x20 scene container, §11.7]
> ⚠️ **सुधार (पिछले "कोई ब्लॉकिंग चेकसम नहीं है" को प्रतिस्थापित करता है)।** पहले के संशोधनों में `@0x00` को एक
> प्रति-डायल id/हैश के रूप में और `@0x20` को "3× u32 id/हैश शब्द \[CRC नहीं]" के रूप में पढ़ा गया था, और कहा गया था कि CRC32/Adler32/
> बाइट-सम सभी मेल खाने में विफल रहते हैं। दोनों शब्द **CRC32 हैं** — पहले के परीक्षण उन्हें चूक गए क्योंकि
> वैरिएंट गैर-मानक है, और क्योंकि "`0x20` पर 3 शब्द" की व्याख्या एकल CRC
> शब्द को `0x24` पर शुरू होने वाले सीन कंटेनर के पहले बाइट्स के साथ मिला रही थी (इसी तरह "`0x2c` पर दोहराया गया नाम"
> सीन का `0x86` नाम नोड है, §11.11)। खोज
> [freethinkel/fmc](https://github.com/freethinkel/fmc) से; यहाँ पुनः सत्यापित।
**CRC32-raw** = परावर्तित IEEE बहुपद `0xEDB88320`, **`init = 0`**, और **कोई अंतिम XOR नहीं** — अर्थात
न तो मानक `crc32` का `init=0xFFFFFFFF` और न ही `^0xFFFFFFFF`। यही पूरा कारण है कि
तैयार-मिला CRC32 कभी मेल नहीं खाता। क्रम निर्भरता पर ध्यान दें: `crc_assets` उस सीमा के अंदर स्थित है जो
`crc_tree` द्वारा कवर की जाती है, इसलिए **पहले `@0x20` लिखें, फिर `@0x00` की गणना करें**।```python
def crc32_raw(data: bytes) -> int: # tab = standard 0xEDB88320 reflected table
c = 0 # init 0, no final inversion
for b in data: c = tab[(c ^ b) & 0xFF] ^ (c >> 8)
return c & 0xFFFFFFFF
crc_tree = crc32_raw(f[0x04:0x24] + f[0x24:first_asset])
crc_assets = crc32_raw(f[first_asset:len(f)-36])
सत्यापित: 9/9 शुद्ध स्टोर डायल दोनों शब्दों पर मेल खाते हैं, और इस रिपॉजिटरी के अपने 6/6 टेम्पलेट
crc_tree पर मेल खाते हैं।
🟡 फर्मवेयर किसी भी CRC को लागू करता हुआ प्रतीत नहीं होता। इस रिपॉजिटरी ने
9075के ऊपर जो भी डायल इंस्टॉल किए हैं — जिसमें समान-फुटप्रिंट इन-प्लेस एडिट पथ (§11.6) द्वारा निर्मित रीस्किन भी शामिल हैं, जो एसेट पेलोड और X/Y बाइट्स को हेडर की पुनर्गणना किए बिना बदलता है — डिवाइस पर ठीक से रेंडर हुए। इसलिए पुराना CRC वह चीज़ नहीं है जो0aअस्वीकृति का कारण बनती है (वह कंटेनर-विंडो इनवेरिएंट है, §11.7)। CRCs को वैसे भी सही-लिखें मानें: सस्ते, और फॉर्मेट में एकमात्र ज्ञात इंटीग्रिटी फ़ील्ड। जो भी सीन या एसेट पूल को फिर से लिखता है, उसे दोनों शब्दों की पुनर्गणना करनी चाहिए।
स्टब डायल (~173 B, जैसे ids 273/274/277) ROM में बेक किए गए फेस के लिए प्लेसहोल्डर हैं: हेडर + डायरेक्टरी, कोई वास्तविक एसेट नहीं।
एसेट्स — प्रत्येक dimsWord(u32 LE) ‖ len(u32 LE) ‖ LZ4(payload) है, जहाँ
cf = dimsWord & 0x1f, w = (dimsWord >> 10) & 0x7FF, h = (dimsWord >> 21) & 0x7FF, और len
पहले LZ4 बाइट से गिना जाता है (वह 1f 00 01 00 जो आप अक्सर वहाँ देखते हैं, पहला LZ4 टोकन है — इसे
छोड़ें नहीं)। डीकंप्रेस्ड आकार = w·h·bpp:
✅ 103 डायल्स में सभी 4151/4151 एसेट्स w·h·bpp पर एक मानक lz4.block
डीकंप्रेसर के साथ बिल्कुल डिकोड होते हैं। पारदर्शिता अल्फा बाइट (cf=5/24) या 0x0000 (वृत्त के बाहर cf=4) है —
कोई RLE नहीं है और कोई "एस्केप" नहीं है। एन्कोड = पुनः-रास्टर → मानक LZ4 → [dimsWord][len][LZ4]।
9075 के लिए INIT_2 — AES-एन्क्रिप्टेड बॉडी:```
kind(1) ‖ old_id(u32 LE) ‖ new_id(u32 LE) ‖ file_len(u32 LE)
`kind` = `0x02`/`0x03`; `old_id` = वर्तमान सक्रिय डायल (`9055` से); `file_len` = वास्तविक `.bin`
आकार (= `@0x18 + 36`). स्टोर `.bin` को जैसा-का-तैसा इंस्टॉल करना गारंटीड रास्ता है (Ring Data id 359 +
102 अन्य पुष्टि किए गए). (संदर्भ: `core-rust/engine.rs::build_dial_replace_init`.)
### 11.5 संरचित निर्देशिका व्याकरण ✅ (डिकोड किया गया और लागू किया गया — संशोधित 2026-07-02)
> **⚠️ संशोधन (2026-07-02): नीचे दिया गया फ्लैट `61 01 00` रिकॉर्ड स्कीमा व्यवस्थित रूप से
> OFF-BY-ONE था।** सीन बॉडी एक साफ TLV है (§11.7); एक drawable **लीफ बॉडी** (टैग `0x30`/`0x38`
> स्टैटिक, `0x70` पॉइंटर) है:
>
> ```
> 01 xx 00 [X u16][Y u16] …attrs… 61 [count u16][base u32][count×id u16] [05 05 00 01 pivX pivY]
> ```
>
> - attr `0x01` बॉडी खोलता है: **X,Y = 466² कैनवास पर टॉप-लेफ्ट** (SDK के `sty_picture_t` का `s16 x,y`).
> - **फ्रेम टेबल `61 …` बॉडी को बंद करती है** (`base` = एसेट ptr; `count` 1 = इमेज, 10/11 =
> डिजिट एटलस — पुराना "रिकॉर्ड टाइप `0a/0b`" वास्तव में यही count था! — 7/13/2 = कॉम्प्लिकेशन
> फ्रेम शीट).
> - पॉइंटर एक्स्ट्रा: `0x01` attr के अंदर सोर्स+स्केल `[src] 00 3c 00`; पिवट `05 05 00 01 [pivX][pivY]`
> **ट्रेलर** में। **रोटेशन सेंटर = प्रति पॉइंटर `(X+pivX, Y+pivY)`** —
> कोई फिक्स्ड (233,233) नहीं: ऑफ-सेंटर सबडायल मौजूद हैं (जैसे डायल 366 की सुइयां 150,150 के आसपास घूमती हैं).
>
> `61 01 00` के लिए लीनियर स्कैन तत्व **N** के फ्रेम-टेबल+पिवट को तत्व **N+1** के X/Y
> (और टैग बाइट, पुराना "f3") से जोड़ रहा था — यह केवल एनालॉग डायल पर *सही दिखता* था जिनकी
> आसन्न सुइयां लगभग समान ज्यामिति साझा करती हैं। "कॉम्पैक्ट वेरिएंट दीवार" (spec 24 §24.4.5)
> यही गलत पठन था। `core-rust/watchface_struct.rs` में `scan_scene_drawables` के रूप में लागू किया गया
> और `wfweb/src/codec/parse.ts` (सीन = इमेज/पॉइंटर्स के लिए प्राथमिक स्रोत; फ्लैट स्कैन टेक्स्ट +
> नॉन-एनवेलप फॉलबैक के लिए रखा गया). `wfweb/compare.html` ओरेकल द्वारा मान्य (रेंडर बनाम आधिकारिक
> स्टोर PNG, 99 डायल): 64→72 अच्छे, 8→5 खराब, माध्य अंतर 9.3→7.4%.
ऐतिहासिक फ्लैट-रिकॉर्ड पठन (प्रतिस्थापित, संदर्भ के लिए रखा गया):
- **स्टैटिक इमेज** (`61 01 00`): `asset_ptr(u32) ‖ elemId(u16) ‖ 05 05 00 01 ‖ pivotX(u16) ‖
pivotY(u16) ‖ 3B ‖ 01 ‖ 1b 00 ‖ X(u16) ‖ Y(u16)`. 466² कैनवास पर टॉप-लेफ्ट = `(X−pivotX, Y−pivotY)`.
- **पॉइंटर/सुई** — वही इमेज रिकॉर्ड, रनटाइम पर घुमाया गया। **रोटेशन सेंटर = `(X+pivotX, Y+pivotY)`**
(एनालॉग डायल पर ≈ 233,233). **डेटा स्रोत रिकॉर्ड ऑफसेट `+36` पर एक `u8` है**, स्केल `u16` at
`+38` (=60): `0x0a`/`0x70` = घंटा (`h·30°+m·0.5°`), `0x0e`/`0x71` = मिनट (`m·6°+s·0.1°`),
`0x12`/`0x72` = सेकंड (`s·6°`). ✅ गेटर्स को डिसअसेंबल करके पुष्टि की गई (RTC फॉलबैक 10:10:30).
- **टेक्स्ट / नंबर विजेट** (`61 0a 00`): `asset_ptr(u32) ‖ [10×u16 फॉन्ट मेट्रिक्स] ‖ 40 01 00 ‖ फ्लैग ‖
3B ‖ 01 ‖ u16 ‖ X(u16) ‖ Y(u16)`. `asset_ptr` ग्लिफ "0" की ओर इशारा करता है; अंक *d* = एसेट at
`index("0") + d` (10 क्रमागत cf=5 स्प्राइट, जैसे `0123456789` और `,°` विराम चिह्न). ✅ रेंडर किया गया.
- **कॉम्प्लिकेशन फिल = फ्रेम-इंडेक्स** (✅ डिजिट/एनम/गेज कॉम्प्लिकेशन के लिए पुष्टि, count>1): मान
`.bin` में एक **प्री-रेंडर फ्रेम शीट** को इंडेक्स करता है — `frame = (count−1)·val/100` (प्रतिशत) या
`frame = value` (फ्लिप डिजिट / एनम). फ्रेम टेबल = सब-रिकॉर्ड `61 ‖ count(u16) ‖ base(u32) ‖
count×id(u16)`. जैसे 327 Digit Max का बड़ा घंटा 13-फ्रेम शीट है (नंबर 0–12), `frame = hour`.
- **प्रोग्रेस रिंग / आर्क = रनटाइम सेक्टर क्लिप** (✅ 2026-07-02, **spec 25 §2 में "रिंग फ्रेम शीट हैं"
पठन को सही करता है**): तत्व टैग **`0x81`** एक **एकल** फुल डिस्क रखता है (`61` फ्रेम-टेबल
`count == 1`), और आंशिक वेज वह डिस्क है जो **पाई सेक्टर में क्लिप की गई है** (`frac = value/max`,
12 बजे से घड़ी की दिशा में) — 322 Glare 2 पर पिक्सेल-दर-पिक्सेल सत्यापित और **20 डायल** पर `count==1`
पुष्टि की गई. डिस्क पर: `0x81` बॉडी = सब `0x01` (ज्यामिति `x@+0 y@+2 w@+4 h@+6`, इनलाइन `61 1 base`
= डिस्क) + सब `0x5b` (आर्क स्पेक). wfweb में लागू (`blendSector`).
⚠️ `0x5b` सब-रिकॉर्ड **केवल "`max` u16 `@+4`" नहीं है** जैसा पहले दस्तावेज़ित किया गया था — वह
`max i32` के निचले आधे हिस्से को पढ़ता था और **स्टार्ट/एंड स्वीप कोण और स्ट्रोक चौड़ाई** को चूक गया जो
उसके ठीक बाद बैठते हैं। एक प्रोसीजरल सिबलिंग `0x80`/`0x5a` भी है (स्पष्ट त्रिज्या के साथ) जिसे यह
दस्तावेज़ कभी कवर नहीं करता था। पूर्ण रिकॉर्ड लेआउट, और पिछली "12 बजे से घड़ी की दिशा में"
धारणा क्या गलत थी: **§11.15**.
- **डेटा स्रोत id** — तत्व का `82` attr-ब्लॉक `delim+3` पर बैठता है (अंतिम `40 01 00` के बाद),
और **स्रोत id `+0x14` पर एक `u8` है** (साथ ही `relX@+0x07 s16`, `relY@+0x09 s16`, `anchor@+0x0C/0E`,
`mode@+0x15`, `frame-count@+0x1A`). Anchor < 0 = माता-पिता के किनारे पर संरेखित करें. 🔎 फर्मवेयर
id को `0x101f371c` पर 142-एंट्री गेटर टेबल के माध्यम से हल करता है (प्रत्येक `ux2sys_get(type)` कॉल करता है).
⚠️ **id को संरचना के `meta[9]` के रूप में पढ़ना पसंद करें (§11.11)** — एक फिक्स्ड फील्ड — इस फॉरवर्ड
`82`-attr स्कैन के ऊपर, जो §11.8/§11.9 में वर्णित ऑफ-बाय-वन स्रोत है। पूर्ण id तालिका §16 में; ध्यान दें
कि स्वास्थ्य/मौसम लेबल जो इस अनुभाग ने पहले इनलाइन सूचीबद्ध किए थे (`0x19` HR, `0x1b` बैटरी,
`0x24` तापमान, `0x36` कदम) **विवादित और संभवतः गलत हैं** — §16 में ⚠️ बॉक्स देखें।
(नीचे `0x07:0x0b:0x0f` = HH:MM:SS समूह उदाहरण अप्रभावित है.)
- **ग्रुप नोड** (`0x68`): अपने बच्चों को अपने स्वयं के TLV बॉडी के अंदर नेस्ट करता है (`0x60` = मान/टेक्स्ट,
`0x30` = स्टैटिक); प्रत्येक `0x60` अपना स्रोत id `data+16` पर रखता है। जैसे एक ग्रुप `0x07:0x0b:0x0f` =
HH:MM:SS घड़ी। TLV तत्व पार्सर = `0x100db55c` (जंप टेबल `tag−0x70` द्वारा अनुक्रमित).
### 11.6 ऑथरिंग मैट्रिक्स
| पथ | स्थिति | नोट्स |
|---|---|---|
| किसी भी इमेज से फोटो डायल | ✅ **पूर्ण** | §11.3; डिवाइस पर मान्य |
| 103 स्टोर डायल में से कोई भी इंस्टॉल करें | ✅ **पूर्ण** | §11.4; `9075`, `old_id`=सक्रिय |
| स्टोर डायल का cf=4 बैकग्राउंड रीस्किन करें | ✅ **लाइव काम करता है** | पूर्ण पेलोड को जगह पर स्वैप करें, एसेट `len` को **नए** ब्लॉक आकार (≤ पुराना) पर सेट करें, समान फ़ाइल फुटप्रिंट रखें, फ्रेश-इंस्टॉल |
| टेम्प्लेटिंग द्वारा पुनः-ऑथर करें (किसी भी परत के पिक्सेल स्वैप करें + ज्यामिति स्थानांतरित करें) | ✅ **BLE के माध्यम से रेंडर होता है** | डायल 373: bg→सियान + एक cf=5 स्प्राइट→लाल + X 224→100 स्थानांतरित, सभी रेंडर, सुइयां लाइव |
| स्क्रैच से 100 %-सिंथेटिक संरचित डायल | ✅ **बिल्डर पूर्ण, ऑफलाइन-मान्य** | `watchface_struct.rs` में `0x20` एनवेलप बिल्डर (`build_container`/`serialize`/`validate_container`); सभी 103 डायल बाइट-एक्सैक्ट राउंड-ट्रिप करता है + सिंथेटिक फर्मवेयर वैलिडेटर (§11.7) पास करता है। 🟡 `9075` पर ऑन-डिवाइस रेंडर अभी फिल्माया नहीं गया |
| सिस्टम फॉन्ट (`.font`) | ✅ **डिकोड/रेंडर (सभी)** | LVGL bin (मालिकाना नहीं); 32 नंबर फॉन्ट (`num*/nm*`, असम्पीडित) + 24 टेक्स्ट फॉन्ट (`font*`, LVGL RLE `comp=1`) सभी डिकोड — 12208 ग्लिफ, 0 ओवररन, पूर्ण ASCII। RLE = LVGL v8.3 `lv_font_fmt_txt.c` (3-स्टेट SINGLE/REPEATE/COUNTER + प्रति-पंक्ति XOR प्रीफिल्टर), 1:1 पोर्टेड, कोई डिसअसेंबली नहीं |
⚠️ रीस्किन/पुनः-ऑथर नुकसान जो ब्लैक स्क्रीन या `0a` का कारण बनते हैं: पुराने **एसेट `len`** को छोड़ना (घड़ी
ब्लॉक से आगे पढ़ती है → ओवररन → ब्लैक); **फ़ाइल को बढ़ाना** (इंस्टॉल पर अस्वीकृत); किसी id को फ्रेश
इंस्टॉल के बजाय **जगह में** पुनः उपयोग करना; रेफ-टेल ब्लॉक-साइज़ श्रृंखला (§11.11) को ठीक किए बिना
**एसेट्स को पुनः क्रमबद्ध करना**। आज घातक नहीं है लेकिन फिर भी इसे सही लिखें: सीन या एसेट पूल में कोई भी
परिवर्तन दो हेडर **CRC32** शब्दों को अमान्य कर देता है — दोनों की पुनर्गणना करें (§11.4), `@0x20` पहले `@0x00`।
### 11.7 `0x20` सीन एनवेलप — डिकोड किया गया और बिल्डर लागू ✅
एक पुनः-ऑथर **वास्तविक** डायल रेंडर होता है क्योंकि यह फ़ाइल के सीन एनवेलप को संरक्षित करता है। एक विशुद्ध सिंथेटिक
फ्लैट `61 …` रिकॉर्ड का बॉडी **अस्वीकार** किया जाता है — फर्मवेयर पार्सर (`WFManager_Parser`, `0xdb35c`)
आवश्यकता है कि बॉडी (ऑफसेट `0x24` से) `0x20` सीन कंटेनर से शुरू हो। पूर्ण फ़ाइल है:```
[0x00,0x24) header: perDialId@0 · version=1@4 · name[16]@8 · size_a@0x18 · size_b@0x1c · idWord0@0x20
[0x24, fa) scene: 20 <u16 L0> ( 21 <u16 L1> ( 86 <len>=name , 30/70/80/81… drawables ) [ 22 … AOD ] )
[fa, EOF) assets: [dimsWord u32][len u32][payload = 1f 00 01 00 + LZ4] …
size_a = filesize−36 · size_b = filesize−36−first_asset · 0x27+L0 == first_asset
दृश्य एक स्वच्छ नेस्टेड TLV है — [tag u8][len u16 LE][body], कंटेनर टैग 0x20/0x21/0x22/0x68
रीकर्स करते हैं, लीफ ड्रॉएबल 0x30 (स्टैटिक) / 0x70 (एलिमेंट/पॉइंटर) / 0x80 / 0x81 / 0x86 (नाम)।
(फ्लैट 61 01 00 / 61 0a 00 रिकॉर्ड वे पैटर्न हैं जो ड्रॉएबल बॉडी के अंदर रहते हैं; पुराना
पार्सर उन्हें ह्यूरिस्टिक रूप से ढूंढता था — और आसन्न बॉडी को आपस में जोड़ देता था, §11.5 संशोधन देखें।
ड्रॉएबल बॉडी लेआउट अब वहाँ पूरी तरह से डिकोड किया गया है।) हर चाइल्ड का offset+len उसके
पैरेंट की विंडो के अंदर फिट होना चाहिए;
पहला बॉडी बाइट ≠ 0x20 → पार्सर एरर −16; विंडो से बाहर जाने वाला चाइल्ड → −2; दोनों में से कोई भी
9065 हैंडलर (0xeb50c) को फिनिश लिखने पर मजबूर करता है।
बिल्डर लागू और ऑफलाइन-वैलिडेटेड है (core-rust/watchface_struct.rs:
SceneNode / serialize / parse_scene / validate_container / build_container /
build_container_raw; CLI cmfwatch-wfgen reframe):
scene_roundtrip_identity — सभी 103 स्टोर डायल: parse_scene→serialize दृश्य को
बाइट-दर-बाइट पुनरुत्पादित करता है (पुनर्गणित नेस्टेड len मेल खाते हैं) और validate_container हर एक पर पास होता है।build_reframe_identity / CLI reframe — पूरे .bin को शुरू से फिर से जोड़ना फ़ाइल को
बाइट-दर-बाइट पुनरुत्पादित करता है 1 नाम-पैडिंग बाइट को छोड़कर (@0x17; चेकसम नहीं)।build_container_synthetic — एक नया डायल (बैकग्राउंड + 20→21 में नेस्टेड ड्रॉएबल) बनाता है जो
फर्मवेयर के सटीक इनवेरिएंट को पास करता है (build_container सही नेस्टेड विंडो उत्सर्जित करता है)।validate_rejects_bad_containers — एक फ्लैट बॉडी को अस्वीकार करता है (→ , ऐतिहासिक
बग) और एक चाइल्ड जो अपनी विंडो से बाहर जाता है (→ )।🟡 अभी भी अप्रमाणित (वॉच की आवश्यकता है, गैर-अवरोधक): 9075 पर शुरू से सिंथेटिक अपलोड करना
और उसे रेंडर होते देखना — ऑफलाइन संरचनात्मक प्रमाण पहले से ही उस चीज़ को कवर करता है जिसने 0a अस्वीकृति का कारण बना।
wfweb रेंडर को आधिकारिक स्टोर थंबनेल (सभी 103 डायल पर पिक्सेल ओरेकल) के साथ क्रॉस-रेफरेंस किया और चार अंतराल बंद किए:
i16 (साइन्ड) हैं। ✅ एंकर नेगेटिव हो सकते हैं उन एलिमेंट्स के लिए जो
कैनवास से बाहर फैलते हैं — जैसे 275 का लाल सेकंड हैंड Y = 0xFFFC = −4 पर बैठता है (एक 30×281 स्प्राइट,
स्रोत 0x12, केंद्र से ऊपरी किनारे से घुमाया गया)। X/Y को u16 (65532) के रूप में पढ़ने से
गार्ड इसे छोड़ देता था। दोनों को साइन्ड के रूप में पार्स करें और एक छोटी नेगेटिव रेंज की अनुमति दें।0x60 img_numbers हो सकते हैं (केवल 0x68 ग्रुप के अंदर नहीं), और
असली डेटा स्रोत रिकॉर्ड ऑफसेट −5 पर u8 है — फॉरवर्ड 82-एट्र स्कैन यहाँ
व्यवस्थित रूप से ऑफ-बाय-वन है और अगले सिबलिंग की एट्र पकड़ लेता है (275 में मिनट अंक
ने वीकडे 0x18 उठाया)। 275 का "10:10" = घंटा 0x07@X≈306 + मिनट 0x0b@X≈369 जिसमें
उनके बीच एक आसन्न स्टैटिक है, प्रत्येक एक 11-ग्लिफ़ एटलस () है। ⚠️ X/Y को
से सही करते समय, , या एक री-एक्सपोर्ट उन बाइट्स को दूषित कर देता है (समान-फुटप्रिंट तोड़ता है → )।इसके अलावा: आधिकारिक स्टोर थंबनेल 10:10 पर रेंडर किए जाते हैं (क्लासिक मार्केटिंग समय), 10:12 नहीं — ओरेकल समय को 10:10 से मिलाने से मीन पिक्सेल-डिफ़ उल्लेखनीय रूप से गिरता है। wfweb का पार्सर अब सभी 103 डायल को बाइट-एक्सैक्ट राउंड-ट्रिप करता है (ऊपर X/Y राइट-ऑफसेट फिक्स ने अंतिम बेमेल साफ़ किए)।
0x22 AOD कंटेनर को अपने अलग दृश्य में अलग करें। ✅ सीन वॉकर पहले से ही 0x22 को छोड़ देता है,
लेकिन फ्लैट टेक्स्ट/नंबर स्कैन पूरे [0x30, firstAsset) को चलता था — इसलिए यह
प्रत्येक एलिमेंट के ऑलवेज़-ऑन (AOD) वेरिएंट को एक सामान्य लेयर के रूप में उत्सर्जित करता था। "Gradient" पर AOD ग्रे डेट
एटलस (0x22 में ऑफसेट) सामान्य लाल के ऊपर खींचा गया। फिक्स: हर 0x22 रिकॉर्ड को
layer.aod=true के साथ टैग करें (अपने स्वयं के डेडुप सेट के साथ) और renderAt(…, aod) को उन्हें केवल AOD मोड में दिखाने दें
(सामान्य मोड aod लेयर छुपाता है; AOD मोड सामान्य लेयर छुपाता है; बैकग्राउंड setAod द्वारा स्वैप किया जाता है
और हमेशा खींचा जाता है)। कॉर्पस में सामान्य मोड में शुद्ध ओरेकल जीत (284: 31%→21%, +18 अन्य) —
AOD वेरिएंट कई डायल पर ओवरड्रॉ कर रहे थे — और एडिटर का AOD टॉगल अब वास्तविक
ऑलवेज़-ऑन लेआउट दिखाता है न कि सामान्य वाले। असली AOD एक काली स्क्रीन है (कोई डिम्ड सीन नहीं):
यदि डायल में समर्पित AOD बैकग्राउंड फ्रेम (dial.aod) नहीं है, तो सामान्य सीन AOD मोड में छिपा होता है
ताकि यह काला + 0x22 एलिमेंट्स को उनके अपने रंग में रेंडर करे। AOD भी सीन वॉकर के माध्यम से पार्स होते हैं
(यह अब कंटेनर में रीकर्स करता है जो ड्रॉएबल्स को टैग करता है, उन्हें फ्लैट स्कैन पर छोड़ने के बजाय जहां उनका पिवट मेल नहीं खाता था → "अनपोज़िशन्ड"); AOD हैंड्स कैनवास
केंद्र पर घूमते हैं ( कभी-कभी एक ऑफ-सेंटर हैंड x/y ले जाता है जिसे फर्मवेयर अनदेखा करता है — जैसे
Gradient का घंटा )। एडिटर इसे एक के रूप में भी उजागर करता है
(§UI): प्रत्येक स्क्रीन केवल अपनी लेयर दिखाता है और संपादन स्वतंत्र रूप से बने रहते हैं। सामान्य-मोड रेंडर
पूरे समय बाइट-समान है; राउंडट्रिप सभी 103 डायल पर बाइट-एक्सैक्ट रहता है।40 01 00 XX बाइट (✅ फर्मवेयर-पुष्टि)एक img_number कितने अंक खींचता है यह फ़ील्ड रिकॉर्ड में एक एकल बाइट है — एलिमेंट के
40 01 00 XX एट्रिब्यूट सब-रिकॉर्ड का डेटा बाइट XX (वह 0x40 सब-रिकॉर्ड जो
61 [count][base][glyph-ids] फ्रेम टेबल के बाद बैठता है):
XX & 0x0F = अंक स्लॉट की संख्या (0 ⇒ फर्मवेयर डिफ़ॉल्ट 7)।0x80 = ज़ीरो-पैड (अग्रणी शून्य दिखाएं, जैसे "09" बनाम "9")।फर्मवेयर को डिसअसेंबल करके पुष्टि की गई (XIP इमेज 0x10000000; रेंडर रूटीन 0x100d8e60):
NDIG = ldrb[40sub+3] & 0x0F (→7 यदि 0); मान value % 10^NDIG पर क्लैम्प किया जाता है और ठीक NDIG
ग्लिफ़ MS-पहले खींचे जाते हैं, अग्रणी शून्य दबाए जाते हैं जब तक कि bit7 न हो। स्रोत के बाद u16 (डेट के लिए 60,
kcal के लिए 1000) गिनती नहीं है — यह केवल हजारों/मिलियन सेपरेटर-ग्लिफ़
सम्मिलन (cmp #1000/#1000000) को फीड करता है, यही कारण है कि इसे संपादित करने से कुछ नहीं हुआ। स्रोत आईडी भी कैप नहीं करती।
सभी 620 नंबर फ़ील्ड पर कॉर्पस हिस्टोग्राम मेल खाता है: 2-अंकीय फ़ील्ड (घंटा/मिनट/सेकंड/डेट/तापमान/HR) समाप्त
40 01 00 02/0x82; kcal …04; कदम …05; एकल-अंकीय क्लॉक स्प्लिट 0x81। तो डेट फ़ील्ड
40 01 00 82 = 2 अंक, ज़ीरो-पैडेड — यही पूरा कारण है कि एक रीबाउंड फ़ारेनहाइट तापमान
(≥100) छोटा कर दिया गया था।
फिक्स / एडिटर: wfweb नंबर फ़ील्ड के लिए digitCount/digitZeroPad (+digitCountOff) पार्स करता है,
इंस्पेक्टर में "Digits" + "Zero-pad" उजागर करता है, बाइट को इन-प्लेस (समान-फुटप्रिंट) लिखता है,
और प्रीव्यू फर्मवेयर को प्रतिबिंबित करने के लिए digitCount पर क्लैम्प/पैड करता है। तो किसी फ़ील्ड के स्रोत को रीबाइंड करना और
उसकी अंक गणना सेट करना किसी भी फ़ील्ड के लिए काम करता है (जैसे डेट→तापमान °F → Digits 3)। सामान्य-मोड
ओरेकल अपरिवर्तित (0 रिग्रेशन, 3 छोटे सुधार); राउंडट्रिप सभी 103 डायल पर बाइट-एक्सैक्ट।
(पहले की "Digits width"/rectW परिकल्पना गलत थी — चौड़ाई केवल लेआउट है, गिनती नहीं।)
struct रिकॉर्ड, और संसाधन-संदर्भ टेल ✅दृश्य (§11.7) एक स्वच्छ नेस्टेड TLV है — [tag u8][len u16 LE][body]। कॉर्पस में देखे गए अनुसार पूर्ण टैग इन्वेंटरी:
✅ यह इन्वेंटरी कॉर्पस के लिए पूर्ण है। केवल उपरोक्त कंटेनर टैग में रीकर्स करते हुए, जांचे गए सभी 15 डायल का सीन TLV
अपनी घोषित रूट लंबाई तक ठीक चलता है जिसमें शून्य अज्ञात टैग हैं — इसलिए एक पार्सर जो इस तालिका को संभालता है वह पूरे प्रारूप को संभालता है, और एक अज्ञात टैग का मतलब है
एक गलत संरेखित रीड, नई नोड प्रकार नहीं। (सावधान: एक वॉकर जो हर नोड में रीकर्स करता है जिसकी लंबाई
संयोग से ≥ 3 है, struct/0x5b बॉडी में उतरेगा और एक-बार के
"टैग" की एक लंबी टेल की कल्पना करेगा — लीफ बॉडी TLV नहीं हैं।)
💡 लेखन शॉर्टकट:
0x48/0x68ऑटो-लेआउट को पूरी तरह से छोड़ा जा सकता है — हर विजेट को स्क्रीन टॉप लेवल पर सीधे निरपेक्षx,yके साथ रखा जा सकता है, जो शुरू से बिल्डर (§11.7) करता है। केवल मौजूदा डायल पढ़ने के लिए आवश्यक है।0x8000कीmetaचौड़ाई एक struct को फ्रेम के ऑटो-लेआउट चाइल्ड के रूप में चिह्नित करती है (स्थिति पैरेंट से आती है,x,yसे नहीं)।
0x01 struct बॉडी — एक निश्चित 18-बाइट उपसर्ग जिसके बाद एक वैकल्पिक संसाधन-संदर्भ टेल है:```
+0x00 x i16 [signed — can be negative, see §11.8]
+0x02 y i16
+0x04 meta[14] ────────────────────────────────────────────────────
meta[0..1] w u16 [0x8000 = auto-layout child of a frame]
meta[2..3] h u16
meta[4..6] unknown [placeholder-looking (1,0,0)/(4,0,0); see §11.14]
meta[7] accent-tint capability flag — 4 = tintable (§11.14)
meta[9] DATA SOURCE ID (§16)
meta[10] sub / variant
meta[11..13] max u24 LE [the metric's nominal full-scale value]
+0x12 ref tail [61 …] — absent on imageless rings (0x80/0x81, §11.15)
> ✅ यह §11.5/§11.8/§11.9 के "मैजिक ऑफसेट" को एकीकृत करता है। वे अनुभाग फ़ील्ड को `0x61` फ्रेम-टेबल बाइट के *सापेक्ष* स्थित करते हैं — जो इस struct का केवल `+0x12` है, इसलिए `−18`/`−16` = `x`/`y` और
> **`−5` = `meta[9]`, स्रोत id**। समान बाइट्स, एक साफ लेआउट। आगे-स्कैनिंग `82`-attr
> ह्यूरिस्टिक जो व्यवस्थित रूप से एक-ऑफ थी, की बिल्कुल आवश्यकता नहीं है: struct के `meta[9]` को पढ़ें।
> एक संख्या फ़ील्ड का `max` भी केवल `meta[11..13]` है (जैसे दिन-का-महीना फ़ील्ड `max = 99` रखते हैं)।
**Ref tail** (`61`) — एक नोड अपने बिटमैप्स की ओर कैसे इंगित करता है:```
+0x00 0x61 [tail type]
+0x01 count u16 [1 = single image · 10/11 = digit atlas · N = pick-list / frame sheet]
+0x03 base u32 [ABSOLUTE FILE OFFSET of the first asset block]
+0x07 count × u16 = the BLOCK SIZE (8 + payload len) of each referenced asset, in order
⚠️ वे पीछे वाले u16s पहले "count×id(u16)" / ग्लिफ़ आईडी के रूप में दस्तावेज़ित किए गए थे। वे
ब्लॉक आकार हैं: base, साथ ही उनका चालू योग, एसेट पूल में प्रविष्टि दर प्रविष्टि चलता है (✅ एक डिजिट एटलस की सभी 10 प्रविष्टियों के लिए सटीक सत्यापित)। दो परिणाम:
count−1 आकार भार-वहन करने वाले हैं; अंतिम प्रविष्टि का मान कभी अनुसरण नहीं किया जाता, इसलिए
जंगली में फ़ाइलें कभी-कभी वहाँ एक पुराना मान रखती हैं। अंतिम प्रविष्टि पर बेमेल को टूटे हुए संदर्भ के रूप में न मानें।0x28 पूर्वावलोकन — स्टोर/कैटलॉग थंबनेल .bin में ही एम्बेडेड है (15 डायल्स पर 27 पूर्वावलोकन नोड्स), एक 0x08 pvStruct के रूप में: एक 5-बाइट उपसर्ग प्लस वही रेफ़ टेल, बिना x/y के।
अलग PNGs भेजे बिना गैलरी UI बनाने के लिए उपयोगी।
0x02 ✅एक विजेट का 0x02 सहोदर इसे सशर्त बनाता है। इसके बिना, विजेट हमेशा खींचा जाता है। व्याकरण:```
count u8 , count × ( id u8 , op u8 , val u24 LE signed ) [5 bytes per entry]
`id` एक डेटा-स्रोत id है (§16) — जिसमें §11.13 के **सिंथेटिक स्लॉट ids** भी शामिल हैं। ऑपरेटर, जिनकी घटना गिनती 15 डायल्स पर मापी गई है:
| op | अर्थ | देखा गया |
|---|---|---|
| `0x01` | ड्रॉ करें यदि `value == val` | 99 |
| `0x81` | `0x01` के समान (बिट `0x80` सेट — परस्पर-अनन्य वेरिएंट्स पर दिखता है) | 48 |
| `0x02` | **छिपाएँ** यदि `value == val` | 7 |
| `0x03` | ड्रॉ करें यदि `value == val`, जहाँ `val` एक **नो-डेटा मार्कर** है (जैसे HR `1000`) | 13 |
| `0x05` | ड्रॉ करें यदि `value >= val` | 58 |
| `0x06` | ड्रॉ करें यदि `value <= val` | 50 |
| `0x04` | ⚠️ **अज्ञात** — 15 घटनाएँ, कोई पुष्टि किया गया अर्थ नहीं | 15 |
संयोजन नियम (संदर्भ रेंडरर द्वारा लागू, मास्क `op & 0x7f`): समानता प्रविष्टियाँ **OR**-ed होती हैं, फिर hide/`>=`/`<=` प्रविष्टियाँ **सभी** मान्य होनी चाहिए।
यह एक तंत्र प्रारूप की अधिकांश रनटाइम परिवर्तनशीलता को कवर करता है, और उन संरचनाओं की व्याख्या करता है जो डुप्लिकेट विजेट्स जैसी दिखती हैं:
- **12h / 24h और मीट्रिक / इम्पीरियल लेआउट** — एक ही स्थान पर दो विजेट सेट स्टैक किए गए, प्रत्येक `id 0x73` (यूनिट्स फ्लैग) से बंधा हुआ है, जिसमें `val` 0 या 1 है। डायल 275 में ऐसे छह जोड़े हैं (12 नोड्स)।
- **"नो डेटा" प्लेसहोल्डर्स** — op `0x03` एक सेंटिनल के विरुद्ध, जैसे `id 0x5f, val 1000` (डायल 275, दो बार): मीट्रिक अनुपलब्ध होने पर तापमान के बजाय एम-डैश आर्ट ड्रॉ करें।
- **बकेटेड हाइलाइट्स** — एक युग्मित `0x05`/`0x06` रेंज, जैसे Metaball की चेन जहाँ प्रत्येक लिंक अपनी 5-मिनट की विंडो के लिए रोशन होता है।
- **कॉम्प्लिकेशन-स्लॉट विकल्प** — §11.13 के सिंथेटिक स्लॉट ids से बंधे हुए।
### 11.13 कॉन्फ़िगरेबल कॉम्प्लिकेशन स्लॉट्स — `0x85` + `0x5f` ✅ (§11.8 को प्रतिस्थापित करता है)
§11.8 ने निष्कर्ष निकाला था कि एक कॉन्फ़िगरेबल कॉम्प्लिकेशन का सक्रिय मीट्रिक डिवाइस RAM स्थिति है और इसे फ़ाइल से पुनर्प्राप्त नहीं किया जा सकता। **वह गलत था** — स्लॉट का मीट्रिक मेनू और उसका डिफ़ॉल्ट चयन दोनों `.bin` में हैं। प्रत्येक `0x85` नोड एक `0x5f` सिबलिंग रखता है:```
+0x00 slotIndex u8 [0-based position among sibling 0x85 nodes]
+0x01 count u8 [how many metrics this slot offers]
+0x02 activeIdx u8 [index into the list below = the DEFAULT SHOWN METRIC]
+0x03 count × u8 [the metric ids themselves (§16)] … NUL padding
वास्तव में जो विकल्प खींचे जाते हैं वे पेड़ में कहीं और सामान्य 0x68 समूह होते हैं, जिनमें से प्रत्येक सिंथेटिक id 0x79 + slotIndex पर एक 0x02 शर्त (§11.12) द्वारा गेट किया जाता है — इसलिए स्लॉट 0 के वेरिएंट 0x79 पर बंधते हैं, स्लॉट 1 के 0x7a पर, और इसी तरह। एक स्लॉट रेंडर करने के लिए: activeIdx पढ़ें, फिर उस वेरिएंट को खींचें जिसकी शर्त उस इंडेक्स से मेल खाती है।
वास्तविक डायल पर मापा गया:
डायल 275 के दो 6-मेट्रिक स्लॉट इसके 26 0x02 नोड्स में से 12 के लिए जिम्मेदार हैं, बिल्कुल अनुमान के अनुसार:
01 79 81 0X 00 00 और 01 7a 81 0X 00 00 for X = 0..5 — 0x79 (स्लॉट 0) पर कुंजीबद्ध छह विकल्प
और 0x7a (स्लॉट 1) पर छह। (वे 0x79/0x7a बाइट्स जिन्हें §11.8 ने "instance byte" कहा था, ये bind ids हैं।)
इसके शेष 14 असंबंधित हैं: 12 0x73 पर (24h/metric-units फ़्लैग, val 0 या 1 — छह
विजेट जोड़े 12h और 24h लेआउट के बीच स्वैप करते हैं) और 2 0x5f पर op 0x03 और val = 1000 के साथ,
तापमान no-data प्लेसहोल्डर।
अभी भी वास्तविक डिवाइस स्थिति: उपयोगकर्ता बाद में कंपेनियन ऐप में जो भी चुनता है वह रनटाइम पर
activeIdx को ओवरराइड करता है, इसलिए एक प्रीव्यू फ़ाइल डिफ़ॉल्ट को पुन: प्रस्तुत करता है, जरूरी नहीं कि
किसी दिए गए घड़ी पर जो दिखता है। एक 0x85 नोड का imgs[0] एक "tap to configure" प्लेसहोल्डर है जिसे फर्मवेयर केवल
अपने स्वयं के एडिट मोड में खींचता है — सामान्य समय-प्रदर्शन का प्रीव्यू करते समय इसे छोड़ दें।
meta[7] == 4 क्षमता फ़्लैग ✅कुछ डायल उपयोगकर्ता को डिवाइस पर एक accent रंग चुनने देते हैं, और फर्मवेयर इसे रेंडर समय पर
विजेट के बिटमैप में प्रतिस्थापित करता है। स्विच एक एकल बाइट है: struct का meta[7] (§11.11) —
यानी 0x01 बॉडी का बाइट +0x0B — 4 के बराबर होना उस विजेट के संसाधन(ओं) को tintable के रूप में चिह्नित करता है।
.bin अपने मूल पिक्सेल रखना चाहिए या आप उपयोगकर्ता की पसंद को स्थायी रूप से खो देंगे। टिंट केवल
प्रीव्यू/कैनवास पथ में लागू करें।⚠️ इसे रंग ह्यूरिस्टिक में "सुधारने" का प्रयास न करें — वह रास्ता एक सिद्ध मृत अंत है (fmc द्वारा कठिन तरीके से करने के बाद प्रलेखित)। सहज सिद्धांत यह है कि फ़्लैग किए गए पिक्सेल किसी पहचानने योग्य प्लेसहोल्डर रंग में बेक किए जाते हैं जिसे फर्मवेयर बदल देता है। यह काम नहीं कर सकता: डायल 348 Tumbler की tintable रिंग और डायल 282 Radar Sweep / 291 Vertical की सामान्य गैर-tintable अंक पट्टियाँ बिल्कुल वही
(255,72,32)RGB बेक करती हैं (हर पिक्सेल पर पूरी तरह जाँचा गया); और डायल 305 Dots (घंटे की सुई) और 306 Large Number (अंक) tintable हैं जबकि सादे सफेद बेक किए गए हैं, इसलिए एक रंग परीक्षण उन्हें पूरी तरह से चूक जाएगा। क्रमिक परिशोधन (1 → 4 संदर्भ रंग, साथ ही एक विजेट-भूमिका allowlist) सभी विफल रहे। फ़्लैग पढ़ें।वास्तविक डिवाइस / कंपेनियन ऐप के विरुद्ध 7 डायल पर क्रॉस-चेक किया गया, दोनों दिशाओं पर ज़ोर देने के लिए चुना गया — 349 Theatre, 376 Digits time, 305 Dots, 306 Large Number, 304 Elaborate 2 सभी accent सेटिंग प्रदान करते हैं और सभी में
meta[7]==4विजेट हैं; 316 Trailing (लाल रंग की सुई, कोई सेटिंग नहीं), 312 Disc और 295 Vortex कोई नहीं प्रदान करते हैं और उनमें शून्य फ़्लैग किए गए विजेट हैं।
meta[4..6] फ़्लैग के ठीक बगल में बैठता है और ऐसा लगता है कि कुछ structs पर यह एक रंग एन्कोड कर सकता है
(पूंछ f1=1,f2=255 के साथ एक वास्तविक दिखने वाला RGB, बनाम फ़्लैग किए गए structs के प्लेसहोल्डर (1,0,0)/(4,0,0))।
यह accent क्षमता के साथ सहसंबंधित नहीं है। ⚠️ अनसुलझा; इसे अनदेखा करें।
0x80/0x5a (प्रक्रियात्मक) और 0x81/0x5b (छवि-क्लिप किया गया) ✅दोनों रिंग किस्में एक छोटे struct (x, y, स्रोत id के साथ meta — आमतौर पर कोई
ref पूंछ नहीं) को एक arc-spec सहोदर के साथ जोड़ती हैं। §11.5 ने केवल "0x5b: max u16 @+4" प्रलेखित किया, जो
एक max i32 का निचला आधा है और स्वीप ज्यामिति को छोड़ देता है। पूरा रिकॉर्ड:```
+0x00 min i32 LE [always 0 in the corpus]
+0x04 max i32 LE [100 in the corpus, except dial 332 = 60]
+0x08 start i16 LE [sweep start, units of 0.1° — SIGNED]
+0x0a end i16 LE [sweep end, units of 0.1° — SIGNED]
+0x0c width u16 LE [stroke width in px]
+0x0e radius u16 LE [0x5a ONLY — 0x81 takes its radius from the clipped image]
+0x0e / +0x10 trailer 01 00 kk ⚠️ unresolved (see below)
युग्मन कठोर है: `0x80` हमेशा ठीक `0x01` + एक **19-बाइट** `0x5a` रखता है, `0x81` हमेशा ठीक
`0x01` + एक **17-बाइट** `0x5b` रखता है (✅ 15 डायल पर 26/26 रिंग)। ⚠️ लेकिन **इमेज-क्लिप्ड `0x81`
हावी है** — उन 26 में से 25। प्रोसीजरल `0x80`/`0x5a` वैरिएंट **एक बार** दिखा (डायल 273), इसलिए इसका
`radius` फ़ील्ड और 19-बाइट लेआउट एक ही नमूने पर टिका है; दोबारा देखे जाने तक संदेह के साथ व्यवहार करें।
`frac = clamp((value − min) / (max − min), 0, 1)`, और भरा हुआ आर्क `start` से `end` की ओर चलता है।
कोण शून्य **3 बजे** पर है, धनात्मक दक्षिणावर्त। मापे गए उदाहरण:
| डायल | टैग | min..max | start → end | चौड़ाई | त्रिज्या |
|---|---|---|---|---|---|
| 273 Activity Mood | `0x5a` | 0..100 | **−102.8° → 102.8°** | 42 | 222 |
| 273 Activity Mood | `0x5b` | 0..100 | 270.0° → 90.0° | 80 | (इमेज) |
| 276 Dichotomy | `0x5b` | 0..100 | 60.0° → −120.0° | 23 | (इमेज) |
| 304 Elaborate 2 | `0x5b` | 0..100 | −2.0° → 358.0° | 24 / 80 | (इमेज) |
| 366 Combo | `0x5b` | 0..100 | 0.0° → 270.0° | 18 | (इमेज) |
| 368 Function | `0x5b` | 0..100 | 0.0° → 360.0° | 20 | (इमेज) |
> ⚠️ **यह §11.5 में "12 बजे से दक्षिणावर्त" धारणा को सुधारता है।** वह केवल विशेष
> मामला है `start = 0, end = 3600` (एक पूर्ण चक्कर, जहाँ परंपरा अप्रत्यक्ष है)। असली डायल
> **आंशिक गेज** (273 का ±102.8° पंखा, 366 का 270° तीन-चौथाई रिंग) और **नकारात्मक चक्कर**
> (276 का 60° → −120°) उपयोग करते हैं, इसलिए एक रेंडरर जो हमेशा ऊपर से पूरा वृत्त घुमाता है वह उन्हें गलत बनाता है।
> 🟡 सटीक कोण-शून्य परंपरा और दिशा नियम fmc के रेंडरर से आते हैं, इन ऑन-डिस्क मानों के
> विरुद्ध क्रॉस-चेक किए गए — इस रिपो द्वारा डिवाइस पर पिक्सेल-दर-पिक्सेल स्वतंत्र रूप से सत्यापित नहीं।
> ध्यान दें `frac = value/max` और §11.5 में `0x81` सेक्टर-क्लिप परिणाम **सत्यापित** किए गए थे (डायल 322)।
⚠️ **अनसुलझा: 3-बाइट ट्रेलर `01 00 kk`।** `kk` प्रशंसनीय मान लेता है (104, 152, 216,
232, 248) और स्पष्ट परिकल्पना एक त्रिज्या है — **परीक्षण किया और खारिज किया**: डायल 366 `kk = 104`
दोनों 82×82 और 166×166 रिंग के लिए उपयोग करता है, और डायल 273 `kk = 232` दोनों 440×440 और 284×284
रिंग के लिए उपयोग करता है। त्रिज्या नहीं, व्यास नहीं। संभवतः अपारदर्शिता/शैली। इसे वैसे ही राउंड-ट्रिप करें।
🟡 **रिपोर्ट की गई समस्या, यहाँ असत्यापित:** fmc के रेंडरर में, एक `0x60` संख्या जिसका स्रोत id उसी स्क्रीन पर
किसी **भी** रिंग के id के बराबर है, कच्चे मान के बजाय `round(frac × 100)` रेंडर करती है — इसलिए
हृदय-गति रिंग के बगल में हृदय-गति संख्या `71 bpm` के बजाय `36` दिखाती है। उनका दस्तावेजीकृत
समाधान रिंग और संख्या को एक ही मीट्रिक के दो **उपनाम** ids पर रखना है (कदम
`0x19`/`0x26`/`0x49`, कैलोरी `0x1c`/`0x1e`/`0x48`)। यह फर्मवेयर व्यवहार है या उनके
रेंडरर के लिए विशिष्ट है — **स्थापित नहीं** — इसके आसपास डिज़ाइन करने से पहले लाइव जाँच के लायक।
---
## 12. बल्क ट्रांसफर और OTA विवरण
ट्रांसफर तालिका §6 में है। अतिरिक्त पुष्टि किए गए बिंदु:
- **AGPS/EPO** ✅: पहला लिखा गया चंक ASCII हेडर `000000010000…` से शुरू होता है। पूर्ण
init → `[A05F ↔ 905F]×N` → फिनिश लूप वायर पर देखा गया (~892 चंक)।
- **फर्मवेयर OTA** (`9040`–`9042`, फिनिश `9041`) 🔎: संरचना मैप की गई; INIT2 पेलोड = संस्करण बाइट्स
(जैसे `0b 00 00 39` = 11.0.0.57)। **फ़ील्ड-परीक्षण नहीं** (ऐप यहाँ FW अपडेट अक्षम करता है)। फर्मवेयर
इमेज **अहस्ताक्षरित प्रतीत होती हैं — अखंडता केवल CRC32 है** (RE में कोई असममित हस्ताक्षर नहीं देखा गया)।
- ⚠️ क्योंकि OTA और `FACTORY_RESET (009A 0001)` प्रमाणित सत्र साझा करते हैं, एक एकल वैध BLE
प्रमाणीकरण घड़ी को मिटाने या (सिद्धांत रूप में) ब्रिक करने के लिए पर्याप्त है। सावधानी से संभालें।
---
## 13. सेंसर
✅ BLE के माध्यम से हार्डवेयर उजागर:
- **ऑप्टिकल PPG** — हृदय गति (मैनुअल/ऑटो/वर्कआउट/आराम), SpO₂, और HRV-व्युत्पन्न तनाव।
- **3-अक्ष एक्सेलेरोमीटर** — कदम, दूरी, कैलोरी, नींद चरण, कलाई-उठाना, कैडेंस।
- **GNSS/GPS** (AGPS-सहायित) — वर्कआउट ट्रैक (`WORKOUT_GPS`) और स्थान पुश (`GPS_PUSH`)।
कोई **बैरोमीटर/अल्टीमीटर, कंपास, जाइरोस्कोप, या त्वचा/शरीर-तापमान सेंसर** नहीं है। एक आंतरिक
NTC थर्मिस्टर (बोर्ड/बैटरी तापमान) मौजूद है लेकिन केवल AT चैनल के माध्यम से पठनीय है
(`AT GETNTCTEMP`, §14) — इस SKU पर `0155` त्वचा-तापमान इतिहास स्ट्रीम खाली है।
**डेटा-विजेट अपहरण** (कोई वास्तविक कॉम्प्लिकेशन/डेटा-बाइंडिंग API नहीं है — §11.5 देखें): घड़ी के
मौजूदा टेक्स्ट फ़ील्ड को ग्लांसेबल बाहरी डेटा दिखाने के लिए पुनः उपयोग किया जा सकता है। सिद्ध ✅: मौसम
**शहर स्ट्रिंग** (`WEATHER_SET_1`, जैसे `"BRA 2x1 ARG"` विजेट पर दिखाई दी) और संगीत
**ट्रैक/कलाकार** फ़ील्ड; **संपर्क** सूची (20 × नाम[32]+संख्या[25]) एक स्क्रॉल करने योग्य डेटा
पैनल के रूप में काम करती है। सभी पुश हैं, स्थायी कॉम्प्लिकेशन नहीं।
---
## 14. AT फैक्ट्री / शेल चैनल (`77d4ff01` / `77d4ff02`)
फ्रेम किए गए प्रोटोकॉल से स्वतंत्र एक अलग सादा-पाठ AT कमांड चैनल। ✅ लाइव परीक्षण किया गया:
- **पढ़ें:** `AT GETSECRET` (16-बाइट पेयरिंग सीक्रेट), `GETVERSION`, `GETSN`, `GETNAME`, `GETPID`,
`GETBATLV` (कच्चा mV, जैसे `3853mv`), `GETGSENSOR` (कच्चा एक्सेल g में, `X=… Y=… Z=…`),
`GETNTCTEMP` (°C, आंतरिक NTC)।
- **लिखें / सक्रिय करें:** `AT SETMOTOR=1` (मोटर कंपन करें), `SETHR/SETHRV/SETSPO2=…` (सेंसर परीक्षण
इंजेक्शन), `SETLCDSWITCH/SETGPSSWITCH/SETKEYSWITCH`।
उत्तर `,OK` में समाप्त होते हैं। `SET*` कमांड आम तौर पर निष्पादित होते हैं लेकिन BLE पर `,OK` प्रतिध्वनित नहीं कर सकते — मामले के अनुसार पुष्टि करें।
---
## 15. फर्मवेयर-गेटेड / अनुपलब्ध सुविधाएँ (🔎 फर्मवेयर RE)
कुछ सुविधाएँ फर्मवेयर में मौजूद हैं लेकिन SKU/क्षेत्र द्वारा अक्षम हैं और **फोन/BLE से पहुंच योग्य नहीं हैं** —
उन्हें फर्मवेयर मॉड की आवश्यकता है, जो यहाँ दायरे से बाहर है:
- **ChatGPT वॉयस** — गेट = `ux2sys` फीचर id `0x9e`, बूट पर NVRAM/EFUSE/क्षेत्र से सीडेड; इस
SKU पर सपोर्ट फ्लैग `908b = 00`। फोन, खाते या BLE से प्रभावित नहीं (प्रयोग + RE द्वारा पुष्टि)। ऐप केवल एक रिले है; ऑडियो फोन → Nothing क्लाउड जाता है।
- **रक्तचाप** — फर्मवेयर में एक पूर्ण सबसिस्टम मौजूद है, SKU/क्षेत्र द्वारा बंद।
- **Alipay / NFC भुगतान** — पूर्ण UI मौजूद, केवल चीन-SKU।
- **हार्डवेयर/फर्मवेयर में अनुपस्थित:** ECG, SOS/आपातकाल, सामान्य NFC।
---
## 16. डेटा-स्रोत / कॉम्प्लिकेशन गेटर ids
BLE क्लाइंट बनाने के लिए आवश्यक नहीं, लेकिन **डायल लिखने या रेंडर करने के लिए आवश्यक**: यह `meta[9]` (§11.11) का मान है जो एक विजेट को लाइव डेटा से बांधता है, एक दृश्यता स्थिति का `id` (§11.12), और
एक स्लॉट के मीट्रिक मेनू की प्रविष्टियाँ (§11.13)। फर्मवेयर इसे `0x101f371c` पर **142-प्रविष्टि गेटर
डिस्पैच तालिका** के माध्यम से हल करता है, प्रत्येक प्रविष्टि `ux2sys_get(type)` कहती है (🔎 फर्मवेयर RE)।
### समय / दिनांक — ✅ अच्छी तरह स्थापित
| id | अर्थ | id | अर्थ |
|---|---|---|---|
| `0x01` | घंटा (डिवाइस सेटिंग के अनुसार 12/24 घंटे) | `0x0f`, `0x12` | सेकंड (स्मूथ) |
| `0x04` | घंटा (24 घंटे) | `0x10`, `0x11` | सेकंड दहाई / इकाई |
| `0x07` | घंटा (अनिवार्य 24 घंटे) | `0x71`, `0x72` | सेकंड (टिकिंग / हाथ कोण) |
| `0x02`, `0x03` | घंटा-12 घंटे दहाई / इकाई | `0x13` | AM/PM फ्लैग (0 = AM, 1 = PM) |
| उद्देश्य | सेवा | विशेषता | गुण |
|---|
| कमांड लिखें | 0000fff0-0000-1000-8000-00805f9b34fb | 0000fff2-… | Write |
| कमांड सूचित करें | 0000fff0-… | 0000fff1-… | Notify |
| शेल लिखें (AT) | — | 77d4ff01-2fe2-2334-0d35-9ccd078f529c | Write |
| शेल सूचित करें (AT) | — | 77d4ff02-… | Notify |
| बल्क डेटा लिखें | — | 02f00000-0000-0000-0000-00000000ffe1 | Write |
| बल्क डेटा सूचित करें | — | 02f00000-…ffe2 | Notify |
| नाम | cmd1,cmd2 |
|---|
| TIME | FFFF 8004 |
| FIRMWARE_VERSION_GET / _RET | FFFF 8006 / FFFF 0006 |
| SERIAL_NUMBER_GET / _RET | 00DE 0002 / 00DE 0001 |
| BATTERY | 005C 0001 |
| TRIGGER_SYNC | 005C 0002 |
| USER_INFO_SET / _RET 🔎✅ | 0095 0001 / 0095 0003 |
| FACTORY_RESET | 009A 0001 |
| DEVICE_REBOOT 🔎 | FFFF 9080 |
| RESOLUTION_GET 🔎 (→ 466×360) | FFFF 907F |
| GPS_PUSH / _RET | FFFF 906A / FFFF A06A |
| UNBIND_SET / _RET | FFFF 907A / FFFF A07A |
| नाम | cmd1,cmd2 |
|---|
| AUTH_PHONE_NAME | FFFF 8049 |
| AUTH_WATCH_MAC | FFFF 0049 |
| AUTH_PAIR_REQUEST / _REPLY | FFFF 8047 / FFFF 0048 |
| AUTH_NONCE_REQUEST / _REPLY | FFFF 804B / FFFF 004C |
| AUTHENTICATED_CONFIRM_REQUEST / _REPLY | FFFF 804D / FFFF 0004 |
| AUTH_FAILED | FFFF A061 |
| नाम | cmd1,cmd2 |
|---|
| APP_NOTIFICATION | 0065 0001 |
| INCOMING_CALL ⚠️ | 0064 0001 |
| CALL_REMINDER_REQUEST / _RESPONSE | FFFF 9066 / FFFF A066 |
| FIND_PHONE | 005B 0001 |
| FIND_WATCH | 005D 0001 |
| FIND_WATCH_TOGGLE | FFFF 9069 |
| SMS_MESSAGE_PUSH / _RET | FFFF 906E / FFFF A06E |
| QUICK_REPLY_SET / _RET | FFFF 9073 / FFFF A073 |
| नाम | cmd1,cmd2 |
|---|
| ALARMS_SET / _GET | 0063 0001 / 0063 0002 |
| CONTACTS_SET / _GET | 00D5 0001 / 00D5 0002 |
| STANDING_REMINDER_SET / _GET | 0060 0001 / 0060 0002 |
| WATER_REMINDER_SET / _GET | 0061 0001 / 0061 0002 |
| TASK_REMINDER_SET / _RET ⚠️ | FFFF 9072 / FFFF A072 |
| नाम | cmd1,cmd2 |
|---|
| GOALS_SET / _ACK | 005E 0001 / 005E 0003 |
| UNIT_LENGTH / _ACK | FFFF 9067 / FFFF A067 |
| UNIT_TEMPERATURE / _ACK | FFFF 9068 / FFFF A068 |
| TIME_FORMAT / _ACK | 005F 0001 / 005F 0003 |
| WAKE_ON_WRIST_RAISE / _GET / _ACK | 0062 0001 / 0062 0002 / 0062 0003 |
| LANGUAGE_SET / _RET | FFFF 9058 / FFFF A06B |
| HEART_MONITORING_ENABLED_SET / _GET | 009B 0001 / 009B 0002 |
| HEART_MONITORING_ALERTS | FFFF 9059 |
| DO_NOT_DISTURB / _GET | 0099 0001 / 0099 0002 |
| SPORTS_SET / _GET | 00DC 0001 / 00DC 0002 |
| SPORT_LINKAGE_SET / _RET | FFFF 9076 / FFFF A076 |
| SPORT_DATA_SYNC 🔎 (लाइव HR/cal/steps) | FFFF 9078 / FFFF A078 |
| FEMALE_CYCLE_SET / _RET | FFFF 9071 / FFFF A071 |
| SLEEP_CONFIG_SET / _RET (लक्ष्य मिनट) | FFFF 9074 / FFFF A074 |
| WORLD_CLOCK_GET | FFFF 906F |
| WORLD_CLOCK_DST_SET / _RET | FFFF 9083 / FFFF A083 |
| VITALITY_GET / _RET | FFFF 9079 / FFFF A079 |
| VITALITY_SW_SET / _RET | FFFF 9070 / FFFF A070 |
| नाम | cmd1,cmd2 |
|---|
| DIAL_COMMAND_SET / _RET (सूची/पुनःक्रम/चयन) | FFFF 9055 / FFFF A055 |
| DIAL_CONFIG_SET / _RET | FFFF 9075 / FFFF A075 |
| CHANGE_DIAL (⚠️ 1.0.0.73 पर निष्क्रिय — उपयोग न करें) | 009F 0001 |
| QUICK_CARD_SET/GET / _RET (दोनों 906D पर) | FFFF 906D / FFFF A06D |
| नाम | cmd1,cmd2 |
|---|
| ACTIVITY_FETCH_1 / _2 | FFFF 8005 / FFFF 9057 |
| ACTIVITY_FETCH_ACK_1 / _2 | FFFF 0005 / FFFF A057 |
| ACTIVITY_DATA | 0056 0001 |
| SLEEP_DATA / _GET | 0058 0001 / 0058 0002 |
| SPO2 | 0055 0001 |
| STRESS | 009D 0001 |
| HEART_RATE_MANUAL_AUTO | 0053 0001 |
| HEART_RATE_RESTING | 00DA 0001 |
| HEART_RATE_WORKOUT | 00E0 0001 |
| SKIN_TEMP_HISTORY 🔎 (इस SKU पर खाली) | 0155 0001 / 0155 0002 |
| WORKOUT_SUMMARY / _V3 | 0057 0001 / 0160 0001 |
| WORKOUT_GPS | FFFF A05A |
| डोमेन | INIT1 req/reply | INIT2 req/reply | CHUNK req/write | FINISH ack1/ack2 |
|---|
| वॉचफेस (फोटो) | 8052/0052 | 9063/A063 | A064/9064 | A065/9065 |
| वॉचफेस (संरचित/स्विच) | 8052/0052 | 9075/A075 | A064/9064 | A065/9065 |
| फ़र्मवेयर | 9052/A052 | 9040/A040 | A042/9042 | A041/9041 |
| AGPS/EPO | 905E/A05E | — | A05F/905F | A060/9060 |
| ऑफ़सेट | आकार | फ़ील्ड |
|---|
| 0 | 4 | टाइमस्टैम्प (epoch s) |
| 4 | 4 | कदम |
| 8 | 4 | दूरी (m) |
| 12 | 4 | कैलोरी |
| 16 | 16 | आरक्षित (0 देखा गया) |
| ऑफ़सेट | आकार | फ़ील्ड |
|---|
| 0 | 4 | session_start (epoch, UTC) |
| 4 | 4 | wakeup (epoch, UTC) |
| 8 | 2 | total_deep_s |
| 10 | 2 | total_core_s |
| 12 | 2 | total_rem_s |
| 14 | 2 | total_awake_s |
| 16 | 2 | ⚠️ [अनिश्चित] (सत्र id/स्कोर? देखे गए मान रिकॉर्ड योग से मेल नहीं खाते) |
ac 49 1f 0100D5 0001) ✅: N × 57 बाइट = name(32) ‖ phone(25)। वॉच UI 20 तक दिखाता है।0063 0001) ✅ — Gadgetbridge को सही करता है (जिसने लेबल को अंत में रखा, 0xff-पैडेड — गलत)। प्रति अलार्म 40 बाइट, बिग-एंडियन: secondsOfDay(i32) ‖ index(u8) ‖ enabled(u8) ‖ repetition-bitmask(u8) ‖ flag(u8) ‖ label[32] UTF-8। लेबल ऑफ़सेट 8 पर है और वॉच पर दिखता है। repetition = सप्ताहांत बिटमास्क (0 = एक-बार); flag ⚠️ [अनिश्चित] है (एक-बार मार्कर?)। उदाहरण (13:30, idx 2): 0000bdd8 02 01 15 00 "Alarm…"।005E 0001) ✅ — आधिकारिक ऐप और संदर्भ कार्यान्वयन 10-बाइट, बिग-एंडियन DailyTargetBean v1 का उपयोग करते हैं: steps(u32 BE) ‖ distance_m(u32 BE) ‖ calories_kcal(u16 BE)। (यह Gadgetbridge रूप है; पहले की रिपोर्टें कि वॉच ने इसे "अनदेखा" किया, एक पुराना-सत्र डिक्रिप्ट बग था, पेलोड समस्या नहीं।) 🔎 फ़र्मवेयर RE एक लंबा 29-बाइट विस्तारित वैरिएंट भी दिखाता है (sleep_min/exercise_min/stand_h + 6 सक्षम फ़्लैग जोड़ता है, सभी u32 BE एक flag(u16 LE) उपसर्ग के बाद, सीमाएँ लागू: steps 2000–30000, dist 1000–99000, cal 100–5000, sleep 360–720, exercise 30–90, stand 6–16) — ऐप का डिफ़ॉल्ट पथ नहीं; जब तक अतिरिक्त लक्ष्यों की आवश्यकता न हो, 10-बाइट रूप को प्राथमिकता दें।0060/0061 0001) ✅: 11 बाइट: enabled(1) ‖ threshold_min(u16 LE) ‖ dndStart(u32 LE) ‖ dndEnd(u32 LE)। ध्यान दें कि UI में दिखाई गई "सक्रिय विंडो 08:00–22:00" एक निश्चित फ़र्मवेयर डिफ़ॉल्ट है और पेलोड में नहीं ले जाई जाती।00DC 0001) ✅: count(1) = 36 स्लॉट ‖ activityTypeCode[36] (सक्रिय कोड फिर 00 पैडिंग)। चुनता है कि वॉच के वर्कआउट मेनू में कौन से खेल दिखाई देते हैं।009B 0001) ✅: kind बाइट — 01 = 24/7 HR, 02 = SpO₂, 04 = तनाव (हर 30 मिनट में मापा जाता है)।FFFF 9059) ✅: अक्षम = 00; सक्षम = 01 ‖ hrLow ‖ hrHigh ‖ sportHrHigh ‖ spo2Low ‖ 00 00 00 00 (एक 0/255 सीमा = "कोई सीमा नहीं")।FFFF 9071) ✅: 01 ‖ predictionOpen ‖ notifySwitch ‖ cycleStartSwitch ‖ cycleStartNotifyBefore ‖ ovulationStartSwitch ‖ ovulationStartNotifyBefore ‖ fertileStartSwitch ‖ fertileStartNotifyBefore ‖ period(1) ‖ cyclePeriod(1) ‖ cycleStartDate(u32) ‖ markStart(u32) ‖ markEnd(u32) (कैप्चर किया गया: period=5, cyclePeriod=0x1c=28)।FFFF 9073) ✅: TLV — count(1) ‖ total(1) ‖ [id(1) ‖ len(u16 LE) ‖ msg-UTF8]… (7 डिफ़ॉल्ट उत्तर कैप्चर और डिक्रिप्ट किए गए)।FFFF 906F) ✅: नाम नहीं, संख्यात्मक शहर ID भेजता है (01 ‖ count ‖ cityId(2 BE)…); वॉच आंतरिक तालिका से id मैप करती है। DST कॉन्फ़िग FFFF 9083 = count ‖ [id(u16 LE) ‖ dst(u16 LE) ‖ start(u32 LE) ‖ end(u32 LE)]…।FFFF 905C, 131 B) ✅: state(1: 0=none/1=paused/2=playing) ‖ volume(1) ‖ volumeMax(1) ‖ track(64) ‖ artist(64)। वॉच MUSIC_BUTTON (A05D) भी वापस भेजती है।FFFF 906B, 199 B) ✅ — इसका उपयोग करें: 7×9-बाइट दिन + 24×2-बाइट घंटे + city(32) + 7×8-बाइट सूर्योदय/सूर्यास्त (LE)। तापमान (temp_c + 100) & 0xFF के रूप में एन्कोडेड। ⚠️ वही पेलोड WEATHER_SET_2 (0066 0001) पर भेजा गया Pro 2 पर मौसम विजेट को अपडेट नहीं करता — हमेशा 906B का उपयोग करें। (शहर स्ट्रिंग भी एक सिद्ध डेटा-हाईजैक वेक्टर है — §13 देखें।)005D 0001) ✅: पेलोड 0x01 → वॉच बजती/कंपन करती है (+ ACK 005D 0003)।FFFF 906A) ✅ — बिग-एंडियन, देशांतर पहले: 16 बाइट ts(u32 BE) ‖ lon×1e7(i32 BE) ‖ lat×1e7(i32 BE) ‖ 00 00। वास्तविक स्थान के विरुद्ध मान्य।FFFF A05A) ✅ — लिटिल-एंडियन, देशांतर पहले: 12 बाइट ts(i32) ‖ lon×1e7(i32) ‖ lat×1e7(i32)।FFFF 8004): §7 देखें।| cf | bpp | रास्टर (LZ4 के बाद) | उपयोग |
|---|
| 4 | 2 | RGB565-LE | अपारदर्शी पृष्ठभूमि (FULL/THUMB) |
| 5 | 3 | RGB565-LE (2 B) + अल्फा (1 B) प्रति px | एंटी-अलियास्ड स्प्राइट्स (ग्लिफ़, हाथ, आइकन) |
| 13 (0x0d) | 0.5 | 4-बिट अल्फा मास्क; फर्मवेयर रनटाइम पर टिंट करता है | डिजिट-ग्लिफ़ एटलस |
| 24 (0x18) | 4 | RGBA8888 | फुल-कलर लेयर्स (हमेशा-ऑन aodImage सहित) |
| 1 | — | JPEG/JFIF (ff d8 ff), किसी भी डिकोडर से निकालें | दुर्लभ एनीमेशन फ्रेम |
0a0x61NotEnvelope0aChildOverflow:61 0a 00−18/−160a.bin में नहीं है0x68 ग्रुप
नोड्स के रूप में लिखा जाता है जो एक ही (x,y) पर स्टैक किए जाते हैं, प्रत्येक केवल तब खींचा जाता है जब एक विज़िबिलिटी शर्त मेल खाती है — इतना
सही था। लेकिन प्रति-स्लॉट मेट्रिक सूची (275 का 0x1c/0x6a/0x48/0x24/0x19/0x76) ठीक वही
मेट्रिक सूची है, "विकल्प/शैली आईडी" नहीं; डिफ़ॉल्ट सक्रिय इंडेक्स फ़ाइल में एक बाइट है; और
0x79/0x7a बाइट "इंस्टेंस बाइट" नहीं बल्कि वह आईडी है जिस पर विकल्प कुंजीबद्ध हैं
(0x79 + slotIndex)। एक स्टैटिक प्रीव्यू कर सकता है फ़ाइल के डिफ़ॉल्ट को पुनरुत्पादित कर सकता है। §11.13 देखें।(446,0) पर bpm टेक्स्ट, 275/302/325/365/375 पर देखा गया)
वे स्लॉट हैं जिन्हें फर्मवेयर डिफ़ॉल्ट दृश्य में नहीं खींचता — उनका मान कैनवास किनारे से पहले फिट भी नहीं हो सकता।
प्रीव्यू में छिपे हुए के रूप में मानें।0x22aod0x22@69,2090x60 img_number (cnt=10) — स्रोत −5 पर, फॉरवर्ड ऑफ-बाय-वन। ✅ §11.8 के समान ऑफ-बाय-वन
लेकिन गैर-क्लॉक नंबरों के लिए: "Gradient" की डेट (203,80) टॉप-सेंटर पर स्रोत
0x17 के साथ बैठी थी, लेकिन फॉरवर्ड 82-स्कैन ने पड़ोसी पॉइंटर का एंगल गेटर (0x0a) और
पॉइंटर की स्थिति पकड़ ली → नंबर पॉइंटर के स्थान पर एक नकली स्रोत के साथ रेंडर हुआ। फिक्स: एक
61 0a 00 img_number के लिए 0x60 रैपर में, −5/−18/−16 पर भरोसा करें जब फॉरवर्ड स्रोत
एक नंबर के लिए असंभव हो (स्रोत-0 या एक पॉइंटर-एंगल गेटर 0x0a/0e/12/70/71/72) और
−18/−16 स्थिति मान्य और गैर-शून्य हो (गैर-शून्य गार्ड relX=0 वाले ग्रुप-चाइल्ड अंकों को छोड़ देता है)।0x17 = डेट (महीने का दिन), 0x24 = तापमान — अलग। डायल 340 दोनों का उपयोग करता है (0x17
"Jun 09" और एक अलग 0x24 तापमान), इसलिए 0x17 डेट है, तापमान नहीं। एक डायल जिसकी वॉच
0x17 स्लॉट में तापमान दिखाती है वह एक उपयोगकर्ता-कॉन्फ़िगर कॉम्प्लिकेशन (डिवाइस स्थिति) है, फ़ाइल डिफ़ॉल्ट नहीं।| टैग | भूमिका | कंटेनर? | बॉडी |
|---|
0x20 | सीन रूट (बॉडी रैपर, ड्रॉएबल नहीं) | ✅ | चिल्ड्रन |
0x21 | सामान्य स्क्रीन | ✅ | चिल्ड्रन |
0x22 | AOD स्क्रीन (§11.9) | ✅ | चिल्ड्रन |
0x28 | एम्बेडेड कैटलॉग प्रीव्यू थंबनेल | ✅ | एक 0x08 चाइल्ड |
0x68 | ग्रुप / ऑटो-लेआउट कंटेनर | ✅ | 0x48 फ्रेम + चिल्ड्रन |
0x30 | स्टैटिक इमेज, या N इमेज से मान-द्वारा-पिक | ✅ | 0x01 (+0x02) |
0x60 | लाइव न्यूमेरिक रीडआउट (अंक पट्टी) | ✅ | 0x01 + 0x40 (+0x02) |
0x70 | घूमता हुआ हैंड | ✅ | 0x01 + 0x05 पिवट |
0x80 | प्रोग्रेस रिंग, प्रोसीजरल | ✅ | 0x01 + 0x5a (§11.15) |
0x81 | प्रोग्रेस रिंग, इमेज-क्लिप्ड | ✅ | 0x01 + 0x5b (§11.15) |
0x85 | उपयोगकर्ता-असाइन करने योग्य कॉम्प्लिकेशन स्लॉट | ✅ | 0x01 + 0x5f (§11.13) |
0x01 | struct — ज्यामिति + एट्रिब्यूट्स (नीचे) | — | x,y,meta[14] + रेफ टेल |
0x02 | विज़िबिलिटी शर्त (§11.12) | — | शर्त सूची |
0x05 | पिवट — flag u8, pivotX u16, pivotY u16 | — | 5 B |
0x08 | pvStruct — prefix[5] + रेफ टेल, कोई x/y नहीं (केवल प्रीव्यू) | — | — |
0x40 | अंक गणना / ज़ीरो-पैड फ्लैग (§11.10) | — | 1 B |
0x48 | फ्रेम — x,y,w,h,gap,align ऑटो-लेआउट पंक्ति/स्तंभ | — | — |
0x5a / 0x5b | 0x80 / 0x81 के लिए आर्क स्पेक (§11.15) | — | 19 B / 17 B |
0x5f | 0x85 के लिए स्लॉट मेट्रिक सूची (§11.13) | — | — |
0x86 | डिस्प्ले-नाम नोड, हमेशा ठीक 64 बाइट, NUL-समाप्त, खींचा नहीं गया | — | 64 B |
| dial | slot | count | activeIdx | metric ids | → active |
|---|
| 275 SlopeTime | 0 | 6 | 0 | 1c 6a 48 24 19 76 | 0x1c calories |
| 275 SlopeTime | 1 | 6 | 4 | 1c 6a 48 24 19 76 | 0x19 steps |
| 368 Function | 0 | 8 | 0 | 5f 1c 19 48 24 76 1a 8b | 0x5f temperature |
| 368 Function | 1 | 8 | 6 | 5f 1c 19 48 24 76 1a 8b | 0x1a heart rate |
| 273 Activity Mood | 0 | 4 | 0 | 1c 24 48 6a | 0x1c calories |
| 304 Elaborate 2 | 0/1 | 4 | 0 | 1c 48 6a 24 / 24 1c 6a 48 | 0x1c / 0x24 |