__init__.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2020 Dan Halbert for Adafruit Industries LLC
  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
 13  # all 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
 21  # THE SOFTWARE.
 22  """
 23  `adafruit_ble_berrymed_pulse_oximeter`
 24  ================================================================================
 25  
 26  BLE Support for Berrymed Pulse Oximeters
 27  
 28  
 29  * Author(s): Adafruit Industries
 30  
 31  Implementation Notes
 32  --------------------
 33  
 34  **Hardware:**
 35  
 36  * BM1000C, made by Shanghai Berry Electronic Tech Co.,Ltd
 37  
 38    Protocol defined here: https://github.com/zh2x/BCI_Protocol
 39    Thanks as well to:
 40    * https://github.com/ehborisov/BerryMed-Pulse-Oximeter-tool
 41    * https://github.com/ScheindorfHyenetics/berrymedBluetoothOxymeter
 42  
 43  **Software and Dependencies:**
 44  
 45  * Adafruit CircuitPython firmware for the supported boards:
 46    https://github.com/adafruit/circuitpython/releases
 47  """
 48  
 49  # imports
 50  
 51  from collections import namedtuple
 52  import struct
 53  
 54  from .adafruit_ble_transparent_uart import TransparentUARTService
 55  
 56  __version__ = "0.0.0-auto.0"
 57  __repo__ = (
 58      "https://github.com/adafruit/Adafruit_CircuitPython_BLE_BerryMed_Pulse_Oximeter.git"
 59  )
 60  
 61  
 62  PulseOximeterValues = namedtuple(
 63      "PulseOximeterValues", ("valid", "spo2", "pulse_rate", "pleth", "finger_present"),
 64  )
 65  """Namedtuple for measurement values.
 66  
 67  * `PulseOximeterValues.valid`
 68  
 69          ``True`` if sensor readings are not valid right now
 70  
 71  * `PulseOximeterValues.finger_present`
 72  
 73          ``True`` if finger present.
 74  
 75  * `PulseOximeterValues.spo2`
 76  
 77          SpO2 value (int): 0-100%: blood oxygen saturation level.
 78  
 79  * `PulseOximeterValues.pulse_rate`
 80  
 81          Pulse rate, in beats per minute (int).
 82  
 83  * `PulseOximeterValues.pleth`
 84  
 85          Plethysmograph value, 0-100 (int).
 86  
 87  For example::
 88  
 89      bpm = svc.values.pulse_rate
 90  """
 91  
 92  
 93  class BerryMedPulseOximeterService(TransparentUARTService):
 94      """Service for reading from a BerryMed BM1000C or BM100E Pulse oximeter."""
 95  
 96      @property
 97      def values(self):
 98          """All the pulse oximeter values, returned as a PulseOximeterValues
 99          namedtuple.
100  
101          Return ``None`` if no data available.
102          """
103          first_byte = self.read(1)
104          # Wait for a byte with the high bit set, which indicates the beginning
105          # a data packet.
106          if not first_byte:
107              return None
108          header = first_byte[0]
109          if header & 0x80 == 0:
110              # Not synchronized.
111              return None
112  
113          data = self.read(4)
114          if not data or len(data) != 4:
115              return None
116  
117          # Ignoring these values, which aren't that interesting.
118          #
119          # pulse_beep = bool(header & 0x40)
120          # probe_unplugged = bool(header & 0x20)
121          #
122          # Bar graph height: not sure what this is measuring
123          # bar_graph = data[1] & 0x0F
124          #
125          # This is the device sensor signal, not the BLE signal.
126          # has_sensor_signal = bool(header & 0x010)
127          # sensor_signal_strength = header & 0x0F
128          #
129          # Acquiring pulse value
130          # pulse_search = bool(data[1] & 0x20)
131  
132          # Plethysmograph value, 0-100.
133          pleth = data[0]
134  
135          # Finger detected
136          finger_present = not bool(data[1] & 0x10)
137  
138          # Pulse rate: 255 if invalid.
139          # The high bit of the pulse rate is sent in a different byte.
140          pulse_rate = data[2] | (data[1] & 0x40) << 1
141  
142          # SpO2: 0-100, or 127 if invalid
143          spo2 = data[3]
144  
145          valid = spo2 != 127
146  
147          return PulseOximeterValues(
148              valid=valid,
149              finger_present=finger_present,
150              spo2=spo2,
151              pulse_rate=pulse_rate,
152              pleth=pleth,
153          )