Repository: creotiv/MiBand2 Branch: master Commit: 1926fcf0968b Files: 9 Total size: 37.9 KB Directory structure: gitextract_gopglz3o/ ├── .gitignore ├── LICENSE ├── README.md ├── base.py ├── constants.py ├── dump.py ├── example.py ├── plot.py └── requirements.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.pyc *.log heartrate_*.* .env env ================================================ FILE: LICENSE ================================================ Creative Commons Legal Code CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. ================================================ FILE: README.md ================================================ # MiBand2 Library to work with Xiaomi MiBand 2 (Support python2/python3) [Read the Article here](https://medium.com/@a.nikishaev/how-i-hacked-xiaomi-miband-2-to-control-it-from-linux-a5bd2f36d3ad) # Contributors & Info Sources 1) Base lib provided by [Leo Soares](https://github.com/leojrfs/miband2) 2) Additional debug & fixes was made by my friend [Volodymyr Shymanskyy](https://github.com/vshymanskyy/miband2-python-test) 3) Some info that really helped i got from [Freeyourgadget team](https://github.com/Freeyourgadget/Gadgetbridge/tree/master/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2) ## Interesting stuff [More interesing stuff about Software Developing](http://t.me/devs_world) # Run 1) Install dependencies ```sh pip install -r requirements.txt ``` 2) Turn on your Bluetooth 3) Unpair you MiBand2 from current mobile apps 4) Find out you MiBand2 MAC address ```sh sudo hcitool lescan ``` 5) Run this to auth device ```sh python example.py --mac MAC_ADDRESS --init ``` 6) Run this to call demo functions ```sh python example.py --standard --mac MAC_ADDRESS python example.py --help ``` 7) If you having problems(BLE can glitch sometimes) try this and repeat from 4) ```sh sudo hciconfig hci0 reset ``` Also there is cool JS library that made Volodymyr Shymansky https://github.com/vshymanskyy/miband-js # Donate If you like what im doing, you can send me some money for pepsi(i dont drink alcohol). https://patreon.com/mlworld

CC0
To the extent possible under law, Andrey Nikishaev has waived all copyright and related or neighboring rights to Library to work with Xiaomi MiBand 2 .

================================================ FILE: base.py ================================================ import struct import time import logging from datetime import datetime, timedelta from Crypto.Cipher import AES try: from Queue import Queue, Empty except ImportError: from queue import Queue, Empty from bluepy.btle import Peripheral, DefaultDelegate, ADDR_TYPE_RANDOM, BTLEException from constants import UUIDS, AUTH_STATES, ALERT_TYPES, QUEUE_TYPES class AuthenticationDelegate(DefaultDelegate): """This Class inherits DefaultDelegate to handle the authentication process.""" def __init__(self, device): DefaultDelegate.__init__(self) self.device = device def handleNotification(self, hnd, data): # Debug purposes if hnd == self.device._char_auth.getHandle(): if data[:3] == b'\x10\x01\x01': self.device._req_rdn() elif data[:3] == b'\x10\x01\x04': self.device.state = AUTH_STATES.KEY_SENDING_FAILED elif data[:3] == b'\x10\x02\x01': # 16 bytes random_nr = data[3:] self.device._send_enc_rdn(random_nr) elif data[:3] == b'\x10\x02\x04': self.device.state = AUTH_STATES.REQUEST_RN_ERROR elif data[:3] == b'\x10\x03\x01': self.device.state = AUTH_STATES.AUTH_OK elif data[:3] == b'\x10\x03\x04': self.device.status = AUTH_STATES.ENCRIPTION_KEY_FAILED self.device._send_key() else: self.device.state = AUTH_STATES.AUTH_FAILED elif hnd == self.device._char_heart_measure.getHandle(): self.device.queue.put((QUEUE_TYPES.HEART, data)) elif hnd == 0x38: # Not sure about this, need test if len(data) == 20 and struct.unpack('b', data[0:1])[0] == 1: self.device.queue.put((QUEUE_TYPES.RAW_ACCEL, data)) elif len(data) == 16: self.device.queue.put((QUEUE_TYPES.RAW_HEART, data)) # The fetch characteristic controls the communication with the activity characteristic. # It can trigger the communication. elif hnd == self.device._char_fetch.getHandle(): if data[:3] == b'\x10\x01\x01': # get timestamp from what date the data actually is received year = struct.unpack(" datetime.now() - timedelta(minutes=1): self.device.active = False return print("Trigger more communication") time.sleep(1) t = self.device.last_timestamp + timedelta(minutes=1) self.device.start_get_previews_data(t) else: pkg = self.device.pkg self.device.pkg += 1 i = 1 while i < len(data): index = int(pkg) * 4 + (i - 1) / 4 timestamp = self.device.first_timestamp + timedelta(minutes=index) self.device.last_timestamp = timestamp # category = int.from_bytes(data[i:i + 1], byteorder='little') category = struct.unpack("= 2 else None month = struct.unpack('b', bytes[2:3])[0] if len(bytes) >= 3 else None day = struct.unpack('b', bytes[3:4])[0] if len(bytes) >= 4 else None hours = struct.unpack('b', bytes[4:5])[0] if len(bytes) >= 5 else None minutes = struct.unpack('b', bytes[5:6])[0] if len(bytes) >= 6 else None seconds = struct.unpack('b', bytes[6:7])[0] if len(bytes) >= 7 else None day_of_week = struct.unpack('b', bytes[7:8])[0] if len(bytes) >= 8 else None fractions256 = struct.unpack('b', bytes[8:9])[0] if len(bytes) >= 9 else None return {"date": datetime(*(year, month, day, hours, minutes, seconds)), "day_of_week": day_of_week, "fractions256": fractions256} @staticmethod def create_date_data(date): data = struct.pack( 'hbbbbbbbxx', date.year, date.month, date.day, date.hour, date.minute, date.second, date.weekday(), 0 ) return data def _parse_battery_response(self, bytes): level = struct.unpack('b', bytes[1:2])[0] if len(bytes) >= 2 else None last_level = struct.unpack('b', bytes[19:20])[0] if len(bytes) >= 20 else None status = 'normal' if struct.unpack('b', bytes[2:3])[0] == 0 else "charging" datetime_last_charge = self._parse_date(bytes[11:18]) datetime_last_off = self._parse_date(bytes[3:10]) # WTF? # struct.unpack('b', bytes[10]) # struct.unpack('b', bytes[18]) # print struct.unpack('b', bytes[10]), struct.unpack('b', bytes[18]) res = { "status": status, "level": level, "last_level": last_level, "last_charge": datetime_last_charge, "last_off": datetime_last_off } return res # Queue ################################################################### def _get_from_queue(self, _type): try: res = self.queue.get(False) except Empty: return None if res[0] != _type: self.queue.put(res) return None return res[1] def _parse_queue(self): while True: try: res = self.queue.get(False) _type = res[0] if self.heart_measure_callback and _type == QUEUE_TYPES.HEART: self.heart_measure_callback(struct.unpack('bb', res[1])[1]) elif self.heart_raw_callback and _type == QUEUE_TYPES.RAW_HEART: self.heart_raw_callback(self._parse_raw_heart(res[1])) elif self.accel_raw_callback and _type == QUEUE_TYPES.RAW_ACCEL: self.accel_raw_callback(self._parse_raw_accel(res[1])) except Empty: break # API #################################################################### def initialize(self): self.setDelegate(AuthenticationDelegate(self)) self._send_key() while True: self.waitForNotifications(0.1) if self.state == AUTH_STATES.AUTH_OK: self._log.info('Initialized') self._auth_notif(False) return True elif self.state is None: continue self._log.error(self.state) return False def authenticate(self): self.setDelegate(AuthenticationDelegate(self)) self._req_rdn() while True: self.waitForNotifications(0.1) if self.state == AUTH_STATES.AUTH_OK: self._log.info('Authenticated') return True elif self.state is None: continue self._log.error(self.state) return False def get_battery_info(self): char = self.svc_1.getCharacteristics(UUIDS.CHARACTERISTIC_BATTERY)[0] return self._parse_battery_response(char.read()) def get_current_time(self): char = self.svc_1.getCharacteristics(UUIDS.CHARACTERISTIC_CURRENT_TIME)[0] return self._parse_date(char.read()[0:9]) def set_current_time(self, date): char = self.svc_1.getCharacteristics(UUIDS.CHARACTERISTIC_CURRENT_TIME)[0] return char.write(self.create_date_data(date), True) def get_revision(self): svc = self.getServiceByUUID(UUIDS.SERVICE_DEVICE_INFO) char = svc.getCharacteristics(UUIDS.CHARACTERISTIC_REVISION)[0] data = char.read() revision = struct.unpack('9s', data[-9:])[0] if len(data) == 9 else None return revision def get_hrdw_revision(self): svc = self.getServiceByUUID(UUIDS.SERVICE_DEVICE_INFO) char = svc.getCharacteristics(UUIDS.CHARACTERISTIC_HRDW_REVISION)[0] data = char.read() revision = struct.unpack('8s', data[-8:])[0] if len(data) == 8 else None return revision def set_encoding(self, encoding="en_US"): char = self.svc_1.getCharacteristics(UUIDS.CHARACTERISTIC_CONFIGURATION)[0] packet = struct.pack('5s', encoding) packet = b'\x06\x17\x00' + packet return char.write(packet) def set_heart_monitor_sleep_support(self, enabled=True, measure_minute_interval=1): char_m = self.svc_heart.getCharacteristics(UUIDS.CHARACTERISTIC_HEART_RATE_MEASURE)[0] char_d = char_m.getDescriptors(forUUID=UUIDS.NOTIFICATION_DESCRIPTOR)[0] char_d.write(b'\x01\x00', True) self._char_heart_ctrl.write(b'\x15\x00\x00', True) # measure interval set to off self._char_heart_ctrl.write(b'\x14\x00', True) if enabled: if measure_minute_interval > 120: measure_minute_interval = 120 self._char_heart_ctrl.write(b'\x15\x00\x01', True) # measure interval set self._char_heart_ctrl.write(b'\x14' + bytes([measure_minute_interval]), True) char_d.write(b'\x00\x00', True) def set_heart_monitor_measurement_interval(self, enabled=True, measure_minute_interval=1): if enabled: if measure_minute_interval > 120: measure_minute_interval = 120 self._char_heart_ctrl.write(b'\x14' + bytes([measure_minute_interval]), True) else: self._char_heart_ctrl.write(b'\x14\x00', True) def get_serial(self): svc = self.getServiceByUUID(UUIDS.SERVICE_DEVICE_INFO) char = svc.getCharacteristics(UUIDS.CHARACTERISTIC_SERIAL)[0] data = char.read() serial = struct.unpack('12s', data[-12:])[0] if len(data) == 12 else None return serial def get_steps(self): char = self.svc_1.getCharacteristics(UUIDS.CHARACTERISTIC_STEPS)[0] a = char.read() steps = struct.unpack('h', a[1:3])[0] if len(a) >= 3 else None meters = struct.unpack('h', a[5:7])[0] if len(a) >= 7 else None fat_grams = struct.unpack('h', a[2:4])[0] if len(a) >= 4 else None # why only 1 byte?? calories = struct.unpack('b', a[9:10])[0] if len(a) >= 10 else None return { "steps": steps, "meters": meters, "fat_grams": fat_grams, "calories": calories } def send_alert(self, _type): svc = self.getServiceByUUID(UUIDS.SERVICE_ALERT) char = svc.getCharacteristics(UUIDS.CHARACTERISTIC_ALERT)[0] char.write(_type) def get_heart_rate_one_time(self): # stop continous self._char_heart_ctrl.write(b'\x15\x01\x00', True) # stop manual self._char_heart_ctrl.write(b'\x15\x02\x00', True) # start manual self._char_heart_ctrl.write(b'\x15\x02\x01', True) res = None while not res: self.waitForNotifications(self.timeout) res = self._get_from_queue(QUEUE_TYPES.HEART) rate = struct.unpack('bb', res)[1] return rate def start_heart_rate_realtime(self, heart_measure_callback): char_m = self.svc_heart.getCharacteristics(UUIDS.CHARACTERISTIC_HEART_RATE_MEASURE)[0] char_d = char_m.getDescriptors(forUUID=UUIDS.NOTIFICATION_DESCRIPTOR)[0] char_ctrl = self.svc_heart.getCharacteristics(UUIDS.CHARACTERISTIC_HEART_RATE_CONTROL)[0] self.heart_measure_callback = heart_measure_callback # stop heart monitor continues & manual char_ctrl.write(b'\x15\x02\x00', True) char_ctrl.write(b'\x15\x01\x00', True) # enable heart monitor notifications char_d.write(b'\x01\x00', True) # start hear monitor continues char_ctrl.write(b'\x15\x01\x01', True) t = time.time() while True: self.waitForNotifications(0.5) self._parse_queue() # send ping request every 12 sec if (time.time() - t) >= 12: char_ctrl.write(b'\x16', True) t = time.time() def start_raw_data_realtime(self, heart_measure_callback=None, heart_raw_callback=None, accel_raw_callback=None): char_m = self.svc_heart.getCharacteristics(UUIDS.CHARACTERISTIC_HEART_RATE_MEASURE)[0] char_d = char_m.getDescriptors(forUUID=UUIDS.NOTIFICATION_DESCRIPTOR)[0] char_ctrl = self.svc_heart.getCharacteristics(UUIDS.CHARACTERISTIC_HEART_RATE_CONTROL)[0] if heart_measure_callback: self.heart_measure_callback = heart_measure_callback if heart_raw_callback: self.heart_raw_callback = heart_raw_callback if accel_raw_callback: self.accel_raw_callback = accel_raw_callback char_sensor = self.svc_1.getCharacteristics(UUIDS.CHARACTERISTIC_SENSOR)[0] # char_sens_d = char_sensor1.getDescriptors(forUUID=UUIDS.NOTIFICATION_DESCRIPTOR)[0] # char_sensor2 = self.svc_1.getCharacteristics('000000010000351221180009af100700')[0] # char_sens_d2 = char_sensor2.getDescriptors(forUUID=UUIDS.NOTIFICATION_DESCRIPTOR)[0] # char_sensor3 = self.svc_1.getCharacteristics('000000070000351221180009af100700')[0] # char_sens_d3 = char_sensor3.getDescriptors(forUUID=UUIDS.NOTIFICATION_DESCRIPTOR)[0] # char_sens_d1.write(b'\x01\x00', True) # char_sens_d2.write(b'\x01\x00', True) # char_sensor2.write(b'\x01\x03\x19') # char_sens_d2.write(b'\x00\x00', True) # char_d.write(b'\x01\x00', True) # char_ctrl.write(b'\x15\x01\x01', True) # char_sensor2.write(b'\x02') # stop heart monitor continues & manual char_ctrl.write(b'\x15\x02\x00', True) char_ctrl.write(b'\x15\x01\x00', True) # WTF # char_sens_d1.write(b'\x01\x00', True) # enabling accelerometer & heart monitor raw data notifications char_sensor.write(b'\x01\x03\x19') # IMO: enablee heart monitor notifications char_d.write(b'\x01\x00', True) # start hear monitor continues char_ctrl.write(b'\x15\x01\x01', True) # WTF char_sensor.write(b'\x02') t = time.time() while True: self.waitForNotifications(0.5) self._parse_queue() # send ping request every 12 sec if (time.time() - t) >= 12: char_ctrl.write(b'\x16', True) t = time.time() def stop_realtime(self): char_m = self.svc_heart.getCharacteristics(UUIDS.CHARACTERISTIC_HEART_RATE_MEASURE)[0] char_d = char_m.getDescriptors(forUUID=UUIDS.NOTIFICATION_DESCRIPTOR)[0] char_ctrl = self.svc_heart.getCharacteristics(UUIDS.CHARACTERISTIC_HEART_RATE_CONTROL)[0] char_sensor1 = self.svc_1.getCharacteristics(UUIDS.CHARACTERISTIC_HZ)[0] char_sens_d1 = char_sensor1.getDescriptors(forUUID=UUIDS.NOTIFICATION_DESCRIPTOR)[0] char_sensor2 = self.svc_1.getCharacteristics(UUIDS.CHARACTERISTIC_SENSOR)[0] # stop heart monitor continues char_ctrl.write(b'\x15\x01\x00', True) char_ctrl.write(b'\x15\x01\x00', True) # IMO: stop heart monitor notifications char_d.write(b'\x00\x00', True) # WTF char_sensor2.write(b'\x03') # IMO: stop notifications from sensors char_sens_d1.write(b'\x00\x00', True) self.heart_measure_callback = None self.heart_raw_callback = None self.accel_raw_callback = None def start_get_previews_data(self, start_timestamp): self._auth_previews_data_notif(True) self.waitForNotifications(0.1) print("Trigger activity communication") year = struct.pack("