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

SHTC3 support #492

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
174 changes: 174 additions & 0 deletions octoprint_enclosure/SHTC3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import sys
import time

from smbus2 import SMBus, i2c_msg

try:
import struct
except ImportError:
import ustruct as struct

from crc import CrcCalculator, Configuration
width = 8
poly = 0x31
init_value = 0xFF
final_xor_value = 0x00
reverse_input = False
reverse_output = False
configuration = Configuration(width, poly, init_value, final_xor_value, reverse_input, reverse_output)

use_table = True
crc_calculator = CrcCalculator(configuration, use_table)


class SHTC3Exception(Exception):
""" Base class for exception """


class SHTC3DeviceNotFound(SHTC3Exception, ValueError):
""" Device not found """


class SHTC3ReadError(SHTC3Exception, RuntimeError):
""" Read error or CRC mismatch """


CMD_SLEEP = 0xB098
CMD_WAKE = 0x3517 # 240µs
CMD_RESET = 0x805D # 240µs

CMD_READ_ID = 0xEFC8

# Normal sample time 12ms
# Low sample time 0.8ms
CMD_NORMAL_T = 0x7866
CMD_NORMAL_RH = 0x58E0
CMD_LOW_T = 0x609C
CMD_LOW_RH = 0x401A

CMD_NORMAL_STRETCH_T = 0x7CA2
CMD_NORMAL_STRETCH_RH = 0x5C24
CMD_LOW_STRETCH_T = 0x6458
CMD_LOW_STRETCH_RH = 0x44DE

SHTC3_I2CADDR_DEFAULT = 0x70


def send_command(bus, address, cmd):
"""Write the command bytes to the bus."""
byte_array = cmd.to_bytes(2, 'big')
for b in byte_array:
bus.write_byte_data(address, 0, b)
time.sleep(0.001)


def write_then_read(bus, address, cmd, read_length):
"""In a single transaction write a cmd, then read the result."""
cmd_bytes = cmd.to_bytes(2, 'big')
write = i2c_msg.write(address, cmd_bytes)
read = i2c_msg.read(address, read_length)

bus.i2c_rdwr(write, read)

return bytes(read)


def to_relative_humidity(raw_data):
"""Convert the linearized 16 bit values into Relative humidity (result in %RH)

Source: https://sensirion.com/media/documents/643F9C8E/6164081E/Sensirion_Humidity_Sensors_SHTC3_Datasheet.pdf
"""
return 100.0 * (raw_data / 2 ** 16)


def to_temperature(raw_data):
"""Convert the linearized 16 bit values into Temperature (result in °C)

Source: https://sensirion.com/media/documents/643F9C8E/6164081E/Sensirion_Humidity_Sensors_SHTC3_Datasheet.pdf
"""
return -45 + 175 * (raw_data / 2 ** 16)


def sample_temperature(bus, address):

send_command(bus, address, CMD_RESET)
time.sleep(0.001)
send_command(bus, address, CMD_WAKE)
time.sleep(0.001)

try:
result = write_then_read(bus, address, CMD_NORMAL_STRETCH_T, 3)

if not crc_calculator.verify_checksum(result[0:2], result[2]):
raise SHTC3ReadError('CRC Mismatch')

raw_temp, crc = struct.unpack(">HB", result)
return to_temperature(raw_temp)

finally:
send_command(bus, address, CMD_SLEEP)


def sample_humidity(bus, address):
send_command(bus, address, CMD_RESET)
time.sleep(0.001)
send_command(bus, address, CMD_WAKE)
time.sleep(0.001)

try:
result = write_then_read(bus, address, CMD_NORMAL_STRETCH_RH, 3)

if not crc_calculator.verify_checksum(result[0:2], result[2]):
raise SHTC3ReadError('CRC Mismatch')

raw_rh, crc = struct.unpack(">HB", result)
return to_relative_humidity(raw_rh)

finally:
send_command(bus, address, CMD_SLEEP)


def sample_both(bus, address):
"""Sample of temperature and humidity."""

send_command(bus, address, CMD_RESET)
send_command(bus, address, CMD_WAKE)

try:
result = write_then_read(bus, address, CMD_NORMAL_STRETCH_T, 6)

if not crc_calculator.verify_checksum(result[0:2], result[2]):
raise SHTC3ReadError('CRC Mismatch')

if not crc_calculator.verify_checksum(result[3:5], result[5]):
raise SHTC3ReadError('CRC Mismatch')

raw_temp, crc_temp, raw_rh, crc_rh = struct.unpack(">HBHB", result)
temperature = to_temperature(raw_temp)
humidity = to_relative_humidity(raw_rh)

return temperature, humidity

finally:
# Sleep
send_command(bus, address, CMD_SLEEP)


def main():
# get i2c bus and bus address if provided or use defaults
address = SHTC3_I2CADDR_DEFAULT
bus = SMBus(1)
if len(sys.argv) > 1:
bus = SMBus(int(sys.argv[1]))
address = int(sys.argv[2], 16)

try:
temperature, humidity = sample_both(bus, address)
print('{0:0.1f} | {1:0.1f}'.format(temperature, humidity))

except Exception:
print('-1 | -1')


if __name__ == "__main__":
main()
30 changes: 30 additions & 0 deletions octoprint_enclosure/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,9 @@ def get_sensor_data(self, sensor):
elif sensor['temp_sensor_type'] == "hum_raw_i2c":
hum, temp = self.read_raw_i2c_temp(sensor)
airquality = 0
elif sensor['temp_sensor_type'] == "shtc3":
temp, hum = self.read_shtc3_temp(sensor['temp_sensor_address'], sensor['temp_sensor_i2cbus'])
airquality = 0
else:
self._logger.info("temp_sensor_type no match")
temp = None
Expand Down Expand Up @@ -1137,6 +1140,33 @@ def read_mcp_temp(self, address, i2cbus):
self.log_error(ex)
return 0

def read_shtc3_temp(self, address, i2cbus):
try:
script = os.path.dirname(os.path.realpath(__file__)) + "/SHTC3.py"
cmd = [sys.executable, script, str(i2cbus), str(address)]
if self._settings.get(["use_sudo"]):
cmd.insert(0, "sudo")

if self._settings.get(["debug_temperature_log"]) is True:
self._logger.debug("Temperature SHTC3 cmd: %s", " ".join(cmd))

stdout = Popen(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True)
output, errors = stdout.communicate()

if self._settings.get(["debug_temperature_log"]) is True:
if len(errors) > 0:
self._logger.error("SHTC3 error: %s", errors)
else:
self._logger.info("SHTC3 result: %s", output)

temp, hum = output.split("|")
return self.to_float(temp.strip()), self.to_float(hum.strip())

except Exception as ex:
self._logger.info("Failed to execute python scripts, try disabling use SUDO on advanced section.")
self.log_error(ex)
return 0, 0

def read_dht_temp(self, sensor, pin):
try:
script = os.path.dirname(os.path.realpath(__file__)) + "/getDHTTemp.py "
Expand Down
2 changes: 1 addition & 1 deletion octoprint_enclosure/static/js/enclosure.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ $(function () {
self.notifications = ko.observableArray([]);

self.humidityCapableSensor = function(sensor){
if (['11', '20', '22', '2302', 'bme280', 'bme680', 'am2320', 'aht10' , 'si7021', 'hum_raw_i2c', 'temp_raw_i2c'].indexOf(sensor) >= 0){
if (['11', '20', '22', '2302', 'bme280', 'bme680', 'am2320', 'aht10' , 'si7021', 'hum_raw_i2c', 'temp_raw_i2c', 'shtc3'].indexOf(sensor) >= 0){
return true;
}
return false;
Expand Down
9 changes: 5 additions & 4 deletions octoprint_enclosure/templates/enclosure_settings.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@
<select data-bind="value: temp_sensor_type">
<option value="">Select Sensor</option>
<option value="11">DHT11</option>
<option value="20">DHT20</option>
<option value="20">DHT20</option>
<option value="22">DHT22</option>
<option value="2302">AM2302</option>
<option value="18b20">DS18B20</option>
Expand All @@ -617,6 +617,7 @@
<option value="max31855">MAX31855</option>
<option value="rpi">Raspberry Pi CPU</option>
<option value="mcp9808">MCP9808</option>
<option value="shtc3">SHTC3</option>
<option value="temp_raw_i2c">Raw I2C Temperature</option>
<option value="hum_raw_i2c">Raw I2C Humidity</option>
</select>
Expand Down Expand Up @@ -663,7 +664,7 @@
<span class="help-inline">GPIO pin for temperature sensor, recommended to use 4 as DS18B20 has default support to pin #4 (BCM)</span>
</div>
</div>
<div class"control-group">
<div class="control-group">
<label class="control-label" for="settings-enclosure-dhtPin">{{ _('Input Pull Resistor') }}</label>
<div class="controls">
<select data-bind="value: input_pull_resistor">
Expand All @@ -682,7 +683,7 @@
</div>
</div>
<!-- /ko -->
<!-- ko if: ($data.temp_sensor_type() == "si7021") || ($data.temp_sensor_type() == "20") || ($data.temp_sensor_type() == "bme280") || ($data.temp_sensor_type() == "am2320") || ($data.temp_sensor_type() == "aht10") || ($data.temp_sensor_type() == "tmp102") || ($data.temp_sensor_type() == "mcp9808") -->
<!-- ko if: ($data.temp_sensor_type() == "si7021") || ($data.temp_sensor_type() == "20") || ($data.temp_sensor_type() == "bme280") || ($data.temp_sensor_type() == "am2320") || ($data.temp_sensor_type() == "aht10") || ($data.temp_sensor_type() == "tmp102") || ($data.temp_sensor_type() == "mcp9808") || ($data.temp_sensor_type() == "shtc3") -->
<div class="control-group">
<label class="control-label" for="settings-enclosure-dhtPin">{{ _('Sensor Pin') }}</label>
<div class="controls">
Expand Down Expand Up @@ -732,7 +733,7 @@
</div>

<!-- /ko -->
<!-- ko ifnot: ($data.temp_sensor_type() == "rpi") || ($data.temp_sensor_type() == "18b20") || ($data.temp_sensor_type() == "si7021") || ($data.temp_sensor_type() == "bme280") || ($data.temp_sensor_type() == "am2320") || ($data.temp_sensor_type() == "tmp102") || ($data.temp_sensor_type() == "max31855") || ($data.temp_sensor_type() == "mcp9808") || ($data.temp_sensor_type() == "temp_raw_i2c") || ($data.temp_sensor_type() == "hum_raw_i2c") -->
<!-- ko ifnot: ($data.temp_sensor_type() == "rpi") || ($data.temp_sensor_type() == "18b20") || ($data.temp_sensor_type() == "si7021") || ($data.temp_sensor_type() == "bme280") || ($data.temp_sensor_type() == "am2320") || ($data.temp_sensor_type() == "tmp102") || ($data.temp_sensor_type() == "max31855") || ($data.temp_sensor_type() == "mcp9808") || ($data.temp_sensor_type() == "shtc3") || ($data.temp_sensor_type() == "temp_raw_i2c") || ($data.temp_sensor_type() == "hum_raw_i2c") -->
<div class="control-group">
<label class="control-label" for="settings-enclosure-dhtPin">{{ _('Sensor Pin') }}</label>
<div class="controls">
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
plugin_license = "AGPLv3"

# Any additional requirements besides OctoPrint should be listed here
plugin_requires = ["RPi.GPIO>=0.6.5", "requests>=2.7", "smbus2>=0.3.0", "gpiozero==1.6.2", "RPi.bme280", "bme680"]
plugin_requires = ["RPi.GPIO>=0.6.5", "requests>=2.7", "smbus2>=0.3.0", "gpiozero==1.6.2", "RPi.bme280", "bme680", "crc"]

additional_setup_parameters = {}

Expand Down