diff --git a/octoprint_enclosure/SHTC3.py b/octoprint_enclosure/SHTC3.py new file mode 100644 index 0000000..78c8def --- /dev/null +++ b/octoprint_enclosure/SHTC3.py @@ -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() diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 72d8376..07e5773 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -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 @@ -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 " diff --git a/octoprint_enclosure/static/js/enclosure.js b/octoprint_enclosure/static/js/enclosure.js index 5e0a935..d1cca41 100644 --- a/octoprint_enclosure/static/js/enclosure.js +++ b/octoprint_enclosure/static/js/enclosure.js @@ -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; diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index 473ebb0..6b217f0 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -604,7 +604,7 @@ @@ -663,7 +664,7 @@ GPIO pin for temperature sensor, recommended to use 4 as DS18B20 has default support to pin #4 (BCM) -
+