├── .gitignore ├── LICENSE ├── README.md ├── bluetoothd.conf ├── openxshareble ├── __init__.py ├── app.py └── ble │ ├── __init__.py │ ├── attrs.py │ ├── readdata.py │ └── uart.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | /setuptools-1.0.tar.gz 2 | 3 | .*.sw? 4 | *-private* 5 | *-ignore* 6 | 7 | *.pyo 8 | *.pyi 9 | *.pyc 10 | *.py[ico] 11 | 12 | .DS_*/ 13 | node_modules/ 14 | 15 | *.egg-info/ 16 | doc/_build/ 17 | dist/ 18 | .idea/ 19 | *.iml 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ben West 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # openxshareble 3 | 4 | A pure python ble driver for Dexcom G4 + Share suitable for openaps. 5 | 6 | 7 | ## Install 8 | 9 | This depends on 10 | [my patches](https://github.com/adafruit/Adafruit_Python_BluefruitLE/pull/8) 11 | to Adafruit's ble library. 12 | 13 | Install dependencies from source: 14 | ```bash 15 | git clone -b wip/bewest/custom-gatt-profile https://github.com/bewest/Adafruit_Python_BluefruitLE.git 16 | cd Adafruit_Python_BluefruitLE 17 | sudo python setup.py develop 18 | ``` 19 | 20 | Install `openxshareble` 21 | ```bash 22 | git clone https://github.com/bewest/openxshareble.git 23 | cd openxshareble 24 | sudo python setup.py develop 25 | ``` 26 | 27 | Check installation, press `q` to quit. 28 | ```bash 29 | pydoc openxshareble 30 | ``` 31 | 32 | ## Use 33 | 34 | This module is intended to be used by `openaps`. 35 | Install openaps, configure an instance. 36 | ```bash 37 | sudo easy_install -ZU openaps 38 | openaps init share-demo 39 | cd share-demo 40 | ``` 41 | 42 | ```bash 43 | openaps device show 44 | ``` 45 | ### Vendor 46 | Openaps **vendors** are specific implementations. openaps can makes it 47 | possible to **use** things that **vendors** provide, using the **device** 48 | configuration. 49 | 50 | Devices can only be added to openaps when a suitable vendor provides uses. In 51 | this case, we want `openxshareble` module to provide uses for the G4+Share 52 | device, so we'll add it as a vendor. 53 | Make available as an openaps vendor. 54 | 55 | ```bash 56 | openaps vendor add openxshareble 57 | ``` 58 | 59 | ### Device 60 | Openaps **device**s the configuration that tells openaps which vendors you are using. 61 | To configure a device, openaps needs to know the vendor providing the uses, and 62 | we also store a friendly name to refer to it later. 63 | 64 | Let's create a **device** called `share`, with a configuration so that it uses 65 | the new `openxshareble` **vendor**: 66 | ```bash 67 | openaps device add share openxshareble 68 | openaps use share -h 69 | ``` 70 | 71 | Built in help menu: 72 | ``` 73 | 74 | bewest@bewest-MacBookPro:~/Documents/openaps$ openaps use share -h 75 | usage: openaps-use share [-h] USAGE ... 76 | 77 | optional arguments: 78 | -h, --help show this help message and exit 79 | 80 | ## Device share: 81 | vendor openxshareble 82 | 83 | openxshareble 84 | 85 | 86 | 87 | USAGE Usage Details 88 | calibrations read calibration entry records 89 | configure Configure DEXCOMRX serial number. 90 | glucose glucose 91 | iter_calibrations read last calibration records, default 10, eg: 92 | iter_calibrations_hours 93 | read last of calibration records, default 1, 94 | eg: 95 | iter_glucose read last glucose records, default 100, eg: 96 | iter_glucose_hours read last of glucose records, default 1, eg: 97 | iter_sensor 98 | iter_sensor_hours 99 | iter_sensor_insertions 100 | read last sensor insertion, removal, and 101 | expiration records, default 10, eg: 102 | iter_sensor_insertions_hours 103 | read last of sensor insertion, removal, and 104 | expiration records, default 1, eg: 105 | list_dexcom Scan the environment looking for Dexcom devices. 106 | scan scan for usb stick 107 | sensor Fetch Sensor (raw) records from Dexcom receiver. 108 | sensor_insertions read sensor insertion, removal, and expiration records 109 | of sensors 110 | bewest@bewest-MacBookPro:~/Documents/openaps$ 111 | 112 | ``` 113 | 114 | ### Use 115 | 116 | Now we can **use** the `share` device, the same way as any other device in 117 | `openaps`. In particular, notice the uses here are the exact same as the uses 118 | available for the classic cabled version of the `dexcom` vendor. The `uses` 119 | have automatically been converted, the only difference is the wireless ble 120 | function. Congratulations on cutting the cords! 121 | 122 | ### Configuring wireless usage 123 | Before we can fetch any data from our receiver, we need to pair to it. In 124 | particular, this process requires knowledge of the Dexcom serial number. 125 | Replace `SM512345678` with the serial printed on the back of your own Dexcom 126 | G4+Share receiver. 127 | 128 | This module only works on Dexcom G4+Share Receivers, and none others. 129 | Examples of viewing available receivers (debug output, mostly), and the 130 | `configure` use: 131 | ```bash 132 | openaps use share list_dexcom 133 | openaps use share -h 134 | openaps use share configure -h 135 | 136 | ``` 137 | ##### `openaps use share configure` 138 | Here's what the `configure` command looks like: 139 | ```bash 140 | bewest@bewest-MacBookPro:~/Documents/openaps$ openaps use share configure -h 141 | usage: openaps-use share configure [-h] [--serial SERIAL] 142 | 143 | Configure DEXCOMRX serial number. 144 | 145 | optional arguments: 146 | -h, --help show this help message and exit 147 | --serial SERIAL DexcomRX Serial Number 148 | 149 | Update the configuration so that your serial number is used to communicate 150 | with your Dexcom G4 with Share. 151 | Default: 152 | 153 | ``` 154 | 155 | Similar to `decocare` and the `medtronic` vendors, this module needs to know 156 | the serial number of the remote device, in order to talk to the correct device. 157 | There are two ways to use this command, the first is to use the environment 158 | variable `DEXCOMRX`, eg, replace `HELLO` with your own `SM12345678` serial 159 | number. 160 | 161 | ```bash 162 | # prints nothing first time 163 | openaps use share configure 164 | 165 | # sets share serial number to HELLO 166 | DEXCOMRX=HELLO openaps use share configure 167 | # watch it say HELLO back ;-) 168 | openaps use share configure 169 | DEXCOMRX=HELLO openaps use share configure 170 | openaps use share configure 171 | ``` 172 | 173 | Example clearing the serial number: 174 | ```bash 175 | openaps use share configure --serial '' 176 | # print/test/list the results with no arguments 177 | openaps use share configure 178 | openaps use share configure --serial None 179 | ``` 180 | 181 | Example configuring: 182 | ```bash 183 | # replace SM12345678 with your own serial number 184 | openaps use share configure --serial SM512345678 185 | # print/test/list the results with no arguments 186 | openaps use share configure 187 | openaps use share -h 188 | ``` 189 | 190 | ### Example reports 191 | Additional commands, these may be helpful in actually using the data in an 192 | openaps loop. 193 | ```bash 194 | openaps use share iter_glucose_hours 3 195 | openaps report add monitor/share-glucose.json JSON share iter_glucose_hours 3 196 | openaps report invoke monitor/share-glu 197 | openaps report invoke monitor/share-glucose.json 198 | openaps use tz -h 199 | openaps use tz glucose monitor/share-glucose.json 200 | openaps use tz rezone monitor/share-glucose.json 201 | openaps use tz rezone -h monitor/share-glucose.json 202 | openaps use tz rezone --date system_time --date display_time monitor/share-glucose.json 203 | openaps use tz rezone --date system_time --date display_time monitor/share-glucose.json | json -e "this.trend = this.trend_arrow; this.device = 'share'; this.sgv = this.glucose; this.type='sgv'" 204 | openaps use tz rezone --date system_time --date display_time monitor/share-glucose.json | json -e "this.trend = this.trend_arrow; this.device = 'share'; this.sgv = this.glucose; this.type='sgv'; this.dateString = this.display_time" 205 | openaps use tz rezone --date system_time --date display_time monitor/share-glucose.json | json -e "this.trend = this.trend_arrow; this.device = 'share'; this.sgv = this.glucose; this.type='sgv'; this.dateString = this.display_time" | head 206 | openaps use cgm -h 207 | openaps report add monitor/share-glucose-zoned.json JSON tz rezone --date system_time --date display_time monitor/share-glucose.json 208 | ``` 209 | 210 | # License 211 | 212 | See `LICENSE` 213 | The MIT License (MIT) 214 | 215 | Copyright (c) 2015 Ben West 216 | 217 | Permission is hereby granted, free of charge, to any person obtaining a copy 218 | of this software and associated documentation files (the "Software"), to deal 219 | in the Software without restriction, including without limitation the rights 220 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 221 | copies of the Software, and to permit persons to whom the Software is 222 | furnished to do so, subject to the following conditions: 223 | 224 | The above copyright notice and this permission notice shall be included in all 225 | copies or substantial portions of the Software. 226 | 227 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 228 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 229 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 230 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 231 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 232 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 233 | SOFTWARE. 234 | 235 | -------------------------------------------------------------------------------- /bluetoothd.conf: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /openxshareble/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | openxshareble - pure python driver to communicate with Dexcom G4+Share 4 | over ble. Allows openaps to use ble to talk to Dexcom G4+Share. 5 | 6 | Examples: 7 | ``` 8 | # add vendor to openaps 9 | openaps vendor add openxshareble 10 | # create device called share to use the new vendor 11 | openaps device add share openxshareble 12 | # use it, eg: 13 | openaps use share iter_glucose 2 14 | ``` 15 | 16 | """ 17 | 18 | import logging 19 | log = logging.getLogger(__name__) 20 | import os 21 | import app 22 | from openaps.uses.use import Use 23 | from openaps.uses.registry import Registry 24 | 25 | 26 | # we'll just replace all the original dexcom uses with new ones that swap 27 | # out how the data is transported. 28 | from openaps.vendors import dexcom 29 | 30 | def configure_use_app (app, parser): 31 | pass 32 | 33 | def configure_add_app (app, parser): 34 | pass 35 | 36 | def configure_app (app, parser): 37 | pass 38 | 39 | def configure_parser (parser): 40 | pass 41 | 42 | def main (args, app): 43 | """ 44 | """ 45 | pass 46 | 47 | def set_config (args, device): 48 | return "" 49 | def display_device (device): 50 | return "/%s" % device.get('serial') 51 | 52 | # get a new usage registry for this module 53 | use = Registry( ) 54 | get_uses = use.get_uses 55 | 56 | class BLEUsage (Use, app.App): 57 | """ Generic subclass for a single openaps use to use Dexcom's ble 58 | """ 59 | 60 | __init__ = Use.__init__ 61 | def before_main (self, args, app): 62 | """ 63 | Get info such as serial from the device configuration. 64 | """ 65 | 66 | logAddress = self.device.get('logAddress', '/dev/log') 67 | logLevel = getattr(logging, self.device.get('logLevel', 'INFO')) 68 | for handler in log.handlers[:]: 69 | log.removeHandler(handler) 70 | log.addHandler(logging.handlers.SysLogHandler(address=logAddress)) 71 | log.setLevel(logLevel) 72 | serial = self.device.get('serial', None) 73 | mac = self.device.get('mac', None) 74 | # run prolog/setup 75 | self.prolog(mac=mac) 76 | # setup dexcom in particular, with configured serial number 77 | self.setup_dexcom(serial=serial) 78 | def after_main (self, args, app): 79 | # run any after code 80 | self.epilog( ) 81 | def __call__ (self, args, app): 82 | """ 83 | Using BLE requires dropping into glib's threading mechanics. 84 | """ 85 | output = None 86 | # setup glib threading, configure a main loop in glib/dbus 87 | self.setup_ble( ) 88 | def run ( ): 89 | """ 90 | Run the main logic of the app, capturing the output. 91 | """ 92 | self.before_main(args, app) 93 | output = self.main(args, app) 94 | self.after_main(args, app) 95 | return output 96 | # run the configured glib loop. 97 | res = self.ble.run_mainloop_with(run, quit_with_loop=False) 98 | return res 99 | 100 | @use( ) 101 | class configure (Use): 102 | DEXCOMRX=os.environ.get('DEXCOMRX', '') 103 | __doc__ = """ 104 | Configure DEXCOMRX serial number. 105 | 106 | 107 | Update the configuration so that your serial number is used to communicate 108 | with your Dexcom G4 with Share. 109 | Default: {DEXCOMRX} 110 | """.format(DEXCOMRX=DEXCOMRX) 111 | 112 | __init__ = Use.__init__ 113 | def configure_app (self, app, parser): 114 | parser.add_argument('--serial', metavar='DEXCOMRX', default=os.environ.get('DEXCOMRX', ''), help="DexcomRX Serial Number") 115 | parser.add_argument('--mac', metavar='DEXCOMRX_MAC', 116 | default=os.environ.get('DEXCOMRX_MAC', ''), 117 | help="DexcomRX BLE MAC address") 118 | def get_params (self, args): 119 | conf = dict( ) 120 | if args.serial.startswith('SM') and len(args.serial) > 8: 121 | conf.update(serial=args.serial) 122 | else: 123 | conf.update(serial=None) 124 | if len(args.mac.split(':')) == 6: 125 | conf.update(mac=args.mac) 126 | else: 127 | conf.update(mac=None) 128 | return conf 129 | def main (self, args, app): 130 | results = dict(serial=self.device.get('serial', None), mac=self.device.get('mac', None)) 131 | params = self.get_params(args) 132 | dirty = False 133 | for field in [ 'serial', 'mac' ]: 134 | value = params.get(field) 135 | if getattr(self.device, 'extra', None): 136 | print "{field}={value}".format(field=field, value=results[field]) 137 | log.info("{field}={value}".format(field=field, value=results[field])) 138 | if value: 139 | if value != results.get(field, None): 140 | dirty = True 141 | print "saving %s" % value 142 | results[field] = value 143 | self.device.extra.add_option(field, value) 144 | if dirty: 145 | self.device.store(app.config) 146 | app.config.save( ) 147 | return results 148 | 149 | 150 | @use( ) 151 | class list_dexcom (BLEUsage): 152 | """ 153 | Scan the environment looking for Dexcom devices. 154 | 155 | Returns a list of scanned devices. 156 | """ 157 | 158 | disconnect_on_after = False 159 | def before_main (self, args, app): 160 | pass 161 | def main (self, args, app): 162 | if not self.device.get('serial', None): 163 | self.disconnect_on_after = False 164 | receivers = self.enumerate_dexcoms( ) 165 | results = [ ] 166 | for device in receivers: 167 | results.append(dict(name=str(device.name) 168 | , mac=str(device.id) 169 | , advertised=map(str, device.advertised))) 170 | return results 171 | 172 | class DexcomTask (BLEUsage): 173 | """ 174 | Generic utility to help swap original dexcom uses with new ones using 175 | the logic from this module to set up the usage. 176 | """ 177 | disconnect_on_after = True 178 | @classmethod 179 | def Emulate (Klass, usage): 180 | """ 181 | Given an original Use, an implementation, returns a transformed 182 | Use class so that our logic controls how data gets transported. 183 | """ 184 | class EmulatedUsage (usage, Klass): 185 | __doc__ = usage.__doc__ 186 | __name__ = usage.__name__ 187 | 188 | before_main = Klass.before_main 189 | after_main = Klass.after_main 190 | __call__ = Klass.__call__ 191 | 192 | EmulatedUsage.__name__ = usage.__name__ 193 | return EmulatedUsage 194 | 195 | def transform (existing, Emulator, Original): 196 | """ 197 | helper method to extract substitutes from original list of uses. 198 | """ 199 | results = dict( ) 200 | for name, usage in existing.use.__USES__.items( ): 201 | if issubclass(usage, Original): 202 | adapted = Emulator.Emulate(usage) 203 | adapted.__name__ = name 204 | if name not in results: 205 | results[name] = adapted 206 | return results 207 | 208 | 209 | adapted = transform(dexcom, DexcomTask, dexcom.scan) 210 | use.__USES__.update(**adapted) 211 | 212 | -------------------------------------------------------------------------------- /openxshareble/app.py: -------------------------------------------------------------------------------- 1 | 2 | import Adafruit_BluefruitLE 3 | from Adafruit_BluefruitLE.services import UART as OriginalUART 4 | # from ble import uart 5 | from ble.uart import UART 6 | from ble.readdata import Device 7 | import time 8 | import atexit 9 | import logging 10 | log = logging.getLogger(__name__) 11 | 12 | class App (object): 13 | """ A high level application object. 14 | 15 | Any application needing to talk to Dexcom G4 + Share will need 16 | to perform operations to setup the ble data transport. This class 17 | mixes the UART, ble code, and provides helpful prolog and epilog 18 | routines that run before and after main, respectively. 19 | """ 20 | def __init__ (self, **kwds): 21 | self.disconnect_on_after = kwds.get('disconnect_on_after', False) 22 | pass 23 | def setup_ble (self): 24 | self.remote = None 25 | self.ble = Adafruit_BluefruitLE.get_provider() 26 | # Initialize the BLE system. MUST be called before other BLE calls! 27 | self.ble.initialize() 28 | # Get the first available BLE network adapter and make sure it's powered on. 29 | self.adapter = self.ble.get_default_adapter() 30 | self.adapter.power_on() 31 | log.info('Using adapter: {0}'.format(self.adapter.name)) 32 | self.dexcom = None 33 | pass 34 | def setup_dexcom (self, serial=None, mac=None): 35 | # Once connected do everything else in a try/finally to make sure the device 36 | # is disconnected when done. 37 | try: 38 | # Wait for service discovery to complete for the UART service. Will 39 | # time out after 60 seconds (specify timeout_sec parameter to override). 40 | # print device._device.GattServices 41 | log.info('Discovering services...') 42 | UART.discover(self.remote) 43 | 44 | # Once service discovery is complete create an instance of the service 45 | # and start interacting with it. 46 | self.uart = UART(self.remote, SERIAL=serial) 47 | 48 | 49 | self.dexcom = Device(self.uart) 50 | # log.info("DEXCOM", self.dexcom) 51 | if not self.dexcom: 52 | self.dexcom = Device(self.uart) 53 | except: 54 | # Make sure device is disconnected on exit. 55 | if self.disconnect_on_after: 56 | self.remote.disconnect() 57 | def prolog (self, clear_cached_data=True, disconnect_devices=True, scan_devices=True, connect=True, mac=None): 58 | """ 59 | Things to do before running the main part of the application. 60 | """ 61 | # Clear any cached data because both bluez and CoreBluetooth have issues with 62 | # caching data and it going stale. 63 | if clear_cached_data: 64 | self.ble.clear_cached_data() 65 | 66 | 67 | if disconnect_devices: 68 | # Disconnect any currently connected UART devices. Good for cleaning up and 69 | # starting from a fresh state. 70 | log.info('Disconnecting any connected UART devices...') 71 | UART.disconnect_devices() 72 | 73 | if scan_devices: 74 | # Scan for UART devices. 75 | log.info('Searching for UART device...') 76 | try: 77 | if mac: 78 | self.remote = self.select_mac(mac=mac) 79 | else: 80 | self.adapter.start_scan() 81 | # Search for the first UART device found (will time out after 60 seconds 82 | # but you can specify an optional timeout_sec parameter to change it). 83 | self.remote = UART.find_device() 84 | if self.remote is None: 85 | raise RuntimeError('Failed to find UART device!') 86 | finally: 87 | # Make sure scanning is stopped before exiting. 88 | if self.adapter.is_scanning: 89 | self.adapter.stop_scan() 90 | 91 | if connect and not self.remote.is_connected: 92 | log.info('Connecting to device...') 93 | self.remote.connect() # Will time out after 60 seconds, specify timeout_sec parameter 94 | # to change the timeout. 95 | log.info(self.remote.name) 96 | # device._device.Pair( ) 97 | # log.info(self.ble._print_tree( )) 98 | for service in self.remote.list_services( ): 99 | log.info("services: %s %s", service, service.uuid) 100 | log.info("ADVERTISED") 101 | log.info(self.remote.advertised) 102 | 103 | pass 104 | def select_mac (self, mac=None, **kwds): 105 | for device in self.enumerate_dexcoms(**kwds): 106 | if str(device.id) == mac: 107 | return device 108 | def enumerate_dexcoms (self, timeout_secs=10): 109 | self.adapter.start_scan() 110 | # Use atexit.register to call the adapter stop_scan function before quiting. 111 | # This is good practice for calling cleanup code in this main function as 112 | # a try/finally block might not be called since this is a background thread. 113 | def maybe_stop ( ): 114 | if self.adapter.is_scanning: 115 | self.adapter.stop_scan( ) 116 | # atexit.register(maybe_stop) 117 | log.info('Searching for UART devices...') 118 | 119 | # print('Press Ctrl-C to quit (will take ~30 seconds on OSX).') 120 | # Enter a loop and print out whenever a new UART device is found. 121 | start = time.time( ) 122 | now = time.time( ) 123 | known_uarts = set() 124 | while (now - start) < timeout_secs: 125 | # Call UART.find_devices to get a list of any UART devices that 126 | # have been found. This call will quickly return results and does 127 | # not wait for devices to appear. 128 | found = set(UART.find_devices()) 129 | # Check for new devices that haven't been seen yet and print out 130 | # their name and ID (MAC address on Linux, GUID on OSX). 131 | new = found - known_uarts 132 | for device in new: 133 | log.info('Found UART: {0} [{1}]'.format(device.name, device.id)) 134 | known_uarts.update(new) 135 | # Sleep for a second and see if new devices have appeared. 136 | time.sleep(1.0) 137 | now = time.time( ) 138 | self.adapter.stop_scan( ) 139 | return known_uarts 140 | 141 | def epilog (self): 142 | """ 143 | Things to do after running the main part of the application. 144 | """ 145 | # Make sure device is disconnected on exit. 146 | if self.disconnect_on_after and self.remote.is_connected: 147 | self.remote.disconnect() 148 | # self.ble._gobject_mainloop.quit( ) 149 | pass 150 | def set_handler (self, handler): 151 | self.handler = handler 152 | def run (self): 153 | self.ble.run_mainloop_with(self.main) 154 | pass 155 | def main (self): 156 | """ 157 | Subclasses should replace this method. 158 | """ 159 | -------------------------------------------------------------------------------- /openxshareble/ble/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openaps/openxshareble/e52212dcd04d111dd5438cf1a31fe59e8e6aa157/openxshareble/ble/__init__.py -------------------------------------------------------------------------------- /openxshareble/ble/attrs.py: -------------------------------------------------------------------------------- 1 | 2 | import uuid 3 | 4 | class Attrs: 5 | CradleService = uuid.UUID("F0ABA0B1-EBFA-F96F-28DA-076C35A521DB"); 6 | 7 | # Share Characteristic Strings 8 | AuthenticationCode = uuid.UUID("F0ABACAC-EBFA-F96F-28DA-076C35A521DB"); 9 | ShareMessageReceiver = uuid.UUID("F0ABB20A-EBFA-F96F-28DA-076C35A521DB"); # Max 20 Bytes - Writable 10 | ShareMessageResponse = uuid.UUID("F0ABB20B-EBFA-F96F-28DA-076C35A521DB"); # Max 20 Bytes 11 | Command = uuid.UUID("F0ABB0CC-EBFA-F96F-28DA-076C35A521DB"); 12 | Response = uuid.UUID("F0ABB0CD-EBFA-F96F-28DA-076C35A521DB"); # Writable? 13 | HeartBeat = uuid.UUID("F0AB2B18-EBFA-F96F-28DA-076C35A521DB"); 14 | 15 | # Possible new uuids???? 60bfxxxx-60b0-4d4f-0000-000160c48d70 16 | CradleService2 = uuid.UUID("F0ACA0B1-EBFA-F96F-28DA-076C35A521DB"); 17 | AuthenticationCode2 = uuid.UUID("F0ACACAC-EBFA-F96F-28DA-076C35A521DB"); # read, write 18 | ShareMessageReceiver2 = uuid.UUID("F0ACB20A-EBFA-F96F-28DA-076C35A521DB"); # read, write 19 | ShareMessageResponse2 = uuid.UUID("F0ACB20B-EBFA-F96F-28DA-076C35A521DB"); # indicate, read 20 | Command2 = uuid.UUID("F0ACB0CC-EBFA-F96F-28DA-076C35A521DB"); # read, write 21 | Response2 = uuid.UUID("F0ACB0CD-EBFA-F96F-28DA-076C35A521DB"); # indicate, read, write 22 | HeartBeat2 = uuid.UUID("F0AC2B18-EBFA-F96F-28DA-076C35A521DB"); # notify, read 23 | 24 | # Device Info 25 | DeviceService = uuid.UUID("00001804-0000-1000-8000-00805f9b34fb"); 26 | PowerLevel = uuid.UUID("00002a07-0000-1000-8000-00805f9b34fb"); 27 | 28 | VENDOR_UUID = uuid.UUID("F0ACA0B1-EBFA-F96F-28DA-076C35A521DB") 29 | 30 | -------------------------------------------------------------------------------- /openxshareble/ble/readdata.py: -------------------------------------------------------------------------------- 1 | 2 | from dexcom_reader import readdata 3 | import xml.etree.ElementTree as etree 4 | 5 | 6 | class Device (readdata.Dexcom): 7 | """ 8 | Local shim of original readdata.Dexcom from dexcom_reader. 9 | This simply replaces some of the logic to set up the data transport 10 | mechanisms. 11 | """ 12 | def __init__ (self, uart): 13 | # assign serial port/uart device 14 | self.uart = uart 15 | 16 | def Connect (self): 17 | return True 18 | 19 | @property 20 | def port (self): 21 | return self.uart 22 | 23 | def Disconnect (self): 24 | return True 25 | 26 | def flush (self): 27 | return True 28 | 29 | def write (self, data, *args, **kwargs): 30 | # the usb protocol is exactly the same, the ble protocol adds a 31 | # prefix of 0x01, 0x01 to every message written. 32 | prefix = str(bytearray([ 0x01, 0x01 ])) 33 | return self.port.write(prefix + data, *args, **kwargs) 34 | 35 | -------------------------------------------------------------------------------- /openxshareble/ble/uart.py: -------------------------------------------------------------------------------- 1 | 2 | import Adafruit_BluefruitLE 3 | from Adafruit_BluefruitLE.services import UART as OriginalUART 4 | import Queue 5 | import uuid 6 | import time 7 | from attrs import Attrs 8 | import logging 9 | log = logging.getLogger(__name__) 10 | 11 | class ShareUART (OriginalUART): 12 | ADVERTISED = [Attrs.CradleService] 13 | # SERVICES = [Attrs.DeviceService] 14 | SERVICES = [Attrs.CradleService] 15 | CHARACTERISTICS = [Attrs.AuthenticationCode, Attrs.Command, Attrs.Response, Attrs.ShareMessageReceiver, Attrs.ShareMessageResponse, Attrs.HeartBeat, Attrs.DeviceService, Attrs.PowerLevel] 16 | 17 | UART_SERVICE_UUID = Attrs.CradleService 18 | TX_CHAR_UUID = Attrs.Command 19 | RX_CHAR_UUID = Attrs.Response 20 | pass 21 | class Share2UART (OriginalUART): 22 | # ADVERTISED = [Attrs.CradleService2] 23 | # ADVERTISED = [Attrs.VENDOR_UUID] 24 | ADVERTISED = [Attrs.VENDOR_UUID] 25 | # SERVICES = [Attrs.DeviceService] 26 | # SERVICES = [Attrs.CradleService2, Attrs.VENDOR_UUID] 27 | SERVICES = [Attrs.VENDOR_UUID, Attrs.DeviceService] 28 | # CHARACTERISTICS = [Attrs.AuthenticationCode2, Attrs.Command2, Attrs.Response2, Attrs.ShareMessageReceiver2, Attrs.ShareMessageResponse2, Attrs.HeartBeat2, Attrs.DeviceService, Attrs.PowerLevel] 29 | CHARACTERISTICS = [ ] 30 | 31 | HEARTBEAT_UUID = Attrs.HeartBeat2 32 | # UART_SERVICE_UUID = Attrs.CradleService2 33 | UART_SERVICE_UUID = Attrs.VENDOR_UUID 34 | TX_CHAR_UUID = Attrs.ShareMessageReceiver2 35 | RX_CHAR_UUID = Attrs.ShareMessageResponse2 36 | SendDataUUID = Attrs.ShareMessageReceiver2 37 | RcveDataUUID = Attrs.ShareMessageResponse2 38 | CommandUUID = Attrs.Command2 39 | ResponseUUID = Attrs.Response2 40 | AUTH_UUID = Attrs.AuthenticationCode2 41 | def __init__(self, device, **kwds): 42 | """Initialize UART from provided bluez device.""" 43 | # Find the UART service and characteristics associated with the device. 44 | log = logging.getLogger(__name__) 45 | self.log = log.getChild('uart') 46 | self._uart = device.find_service(self.UART_SERVICE_UUID) 47 | log.info("UART %s", self._uart) 48 | self._queue = Queue.Queue() 49 | r = device.is_paired 50 | self.serial = kwds.pop('SERIAL', None) 51 | log.info("paired? %s", r) 52 | if not r: 53 | log.info("pairing...") 54 | # help(device._device) 55 | # help(device._device.Pair) 56 | device.pair( ) 57 | # device._device.Pair( ) 58 | log.info("paired") 59 | log.info(device.advertised) 60 | log.info("finding service") 61 | self._uart = device.find_service(self.UART_SERVICE_UUID) 62 | log.info("SERVICE %s", self._uart) 63 | self.pair_auth_code(self.serial) 64 | """ 65 | for svc in device.list_services( ): 66 | log.info("{0}, {1}, {2}, {3}", svc.uuid, svc.uuid == self.UART_SERVICE_UUID, svc, svc._service) 67 | log.info("CHARACTERISTICS") 68 | chrsts = svc.list_characteristics( ) 69 | for chtr in chrsts: 70 | log.info("{0} {1} {2}", chtr.uuid, chtr, chtr._characteristic) 71 | """ 72 | # print device.list_services( ) 73 | self.setup_dexcom( ) 74 | def set_serial (self, SERIAL): 75 | self.serial = SERIAL 76 | def pair_auth_code (self, serial): 77 | self.log.info("sending auth code %s", serial) 78 | self._auth = self._uart.find_characteristic(self.AUTH_UUID) 79 | self.log.info(self._auth) 80 | # self._auth. 81 | msg = bytearray(serial + "000000") 82 | self._auth.write_value(str(msg)) 83 | def setup_dexcom_heartbeat (self): 84 | self._heartbeat = self._uart.find_characteristic(self.HEARTBEAT_UUID) 85 | def do_heartbeat (self): 86 | if not self._heartbeat.notifying: 87 | self._heartbeat.start_notify(self._heartbeat_tick) 88 | def setup_dexcom (self): 89 | self.remainder = bytearray( ) 90 | self._tx = self._uart.find_characteristic(self.TX_CHAR_UUID) 91 | self._rx = self._uart.find_characteristic(self.RX_CHAR_UUID) 92 | # Use a queue to pass data received from the RX property change back to 93 | # the main thread in a thread-safe way. 94 | self.setup_dexcom_heartbeat( ) 95 | self.do_heartbeat( ) 96 | """ 97 | self._heartbeat = self._uart.find_characteristic(self.HEARTBEAT_UUID) 98 | if not self._heartbeat.notifying: 99 | self._heartbeat.start_notify(self._heartbeat_tick) 100 | """ 101 | self._char_rcv_data = self._uart.find_characteristic(self.RcveDataUUID) 102 | if self._rx.notifying: 103 | self._rx.stop_notify( ) 104 | if not self._rx.notifying: 105 | self._rx.start_notify(self._rx_received) 106 | def _heartbeat_tick (self, data): 107 | self.log.info("_heartbeat_tick %s", str(data).encode('hex')) 108 | def _on_rcv (self, data): 109 | self.log.info("_on_rcv %s", str(data).encode('hex')) 110 | 111 | def read (self, size=1, timeout_sec=None): 112 | spool = bytearray( ) 113 | spool.extend(self.remainder) 114 | self.remainder = bytearray( ) 115 | while len(spool) < size: 116 | spool.extend(self.pop(timeout_sec=timeout_sec)) 117 | time.sleep(.100) 118 | self.remainder.extend(spool[size:]) 119 | return str(spool[:size]) 120 | def pop (self, timeout_sec=None): 121 | return super(Share2UART, self).read(timeout_sec=timeout_sec) 122 | 123 | class BothShare (ShareUART): 124 | ADVERTISED = ShareUART.ADVERTISED + Share2UART.ADVERTISED 125 | # SERVICES = [Attrs.DeviceService] 126 | SERVICES = ShareUART.SERVICES + Share2UART.SERVICES 127 | CHARACTERISTICS = ShareUART.SERVICES + Share2UART.SERVICES 128 | 129 | 130 | UART_SERVICE_UUID = Attrs.CradleService2 131 | TX_CHAR_UUID = Attrs.Command2 132 | RX_CHAR_UUID = Attrs.Response2 133 | pass 134 | 135 | class UART (Share2UART): 136 | pass 137 | 138 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # from ez_setup import use_setuptools 2 | # use_setuptools() 3 | from setuptools import setup, find_packages 4 | 5 | setup(name = 'openxshareble', 6 | version = '0.0.0', 7 | author = 'Ben West', 8 | author_email = 'bewest@gmail.com', 9 | description = 'Python library for interacting with Dexcom G4 with Share over Bluetooth low energy.', 10 | license = 'MIT', 11 | url = 'https://github.com/bewest/openxshareble/', 12 | packages = find_packages()) 13 | --------------------------------------------------------------------------------