Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add measure_dp and general tidying #1

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 29 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
#SHT30 Sensor driver in micropython
# SHT30 Sensor driver in micropython

Micropython driver for [SHT30 Shield](https://www.wemos.cc/product/sht30-shield.html) for [Wemos D1 Mini (and PRO)](https://www.wemos.cc/product/d1-mini-pro.html).

The driver has been tested on Wemos D1 mini PRO, but It should work on whatever other micropython board, if anyone find problems in other boards, please open an issue and We'll see.
The driver has been tested on Wemos D1 mini PRO, but it should work on whatever other micropython board, if anyone find problems in other boards, please open an issue and we'll see.

##Motivation
## Motivation
The SHT30 shield for ESP8266 board Wemos D1 Mini has an Arduino driver but not a micropython one.

##References:
## References:

* [Sensor Datasheet](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf)
* [Arduino driver](https://github.com/wemos/WEMOS_SHT3x_Arduino_Library)
* [SHT30 C Code Examples](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/11_Sample_Codes_Software/Humidity_Sensors/Sensirion_Humidity_Sensors_SHT3x_Sample_Code_V2.pdf) from sensor manufacturer

##Examples of use:
## Examples of use:

###How to get the temperature and relative humidity:
### How to get the temperature and relative humidity:

The `measure()` method returns a tuple with the temperature in celsius grades and the relative humidity in percentage.
The `measure()` method returns a tuple with the temperature in degrees Celsius and the relative humidity in percentage.
If the measurement cannot be performed then an exception is raised (`SHT30Error`)

```python
Expand All @@ -27,15 +27,26 @@ sensor = SHT30()

temperature, humidity = sensor.measure()

print('Temperature:', temperature, 'ºC, RH:', humidity, '%')
print('Temperature:', temperature, '°C, RH:', humidity, '%')
```

There is another method, `measure_int()`, that returns 4 integer values, **no floating point operation is done**, designed
for environments that doesn't support floating point operations, the four values are:
The `measure_dp()` method returns a similar tuple but with the dew point temperature (i.e. the temperature at which dew will form if the air is cooled adiabatically) instead of relative humidity. Both values are degrees Celsius.

```python
from sht30 import SHT30

sensor = SHT30()

temperature, dew_point = sensor.measure_dp()
print('Temperature:', temperature, '°C, dew point:', dew_point, '°C')
```

There is another method, `measure_int()`, that returns 4 integer values, **no floating point operation is done**, designed
for environments that doesn't support floating point operations, the four values are:

Temperature (integer part), Temperature (decimal part), RH (integer part), RH (decimal part)

For intance, if the `measure()` method returns `(21.5623, 32.0712)` the `measure_int()` method would return: `(24, 56, 32, 7)` The decimal
For instance, if the `measure()` method returns `(21.5623, 32.0712)` the `measure_int()` method would return: `(24, 56, 32, 7)` The decimal
part is limited to 2 decimal digits.

```python
Expand All @@ -44,15 +55,15 @@ t_int, t_dec, h_int, h_dec = sensor.measure_int()
print('Temperature: %i.%02i °C, RH: %i.%02i %%' % (t_int, t_dec, h_int, h_dec))
```

Both methods allow a `raw` param that when It's `True` returns the sensor measurement as-is, It's a `bytearray(6)` with the format defined in the sensor datasheet document.
Both methods allow a `raw` param that when it's `True` returns the sensor measurement as-is, it's a `bytearray(6)` with the format defined in the sensor datasheet document.

```python
raw_measure = sensor.measure(raw=True)

print('Sensor measurement', raw_measure)
```

###Check if shield is connected
### Check if shield is connected

```python
from sht30 import SHT30
Expand All @@ -63,7 +74,7 @@ print('Is connected:', sensor.is_present())

```

###Read sensor status
### Read sensor status

Check the [Sensor Datasheet](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf) for further info about sensor status register
```python
Expand All @@ -74,13 +85,13 @@ sensor = SHT30()
print('Status register:', bin(sensor.status()))
print('Single bit check, HEATER_MASK:', bool(sensor.status() & SHT30.HEATER_MASK))

#The status register can be cleared with
# The status register can be cleared with
sensor.clear_status()

```


###Reset the sensor
### Reset the sensor

The driver allows a soft reset of the sensor

Expand All @@ -94,9 +105,9 @@ sensor.reset()



###Error management
### Error management

When the driver cannot access to the measurement an exception `SHT30Error` is raised
When the driver cannot access the sensor an `SHT30Error` exception is raised

```python
from sht30 import SHT30
Expand All @@ -108,5 +119,4 @@ try:
except SHT30Error as ex:
print('Error:', ex)


```
101 changes: 58 additions & 43 deletions sht30.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,29 @@

# I2C address B 0x45 ADDR (pin 2) connected to VDD
DEFAULT_I2C_ADDRESS = 0x45
# I2C address A 0x44 ADDR (pin 2) connected to VSS
ALTERNATE_I2C_ADDRESS = 0x44


class SHT30():
"""
SHT30 sensor driver in pure python based on I2C bus
References:

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
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'
Expand All @@ -36,60 +39,60 @@ class SHT30():
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):
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 true if the sensor is correctly connected, False otherwise
"""
return self.i2c_addr in self.i2c.scan()
def set_delta(self, delta_temp = 0, delta_hum = 0):

def set_delta(self, delta_temp=0, delta_hum=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;
crc ^= b
for _ in range(8, 0, -1):
if crc & 0x80:
crc = (crc << 1) ^ SHT30.POLYNOMIAL;
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
The response data is validated by CRC
"""
try:
self.i2c.start();
self.i2c.writeto(self.i2c_addr, cmd_request);
self.i2c.start()
self.i2c.writeto(self.i2c_addr, cmd_request)
if not response_size:
self.i2c.stop();
self.i2c.stop()
return
time.sleep_ms(read_delay_ms)
data = self.i2c.readfrom(self.i2c_addr, response_size)
self.i2c.stop();
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)
Expand All @@ -105,77 +108,90 @@ def clear_status(self):
"""
Clear the status register
"""
return self.send_cmd(SHT30.CLEAR_STATUS_CMD, None);
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);
return self.send_cmd(SHT30.RESET_CMD, None)

def status(self, raw=False):
"""
Get the sensor status register.
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);
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
If raw==True returns a bytearray(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);
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;
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
If raw==True returns a bytearray(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:
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_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

def measure_dp(self):
"""
Get the temperature (T) and approximate dew point (Tdp) measurements in
degrees Celsius. Dew point is calculated using a simple approximation
valid with RH > 0.5.

References:
* https://en.wikipedia.org/wiki/Dew_point#Calculating_the_dew_point
"""

temp, humidity = self.measure()
return temp, temp - (100 - humidity) / 5


class SHT30Error(Exception):
"""
Custom exception for errors on sensor management
"""
BUS_ERROR = 0x01
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"
Expand All @@ -185,4 +201,3 @@ def get_message(self):
return "CRC error"
else:
return "Unknown error"