text: from machine import I2C, Pin
  import time
  
  __version__ = '0.2.1'
  __author__ = 'Roberto Sánchez'
  __license__ = "Apache License 2.0. https://www.apache.org/licenses/LICENSE-2.0"
  
  # I2C address B 0x45 ADDR (pin 2) connected to VDD
  DEFAULT_I2C_ADDRESS = 0x45
  
  
  class SHT30():
      """
      SHT30 sensor driver in pure python based on I2C bus
  
      References:
      * https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf
      * https://www.wemos.cc/sites/default/files/2016-11/SHT30-DIS_datasheet.pdf
      * https://github.com/wemos/WEMOS_SHT3x_Arduino_Library
      * https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/11_Sample_Codes_Software/Humidity_Sensors/Sensirion_Humidity_Sensors_SHT3x_Sample_Code_V2.pdf
      """
      POLYNOMIAL = 0x131  # P(x) = x^8 + x^5 + x^4 + 1 = 100110001
  
      ALERT_PENDING_MASK = 0x8000  # 15
      HEATER_MASK = 0x2000  # 13
      RH_ALERT_MASK = 0x0800  # 11
      T_ALERT_MASK = 0x0400  # 10
      RESET_MASK = 0x0010  # 4
      CMD_STATUS_MASK = 0x0002  # 1
      WRITE_STATUS_MASK = 0x0001  # 0
  
      # MSB = 0x2C LSB = 0x06 Repeatability = High, Clock stretching = enabled
      MEASURE_CMD = b'\x2C\x10'
      STATUS_CMD = b'\xF3\x2D'
      RESET_CMD = b'\x30\xA2'
      CLEAR_STATUS_CMD = b'\x30\x41'
      ENABLE_HEATER_CMD = b'\x30\x6D'
      DISABLE_HEATER_CMD = b'\x30\x66'
  
      def __init__(self, scl_pin=5, sda_pin=4, delta_temp=0, delta_hum=0, i2c_address=DEFAULT_I2C_ADDRESS):
          self.i2c = I2C(scl=Pin(scl_pin), sda=Pin(sda_pin))
          self.i2c_addr = i2c_address
          self.set_delta(delta_temp, delta_hum)
          time.sleep_ms(50)
  
      def init(self, scl_pin=5, sda_pin=4):
          """
          Init the I2C bus using the new pin values
          """
          self.i2c.init(scl=Pin(scl_pin), sda=Pin(sda_pin))
  
      def is_present(self):
          """
          Return true if the sensor is correctly conneced, False otherwise
          """
          return self.i2c_addr in self.i2c.scan()
  
      def set_delta(self, delta_temp=0.0, delta_hum=0.0):
          """
          Apply a delta value on the future measurements of temperature and/or humidity
          The units are Celsius for temperature and percent for humidity (can be negative values)
          """
          self.delta_temp = delta_temp
          self.delta_hum = delta_hum
  
      def _check_crc(self, data):
          # calculates 8-Bit checksum with given polynomial
          crc = 0xFF
  
          for b in data[:-1]:
              crc ^= b;
              for _ in range(8, 0, -1):
                  if crc & 0x80:
                      crc = (crc << 1) ^ SHT30.POLYNOMIAL;
                  else:
                      crc <<= 1
          crc_to_check = data[-1]
          return crc_to_check == crc
  
      def send_cmd(self, cmd_request, response_size=6, read_delay_ms=100):
          """
          Send a command to the sensor and read (optionally) the response
          The responsed data is validated by CRC
          """
          try:
              self.i2c.start();
              self.i2c.writeto(self.i2c_addr, cmd_request);
              if not response_size:
                  self.i2c.stop();
                  return
              time.sleep_ms(read_delay_ms)
              data = self.i2c.readfrom(self.i2c_addr, response_size)
              self.i2c.stop();
              for i in range(response_size // 3):
                  if not self._check_crc(data[i * 3:(i + 1) * 3]):  # pos 2 and 5 are CRC
                      raise SHT30Error(SHT30Error.CRC_ERROR)
              if data == bytearray(response_size):
                  raise SHT30Error(SHT30Error.DATA_ERROR)
              return data
          except OSError as ex:
              if 'I2C' in ex.args[0]:
                  raise SHT30Error(SHT30Error.BUS_ERROR)
              raise ex
  
      def clear_status(self):
          """
          Clear the status register
          """
          return self.send_cmd(SHT30.CLEAR_STATUS_CMD, None);
  
      def reset(self):
          """
          Send a soft-reset to the sensor
          """
          return self.send_cmd(SHT30.RESET_CMD, None);
  
      def status(self, raw=False):
          """
          Get the sensor status register.
          It returns a int value or the bytearray(3) if raw==True
          """
          data = self.send_cmd(SHT30.STATUS_CMD, 3, read_delay_ms=20);
  
          if raw:
              return data
  
          status_register = data[0] << 8 | data[1]
          return status_register
  
      def measure(self, raw=False):
          """
          If raw==True returns a bytearrya(6) with sensor direct measurement otherwise
          It gets the temperature (T) and humidity (RH) measurement and return them.
  
          The units are Celsius and percent
          """
          data = self.send_cmd(SHT30.MEASURE_CMD, 6);
  
          if raw:
              return data
  
          t_celsius = (((data[0] << 8 | data[1]) * 175) / 0xFFFF) - 45 + self.delta_temp;
          rh = (((data[3] << 8 | data[4]) * 100.0) / 0xFFFF) + self.delta_hum;
          return t_celsius, rh
  
      def measure_int(self, raw=False):
          """
          Get the temperature (T) and humidity (RH) measurement using integers.
          If raw==True returns a bytearrya(6) with sensor direct measurement otherwise
          It returns a tuple with 4 values: T integer, T decimal, H integer, H decimal
          For instance to return T=24.0512 and RH= 34.662 This method will return
          (24, 5, 34, 66) Only 2 decimal digits are returned, .05 becomes 5
          Delta values are not applied in this method
          The units are Celsius and percent.
          """
          data = self.send_cmd(SHT30.MEASURE_CMD, 6);
          if raw:
              return data
          aux = (data[0] << 8 | data[1]) * 175
          t_int = (aux // 0xffff) - 45;
          t_dec = (aux % 0xffff * 100) // 0xffff
          aux = (data[3] << 8 | data[4]) * 100
          h_int = aux // 0xffff
          h_dec = (aux % 0xffff * 100) // 0xffff
          return t_int, t_dec, h_int, h_dec
  
  
  class SHT30Error(Exception):
      """
      Custom exception for errors on sensor management
      """
      BUS_ERROR = 0x01
      DATA_ERROR = 0x02
      CRC_ERROR = 0x03
  
      def __init__(self, error_code=None):
          self.error_code = error_code
          super().__init__(self.get_message())
  
      def get_message(self):
          if self.error_code == SHT30Error.BUS_ERROR:
              return "Bus error"
          elif self.error_code == SHT30Error.DATA_ERROR:
              return "Data error"
          elif self.error_code == SHT30Error.CRC_ERROR:
              return "CRC error"
          else:
              return "Unknown error"
  
  
  def test():
      sensor = SHT30()
      sensor.set_delta(0.0, 0.0)
  
      for i in range(10):
          temp, hum = sensor.measure()
          print('Temperature is {0}\tHumidity is {1}'. format(temp, hum))
  
          time.sleep(2)
  
  
  if __name__ == '__main__':
      test()
  
  