
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, serial 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) ले जाता है, जो कमांड चैनल पर नियंत्रण ऑपकोड द्वारा समन्वित होते हैं।
✅ एक पूर्ण वास्तविक सत्र एकल कमांड चैनल पर चला — 160 सेकंड के भारी उपयोग वाले कैप्चर के दौरान स्पष्ट 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 − 2`
✅ सभी देखे गए एन्क्रिप्टेड फ्रेम `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 = वॉच के नॉन्स रिप्लाई से बाइट्स।कुंजी सेट होने के बाद, सभी कमांड-चैनल फ्रेम AES-एन्क्रिप्टेड होते हैं, सिवाय §5 में सूचीबद्ध प्लेनटेक्स्ट ऑपकोड के।
✅ दोनों व्युत्पत्ति मान्य की गईं: रूटेड फोन के ntwatch.db से प्राप्त authkey, कैप्चर किए गए rnd1/rnd2/secret से व्युत्पन्न मान से मेल खाता है; कैप्चर किए गए नॉन्स से पुनरुत्पादित sessionKey लाइव फ्रेम को डिक्रिप्ट करता है।
दो प्रवेश पथ समान नॉन्स/कन्फर्म टेल साझा करते हैं।
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)।
अधिकांश सेटिंग्स के लिए कोई अलग "रीड" opcode नहीं है। *_GET (cmd2 = 0x0002, पेलोड
0xA5) भेजने पर घड़ी SET opcode (cmd2 = 0x0001) के साथ उत्तर देती है जो वर्तमान मान रखता है।
SET कमांड को cmd2 = 0x0003 और खाली बॉडी के साथ स्वीकार किया जाता है।
फ़्रेम AES-एन्क्रिप्टेड होते हैं जब की सेट होती है, इन opcodes को छोड़कर जो हमेशा प्लेनटेक्स्ट होते हैं:
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-केवल opcodes (
FFFF 8051,FFFF 0051,FFFF 90A2,FFFF 90C5,FFFF A056,FFFF 908A/908BChatGPT स्थिति/समर्थन) को ऐप के हर्मीस बाइटकोड में संभाला जाता है, Java लेयर में नहीं। उनके हेडर कैप्चर में दिखाई देते हैं लेकिन पेलोड अर्थ ⚠️ [अनिश्चित] हैं।
वॉचफ़ेस / फ़र्मवेयर / AGPS एक init → chunk-request/chunk-write लूप → फिनिश-ack का उपयोग करते हैं:
(सभी cmd1 = FFFF।) घड़ी DATA_CHUNK_REQUEST_*(ऑफ़सेट, लंबाई) उत्सर्जित करके लूप को चलाती है
(ऑफ़सेट/लंबाई = u32 बिग-एंडियन); फ़ोन DATA_CHUNK_WRITE_* के साथ उत्तर देता है जो डेटा
कैरेक्टरिस्टिक पर payload[offset..offset+length] ले जाता है। विवरण के लिए §11–§12 देखें।
TIME (FFFF 8004) पेलोड = epochSeconds(i32, BE) ‖ utcOffsetMillis(i32, BE). प्रमाणीकरण के तुरंत बाद
भेजा जाता है ताकि घड़ी स्थानीय समय दिखाए (और डेटा क्वेरी को अनब्लॉक करे — देखें §4.3)।
⚠️ घड़ी से स्वास्थ्य टाइमस्टैम्प UTC में होते हैं। साथी ऐप को स्थानीय कैलेंडर दिन / समय-का-समय प्राप्त करने से पहले स्थानीय UTC ऑफ़सेट जोड़ना होगा। (कच्चे UTC दिन द्वारा स्वास्थ्य को बकेट करने से गलत स्थानीय समय पर दिन बदल जाता है।)
TIME_FORMAT (005F 0001) पेलोड = 1 बाइट: 00 = 24h, 01 = 12h।
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 s में ~170–210 सूचना फ़्रेम धकेलता है। ✅
ACTIVITY_DATA (प्रत्येक 32 बाइट्स, LE) ✅कैलोरी इकाई: गतिविधि कैलोरी कैल (ग्राम-कैलोरी) में रिपोर्ट की जाती हैं। दैनिक योग को 1000 से विभाजित करके kcal प्राप्त करें। (इसके विपरीत, वर्कआउट-सारांश कैलोरी पहले से ही kcal में होती हैं।)
टाइमस्टैम्प(i32 LE) ‖ मान(i32 LE)
(मान = bpm / SpO₂ % / तनाव सूचकांक)।00DA 0001) अलग है — 5 बाइट्स: टाइमस्टैम्प(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-बाइट रिकॉर्ड: टाइमस्टैम्प(u32) ‖ अवधि_s(u16) ‖ अवस्था(u16).
अवस्था कोड: 1 = गहरी, 2 = मध्य/हल्की, 3 = REM, 4 = जागृत। ✅ एक पूरी रात के विरुद्ध मान्य किया गया
(दो सत्र, D/C/R/A योग मेल खाते हैं)।
WORKOUT_SUMMARY v1 (54 बाइट्स) / _V3 (0160 0001)v1: आरंभ(u32), अंत(u32), अवधि_s(u32), फिर प्रकार/कैलोरी/कदम/दूरी/औसत-HR और एक
GPS/विस्तारित ब्लॉक। ✅ v1 लेआउट फ़र्मवेयर के विरुद्ध पुष्टि की गई। WORKOUT_SUMMARY_V3 उसी डेटा के लिए
एक नया लेआउट है और एक ~40-बाइट विस्तारित ब्लॉक (व्यायामभार, एरोबिक/एनारोबिक, रिकवरीटाइम,
VO₂max, कैडेंस, 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) ✅: उत्तर = स्तर(1) ‖ चार्जिंग(1) (जैसे 3b 00 = 59 %, चार्ज नहीं हो रहा)।00DE 0001) ✅: len(1) ‖ ASCII (जैसे 10 + "CI04102520008192")।0095 0001) ✅: ऊंचाई_सेमी(1) ‖ वजन_किग्रा(1) ‖ आयु(1) ‖ लिंग(1: 1=M)
(जैसे = 172 सेमी / 73 किग्रा / 31 / पुरुष)।now/utc_offset को स्पष्ट पैरामीटर के रूप में लेते हैं
(नियतात्मक, परीक्षण योग्य)। परिवहन वास्तविक समय प्रदान करता है।TIME सब कुछ गेट करता है (§4.3) — इसे पहले भेजें अन्यथा घड़ी डेटा क्वेरी पर मौन रहती है।GOALS_SET और
GPS_PUSH बिग-एंडियन हैं, और बल्क-ट्रांसफर ऑफ़सेट/लंबाई बिग-एंडियन हैं।घड़ी समर्थन करती है (a) फ़ोटो/कस्टम डायल (एक पृष्ठभूमि छवि + एक फ़र्मवेयर-निर्मित डिजिटल घड़ी) और (b) संरचित डायल (अंतर्निहित / स्टोर चेहरे: एक पृष्ठभूमि प्लस स्थित स्प्राइट परतें, हाथ, और टेक्स्ट विजेट)। दोनों §6 में init → chunk लूप के माध्यम से डेटा चैनल पर स्थानांतरित होते हैं।
वास्तव में क्या काम करता है (✅ लाइव मान्य): किसी भी छवि से फ़ोटो डायल बनाना और उसे इंस्टॉल करना; 103 स्टोर डायल को ऑफ़लाइन इंस्टॉल करना; एक संरचित डायल को रीस्किन करना (पृष्ठभूमि या किसी भी गैर-पृष्ठभूमि स्प्राइट को बदलना) और इसकी परतों को स्थानांतरित करना; सक्रिय चेहरे को पुनःक्रमित / स्विच करना; और शुरू से एक संरचित डायल बनाना —
0x20दृश्य लिफाफा डिकोड किया गया है और बिल्डर कार्यान्वित किया गया है (§11.7), सभी 103 स्टोर डायल को बाइट-दर-बाइट राउंड-ट्रिप करने और सिंथेटिक कंटेनरों का उत्सर्जन करने के लिए ऑफ़लाइन सिद्ध किया गया है जो फ़र्मवेयर के अपने वैलिडेटर को पास करते हैं। 🟡 एकमात्र अप्रमाणित कदम9075पर उपकरण पर एक खरोंच-से-सिंथेटिक रेंडर देखना है (संरचनात्मक ऑफ़लाइन प्रमाण पहले से ही उस चीज़ को कवर करता है जो0aअस्वीकार का कारण बनती थी)। कोई कोडेक या परिवहन बाधा नहीं है और विक्रेता टूलचेन की कोई आवश्यकता नहीं है। पुराने "संरचित रेंडर RES-pack-बेक्ड / BLE पर असंभव है" और "cf=0x1f सर्वर-साइड कोडेक" दावे गलत थे (एक ऑफ़सेट+बाइट्स-प्रति-पिक्सेल बग) — फ़र्मवेयर संरचित डायल को आपके द्वारा भेजी गई फ़ाइल से डेटा-संचालित रेंडर करता है।
DIAL_COMMAND (9055 / a055) ✅a055 = result(u8) ‖ selectIndex(u8) ‖ total(u8) ‖ max(u8) ‖ N × dialId(u32 LE) ‖ ffffffff। उदाहरण: 01 05 06 07 … = सक्रिय #5, 6 डायल, अधिकतम 7।CHANGE_DIAL (009F 0001) fw 1.0.0.73 पर निष्क्रिय है (एक स्थिरांक लौटाता है, स्विच नहीं करता) — इसका
उपयोग न करें।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`)। पहले से मौजूद किसी आईडी को पुनः स्थापित करने के लिए, **पहले इसे हटाएं** (9055 list-minus-id) फिर "नया" अपलोड करें — स्थान पर किसी आईडी का पुन: उपयोग करने पर `0a` मिलता है।
### 11.3 फोटो / कस्टम डायल — ✅ fully validated end-to-end
**Container** (बाइट-सत्यापित राउंड-ट्रिप; सभी फ़ील्ड लिटिल-एंडियन):```
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]
Codec = standard LZ4 block over RGB565 little-endian, top-down (payloadLen पहले LZ4 बाइट से गिनती करता है). आधिकारिक ऐप LZ4-HC का उपयोग करता है और 21-बाइट LZ4-ब्लॉक हेडर/फुटर को हटा देता है; एक सादा लिटरल-ओनली LZ4 एनकोडर भी काम करता है — घड़ी किसी भी मान्य LZ4 को स्वीकार करती है, बाइट-पहचान आवश्यक नहीं है। उत्कीर्ण वृत्त (केंद्र 233,233, त्रिज्या 233) के बाहर के पिक्सेल 0x0000 पर सेट किए जाते हैं।
INIT_2 for 9063 — सटीक हेडर (✅ यह वही है जो काम करता है):```
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` रूप समाप्ति `0a` के साथ **अस्वीकृत** होता है — ऊपर दिए गए पूर्ण हेडर का उपयोग करें। (संदर्भ कार्यान्वयन: `core-rust/engine.rs::build_wf_init2`, आधिकारिक ऐप में `C6135t.m31104u` की प्रतिकृति।)
**नुस्खा:** छवि को 466×466 (और एक 270×270 थंबनेल) में आकार बदलें, RGB565-LE शीर्ष-से-नीचे में बदलें, वैकल्पिक रूप से वृत्त के बाहर के पिक्सेल को शून्य करें, प्रत्येक को LZ4-संपीड़ित करें, उपरोक्त कंटेनर को इकट्ठा करें, और `9063` पाइपलाइन के माध्यम से `watchfaceId = 0xFFFFFFFF` के साथ अपलोड करें। (संदर्भ कोडेक: `core-rust/watchface.rs`, `work/codec_dfa.py`.)
### 11.4 संरचित / स्टोर डायल — कंटेनर और कोडेक्स ✅
**हेडर** (सभी 103 स्टोर डायल में समान; सभी फ़ील्ड लिटिल-एंडियन):```
0x00 perDialId u32 LE [per-dial id/hash; NOT a content checksum — 4 "Default" dials share one]
0x04 version 0x00000001 [constant]
0x08 name char[] [NUL-terminated, e.g. "SlopeTime", "Metaball"]
0x18 size_a u32 LE [= filesize − 36] ✅ 100 % confirmed across 103 dials
0x1c size_b u32 LE [data-section length, < size_a]
0x20 3× u32 LE id/hash words [not a CRC]
0x2c name (repeated on larger dials)
~0x60 directory of layer records (61 xx 00 …) then the asset pool
कोई ब्लॉकिंग चेकसम नहीं है (CRC32/Adler32/byte-sum सभी मेल नहीं खाते) — रीपैकिंग पर कोई रोक नहीं है। स्टब डायल (~173 B, उदा. आईडी 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:
✅ All 4151/4151 एसेट्स 103 डायलों में, w·h·bpp पर एक मानक lz4.block डीकंप्रेसर के साथ बिल्कुल डिकोड होते हैं। पारदर्शिता अल्फा बाइट (cf=5/24) या 0x0000 (cf=4 वृत्त के बाहर) है — कोई RLE या "escape" नहीं है। एनकोड = री-रास्टर → मानक LZ4 → [dimsWord][len][LZ4]।
9075 के लिए INIT_2 — AES-encrypted body:```
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 आईडी 359 +
102 अन्य पुष्ट). (संदर्भ: `core-rust/engine.rs::build_dial_replace_init`.)
### 11.5 संरचित निर्देशिका व्याकरण ✅ (डिकोड & कार्यान्वित — संशोधित 2026-07-02)
> **⚠️ संशोधन (2026-07-02): नीचे दिया गया सपाट `61 01 00` रिकॉर्ड स्कीमा व्यवस्थित रूप से
> ऑफ-बाय-वन था।** दृश्य बॉडी एक स्वच्छ TLV (§11.7) है; एक ड्रॉ करने योग्य **लीफ बॉडी** (टैग `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 = top-left** 466² कैनवास पर (SDK के `sty_picture_t` का `s16 x,y`)।
> - **फ्रेम तालिका `61 …` बॉडी को बंद करती है** (`base` = एसेट ptr; `count` 1 = छवि, 10/11 =
> अंक एटलस — पुराना "रिकॉर्ड प्रकार `0a/0b`" वास्तव में यही count था! — 7/13/2 = कॉम्प्लिकेशन
> फ्रेम शीट)।
> - पॉइंटर एक्स्ट्रा: `0x01` attr के अंदर source+scale `[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") से जोड़ रहा था — यह केवल एनालॉग डायल पर *सही* दिखता था जिनके
> आसन्न हाथों की ज्यामिति लगभग समान होती है। "कॉम्पैक्ट वेरिएंट वॉल" (स्पेक 24 §24.4.5) यही
> गलत पढ़ाई था। `core-rust/watchface_struct.rs` और `wfweb/src/codec/parse.ts` में `scan_scene_drawables` के रूप में कार्यान्वित किया गया (दृश्य = छवियों/पॉइंटर्स के लिए प्राथमिक स्रोत; टेक्स्ट + गैर-लिफाफा फॉलबैक के लिए सपाट स्कैन रखा गया)। `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² कैनवास पर top-left = `(X−pivotX, Y−pivotY)`।
- **पॉइंटर/हैंड** — समान छवि रिकॉर्ड, रनटाइम पर घुमाया जाता है। **रोटेशन केंद्र = `(X+pivotX, Y+pivotY)`**
(≈ 233,233 एनालॉग डायल पर)। **डेटा स्रोत रिकॉर्ड ऑफ़सेट `+36` पर एक `u8` है**, स्केल `u16` `+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 ‖ flag ‖
3B ‖ 01 ‖ u16 ‖ X(u16) ‖ Y(u16)`. `asset_ptr` ग्लिफ़ "0" की ओर इंगित करता है; अंक *d* = `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, **स्पेक 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` (`max` u16 `@+4`, =100 सिवाय 332=60)। wfweb (`blendSector`) में कार्यान्वित किया गया।
- **डेटा स्रोत आईडी** — तत्व का `82` attr-ब्लॉक `delim+3` पर बैठता है (अंतिम `40 01 00` के बाद), और **स्रोत आईडी `+0x14` पर एक `u8` है** (साथ ही `relX@+0x07 s16`, `relY@+0x09 s16`, `anchor@+0x0C/0E`, `mode@+0x15`, `frame-count@+0x1A`). Anchor < 0 = मूल के किनारे पर संरेखित करें। 🔎 फर्मवेयर आईडी को `0x101f371c` पर 142-प्रविष्टि गेटर तालिका के माध्यम से हल करता है (प्रत्येक `ux2sys_get(type)` कहता है)। सामान्य आईडी (§16): `0x07` घंटा, `0x0b` मिनट, `0x0f` सेकंड, `0x16` माह, `0x18` सप्ताह का दिन, `0x13` AM/PM, `0x19` HR, `0x1b` बैटरी %, `0x24` तापमान, `0x36` कदम, `0x70/71/72` हाथ कोण, `0x25–27` लक्ष्य %. (यह नीचे `0x07:0x0b:0x0f` = HH:MM:SS समूह उदाहरण से मेल खाता है।)
- **समूह नोड** (`0x68`): अपने बच्चों को अपने स्वयं के TLV बॉडी के अंदर नेस्ट करता है (`0x60` = मान/टेक्स्ट, `0x30` = स्थिर); प्रत्येक `0x60` अपनी स्रोत आईडी `data+16` पर रखता है। उदा. एक समूह `0x07:0x0b:0x0f` = HH:MM:SS घड़ी। TLV तत्व पार्सर = `0x100db55c` (`tag−0x70` द्वारा अनुक्रमित जंप तालिका)।
### 11.6 लेखक मैट्रिक्स
| पथ | स्थिति | नोट्स |
|---|---|---|
| किसी भी छवि से फोटो डायल | ✅ **हो गया** | §11.3; डिवाइस पर मान्य किया गया |
| 103 स्टोर डायलों में से किसी को स्थापित करें | ✅ **हो गया** | §11.4; `9075`, `old_id`=सक्रिय |
| स्टोर डायल के cf=4 पृष्ठभूमि को फिर से स्किन करें | ✅ **लाइव काम करता है** | FULL पेलोड को उसी स्थान पर बदलें, एसेट `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`** को छोड़ना (घड़ी ब्लॉक से आगे पढ़ती है → ओवररन → ब्लैक); **फ़ाइल को बड़ा करना** (स्थापना पर अस्वीकृत); किसी आईडी को **उसी स्थान पर** पुन: उपयोग करना न कि ताज़ा स्थापना के रूप में।
### 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 दृश्य को
बाइट-दर-बाइट पुन: उत्पन्न करता है (पुनर्गणित नेस्टेड lens मेल खाते हैं) और 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 मोड में सामान्य दृश्य छिपा दिया जाता है ताकि यह काला + एलिमेंट्स को उनके स्वयं के रंग पर रेंडर करे। 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 = शून्य-पैड (प्रमुख शून्य दिखाएं, जैसे "9" के बजाय "09").फर्मवेयर को डिस्सेम्बल करके पुष्टि की गई (XIP इमेज 0x10000000; रेंडर रूटीन 0x100d8e60):
NDIG = ldrb[40sub+3] & 0x0F (→7 यदि 0); मान को value % 10^NDIG में क्लैंप किया गया है और बिल्कुल NDIG
ग्लिफ़ MS-पहले खींचे जाते हैं, प्रमुख शून्य दबाए जाते हैं जब तक कि बिट7 न हो। स्रोत के बाद 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 परिकल्पना गलत थी — चौड़ाई केवल लेआउट है, गिनती नहीं।)
स्थानांतरण तालिका §6 में है। अतिरिक्त पुष्टि किए गए बिंदु:
000000010000… से शुरू होता है। पूर्ण आरंभीकरण → [A05F ↔ 905F]×N → समाप्त लूप वायर पर देखा गया (~892 चंक)।9040–9042, समाप्त 9041) 🔎: संरचना मैप की गई; INIT2 पेलोड = संस्करण बाइट्स
(जैसे 0b 00 00 39 = 11.0.0.57)। फ़ील्ड-परीक्षित नहीं (ऐप यहां FW अपडेट अक्षम करता है)। फर्मवेयर
छवियां अहस्ताक्षरित प्रतीत होती हैं — अखंडता केवल CRC32 है (RE में कोई असममित हस्ताक्षर नहीं देखा गया)।FACTORY_RESET (009A 0001) प्रमाणित सत्र साझा करते हैं, एक एकल मान्य BLE
प्रमाणीकरण घड़ी को मिटाने या (सिद्धांततः) ब्रिक करने के लिए पर्याप्त है। सावधानी से संभालें।✅ BLE के माध्यम से हार्डवेयर उजागर:
WORKOUT_GPS) और स्थान पुश (GPS_PUSH).कोई बैरोमीटर/अल्टीमीटर, कम्पास, जायरोस्कोप या त्वचा/शरीर-तापमान सेंसर नहीं है। एक आंतरिक
NTC थर्मिस्टर (बोर्ड/बैटरी तापमान) मौजूद है लेकिन केवल AT चैनल के माध्यम से पढ़ने योग्य है
(AT GETNTCTEMP, §14) — इस SKU पर 0155 त्वचा-तापमान इतिहास स्ट्रीम खाली है।
डेटा-विजेट हाइजैक (कोई वास्तविक कॉम्प्लिकेशन/डेटा-बाइंडिंग API नहीं है — §11.5 देखें): घड़ी के
मौजूदा टेक्स्ट फ़ील्ड को निगाहों के लिए बाहरी डेटा दिखाने के लिए पुनर्उद्देशित किया जा सकता है। सिद्ध ✅: मौसम
शहर स्ट्रिंग (WEATHER_SET_1, जैसे "BRA 2x1 ARG" विजेट पर दिखाई दी) और संगीत
ट्रैक/कलाकार फ़ील्ड; संपर्क सूची (20 × name[32]+number[25]) एक स्क्रॉल करने योग्य डेटा पैनल के रूप में काम करती है।
सभी पुश हैं, स्थायी कॉम्प्लिकेशन नहीं।
77d4ff01 / 77d4ff02)फ्रेम किए गए प्रोटोकॉल से स्वतंत्र एक अलग सादा-पाठ AT कमांड चैनल। ✅ लाइव परीक्षण किया गया:
AT GETSECRET (16-बाइट पेयरिंग सीक्रेट), GETVERSION, GETSN, GETNAME, GETPID,
GETBATLV (कच्चा mV, जैसे 3853mv), GETGSENSOR (कच्चा accel g में, X=… Y=… Z=…),
GETNTCTEMP (°C, आंतरिक NTC).AT SETMOTOR=1 (मोटर को कंपन करें), SETHR/SETHRV/SETSPO2=… (सेंसर परीक्षण
इंजेक्शन), SETLCDSWITCH/SETGPSSWITCH/SETKEYSWITCH.उत्तर ,OK में समाप्त होते हैं। SET* कमांड आम तौर पर निष्पादित होते हैं लेकिन BLE पर ,OK प्रतिध्वनित नहीं कर सकते —
मामला दर मामला पुष्टि करें।
कुछ सुविधाएं फर्मवेयर में मौजूद हैं लेकिन SKU/क्षेत्र द्वारा अक्षम की गई हैं और फोन/BLE से पहुंच योग्य नहीं हैं — उन्हें फर्मवेयर मॉड की आवश्यकता है, जो यहां दायरे से बाहर है:
ux2sys फीचर आईडी 0x9e, बूट पर NVRAM/EFUSE/क्षेत्र से सीडेड; इस
SKU पर समर्थन ध्वज 908b = 00। फोन, खाते या BLE से प्रभावित नहीं किया जा सकता (प्रयोग + RE द्वारा पुष्टि की गई)। ऐप केवल एक रिले है; ऑडियो फोन → Nothing क्लाउड जाता है।BLE क्लाइंट बनाने के लिए आवश्यक नहीं — पूर्णता के लिए शामिल। फर्मवेयर का डायल रेंडरर प्रत्येक कॉम्प्लिकेशन स्लॉट को एक
संख्यात्मक गेटर आईडी (142-प्रविष्टि डिस्पैच टेबल) से बांधता है। चयनित आईडी:
0x07 घंटा, 0x0a संयुक्त घड़ी कोण, 0x0b मिनट, 0x0f सेकंड, 0x18 सप्ताह का दिन,
0x19 हृदय गति, 0x1b बैटरी %, 0x24 तापमान, 0x36 कदम,
0x70/0x71/0x72 घंटा/मिनट/सेकंड हैंड कोण, 0x25–0x27 लक्ष्य %। रिंग/आर्क कॉम्प्लिकेशन एक
प्री-रेंडर्ड फ्रेम शीट को इंडेक्स करते हैं (जैसे 50% = 100 में से फ्रेम 50), प्रति-पिक्सेल आर्क नहीं — फ्रेम्स
आपके द्वारा भेजे गए .bin में बेक किए गए हैं (§11.5), इसलिए किसी बाहरी RES पैक की आवश्यकता नहीं है।
QUICK_CARD (906D) ✅घड़ी के होम टाइल्स। फोन केवल यह चुनता है कि कौन से टाइल दिखाने हैं और किस क्रम में —
टाइल्स फर्मवेयर द्वारा रेंडर किए जाते हैं (कोई सामग्री चैनल नहीं)। पहला पेलोड बाइट = उप-कमांड: 00 = GET, 01 =
SET; दोनों 0x906D का उपयोग करते हैं (0x906C सूचीबद्ध है लेकिन उपयोग नहीं किया गया — इसे क्वेरी करने पर टाइमआउट होता है)। उत्तर = A06D।
🛑 एक मनगढ़ंत
assemblyIdभेजने से घड़ी के स्क्रीन मिट जाते हैं (यह सूची स्वीकार करता है, आईडी से मेल नहीं खा सकता, कुछ नहीं दिखाता)। केवल वे आईडी भेजें जिन्हें आप GET के माध्यम से वापस पढ़ते हैं; आधिकारिक ऐप या फ़ैक्टरी रीसेट के माध्यम से पुनर्प्राप्त करें।
GET उत्तर ✅: status(1) ‖ 00 ‖ N(1) ‖ N × समूह, समूह = tag=01 ‖ K(1) ‖ K×(assemblyId, sportId)।
वास्तविक फ्रेम: 01 00 04 01 02 5d00 6100 01 03 1900 2e00 2300 01 03 5c00 0400 5a02 01 03 4800 5100 5300
= 4 स्क्रीन / 11 कार्ड (5a02 = स्पोर्ट कार्ड, sportId 2)।
स्लॉट: प्रत्येक स्क्रीन में 4 स्लॉट हैं। कार्ड का प्रकार उसका आकार निर्धारित करता है — circular/square = 1 स्लॉट,
rectangle = 2 स्लॉट। मान्यता शुद्ध स्लॉट अंकगणित है (Σ ≤ 4 प्रति स्क्रीन); कोई परस्पर अनन्य कार्ड नहीं। sportId स्पोर्ट कार्ड (87–91) को छोड़कर 0 है। आईडी 64 और 95 मौजूद नहीं हैं।
assemblyId सूची (प्रत्येक तार्किक प्रकार = 6 शैली प्रकार _0.._5 का एक सन्निकट श्रेणी;
0 = खाली स्लॉट):
उपरोक्त बाइट लेआउट फर्मवेयर (1.0.0.73), आधिकारिक APK (3.5.7), और एक वास्तविक डिवाइस के विरुद्ध डिक्रिप्टेड लाइव कैप्चर से पुनर्निर्मित किए गए थे। इस प्रोजेक्ट के लिए संदर्भ कार्यान्वयन core-rust/src/{commands,frame,crypto,health,session}.rs (Rust) और cmftool/ Python टूल्स (pair.py, session.py, wf_codec.py, upload_custom.py, …) में रहता है।
| उद्देश्य | सेवा | विशेषता | गुण |
|---|
| कमांड लिखें | 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/कदम) | 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 अनुरोध/उत्तर | INIT2 अनुरोध/उत्तर | CHUNK अनुरोध/लिखें | 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 | टाइमस्टैम्प (सेकंड में युग) |
| 4 | 4 | कदम |
| 8 | 4 | दूरी (मीटर) |
| 12 | 4 | कैलोरी |
| 16 | 16 | आरक्षित (0 देखा गया) |
| ऑफ़सेट | आकार | फ़ील्ड |
|---|
| 0 | 4 | सत्र_आरंभ (युग, UTC) |
| 4 | 4 | जागरण (युग, UTC) |
| 8 | 2 | कुल_गहरा_s |
| 10 | 2 | कुल_मध्य_s |
| 12 | 2 | कुल_REM_s |
| 14 | 2 | कुल_जाग_s |
| 16 | 2 | ⚠️ [अनिश्चित] (सत्र आईडी/स्कोर? देखे गए मान रिकॉर्ड योग से मेल नहीं खाते) |
ac 49 1f 0100D5 0001) ✅: N × 57 बाइट्स = नाम(32) ‖ फ़ोन(25)। घड़ी UI 20 तक दिखाता है।0063 0001) ✅ — Gadgetbridge को सही करता है (जिसने लेबल को अंत में रखा,
0xff-पैडेड — गलत)। प्रति अलार्म 40 बाइट्स, बिग-एंडियन:
दिन_का_सेकंड(i32) ‖ सूचकांक(u8) ‖ सक्षम(u8) ‖ पुनरावृत्ति-बिटमास्क(u8) ‖ फ़्लैग(u8) ‖ लेबल[32] UTF-8।
लेबल ऑफ़सेट 8 पर है और घड़ी पर दिखता है। पुनरावृत्ति = सप्ताह के दिन की बिटमास्क (0 = एक बार);
फ़्लैग ⚠️ [अनिश्चित] है (एक-बार मार्कर?)। उदाहरण (13:30, idx 2): 0000bdd8 02 01 15 00 "Alarm…"।005E 0001) ✅ — आधिकारिक ऐप और संदर्भ कार्यान्वयन 10-बाइट, बिग-एंडियन DailyTargetBean v1 का उपयोग करते हैं: कदम(u32 BE) ‖ दूरी_m(u32 BE) ‖ कैलोरी_kcal(u16 BE)। (यह Gadgetbridge रूप है; पहले की रिपोर्ट कि घड़ी ने इसे "अनदेखा" किया
एक पुराने-सत्र डिक्रिप्ट बग था, पेलोड समस्या नहीं।) 🔎 फ़र्मवेयर RE एक लंबा 29-बाइट विस्तारित प्रकार भी दिखाता है (जोड़ता है sleep_min/exercise_min/stand_h + 6 सक्षम फ़्लैग, सभी u32
BE एक flag(u16 LE) उपसर्ग के बाद, रेंज प्रवर्तन के साथ: कदम 2000–30000, दूरी 1000–99000, कैल
100–5000, नींद 360–720, व्यायाम 30–90, खड़ा 6–16) — यह ऐप का डिफ़ॉल्ट पथ नहीं है; जब तक आपको
अतिरिक्त लक्ष्यों की आवश्यकता न हो, 10-बाइट फ़ॉर्म पसंद करें।0060/0061 0001) ✅: 11 बाइट्स:
सक्षम(1) ‖ सीमा_मिनट(u16 LE) ‖ dndStart(u32 LE) ‖ dndEnd(u32 LE)। ध्यान दें UI में दिखाई गई "सक्रिय विंडो
08:00–22:00" एक निश्चित फ़र्मवेयर डिफ़ॉल्ट है और पेलोड में नहीं ले जाया जाता है।00DC 0001) ✅: गिनती(1) = 36 स्लॉट ‖ गतिविधि प्रकार कोड[36] (सक्रिय कोड फिर 00
पैडिंग)। चुनता है कि घड़ी के वर्कआउट मेनू में कौन से खेल दिखाई दें।009B 0001) ✅: प्रकार बाइट — 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 — गिनती(1) ‖ कुल(1) ‖ [id(1) ‖ len(u16 LE) ‖ msg-UTF8]…
(7 डिफ़ॉल्ट उत्तर कैप्चर और डिक्रिप्ट किए गए)।FFFF 906F) ✅: संख्यात्मक शहर आईडी भेजता है, नाम नहीं (01 ‖ गिनती ‖ cityId(2 BE)…);
घड़ी आंतरिक तालिका से आईडी मैप करती है। DST कॉन्फ़िग FFFF 9083 =
गिनती ‖ [id(u16 LE) ‖ dst(u16 LE) ‖ start(u32 LE) ‖ end(u32 LE)]…।FFFF 905C, 131 B) ✅: स्थिति(1: 0=कोई नहीं/1=रोका/2=चल रहा) ‖ वॉल्यूम(1) ‖ volumeMax(1) ‖ ट्रैक(64) ‖ कलाकार(64)। घड़ी वापस MUSIC_BUTTON (A05D) भी भेजती है।FFFF 906B, 199 B) ✅ — इसका उपयोग करें: 7×9-बाइट दिन + 24×2-बाइट घंटे +
शहर(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 | raster (LZ4 के बाद) | उपयोग |
|---|
| 4 | 2 | RGB565-LE | अपारदर्शी पृष्ठभूमि (FULL/THUMB) |
| 5 | 3 | प्रति px RGB565-LE (2 B) + अल्फा (1 B) | anti-aliased स्प्राइट्स (ग्लिफ़, हाथ, आइकन) |
| 13 (0x0d) | 0.5 | 4-बिट अल्फा मास्क; फर्मवेयर runtime पर रंग टिंट करता है | digit-glyph एटलस |
| 24 (0x18) | 4 | RGBA8888 | पूर्ण-रंग परतें (incl. हमेशा चालू रहने वाली aodImage) |
| 1 | — | JPEG/JFIF (ff d8 ff), किसी भी डिकोडर से निकालें | दुर्लभ एनिमेशन फ्रेम |
0a0x61NotEnvelope0aChildOverflow:61 0a 00−18/−160a.bin में नहीं है। ⚠️ एक कॉन्फ़िगरेबल
कॉम्प्लिकेशन को N 0x68 समूह नोड्स के रूप में एक ही (x,y) पर स्टैक किया गया, प्रत्येक एक अलग
स्रोत से बंधा हुआ (275 के दो वृत्त: प्रति स्लॉट 0x1e/0x6a/0x48/0x24/0x19 — विकल्प/शैली आईडी, न कि दिखाया गया मीट्रिक); दो वृत्त अपने आयत + एक इंस्टेंस बाइट (0x79/0x7a) के अलावा बाइट-समान हैं। कौन सा मीट्रिक दिखता है (STEPS बनाम KCAL बनाम …) डिवाइस RAM/कॉन्फ़िग स्थिति है, इसलिए एक स्थैतिक पूर्वावलोकन इसे फ़ाइल से पुन: उत्पन्न नहीं कर सकता — केवल सर्वोत्तम प्रयास।(446,0) पर bpm टेक्स्ट, जो 275/302/325/365/375 पर देखा गया)
वे स्लॉट हैं जिन्हें फर्मवेयर डिफ़ॉल्ट व्यू में नहीं खींचता — उनका मान कैनवास किनारे से पहले फिट भी नहीं हो सकता। पूर्वावलोकन में छिपा हुआ मानें।0x220x22aod0x22@69,2090x60 img_number (cnt=10) — स्रोत −5 पर, आगे ऑफ-बाय-वन। ✅ §11.8 के समान ऑफ-बाय-वन
लेकिन गैर-घड़ी संख्याओं के लिए: "Gradient" की तिथि शीर्ष-केंद्र पर (203,80) पर बैठी थी जिसका स्रोत
0x17 था, लेकिन आगे 82-स्कैन ने पड़ोसी पॉइंटर के कोण गेटर (0x0a) को पकड़ लिया
और पॉइंटर की स्थिति → संख्या पॉइंटर के स्थान पर एक बोगस स्रोत के साथ रेंडर हुई। फिक्स: 0x60 रैपर में 61 0a 00 img_number के लिए, −5/−18/−16 पर भरोसा करें जब आगे का स्रोत संख्या के लिए असंभव हो (स्रोत-0 या एक पॉइंटर-एंगल गेटर 0x0a/0e/12/70/71/72) और −18/−16 स्थिति मान्य और गैर-शून्य हो (गैर-शून्य गार्ड relX=0 वाले समूह-चाइल्ड अंकों को छोड़ देता है)।0x17 = तिथि (महीने का दिन), 0x24 = तापमान — अलग। डायल 340 दोनों का उपयोग करता है (0x17
"Jun 09" और एक अलग 0x24 तापमान), इसलिए 0x17 तिथि है, तापमान नहीं। एक डायल जिसकी घड़ी 0x17 स्लॉट में तापमान दिखाती है, वह उपयोगकर्ता-कॉन्फ़िगर्ड कॉम्प्लिकेशन (डिवाइस स्थिति) है, फ़ाइल डिफ़ॉल्ट नहीं।| dec | कार्ड | dec | कार्ड |
|---|
| 0 | खाली स्लॉट | 49–53,97–98 | मौसम |
| 1–6 | कदम | 54–58 | टाइमर |
| 7–12 | कैलोरी | 59–62 | श्वास |
| 13–18 | स्टैंड | 63,65–67 | स्टॉपवॉच |
| 19–24 | मध्यम गतिविधि | 68–71 | बैटरी |
| 25–30 | हृदय गति | 72–76 | हाल ही में |
| 31–36 | SpO₂ | 77–81 | संपर्क |
| 37–42 | तनाव | 82–86 | डायल / फोन |
| 43–48 | नींद | 87–91 | स्पोर्ट (sportId ≠ 0) |
| 92 | संगीत | 93/94/96 | गतिविधि रिकॉर्ड / PAI / चक्र |