├── .gitignore ├── new.fw ├── requirements.txt ├── Mili_wuhan.fw ├── Mili_wuhan.res ├── Yogesh_Theme_1.0_v44_packed.res ├── Yogesh_Theme_1.1_v44_packed.res ├── checkcrc.py ├── README.md ├── constants.py ├── main.py └── auth.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.log 3 | heartrate_*.* 4 | -------------------------------------------------------------------------------- /new.fw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yogeshojha/MiBand3/HEAD/new.fw -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bluepy 2 | pycrypto 3 | curses-menu 4 | crc16 5 | -------------------------------------------------------------------------------- /Mili_wuhan.fw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yogeshojha/MiBand3/HEAD/Mili_wuhan.fw -------------------------------------------------------------------------------- /Mili_wuhan.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yogeshojha/MiBand3/HEAD/Mili_wuhan.res -------------------------------------------------------------------------------- /Yogesh_Theme_1.0_v44_packed.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yogeshojha/MiBand3/HEAD/Yogesh_Theme_1.0_v44_packed.res -------------------------------------------------------------------------------- /Yogesh_Theme_1.1_v44_packed.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yogeshojha/MiBand3/HEAD/Yogesh_Theme_1.1_v44_packed.res -------------------------------------------------------------------------------- /checkcrc.py: -------------------------------------------------------------------------------- 1 | import crc16 2 | import os 3 | crc = 0xFFFF 4 | filename = "Mili_wuhan.res" 5 | extension = os.path.splitext(filename)[1][1:] 6 | print(hex(os.path.getsize(filename))) 7 | print extension 8 | with open(filename) as f: 9 | while True: 10 | c = f.read(1) #takes 20 bytes :D 11 | if not c: 12 | print "Update Over" 13 | break 14 | cInt = int(c.encode('hex'), 16) 15 | crc = ((crc >> 8) | (crc << 8)) & 0xFFFF 16 | crc ^= (cInt & 0xff) 17 | crc ^= ((crc & 0xff) >> 4) 18 | crc ^= (crc << 12) & 0xffff 19 | crc ^= ((crc & 0xFF) << 5) & 0xffff 20 | # raw_input() 21 | crc &= 0xffff 22 | print(crc) 23 | print(hex(crc & 0xff)) 24 | print(hex((crc >> 8) & 0xff)) 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # I hacked MiBand 3, and here is how I did it 2 | 3 | ![1_sC2gb3SimjuTXXQO4wVxGA](https://miro.medium.com/max/700/1*4M9tV1tfWS0Q6tSzaeYfxg.jpeg) 4 | 5 | ![1_sC2gb3SimjuTXXQO4wVxGA](https://user-images.githubusercontent.com/17223002/171925123-7ef0e595-f150-48c4-b844-4e04d404ed58.png) 6 | 7 | Detailed Writeup on how to use this Library 8 | 9 | [I hacked MiBand 3, and here is how I did it. Part I](https://medium.com/@yogeshojha/i-hacked-xiaomi-miband-3-and-here-is-how-i-did-it-43d68c272391) 10 | 11 | [I hacked MiBand 3, and here is how I did it Part II — Reverse Engineering to upload Firmware and Resources Over the Air](https://medium.com/@yogeshojha/i-hacked-miband-3-and-here-is-how-i-did-it-part-ii-reverse-engineering-to-upload-firmware-and-b28a05dfc308) 12 | 13 | # Video POCs 14 | 15 | 16 | ## MI Band Pairing and Sending Calls 17 | 18 | [![POC](https://img.youtube.com/vi/W-18ATOgwmA/0.jpg)](https://www.youtube.com/watch?v=W-18ATOgwmA) 19 | 20 | ## Uploading Firmware OTA 21 | 22 | [![POC](https://img.youtube.com/vi/mubNS-8hh0I/0.jpg)](https://www.youtube.com/watch?v=mubNS-8hh0I) 23 | 24 | # Run 25 | 26 | ### Install dependencies 27 | 28 | `pip install -r requirements.txt` 29 | 30 | ### Connection to MiBand 31 | 32 | Turn on your Bluetooth 33 | 34 | Unpair you MiBand2 from current mobile apps 35 | 36 | Find out your MiBand3 MAC address 37 | 38 | ```sudo hcitool lescan``` 39 | 40 | Run this to auth device 41 | 42 | ```python main.py MAC_ADDRESS --init``` 43 | 44 | If you having problems(BLE can glitch sometimes) 45 | 46 | ```sudo hciconfig hci0 reset``` 47 | 48 | ### If you have trouble installing bluepy 49 | 50 | ```sudo apt-get install libglib2-dev ``` 51 | 52 | 53 | ### Fix hcitool I/O Error 54 | 55 | ```sudo service bluetooth restart ``` 56 | -------------------------------------------------------------------------------- /constants.py: -------------------------------------------------------------------------------- 1 | ___all__ = ['UUIDS'] 2 | 3 | 4 | class Immutable(type): 5 | 6 | def __call__(*args): 7 | raise Exception("You can't create instance of immutable object") 8 | 9 | def __setattr__(*args): 10 | raise Exception("You can't modify immutable object") 11 | 12 | 13 | class UUIDS(object): 14 | 15 | __metaclass__ = Immutable 16 | 17 | BASE = "0000%s-0000-1000-8000-00805f9b34fb" 18 | 19 | SERVICE_MIBAND1 = BASE % 'fee0' 20 | SERVICE_MIBAND2 = BASE % 'fee1' 21 | 22 | SERVICE_ALERT = BASE % '1802' 23 | SERVICE_ALERT_NOTIFICATION = BASE % '1811' 24 | SERVICE_HEART_RATE = BASE % '180d' 25 | SERVICE_DEVICE_INFO = BASE % '180a' 26 | 27 | CHARACTERISTIC_HZ = "00000002-0000-3512-2118-0009af100700" 28 | CHARACTERISTIC_SENSOR = "00000001-0000-3512-2118-0009af100700" 29 | CHARACTERISTIC_AUTH = "00000009-0000-3512-2118-0009af100700" 30 | CHARACTERISTIC_HEART_RATE_MEASURE = "00002a37-0000-1000-8000-00805f9b34fb" 31 | CHARACTERISTIC_HEART_RATE_CONTROL = "00002a39-0000-1000-8000-00805f9b34fb" 32 | CHARACTERISTIC_ALERT = "00002a06-0000-1000-8000-00805f9b34fb" 33 | CHARACTERISTIC_CUSTOM_ALERT = "00002a46-0000-1000-8000-00805f9b34fb" 34 | CHARACTERISTIC_BATTERY = "00000006-0000-3512-2118-0009af100700" 35 | CHARACTERISTIC_STEPS = "00000007-0000-3512-2118-0009af100700" 36 | CHARACTERISTIC_LE_PARAMS = BASE % "FF09" 37 | CHARACTERISTIC_REVISION = 0x2a28 38 | CHARACTERISTIC_SERIAL = 0x2a25 39 | CHARACTERISTIC_HRDW_REVISION = 0x2a27 40 | CHARACTERISTIC_CONFIGURATION = "00000003-0000-3512-2118-0009af100700" 41 | CHARACTERISTIC_DEVICEEVENT = "00000010-0000-3512-2118-0009af100700" 42 | 43 | CHARACTERISTIC_CURRENT_TIME = BASE % '2A2B' 44 | CHARACTERISTIC_AGE = BASE % '2A80' 45 | CHARACTERISTIC_USER_SETTINGS = "00000008-0000-3512-2118-0009af100700" 46 | 47 | NOTIFICATION_DESCRIPTOR = 0x2902 48 | 49 | # Device Firmware Update 50 | SERVICE_DFU_FIRMWARE = "00001530-0000-3512-2118-0009af100700" 51 | CHARACTERISTIC_DFU_FIRMWARE = "00001531-0000-3512-2118-0009af100700" 52 | CHARACTERISTIC_DFU_FIRMWARE_WRITE = "00001532-0000-3512-2118-0009af100700" 53 | 54 | class AUTH_STATES(object): 55 | 56 | __metaclass__ = Immutable 57 | 58 | AUTH_OK = "Auth ok" 59 | AUTH_FAILED = "Auth failed" 60 | ENCRIPTION_KEY_FAILED = "Encryption key auth fail, sending new key" 61 | KEY_SENDING_FAILED = "Key sending failed" 62 | REQUEST_RN_ERROR = "Something went wrong when requesting the random number" 63 | 64 | 65 | class ALERT_TYPES(object): 66 | 67 | __metaclass__ = Immutable 68 | 69 | NONE = '\x00' 70 | MESSAGE = '\x01' 71 | PHONE = '\x02' 72 | 73 | class QUEUE_TYPES(object): 74 | 75 | __metaclass__ = Immutable 76 | 77 | HEART = 'heart' 78 | RAW_ACCEL = 'raw_accel' 79 | RAW_HEART = 'raw_heart' 80 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from auth import MiBand3 3 | from cursesmenu import * 4 | from cursesmenu.items import * 5 | from constants import ALERT_TYPES 6 | import time 7 | import os 8 | def call_immediate(): 9 | print 'Sending Call Alert' 10 | time.sleep(1) 11 | band.send_alert(ALERT_TYPES.PHONE) 12 | def msg_immediate(): 13 | print 'Sending Message Alert' 14 | time.sleep(1) 15 | band.send_alert(ALERT_TYPES.MESSAGE) 16 | def detail_info(): 17 | print 'MiBand' 18 | print 'Soft revision:',band.get_revision() 19 | print 'Hardware revision:',band.get_hrdw_revision() 20 | print 'Serial:',band.get_serial() 21 | print 'Battery:', band.get_battery_info() 22 | print 'Time:', band.get_current_time() 23 | print 'Steps:', band.get_steps() 24 | raw_input('Press Enter to continue') 25 | def custom_message(): 26 | band.send_custom_alert(5) 27 | 28 | def custom_call(): 29 | # custom_call 30 | band.send_custom_alert(3) 31 | 32 | def custom_missed_call(): 33 | band.send_custom_alert(4) 34 | def l(x): 35 | print 'Realtime heart BPM:', x 36 | 37 | def heart_beat(): 38 | band.start_raw_data_realtime(heart_measure_callback=l) 39 | raw_input('Press Enter to continue') 40 | 41 | def change_date(): 42 | band.change_date() 43 | MAC_ADDR = sys.argv[1] 44 | print 'Attempting to connect to ', MAC_ADDR 45 | 46 | def updateFirmware(): 47 | fileName = raw_input('Enter the file Name with Extension\n') 48 | band.dfuUpdate(fileName) 49 | 50 | band = MiBand3(MAC_ADDR, debug=True) 51 | band.setSecurityLevel(level = "medium") 52 | 53 | # Authenticate the MiBand 54 | if len(sys.argv) > 2: 55 | if band.initialize(): 56 | print("Initialized...") 57 | band.disconnect() 58 | sys.exit(0) 59 | else: 60 | band.authenticate() 61 | 62 | menu = CursesMenu("MiBand MAC: " + MAC_ADDR, "Select an option") 63 | detail_menu = FunctionItem("View Band Detail info", detail_info) 64 | call_notif = FunctionItem("Send a High Prority Call Notification", call_immediate) 65 | msg_notif = FunctionItem("Send a Medium Prority Message Notification", msg_immediate) 66 | msg_alert = FunctionItem("Send a Message Notification", custom_message) 67 | call_alert = FunctionItem("Send a Call Notification", custom_call) 68 | miss_call_alert = FunctionItem("Send a Missed Call Notification", custom_missed_call) 69 | change_date_time = FunctionItem("Change Date and Time", change_date) 70 | heart_beat_menu = FunctionItem("Get Heart BPM", heart_beat) 71 | dfu_update_menu = FunctionItem("DFU Update", updateFirmware) 72 | 73 | menu.append_item(detail_menu) 74 | menu.append_item(call_notif) 75 | menu.append_item(msg_notif) 76 | menu.append_item(msg_alert) 77 | menu.append_item(call_alert) 78 | menu.append_item(change_date_time) 79 | menu.append_item(miss_call_alert) 80 | menu.append_item(heart_beat_menu) 81 | menu.append_item(dfu_update_menu) 82 | menu.show() 83 | -------------------------------------------------------------------------------- /auth.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import time 3 | import logging 4 | from datetime import datetime 5 | from Crypto.Cipher import AES 6 | from Queue import Queue, Empty 7 | from bluepy.btle import Peripheral, DefaultDelegate, ADDR_TYPE_RANDOM, BTLEException 8 | import crc16 9 | import os 10 | import struct 11 | 12 | from constants import UUIDS, AUTH_STATES, ALERT_TYPES, QUEUE_TYPES 13 | 14 | 15 | class AuthenticationDelegate(DefaultDelegate): 16 | 17 | """This Class inherits DefaultDelegate to handle the authentication process.""" 18 | 19 | def __init__(self, device): 20 | DefaultDelegate.__init__(self) 21 | self.device = device 22 | 23 | def handleNotification(self, hnd, data): 24 | if hnd == self.device._char_auth.getHandle(): 25 | if data[:3] == b'\x10\x01\x01': 26 | self.device._req_rdn() 27 | elif data[:3] == b'\x10\x01\x04': 28 | self.device.state = AUTH_STATES.KEY_SENDING_FAILED 29 | elif data[:3] == b'\x10\x02\x01': 30 | # 16 bytes 31 | random_nr = data[3:] 32 | self.device._send_enc_rdn(random_nr) 33 | elif data[:3] == b'\x10\x02\x04': 34 | self.device.state = AUTH_STATES.REQUEST_RN_ERROR 35 | elif data[:3] == b'\x10\x03\x01': 36 | self.device.state = AUTH_STATES.AUTH_OK 37 | elif data[:3] == b'\x10\x03\x04': 38 | self.device.status = AUTH_STATES.ENCRIPTION_KEY_FAILED 39 | self.device._send_key() 40 | else: 41 | self.device.state = AUTH_STATES.AUTH_FAILED 42 | elif hnd == self.device._char_heart_measure.getHandle(): 43 | self.device.queue.put((QUEUE_TYPES.HEART, data)) 44 | elif hnd == 0x38: 45 | # Not sure about this, need test 46 | if len(data) == 20 and struct.unpack('b', data[0])[0] == 1: 47 | self.device.queue.put((QUEUE_TYPES.RAW_ACCEL, data)) 48 | elif len(data) == 16: 49 | self.device.queue.put((QUEUE_TYPES.RAW_HEART, data)) 50 | else: 51 | self.device._log.error("Unhandled Response " + hex(hnd) + ": " + 52 | str(data.encode("hex")) + " len:" + str(len(data))) 53 | 54 | 55 | class MiBand3(Peripheral): 56 | _KEY = b'\x01\x23\x45\x67\x89\x01\x22\x23\x34\x45\x56\x67\x78\x89\x90\x02' 57 | _send_key_cmd = struct.pack('<18s', b'\x01\x08' + _KEY) 58 | _send_rnd_cmd = struct.pack('<2s', b'\x02\x08') 59 | _send_enc_key = struct.pack('<2s', b'\x03\x08') 60 | 61 | def __init__(self, mac_address, timeout=0.5, debug=False): 62 | FORMAT = '%(asctime)-15s %(name)s (%(levelname)s) > %(message)s' 63 | logging.basicConfig(format=FORMAT) 64 | log_level = logging.WARNING if not debug else logging.DEBUG 65 | self._log = logging.getLogger(self.__class__.__name__) 66 | self._log.setLevel(log_level) 67 | 68 | self._log.info('Connecting to ' + mac_address) 69 | Peripheral.__init__(self, mac_address, addrType=ADDR_TYPE_RANDOM) 70 | self._log.info('Connected') 71 | 72 | self.timeout = timeout 73 | self.mac_address = mac_address 74 | self.state = None 75 | self.queue = Queue() 76 | self.heart_measure_callback = None 77 | self.heart_raw_callback = None 78 | self.accel_raw_callback = None 79 | 80 | self.svc_1 = self.getServiceByUUID(UUIDS.SERVICE_MIBAND1) 81 | self.svc_2 = self.getServiceByUUID(UUIDS.SERVICE_MIBAND2) 82 | self.svc_heart = self.getServiceByUUID(UUIDS.SERVICE_HEART_RATE) 83 | 84 | self._char_auth = self.svc_2.getCharacteristics(UUIDS.CHARACTERISTIC_AUTH)[0] 85 | self._desc_auth = self._char_auth.getDescriptors(forUUID=UUIDS.NOTIFICATION_DESCRIPTOR)[0] 86 | 87 | self._char_heart_ctrl = self.svc_heart.getCharacteristics(UUIDS.CHARACTERISTIC_HEART_RATE_CONTROL)[0] 88 | self._char_heart_measure = self.svc_heart.getCharacteristics(UUIDS.CHARACTERISTIC_HEART_RATE_MEASURE)[0] 89 | 90 | # Enable auth service notifications on startup 91 | self._auth_notif(True) 92 | # Let band to settle 93 | self.waitForNotifications(0.1) 94 | 95 | # Auth helpers ###################################################################### 96 | 97 | def _auth_notif(self, enabled): 98 | if enabled: 99 | self._log.info("Enabling Auth Service notifications status...") 100 | self._desc_auth.write(b"\x01\x00", True) 101 | elif not enabled: 102 | self._log.info("Disabling Auth Service notifications status...") 103 | self._desc_auth.write(b"\x00\x00", True) 104 | else: 105 | self._log.error("Something went wrong while changing the Auth Service notifications status...") 106 | 107 | def _encrypt(self, message): 108 | aes = AES.new(self._KEY, AES.MODE_ECB) 109 | return aes.encrypt(message) 110 | 111 | def _send_key(self): 112 | self._log.info("Sending Key...") 113 | self._char_auth.write(self._send_key_cmd) 114 | self.waitForNotifications(self.timeout) 115 | 116 | def _req_rdn(self): 117 | self._log.info("Requesting random number...") 118 | self._char_auth.write(self._send_rnd_cmd) 119 | self.waitForNotifications(self.timeout) 120 | 121 | def _send_enc_rdn(self, data): 122 | self._log.info("Sending encrypted random number") 123 | cmd = self._send_enc_key + self._encrypt(data) 124 | send_cmd = struct.pack('<18s', cmd) 125 | self._char_auth.write(send_cmd) 126 | self.waitForNotifications(self.timeout) 127 | 128 | # Parse helpers ################################################################### 129 | 130 | def _parse_raw_accel(self, bytes): 131 | res = [] 132 | for i in xrange(3): 133 | g = struct.unpack('hhh', bytes[2 + i * 6:8 + i * 6]) 134 | res.append({'x': g[0], 'y': g[1], 'wtf': g[2]}) 135 | return res 136 | 137 | def _parse_raw_heart(self, bytes): 138 | res = struct.unpack('HHHHHHH', bytes[2:]) 139 | return res 140 | 141 | def _parse_date(self, bytes): 142 | year = struct.unpack('h', bytes[0:2])[0] if len(bytes) >= 2 else None 143 | month = struct.unpack('b', bytes[2])[0] if len(bytes) >= 3 else None 144 | day = struct.unpack('b', bytes[3])[0] if len(bytes) >= 4 else None 145 | hours = struct.unpack('b', bytes[4])[0] if len(bytes) >= 5 else None 146 | minutes = struct.unpack('b', bytes[5])[0] if len(bytes) >= 6 else None 147 | seconds = struct.unpack('b', bytes[6])[0] if len(bytes) >= 7 else None 148 | day_of_week = struct.unpack('b', bytes[7])[0] if len(bytes) >= 8 else None 149 | fractions256 = struct.unpack('b', bytes[8])[0] if len(bytes) >= 9 else None 150 | 151 | return {"date": datetime(*(year, month, day, hours, minutes, seconds)), "day_of_week": day_of_week, "fractions256": fractions256} 152 | 153 | def _parse_battery_response(self, bytes): 154 | level = struct.unpack('b', bytes[1])[0] if len(bytes) >= 2 else None 155 | last_level = struct.unpack('b', bytes[19])[0] if len(bytes) >= 20 else None 156 | status = 'normal' if struct.unpack('b', bytes[2])[0] == 0 else "charging" 157 | datetime_last_charge = self._parse_date(bytes[11:18]) 158 | datetime_last_off = self._parse_date(bytes[3:10]) 159 | 160 | res = { 161 | "status": status, 162 | "level": level, 163 | "last_level": last_level, 164 | "last_level": last_level, 165 | "last_charge": datetime_last_charge, 166 | "last_off": datetime_last_off 167 | } 168 | return res 169 | 170 | # Queue ################################################################### 171 | 172 | def _get_from_queue(self, _type): 173 | try: 174 | res = self.queue.get(False) 175 | except Empty: 176 | return None 177 | if res[0] != _type: 178 | self.queue.put(res) 179 | return None 180 | return res[1] 181 | 182 | def _parse_queue(self): 183 | while True: 184 | try: 185 | res = self.queue.get(False) 186 | _type = res[0] 187 | if self.heart_measure_callback and _type == QUEUE_TYPES.HEART: 188 | self.heart_measure_callback(struct.unpack('bb', res[1])[1]) 189 | elif self.heart_raw_callback and _type == QUEUE_TYPES.RAW_HEART: 190 | self.heart_raw_callback(self._parse_raw_heart(res[1])) 191 | elif self.accel_raw_callback and _type == QUEUE_TYPES.RAW_ACCEL: 192 | self.accel_raw_callback(self._parse_raw_accel(res[1])) 193 | except Empty: 194 | break 195 | 196 | # API #################################################################### 197 | 198 | def initialize(self): 199 | self.setDelegate(AuthenticationDelegate(self)) 200 | self._send_key() 201 | 202 | while True: 203 | self.waitForNotifications(0.1) 204 | if self.state == AUTH_STATES.AUTH_OK: 205 | self._log.info('Initialized') 206 | self._auth_notif(False) 207 | return True 208 | elif self.state is None: 209 | continue 210 | 211 | self._log.error(self.state) 212 | return False 213 | 214 | def authenticate(self): 215 | self.setDelegate(AuthenticationDelegate(self)) 216 | self._req_rdn() 217 | 218 | while True: 219 | self.waitForNotifications(0.1) 220 | if self.state == AUTH_STATES.AUTH_OK: 221 | self._log.info('Authenticated') 222 | return True 223 | elif self.state is None: 224 | continue 225 | 226 | self._log.error(self.state) 227 | return False 228 | 229 | def get_battery_info(self): 230 | char = self.svc_1.getCharacteristics(UUIDS.CHARACTERISTIC_BATTERY)[0] 231 | return self._parse_battery_response(char.read()) 232 | 233 | def get_current_time(self): 234 | char = self.svc_1.getCharacteristics(UUIDS.CHARACTERISTIC_CURRENT_TIME)[0] 235 | return self._parse_date(char.read()[0:9]) 236 | 237 | def get_revision(self): 238 | svc = self.getServiceByUUID(UUIDS.SERVICE_DEVICE_INFO) 239 | char = svc.getCharacteristics(UUIDS.CHARACTERISTIC_REVISION)[0] 240 | data = char.read() 241 | return data 242 | 243 | def get_hrdw_revision(self): 244 | svc = self.getServiceByUUID(UUIDS.SERVICE_DEVICE_INFO) 245 | char = svc.getCharacteristics(UUIDS.CHARACTERISTIC_HRDW_REVISION)[0] 246 | data = char.read() 247 | return data 248 | 249 | def set_encoding(self, encoding="en_US"): 250 | char = self.svc_1.getCharacteristics(UUIDS.CHARACTERISTIC_CONFIGURATION)[0] 251 | packet = struct.pack('5s', encoding) 252 | packet = b'\x06\x17\x00' + packet 253 | return char.write(packet) 254 | 255 | def set_heart_monitor_sleep_support(self, enabled=True, measure_minute_interval=1): 256 | char_m = self.svc_heart.getCharacteristics(UUIDS.CHARACTERISTIC_HEART_RATE_MEASURE)[0] 257 | char_d = char_m.getDescriptors(forUUID=UUIDS.NOTIFICATION_DESCRIPTOR)[0] 258 | char_d.write(b'\x01\x00', True) 259 | self._char_heart_ctrl.write(b'\x15\x00\x00', True) 260 | # measure interval set to off 261 | self._char_heart_ctrl.write(b'\x14\x00', True) 262 | if enabled: 263 | self._char_heart_ctrl.write(b'\x15\x00\x01', True) 264 | # measure interval set 265 | self._char_heart_ctrl.write(b'\x14' + str(measure_minute_interval).encode(), True) 266 | char_d.write(b'\x00\x00', True) 267 | 268 | def get_serial(self): 269 | svc = self.getServiceByUUID(UUIDS.SERVICE_DEVICE_INFO) 270 | char = svc.getCharacteristics(UUIDS.CHARACTERISTIC_SERIAL)[0] 271 | data = char.read() 272 | serial = struct.unpack('12s', data[-12:])[0] if len(data) == 12 else None 273 | return serial 274 | 275 | def get_steps(self): 276 | char = self.svc_1.getCharacteristics(UUIDS.CHARACTERISTIC_STEPS)[0] 277 | a = char.read() 278 | steps = struct.unpack('h', a[1:3])[0] if len(a) >= 3 else None 279 | meters = struct.unpack('h', a[5:7])[0] if len(a) >= 7 else None 280 | fat_gramms = struct.unpack('h', a[2:4])[0] if len(a) >= 4 else None 281 | # why only 1 byte?? 282 | callories = struct.unpack('b', a[9])[0] if len(a) >= 10 else None 283 | return { 284 | "steps": steps, 285 | "meters": meters, 286 | "fat_gramms": fat_gramms, 287 | "callories": callories 288 | } 289 | 290 | def send_alert(self, _type): 291 | svc = self.getServiceByUUID(UUIDS.SERVICE_ALERT) 292 | char = svc.getCharacteristics(UUIDS.CHARACTERISTIC_ALERT)[0] 293 | char.write(_type) 294 | 295 | def send_custom_alert(self, type): 296 | if type == 5: 297 | base_value = '\x05\x01' 298 | elif type == 4: 299 | base_value = '\x04\x01' 300 | elif type == 3: 301 | base_value = '\x03\x01' 302 | phone = raw_input('Sender Name or Caller ID') 303 | svc = self.getServiceByUUID(UUIDS.SERVICE_ALERT_NOTIFICATION) 304 | char = svc.getCharacteristics(UUIDS.CHARACTERISTIC_CUSTOM_ALERT)[0] 305 | char.write(base_value+phone, withResponse=True) 306 | 307 | def change_date(self): 308 | print('Change date and time') 309 | svc = self.getServiceByUUID(UUIDS.SERVICE_MIBAND1) 310 | char = svc.getCharacteristics(UUIDS.CHARACTERISTIC_CURRENT_TIME)[0] 311 | # date = raw_input('Enter the date in dd-mm-yyyy format\n') 312 | # time = raw_input('Enter the time in HH:MM:SS format\n') 313 | # 314 | # day = int(date[:2]) 315 | # month = int(date[3:5]) 316 | # year = int(date[6:10]) 317 | # fraction = year / 256 318 | # rem = year % 256 319 | # 320 | # hour = int(time[:2]) 321 | # minute = int(time[3:5]) 322 | # seconds = int(time[6:]) 323 | # 324 | # write_val = format(rem, '#04x') + format(fraction, '#04x') + format(month, '#04x') + format(day, '#04x') + format(hour, '#04x') + format(minute, '#04x') + format(seconds, '#04x') + format(5, '#04x') + format(0, '#04x') + format(0, '#04x') +'0x16' 325 | # write_val = write_val.replace('0x', '\\x') 326 | # print(write_val) 327 | char.write('\xe2\x07\x01\x1e\x00\x00\x00\x00\x00\x00\x16', withResponse=True) 328 | raw_input('Date Changed, press any key to continue') 329 | def dfuUpdate(self, fileName): 330 | print('Update Firmware/Resource') 331 | svc = self.getServiceByUUID(UUIDS.SERVICE_DFU_FIRMWARE) 332 | char = svc.getCharacteristics(UUIDS.CHARACTERISTIC_DFU_FIRMWARE)[0] 333 | extension = os.path.splitext(fileName)[1][1:] 334 | fileSize = os.path.getsize(fileName) 335 | # calculating crc checksum of firmware 336 | #crc16 337 | crc = 0xFFFF 338 | with open(fileName) as f: 339 | while True: 340 | c = f.read(1) 341 | if not c: 342 | break 343 | cInt = int(c.encode('hex'), 16) #converting hex to int 344 | # now calculate crc 345 | crc = ((crc >> 8) | (crc << 8)) & 0xFFFF 346 | crc ^= (cInt & 0xff) 347 | crc ^= ((crc & 0xff) >> 4) 348 | crc ^= (crc << 12) & 0xFFFF 349 | crc ^= ((crc & 0xFF) << 5) & 0xFFFFFF 350 | crc &= 0xFFFF 351 | print('CRC Value is-->', crc) 352 | raw_input('Press Enter to Continue') 353 | if extension.lower() == "res": 354 | # file size hex value is 355 | char.write('\x01'+ struct.pack(" ', hex(crc & 0xFF), hex((crc >> 8) & 0xFF)) 372 | checkSum = b'\x04' + chr(crc & 0xFF) + chr((crc >> 8) & 0xFF) 373 | char.write(checkSum, withResponse=True) 374 | if extension.lower() == "fw": 375 | self.waitForNotifications(0.5) 376 | char.write('\x05', withResponse=True) 377 | print('Update Complete') 378 | raw_input('Press Enter to Continue') 379 | def start_raw_data_realtime(self, heart_measure_callback=None, heart_raw_callback=None, accel_raw_callback=None): 380 | char_m = self.svc_heart.getCharacteristics(UUIDS.CHARACTERISTIC_HEART_RATE_MEASURE)[0] 381 | char_d = char_m.getDescriptors(forUUID=UUIDS.NOTIFICATION_DESCRIPTOR)[0] 382 | char_ctrl = self.svc_heart.getCharacteristics(UUIDS.CHARACTERISTIC_HEART_RATE_CONTROL)[0] 383 | 384 | if heart_measure_callback: 385 | self.heart_measure_callback = heart_measure_callback 386 | if heart_raw_callback: 387 | self.heart_raw_callback = heart_raw_callback 388 | if accel_raw_callback: 389 | self.accel_raw_callback = accel_raw_callback 390 | 391 | char_sensor = self.svc_1.getCharacteristics(UUIDS.CHARACTERISTIC_SENSOR)[0] 392 | 393 | # stop heart monitor continues & manual 394 | char_ctrl.write(b'\x15\x02\x00', True) 395 | char_ctrl.write(b'\x15\x01\x00', True) 396 | # WTF 397 | # char_sens_d1.write(b'\x01\x00', True) 398 | # enabling accelerometer & heart monitor raw data notifications 399 | char_sensor.write(b'\x01\x03\x19') 400 | # IMO: enablee heart monitor notifications 401 | char_d.write(b'\x01\x00', True) 402 | # start hear monitor continues 403 | char_ctrl.write(b'\x15\x01\x01', True) 404 | # WTF 405 | char_sensor.write(b'\x02') 406 | t = time.time() 407 | while True: 408 | self.waitForNotifications(0.5) 409 | self._parse_queue() 410 | # send ping request every 12 sec 411 | if (time.time() - t) >= 12: 412 | char_ctrl.write(b'\x16', True) 413 | t = time.time() 414 | 415 | def stop_realtime(self): 416 | char_m = self.svc_heart.getCharacteristics(UUIDS.CHARACTERISTIC_HEART_RATE_MEASURE)[0] 417 | char_d = char_m.getDescriptors(forUUID=UUIDS.NOTIFICATION_DESCRIPTOR)[0] 418 | char_ctrl = self.svc_heart.getCharacteristics(UUIDS.CHARACTERISTIC_HEART_RATE_CONTROL)[0] 419 | 420 | char_sensor1 = self.svc_1.getCharacteristics(UUIDS.CHARACTERISTIC_HZ)[0] 421 | char_sens_d1 = char_sensor1.getDescriptors(forUUID=UUIDS.NOTIFICATION_DESCRIPTOR)[0] 422 | 423 | char_sensor2 = self.svc_1.getCharacteristics(UUIDS.CHARACTERISTIC_SENSOR)[0] 424 | 425 | # stop heart monitor continues 426 | char_ctrl.write(b'\x15\x01\x00', True) 427 | char_ctrl.write(b'\x15\x01\x00', True) 428 | # IMO: stop heart monitor notifications 429 | char_d.write(b'\x00\x00', True) 430 | # WTF 431 | char_sensor2.write(b'\x03') 432 | # IMO: stop notifications from sensors 433 | char_sens_d1.write(b'\x00\x00', True) 434 | 435 | self.heart_measure_callback = None 436 | self.heart_raw_callback = None 437 | self.accel_raw_callback = None 438 | 439 | def start_get_previews_data(self, start_timestamp): 440 | self._auth_previews_data_notif(True) 441 | self.waitForNotifications(0.1) 442 | print("Trigger activity communication") 443 | year = struct.pack("