From 145978639bb1504abe44c5116349fe10475117c0 Mon Sep 17 00:00:00 2001 From: cristianku Date: Mon, 19 Aug 2019 11:34:43 +0200 Subject: [PATCH 001/104] Create bme680.py additional bme680 --- octoprint_enclosure/bme680.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 octoprint_enclosure/bme680.py diff --git a/octoprint_enclosure/bme680.py b/octoprint_enclosure/bme680.py new file mode 100644 index 0000000..99eea14 --- /dev/null +++ b/octoprint_enclosure/bme680.py @@ -0,0 +1,24 @@ +import bme680 + +# if len(sys.argv) == 2: +# DEVICE = int(sys.argv[1],16) +# else: +# print('-1 | -1') +# sys.exit(1) + +try: + sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY) +except IOError: + sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY) + +# These oversampling settings can be tweaked to +# change the balance between accuracy and noise in +# the data. + +sensor.set_humidity_oversample(bme680.OS_2X) +sensor.set_pressure_oversample(bme680.OS_4X) +sensor.set_temperature_oversample(bme680.OS_8X) +sensor.set_filter(bme680.FILTER_SIZE_3) + +if sensor.get_sensor_data(): + print('{0:0.1f} | {1:0.1f}'.format(sensor.data.temperature, sensor.data.humidity)) -- 2.39.5 From 954803fc92d35c8b5f8801db5f2632ba7a6daa63 Mon Sep 17 00:00:00 2001 From: cristianku Date: Mon, 19 Aug 2019 11:38:11 +0200 Subject: [PATCH 002/104] added bme680 sensor --- octoprint_enclosure/templates/enclosure_settings.jinja2 | 1 + 1 file changed, 1 insertion(+) diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index 7612751..8ce4d29 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -536,6 +536,7 @@ + -- 2.39.5 From 899898642b799f55c49d00794f79e24cd42242ad Mon Sep 17 00:00:00 2001 From: cristianku Date: Mon, 19 Aug 2019 11:42:58 +0200 Subject: [PATCH 003/104] added new sensor bme680 --- octoprint_enclosure/__init__.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 9c1fc94..f8c444c 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -504,6 +504,8 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP hum = 0 elif sensor['temp_sensor_type'] == "bme280": temp, hum = self.read_bme280_temp(sensor['temp_sensor_address']) + elif sensor['temp_sensor_type'] == "bme680": + temp, hum = self.read_bme680_temp(sensor['temp_sensor_address']) elif sensor['temp_sensor_type'] == "si7021": temp, hum = self.read_si7021_temp(sensor['temp_sensor_address']) elif sensor['temp_sensor_type'] == "tmp102": @@ -598,6 +600,27 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.log_error(ex) return (0, 0) + def read_bme680_temp(self, address): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/BME680.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python " + script + str(address) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature BME680 cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("BME680 result: %s", stdout) + temp, hum = stdout.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 of the plugin.") + self.log_error(ex) + return (0, 0) + def read_si7021_temp(self, address): try: script = os.path.dirname(os.path.realpath(__file__)) + "/SI7021.py " -- 2.39.5 From 02cd291d5ad4f4a9d443308c9f443b98e3983d2a Mon Sep 17 00:00:00 2001 From: cristianku Date: Mon, 19 Aug 2019 11:50:04 +0200 Subject: [PATCH 004/104] Update bme680.py --- octoprint_enclosure/bme680.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/octoprint_enclosure/bme680.py b/octoprint_enclosure/bme680.py index 99eea14..1bb6d9e 100644 --- a/octoprint_enclosure/bme680.py +++ b/octoprint_enclosure/bme680.py @@ -1,11 +1,5 @@ import bme680 -# if len(sys.argv) == 2: -# DEVICE = int(sys.argv[1],16) -# else: -# print('-1 | -1') -# sys.exit(1) - try: sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY) except IOError: -- 2.39.5 From 0d807bc5bb78d3389bc1527df3a8db8381eebdbd Mon Sep 17 00:00:00 2001 From: cristianku Date: Mon, 19 Aug 2019 17:44:25 +0200 Subject: [PATCH 005/104] implemented bme680 with air quality --- octoprint_enclosure/__init__.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index f8c444c..f4f2584 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -505,7 +505,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP elif sensor['temp_sensor_type'] == "bme280": temp, hum = self.read_bme280_temp(sensor['temp_sensor_address']) elif sensor['temp_sensor_type'] == "bme680": - temp, hum = self.read_bme680_temp(sensor['temp_sensor_address']) + temp, hum, airquality = self.read_bme680_temp(sensor['temp_sensor_address']) elif sensor['temp_sensor_type'] == "si7021": temp, hum = self.read_si7021_temp(sensor['temp_sensor_address']) elif sensor['temp_sensor_type'] == "tmp102": @@ -613,14 +613,15 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() if self._settings.get(["debug_temperature_log"]) is True: self._logger.debug("BME680 result: %s", stdout) - temp, hum = stdout.split("|") - return (self.to_float(temp.strip()), self.to_float(hum.strip())) + temp, hum, airq = stdout.split("|") + return (self.to_float(temp.strip()), self.to_float(hum.strip()), self.to_float(airq.strip())) except Exception as ex: self._logger.info( "Failed to execute python scripts, try disabling use SUDO on advanced section of the plugin.") self.log_error(ex) return (0, 0) - + + def read_si7021_temp(self, address): try: script = os.path.dirname(os.path.realpath(__file__)) + "/SI7021.py " @@ -1579,4 +1580,4 @@ def __plugin_load__(): __plugin_hooks__ = { "octoprint.comm.protocol.gcode.queuing" : __plugin_implementation__.hook_gcode_queuing, "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information - } \ No newline at end of file + } -- 2.39.5 From b1e99f23080b453af1416f50805a3da11f063c12 Mon Sep 17 00:00:00 2001 From: cristianku Date: Mon, 19 Aug 2019 17:48:39 +0200 Subject: [PATCH 006/104] Update __init__.py --- octoprint_enclosure/__init__.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index f4f2584..fa13adf 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -342,11 +342,11 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP try: sensor_data = [] for sensor in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): - temp, hum = self.get_sensor_data(sensor) + temp, hum, airquality = self.get_sensor_data(sensor) if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Sensor %s Temperature: %s humidity %s", sensor['label'], temp, hum) - if temp is not None and hum is not None: - sensor_data.append(dict(index_id=sensor['index_id'], temperature=temp, humidity=hum)) + self._logger.debug("Sensor %s Temperature: %s humidity %s Airquality %s", sensor['label'], temp, hum, airquality) + if temp is not None and hum is not None and airquality is not None: + sensor_data.append(dict(index_id=sensor['index_id'], temperature=temp, humidity=hum, airquality=airquality)) self.temperature_sensor_data = sensor_data self.handle_temp_hum_control() self.handle_temperature_events() @@ -495,35 +495,43 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP def get_sensor_data(self, sensor): try: if self.development_mode: - temp, hum = self.read_dummy_temp() + temp, hum, airquality = self.read_dummy_temp() else: if sensor['temp_sensor_type'] in ["11", "22", "2302"]: temp, hum = self.read_dht_temp(sensor['temp_sensor_type'], sensor['gpio_pin']) elif sensor['temp_sensor_type'] == "18b20": temp = self.read_18b20_temp(sensor['ds18b20_serial']) hum = 0 + airquality = 0 elif sensor['temp_sensor_type'] == "bme280": temp, hum = self.read_bme280_temp(sensor['temp_sensor_address']) + airquality = 0 elif sensor['temp_sensor_type'] == "bme680": temp, hum, airquality = self.read_bme680_temp(sensor['temp_sensor_address']) elif sensor['temp_sensor_type'] == "si7021": temp, hum = self.read_si7021_temp(sensor['temp_sensor_address']) + airquality = 0 elif sensor['temp_sensor_type'] == "tmp102": temp = self.read_tmp102_temp(sensor['temp_sensor_address']) hum = 0 + airquality = 0 elif sensor['temp_sensor_type'] == "max31855": temp = self.read_max31855_temp(sensor['temp_sensor_address']) hum = 0 + airquality = 0 else: self._logger.info("temp_sensor_type no match") temp = None hum = None - if temp != -1 and hum != -1: + airquality = None + if temp != -1 and hum != -1 and airquality != -1: temp = round(self.to_float(temp), 1) if not sensor['use_fahrenheit'] else round( self.to_float(temp) * 1.8 + 32, 1) hum = round(self.to_float(hum), 1) - return temp, hum - return None, None + airquality = round(self.to_float(airquality), 1) + return temp, hum, airquality + return None, None, None + except Exception as ex: self.log_error(ex) @@ -556,7 +564,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.dummy_value = return_value - return return_value, return_value + return return_value, return_value, return_value def read_dht_temp(self, sensor, pin): try: -- 2.39.5 From 3fa482dfb20861b4c46a283217fd6d2d76dda658 Mon Sep 17 00:00:00 2001 From: cristianku Date: Mon, 19 Aug 2019 23:07:45 +0200 Subject: [PATCH 007/104] bme680 air quality index --- octoprint_enclosure/BME680_AQI.py | 130 ++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 octoprint_enclosure/BME680_AQI.py diff --git a/octoprint_enclosure/BME680_AQI.py b/octoprint_enclosure/BME680_AQI.py new file mode 100644 index 0000000..231c12c --- /dev/null +++ b/octoprint_enclosure/BME680_AQI.py @@ -0,0 +1,130 @@ +import bme680 +import time + + +try: + sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY) +except IOError: + sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY) + +# These calibration data can safely be commented +# out, if desired. + + +# These oversampling settings can be tweaked to +# change the balance between accuracy and noise in +# the data. + +hum_weighting = float(0.25) # so hum effect is 25% of the total air quality score +gas_weighting = float(0.75) # so gas effect is 75% of the total air quality score + +sensor.set_humidity_oversample(bme680.OS_2X) +sensor.set_pressure_oversample(bme680.OS_2X) +sensor.set_temperature_oversample(bme680.OS_2X) +sensor.set_filter(bme680.FILTER_SIZE_3) + +sensor.set_gas_heater_temperature(320) +sensor.set_gas_heater_duration(150) +sensor.select_gas_heater_profile(0) +sensor.set_gas_status(bme680.ENABLE_GAS_MEAS) + +gas_reference = float(250000) +hum_reference = float(40) +getgasreference_count = int(0) + + +def GetGasReference(gas_reference): + # Now run the sensor for a burn-in period, then use combination of relative humidity and gas resistance to estimate indoor air quality as a percentage. + # print("Getting a new gas reference value") + readings = int(10) + while True: + sensor.get_sensor_data() + if sensor.data.heat_stable: + for i in range(1, readings): # // read gas for 10 x 0.150mS = 1.5secs + sensor.get_sensor_data() + gas_reference = gas_reference + sensor.data.gas_resistance + # print(sensor.data.gas_resistance) + gas_reference = gas_reference / readings + return + + + + + # for i in range(1, readings): # // read gas for 10 x 0.150mS = 1.5secs + # gas_reference = gas_reference + sensor.data.gas_resistance + # print(sensor.data.gas_resistance) + # gas_reference = gas_reference / readings + + +def CalculateIAQ(score): + IAQ_text = "Air quality is " + score = float((100 - score) * 5) + if score >= 301: + IAQ_text = IAQ_text + "Hazardous" + elif score >= 201 and score <= 300: + IAQ_text = IAQ_text + "Very Unhealthy" + elif score >= 176 and score <= 200: + IAQ_text = IAQ_text + "Unhealthy" + elif score >= 151 and score <= 175: + IAQ_text = IAQ_text + "Unhealthy for Sensitive Groups" + elif score >= 51 and score <= 150: + IAQ_text = IAQ_text + "Moderate" + elif score >= 00 and score <= 50: + IAQ_text = IAQ_text + "Good" + return IAQ_text + + + +#Calculate humidity contribution to IAQ index +current_humidity = float(sensor.data.humidity) +if (current_humidity >= 38 and current_humidity <= 42): + hum_score = float(0.25*100) # Humidity +/-5% around optimum +else: + #sub-optimal + if (current_humidity < 38): + hum_score = float(0.25/hum_reference*current_humidity*100) + else: + hum_score = ((-0.25/(100-hum_reference)*current_humidity)+0.416666)*100 + + +#Calculate gas contribution to IAQ index +gas_lower_limit = float(5000) # Bad air quality limit +gas_upper_limit = float(50000) # Good air quality limit + +if (gas_reference > gas_upper_limit): + gas_reference = gas_upper_limit #gas_reference = 250000 see above +if (gas_reference < gas_lower_limit): + gas_reference = gas_lower_limit + +gas_score = float((0.75/(gas_upper_limit-gas_lower_limit)*gas_reference -(gas_lower_limit*(0.75/(gas_upper_limit-gas_lower_limit))))*100) + +# print('gas_upper_limit--------') +# print(gas_upper_limit) +# +# print('gas_lower_limit--------') +# print(gas_lower_limit) +# +# +# print('gas score---------') +# print(gas_score) +#Combine results for the final IAQ index value (0-100% where 100% is good quality air) +air_quality_score = float(hum_score + gas_score) + +# print('Air Quality = {0:.2f} % derived from 25% of Humidity reading and 75% of Gas reading - 100% is good quality air'.format( air_quality_score)) + +# print('Humidity element was : {0:.2f} of 0.25'.format( hum_score/100)) +# print(' Gas element was : {0:.2f} of 0.25'.format( gas_score/100)) + + + + +# if (sensor.data.gas_resistance < 120000): +# print('Poor air qulity *****') + +GetGasReference(gas_reference) + +# print(CalculateIAQ(air_quality_score)) +# print("------------------------------------------------") +# time.sleep(2) +print('{0:0.1f}'.format(air_quality_score)) + -- 2.39.5 From de5aa9463f3d5981762b71a211b5ed266733d843 Mon Sep 17 00:00:00 2001 From: munzli Date: Sun, 29 Sep 2019 17:55:34 +0200 Subject: [PATCH 008/104] added support for mcp9808 temperature sensor --- README.md | 2 +- octoprint_enclosure/__init__.py | 19 ++++++ octoprint_enclosure/mcp9808.py | 61 +++++++++++++++++++ .../templates/enclosure_settings.jinja2 | 5 +- setup.py | 2 +- 5 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 octoprint_enclosure/mcp9808.py diff --git a/README.md b/README.md index fc4f104..5e75eba 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ The response will either have YES or NO at the end of the first line. If it is y Copy the serial number, you will need to configure the plugin. Note that for the serial number includes the 28-, for example 28-0000069834ff. -* For the SI7021, BME280 and TMP102 sensors +* For the SI7021, BME280, TMP102 and MCP9808 sensors Enable I2C on your raspberry pi, depending on raspi-config version, step by step can be different: diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 9c1fc94..54b16c0 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -512,6 +512,9 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP elif sensor['temp_sensor_type'] == "max31855": temp = self.read_max31855_temp(sensor['temp_sensor_address']) hum = 0 + elif sensor['temp_sensor_type'] == "mcp9808": + temp = self.read_mcp_temp() + hum = 0 else: self._logger.info("temp_sensor_type no match") temp = None @@ -556,6 +559,22 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP return return_value, return_value + def read_mcp_temp(self): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/mcp9808.py" + args = ["python", script] + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature MCP9808 cmd: %s", " ".join(args)) + proc = Popen(args, stdout=PIPE) + stdout, _ = proc.communicate() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("MCP9808 result: %s", stdout) + return self.to_float(stdout.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 + def read_dht_temp(self, sensor, pin): try: script = os.path.dirname(os.path.realpath(__file__)) + "/getDHTTemp.py " diff --git a/octoprint_enclosure/mcp9808.py b/octoprint_enclosure/mcp9808.py new file mode 100644 index 0000000..d324b47 --- /dev/null +++ b/octoprint_enclosure/mcp9808.py @@ -0,0 +1,61 @@ +import smbus + +# default I2C address for device. +MCP9808_I2CADDR_DEFAULT = 0x18 + +# register addresses. +MCP9808_REG_CONFIG = 0x01 +MCP9808_REG_UPPER_TEMP = 0x02 +MCP9808_REG_LOWER_TEMP = 0x03 +MCP9808_REG_CRIT_TEMP = 0x04 +MCP9808_REG_AMBIENT_TEMP = 0x05 +MCP9808_REG_MANUF_ID = 0x06 +MCP9808_REG_DEVICE_ID = 0x07 +MCP9808_REG_RESOLUTION = 0x08 + +# configuration register values. +MCP9808_REG_CONFIG_CONTCONV = 0x0000 +MCP9808_REG_CONFIG_SHUTDOWN = 0x0100 +MCP9808_REG_CONFIG_CRITLOCKED = 0x0080 +MCP9808_REG_CONFIG_WINLOCKED = 0x0040 +MCP9808_REG_CONFIG_INTCLR = 0x0020 +MCP9808_REG_CONFIG_ALERTSTAT = 0x0010 +MCP9808_REG_CONFIG_ALERTCTRL = 0x0008 +MCP9808_REG_CONFIG_ALERTSEL = 0x0002 +MCP9808_REG_CONFIG_ALERTPOL = 0x0002 +MCP9808_REG_CONFIG_ALERTMODE = 0x0001 + + +def main(): + # get I2C bus + bus = smbus.SMBus(1) + + # MCP9808 address, 0x18(24) + # configuration register, 0x01(1) + # continuous conversion mode, power-up default + config = [MCP9808_REG_CONFIG_CONTCONV, 0x00] + bus.write_i2c_block_data(MCP9808_I2CADDR_DEFAULT, MCP9808_REG_CONFIG, config) + + # MCP9808 address, 0x18(24) + # select resolution rgister, 0x08(8) + # resolution = +0.0625 / C, 0x03(03) + bus.write_byte_data(MCP9808_I2CADDR_DEFAULT, MCP9808_REG_RESOLUTION, 0x03) + + # MCP9808 address, 0x18(24) + # read data back from 0x05(5), 2 bytes + # temp MSB, TEMP LSB + data = bus.read_i2c_block_data(MCP9808_I2CADDR_DEFAULT, MCP9808_REG_AMBIENT_TEMP, 2) + + # convert the data to 13-bits + ctemp = ((data[0] & 0x1F) * 256) + data[1] + if ctemp > 4095: + ctemp -= 8192 + ctemp = ctemp * 0.0625 + # ftemp = ctemp * 1.8 + 32 + + # output data + print('{0:0.2f}'.format(ctemp)) + + +if __name__ == "__main__": + main() diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index 7612751..29bdc5f 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -538,6 +538,7 @@ + Attention You need to install and configure the necessary libraries for the temperature sensor, check @@ -562,7 +563,7 @@ - +
@@ -604,7 +605,7 @@
- +
diff --git a/setup.py b/setup.py index 54921de..71166b0 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ plugin_package = "octoprint_enclosure" plugin_name = "OctoPrint-Enclosure" # The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module -plugin_version = "4.12" +plugin_version = "4.13" # The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin # module -- 2.39.5 From 01e727cb94fc104e3b96e4f27a47aee917ec57fb Mon Sep 17 00:00:00 2001 From: munzli Date: Sun, 29 Sep 2019 19:00:31 +0200 Subject: [PATCH 009/104] configurable sensor address for mcp9808 --- octoprint_enclosure/__init__.py | 6 +++--- octoprint_enclosure/mcp9808.py | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 54b16c0..51165b7 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -513,7 +513,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP temp = self.read_max31855_temp(sensor['temp_sensor_address']) hum = 0 elif sensor['temp_sensor_type'] == "mcp9808": - temp = self.read_mcp_temp() + temp = self.read_mcp_temp(sensor['temp_sensor_address']) hum = 0 else: self._logger.info("temp_sensor_type no match") @@ -559,10 +559,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP return return_value, return_value - def read_mcp_temp(self): + def read_mcp_temp(self, address): try: script = os.path.dirname(os.path.realpath(__file__)) + "/mcp9808.py" - args = ["python", script] + args = ["python", script, str(address)] if self._settings.get(["debug_temperature_log"]) is True: self._logger.debug("Temperature MCP9808 cmd: %s", " ".join(args)) proc = Popen(args, stdout=PIPE) diff --git a/octoprint_enclosure/mcp9808.py b/octoprint_enclosure/mcp9808.py index d324b47..672045b 100644 --- a/octoprint_enclosure/mcp9808.py +++ b/octoprint_enclosure/mcp9808.py @@ -1,3 +1,4 @@ +import sys import smbus # default I2C address for device. @@ -27,24 +28,29 @@ MCP9808_REG_CONFIG_ALERTMODE = 0x0001 def main(): + # get bus address if provided or use default address + address = MCP9808_I2CADDR_DEFAULT + if len(sys.argv) == 2: + address = int(sys.argv[1], 16) + # get I2C bus bus = smbus.SMBus(1) - # MCP9808 address, 0x18(24) + # MCP9808 address, default 0x18(24) # configuration register, 0x01(1) # continuous conversion mode, power-up default config = [MCP9808_REG_CONFIG_CONTCONV, 0x00] - bus.write_i2c_block_data(MCP9808_I2CADDR_DEFAULT, MCP9808_REG_CONFIG, config) + bus.write_i2c_block_data(address, MCP9808_REG_CONFIG, config) - # MCP9808 address, 0x18(24) + # MCP9808 address, default 0x18(24) # select resolution rgister, 0x08(8) # resolution = +0.0625 / C, 0x03(03) - bus.write_byte_data(MCP9808_I2CADDR_DEFAULT, MCP9808_REG_RESOLUTION, 0x03) + bus.write_byte_data(address, MCP9808_REG_RESOLUTION, 0x03) - # MCP9808 address, 0x18(24) + # MCP9808 address, default 0x18(24) # read data back from 0x05(5), 2 bytes # temp MSB, TEMP LSB - data = bus.read_i2c_block_data(MCP9808_I2CADDR_DEFAULT, MCP9808_REG_AMBIENT_TEMP, 2) + data = bus.read_i2c_block_data(address, MCP9808_REG_AMBIENT_TEMP, 2) # convert the data to 13-bits ctemp = ((data[0] & 0x1F) * 256) + data[1] -- 2.39.5 From e8237841dd3966c75541a170400c78a644bd98f9 Mon Sep 17 00:00:00 2001 From: UnchartedBull Date: Tue, 8 Oct 2019 23:28:27 +0200 Subject: [PATCH 010/104] Add Temperature GET API --- octoprint_enclosure/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 9c1fc94..103d176 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -174,6 +174,18 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP gpio_status.append(dict(index_id=index, status=val)) return flask.Response(json.dumps(gpio_status), mimetype='application/json') + @octoprint.plugin.BlueprintPlugin.route("/getTemperatureStatus", methods=["GET"]) + def get_temperature_status(self): + temperature_status = [] + for rpi_input in self.rpi_input: + if rpi_input['input_type'] == 'temperature_sensor': + temperature = self.to_int(rpi_input['temp_sensor_temp']) + humidity = self.to_int(rpi_input['temp_sensor_humidity']) + index = self.to_int(rpi_input['index_id']) + label = rpi_input['label'] + temperature_status.append(dict(index_id=index, label=label, temperature=temperature, humidity=humidity)) + return flask.Response(json.dumps(temperature_status), mimetype='application/json') + @octoprint.plugin.BlueprintPlugin.route("/setIO", methods=["GET"]) def set_io(self): index = flask.request.values["index_id"] -- 2.39.5 From 0983363b5f7c8a383cb179a0fb9285b0fd85aa2e Mon Sep 17 00:00:00 2001 From: Timon G Date: Wed, 9 Oct 2019 00:38:50 +0200 Subject: [PATCH 011/104] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 54921de..71166b0 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ plugin_package = "octoprint_enclosure" plugin_name = "OctoPrint-Enclosure" # The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module -plugin_version = "4.12" +plugin_version = "4.13" # The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin # module -- 2.39.5 From 5798beeeec51860a45a4aaed6478e646813a0cdc Mon Sep 17 00:00:00 2001 From: UnchartedBull Date: Wed, 9 Oct 2019 13:59:51 +0200 Subject: [PATCH 012/104] first draft for kind of RESTful API --- octoprint_enclosure/__init__.py | 62 +++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 103d176..e786d5d 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -142,6 +142,8 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.rpi_inputs = self._settings.get(["rpi_inputs"]) # ~~ Blueprintplugin mixin + ## POST /temperature/$id + ## maybe PATCH will be the more official way to do. Feedback? @octoprint.plugin.BlueprintPlugin.route("/setEnclosureTempHum", methods=["GET"]) def set_enclosure_temp_humidity(self): set_value = self.to_float(flask.request.values["set_temperature"]) @@ -153,27 +155,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.handle_temp_hum_control() return flask.jsonify(success=True) - @octoprint.plugin.BlueprintPlugin.route("/clearGPIOMode", methods=["GET"]) - def clear_gpio_mode(self): - GPIO.cleanup() - return flask.jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/updateUI", methods=["GET"]) - def update_ui_requested(self): - self.update_ui() - return flask.jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/getOutputStatus", methods=["GET"]) - def get_output_status(self): - gpio_status = [] - for rpi_output in self.rpi_outputs: - if rpi_output['output_type'] == 'regular': - pin = self.to_int(rpi_output['gpio_pin']) - val = GPIO.input(pin) if not rpi_output['active_low'] else (not GPIO.input(pin)) - index = self.to_int(rpi_output['index_id']) - gpio_status.append(dict(index_id=index, status=val)) - return flask.Response(json.dumps(gpio_status), mimetype='application/json') - + ## GET /temperature/$id might as well also GET /temperature @octoprint.plugin.BlueprintPlugin.route("/getTemperatureStatus", methods=["GET"]) def get_temperature_status(self): temperature_status = [] @@ -186,6 +168,32 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP temperature_status.append(dict(index_id=index, label=label, temperature=temperature, humidity=humidity)) return flask.Response(json.dumps(temperature_status), mimetype='application/json') + ## POST /clear + @octoprint.plugin.BlueprintPlugin.route("/clearGPIOMode", methods=["GET"]) + def clear_gpio_mode(self): + GPIO.cleanup() + return flask.jsonify(success=True) + + ## POST /update + @octoprint.plugin.BlueprintPlugin.route("/updateUI", methods=["GET"]) + def update_ui_requested(self): + self.update_ui() + return flask.jsonify(success=True) + + ## GET /output/$id might as well also GET /output + @octoprint.plugin.BlueprintPlugin.route("/getOutputStatus", methods=["GET"]) + def get_output_status(self): + gpio_status = [] + for rpi_output in self.rpi_outputs: + if rpi_output['output_type'] == 'regular': + pin = self.to_int(rpi_output['gpio_pin']) + val = GPIO.input(pin) if not rpi_output['active_low'] else (not GPIO.input(pin)) + index = self.to_int(rpi_output['index_id']) + gpio_status.append(dict(index_id=index, status=val)) + return flask.Response(json.dumps(gpio_status), mimetype='application/json') + + ## POST /output/$id + ## maybe PATCH will be the more official way to do. Feedback? @octoprint.plugin.BlueprintPlugin.route("/setIO", methods=["GET"]) def set_io(self): index = flask.request.values["index_id"] @@ -196,6 +204,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) return flask.jsonify(success=True) + ## POST /shell @octoprint.plugin.BlueprintPlugin.route("/sendShellCommand", methods=["GET"]) def send_shell_command(self): output_index = self.to_int(flask.request.values["index_id"]) @@ -206,6 +215,8 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.shell_command(command) return flask.jsonify(success=True) + ## Possibly include this into POST/PATCH /output/$id and make it into the JSON body? + ## or maybe PATCH/POST /output/$id/auto-startup @octoprint.plugin.BlueprintPlugin.route("/setAutoStartUp", methods=["GET"]) def set_auto_startup(self): index = flask.request.values["index_id"] @@ -222,6 +233,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._settings.set(["rpi_outputs"], self.rpi_outputs) return flask.jsonify(success=True) + ## same as AutoStartup? @octoprint.plugin.BlueprintPlugin.route("/setAutoShutdown", methods=["GET"]) def set_auto_shutdown(self): index = flask.request.values["index_id"] @@ -239,6 +251,8 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._settings.set(["rpi_outputs"], self.rpi_outputs) return flask.jsonify(success=True) + ## POST filament/$id + ## maybe think of a GET for this as well @octoprint.plugin.BlueprintPlugin.route("/setFilamentSensor", methods=["GET"]) def set_filament_sensor(self): index = flask.request.values["index_id"] @@ -250,6 +264,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._settings.set(["rpi_inputs"], self.rpi_inputs) return flask.jsonify(success=True) + ## POST pwm/$id @octoprint.plugin.BlueprintPlugin.route("/setPWM", methods=["GET"]) def set_pwm(self): set_value = self.to_int(flask.request.values["new_duty_cycle"]) @@ -261,6 +276,8 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.write_pwm(gpio, set_value) return flask.jsonify(success=True) + ## POST gcode/$id + ## I think id might not even be needed here @octoprint.plugin.BlueprintPlugin.route("/sendGcodeCommand", methods=["GET"]) def requested_gcode_command(self): gpio_index = self.to_int(flask.request.values["index_id"]) @@ -268,6 +285,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.send_gcode_command(rpi_output['gcode']) return flask.jsonify(success=True) + ## POST neopixel/$id @octoprint.plugin.BlueprintPlugin.route("/setNeopixel", methods=["GET"]) def set_neopixel(self): """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" @@ -288,6 +306,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP return flask.jsonify(success=True) + ## POST rgb-led/$id @octoprint.plugin.BlueprintPlugin.route("/setLedstripColor", methods=["GET"]) def set_ledstrip_color(self): """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" @@ -1363,7 +1382,6 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP queue_id = '{0!s}_{1!s}'.format(index_id, sufix) - self._logger.debug("Scheduling neopixel output id %s for on %s delay_seconds", queue_id, delay_seconds) thread = threading.Timer(delay_seconds, self.send_neopixel_command, -- 2.39.5 From 5023e08c77574a18b0039634629b244ac44569d1 Mon Sep 17 00:00:00 2001 From: UnchartedBull Date: Wed, 9 Oct 2019 15:04:04 +0200 Subject: [PATCH 013/104] started changing first apis --- .gitignore | 2 + octoprint_enclosure/__init__.py | 228 ++++++++++++++++++++++++++++++-- 2 files changed, 219 insertions(+), 11 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e75ccaa --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +venv +.vscode diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index e786d5d..c49f0cf 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -7,6 +7,8 @@ from .ledstrip import LEDStrip import octoprint.plugin import RPi.GPIO as GPIO import flask +from flask import jsonify, request, make_response, Response +from werkzeug.exceptions import BadRequest import time import sys import glob @@ -159,7 +161,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP @octoprint.plugin.BlueprintPlugin.route("/getTemperatureStatus", methods=["GET"]) def get_temperature_status(self): temperature_status = [] - for rpi_input in self.rpi_input: + for rpi_input in self.rpi_inputs: if rpi_input['input_type'] == 'temperature_sensor': temperature = self.to_int(rpi_input['temp_sensor_temp']) humidity = self.to_int(rpi_input['temp_sensor_humidity']) @@ -253,8 +255,211 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP ## POST filament/$id ## maybe think of a GET for this as well + @octoprint.plugin.BlueprintPlugin.route("/filament/", methods=["PATCH"]) + def set_filament_sensor(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + for sensor in self.rpi_inputs: + if identifier == self.to_int(sensor['index_id']): + sensor['filament_sensor_enabled'] = value + self._logger.info("Setting filament sensor for input %s to : %s", identifier, value) + self._settings.set(["rpi_inputs"], self.rpi_inputs) + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/filament/", methods=["GET"]) + def get_filament_sensor(self, identifier): + for sensor in self.rpi_inputs: + if identifier == self.to_int(sensor['index_id']): + return jsonify(sensor) + return make_response('', 404) + + @octoprint.plugin.BlueprintPlugin.route("/pwm/", methods=["PATCH"]) + def set_pwm(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'dutyCycle' not in data: + return make_response("missing dutyCycle attribute", 406) + + set_value = self.to_int(data['dutyCycle']) + for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == identifier]: + rpi_output['duty_cycle'] = set_value + rpi_output['new_duty_cycle'] = "" + gpio = self.to_int(rpi_output['gpio_pin']) + self.write_pwm(gpio, set_value) + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/gcode/", methods=["POST"]) + def requested_gcode_command(self, identifier): + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() + self.send_gcode_command(rpi_output['gcode']) + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/neopixel/", methods=["PATCH"]) + def set_neopixel(self, identifier): + """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'red' not in data: + return make_response("missing red attribute", 406) + if 'green' not in data: + return make_response("missing green attribute", 406) + if 'blue' not in data: + return make_response("missing blue attribute", 406) + + red = data['red'] + green = data['green'] + blue = data['blue'] + + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + led_count = rpi_output['neopixel_count'] + led_brightness = rpi_output['neopixel_brightness'] + address = rpi_output['microcontroller_address'] + + neopixel_dirrect = rpi_output['output_type'] == 'neopixel_direct' + + self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, green, + blue, address, neopixel_dirrect, identifier) + + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/rgb-led/", methods=["PATCH"]) + def set_ledstrip_color(self, identifier): + """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'rgb' not in data: + return make_response("missing rgb attribute", 406) + rgb = data['rgb'] + + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + self.ledstrip_set_rgb(rpi_output, rgb) + + return make_response('', 204) + + + + + + """ + DEPRECATION + This API will be deprecated in a future version + """ + + # ~~ Blueprintplugin mixin + @octoprint.plugin.BlueprintPlugin.route("/setEnclosureTempHum", methods=["GET"]) + def set_enclosure_temp_humidity_old(self): + set_value = self.to_float(flask.request.values["set_temperature"]) + index_id = self.to_int(flask.request.values["index_id"]) + + for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == index_id]: + temp_hum_control['temp_ctr_set_value'] = set_value + + self.handle_temp_hum_control() + return flask.jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/clearGPIOMode", methods=["GET"]) + def clear_gpio_mode_old(self): + GPIO.cleanup() + return flask.jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/updateUI", methods=["GET"]) + def update_ui_requested_old(self): + self.update_ui() + return flask.jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/getOutputStatus", methods=["GET"]) + def get_output_status_old(self): + gpio_status = [] + for rpi_output in self.rpi_outputs: + if rpi_output['output_type'] == 'regular': + pin = self.to_int(rpi_output['gpio_pin']) + val = GPIO.input(pin) if not rpi_output['active_low'] else (not GPIO.input(pin)) + index = self.to_int(rpi_output['index_id']) + gpio_status.append(dict(index_id=index, status=val)) + return flask.Response(json.dumps(gpio_status), mimetype='application/json') + + @octoprint.plugin.BlueprintPlugin.route("/setIO", methods=["GET"]) + def set_io_old(self): + index = flask.request.values["index_id"] + value = True if flask.request.values["status"] == 'true' else False + for rpi_output in self.rpi_outputs: + if self.to_int(index) == self.to_int(rpi_output['index_id']): + val = (not value) if rpi_output['active_low'] else value + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + return flask.jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/sendShellCommand", methods=["GET"]) + def send_shell_command_old(self): + output_index = self.to_int(flask.request.values["index_id"]) + + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == output_index].pop() + + command = rpi_output['shell_script'] + self.shell_command(command) + return flask.jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setAutoStartUp", methods=["GET"]) + def set_auto_startup_old(self): + index = flask.request.values["index_id"] + value = True if flask.request.values["status"] == 'true' else False + + if not value: + suffix = 'auto_startup' + queue_id = '{0!s}_{1!s}'.format(index, suffix) + self.stop_queue_item(queue_id) + for output in self.rpi_outputs: + if self.to_int(index) == self.to_int(output['index_id']): + output['auto_startup'] = value + self._logger.info("Setting auto startup for output %s to : %s", index, value) + self._settings.set(["rpi_outputs"], self.rpi_outputs) + return flask.jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setAutoShutdown", methods=["GET"]) + def set_auto_shutdown_old(self): + index = flask.request.values["index_id"] + value = True if flask.request.values["status"] == 'true' else False + + if not value: + suffix = 'auto_shutdown' + queue_id = '{0!s}_{1!s}'.format(index, suffix) + self.stop_queue_item(queue_id) + + for output in self.rpi_outputs: + if self.to_int(index) == self.to_int(output['index_id']): + output['auto_shutdown'] = value + self._logger.info("Setting auto shutdown for output %s to : %s", index, value) + self._settings.set(["rpi_outputs"], self.rpi_outputs) + return flask.jsonify(success=True) + @octoprint.plugin.BlueprintPlugin.route("/setFilamentSensor", methods=["GET"]) - def set_filament_sensor(self): + def set_filament_sensor_old(self): index = flask.request.values["index_id"] value = True if flask.request.values["status"] == 'true' else False for sensor in self.rpi_inputs: @@ -264,9 +469,8 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._settings.set(["rpi_inputs"], self.rpi_inputs) return flask.jsonify(success=True) - ## POST pwm/$id @octoprint.plugin.BlueprintPlugin.route("/setPWM", methods=["GET"]) - def set_pwm(self): + def set_pwm_old(self): set_value = self.to_int(flask.request.values["new_duty_cycle"]) index_id = self.to_int(flask.request.values["index_id"]) for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == index_id]: @@ -276,18 +480,15 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.write_pwm(gpio, set_value) return flask.jsonify(success=True) - ## POST gcode/$id - ## I think id might not even be needed here @octoprint.plugin.BlueprintPlugin.route("/sendGcodeCommand", methods=["GET"]) - def requested_gcode_command(self): + def requested_gcode_command_old(self): gpio_index = self.to_int(flask.request.values["index_id"]) rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == gpio_index].pop() self.send_gcode_command(rpi_output['gcode']) return flask.jsonify(success=True) - ## POST neopixel/$id @octoprint.plugin.BlueprintPlugin.route("/setNeopixel", methods=["GET"]) - def set_neopixel(self): + def set_neopixel_old(self): """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" gpio_index = self.to_int(flask.request.values["index_id"]) red = flask.request.values["red"] @@ -306,9 +507,8 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP return flask.jsonify(success=True) - ## POST rgb-led/$id @octoprint.plugin.BlueprintPlugin.route("/setLedstripColor", methods=["GET"]) - def set_ledstrip_color(self): + def set_ledstrip_color_old(self): """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" gpio_index = self.to_int(flask.request.values["index_id"]) rgb = flask.request.values["rgb"] @@ -318,6 +518,12 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP return flask.jsonify(success=True) + # DEPREACTION END + + + + + def send_neopixel_command(self, led_pin, led_count, led_brightness, red, green, blue, address, neopixel_dirrect, index_id, queue_id=None): """Send neopixel command -- 2.39.5 From 0601d165591fb844976a70fe5e9f8f42e4bcec33 Mon Sep 17 00:00:00 2001 From: UnchartedBull Date: Wed, 9 Oct 2019 17:11:33 +0200 Subject: [PATCH 014/104] rewrote API to be kind of RESTful --- octoprint_enclosure/__init__.py | 247 ++++++++++++++++++-------------- 1 file changed, 142 insertions(+), 105 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index c49f0cf..7dc784d 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -144,46 +144,56 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.rpi_inputs = self._settings.get(["rpi_inputs"]) # ~~ Blueprintplugin mixin - ## POST /temperature/$id - ## maybe PATCH will be the more official way to do. Feedback? - @octoprint.plugin.BlueprintPlugin.route("/setEnclosureTempHum", methods=["GET"]) - def set_enclosure_temp_humidity(self): - set_value = self.to_float(flask.request.values["set_temperature"]) - index_id = self.to_int(flask.request.values["index_id"]) - - for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == index_id]: - temp_hum_control['temp_ctr_set_value'] = set_value - - self.handle_temp_hum_control() - return flask.jsonify(success=True) - - ## GET /temperature/$id might as well also GET /temperature - @octoprint.plugin.BlueprintPlugin.route("/getTemperatureStatus", methods=["GET"]) + @octoprint.plugin.BlueprintPlugin.route("/temperature", methods=["GET"]) def get_temperature_status(self): temperature_status = [] for rpi_input in self.rpi_inputs: if rpi_input['input_type'] == 'temperature_sensor': - temperature = self.to_int(rpi_input['temp_sensor_temp']) - humidity = self.to_int(rpi_input['temp_sensor_humidity']) + temperature = self.to_float(rpi_input['temp_sensor_temp']) + humidity = self.to_float(rpi_input['temp_sensor_humidity']) index = self.to_int(rpi_input['index_id']) label = rpi_input['label'] temperature_status.append(dict(index_id=index, label=label, temperature=temperature, humidity=humidity)) - return flask.Response(json.dumps(temperature_status), mimetype='application/json') + return jsonify(temperature_status) - ## POST /clear - @octoprint.plugin.BlueprintPlugin.route("/clearGPIOMode", methods=["GET"]) + @octoprint.plugin.BlueprintPlugin.route("/temperature/", methods=["GET"]) + def get_single_temperature_status(self, identifier): + for rpi_input in self.rpi_inputs: + if identifier == self.to_int(rpi_input['index_id']): + return jsonify(rpi_input) + return make_response('', 404) + + @octoprint.plugin.BlueprintPlugin.route("/temperature/", methods=["PATCH"]) + def set_enclosure_temp_humidity(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'temperature' not in data: + return make_response("missing temperature attribute", 406) + + set_value = data["temperature"] + + for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == identifier]: + temp_hum_control['temp_ctr_set_value'] = set_value + + self.handle_temp_hum_control() + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/clear-gpio", methods=["POST"]) def clear_gpio_mode(self): GPIO.cleanup() - return flask.jsonify(success=True) + return make_response('', 204) - ## POST /update - @octoprint.plugin.BlueprintPlugin.route("/updateUI", methods=["GET"]) + @octoprint.plugin.BlueprintPlugin.route("/update", methods=["POST"]) def update_ui_requested(self): self.update_ui() - return flask.jsonify(success=True) + return make_response('', 204) - ## GET /output/$id might as well also GET /output - @octoprint.plugin.BlueprintPlugin.route("/getOutputStatus", methods=["GET"]) + @octoprint.plugin.BlueprintPlugin.route("/output", methods=["GET"]) def get_output_status(self): gpio_status = [] for rpi_output in self.rpi_outputs: @@ -192,69 +202,103 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP val = GPIO.input(pin) if not rpi_output['active_low'] else (not GPIO.input(pin)) index = self.to_int(rpi_output['index_id']) gpio_status.append(dict(index_id=index, status=val)) - return flask.Response(json.dumps(gpio_status), mimetype='application/json') + return jsonify(gpio_status) - ## POST /output/$id - ## maybe PATCH will be the more official way to do. Feedback? - @octoprint.plugin.BlueprintPlugin.route("/setIO", methods=["GET"]) - def set_io(self): - index = flask.request.values["index_id"] - value = True if flask.request.values["status"] == 'true' else False + @octoprint.plugin.BlueprintPlugin.route("/output/", methods=["GET"]) + def get_single_output_status(self, identifier): for rpi_output in self.rpi_outputs: - if self.to_int(index) == self.to_int(rpi_output['index_id']): + if identifier == self.to_int(rpi_output['index_id']): + return jsonify(rpi_output) + return make_response('', 404) + + @octoprint.plugin.BlueprintPlugin.route("/output/", methods=["PATCH"]) + def set_io(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): val = (not value) if rpi_output['active_low'] else value self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) - return flask.jsonify(success=True) + return make_response('', 204) - ## POST /shell - @octoprint.plugin.BlueprintPlugin.route("/sendShellCommand", methods=["GET"]) - def send_shell_command(self): - output_index = self.to_int(flask.request.values["index_id"]) - - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == output_index].pop() + @octoprint.plugin.BlueprintPlugin.route("/shell/", methods=["POST"]) + def send_shell_command(self, identifier): + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() command = rpi_output['shell_script'] self.shell_command(command) - return flask.jsonify(success=True) + return make_response('', 204) - ## Possibly include this into POST/PATCH /output/$id and make it into the JSON body? - ## or maybe PATCH/POST /output/$id/auto-startup - @octoprint.plugin.BlueprintPlugin.route("/setAutoStartUp", methods=["GET"]) - def set_auto_startup(self): - index = flask.request.values["index_id"] - value = True if flask.request.values["status"] == 'true' else False + @octoprint.plugin.BlueprintPlugin.route("/output//auto-startup", methods=["PATCH"]) + def set_auto_startup(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] if not value: suffix = 'auto_startup' - queue_id = '{0!s}_{1!s}'.format(index, suffix) + queue_id = '{0!s}_{1!s}'.format(str(identifier), suffix) self.stop_queue_item(queue_id) for output in self.rpi_outputs: - if self.to_int(index) == self.to_int(output['index_id']): + if identifier == self.to_int(output['index_id']): output['auto_startup'] = value - self._logger.info("Setting auto startup for output %s to : %s", index, value) + self._logger.info("Setting auto startup for output %s to : %s", str(identifier), value) self._settings.set(["rpi_outputs"], self.rpi_outputs) - return flask.jsonify(success=True) + return make_response('', 204) - ## same as AutoStartup? - @octoprint.plugin.BlueprintPlugin.route("/setAutoShutdown", methods=["GET"]) - def set_auto_shutdown(self): - index = flask.request.values["index_id"] - value = True if flask.request.values["status"] == 'true' else False + @octoprint.plugin.BlueprintPlugin.route("/output//auto-shutdown", methods=["PATCH"]) + def set_auto_shutdown(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] if not value: suffix = 'auto_shutdown' - queue_id = '{0!s}_{1!s}'.format(index, suffix) + queue_id = '{0!s}_{1!s}'.format(str(identifier), suffix) self.stop_queue_item(queue_id) for output in self.rpi_outputs: - if self.to_int(index) == self.to_int(output['index_id']): + if identifier == self.to_int(output['index_id']): output['auto_shutdown'] = value - self._logger.info("Setting auto shutdown for output %s to : %s", index, value) + self._logger.info("Setting auto shutdown for output %s to : %s", str(identifier), value) self._settings.set(["rpi_outputs"], self.rpi_outputs) - return flask.jsonify(success=True) + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/filament/", methods=["GET"]) + def get_filament_sensor(self, identifier): + for sensor in self.rpi_inputs: + if identifier == self.to_int(sensor['index_id']): + return jsonify(sensor) + return make_response('', 404) + + # TODO: maybe get all filament sensors via GET /filament. What would be they correct type here? - ## POST filament/$id - ## maybe think of a GET for this as well @octoprint.plugin.BlueprintPlugin.route("/filament/", methods=["PATCH"]) def set_filament_sensor(self, identifier): if "application/json" not in request.headers["Content-Type"]: @@ -272,17 +316,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP for sensor in self.rpi_inputs: if identifier == self.to_int(sensor['index_id']): sensor['filament_sensor_enabled'] = value - self._logger.info("Setting filament sensor for input %s to : %s", identifier, value) + self._logger.info("Setting filament sensor for input %s to : %s", str(identifier), value) self._settings.set(["rpi_inputs"], self.rpi_inputs) return make_response('', 204) - @octoprint.plugin.BlueprintPlugin.route("/filament/", methods=["GET"]) - def get_filament_sensor(self, identifier): - for sensor in self.rpi_inputs: - if identifier == self.to_int(sensor['index_id']): - return jsonify(sensor) - return make_response('', 404) - @octoprint.plugin.BlueprintPlugin.route("/pwm/", methods=["PATCH"]) def set_pwm(self, identifier): if "application/json" not in request.headers["Content-Type"]: @@ -375,24 +412,24 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP # ~~ Blueprintplugin mixin @octoprint.plugin.BlueprintPlugin.route("/setEnclosureTempHum", methods=["GET"]) def set_enclosure_temp_humidity_old(self): - set_value = self.to_float(flask.request.values["set_temperature"]) - index_id = self.to_int(flask.request.values["index_id"]) + set_value = self.to_float(request.values["set_temperature"]) + index_id = self.to_int(request.values["index_id"]) for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == index_id]: temp_hum_control['temp_ctr_set_value'] = set_value self.handle_temp_hum_control() - return flask.jsonify(success=True) + return jsonify(success=True) @octoprint.plugin.BlueprintPlugin.route("/clearGPIOMode", methods=["GET"]) def clear_gpio_mode_old(self): GPIO.cleanup() - return flask.jsonify(success=True) + return jsonify(success=True) @octoprint.plugin.BlueprintPlugin.route("/updateUI", methods=["GET"]) def update_ui_requested_old(self): self.update_ui() - return flask.jsonify(success=True) + return jsonify(success=True) @octoprint.plugin.BlueprintPlugin.route("/getOutputStatus", methods=["GET"]) def get_output_status_old(self): @@ -403,32 +440,32 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP val = GPIO.input(pin) if not rpi_output['active_low'] else (not GPIO.input(pin)) index = self.to_int(rpi_output['index_id']) gpio_status.append(dict(index_id=index, status=val)) - return flask.Response(json.dumps(gpio_status), mimetype='application/json') + return Response(json.dumps(gpio_status), mimetype='application/json') @octoprint.plugin.BlueprintPlugin.route("/setIO", methods=["GET"]) def set_io_old(self): - index = flask.request.values["index_id"] - value = True if flask.request.values["status"] == 'true' else False + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False for rpi_output in self.rpi_outputs: if self.to_int(index) == self.to_int(rpi_output['index_id']): val = (not value) if rpi_output['active_low'] else value self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) - return flask.jsonify(success=True) + return jsonify(success=True) @octoprint.plugin.BlueprintPlugin.route("/sendShellCommand", methods=["GET"]) def send_shell_command_old(self): - output_index = self.to_int(flask.request.values["index_id"]) + output_index = self.to_int(request.values["index_id"]) rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == output_index].pop() command = rpi_output['shell_script'] self.shell_command(command) - return flask.jsonify(success=True) + return jsonify(success=True) @octoprint.plugin.BlueprintPlugin.route("/setAutoStartUp", methods=["GET"]) def set_auto_startup_old(self): - index = flask.request.values["index_id"] - value = True if flask.request.values["status"] == 'true' else False + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False if not value: suffix = 'auto_startup' @@ -439,12 +476,12 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP output['auto_startup'] = value self._logger.info("Setting auto startup for output %s to : %s", index, value) self._settings.set(["rpi_outputs"], self.rpi_outputs) - return flask.jsonify(success=True) + return jsonify(success=True) @octoprint.plugin.BlueprintPlugin.route("/setAutoShutdown", methods=["GET"]) def set_auto_shutdown_old(self): - index = flask.request.values["index_id"] - value = True if flask.request.values["status"] == 'true' else False + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False if not value: suffix = 'auto_shutdown' @@ -456,44 +493,44 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP output['auto_shutdown'] = value self._logger.info("Setting auto shutdown for output %s to : %s", index, value) self._settings.set(["rpi_outputs"], self.rpi_outputs) - return flask.jsonify(success=True) + return jsonify(success=True) @octoprint.plugin.BlueprintPlugin.route("/setFilamentSensor", methods=["GET"]) def set_filament_sensor_old(self): - index = flask.request.values["index_id"] - value = True if flask.request.values["status"] == 'true' else False + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False for sensor in self.rpi_inputs: if self.to_int(index) == self.to_int(sensor['index_id']): sensor['filament_sensor_enabled'] = value self._logger.info("Setting filament sensor for input %s to : %s", index, value) self._settings.set(["rpi_inputs"], self.rpi_inputs) - return flask.jsonify(success=True) + return jsonify(success=True) @octoprint.plugin.BlueprintPlugin.route("/setPWM", methods=["GET"]) def set_pwm_old(self): - set_value = self.to_int(flask.request.values["new_duty_cycle"]) - index_id = self.to_int(flask.request.values["index_id"]) + set_value = self.to_int(request.values["new_duty_cycle"]) + index_id = self.to_int(request.values["index_id"]) for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == index_id]: rpi_output['duty_cycle'] = set_value rpi_output['new_duty_cycle'] = "" gpio = self.to_int(rpi_output['gpio_pin']) self.write_pwm(gpio, set_value) - return flask.jsonify(success=True) + return jsonify(success=True) @octoprint.plugin.BlueprintPlugin.route("/sendGcodeCommand", methods=["GET"]) def requested_gcode_command_old(self): - gpio_index = self.to_int(flask.request.values["index_id"]) + gpio_index = self.to_int(request.values["index_id"]) rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == gpio_index].pop() self.send_gcode_command(rpi_output['gcode']) - return flask.jsonify(success=True) + return jsonify(success=True) @octoprint.plugin.BlueprintPlugin.route("/setNeopixel", methods=["GET"]) def set_neopixel_old(self): """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" - gpio_index = self.to_int(flask.request.values["index_id"]) - red = flask.request.values["red"] - green = flask.request.values["green"] - blue = flask.request.values["blue"] + gpio_index = self.to_int(request.values["index_id"]) + red = request.values["red"] + green = request.values["green"] + blue = request.values["blue"] for rpi_output in self.rpi_outputs: if gpio_index == self.to_int(rpi_output['index_id']): led_count = rpi_output['neopixel_count'] @@ -505,18 +542,18 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, green, blue, address, neopixel_dirrect, gpio_index) - return flask.jsonify(success=True) + return jsonify(success=True) @octoprint.plugin.BlueprintPlugin.route("/setLedstripColor", methods=["GET"]) def set_ledstrip_color_old(self): """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" - gpio_index = self.to_int(flask.request.values["index_id"]) - rgb = flask.request.values["rgb"] + gpio_index = self.to_int(request.values["index_id"]) + rgb = request.values["rgb"] for rpi_output in self.rpi_outputs: if gpio_index == self.to_int(rpi_output['index_id']): self.ledstrip_set_rgb(rpi_output, rgb) - return flask.jsonify(success=True) + return jsonify(success=True) # DEPREACTION END @@ -1395,7 +1432,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._logger.info("Error: Too many redirects") except requests.exceptions.RequestException as reqe: self._logger.info("Error: {e}".format(e=reqe)) - if res.status_code != requests.codes.ok: + if res.status_code != requests.codes['ok']: try: j = res.json() except ValueError: -- 2.39.5 From b62235af1581e211b7d960f4920feb826becc354 Mon Sep 17 00:00:00 2001 From: UnchartedBull Date: Wed, 9 Oct 2019 17:17:37 +0200 Subject: [PATCH 015/104] remove flask import --- octoprint_enclosure/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 7dc784d..50ae142 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -6,7 +6,6 @@ from subprocess import Popen, PIPE from .ledstrip import LEDStrip import octoprint.plugin import RPi.GPIO as GPIO -import flask from flask import jsonify, request, make_response, Response from werkzeug.exceptions import BadRequest import time -- 2.39.5 From 11897cdbbc5770c7bb4b875a3d4d2f12e9bc69fd Mon Sep 17 00:00:00 2001 From: UnchartedBull Date: Wed, 9 Oct 2019 22:28:17 +0200 Subject: [PATCH 016/104] Limit temperature outputs --- octoprint_enclosure/__init__.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 50ae142..3262fde 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -152,14 +152,25 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP humidity = self.to_float(rpi_input['temp_sensor_humidity']) index = self.to_int(rpi_input['index_id']) label = rpi_input['label'] - temperature_status.append(dict(index_id=index, label=label, temperature=temperature, humidity=humidity)) + temperature_status.append({'index_id': index, 'label': label, 'temperature': temperature, 'humidity': humidity}) return jsonify(temperature_status) @octoprint.plugin.BlueprintPlugin.route("/temperature/", methods=["GET"]) def get_single_temperature_status(self, identifier): for rpi_input in self.rpi_inputs: if identifier == self.to_int(rpi_input['index_id']): - return jsonify(rpi_input) + return jsonify({ + 'indexId': rpi_input['index_id'], + 'gpioPin': rpi_input['gpio_pin'], + 'type': rpi_input['temp_sensor_type'], + 'inputType': rpi_input['input_type'], + 'label': rpi_input['label'], + 'address': rpi_input['temp_sensor_address'], + 'humidity': self.to_float(rpi_input['temp_sensor_humidity']), + 'temperature': self.to_float(rpi_input['temp_sensor_temp']), + 'useFahrenheit': rpi_input['use_fahrenheit'], + 'showNavbar': rpi_input['temp_sensor_navbar'] + }) return make_response('', 404) @octoprint.plugin.BlueprintPlugin.route("/temperature/", methods=["PATCH"]) -- 2.39.5 From 6c36d4b924d26c0143b49d3aa9964af04b9da777 Mon Sep 17 00:00:00 2001 From: UnchartedBull Date: Wed, 9 Oct 2019 22:39:41 +0200 Subject: [PATCH 017/104] First idea for new API concept --- octoprint_enclosure/__init__.py | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 3262fde..1d3c76e 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -143,34 +143,18 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.rpi_inputs = self._settings.get(["rpi_inputs"]) # ~~ Blueprintplugin mixin - @octoprint.plugin.BlueprintPlugin.route("/temperature", methods=["GET"]) - def get_temperature_status(self): - temperature_status = [] + @octoprint.plugin.BlueprintPlugin.route("/input", methods=["GET"]) + def get_inputs(self): + inputs = [] for rpi_input in self.rpi_inputs: - if rpi_input['input_type'] == 'temperature_sensor': - temperature = self.to_float(rpi_input['temp_sensor_temp']) - humidity = self.to_float(rpi_input['temp_sensor_humidity']) - index = self.to_int(rpi_input['index_id']) - label = rpi_input['label'] - temperature_status.append({'index_id': index, 'label': label, 'temperature': temperature, 'humidity': humidity}) - return jsonify(temperature_status) + inputs.append(dict(index_id=rpi_input['index_id'], label=rpi_input['label'])) + return jsonify(inputs) - @octoprint.plugin.BlueprintPlugin.route("/temperature/", methods=["GET"]) - def get_single_temperature_status(self, identifier): + @octoprint.plugin.BlueprintPlugin.route("/input/", methods=["GET"]) + def get_input_status(self, identifier): for rpi_input in self.rpi_inputs: if identifier == self.to_int(rpi_input['index_id']): - return jsonify({ - 'indexId': rpi_input['index_id'], - 'gpioPin': rpi_input['gpio_pin'], - 'type': rpi_input['temp_sensor_type'], - 'inputType': rpi_input['input_type'], - 'label': rpi_input['label'], - 'address': rpi_input['temp_sensor_address'], - 'humidity': self.to_float(rpi_input['temp_sensor_humidity']), - 'temperature': self.to_float(rpi_input['temp_sensor_temp']), - 'useFahrenheit': rpi_input['use_fahrenheit'], - 'showNavbar': rpi_input['temp_sensor_navbar'] - }) + return jsonify(rpi_input) return make_response('', 404) @octoprint.plugin.BlueprintPlugin.route("/temperature/", methods=["PATCH"]) -- 2.39.5 From fad2581e016535009477f4bc45af21e9f8971890 Mon Sep 17 00:00:00 2001 From: UnchartedBull Date: Wed, 9 Oct 2019 23:16:01 +0200 Subject: [PATCH 018/104] should work now --- octoprint_enclosure/__init__.py | 99 ++++++++++++++++----------------- 1 file changed, 47 insertions(+), 52 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 1d3c76e..1203cf0 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -143,18 +143,20 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.rpi_inputs = self._settings.get(["rpi_inputs"]) # ~~ Blueprintplugin mixin - @octoprint.plugin.BlueprintPlugin.route("/input", methods=["GET"]) + @octoprint.plugin.BlueprintPlugin.route("/inputs", methods=["GET"]) def get_inputs(self): inputs = [] for rpi_input in self.rpi_inputs: - inputs.append(dict(index_id=rpi_input['index_id'], label=rpi_input['label'])) - return jsonify(inputs) + index = self.to_int(rpi_input['index_id']) + label = rpi_input['label'] + inputs.append(dict(index_id=index, label=label)) + return Response(json.dumps(inputs), mimetype='application/json') - @octoprint.plugin.BlueprintPlugin.route("/input/", methods=["GET"]) + @octoprint.plugin.BlueprintPlugin.route("/inputs/", methods=["GET"]) def get_input_status(self, identifier): for rpi_input in self.rpi_inputs: if identifier == self.to_int(rpi_input['index_id']): - return jsonify(rpi_input) + return Response(json.dumps(rpi_input), mimetype='application/json') return make_response('', 404) @octoprint.plugin.BlueprintPlugin.route("/temperature/", methods=["PATCH"]) @@ -177,32 +179,45 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.handle_temp_hum_control() return make_response('', 204) - @octoprint.plugin.BlueprintPlugin.route("/clear-gpio", methods=["POST"]) - def clear_gpio_mode(self): - GPIO.cleanup() + @octoprint.plugin.BlueprintPlugin.route("/filament/", methods=["PATCH"]) + def set_filament_sensor(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + for sensor in self.rpi_inputs: + if identifier == self.to_int(sensor['index_id']): + sensor['filament_sensor_enabled'] = value + self._logger.info("Setting filament sensor for input %s to : %s", str(identifier), value) + self._settings.set(["rpi_inputs"], self.rpi_inputs) return make_response('', 204) - @octoprint.plugin.BlueprintPlugin.route("/update", methods=["POST"]) - def update_ui_requested(self): - self.update_ui() - return make_response('', 204) - - @octoprint.plugin.BlueprintPlugin.route("/output", methods=["GET"]) - def get_output_status(self): + @octoprint.plugin.BlueprintPlugin.route("/outputs", methods=["GET"]) + def get_outputs(self): gpio_status = [] for rpi_output in self.rpi_outputs: if rpi_output['output_type'] == 'regular': - pin = self.to_int(rpi_output['gpio_pin']) - val = GPIO.input(pin) if not rpi_output['active_low'] else (not GPIO.input(pin)) index = self.to_int(rpi_output['index_id']) - gpio_status.append(dict(index_id=index, status=val)) - return jsonify(gpio_status) + label = rpi_output['label'] + gpio_status.append(dict(index_id=index, label=label)) + return Response(json.dumps(gpio_status), mimetype='application/json') - @octoprint.plugin.BlueprintPlugin.route("/output/", methods=["GET"]) - def get_single_output_status(self, identifier): + @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["GET"]) + def get_output_status(self, identifier): for rpi_output in self.rpi_outputs: if identifier == self.to_int(rpi_output['index_id']): - return jsonify(rpi_output) + out = rpi_output.copy() + pin = self.to_int(rpi_output['gpio_pin']) + out['val'] = GPIO.input(pin) if not rpi_output['active_low'] else (not GPIO.input(pin)) + return Response(json.dumps(rpi_output), mimetype='application/json') return make_response('', 404) @octoprint.plugin.BlueprintPlugin.route("/output/", methods=["PATCH"]) @@ -225,6 +240,16 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) return make_response('', 204) + @octoprint.plugin.BlueprintPlugin.route("/clear-gpio", methods=["POST"]) + def clear_gpio_mode(self): + GPIO.cleanup() + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/update", methods=["POST"]) + def update_ui_requested(self): + self.update_ui() + return make_response('', 204) + @octoprint.plugin.BlueprintPlugin.route("/shell/", methods=["POST"]) def send_shell_command(self, identifier): rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() @@ -284,36 +309,6 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._settings.set(["rpi_outputs"], self.rpi_outputs) return make_response('', 204) - @octoprint.plugin.BlueprintPlugin.route("/filament/", methods=["GET"]) - def get_filament_sensor(self, identifier): - for sensor in self.rpi_inputs: - if identifier == self.to_int(sensor['index_id']): - return jsonify(sensor) - return make_response('', 404) - - # TODO: maybe get all filament sensors via GET /filament. What would be they correct type here? - - @octoprint.plugin.BlueprintPlugin.route("/filament/", methods=["PATCH"]) - def set_filament_sensor(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'status' not in data: - return make_response("missing status attribute", 406) - - value = data["status"] - - for sensor in self.rpi_inputs: - if identifier == self.to_int(sensor['index_id']): - sensor['filament_sensor_enabled'] = value - self._logger.info("Setting filament sensor for input %s to : %s", str(identifier), value) - self._settings.set(["rpi_inputs"], self.rpi_inputs) - return make_response('', 204) - @octoprint.plugin.BlueprintPlugin.route("/pwm/", methods=["PATCH"]) def set_pwm(self, identifier): if "application/json" not in request.headers["Content-Type"]: -- 2.39.5 From f9e32d8e8b5fe02a37cd009468702547c314d9ad Mon Sep 17 00:00:00 2001 From: UnchartedBull Date: Wed, 9 Oct 2019 23:17:21 +0200 Subject: [PATCH 019/104] Rename --- octoprint_enclosure/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 1203cf0..26aa559 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -202,13 +202,13 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP @octoprint.plugin.BlueprintPlugin.route("/outputs", methods=["GET"]) def get_outputs(self): - gpio_status = [] + outputs = [] for rpi_output in self.rpi_outputs: if rpi_output['output_type'] == 'regular': index = self.to_int(rpi_output['index_id']) label = rpi_output['label'] - gpio_status.append(dict(index_id=index, label=label)) - return Response(json.dumps(gpio_status), mimetype='application/json') + outputs.append(dict(index_id=index, label=label)) + return Response(json.dumps(outputs), mimetype='application/json') @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["GET"]) def get_output_status(self, identifier): -- 2.39.5 From c54219fd1af8392dabb48beb8acd307e78fcbb0c Mon Sep 17 00:00:00 2001 From: UnchartedBull Date: Wed, 9 Oct 2019 23:42:14 +0200 Subject: [PATCH 020/104] correct naming plus restructuring --- octoprint_enclosure/__init__.py | 42 ++++++++++++++++----------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 26aa559..80759b9 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -220,7 +220,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP return Response(json.dumps(rpi_output), mimetype='application/json') return make_response('', 404) - @octoprint.plugin.BlueprintPlugin.route("/output/", methods=["PATCH"]) + @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["PATCH"]) def set_io(self, identifier): if "application/json" not in request.headers["Content-Type"]: return make_response("expected json", 400) @@ -240,25 +240,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) return make_response('', 204) - @octoprint.plugin.BlueprintPlugin.route("/clear-gpio", methods=["POST"]) - def clear_gpio_mode(self): - GPIO.cleanup() - return make_response('', 204) - - @octoprint.plugin.BlueprintPlugin.route("/update", methods=["POST"]) - def update_ui_requested(self): - self.update_ui() - return make_response('', 204) - - @octoprint.plugin.BlueprintPlugin.route("/shell/", methods=["POST"]) - def send_shell_command(self, identifier): - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() - - command = rpi_output['shell_script'] - self.shell_command(command) - return make_response('', 204) - - @octoprint.plugin.BlueprintPlugin.route("/output//auto-startup", methods=["PATCH"]) + @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-startup", methods=["PATCH"]) def set_auto_startup(self, identifier): if "application/json" not in request.headers["Content-Type"]: return make_response("expected json", 400) @@ -283,7 +265,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._settings.set(["rpi_outputs"], self.rpi_outputs) return make_response('', 204) - @octoprint.plugin.BlueprintPlugin.route("/output//auto-shutdown", methods=["PATCH"]) + @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-shutdown", methods=["PATCH"]) def set_auto_shutdown(self, identifier): if "application/json" not in request.headers["Content-Type"]: return make_response("expected json", 400) @@ -309,6 +291,24 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._settings.set(["rpi_outputs"], self.rpi_outputs) return make_response('', 204) + @octoprint.plugin.BlueprintPlugin.route("/clear-gpio", methods=["POST"]) + def clear_gpio_mode(self): + GPIO.cleanup() + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/update", methods=["POST"]) + def update_ui_requested(self): + self.update_ui() + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/shell/", methods=["POST"]) + def send_shell_command(self, identifier): + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() + + command = rpi_output['shell_script'] + self.shell_command(command) + return make_response('', 204) + @octoprint.plugin.BlueprintPlugin.route("/pwm/", methods=["PATCH"]) def set_pwm(self, identifier): if "application/json" not in request.headers["Content-Type"]: -- 2.39.5 From a9ef64eb23c379641d8a11a2cc8beb31e199d17d Mon Sep 17 00:00:00 2001 From: StefanCohen <33824565+StefanCohen@users.noreply.github.com> Date: Thu, 10 Oct 2019 00:40:43 +0200 Subject: [PATCH 021/104] First version of the new API documentation The new REST API documentation --- API.md | 169 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 API.md diff --git a/API.md b/API.md new file mode 100644 index 0000000..4daf5ed --- /dev/null +++ b/API.md @@ -0,0 +1,169 @@ +# API Reference v2.0 + +## List all Inputs. + +Method: GET + +http:///plugin/enclosure/inputs?apikey= + +Response: + +``` +[ + { + "index_id": 1, + "label": "Input 1" + } +] +``` + + +## List a specific input. + +Method: GET + +http:///plugin/enclosure/inputs/1?apikey= + +Response: +``` +{ + "controlled_io": null, + "filament_sensor_timeout": 120, + "filament_sensor_enabled": true, + "temp_sensor_address": "", + "printer_action": "filament", + "controlled_io_set_value": "low", + "temp_sensor_type": "11", + "temp_sensor_navbar": true, + "temp_sensor_humidity": 19, + "edge": "fall", + "ds18b20_serial": "", + "action_type": "output_control", + "input_pull_resistor": "input_pull_up", + "input_type": "temperature_sensor", + "temp_sensor_temp": 33, + "label": "Input 1", + "index_id": 1, + "use_fahrenheit": false, + "gpio_pin": "4" +} +``` + +## List all outputs + +Method: GET + +http:///plugin/enclosure/outputs?apikey= + +Response: +``` +[ + { + "index_id": 1, + "label": "Ouput 1" + } +] +``` + +## List a specific output + +Method: GET + +http:///plugin/enclosure/outputs/1?apikey= + +Response: +``` +{ + "linked_temp_sensor": "", + "ledstrip_gpio_dat": "", + "startup_time": 0, + "temp_ctr_deadband": 0, + "neopixel_brightness": 255, + "new_duty_cycle": "", + "gpio_pin": 0, + "default_duty_cycle": 0, + "neopixel_color": "rgb(0,0,0)", + "hide_btn_ui": false, + "temp_ctr_set_value": 0, + "temp_ctr_default_value": 0, + "default_neopixel_color": "", + "controlled_io_set_value": "Low", + "auto_shutdown": false, + "shell_script": "", + "label": "Ouput 1", + "default_ledstrip_color": "", + "duty_a": 0, + "toggle_timer_off": 0, + "alarm_set_temp": 0, + "ledstrip_gpio_clk": "", + "auto_startup": false, + "controlled_io": 0, + "shutdown_time": 0, + "temp_ctr_type": "heater", + "gcode": "M117 Test", + "shutdown_on_failed": false, + "temperature_b": 0, + "ledstrip_color": "rgb(0,0,0)", + "temperature_a": 0, + "neopixel_count": 0, + "duty_cycle": 0, + "toggle_timer_on": 0, + "show_on_navbar": false, + "duty_b": 0, + "toggle_timer": false, + "pwm_status": 50, + "gpio_status": false, + "pwm_frequency": 50, + "new_ledstrip_color": "", + "startup_with_server": true, + "active_low": true, + "temp_ctr_max_temp": 0, + "pwm_temperature_linked": false, + "temp_ctr_new_set_value": "", + "output_type": "regular", + "microcontroller_address": 0, + "index_id": 1, + "new_neopixel_color": "" +} +``` + +## Enable/Disable Output: + +http:///plugin/enclosure/outputs/1?apikey= + +Method: PATCH +Content-Type: application/json +Body: { "status": boolean } + +example: +``` +{ "status": true } +``` + + +## Enable/Disable Output auto-shutdown: + +http:///plugin/enclosure/outputs/1/auto-shutdown?apikey= + +Method: PATCH +Content-Type: application/json +Body: { "status": boolean } + +example: +``` +{ "status": true } +``` + + +## Enable/Disable Output auto-shutdown: + +http:///plugin/enclosure/outputs/1/auto-startup?apikey= + +Method: PATCH +Content-Type: application/json +Body: { "status": boolean } + +example: +``` +{ "status": true } +``` -- 2.39.5 From 893fb11463db940b137b84ae48b9213a22c4b185 Mon Sep 17 00:00:00 2001 From: UnchartedBull Date: Thu, 10 Oct 2019 15:29:48 +0200 Subject: [PATCH 022/104] Update docs --- API.md | 389 +++++++++++++++++++++++--------- octoprint_enclosure/__init__.py | 105 +++++---- 2 files changed, 347 insertions(+), 147 deletions(-) diff --git a/API.md b/API.md index 4daf5ed..e5ce300 100644 --- a/API.md +++ b/API.md @@ -1,169 +1,342 @@ # API Reference v2.0 -## List all Inputs. +The API is located at \/plugin/enclosure. This needs to be added before each endpoint in order for the API to function properly. The API either returns a `application/json` body or an empty body for a successful request. -Method: GET +A failed request will return an error code as well as a short error description. -http:///plugin/enclosure/inputs?apikey= +## List all inputs -Response: +Endpoint: **GET** `/inputs` + +Response (200): ``` [ { - "index_id": 1, - "label": "Input 1" + "index_id": number, + "label": string } ] ``` +Error Responses: + - none -## List a specific input. +## List a specific input -Method: GET +Endpoint: **GET** `/inputs/` -http:///plugin/enclosure/inputs/1?apikey= +*Note: id needs to be int (index_id)* -Response: +Response (200): ``` { "controlled_io": null, - "filament_sensor_timeout": 120, - "filament_sensor_enabled": true, - "temp_sensor_address": "", - "printer_action": "filament", - "controlled_io_set_value": "low", - "temp_sensor_type": "11", - "temp_sensor_navbar": true, - "temp_sensor_humidity": 19, - "edge": "fall", - "ds18b20_serial": "", - "action_type": "output_control", - "input_pull_resistor": "input_pull_up", - "input_type": "temperature_sensor", - "temp_sensor_temp": 33, - "label": "Input 1", - "index_id": 1, - "use_fahrenheit": false, - "gpio_pin": "4" + "filament_sensor_timeout": number, + "filament_sensor_enabled": boolean, + "temp_sensor_address": string, + "printer_action": string, + "controlled_io_set_value": string, + "temp_sensor_type": string, + "temp_sensor_navbar": boolean, + "temp_sensor_humidity": number, + "edge": string, + "ds18b20_serial": string, + "action_type": string, + "input_pull_resistor": string, + "input_type": string, + "temp_sensor_temp": number, + "label": string, + "index_id": number, + "use_fahrenheit": boolean, + "gpio_pin": string } ``` +Error Responses: + - 404 if specified id cannot be found + ## List all outputs -Method: GET +Endpoint: **GET** `/outputs` -http:///plugin/enclosure/outputs?apikey= - -Response: +Response (200): ``` [ { - "index_id": 1, - "label": "Ouput 1" + "index_id": number, + "label": string } ] ``` +Error Responses: + - none + ## List a specific output -Method: GET +Endpoint: **GET** `/outputs/` -http:///plugin/enclosure/outputs/1?apikey= +*Note: id needs to be int (index_id)* -Response: + +Response (200): ``` { - "linked_temp_sensor": "", - "ledstrip_gpio_dat": "", - "startup_time": 0, - "temp_ctr_deadband": 0, - "neopixel_brightness": 255, - "new_duty_cycle": "", - "gpio_pin": 0, - "default_duty_cycle": 0, - "neopixel_color": "rgb(0,0,0)", - "hide_btn_ui": false, - "temp_ctr_set_value": 0, - "temp_ctr_default_value": 0, - "default_neopixel_color": "", - "controlled_io_set_value": "Low", - "auto_shutdown": false, - "shell_script": "", - "label": "Ouput 1", - "default_ledstrip_color": "", - "duty_a": 0, - "toggle_timer_off": 0, - "alarm_set_temp": 0, - "ledstrip_gpio_clk": "", - "auto_startup": false, - "controlled_io": 0, - "shutdown_time": 0, - "temp_ctr_type": "heater", - "gcode": "M117 Test", - "shutdown_on_failed": false, - "temperature_b": 0, - "ledstrip_color": "rgb(0,0,0)", - "temperature_a": 0, - "neopixel_count": 0, - "duty_cycle": 0, - "toggle_timer_on": 0, - "show_on_navbar": false, - "duty_b": 0, - "toggle_timer": false, - "pwm_status": 50, - "gpio_status": false, - "pwm_frequency": 50, - "new_ledstrip_color": "", - "startup_with_server": true, - "active_low": true, - "temp_ctr_max_temp": 0, - "pwm_temperature_linked": false, - "temp_ctr_new_set_value": "", - "output_type": "regular", - "microcontroller_address": 0, - "index_id": 1, - "new_neopixel_color": "" + "linked_temp_sensor": string, + "ledstrip_gpio_dat": string, + "startup_time": number, + "temp_ctr_deadband": number, + "neopixel_brightness": number, + "new_duty_cycle": string, + "gpio_pin": number, + "default_duty_cycle": number, + "neopixel_color": string, + "hide_btn_ui": boolean, + "temp_ctr_set_value": number, + "temp_ctr_default_value": number, + "default_neopixel_color": string, + "controlled_io_set_value": string, + "auto_shutdown": boolean, + "shell_script": string, + "label": string, + "default_ledstrip_color": string, + "duty_a": number, + "toggle_timer_off": number, + "alarm_set_temp": number, + "ledstrip_gpio_clk": string, + "auto_startup": boolean, + "controlled_io": number, + "shutdown_time": number, + "temp_ctr_type": string, + "gcode": string, + "shutdown_on_failed": boolean, + "temperature_b": number, + "ledstrip_color": string, + "temperature_a": number, + "neopixel_count": number, + "duty_cycle": number, + "toggle_timer_on": number, + "show_on_navbar": boolean, + "duty_b": number, + "toggle_timer": boolean, + "pwm_status": number, + "gpio_status": boolean, + "pwm_frequency": number, + "new_ledstrip_color": string, + "startup_with_server": boolean, + "active_low": boolean, + "temp_ctr_max_temp": number, + "pwm_temperature_linked": boolean, + "temp_ctr_new_set_value": string, + "output_type": string, + "microcontroller_address": number, + "index_id": number, + "new_neopixel_color": string } ``` -## Enable/Disable Output: +Error Responses: + - 404 if specified id cannot be found -http:///plugin/enclosure/outputs/1?apikey= +## Control specific output -Method: PATCH -Content-Type: application/json -Body: { "status": boolean } +Endpoint: **PATCH** `/outputs/` -example: +*Note: id needs to be int (index_id)* + +Body (Content-Type: `application/json`): ``` -{ "status": true } +{ + "status": boolean +} ``` +Response (204): No-Content -## Enable/Disable Output auto-shutdown: +Error Responses: +- 400 - wrong Content-Type or malformed request +- 406 - missing information (missing attribute given in response body) -http:///plugin/enclosure/outputs/1/auto-shutdown?apikey= +## Enable/Disable Output auto-startup -Method: PATCH -Content-Type: application/json -Body: { "status": boolean } +Endpoint: **PATCH** `/outputs//auto-startup` -example: +*Note: id needs to be int (index_id)* + +Body (Content-Type: `application/json`): ``` -{ "status": true } +{ + "status": boolean +} ``` +Response (204): No-Content -## Enable/Disable Output auto-shutdown: +Error Responses: +- 400 - wrong Content-Type or malformed request +- 406 - missing information (missing attribute given in response body) -http:///plugin/enclosure/outputs/1/auto-startup?apikey= +## Control auto-shutdown for specific output -Method: PATCH -Content-Type: application/json -Body: { "status": boolean } +Endpoint: **PATCH** `/outputs//auto-shutdown` -example: +*Note: id needs to be int (index_id)* + +Body (Content-Type: `application/json`): ``` -{ "status": true } +{ + "status": boolean +} ``` + +Response (204): No-Content + +Error Responses: +- 400 - wrong Content-Type or malformed request +- 406 - missing information (missing attribute given in response body) + +## Control temperature + +Endpoint: **PATCH** `/temperature/` + +*Note: id needs to be int (index_id)* + +Body (Content-Type: `application/json`): +``` +{ + "temperature": number +} +``` + +Response (204): No-Content + +Error Responses: +- 400 - wrong Content-Type or malformed request +- 406 - missing information (missing attribute given in response body) + +## Control filament sensor + +Endpoint: **PATCH** `/filament/` + +*Note: id needs to be int (index_id)* + +Body (Content-Type: `application/json`): +``` +{ + "status": boolean +} +``` + +Response (204): No-Content + +Error Responses: +- 400 - wrong Content-Type or malformed request +- 406 - missing information (missing attribute given in response body) + +## Set PWM value + +Endpoint: **PATCH** `/pwm/` + +*Note: id needs to be int (index_id)* + +Body (Content-Type: `application/json`): +``` +{ + "duty_cycle": number +} +``` + +Response (204): No-Content + +Error Responses: +- 400 - wrong Content-Type or malformed request +- 406 - missing information (missing attribute given in response body) + +## Set RGB LED color + +Endpoint: **PATCH** `/rgb-led/` + +*Note: id needs to be int (index_id)* + +Body (Content-Type: `application/json`): +``` +{ + "rgb": string (rgb(r,g,b)) +} +``` + +Response (204): No-Content + +Error Responses: +- 400 - wrong Content-Type or malformed request +- 406 - missing information (missing attribute given in response body) + +## Set neopixel color + +Endpoint: **PATCH** `/neopixel/` + +*Note: id needs to be int (index_id)* + +Body (Content-Type: `application/json`): +``` +{ + "red": number, + "green": number, + "blue": number +} +``` + +Response (204): No-Content + +Error Responses: +- 400 - wrong Content-Type or malformed request +- 406 - missing information (missing attribute given in response body) + +## Clear GPIO Pins + +Endpoint: **POST** `/clear-gpio` + +Body: empty + +Response (204): No-Content + +Error Responses: +- none + +## Update UI + +Endpoint: **POST** `/update` + +Body: empty + +Response (204): No-Content + +Error Responses: +- none + +## Send shell command + +Endpoint: **POST** `/shell/` + +*Note: id needs to be int (index_id)* + +Body: empty + +Response (204): No-Content + +Error Responses: +- none + +## Send gcode command + +Endpoint: **POST** `/gcode/` + +*Note: id needs to be int (index_id)* + +Body: empty + +Response (204): No-Content + +Error Responses: +- none \ No newline at end of file diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 80759b9..ebcf5b8 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -7,6 +7,7 @@ from .ledstrip import LEDStrip import octoprint.plugin import RPi.GPIO as GPIO from flask import jsonify, request, make_response, Response +from octoprint.server.util.flask import restricted_access from werkzeug.exceptions import BadRequest import time import sys @@ -152,6 +153,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP inputs.append(dict(index_id=index, label=label)) return Response(json.dumps(inputs), mimetype='application/json') + @octoprint.plugin.BlueprintPlugin.route("/inputs/", methods=["GET"]) def get_input_status(self, identifier): for rpi_input in self.rpi_inputs: @@ -159,7 +161,9 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP return Response(json.dumps(rpi_input), mimetype='application/json') return make_response('', 404) + @octoprint.plugin.BlueprintPlugin.route("/temperature/", methods=["PATCH"]) + @restricted_access def set_enclosure_temp_humidity(self, identifier): if "application/json" not in request.headers["Content-Type"]: return make_response("expected json", 400) @@ -179,7 +183,9 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.handle_temp_hum_control() return make_response('', 204) + @octoprint.plugin.BlueprintPlugin.route("/filament/", methods=["PATCH"]) + @restricted_access def set_filament_sensor(self, identifier): if "application/json" not in request.headers["Content-Type"]: return make_response("expected json", 400) @@ -200,6 +206,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._settings.set(["rpi_inputs"], self.rpi_inputs) return make_response('', 204) + @octoprint.plugin.BlueprintPlugin.route("/outputs", methods=["GET"]) def get_outputs(self): outputs = [] @@ -210,6 +217,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP outputs.append(dict(index_id=index, label=label)) return Response(json.dumps(outputs), mimetype='application/json') + @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["GET"]) def get_output_status(self, identifier): for rpi_output in self.rpi_outputs: @@ -220,7 +228,9 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP return Response(json.dumps(rpi_output), mimetype='application/json') return make_response('', 404) + @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["PATCH"]) + @restricted_access def set_io(self, identifier): if "application/json" not in request.headers["Content-Type"]: return make_response("expected json", 400) @@ -240,7 +250,9 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) return make_response('', 204) + @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-startup", methods=["PATCH"]) + @restricted_access def set_auto_startup(self, identifier): if "application/json" not in request.headers["Content-Type"]: return make_response("expected json", 400) @@ -265,7 +277,9 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._settings.set(["rpi_outputs"], self.rpi_outputs) return make_response('', 204) + @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-shutdown", methods=["PATCH"]) + @restricted_access def set_auto_shutdown(self, identifier): if "application/json" not in request.headers["Content-Type"]: return make_response("expected json", 400) @@ -291,25 +305,9 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._settings.set(["rpi_outputs"], self.rpi_outputs) return make_response('', 204) - @octoprint.plugin.BlueprintPlugin.route("/clear-gpio", methods=["POST"]) - def clear_gpio_mode(self): - GPIO.cleanup() - return make_response('', 204) - - @octoprint.plugin.BlueprintPlugin.route("/update", methods=["POST"]) - def update_ui_requested(self): - self.update_ui() - return make_response('', 204) - - @octoprint.plugin.BlueprintPlugin.route("/shell/", methods=["POST"]) - def send_shell_command(self, identifier): - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() - - command = rpi_output['shell_script'] - self.shell_command(command) - return make_response('', 204) @octoprint.plugin.BlueprintPlugin.route("/pwm/", methods=["PATCH"]) + @restricted_access def set_pwm(self, identifier): if "application/json" not in request.headers["Content-Type"]: return make_response("expected json", 400) @@ -318,10 +316,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP except BadRequest: return make_response("malformed request", 400) - if 'dutyCycle' not in data: - return make_response("missing dutyCycle attribute", 406) + if 'duty_cycle' not in data: + return make_response("missing duty_cycle attribute", 406) - set_value = self.to_int(data['dutyCycle']) + set_value = self.to_int(data['duty_cycle']) for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == identifier]: rpi_output['duty_cycle'] = set_value rpi_output['new_duty_cycle'] = "" @@ -329,13 +327,30 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.write_pwm(gpio, set_value) return make_response('', 204) - @octoprint.plugin.BlueprintPlugin.route("/gcode/", methods=["POST"]) - def requested_gcode_command(self, identifier): - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() - self.send_gcode_command(rpi_output['gcode']) + @octoprint.plugin.BlueprintPlugin.route("/rgb-led/", methods=["PATCH"]) + @restricted_access + def set_ledstrip_color(self, identifier): + """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'rgb' not in data: + return make_response("missing rgb attribute", 406) + rgb = data['rgb'] + + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + self.ledstrip_set_rgb(rpi_output, rgb) + return make_response('', 204) + @octoprint.plugin.BlueprintPlugin.route("/neopixel/", methods=["PATCH"]) + @restricted_access def set_neopixel(self, identifier): """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" if "application/json" not in request.headers["Content-Type"]: @@ -369,24 +384,36 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP return make_response('', 204) - @octoprint.plugin.BlueprintPlugin.route("/rgb-led/", methods=["PATCH"]) - def set_ledstrip_color(self, identifier): - """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - if 'rgb' not in data: - return make_response("missing rgb attribute", 406) - rgb = data['rgb'] + @octoprint.plugin.BlueprintPlugin.route("/clear-gpio", methods=["POST"]) + @restricted_access + def clear_gpio_mode(self): + GPIO.cleanup() + return make_response('', 204) - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['index_id']): - self.ledstrip_set_rgb(rpi_output, rgb) + @octoprint.plugin.BlueprintPlugin.route("/update", methods=["POST"]) + @restricted_access + def update_ui_requested(self): + self.update_ui() + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/shell/", methods=["POST"]) + @restricted_access + def send_shell_command(self, identifier): + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() + + command = rpi_output['shell_script'] + self.shell_command(command) + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/gcode/", methods=["POST"]) + @restricted_access + def requested_gcode_command(self, identifier): + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() + self.send_gcode_command(rpi_output['gcode']) return make_response('', 204) -- 2.39.5 From 046df7a51dcca706b1ea145dd7d85c40cc964a41 Mon Sep 17 00:00:00 2001 From: UnchartedBull Date: Thu, 10 Oct 2019 17:01:15 +0200 Subject: [PATCH 023/104] Fix output value not returned --- API.md | 1 + octoprint_enclosure/__init__.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/API.md b/API.md index e5ce300..dbee16d 100644 --- a/API.md +++ b/API.md @@ -110,6 +110,7 @@ Response (200): "shutdown_time": number, "temp_ctr_type": string, "gcode": string, + "current_value": boolean, "shutdown_on_failed": boolean, "temperature_b": number, "ledstrip_color": string, diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index ebcf5b8..129043d 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -20,6 +20,7 @@ import requests import inspect import threading import json +import copy class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SettingsPlugin, @@ -222,10 +223,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP def get_output_status(self, identifier): for rpi_output in self.rpi_outputs: if identifier == self.to_int(rpi_output['index_id']): - out = rpi_output.copy() + out = copy.deepcopy(rpi_output) pin = self.to_int(rpi_output['gpio_pin']) - out['val'] = GPIO.input(pin) if not rpi_output['active_low'] else (not GPIO.input(pin)) - return Response(json.dumps(rpi_output), mimetype='application/json') + out['current_value'] = GPIO.input(pin) if not rpi_output['active_low'] else (not GPIO.input(pin)) + return Response(json.dumps(out), mimetype='application/json') return make_response('', 404) -- 2.39.5 From 8d492ab378f366991c01c73f52b1840202129b93 Mon Sep 17 00:00:00 2001 From: UnchartedBull Date: Tue, 26 Nov 2019 16:31:24 +0100 Subject: [PATCH 024/104] Update inputs as well --- octoprint_enclosure/__init__.py | 2 ++ setup.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index e128788..f74aff4 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -637,6 +637,8 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP if self._settings.get(["debug_temperature_log"]) is True: self._logger.debug("Sensor %s Temperature: %s humidity %s", sensor['label'], temp, hum) if temp is not None and hum is not None: + sensor["temp_sensor_temp"] = temp + sensor["temp_sensor_humidity"] = hum sensor_data.append(dict(index_id=sensor['index_id'], temperature=temp, humidity=hum)) self.temperature_sensor_data = sensor_data self.handle_temp_hum_control() diff --git a/setup.py b/setup.py index 71166b0..3dd24a9 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ plugin_package = "octoprint_enclosure" plugin_name = "OctoPrint-Enclosure" # The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module -plugin_version = "4.13" +plugin_version = "4.13.1" # The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin # module -- 2.39.5 From 67e980e9f7dc3ad42a9df695d40f800ca61605cf Mon Sep 17 00:00:00 2001 From: Benjamin Salchow Date: Sun, 15 Mar 2020 13:15:38 +0100 Subject: [PATCH 025/104] adds python 3.7 compatibility --- octoprint_enclosure/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index f74aff4..863e038 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -1857,6 +1857,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP __plugin_name__ = "Enclosure Plugin" +__plugin_pythoncompat__ = ">=2.7,<4" def __plugin_load__(): -- 2.39.5 From 960cf5b300784d26b170cd649a75a0239b1d8309 Mon Sep 17 00:00:00 2001 From: Charles Miller Date: Mon, 20 Apr 2020 15:12:39 +0100 Subject: [PATCH 026/104] Add AM2320.py Adds support and UI options for the AM2320 i2c sensor --- octoprint_enclosure/AM2320.py | 84 +++++++++++++++++++ octoprint_enclosure/__init__.py | 23 +++++ octoprint_enclosure/static/js/enclosure.js | 2 +- .../templates/enclosure_settings.jinja2 | 5 +- 4 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 octoprint_enclosure/AM2320.py diff --git a/octoprint_enclosure/AM2320.py b/octoprint_enclosure/AM2320.py new file mode 100644 index 0000000..67eff87 --- /dev/null +++ b/octoprint_enclosure/AM2320.py @@ -0,0 +1,84 @@ +import smbus +import time + +try: + import struct +except ImportError: + import ustruct as struct + +class AM2320Exception(Exception): + """ Base class for exception """ + +class AM2320DeviceNotFound(AM2320Exception, ValueError): + """ Device not found """ + +class AM2320ReadError(AM2320Exception, RuntimeError): + """ Read error or CRC mismatch """ + +def _crc16(data): + crc = 0xFFFF + for byte in data: + crc ^= byte + for _ in range(8): + if crc & 0x0001: + crc >>= 1 + crc ^= 0xA001 + else: + crc >>= 1 + return crc + + +sensor = smbus.SMBus(1) + +def getTemp(bus): + for _ in range(3): + try: + bus.write_byte(0x5C, 0x00) + except: + pass + #raise AM2320DeviceNotFound("Sensor not found") + + bus.write_i2c_block_data(0x5C, 0x03, [0x02, 2]) + time.sleep(0.001) + result = bytearray(bus.read_i2c_block_data(0x5C, 0x00, 6)) + if result[0] != 0x3 or result[1] != 2: + raise AM2320ReadError("Command does not match returned data") + temp = struct.unpack(">H", result[2:-2])[0] + crc1 = struct.unpack("= 32768: + temp = 32768 - temp + return (temp / 10.0) + +def getHumi(bus): + for _ in range(3): + try: + bus.write_byte(0x5C, 0x00) + except: + pass + + bus.write_i2c_block_data(0x5C, 0x03, [0x00, 2]) + time.sleep(0.001) + result = bytearray(bus.read_i2c_block_data(0x5C, 0x00, 6)) + if result[0] != 0x3 or result[1] != 2: + raise AM2320ReadError("Command does not match returned data") + humi = struct.unpack(">H", result[2:-2])[0] + crc1 = struct.unpack("= 0){ + if (['11', '22', '2302', 'bme280', 'am2320', 'si7021'].indexOf(sensor) >= 0){ return true; } return false; diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index 29bdc5f..45a2b8a 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -536,6 +536,7 @@ + @@ -563,7 +564,7 @@
- +
@@ -605,7 +606,7 @@
- +
-- 2.39.5 From cf3934c2feaa5e66b4a0ef4492bcb4853df57f60 Mon Sep 17 00:00:00 2001 From: Charles Miller Date: Mon, 20 Apr 2020 15:14:53 +0100 Subject: [PATCH 027/104] formatting: AM2320.py --- octoprint_enclosure/AM2320.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/octoprint_enclosure/AM2320.py b/octoprint_enclosure/AM2320.py index 67eff87..245bf01 100644 --- a/octoprint_enclosure/AM2320.py +++ b/octoprint_enclosure/AM2320.py @@ -81,4 +81,4 @@ def main(): print('-1 | -1') if __name__ == "__main__": - main() \ No newline at end of file + main() -- 2.39.5 From 72faf58dfa7688981842cb7a5f5471a7ce6e9a25 Mon Sep 17 00:00:00 2001 From: Charles Miller Date: Mon, 20 Apr 2020 15:17:57 +0100 Subject: [PATCH 028/104] update AM2320.py --- octoprint_enclosure/AM2320.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/octoprint_enclosure/AM2320.py b/octoprint_enclosure/AM2320.py index 245bf01..d55811e 100644 --- a/octoprint_enclosure/AM2320.py +++ b/octoprint_enclosure/AM2320.py @@ -36,7 +36,6 @@ def getTemp(bus): bus.write_byte(0x5C, 0x00) except: pass - #raise AM2320DeviceNotFound("Sensor not found") bus.write_i2c_block_data(0x5C, 0x03, [0x02, 2]) time.sleep(0.001) @@ -69,7 +68,6 @@ def getHumi(bus): crc2 = _crc16(result[0:-2]) if crc1 != crc2: raise AM2320ReadError("CRC Mismatch") - return return (humi / 10.0) def main(): -- 2.39.5 From d6e6de594640287e02db0be58ebfedd58a1ef3fd Mon Sep 17 00:00:00 2001 From: Piranit Date: Sat, 25 Apr 2020 15:30:03 +0300 Subject: [PATCH 029/104] forgotten parameter 'LED_BRIGHTNESS' for initialize Adafruit_NeoPixel added --- octoprint_enclosure/neopixel_direct.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/octoprint_enclosure/neopixel_direct.py b/octoprint_enclosure/neopixel_direct.py index 3af63f9..6259377 100644 --- a/octoprint_enclosure/neopixel_direct.py +++ b/octoprint_enclosure/neopixel_direct.py @@ -17,7 +17,7 @@ else: print("fail") sys.exit(1) -strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT) +strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS) strip.begin() color = Color(red, green, blue) -- 2.39.5 From cccf74bcf8c2a8fb5f5748186db6869f945d05d2 Mon Sep 17 00:00:00 2001 From: Bryan Mayland Date: Tue, 12 May 2020 15:32:16 +0100 Subject: [PATCH 030/104] Do not uppercase user-supplied gcode --- octoprint_enclosure/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index f01f06c..6aff8a1 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -100,7 +100,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP for command in command_string.split(' '): index = command.upper().find(gcode.upper()) if not index == -1: - return command.replace(gcode, '').upper() + return command.replace(gcode, '') return -1 # ~~ StartupPlugin mixin @@ -1261,8 +1261,8 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP item['time'] = time_now for line in self._settings.get(["filament_sensor_gcode"]).split('\n'): if line: - self._printer.commands(line.strip().upper()) - self._logger.info("Sending GCODE command: %s", line.strip().upper()) + self._printer.commands(line.strip()) + self._logger.info("Sending GCODE command: %s", line.strip()) time.sleep(0.2) for notification in self.notifications: if notification['filamentChange']: @@ -1378,8 +1378,8 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP def send_gcode_command(self, command): for line in command.split('\n'): if line: - self._printer.commands(line.strip().upper()) - self._logger.info("Sending GCODE command: %s", line.strip().upper()) + self._printer.commands(line.strip()) + self._logger.info("Sending GCODE command: %s", line.strip()) time.sleep(0.2) def handle_printer_action(self, channel): @@ -1890,4 +1890,4 @@ def __plugin_load__(): __plugin_hooks__ = { "octoprint.comm.protocol.gcode.queuing" : __plugin_implementation__.hook_gcode_queuing, "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information - } \ No newline at end of file + } -- 2.39.5 From 397ad4deadd08496a3d62ca40f6f3552db1450ad Mon Sep 17 00:00:00 2001 From: ameixler Date: Wed, 13 May 2020 18:49:40 -0400 Subject: [PATCH 031/104] testing adding i2cbus settings --- octoprint_enclosure/static/js/enclosure.js | 3 ++- octoprint_enclosure/templates/enclosure_settings.jinja2 | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/octoprint_enclosure/static/js/enclosure.js b/octoprint_enclosure/static/js/enclosure.js index b7a96e5..66e6a31 100644 --- a/octoprint_enclosure/static/js/enclosure.js +++ b/octoprint_enclosure/static/js/enclosure.js @@ -455,7 +455,8 @@ $(function () { printer_action: ko.observable("filament"), temp_sensor_navbar: ko.observable(true), filament_sensor_timeout: ko.observable(120), - filament_sensor_enabled: ko.observable(true) + filament_sensor_enabled: ko.observable(true), + temp_sensor_i2cbus: ko.observable("1") }); }; diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index 45a2b8a..508232c 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -582,6 +582,14 @@ i2cdetect -y 1 on your Raspberry Pi
+
+ +
+ + This value should remain 1 unless you've used dtoverlay=i2c-gpio. + +
+
-- 2.39.5 From 3b6233a731de4e90929148bbde71f837857f0d0c Mon Sep 17 00:00:00 2001 From: ameixler Date: Wed, 13 May 2020 20:38:04 -0400 Subject: [PATCH 032/104] Working changes Confirmed works with 2 Si7021 modules. --- octoprint_enclosure/SI7021.py | 8 ++++++-- octoprint_enclosure/__init__.py | 8 ++++---- octoprint_enclosure/static/js/enclosure.js | 4 ++-- octoprint_enclosure/templates/enclosure_settings.jinja2 | 2 +- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/octoprint_enclosure/SI7021.py b/octoprint_enclosure/SI7021.py index 6b352f1..3e1eb60 100644 --- a/octoprint_enclosure/SI7021.py +++ b/octoprint_enclosure/SI7021.py @@ -3,14 +3,18 @@ import time import sys -if len(sys.argv) == 2: +if len(sys.argv) == 2 or len(sys.argv) == 3: address = int(sys.argv[1],16) + if len(sys.argv) == 3: + busNum = int(sys.argv[2],16) + else: + busNum = 1 else: print('-1 | -1') sys.exit(1) # Get I2C bus -bus = smbus.SMBus(1) +bus = smbus.SMBus(busNum) # SI7021 address, 0x40(64) # 0xF5(245) Select Relative Humidity NO HOLD master mode diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index f01f06c..6e388d9 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -800,7 +800,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP elif sensor['temp_sensor_type'] == "am2320": temp, hum = self.read_am2320_temp() # sensor has fixed address elif sensor['temp_sensor_type'] == "si7021": - temp, hum = self.read_si7021_temp(sensor['temp_sensor_address']) + temp, hum = self.read_si7021_temp(sensor['temp_sensor_address'], sensor['temp_sensor_i2cbus']) elif sensor['temp_sensor_type'] == "tmp102": temp = self.read_tmp102_temp(sensor['temp_sensor_address']) hum = 0 @@ -933,14 +933,14 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.log_error(ex) return (0, 0) - def read_si7021_temp(self, address): + def read_si7021_temp(self, address, i2cbus): try: script = os.path.dirname(os.path.realpath(__file__)) + "/SI7021.py " if self._settings.get(["use_sudo"]): sudo_str = "sudo " else: sudo_str = "" - cmd = sudo_str + "python " + script + str(address) + cmd = sudo_str + "python " + script + str(address) + " " + str(i2cbus) if self._settings.get(["debug_temperature_log"]) is True: self._logger.debug("Temperature SI7021 cmd: %s", cmd) stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() @@ -1890,4 +1890,4 @@ def __plugin_load__(): __plugin_hooks__ = { "octoprint.comm.protocol.gcode.queuing" : __plugin_implementation__.hook_gcode_queuing, "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information - } \ No newline at end of file + } diff --git a/octoprint_enclosure/static/js/enclosure.js b/octoprint_enclosure/static/js/enclosure.js index 66e6a31..e9c4847 100644 --- a/octoprint_enclosure/static/js/enclosure.js +++ b/octoprint_enclosure/static/js/enclosure.js @@ -456,7 +456,7 @@ $(function () { temp_sensor_navbar: ko.observable(true), filament_sensor_timeout: ko.observable(120), filament_sensor_enabled: ko.observable(true), - temp_sensor_i2cbus: ko.observable("1") + temp_sensor_i2cbus: ko.observable(1) }); }; @@ -707,4 +707,4 @@ $(function () { elements: ["#tab_plugin_enclosure", "#settings_plugin_enclosure", "#navbar_plugin_enclosure_1", "#navbar_plugin_enclosure_2"] }); -}); \ No newline at end of file +}); diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index 508232c..a30ca69 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -909,4 +909,4 @@
-
\ No newline at end of file +
-- 2.39.5 From 1d1eb93aa76951ff590b1b34cbff23a6b0bc0016 Mon Sep 17 00:00:00 2001 From: Vitor de Miranda Henrique Date: Sat, 20 Jun 2020 16:05:00 -0500 Subject: [PATCH 033/104] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 5e75eba..c44a05d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +# QUICK NOTE: I suffered an injury last Sunday and broke both bones on my left forearm, it will be impossible to work on on this plugin in the near future, I'm struggling to make it work with a full time job + masters in computers science. This plugin is not abandoned but it is on vacation mode. + + Find the plugin useful? Buy me a coffee [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/VitorHenrique/2) -- 2.39.5 From b0514fcfd3bc80bf28a2acc68ef03dfe08da33e5 Mon Sep 17 00:00:00 2001 From: Vitor de Miranda Henrique Date: Sat, 20 Jun 2020 16:05:33 -0500 Subject: [PATCH 034/104] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c44a05d..f09f270 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# QUICK NOTE: I suffered an injury last Sunday and broke both bones on my left forearm, it will be impossible to work on on this plugin in the near future, I'm struggling to make it work with a full time job + masters in computers science. This plugin is not abandoned but it is on vacation mode. +# QUICK NOTE: I suffered an injury last Sunday (06/20/20) and broke both bones on my left forearm, it will be impossible to work on on this plugin in the near future, I'm struggling to make it work with a full time job + masters in computers science. This plugin is not abandoned but it is on vacation mode. Find the plugin useful? Buy me a coffee -- 2.39.5 From d74c302af7e8cb95453e8252bba81ac090b726cb Mon Sep 17 00:00:00 2001 From: Christian Lorenz Date: Tue, 25 Aug 2020 00:37:17 +0200 Subject: [PATCH 035/104] fixed issues #254 from the original code see https://github.com/vitormhenrique/OctoPrint-Enclosure/issues/254 --- octoprint_enclosure/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index f01f06c..9ffff73 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -835,7 +835,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP for rpi_controlled_output in self.rpi_outputs: if self.to_int(temperature_alarm['controlled_io']) == self.to_int( rpi_controlled_output['index_id']): - val = GPIO.LOW if rpi_controlled_output['active_low'] else GPIO.HIGH + val = GPIO.LOW if temperature_alarm['controlled_io_set_value'] == 'low' else GPIO.HIGH self.write_gpio(self.to_int(rpi_controlled_output['gpio_pin']), val) for notification in self.notifications: if notification['temperatureAction']: @@ -1890,4 +1890,4 @@ def __plugin_load__(): __plugin_hooks__ = { "octoprint.comm.protocol.gcode.queuing" : __plugin_implementation__.hook_gcode_queuing, "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information - } \ No newline at end of file + } -- 2.39.5 From 456497f7e67067514e1711b0566db8988037897c Mon Sep 17 00:00:00 2001 From: Vitor de Miranda Henrique Date: Tue, 15 Sep 2020 09:58:50 -0500 Subject: [PATCH 036/104] Update README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index f09f270..5e75eba 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ -# QUICK NOTE: I suffered an injury last Sunday (06/20/20) and broke both bones on my left forearm, it will be impossible to work on on this plugin in the near future, I'm struggling to make it work with a full time job + masters in computers science. This plugin is not abandoned but it is on vacation mode. - - Find the plugin useful? Buy me a coffee [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/VitorHenrique/2) -- 2.39.5 From 16f3ec817877f94c18fc013c6e8a6bc4642ff7b3 Mon Sep 17 00:00:00 2001 From: Randall Hand Date: Sun, 4 Oct 2020 10:25:35 -0400 Subject: [PATCH 037/104] Convert STDOUT from Popen's to UTF-8 By default, Python3's STDOUT's that come back from Popen commands are byte-arrays, not strings. So attempting to do string operations (like 'split' or 'strip') fail. They must be converted to UTF-8 strings first. This was causing the enclosure plugin to return false debug messages about 'disabling SUDO' when attempting to read my DHT22 sensor. --- octoprint_enclosure/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index b324484..bf0e77e 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -864,7 +864,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP stdout, _ = proc.communicate() if self._settings.get(["debug_temperature_log"]) is True: self._logger.debug("MCP9808 result: %s", stdout) - return self.to_float(stdout.strip()) + return self.to_float(stdout.decode("utf-8").strip()) except Exception as ex: self._logger.info("Failed to execute python scripts, try disabling use SUDO on advanced section.") self.log_error(ex) @@ -883,7 +883,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() if self._settings.get(["debug_temperature_log"]) is True: self._logger.debug("Dht result: %s", stdout) - temp, hum = stdout.split("|") + temp, hum = stdout.decode("utf-8").split("|") return (self.to_float(temp.strip()), self.to_float(hum.strip())) except Exception as ex: self._logger.info( @@ -904,7 +904,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() if self._settings.get(["debug_temperature_log"]) is True: self._logger.debug("BME280 result: %s", stdout) - temp, hum = stdout.split("|") + temp, hum = stdout.decode("utf-8").split("|") return (self.to_float(temp.strip()), self.to_float(hum.strip())) except Exception as ex: self._logger.info( @@ -925,7 +925,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() if self._settings.get(["debug_temperature_log"]) is True: self._logger.debug("AM2320 result: %s", stdout) - temp, hum = stdout.split("|") + temp, hum = stdout.decode("utf-8").split("|") return (self.to_float(temp.strip()), self.to_float(hum.strip())) except Exception as ex: self._logger.info( @@ -946,7 +946,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() if self._settings.get(["debug_temperature_log"]) is True: self._logger.debug("SI7021 result: %s", stdout) - temp, hum = stdout.split("|") + temp, hum = stdout.decode("utf-8").split("|") return (self.to_float(temp.strip()), self.to_float(hum.strip())) except Exception as ex: self._logger.info( @@ -990,7 +990,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP stdout, _ = proc.communicate() if self._settings.get(["debug_temperature_log"]) is True: self._logger.debug("TMP102 result: %s", stdout) - return self.to_float(stdout.strip()) + return self.to_float(stdout.decode("utf-8").strip()) except Exception as ex: self._logger.info("Failed to execute python scripts, try disabling use SUDO on advanced section.") self.log_error(ex) @@ -1006,7 +1006,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP stdout, _ = proc.communicate() if self._settings.get(["debug_temperature_log"]) is True: self._logger.debug("MAX31855 result: %s", stdout) - return self.to_float(stdout.strip()) + return self.to_float(stdout.decode("utf-8").strip()) except Exception as ex: self._logger.info("Failed to execute python scripts, try disabling use SUDO on advanced section.") self.log_error(ex) -- 2.39.5 From 2237920a8eb6c4bfb21dfa539d587a55690d849b Mon Sep 17 00:00:00 2001 From: RFBomb Date: Sat, 24 Oct 2020 09:40:13 -0400 Subject: [PATCH 038/104] Update __init__.py Added IO status to the /inputs and /outputs API calls --- octoprint_enclosure/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index d1681a8..f613d86 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -151,7 +151,9 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP for rpi_input in self.rpi_inputs: index = self.to_int(rpi_input['index_id']) label = rpi_input['label'] - inputs.append(dict(index_id=index, label=label)) + pin = self.to_int(rpi_output['gpio_pin']) + val = GPIO.input(pin) if not rpi_output['active_low'] else (not GPIO.input(pin)) + inputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, Status=val)) return Response(json.dumps(inputs), mimetype='application/json') @@ -215,7 +217,9 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP if rpi_output['output_type'] == 'regular': index = self.to_int(rpi_output['index_id']) label = rpi_output['label'] - outputs.append(dict(index_id=index, label=label)) + pin = self.to_int(rpi_output['gpio_pin']) + val = GPIO.input(pin) if not rpi_output['active_low'] else (not GPIO.input(pin)) + outputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, status=val)) return Response(json.dumps(outputs), mimetype='application/json') -- 2.39.5 From 223d61ff3318506a0f64712f3027aded380a0567 Mon Sep 17 00:00:00 2001 From: RFBomb Date: Sat, 24 Oct 2020 18:51:25 -0400 Subject: [PATCH 039/104] Expanded API for better debugging of inputs - Added 3 support routines at top of the script to normalize / functionize reading the GPIO Pin States for both inputs and outputs. - The '/inputs' and '/outputs' API Calls will now include the current pin state (Human Readable), Label, GPIO Pin Number, and Index in their JSON outputs. These changes also resolve the api returning both 'false' and '0' as the pin states would sometimes show up as from the "val = if-else "statement. --- octoprint_enclosure/__init__.py | 3790 ++++++++++++++++--------------- 1 file changed, 1926 insertions(+), 1864 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index f613d86..d2f1c78 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -23,1864 +23,1926 @@ import json import copy +#Function that returns Boolean output state of the GPIO inputs / outputs +def PinState_Boolean(pin, ActiveLow) : + try: + state = GPIO.input(pin) + if ActiveLow and not state: return True + if not ActiveLow and state: return True + return False + except: + return "ERROR: Unable to read pin" + +#Function that returns human-readable output state of the GPIO inputs / outputs +def PinState_Human(pin, ActiveLow): + PinState = PinState_Boolean(pin, ActiveLow) + if PinState == True : + return " ON " + elif PinState == False: + return " OFF " + else: + return PinState + +#Translates the Pull-Up/Pull-Down GPIO resistor setting to ActiveLow/ActiveHigh boolean +def CheckInputActiveLow(Input_Pull_Resistor): + #input_pull_up + #input_pull_down + if Input_Pull_Resistor == "input_pull_up": + return True + else: + return False + class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SettingsPlugin, - octoprint.plugin.AssetPlugin, octoprint.plugin.BlueprintPlugin, - octoprint.plugin.EventHandlerPlugin): - rpi_outputs = [] - rpi_inputs = [] - waiting_temperature = [] - rpi_outputs_not_changed = [] - notifications = [] - pwm_instances = [] - event_queue = [] - temp_hum_control_status = [] - temperature_sensor_data = [] - last_filament_end_detected = [] - print_complete = False - development_mode = False - dummy_value = 30.0 - dummy_delta = 0.5 - - def start_timer(self): - """ - Function to start timer that checks enclosure temperature - """ - - self._check_temp_timer = RepeatedTimer(10, self.check_enclosure_temp, None, None, True) - self._check_temp_timer.start() - - @staticmethod - def to_float(value): - """Converts value to flow - - Arguments: - value {any} -- value to be - - Returns: - float -- value converted - """ - - try: - val = float(value) - return val - except: - return 0 - - @staticmethod - def to_int(value): - try: - val = int(value) - return val - except: - return 0 - - @staticmethod - def is_hour(value): - try: - datetime.strptime(value, '%H:%M') - return True - except: - return False - - @staticmethod - def create_date(value): - temp_string = datetime.now().strftime('%m/%d/%Y') + " " + value - return datetime.strptime(temp_string, '%m/%d/%Y %H:%M') - - @staticmethod - def constrain(n, minn, maxn): - return max(min(maxn, n), minn) - - @staticmethod - def get_gcode_value(command_string, gcode): - semicolon = command_string.find(';') - if not semicolon == -1: - command_string = command_string[:semicolon] - - for command in command_string.split(' '): - index = command.upper().find(gcode.upper()) - if not index == -1: - return command.replace(gcode, '') - return -1 - - # ~~ StartupPlugin mixin - def on_after_startup(self): - self.pwm_instances = [] - self.event_queue = [] - self.rpi_outputs_not_changed = [] - self.rpi_outputs = self._settings.get(["rpi_outputs"]) - self.rpi_inputs = self._settings.get(["rpi_inputs"]) - self.notifications = self._settings.get(["notifications"]) - self.generate_temp_hum_control_status() - self.setup_gpio() - self.configure_gpio() - self.update_ui() - self.start_outpus_with_server() - self.handle_initial_gpio_control() - self.start_timer() - self.print_complete = False - - def get_settings_version(self): - return 6 - - def on_settings_migrate(self, target, current=None): - self._logger.warn("######### current settings version %s target settings version %s #########", current, target) - self._logger.info("######### Current settings #########") - self._logger.info("rpi_outputs: %s", self.rpi_outputs) - self._logger.info("rpi_inputs: %s", self.rpi_inputs) - self._logger.info("######### End Current Settings #########") - if current >= 4 and target == 6: - self._logger.warn("######### migrating settings to v6 #########") - old_outputs = self._settings.get(["rpi_outputs"]) - for rpi_output in old_outputs: - if 'shutdown_on_failed' not in rpi_output: - rpi_output['shutdown_on_failed'] = False - if 'shell_script' not in rpi_output: - rpi_output['shell_script'] = "" - self._settings.set(["rpi_outputs"], old_outputs) - else: - self._logger.warn("######### settings not compatible #########") - self._settings.set(["rpi_outputs"], []) - self._settings.set(["rpi_inputs"], []) - self.rpi_inputs = self._settings.get(["rpi_inputs"]) - - # ~~ Blueprintplugin mixin - @octoprint.plugin.BlueprintPlugin.route("/inputs", methods=["GET"]) - def get_inputs(self): - inputs = [] - for rpi_input in self.rpi_inputs: - index = self.to_int(rpi_input['index_id']) - label = rpi_input['label'] - pin = self.to_int(rpi_output['gpio_pin']) - val = GPIO.input(pin) if not rpi_output['active_low'] else (not GPIO.input(pin)) - inputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, Status=val)) - return Response(json.dumps(inputs), mimetype='application/json') - - - @octoprint.plugin.BlueprintPlugin.route("/inputs/", methods=["GET"]) - def get_input_status(self, identifier): - for rpi_input in self.rpi_inputs: - if identifier == self.to_int(rpi_input['index_id']): - return Response(json.dumps(rpi_input), mimetype='application/json') - return make_response('', 404) - - - @octoprint.plugin.BlueprintPlugin.route("/temperature/", methods=["PATCH"]) - @restricted_access - def set_enclosure_temp_humidity(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'temperature' not in data: - return make_response("missing temperature attribute", 406) - - set_value = data["temperature"] - - for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == identifier]: - temp_hum_control['temp_ctr_set_value'] = set_value - - self.handle_temp_hum_control() - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/filament/", methods=["PATCH"]) - @restricted_access - def set_filament_sensor(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'status' not in data: - return make_response("missing status attribute", 406) - - value = data["status"] - - for sensor in self.rpi_inputs: - if identifier == self.to_int(sensor['index_id']): - sensor['filament_sensor_enabled'] = value - self._logger.info("Setting filament sensor for input %s to : %s", str(identifier), value) - self._settings.set(["rpi_inputs"], self.rpi_inputs) - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/outputs", methods=["GET"]) - def get_outputs(self): - outputs = [] - for rpi_output in self.rpi_outputs: - if rpi_output['output_type'] == 'regular': - index = self.to_int(rpi_output['index_id']) - label = rpi_output['label'] - pin = self.to_int(rpi_output['gpio_pin']) - val = GPIO.input(pin) if not rpi_output['active_low'] else (not GPIO.input(pin)) - outputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, status=val)) - return Response(json.dumps(outputs), mimetype='application/json') - - - @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["GET"]) - def get_output_status(self, identifier): - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['index_id']): - out = copy.deepcopy(rpi_output) - pin = self.to_int(rpi_output['gpio_pin']) - out['current_value'] = GPIO.input(pin) if not rpi_output['active_low'] else (not GPIO.input(pin)) - return Response(json.dumps(out), mimetype='application/json') - return make_response('', 404) - - - @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["PATCH"]) - @restricted_access - def set_io(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'status' not in data: - return make_response("missing status attribute", 406) - - value = data["status"] - - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['index_id']): - val = (not value) if rpi_output['active_low'] else value - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-startup", methods=["PATCH"]) - @restricted_access - def set_auto_startup(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'status' not in data: - return make_response("missing status attribute", 406) - - value = data["status"] - - if not value: - suffix = 'auto_startup' - queue_id = '{0!s}_{1!s}'.format(str(identifier), suffix) - self.stop_queue_item(queue_id) - for output in self.rpi_outputs: - if identifier == self.to_int(output['index_id']): - output['auto_startup'] = value - self._logger.info("Setting auto startup for output %s to : %s", str(identifier), value) - self._settings.set(["rpi_outputs"], self.rpi_outputs) - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-shutdown", methods=["PATCH"]) - @restricted_access - def set_auto_shutdown(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'status' not in data: - return make_response("missing status attribute", 406) - - value = data["status"] - - if not value: - suffix = 'auto_shutdown' - queue_id = '{0!s}_{1!s}'.format(str(identifier), suffix) - self.stop_queue_item(queue_id) - - for output in self.rpi_outputs: - if identifier == self.to_int(output['index_id']): - output['auto_shutdown'] = value - self._logger.info("Setting auto shutdown for output %s to : %s", str(identifier), value) - self._settings.set(["rpi_outputs"], self.rpi_outputs) - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/pwm/", methods=["PATCH"]) - @restricted_access - def set_pwm(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'duty_cycle' not in data: - return make_response("missing duty_cycle attribute", 406) - - set_value = self.to_int(data['duty_cycle']) - for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == identifier]: - rpi_output['duty_cycle'] = set_value - rpi_output['new_duty_cycle'] = "" - gpio = self.to_int(rpi_output['gpio_pin']) - self.write_pwm(gpio, set_value) - return make_response('', 204) - - @octoprint.plugin.BlueprintPlugin.route("/rgb-led/", methods=["PATCH"]) - @restricted_access - def set_ledstrip_color(self, identifier): - """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'rgb' not in data: - return make_response("missing rgb attribute", 406) - rgb = data['rgb'] - - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['index_id']): - self.ledstrip_set_rgb(rpi_output, rgb) - - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/neopixel/", methods=["PATCH"]) - @restricted_access - def set_neopixel(self, identifier): - """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'red' not in data: - return make_response("missing red attribute", 406) - if 'green' not in data: - return make_response("missing green attribute", 406) - if 'blue' not in data: - return make_response("missing blue attribute", 406) - - red = data['red'] - green = data['green'] - blue = data['blue'] - - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['index_id']): - led_count = rpi_output['neopixel_count'] - led_brightness = rpi_output['neopixel_brightness'] - address = rpi_output['microcontroller_address'] - - neopixel_dirrect = rpi_output['output_type'] == 'neopixel_direct' - - self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, green, - blue, address, neopixel_dirrect, identifier) - - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/clear-gpio", methods=["POST"]) - @restricted_access - def clear_gpio_mode(self): - GPIO.cleanup() - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/update", methods=["POST"]) - @restricted_access - def update_ui_requested(self): - self.update_ui() - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/shell/", methods=["POST"]) - @restricted_access - def send_shell_command(self, identifier): - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() - - command = rpi_output['shell_script'] - self.shell_command(command) - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/gcode/", methods=["POST"]) - @restricted_access - def requested_gcode_command(self, identifier): - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() - self.send_gcode_command(rpi_output['gcode']) - return make_response('', 204) - - - - - - """ - DEPRECATION - This API will be deprecated in a future version - """ - - # ~~ Blueprintplugin mixin - @octoprint.plugin.BlueprintPlugin.route("/setEnclosureTempHum", methods=["GET"]) - def set_enclosure_temp_humidity_old(self): - set_value = self.to_float(request.values["set_temperature"]) - index_id = self.to_int(request.values["index_id"]) - - for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == index_id]: - temp_hum_control['temp_ctr_set_value'] = set_value - - self.handle_temp_hum_control() - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/clearGPIOMode", methods=["GET"]) - def clear_gpio_mode_old(self): - GPIO.cleanup() - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/updateUI", methods=["GET"]) - def update_ui_requested_old(self): - self.update_ui() - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/getOutputStatus", methods=["GET"]) - def get_output_status_old(self): - gpio_status = [] - for rpi_output in self.rpi_outputs: - if rpi_output['output_type'] == 'regular': - pin = self.to_int(rpi_output['gpio_pin']) - val = GPIO.input(pin) if not rpi_output['active_low'] else (not GPIO.input(pin)) - index = self.to_int(rpi_output['index_id']) - gpio_status.append(dict(index_id=index, status=val)) - return Response(json.dumps(gpio_status), mimetype='application/json') - - @octoprint.plugin.BlueprintPlugin.route("/setIO", methods=["GET"]) - def set_io_old(self): - index = request.values["index_id"] - value = True if request.values["status"] == 'true' else False - for rpi_output in self.rpi_outputs: - if self.to_int(index) == self.to_int(rpi_output['index_id']): - val = (not value) if rpi_output['active_low'] else value - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/sendShellCommand", methods=["GET"]) - def send_shell_command_old(self): - output_index = self.to_int(request.values["index_id"]) - - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == output_index].pop() - - command = rpi_output['shell_script'] - self.shell_command(command) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setAutoStartUp", methods=["GET"]) - def set_auto_startup_old(self): - index = request.values["index_id"] - value = True if request.values["status"] == 'true' else False - - if not value: - suffix = 'auto_startup' - queue_id = '{0!s}_{1!s}'.format(index, suffix) - self.stop_queue_item(queue_id) - for output in self.rpi_outputs: - if self.to_int(index) == self.to_int(output['index_id']): - output['auto_startup'] = value - self._logger.info("Setting auto startup for output %s to : %s", index, value) - self._settings.set(["rpi_outputs"], self.rpi_outputs) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setAutoShutdown", methods=["GET"]) - def set_auto_shutdown_old(self): - index = request.values["index_id"] - value = True if request.values["status"] == 'true' else False - - if not value: - suffix = 'auto_shutdown' - queue_id = '{0!s}_{1!s}'.format(index, suffix) - self.stop_queue_item(queue_id) - - for output in self.rpi_outputs: - if self.to_int(index) == self.to_int(output['index_id']): - output['auto_shutdown'] = value - self._logger.info("Setting auto shutdown for output %s to : %s", index, value) - self._settings.set(["rpi_outputs"], self.rpi_outputs) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setFilamentSensor", methods=["GET"]) - def set_filament_sensor_old(self): - index = request.values["index_id"] - value = True if request.values["status"] == 'true' else False - for sensor in self.rpi_inputs: - if self.to_int(index) == self.to_int(sensor['index_id']): - sensor['filament_sensor_enabled'] = value - self._logger.info("Setting filament sensor for input %s to : %s", index, value) - self._settings.set(["rpi_inputs"], self.rpi_inputs) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setPWM", methods=["GET"]) - def set_pwm_old(self): - set_value = self.to_int(request.values["new_duty_cycle"]) - index_id = self.to_int(request.values["index_id"]) - for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == index_id]: - rpi_output['duty_cycle'] = set_value - rpi_output['new_duty_cycle'] = "" - gpio = self.to_int(rpi_output['gpio_pin']) - self.write_pwm(gpio, set_value) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/sendGcodeCommand", methods=["GET"]) - def requested_gcode_command_old(self): - gpio_index = self.to_int(request.values["index_id"]) - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == gpio_index].pop() - self.send_gcode_command(rpi_output['gcode']) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setNeopixel", methods=["GET"]) - def set_neopixel_old(self): - """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" - gpio_index = self.to_int(request.values["index_id"]) - red = request.values["red"] - green = request.values["green"] - blue = request.values["blue"] - for rpi_output in self.rpi_outputs: - if gpio_index == self.to_int(rpi_output['index_id']): - led_count = rpi_output['neopixel_count'] - led_brightness = rpi_output['neopixel_brightness'] - address = rpi_output['microcontroller_address'] - - neopixel_dirrect = rpi_output['output_type'] == 'neopixel_direct' - - self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, green, - blue, address, neopixel_dirrect, gpio_index) - - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setLedstripColor", methods=["GET"]) - def set_ledstrip_color_old(self): - """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" - gpio_index = self.to_int(request.values["index_id"]) - rgb = request.values["rgb"] - for rpi_output in self.rpi_outputs: - if gpio_index == self.to_int(rpi_output['index_id']): - self.ledstrip_set_rgb(rpi_output, rgb) - - return jsonify(success=True) - - # DEPREACTION END - - - - - - def send_neopixel_command(self, led_pin, led_count, led_brightness, red, green, blue, address, neopixel_dirrect, - index_id, queue_id=None): - """Send neopixel command - - Arguments: - led_pin {int} -- GPIO number - ledCount {int} -- number of LEDS - ledBrightness {int} -- brightness from 0 to 255 - red {int} -- red value from 0 to 255 - green {int} -- green value from 0 to 255 - blue {int} -- blue value from 0 to 255 - address {int} -- i2c address from microcontroller - """ - - try: - - for rpi_output in self.rpi_outputs: - if self.to_int(index_id) == self.to_int(rpi_output['index_id']): - rpi_output['neopixel_color'] = 'rgb({0!s},{1!s},{2!s})'.format(red, green, blue) - - if address == '': - address = 0 - - if neopixel_dirrect: - script = os.path.dirname(os.path.realpath(__file__)) + "/neopixel_direct.py " - else: - script = os.path.dirname(os.path.realpath(__file__)) + "/neopixel_indirect.py " - - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - - cmd = sudo_str + "python " + script + str(led_pin) + " " + str(led_count) + " " + str( - led_brightness) + " " + str(red) + " " + str(green) + " " + str(blue) + " " - - if neopixel_dirrect: - dma = self._settings.get(["neopixel_dma"]) or 10 - cmd = cmd + str(dma) - else: - cmd = cmd + str(address) - - if queue_id is not None: - self._logger.debug("running scheduled queue id %s", queue_id) - self._logger.debug("Sending neopixel cmd: %s", cmd) - Popen(cmd, shell=True) - if queue_id is not None: - self.stop_queue_item(queue_id) - except Exception as ex: - self.log_error(ex) - - def check_enclosure_temp(self): - try: - sensor_data = [] - for sensor in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): - temp, hum = self.get_sensor_data(sensor) - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Sensor %s Temperature: %s humidity %s", sensor['label'], temp, hum) - if temp is not None and hum is not None: - sensor["temp_sensor_temp"] = temp - sensor["temp_sensor_humidity"] = hum - sensor_data.append(dict(index_id=sensor['index_id'], temperature=temp, humidity=hum)) - self.temperature_sensor_data = sensor_data - self.handle_temp_hum_control() - self.handle_temperature_events() - self.handle_pwm_linked_temperature() - self.update_ui() - except Exception as ex: - self.log_error(ex) - - def toggle_output(self, output_index, first_run=False): - for output in [item for item in self.rpi_outputs if item['index_id'] == output_index]: - gpio_pin = self.to_int(output['gpio_pin']) - index_id = self.to_int(output['index_id']) - - if output['output_type'] == 'regular': - if first_run: - current_value = False - else: - current_value = (not GPIO.input(gpio_pin)) if output['active_low'] else GPIO.input(gpio_pin) - - if current_value: - time_delay = self.to_int(output['toggle_timer_off']) - else: - time_delay = self.to_int(output['toggle_timer_on']) - - if not self.print_complete: - self.write_gpio(gpio_pin, not current_value) - thread = threading.Timer(time_delay, self.toggle_output, args=[index_id]) - thread.start() - else: - off_value = True if output['active_low'] else False - self.write_gpio(gpio_pin, off_value) - self.update_ui_outputs() - return - - if output['output_type'] == 'pwm': - for pwm in self.pwm_instances: - if gpio_pin in pwm: - if first_run: - current_pwm_value = 0 - else: - if 'duty_cycle' in pwm: - current_pwm_value = pwm['duty_cycle'] - current_pwm_value = self.to_int(current_pwm_value) - else: - current_pwm_value = 0 - - if not current_pwm_value == 0: - time_delay = self.to_int(output['toggle_timer_off']) - write_value = 0 - else: - time_delay = self.to_int(output['toggle_timer_on']) - write_value = self.to_int(output['default_duty_cycle']) - - if not self.print_complete: - self.write_pwm(gpio_pin, write_value) - thread = threading.Timer(time_delay, self.toggle_output, args=[index_id]) - thread.start() - else: - self.write_pwm(self.to_int(output['gpio_pin']), 0) - self.update_ui_outputs() - return - - def update_ui(self): - self.update_ui_outputs() - self.update_ui_current_temperature() - self.update_ui_set_temperature() - self.update_ui_inputs() - - def update_ui_current_temperature(self): - self._plugin_manager.send_plugin_message(self._identifier, dict(sensor_data=self.temperature_sensor_data)) - - def update_ui_set_temperature(self): - result = [] - for temp_crt_output in list(filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): - set_temperature = self.to_float(temp_crt_output['temp_ctr_set_value']) - result.append(dict(index_id=temp_crt_output['index_id'], set_temperature=set_temperature)) - result.append(set_temperature) - self._plugin_manager.send_plugin_message(self._identifier, dict(set_temperature=result)) - - def stop_queue_item(self, queue_id): - old_list = self.event_queue - self._logger.debug("Stopping queue id %s...", queue_id) - for task in self.event_queue: - self._logger.debug("Queue id found...") - if task['queue_id'] == queue_id: - task['thread'].cancel() - self.event_queue.remove(task) - self._logger.debug("Queue id stopped and removed from list...") - self._logger.debug("Old queue list: %s", old_list) - self._logger.debug("New queue list: %s", self.event_queue) - - def update_ui_outputs(self): - try: - regular_status = [] - pwm_status = [] - neopixel_status = [] - temp_control_status = [] - for output in self.rpi_outputs: - index = self.to_int(output['index_id']) - pin = self.to_int(output['gpio_pin']) - startup = output['auto_startup'] - shutdown = output['auto_shutdown'] - - if output['output_type'] == 'regular': - val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) - regular_status.append( - dict(index_id=index, status=val, auto_startup=startup, auto_shutdown=shutdown)) - if output['output_type'] == 'temp_hum_control': - val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) - temp_control_status.append( - dict(index_id=index, status=val, auto_startup=startup, auto_shutdown=shutdown)) - if output['output_type'] == 'neopixel_indirect' or output['output_type'] == 'neopixel_direct': - val = output['neopixel_color'] - neopixel_status.append( - dict(index_id=index, color=val, auto_startup=startup, auto_shutdown=shutdown)) - if output['output_type'] == 'pwm': - for pwm in self.pwm_instances: - if pin in pwm: - if 'duty_cycle' in pwm: - pwm_val = pwm['duty_cycle'] - val = self.to_int(pwm_val) - else: - val = 0 - pwm_status.append( - dict(index_id=index, pwm_value=val, auto_startup=startup, auto_shutdown=shutdown)) - self._plugin_manager.send_plugin_message(self._identifier, - dict(rpi_output_regular=regular_status, rpi_output_pwm=pwm_status, - rpi_output_neopixel=neopixel_status, - rpi_output_temp_hum_ctrl=temp_control_status)) - except Exception as ex: - self.log_error(ex) - - def update_ui_inputs(self): - try: - sensor_status = [] - for sensor in self.rpi_inputs: - if sensor['input_type'] == 'gpio' and sensor['action_type'] == 'printer_control' and sensor[ - 'printer_action'] == 'filament': - index = self.to_int(sensor['index_id']) - value = sensor['filament_sensor_enabled'] - sensor_status.append(dict(index_id=index, filament_sensor_enabled=value)) - self._plugin_manager.send_plugin_message(self._identifier, dict(filament_sensor_status=sensor_status)) - except Exception as ex: - self.log_error(ex) - - def get_sensor_data(self, sensor): - try: - if self.development_mode: - temp, hum = self.read_dummy_temp() - else: - if sensor['temp_sensor_type'] in ["11", "22", "2302"]: - temp, hum = self.read_dht_temp(sensor['temp_sensor_type'], sensor['gpio_pin']) - elif sensor['temp_sensor_type'] == "18b20": - temp = self.read_18b20_temp(sensor['ds18b20_serial']) - hum = 0 - elif sensor['temp_sensor_type'] == "bme280": - temp, hum = self.read_bme280_temp(sensor['temp_sensor_address']) - elif sensor['temp_sensor_type'] == "am2320": - temp, hum = self.read_am2320_temp() # sensor has fixed address - elif sensor['temp_sensor_type'] == "si7021": - temp, hum = self.read_si7021_temp(sensor['temp_sensor_address'], sensor['temp_sensor_i2cbus']) - elif sensor['temp_sensor_type'] == "tmp102": - temp = self.read_tmp102_temp(sensor['temp_sensor_address']) - hum = 0 - elif sensor['temp_sensor_type'] == "max31855": - temp = self.read_max31855_temp(sensor['temp_sensor_address']) - hum = 0 - elif sensor['temp_sensor_type'] == "mcp9808": - temp = self.read_mcp_temp(sensor['temp_sensor_address']) - hum = 0 - else: - self._logger.info("temp_sensor_type no match") - temp = None - hum = None - if temp != -1 and hum != -1: - temp = round(self.to_float(temp), 1) if not sensor['use_fahrenheit'] else round( - self.to_float(temp) * 1.8 + 32, 1) - hum = round(self.to_float(hum), 1) - return temp, hum - return None, None - except Exception as ex: - self.log_error(ex) - - def handle_temperature_events(self): - for temperature_alarm in [item for item in self.rpi_outputs if item['output_type'] == 'temperature_alarm']: - set_temperature = self.to_float(temperature_alarm['alarm_set_temp']) - if int(set_temperature) is 0: - continue - linked_data = [item for item in self.temperature_sensor_data if - item['index_id'] == temperature_alarm['linked_temp_sensor']].pop() - sensor_temperature = self.to_float(linked_data['temperature']) - if set_temperature < sensor_temperature: - for rpi_controlled_output in self.rpi_outputs: - if self.to_int(temperature_alarm['controlled_io']) == self.to_int( - rpi_controlled_output['index_id']): - val = GPIO.LOW if temperature_alarm['controlled_io_set_value'] == 'low' else GPIO.HIGH - self.write_gpio(self.to_int(rpi_controlled_output['gpio_pin']), val) - for notification in self.notifications: - if notification['temperatureAction']: - msg = ("Temperature action: enclosure temperature exceed " + temperature_alarm[ - 'alarm_set_temp']) - self.send_notification(msg) - - def read_dummy_temp(self): - current_value = self.dummy_value - if current_value > 40 or current_value < 30: - self.dummy_delta = - self.dummy_delta - - return_value = current_value + self.dummy_delta - - self.dummy_value = return_value - - return return_value, return_value - - def read_mcp_temp(self, address): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/mcp9808.py" - args = ["python", script, str(address)] - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature MCP9808 cmd: %s", " ".join(args)) - proc = Popen(args, stdout=PIPE) - stdout, _ = proc.communicate() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("MCP9808 result: %s", stdout) - return self.to_float(stdout.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 - - def read_dht_temp(self, sensor, pin): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/getDHTTemp.py " - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - cmd = sudo_str + "python " + script + str(sensor) + " " + str(pin) - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature dht cmd: %s", cmd) - stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Dht result: %s", stdout) - temp, hum = stdout.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 of the plugin.") - self.log_error(ex) - return (0, 0) - - def read_bme280_temp(self, address): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/BME280.py " - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - cmd = sudo_str + "python " + script + str(address) - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature BME280 cmd: %s", cmd) - stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("BME280 result: %s", stdout) - temp, hum = stdout.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 of the plugin.") - self.log_error(ex) - return (0, 0) - - def read_am2320_temp(self): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/AM2320.py " - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - cmd = sudo_str + "python " + script # sensor has fixed address 0x5C - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature AM2320 cmd: %s", cmd) - stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("AM2320 result: %s", stdout) - temp, hum = stdout.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 of the plugin.") - self.log_error(ex) - return (0, 0) - - def read_si7021_temp(self, address, i2cbus): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/SI7021.py " - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - cmd = sudo_str + "python " + script + str(address) + " " + str(i2cbus) - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature SI7021 cmd: %s", cmd) - stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("SI7021 result: %s", stdout) - temp, hum = stdout.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 of the plugin.") - self.log_error(ex) - return (0, 0) - - def read_18b20_temp(self, serial_number): - os.system('modprobe w1-gpio') - os.system('modprobe w1-therm') - - lines = self.read_raw_18b20_temp(serial_number) - while lines[0].strip()[-3:] != 'YES': - time.sleep(0.2) - lines = self.read_raw_18b20_temp(serial_number) - equals_pos = lines[1].find('t=') - if equals_pos != -1: - temp_string = lines[1][equals_pos + 2:] - temp_c = float(temp_string) / 1000. - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("DS18B20 result: %s", temp_c) - return '{0:0.1f}'.format(temp_c) - return 0 - - def read_raw_18b20_temp(self, serial_number): - base_dir = '/sys/bus/w1/devices/' - device_folder = glob.glob(base_dir + str(serial_number) + '*')[0] - device_file = device_folder + '/w1_slave' - device_file_result = open(device_file, 'r') - lines = device_file_result.readlines() - device_file_result.close() - return lines - - def read_tmp102_temp(self, address): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/tmp102.py" - args = ["python", script, str(address)] - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature TMP102 cmd: %s", " ".join(args)) - proc = Popen(args, stdout=PIPE) - stdout, _ = proc.communicate() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("TMP102 result: %s", stdout) - return self.to_float(stdout.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 - - def read_max31855_temp(self, address): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/max31855.py" - args = ["python", script, str(address)] - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature MAX31855 cmd: %s", " ".join(args)) - proc = Popen(args, stdout=PIPE) - stdout, _ = proc.communicate() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("MAX31855 result: %s", stdout) - return self.to_float(stdout.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 - - def handle_pwm_linked_temperature(self): - try: - for pwm_output in list(filter(lambda item: item['output_type'] == 'pwm' and item['pwm_temperature_linked'], - self.rpi_outputs)): - gpio_pin = self.to_int(pwm_output['gpio_pin']) - if self._printer.is_printing(): - index_id = self.to_int(pwm_output['index_id']) - linked_id = self.to_int(pwm_output['linked_temp_sensor']) - linked_data = self.get_linked_temp_sensor_data(linked_id) - current_temp = self.to_float(linked_data['temperature']) - - duty_a = self.to_float(pwm_output['duty_a']) - duty_b = self.to_float(pwm_output['duty_b']) - temp_a = self.to_float(pwm_output['temperature_a']) - temp_b = self.to_float(pwm_output['temperature_b']) - - try: - calculated_duty = ((current_temp - temp_a) * (duty_b - duty_a) / (temp_b - temp_a)) + duty_a - - if current_temp < temp_a: - calculated_duty = 0 - except: - calculated_duty = 0 - - self._logger.debug("Calculated duty for PWM %s is %s", index_id, calculated_duty) - elif self.print_complete: - calculated_duty = self.to_int(pwm_output['duty_cycle']) - else: - calculated_duty = 0 - - self.write_pwm(gpio_pin, self.constrain(calculated_duty, 0, 100)) - - except Exception as ex: - self.log_error(ex) - - def get_linked_temp_sensor_data(self, linked_id): - try: - linked_data = [data for data in self.temperature_sensor_data if data['index_id'] == linked_id].pop() - return linked_data - except: - self._logger.warn("No linked temperature sensor found for %s", linked_id) - return None - - def handle_temp_hum_control(self): - try: - for temp_hum_control in list( - filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): - - set_temperature = self.to_float(temp_hum_control['temp_ctr_set_value']) - temp_deadband = self.to_float(temp_hum_control['temp_ctr_deadband']) - max_temp = self.to_float(temp_hum_control['temp_ctr_max_temp']) - - linked_id = temp_hum_control['linked_temp_sensor'] - - previous_status = list(filter(lambda item: item['index_id'] == temp_hum_control['index_id'], - self.temp_hum_control_status)).pop()['status'] - - if set_temperature == 0: - current_status = False - else: - linked_data = self.get_linked_temp_sensor_data(linked_id) - - control_type = str(temp_hum_control['temp_ctr_type']) - - if control_type == 'dehumidifier': - current_value = self.to_float(linked_data['humidity']) - temp_deadband = 0 - else: - current_value = self.to_float(linked_data['temperature']) - - if control_type == 'cooler' or control_type == 'dehumidifier': - if current_value <= set_temperature and current_value >= (set_temperature - temp_deadband): - current_status = previous_status - elif current_value < set_temperature: - current_status = False - else: - current_status = True - else: - if current_value <= set_temperature and current_value >= (set_temperature - temp_deadband): - current_status = previous_status - elif current_value > set_temperature: - current_status = False - else: - current_status = True - - if control_type == 'heater' and max_temp > 0.0 and max_temp < current_value: - self._logger.debug("Maximum temperature reached for temperature control %s", - temp_hum_control['index_id']) - temp_hum_control['temp_ctr_set_value'] = 0 - current_status = False - - if current_status != previous_status: - if current_status: - self._logger.info("Turning gpio to control temperature on.") - val = False if temp_hum_control['active_low'] else True - self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) - else: - index_id = temp_hum_control['index_id'] - if index_id in self.waiting_temperature: - self.waiting_temperature.remove(index_id) - - if not self.waiting_temperature and self._printer.is_paused(): - self._printer.resume_print() - - self._logger.info("Turning gpio to control temperature off.") - val = True if temp_hum_control['active_low'] else False - self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) - for control_status in self.temp_hum_control_status: - if control_status['index_id'] == temp_hum_control['index_id']: - control_status['status'] = current_status - except Exception as ex: - self.log_error(ex) - - def log_error(self, ex): - template = "An exception of type {0} occurred on {1}. Arguments:\n{2!r}" - message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, ex.args) - self._logger.warn(message) - - def setup_gpio(self): - try: - current_mode = GPIO.getmode() - set_mode = GPIO.BOARD if self._settings.get(["use_board_pin_number"]) else GPIO.BCM - if current_mode is None: - outputs = list(filter( - lambda item: item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ - 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct', - self.rpi_outputs)) - inputs = list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)) - gpios = outputs + inputs - if gpios: - GPIO.setmode(set_mode) - tempstr = "BOARD" if set_mode == GPIO.BOARD else "BCM" - self._logger.info("Setting GPIO mode to %s", tempstr) - elif current_mode != set_mode: - GPIO.setmode(current_mode) - tempstr = "BOARD" if current_mode == GPIO.BOARD else "BCM" - self._settings.set(["use_board_pin_number"], True if current_mode == GPIO.BOARD else False) - warn_msg = "GPIO mode was configured before, GPIO mode will be forced to use: " + tempstr + " as pin numbers. Please update GPIO accordingly!" - self._logger.info(warn_msg) - self._plugin_manager.send_plugin_message(self._identifier, - dict(is_msg=True, msg=warn_msg, msg_type="error")) - GPIO.setwarnings(False) - except Exception as ex: - self.log_error(ex) - - def clear_gpio(self): - try: - - for gpio_out in list(filter( - lambda item: item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ - 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct', - self.rpi_outputs)): - gpio_pin = self.to_int(gpio_out['gpio_pin']) - if gpio_pin not in self.rpi_outputs_not_changed: - GPIO.cleanup(gpio_pin) - - for gpio_in in list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)): - try: - GPIO.remove_event_detect(self.to_int(gpio_in['gpio_pin'])) - except: - pass - GPIO.cleanup(self.to_int(gpio_in['gpio_pin'])) - except Exception as ex: - self.log_error(ex) - - def clear_channel(self, channel): - try: - GPIO.cleanup(self.to_int(channel)) - self._logger.debug("Clearing channel %s", channel) - except Exception as ex: - self.log_error(ex) - - def generate_temp_hum_control_status(self): - status = [] - for temp_hum_control in list(filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): - status.append(dict(index_id=temp_hum_control['index_id'], status=False)) - self.temp_hum_control_status = status - - def configure_gpio(self): - try: - - for gpio_out in list( - filter(lambda item: item['output_type'] == 'regular' or item['output_type'] == 'temp_hum_control', - self.rpi_outputs)): - initial_value = GPIO.HIGH if gpio_out['active_low'] else GPIO.LOW - pin = self.to_int(gpio_out['gpio_pin']) - if pin not in self.rpi_outputs_not_changed: - self._logger.info("Setting GPIO pin %s as OUTPUT with initial value: %s", pin, initial_value) - GPIO.setup(pin, GPIO.OUT, initial=initial_value) - for gpio_out_pwm in list(filter(lambda item: item['output_type'] == 'pwm', self.rpi_outputs)): - pin = self.to_int(gpio_out_pwm['gpio_pin']) - self._logger.info("Setting GPIO pin %s as PWM", pin) - for pwm in (pwm_dict for pwm_dict in self.pwm_instances if pin in pwm_dict): - self.pwm_instances.remove(pwm) - self.clear_channel(pin) - GPIO.setup(pin, GPIO.OUT) - pwm_instance = GPIO.PWM(pin, self.to_int(gpio_out_pwm['pwm_frequency'])) - self._logger.info("starting PWM on pin %s", pin) - pwm_instance.start(0) - self.pwm_instances.append({pin: pwm_instance}) - for gpio_out_neopixel in list( - filter(lambda item: item['output_type'] == 'neopixel_direct', self.rpi_outputs)): - pin = self.to_int(gpio_out_neopixel['gpio_pin']) - self.clear_channel(pin) - - for rpi_input in list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)): - gpio_pin = self.to_int(rpi_input['gpio_pin']) - pull_resistor = GPIO.PUD_UP if rpi_input['input_pull_resistor'] == 'input_pull_up' else GPIO.PUD_DOWN - GPIO.setup(gpio_pin, GPIO.IN, pull_resistor) - edge = GPIO.RISING if rpi_input['edge'] == 'rise' else GPIO.FALLING - - inputs_same_gpio = list( - [r_inp for r_inp in self.rpi_inputs if self.to_int(r_inp['gpio_pin']) == gpio_pin]) - - if len(inputs_same_gpio) > 1: - GPIO.remove_event_detect(gpio_pin) - for other_input in inputs_same_gpio: - if other_input['edge'] is not edge: - edge = GPIO.BOTH - - if rpi_input['action_type'] == 'output_control': - self._logger.info("Adding GPIO event detect on pin %s with edge: %s", gpio_pin, edge) - GPIO.add_event_detect(gpio_pin, edge, callback=self.handle_gpio_control, bouncetime=200) - if (rpi_input['action_type'] == 'printer_control' and rpi_input['printer_action'] != 'filament'): - GPIO.add_event_detect(gpio_pin, edge, callback=self.handle_printer_action, bouncetime=200) - self._logger.info("Adding PRINTER CONTROL event detect on pin %s with edge: %s", gpio_pin, edge) - except Exception as ex: - self.log_error(ex) - - def handle_filamment_detection(self, channel): - try: - for filament_sensor in list(filter( - lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ - 'printer_action'] == 'filament' and self.to_int(item['gpio_pin']) == self.to_int(channel), - self.rpi_inputs)): - if ((filament_sensor['edge'] == 'fall') ^ (GPIO.input(self.to_int(filament_sensor['gpio_pin']))) and - filament_sensor['filament_sensor_enabled']): - last_detected_time = list(filter(lambda item: item['index_id'] == filament_sensor['index_id'], - self.last_filament_end_detected)).pop()['time'] - time_now = time.time() - time_difference = self.to_int(time_now - last_detected_time) - time_out_value = self.to_int(filament_sensor['filament_sensor_timeout']) - if time_difference > time_out_value: - self._logger.info("Detected end of filament.") - for item in self.last_filament_end_detected: - if item['index_id'] == filament_sensor['index_id']: - item['time'] = time_now - for line in self._settings.get(["filament_sensor_gcode"]).split('\n'): - if line: - self._printer.commands(line.strip()) - self._logger.info("Sending GCODE command: %s", line.strip()) - time.sleep(0.2) - for notification in self.notifications: - if notification['filamentChange']: - msg = "Filament change action caused by sensor: " + str(filament_sensor['label']) - self.send_notification(msg) - else: - self._logger.info("Prevented end of filament detection, filament sensor timeout not elapsed.") - except Exception as ex: - self.log_error(ex) - - def start_filament_detection(self): - self.stop_filament_detection() - try: - for filament_sensor in list(filter( - lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ - 'printer_action'] == 'filament', self.rpi_inputs)): - edge = GPIO.RISING if filament_sensor['edge'] == 'rise' else GPIO.FALLING - if GPIO.input(self.to_int(filament_sensor['gpio_pin'])) == (edge == GPIO.RISING): - self._printer.pause_print() - self._logger.info("Started printing with no filament.") - else: - self.last_filament_end_detected.append(dict(index_id=filament_sensor['index_id'], time=0)) - self._logger.info("Adding GPIO event detect on pin %s with edge: %s", filament_sensor['gpio_pin'], - edge) - GPIO.add_event_detect(self.to_int(filament_sensor['gpio_pin']), edge, - callback=self.handle_filamment_detection, bouncetime=200) - except Exception as ex: - self.log_error(ex) - - def stop_filament_detection(self): - try: - self.last_filament_end_detected = [] - for filament_sensor in list(filter( - lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ - 'printer_action'] == 'filament', self.rpi_inputs)): - GPIO.remove_event_detect(self.to_int(filament_sensor['gpio_pin'])) - except Exception as ex: - self.log_error(ex) - - def cancel_all_events_on_queue(self): - for task in self.event_queue: - try: - task['thread'].cancel() - except: - self._logger.warn("Failed to stop task %s.", task) - pass - - def handle_initial_gpio_control(self): - try: - for rpi_input in list( - filter(lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'output_control', - self.rpi_inputs)): - gpio_pin = self.to_int(rpi_input['gpio_pin']) - controlled_io = self.to_int(rpi_input['controlled_io']) - if (rpi_input['edge'] == 'fall') ^ GPIO.input(gpio_pin): - rpi_output = [r_out for r_out in self.rpi_outputs if - self.to_int(r_out['index_id']) == controlled_io].pop() - if rpi_output['output_type'] == 'regular': - val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) - except Exception as ex: - self.log_error(ex) - pass - - def shell_command(self, command): - try: - stdout = (Popen(command, shell=True, stdout=PIPE).stdout).read() - self._plugin_manager.send_plugin_message(self._identifier, - dict(is_msg=True, msg=stdout, msg_type="success")) - except Exception as ex: - self.log_error(ex) - self._plugin_manager.send_plugin_message(self._identifier, - dict(is_msg=True, msg="Could not execute shell script", msg_type="error")) - - def handle_gpio_control(self, channel): - - try: - self._logger.debug("GPIO event triggered on channel %s", channel) - for rpi_input in list( - filter(lambda item: self.to_int(item['gpio_pin']) == self.to_int(channel), self.rpi_inputs)): - gpio_pin = self.to_int(rpi_input['gpio_pin']) - controlled_io = self.to_int(rpi_input['controlled_io']) - if (rpi_input['edge'] == 'fall') ^ GPIO.input(gpio_pin): - rpi_output = [r_out for r_out in self.rpi_outputs if - self.to_int(r_out['index_id']) == controlled_io].pop() - if rpi_output['output_type'] == 'regular': - if rpi_input['controlled_io_set_value'] == 'toggle': - val = GPIO.LOW if GPIO.input( - self.to_int(rpi_output['gpio_pin'])) == GPIO.HIGH else GPIO.HIGH - else: - val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) - for notification in self.notifications: - if notification['gpioAction']: - msg = "GPIO control action caused by input " + str( - rpi_input['label']) + ". Setting GPIO" + str( - rpi_input['controlled_io']) + " to: " + str(rpi_input['controlled_io_set_value']) - self.send_notification(msg) - if rpi_output['output_type'] == 'gcode_output': - self.send_gcode_command(rpi_output['gcode']) - for notification in self.notifications: - if notification['gpioAction']: - msg = "GPIO control action caused by input " + str( - rpi_input['label']) + ". Sending GCODE command" - self.send_notification(msg) - if rpi_output['output_type'] == 'shell_output': - command = rpi_output['shell_script'] - self.shell_command(command) - except Exception as ex: - self.log_error(ex) - pass - - def send_gcode_command(self, command): - for line in command.split('\n'): - if line: - self._printer.commands(line.strip()) - self._logger.info("Sending GCODE command: %s", line.strip()) - time.sleep(0.2) - - def handle_printer_action(self, channel): - try: - for rpi_input in self.rpi_inputs: - if (channel == self.to_int(rpi_input['gpio_pin']) and rpi_input[ - 'action_type'] == 'printer_control' and ( - (rpi_input['edge'] == 'fall') ^ GPIO.input(self.to_int(rpi_input['gpio_pin'])))): - if rpi_input['printer_action'] == 'resume': - self._logger.info("Printer action resume.") - self._printer.resume_print() - elif rpi_input['printer_action'] == 'pause': - self._logger.info("Printer action pause.") - self._printer.pause_print() - elif rpi_input['printer_action'] == 'cancel': - self._logger.info("Printer action cancel.") - self._printer.cancel_print() - elif rpi_input['printer_action'] == 'toggle': - self._logger.info("Printer action toggle.") - if self._printer.is_operational(): - self._printer.toggle_pause_print() - else: - self._printer.connect() - elif rpi_input['printer_action'] == 'start': - self._logger.info("Printer action start.") - self._printer.start_print() - elif rpi_input['printer_action'] == 'toggle_job': - self._logger.info("Printer action toggle_job.") - if self._printer.is_operational(): - if self._printer.is_printing(): - self._printer.cancel_print() - elif self._printer.is_ready(): - self._printer.start_print() - else: - self._printer.connect() - elif rpi_input['printer_action'] == 'stop_temp_hum_control': - self._logger.info("Printer action stopping temperature control.") - for rpi_output in self.rpi_outputs: - if rpi_output['auto_shutdown'] and rpi_output['output_type'] == 'temp_hum_control': - rpi_output['temp_ctr_set_value'] = 0 - self.handle_temp_hum_control() - for notification in self.notifications: - if notification['printer_action']: - msg = "Printer action: " + rpi_input['printer_action'] + " caused by input: " + str( - rpi_input['label']) - self.send_notification(msg) - except Exception as ex: - self.log_error(ex) - pass - - def write_gpio(self, gpio, value, queue_id=None): - try: - GPIO.output(gpio, value) - if queue_id is not None: - self._logger.debug("Running scheduled queue id %s", queue_id) - self._logger.debug("Writing on GPIO: %s value %s", gpio, value) - self.update_ui() - if queue_id is not None: - self.stop_queue_item(queue_id) - except Exception as ex: - template = "An exception of type {0} occurred on {1} when writing on pin {2}. Arguments:\n{3!r}" - message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, gpio, ex.args) - self._logger.warn(message) - pass - - def write_pwm(self, gpio, pwm_value, queue_id=None): - try: - if queue_id is not None: - self._logger.debug("running scheduled queue id %s", queue_id) - for pwm in self.pwm_instances: - if gpio in pwm: - pwm_object = pwm[gpio] - old_pwm_value = pwm['duty_cycle'] if 'duty_cycle' in pwm else -1 - if not self.to_int(old_pwm_value) == self.to_int(pwm_value): - pwm['duty_cycle'] = pwm_value - pwm_object.start(pwm_value) #should be changed back to pwm_object.ChangeDutyCycle() but this - # was causing errors. - self._logger.debug("Writing PWM on gpio: %s value %s", gpio, pwm_value) - self.update_ui() - if queue_id is not None: - self.stop_queue_item(queue_id) - break - except Exception as ex: - self.log_error(ex) - pass - - def get_output_list(self): - result = [] - for rpi_output in self.rpi_outputs: - if rpi_output['output_type'] == 'regular': - result.append(self.to_int(rpi_output['gpio_pin'])) - return result - - def send_notification(self, message): - try: - provider = self._settings.get(["notification_provider"]) - if provider == 'ifttt': - event = self._settings.get(["notification_event_name"]) - api_key = self._settings.get(["notification_api_key"]) - self._logger.debug("Sending notification to: %s with msg: %s with key: %s", provider, message, - api_key) - try: - res = self.ifttt_notification(message, event, api_key) - except requests.exceptions.ConnectionError: - self._logger.info("Error: Could not connect to IFTTT") - except requests.exceptions.HTTPError: - self._logger.info("Error: Received invalid response") - except requests.exceptions.Timeout: - self._logger.info("Error: Request timed out") - except requests.exceptions.TooManyRedirects: - self._logger.info("Error: Too many redirects") - except requests.exceptions.RequestException as reqe: - self._logger.info("Error: {e}".format(e=reqe)) - if res.status_code != requests.codes['ok']: - try: - j = res.json() - except ValueError: - self._logger.info('Error: Could not parse server response. Event not sent') - for err in j['errors']: - self._logger.info('Error: {}'.format(err['message'])) - except Exception as ex: - self.log_error(ex) - pass - - def ifttt_notification(self, message, event, api_key): - url = "https://maker.ifttt.com/trigger/{e}/with/key/{k}/".format(e=event, k=api_key) - payload = {'value1': message} - return requests.post(url, data=payload) - - # ~~ EventPlugin mixin - def on_event(self, event, payload): - if event == Events.CONNECTED: - self.update_ui() - - if event == Events.CLIENT_OPENED: - self.update_ui() - - if event == Events.PRINT_RESUMED: - self.start_filament_detection() - - if event == Events.PRINT_STARTED: - self.print_complete = False - self.cancel_all_events_on_queue() - self.event_queue = [] - self.start_filament_detection() - for rpi_output in self.rpi_outputs: - if rpi_output['auto_startup']: - delay_seconds = self.get_startup_delay_from_output(rpi_output) - self.schedule_auto_startup_outputs(rpi_output, delay_seconds) - if rpi_output['toggle_timer']: - if rpi_output['output_type'] == 'regular' or rpi_output['output_type'] == 'pwm': - self.toggle_output(rpi_output['index_id'], True) - if self.is_hour(rpi_output['shutdown_time']): - shutdown_delay_seconds = self.get_shutdown_delay_from_output(rpi_output) - self.schedule_auto_shutdown_outputs(rpi_output, shutdown_delay_seconds) - self.run_tasks() - self.update_ui() - - elif event == Events.PRINT_DONE: - self.stop_filament_detection() - self.print_complete = True - for rpi_output in self.rpi_outputs: - shutdown_time = rpi_output['shutdown_time'] - if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: - rpi_output['duty_cycle'] = rpi_output['default_duty_cycle'] - if rpi_output['auto_shutdown'] and not self.is_hour(shutdown_time): - delay_seconds = self.to_float(shutdown_time) - self.schedule_auto_shutdown_outputs(rpi_output, delay_seconds) - self.run_tasks() - self.update_ui() - - elif event in (Events.PRINT_CANCELLED, Events.PRINT_FAILED): - self.stop_filament_detection() - self.cancel_all_events_on_queue() - self.event_queue = [] - for rpi_output in self.rpi_outputs: - if rpi_output['shutdown_on_failed']: - shutdown_time = rpi_output['shutdown_time'] - if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: - rpi_output['duty_cycle'] = rpi_output['default_duty_cycle'] - if rpi_output['auto_shutdown'] and not self.is_hour(shutdown_time): - delay_seconds = self.to_float(shutdown_time) - self.schedule_auto_shutdown_outputs(rpi_output, delay_seconds) - if rpi_output['output_type'] == 'temp_hum_control': - rpi_output['temp_ctr_set_value'] = 0 - self.run_tasks() - - if event == Events.PRINT_DONE: - for notification in self.notifications: - if notification['printFinish']: - file_name = os.path.basename(payload["file"]) - elapsed_time_in_seconds = payload["time"] - elapsed_time = octoprint.util.get_formatted_timedelta(timedelta(seconds=elapsed_time_in_seconds)) - msg = "Print job finished: " + file_name + "finished printing in " + file_name, elapsed_time - self.send_notification(msg) - - def run_tasks(self): - for task in self.event_queue: - if not task['thread'].is_alive(): - task['thread'].start() - - def schedule_auto_shutdown_outputs(self, rpi_output, shutdown_delay_seconds): - sufix = 'auto_shutdown' - if rpi_output['output_type'] == 'regular': - value = True if rpi_output['active_low'] else False - self.add_regular_output_to_queue(shutdown_delay_seconds, rpi_output, value, sufix) - if rpi_output['output_type'] == 'ledstrip': - self.ledstrip_set_rgb(rpi_output) - if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: - value = 0 - self.add_pwm_output_to_queue(shutdown_delay_seconds, rpi_output, value, sufix) - if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: - self.schedule_pwm_duty_on_queue(shutdown_delay_seconds, rpi_output, 0, sufix) - if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): - self.add_neopixel_output_to_queue(rpi_output, shutdown_delay_seconds, 0, 0, 0, sufix) - if rpi_output['output_type'] == 'temp_hum_control': - value = 0 - self.add_temperature_output_temperature_queue(shutdown_delay_seconds, rpi_output, value, sufix) - self._logger.debug("Events scheduled to run %s", self.event_queue) - - def ledstrip_set_rgb(self, rpi_output, rgb=None): - clk = rpi_output["ledstrip_gpio_clk"] - data = rpi_output["ledstrip_gpio_dat"] - if clk is not None and data is not None: - ledstrip = LEDStrip(self.to_int(clk), self.to_int(data)) - if rgb is None: - red, green, blue = self.get_color_from_rgb(rpi_output['default_ledstrip_color']) - else: - red, green, blue = self.get_color_from_rgb(rgb) - - self._logger.info("LEDSTRIP set rgb color: %s, %s, %s", red, green, blue) - ledstrip.setcolourrgb(self.to_int(red), self.to_int(green), self.to_int(blue)) - - def start_outpus_with_server(self): - for rpi_output in self.rpi_outputs: - if rpi_output['startup_with_server']: - gpio = self.to_int(rpi_output['gpio_pin']) - if rpi_output['output_type'] == 'regular': - value = False if rpi_output['active_low'] else True - self.write_gpio(gpio, value) - if rpi_output['output_type'] == 'ledstrip': - self.ledstrip_set_rgb(rpi_output) - if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: - value = self.to_int(rpi_output['default_duty_cycle']) - self.write_pwm(gpio, value) - if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): - red, green, blue = self.get_color_from_rgb(rpi_output['default_neopixel_color']) - led_count = rpi_output['neopixel_count'] - led_brightness = rpi_output['neopixel_brightness'] - address = rpi_output['microcontroller_address'] - index_id = self.to_int(rpi_output['index_id']) - neopixel_direct = rpi_output['output_type'] == 'neopixel_direct' - self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, - green, blue, address, neopixel_direct, index_id) - if rpi_output['output_type'] == 'temp_hum_control': - rpi_output['temp_ctr_set_value'] = rpi_output['temp_ctr_default_value'] - - def schedule_auto_startup_outputs(self, rpi_output, delay_seconds): - sufix = 'auto_startup' - if rpi_output['output_type'] == 'regular': - value = False if rpi_output['active_low'] else True - self.add_regular_output_to_queue(delay_seconds, rpi_output, value, sufix) - if rpi_output['output_type'] == 'ledstrip': - self.ledstrip_set_rgb(rpi_output) - if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: - value = self.to_int(rpi_output['default_duty_cycle']) - self.add_pwm_output_to_queue(delay_seconds, rpi_output, value, sufix) - if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): - red, green, blue = self.get_color_from_rgb(rpi_output['default_neopixel_color']) - self.add_neopixel_output_to_queue(rpi_output, delay_seconds, red, green, blue, sufix) - if rpi_output['output_type'] == 'temp_hum_control': - value = rpi_output['temp_ctr_default_value'] - self.add_temperature_output_temperature_queue(delay_seconds, rpi_output, value, sufix) - self._logger.debug("Events scheduled to run %s", self.event_queue) - - def get_color_from_rgb(self, stringColor): - stringColor = stringColor.replace('rgb(', '') - red = stringColor[:stringColor.index(',')] - stringColor = stringColor[stringColor.index(',') + 1:] - green = stringColor[:stringColor.index(',')] - stringColor = stringColor[stringColor.index(',') + 1:] - blue = stringColor[:stringColor.index(')')] - return red, green, blue - - def get_shutdown_delay_from_output(self, rpi_output): - shutdown_time = rpi_output['shutdown_time'] - - shut_down_date_time = self.create_date(shutdown_time) - - if shut_down_date_time < datetime.now(): - shut_down_date_time = shut_down_date_time + timedelta(days=1) - - delay_seconds = (shut_down_date_time - datetime.now()).total_seconds() - - return delay_seconds - - def add_neopixel_output_to_queue(self, rpi_output, delay_seconds, red, green, blue, sufix): - gpio_pin = rpi_output['gpio_pin'] - ledCount = rpi_output['neopixel_count'] - ledBrightness = rpi_output['neopixel_brightness'] - address = rpi_output['microcontroller_address'] - neopixel_direct = rpi_output['output_type'] == 'neopixel_direct' - index_id = self.to_int(rpi_output['index_id']) - - queue_id = '{0!s}_{1!s}'.format(index_id, sufix) - - self._logger.debug("Scheduling neopixel output id %s for on %s delay_seconds", queue_id, delay_seconds) - - thread = threading.Timer(delay_seconds, self.send_neopixel_command, - args=[gpio_pin, ledCount, ledBrightness, red, green, blue, address, neopixel_direct, - index_id, queue_id]) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def add_pwm_output_to_queue(self, delay_seconds, rpi_output, value, sufix): - queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) - - self._logger.debug("Scheduling pwm output id %s for on %s delay_seconds", queue_id, delay_seconds) - - thread = threading.Timer(delay_seconds, self.write_pwm, - args=[self.to_int(rpi_output['gpio_pin']), value, queue_id]) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def schedule_pwm_duty_on_queue(self, delay_seconds, rpi_output, value, sufix): - queue_id = '{0!s}_{1!s}_{2!s}'.format(rpi_output['index_id'], "pwm_linked_temp", sufix) - thread = threading.Timer(delay_seconds, self.set_pwm_duty_cycle, args=[rpi_output, value, queue_id]) - - self._logger.debug("Scheduling pwm linked temp output id %s on %s delay_seconds", queue_id, delay_seconds) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def set_pwm_duty_cycle(self, rpi_output, value, queue_id): - rpi_output['duty_cycle'] = value - if queue_id is not None: - self.stop_queue_item(queue_id) - - def add_regular_output_to_queue(self, delay_seconds, rpi_output, value, sufix): - queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) - - self._logger.debug("Scheduling regular output id %s on %s delay_seconds", queue_id, delay_seconds) - - thread = threading.Timer(delay_seconds, self.write_gpio, - args=[self.to_int(rpi_output['gpio_pin']), value, queue_id]) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def add_temperature_output_temperature_queue(self, delay_seconds, rpi_output, value, sufix): - queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) - self._logger.debug("Scheduling temperature control id %s on %s delay_seconds", queue_id, delay_seconds) - - thread = threading.Timer(delay_seconds, self.write_temperature_to_output, - args=[self.to_int(rpi_output['index_id']), value, queue_id]) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def write_temperature_to_output(self, rpi_output_index, value, queue_id=None): - try: - rpi_output = [r_out for r_out in self.rpi_outputs if - self.to_int(r_out['index_id']) == rpi_output_index].pop() - - if rpi_output['output_type'] == 'temp_hum_control': - rpi_output['temp_ctr_set_value'] = value - - if queue_id is not None: - self._logger.debug("running scheduled queue id %s", queue_id) - self._logger.debug("Setting temperature to output index: %s value %s", rpi_output['index_id'], value) - - self.update_ui() - if queue_id is not None: - self.stop_queue_item(queue_id) - - except Exception as ex: - template = "An exception of type {0} occurred on {1}. Arguments:\n{3!r}" - message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, ex.args) - self._logger.warn(message) - pass - - def get_startup_delay_from_output(self, rpi_output): - start_up_time = rpi_output['startup_time'] - if self.is_hour(start_up_time): - start_up_date_time = self.create_date(start_up_time) - if start_up_date_time < datetime.now(): - delay_seconds = 0.0 - else: - delay_seconds = (start_up_date_time - datetime.now()).total_seconds() - else: - delay_seconds = self.to_float(rpi_output['startup_time']) - return delay_seconds - - # ~~ SettingsPlugin mixin - def on_settings_save(self, data): - outputsBeforeSave = self.get_output_list() - octoprint.plugin.SettingsPlugin.on_settings_save(self, data) - self.rpi_outputs = self._settings.get(["rpi_outputs"]) - self.rpi_inputs = self._settings.get(["rpi_inputs"]) - self.notifications = self._settings.get(["notifications"]) - outputsAfterSave = self.get_output_list() - - commonPins = list(set(outputsBeforeSave) & set(outputsAfterSave)) - - for pin in (pin for pin in outputsBeforeSave if pin not in commonPins): - self.clear_channel(pin) - - self.rpi_outputs_not_changed = commonPins - self.clear_gpio() - - self._logger.debug("rpi_outputs: %s", self.rpi_outputs) - self._logger.debug("rpi_inputs: %s", self.rpi_inputs) - self.setup_gpio() - self.configure_gpio() - self.generate_temp_hum_control_status() - - def get_settings_defaults(self): - return dict(rpi_outputs=[], rpi_inputs=[], - filament_sensor_gcode="G91 ;Set Relative Mode \n" + "G1 E-5.000000 F500 ;Retract 5mm\n" + "G1 Z15 F300 ;move Z up 15mm\n" + "G90 ;Set Absolute Mode\n " + "G1 X20 Y20 F9000 ;Move to hold position\n" + "G91 ;Set Relative Mode\n" + "G1 E-40 F500 ;Retract 40mm\n" + "M0 ;Idle Hold\n" + "G90 ;Set Absolute Mode\n" + "G1 F5000 ;Set speed limits\n" + "G28 X0 Y0 ;Home X Y\n" + "M82 ;Set extruder to Absolute Mode\n" + "G92 E0 ;Set Extruder to 0", - use_sudo=True, neopixel_dma=10, debug=False, gcode_control=False, debug_temperature_log=False, - use_board_pin_number=False, notification_provider="disabled", notification_api_key="", - notification_event_name="printer_event", notifications=[{ - 'printFinish' : True, - 'filamentChange' : True, - 'printer_action' : True, - 'temperatureAction': True, 'gpioAction': True - }]) - - # ~~ TemplatePlugin - def get_template_configs(self): - return [dict(type="settings", custom_bindings=True), dict(type="tab", custom_bindings=True), - dict(type="navbar", custom_bindings=True, suffix="_1", classes=["dropdown"]), - dict(type="navbar", custom_bindings=True, template="enclosure_navbar_input.jinja2", suffix="_2", - classes=["dropdown"])] - - # ~~ AssetPlugin mixin - def get_assets(self): - return dict(js=["js/enclosure.js", "js/bootstrap-colorpicker.min.js"], - css=["css/bootstrap-colorpicker.css", "css/enclosure.css"]) - - # ~~ Softwareupdate hook - def get_update_information(self): - return dict(enclosure=dict(displayName="Enclosure Plugin", displayVersion=self._plugin_version, - # version check: github repository - type="github_release", user="vitormhenrique", repo="OctoPrint-Enclosure", current=self._plugin_version, - # update method: pip - pip="https://github.com/vitormhenrique/OctoPrint-Enclosure/archive/{target_version}.zip")) - - def hook_gcode_queuing(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs): - if self._settings.get(["gcode_control"]) is False: - return - - if cmd.strip().startswith("ENC"): - self._logger.debug("Gcode queuing: %s", cmd) - index_id = self.to_int(self.get_gcode_value(cmd, 'O')) - for output in [item for item in self.rpi_outputs if item['index_id'] == index_id]: - if output['output_type'] == 'regular': - set_value = self.to_int(self.get_gcode_value(cmd, 'S')) - set_value = self.constrain(set_value, 0, 1) - value = True if set_value == 1 else False - value = (not value) if output['active_low'] else value - self.write_gpio(self.to_int(output['gpio_pin']), value) - comm_instance._log("Setting REGULAR output %s to value %s" % (index_id, value)) - return - if output['output_type'] == 'pwm': - set_value = self.to_int(self.get_gcode_value(cmd, 'S')) - set_value = self.constrain(set_value, 0, 100) - output['duty_cycle'] = set_value - self.write_pwm(self.to_int(output['gpio_pin']), set_value) - comm_instance._log("Setting PWM output %s to value %s" % (index_id, set_value)) - return - if output['output_type'] == 'neopixel_indirect' or output['output_type'] == 'neopixel_direct': - red = self.get_gcode_value(cmd, 'R') - green = self.get_gcode_value(cmd, 'G') - blue = self.get_gcode_value(cmd, 'B') - - led_count = output['neopixel_count'] - led_brightness = output['neopixel_brightness'] - address = output['microcontroller_address'] - - index_id = self.to_int(output['index_id']) - - neopixel_direct = output['output_type'] == 'neopixel_direct' - - self.send_neopixel_command(self.to_int(output['gpio_pin']), led_count, led_brightness, red, green, - blue, address, neopixel_direct, index_id) - comm_instance._log( - "Setting NEOPIXEL output %s to red: %s green: %s blue: %s" % (index_id, red, green, blue)) - return - if output['output_type'] == 'temp_hum_control': - set_value = self.to_float(self.get_gcode_value(cmd, 'S')) - should_wait = self.to_int(self.get_gcode_value(cmd, 'W')) - if should_wait == 1 and self._printer.is_printing(): - self._printer.pause_print() - self.waiting_temperature.append(index_id) - output['temp_ctr_set_value'] = set_value - self.update_ui_set_temperature() - self.handle_temp_hum_control() - comm_instance._log("Setting TEMP/HUM control output %s to value %s" % (index_id, set_value)) - return + octoprint.plugin.AssetPlugin, octoprint.plugin.BlueprintPlugin, + octoprint.plugin.EventHandlerPlugin): + rpi_outputs = [] + rpi_inputs = [] + waiting_temperature = [] + rpi_outputs_not_changed = [] + notifications = [] + pwm_instances = [] + event_queue = [] + temp_hum_control_status = [] + temperature_sensor_data = [] + last_filament_end_detected = [] + print_complete = False + development_mode = False + dummy_value = 30.0 + dummy_delta = 0.5 + + def start_timer(self): + """ + Function to start timer that checks enclosure temperature + """ + + self._check_temp_timer = RepeatedTimer(10, self.check_enclosure_temp, None, None, True) + self._check_temp_timer.start() + + @staticmethod + def to_float(value): + """Converts value to flow + Arguments: + value {any} -- value to be + Returns: + float -- value converted + """ + + try: + val = float(value) + return val + except: + return 0 + + @staticmethod + def to_int(value): + try: + val = int(value) + return val + except: + return 0 + + @staticmethod + def is_hour(value): + try: + datetime.strptime(value, '%H:%M') + return True + except: + return False + + @staticmethod + def create_date(value): + temp_string = datetime.now().strftime('%m/%d/%Y') + " " + value + return datetime.strptime(temp_string, '%m/%d/%Y %H:%M') + + @staticmethod + def constrain(n, minn, maxn): + return max(min(maxn, n), minn) + + @staticmethod + def get_gcode_value(command_string, gcode): + semicolon = command_string.find(';') + if not semicolon == -1: + command_string = command_string[:semicolon] + + for command in command_string.split(' '): + index = command.upper().find(gcode.upper()) + if not index == -1: + return command.replace(gcode, '') + return -1 + + # ~~ StartupPlugin mixin + def on_after_startup(self): + self.pwm_instances = [] + self.event_queue = [] + self.rpi_outputs_not_changed = [] + self.rpi_outputs = self._settings.get(["rpi_outputs"]) + self.rpi_inputs = self._settings.get(["rpi_inputs"]) + self.notifications = self._settings.get(["notifications"]) + self.generate_temp_hum_control_status() + self.setup_gpio() + self.configure_gpio() + self.update_ui() + self.start_outpus_with_server() + self.handle_initial_gpio_control() + self.start_timer() + self.print_complete = False + + def get_settings_version(self): + return 6 + + def on_settings_migrate(self, target, current=None): + self._logger.warn("######### current settings version %s target settings version %s #########", current, target) + self._logger.info("######### Current settings #########") + self._logger.info("rpi_outputs: %s", self.rpi_outputs) + self._logger.info("rpi_inputs: %s", self.rpi_inputs) + self._logger.info("######### End Current Settings #########") + if current >= 4 and target == 6: + self._logger.warn("######### migrating settings to v6 #########") + old_outputs = self._settings.get(["rpi_outputs"]) + for rpi_output in old_outputs: + if 'shutdown_on_failed' not in rpi_output: + rpi_output['shutdown_on_failed'] = False + if 'shell_script' not in rpi_output: + rpi_output['shell_script'] = "" + self._settings.set(["rpi_outputs"], old_outputs) + else: + self._logger.warn("######### settings not compatible #########") + self._settings.set(["rpi_outputs"], []) + self._settings.set(["rpi_inputs"], []) + self.rpi_inputs = self._settings.get(["rpi_inputs"]) + + #Scan all configured inputs and outputs and return the pin value + @octoprint.plugin.BlueprintPlugin.route("/ReadPin/", methods=["GET"]) + def ReadSinglePin(self, identifier): + Resp = [] + MatchFound = False + for rpi_input in self.rpi_inputs: + if identifier == self.to_int(rpi_input['gpio_pin']): + MatchFound = True + ConfiguredAs = "Input" + ActiveLow = CheckInputActiveLow(rpi_input['input_pull_resistor']) + pin = self.to_int(rpi_input['gpio_pin']) + val = PinState_Human(pin,ActiveLow) + label = rpi_input['label'] + Resp.append(dict(Configured_As=ConfiguredAs, label=label, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['gpio_pin']): + MatchFound = True + ConfiguredAs = "Output" + ActiveLow = CheckInputActiveLow(rpi_output['active_low']) + pin = self.to_int(rpi_output['gpio_pin']) + val = PinState_Human(pin,ActiveLow) + label = rpi_output['label'] + Resp.append(dict(Configured_As=ConfiguredAs, label=label, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) + if MatchFound == False: + pin = int(identifier) + ConfiguredAs = "Unknown" + ActiveLow = "Unknown" + try: + val = GPIO.input(pin) + except: + val = "GPIO pin not initialized." + Resp.append(dict(Configured_As=ConfiguredAs, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) + return Response(json.dumps(Resp), mimetype='application/json') + + # ~~ Blueprintplugin mixin + @octoprint.plugin.BlueprintPlugin.route("/inputs", methods=["GET"]) + def get_inputs(self): + inputs = [] + for rpi_input in self.rpi_inputs: + index = self.to_int(rpi_input['index_id']) + label = rpi_input['label'] + ActiveLow = CheckInputActiveLow(rpi_input['input_pull_resistor']) + pin = self.to_int(rpi_input['gpio_pin']) + val = PinState_Human(pin,ActiveLow) + inputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, State=val)) + return Response(json.dumps(inputs), mimetype='application/json') + + @octoprint.plugin.BlueprintPlugin.route("/inputs/", methods=["GET"]) + def get_input_status(self, identifier): + for rpi_input in self.rpi_inputs: + if identifier == self.to_int(rpi_input['index_id']): + return Response(json.dumps(rpi_input), mimetype='application/json') + return make_response('', 404) + + + @octoprint.plugin.BlueprintPlugin.route("/temperature/", methods=["PATCH"]) + @restricted_access + def set_enclosure_temp_humidity(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'temperature' not in data: + return make_response("missing temperature attribute", 406) + + set_value = data["temperature"] + + for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == identifier]: + temp_hum_control['temp_ctr_set_value'] = set_value + + self.handle_temp_hum_control() + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/filament/", methods=["PATCH"]) + @restricted_access + def set_filament_sensor(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + for sensor in self.rpi_inputs: + if identifier == self.to_int(sensor['index_id']): + sensor['filament_sensor_enabled'] = value + self._logger.info("Setting filament sensor for input %s to : %s", str(identifier), value) + self._settings.set(["rpi_inputs"], self.rpi_inputs) + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/outputs", methods=["GET"]) + def get_outputs(self): + outputs = [] + for rpi_output in self.rpi_outputs: + if rpi_output['output_type'] == 'regular': + index = self.to_int(rpi_output['index_id']) + label = rpi_output['label'] + pin = self.to_int(rpi_output['gpio_pin']) + ActiveLow = rpi_output['active_low'] + val = PinState_Human(pin,ActiveLow) + outputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, State=val)) + return Response(json.dumps(outputs), mimetype='application/json') + + + @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["GET"]) + def get_output_status(self, identifier): + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + out = copy.deepcopy(rpi_output) + pin = self.to_int(rpi_output['gpio_pin']) + out['current_value'] = PinState_Boolean(pin, rpi_output['active_low'] ) + return Response(json.dumps(out), mimetype='application/json') + return make_response('', 404) + + + @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["PATCH"]) + @restricted_access + def set_io(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + val = (not value) if rpi_output['active_low'] else value + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-startup", methods=["PATCH"]) + @restricted_access + def set_auto_startup(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + if not value: + suffix = 'auto_startup' + queue_id = '{0!s}_{1!s}'.format(str(identifier), suffix) + self.stop_queue_item(queue_id) + for output in self.rpi_outputs: + if identifier == self.to_int(output['index_id']): + output['auto_startup'] = value + self._logger.info("Setting auto startup for output %s to : %s", str(identifier), value) + self._settings.set(["rpi_outputs"], self.rpi_outputs) + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-shutdown", methods=["PATCH"]) + @restricted_access + def set_auto_shutdown(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + if not value: + suffix = 'auto_shutdown' + queue_id = '{0!s}_{1!s}'.format(str(identifier), suffix) + self.stop_queue_item(queue_id) + + for output in self.rpi_outputs: + if identifier == self.to_int(output['index_id']): + output['auto_shutdown'] = value + self._logger.info("Setting auto shutdown for output %s to : %s", str(identifier), value) + self._settings.set(["rpi_outputs"], self.rpi_outputs) + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/pwm/", methods=["PATCH"]) + @restricted_access + def set_pwm(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'duty_cycle' not in data: + return make_response("missing duty_cycle attribute", 406) + + set_value = self.to_int(data['duty_cycle']) + for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == identifier]: + rpi_output['duty_cycle'] = set_value + rpi_output['new_duty_cycle'] = "" + gpio = self.to_int(rpi_output['gpio_pin']) + self.write_pwm(gpio, set_value) + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/rgb-led/", methods=["PATCH"]) + @restricted_access + def set_ledstrip_color(self, identifier): + """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'rgb' not in data: + return make_response("missing rgb attribute", 406) + rgb = data['rgb'] + + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + self.ledstrip_set_rgb(rpi_output, rgb) + + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/neopixel/", methods=["PATCH"]) + @restricted_access + def set_neopixel(self, identifier): + """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'red' not in data: + return make_response("missing red attribute", 406) + if 'green' not in data: + return make_response("missing green attribute", 406) + if 'blue' not in data: + return make_response("missing blue attribute", 406) + + red = data['red'] + green = data['green'] + blue = data['blue'] + + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + led_count = rpi_output['neopixel_count'] + led_brightness = rpi_output['neopixel_brightness'] + address = rpi_output['microcontroller_address'] + + neopixel_dirrect = rpi_output['output_type'] == 'neopixel_direct' + + self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, green, + blue, address, neopixel_dirrect, identifier) + + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/clear-gpio", methods=["POST"]) + @restricted_access + def clear_gpio_mode(self): + GPIO.cleanup() + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/update", methods=["POST"]) + @restricted_access + def update_ui_requested(self): + self.update_ui() + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/shell/", methods=["POST"]) + @restricted_access + def send_shell_command(self, identifier): + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() + + command = rpi_output['shell_script'] + self.shell_command(command) + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/gcode/", methods=["POST"]) + @restricted_access + def requested_gcode_command(self, identifier): + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() + self.send_gcode_command(rpi_output['gcode']) + return make_response('', 204) + + + + + + """ + DEPRECATION + This API will be deprecated in a future version + """ + + # ~~ Blueprintplugin mixin + @octoprint.plugin.BlueprintPlugin.route("/setEnclosureTempHum", methods=["GET"]) + def set_enclosure_temp_humidity_old(self): + set_value = self.to_float(request.values["set_temperature"]) + index_id = self.to_int(request.values["index_id"]) + + for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == index_id]: + temp_hum_control['temp_ctr_set_value'] = set_value + + self.handle_temp_hum_control() + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/clearGPIOMode", methods=["GET"]) + def clear_gpio_mode_old(self): + GPIO.cleanup() + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/updateUI", methods=["GET"]) + def update_ui_requested_old(self): + self.update_ui() + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/getOutputStatus", methods=["GET"]) + def get_output_status_old(self): + gpio_status = [] + for rpi_output in self.rpi_outputs: + if rpi_output['output_type'] == 'regular': + pin = self.to_int(rpi_output['gpio_pin']) + ActiveLow = rpi_output['active_low'] + val = PinState_Boolean(pin, ActiveLow) + val2 = PinState_Human(pin, ActiveLow) + index = self.to_int(rpi_output['index_id']) + gpio_status.append(dict(index_id=index, status=val, State=val2)) + return Response(json.dumps(gpio_status), mimetype='application/json') + + @octoprint.plugin.BlueprintPlugin.route("/setIO", methods=["GET"]) + def set_io_old(self): + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False + for rpi_output in self.rpi_outputs: + if self.to_int(index) == self.to_int(rpi_output['index_id']): + val = (not value) if rpi_output['active_low'] else value + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/sendShellCommand", methods=["GET"]) + def send_shell_command_old(self): + output_index = self.to_int(request.values["index_id"]) + + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == output_index].pop() + + command = rpi_output['shell_script'] + self.shell_command(command) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setAutoStartUp", methods=["GET"]) + def set_auto_startup_old(self): + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False + + if not value: + suffix = 'auto_startup' + queue_id = '{0!s}_{1!s}'.format(index, suffix) + self.stop_queue_item(queue_id) + for output in self.rpi_outputs: + if self.to_int(index) == self.to_int(output['index_id']): + output['auto_startup'] = value + self._logger.info("Setting auto startup for output %s to : %s", index, value) + self._settings.set(["rpi_outputs"], self.rpi_outputs) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setAutoShutdown", methods=["GET"]) + def set_auto_shutdown_old(self): + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False + + if not value: + suffix = 'auto_shutdown' + queue_id = '{0!s}_{1!s}'.format(index, suffix) + self.stop_queue_item(queue_id) + + for output in self.rpi_outputs: + if self.to_int(index) == self.to_int(output['index_id']): + output['auto_shutdown'] = value + self._logger.info("Setting auto shutdown for output %s to : %s", index, value) + self._settings.set(["rpi_outputs"], self.rpi_outputs) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setFilamentSensor", methods=["GET"]) + def set_filament_sensor_old(self): + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False + for sensor in self.rpi_inputs: + if self.to_int(index) == self.to_int(sensor['index_id']): + sensor['filament_sensor_enabled'] = value + self._logger.info("Setting filament sensor for input %s to : %s", index, value) + self._settings.set(["rpi_inputs"], self.rpi_inputs) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setPWM", methods=["GET"]) + def set_pwm_old(self): + set_value = self.to_int(request.values["new_duty_cycle"]) + index_id = self.to_int(request.values["index_id"]) + for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == index_id]: + rpi_output['duty_cycle'] = set_value + rpi_output['new_duty_cycle'] = "" + gpio = self.to_int(rpi_output['gpio_pin']) + self.write_pwm(gpio, set_value) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/sendGcodeCommand", methods=["GET"]) + def requested_gcode_command_old(self): + gpio_index = self.to_int(request.values["index_id"]) + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == gpio_index].pop() + self.send_gcode_command(rpi_output['gcode']) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setNeopixel", methods=["GET"]) + def set_neopixel_old(self): + """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" + gpio_index = self.to_int(request.values["index_id"]) + red = request.values["red"] + green = request.values["green"] + blue = request.values["blue"] + for rpi_output in self.rpi_outputs: + if gpio_index == self.to_int(rpi_output['index_id']): + led_count = rpi_output['neopixel_count'] + led_brightness = rpi_output['neopixel_brightness'] + address = rpi_output['microcontroller_address'] + + neopixel_dirrect = rpi_output['output_type'] == 'neopixel_direct' + + self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, green, + blue, address, neopixel_dirrect, gpio_index) + + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setLedstripColor", methods=["GET"]) + def set_ledstrip_color_old(self): + """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" + gpio_index = self.to_int(request.values["index_id"]) + rgb = request.values["rgb"] + for rpi_output in self.rpi_outputs: + if gpio_index == self.to_int(rpi_output['index_id']): + self.ledstrip_set_rgb(rpi_output, rgb) + + return jsonify(success=True) + + # DEPREACTION END + + + + + + def send_neopixel_command(self, led_pin, led_count, led_brightness, red, green, blue, address, neopixel_dirrect, + index_id, queue_id=None): + """Send neopixel command + Arguments: + led_pin {int} -- GPIO number + ledCount {int} -- number of LEDS + ledBrightness {int} -- brightness from 0 to 255 + red {int} -- red value from 0 to 255 + green {int} -- green value from 0 to 255 + blue {int} -- blue value from 0 to 255 + address {int} -- i2c address from microcontroller + """ + + try: + + for rpi_output in self.rpi_outputs: + if self.to_int(index_id) == self.to_int(rpi_output['index_id']): + rpi_output['neopixel_color'] = 'rgb({0!s},{1!s},{2!s})'.format(red, green, blue) + + if address == '': + address = 0 + + if neopixel_dirrect: + script = os.path.dirname(os.path.realpath(__file__)) + "/neopixel_direct.py " + else: + script = os.path.dirname(os.path.realpath(__file__)) + "/neopixel_indirect.py " + + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + + cmd = sudo_str + "python " + script + str(led_pin) + " " + str(led_count) + " " + str( + led_brightness) + " " + str(red) + " " + str(green) + " " + str(blue) + " " + + if neopixel_dirrect: + dma = self._settings.get(["neopixel_dma"]) or 10 + cmd = cmd + str(dma) + else: + cmd = cmd + str(address) + + if queue_id is not None: + self._logger.debug("running scheduled queue id %s", queue_id) + self._logger.debug("Sending neopixel cmd: %s", cmd) + Popen(cmd, shell=True) + if queue_id is not None: + self.stop_queue_item(queue_id) + except Exception as ex: + self.log_error(ex) + + def check_enclosure_temp(self): + try: + sensor_data = [] + for sensor in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): + temp, hum = self.get_sensor_data(sensor) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Sensor %s Temperature: %s humidity %s", sensor['label'], temp, hum) + if temp is not None and hum is not None: + sensor["temp_sensor_temp"] = temp + sensor["temp_sensor_humidity"] = hum + sensor_data.append(dict(index_id=sensor['index_id'], temperature=temp, humidity=hum)) + self.temperature_sensor_data = sensor_data + self.handle_temp_hum_control() + self.handle_temperature_events() + self.handle_pwm_linked_temperature() + self.update_ui() + except Exception as ex: + self.log_error(ex) + + def toggle_output(self, output_index, first_run=False): + for output in [item for item in self.rpi_outputs if item['index_id'] == output_index]: + gpio_pin = self.to_int(output['gpio_pin']) + index_id = self.to_int(output['index_id']) + + if output['output_type'] == 'regular': + if first_run: + current_value = False + else: + current_value = (not GPIO.input(gpio_pin)) if output['active_low'] else GPIO.input(gpio_pin) + + if current_value: + time_delay = self.to_int(output['toggle_timer_off']) + else: + time_delay = self.to_int(output['toggle_timer_on']) + + if not self.print_complete: + self.write_gpio(gpio_pin, not current_value) + thread = threading.Timer(time_delay, self.toggle_output, args=[index_id]) + thread.start() + else: + off_value = True if output['active_low'] else False + self.write_gpio(gpio_pin, off_value) + self.update_ui_outputs() + return + + if output['output_type'] == 'pwm': + for pwm in self.pwm_instances: + if gpio_pin in pwm: + if first_run: + current_pwm_value = 0 + else: + if 'duty_cycle' in pwm: + current_pwm_value = pwm['duty_cycle'] + current_pwm_value = self.to_int(current_pwm_value) + else: + current_pwm_value = 0 + + if not current_pwm_value == 0: + time_delay = self.to_int(output['toggle_timer_off']) + write_value = 0 + else: + time_delay = self.to_int(output['toggle_timer_on']) + write_value = self.to_int(output['default_duty_cycle']) + + if not self.print_complete: + self.write_pwm(gpio_pin, write_value) + thread = threading.Timer(time_delay, self.toggle_output, args=[index_id]) + thread.start() + else: + self.write_pwm(self.to_int(output['gpio_pin']), 0) + self.update_ui_outputs() + return + + def update_ui(self): + self.update_ui_outputs() + self.update_ui_current_temperature() + self.update_ui_set_temperature() + self.update_ui_inputs() + + def update_ui_current_temperature(self): + self._plugin_manager.send_plugin_message(self._identifier, dict(sensor_data=self.temperature_sensor_data)) + + def update_ui_set_temperature(self): + result = [] + for temp_crt_output in list(filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): + set_temperature = self.to_float(temp_crt_output['temp_ctr_set_value']) + result.append(dict(index_id=temp_crt_output['index_id'], set_temperature=set_temperature)) + result.append(set_temperature) + self._plugin_manager.send_plugin_message(self._identifier, dict(set_temperature=result)) + + def stop_queue_item(self, queue_id): + old_list = self.event_queue + self._logger.debug("Stopping queue id %s...", queue_id) + for task in self.event_queue: + self._logger.debug("Queue id found...") + if task['queue_id'] == queue_id: + task['thread'].cancel() + self.event_queue.remove(task) + self._logger.debug("Queue id stopped and removed from list...") + self._logger.debug("Old queue list: %s", old_list) + self._logger.debug("New queue list: %s", self.event_queue) + + def update_ui_outputs(self): + try: + regular_status = [] + pwm_status = [] + neopixel_status = [] + temp_control_status = [] + for output in self.rpi_outputs: + index = self.to_int(output['index_id']) + pin = self.to_int(output['gpio_pin']) + startup = output['auto_startup'] + shutdown = output['auto_shutdown'] + + if output['output_type'] == 'regular': + val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) + regular_status.append( + dict(index_id=index, status=val, auto_startup=startup, auto_shutdown=shutdown)) + if output['output_type'] == 'temp_hum_control': + val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) + temp_control_status.append( + dict(index_id=index, status=val, auto_startup=startup, auto_shutdown=shutdown)) + if output['output_type'] == 'neopixel_indirect' or output['output_type'] == 'neopixel_direct': + val = output['neopixel_color'] + neopixel_status.append( + dict(index_id=index, color=val, auto_startup=startup, auto_shutdown=shutdown)) + if output['output_type'] == 'pwm': + for pwm in self.pwm_instances: + if pin in pwm: + if 'duty_cycle' in pwm: + pwm_val = pwm['duty_cycle'] + val = self.to_int(pwm_val) + else: + val = 0 + pwm_status.append( + dict(index_id=index, pwm_value=val, auto_startup=startup, auto_shutdown=shutdown)) + self._plugin_manager.send_plugin_message(self._identifier, + dict(rpi_output_regular=regular_status, rpi_output_pwm=pwm_status, + rpi_output_neopixel=neopixel_status, + rpi_output_temp_hum_ctrl=temp_control_status)) + except Exception as ex: + self.log_error(ex) + + def update_ui_inputs(self): + try: + sensor_status = [] + for sensor in self.rpi_inputs: + if sensor['input_type'] == 'gpio' and sensor['action_type'] == 'printer_control' and sensor[ + 'printer_action'] == 'filament': + index = self.to_int(sensor['index_id']) + value = sensor['filament_sensor_enabled'] + sensor_status.append(dict(index_id=index, filament_sensor_enabled=value)) + self._plugin_manager.send_plugin_message(self._identifier, dict(filament_sensor_status=sensor_status)) + except Exception as ex: + self.log_error(ex) + + def get_sensor_data(self, sensor): + try: + if self.development_mode: + temp, hum = self.read_dummy_temp() + else: + if sensor['temp_sensor_type'] in ["11", "22", "2302"]: + temp, hum = self.read_dht_temp(sensor['temp_sensor_type'], sensor['gpio_pin']) + elif sensor['temp_sensor_type'] == "18b20": + temp = self.read_18b20_temp(sensor['ds18b20_serial']) + hum = 0 + elif sensor['temp_sensor_type'] == "bme280": + temp, hum = self.read_bme280_temp(sensor['temp_sensor_address']) + elif sensor['temp_sensor_type'] == "am2320": + temp, hum = self.read_am2320_temp() # sensor has fixed address + elif sensor['temp_sensor_type'] == "si7021": + temp, hum = self.read_si7021_temp(sensor['temp_sensor_address'], sensor['temp_sensor_i2cbus']) + elif sensor['temp_sensor_type'] == "tmp102": + temp = self.read_tmp102_temp(sensor['temp_sensor_address']) + hum = 0 + elif sensor['temp_sensor_type'] == "max31855": + temp = self.read_max31855_temp(sensor['temp_sensor_address']) + hum = 0 + elif sensor['temp_sensor_type'] == "mcp9808": + temp = self.read_mcp_temp(sensor['temp_sensor_address']) + hum = 0 + else: + self._logger.info("temp_sensor_type no match") + temp = None + hum = None + if temp != -1 and hum != -1: + temp = round(self.to_float(temp), 1) if not sensor['use_fahrenheit'] else round( + self.to_float(temp) * 1.8 + 32, 1) + hum = round(self.to_float(hum), 1) + return temp, hum + return None, None + except Exception as ex: + self.log_error(ex) + + def handle_temperature_events(self): + for temperature_alarm in [item for item in self.rpi_outputs if item['output_type'] == 'temperature_alarm']: + set_temperature = self.to_float(temperature_alarm['alarm_set_temp']) + if int(set_temperature) is 0: + continue + linked_data = [item for item in self.temperature_sensor_data if + item['index_id'] == temperature_alarm['linked_temp_sensor']].pop() + sensor_temperature = self.to_float(linked_data['temperature']) + if set_temperature < sensor_temperature: + for rpi_controlled_output in self.rpi_outputs: + if self.to_int(temperature_alarm['controlled_io']) == self.to_int( + rpi_controlled_output['index_id']): + val = GPIO.LOW if temperature_alarm['controlled_io_set_value'] == 'low' else GPIO.HIGH + self.write_gpio(self.to_int(rpi_controlled_output['gpio_pin']), val) + for notification in self.notifications: + if notification['temperatureAction']: + msg = ("Temperature action: enclosure temperature exceed " + temperature_alarm[ + 'alarm_set_temp']) + self.send_notification(msg) + + def read_dummy_temp(self): + current_value = self.dummy_value + if current_value > 40 or current_value < 30: + self.dummy_delta = - self.dummy_delta + + return_value = current_value + self.dummy_delta + + self.dummy_value = return_value + + return return_value, return_value + + def read_mcp_temp(self, address): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/mcp9808.py" + args = ["python", script, str(address)] + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature MCP9808 cmd: %s", " ".join(args)) + proc = Popen(args, stdout=PIPE) + stdout, _ = proc.communicate() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("MCP9808 result: %s", stdout) + return self.to_float(stdout.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 + + def read_dht_temp(self, sensor, pin): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/getDHTTemp.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python " + script + str(sensor) + " " + str(pin) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature dht cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Dht result: %s", stdout) + temp, hum = stdout.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 of the plugin.") + self.log_error(ex) + return (0, 0) + + def read_bme280_temp(self, address): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/BME280.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python " + script + str(address) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature BME280 cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("BME280 result: %s", stdout) + temp, hum = stdout.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 of the plugin.") + self.log_error(ex) + return (0, 0) + + def read_am2320_temp(self): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/AM2320.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python " + script # sensor has fixed address 0x5C + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature AM2320 cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("AM2320 result: %s", stdout) + temp, hum = stdout.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 of the plugin.") + self.log_error(ex) + return (0, 0) + + def read_si7021_temp(self, address, i2cbus): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/SI7021.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python " + script + str(address) + " " + str(i2cbus) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature SI7021 cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("SI7021 result: %s", stdout) + temp, hum = stdout.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 of the plugin.") + self.log_error(ex) + return (0, 0) + + def read_18b20_temp(self, serial_number): + os.system('modprobe w1-gpio') + os.system('modprobe w1-therm') + + lines = self.read_raw_18b20_temp(serial_number) + while lines[0].strip()[-3:] != 'YES': + time.sleep(0.2) + lines = self.read_raw_18b20_temp(serial_number) + equals_pos = lines[1].find('t=') + if equals_pos != -1: + temp_string = lines[1][equals_pos + 2:] + temp_c = float(temp_string) / 1000. + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("DS18B20 result: %s", temp_c) + return '{0:0.1f}'.format(temp_c) + return 0 + + def read_raw_18b20_temp(self, serial_number): + base_dir = '/sys/bus/w1/devices/' + device_folder = glob.glob(base_dir + str(serial_number) + '*')[0] + device_file = device_folder + '/w1_slave' + device_file_result = open(device_file, 'r') + lines = device_file_result.readlines() + device_file_result.close() + return lines + + def read_tmp102_temp(self, address): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/tmp102.py" + args = ["python", script, str(address)] + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature TMP102 cmd: %s", " ".join(args)) + proc = Popen(args, stdout=PIPE) + stdout, _ = proc.communicate() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("TMP102 result: %s", stdout) + return self.to_float(stdout.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 + + def read_max31855_temp(self, address): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/max31855.py" + args = ["python", script, str(address)] + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature MAX31855 cmd: %s", " ".join(args)) + proc = Popen(args, stdout=PIPE) + stdout, _ = proc.communicate() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("MAX31855 result: %s", stdout) + return self.to_float(stdout.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 + + def handle_pwm_linked_temperature(self): + try: + for pwm_output in list(filter(lambda item: item['output_type'] == 'pwm' and item['pwm_temperature_linked'], + self.rpi_outputs)): + gpio_pin = self.to_int(pwm_output['gpio_pin']) + if self._printer.is_printing(): + index_id = self.to_int(pwm_output['index_id']) + linked_id = self.to_int(pwm_output['linked_temp_sensor']) + linked_data = self.get_linked_temp_sensor_data(linked_id) + current_temp = self.to_float(linked_data['temperature']) + + duty_a = self.to_float(pwm_output['duty_a']) + duty_b = self.to_float(pwm_output['duty_b']) + temp_a = self.to_float(pwm_output['temperature_a']) + temp_b = self.to_float(pwm_output['temperature_b']) + + try: + calculated_duty = ((current_temp - temp_a) * (duty_b - duty_a) / (temp_b - temp_a)) + duty_a + + if current_temp < temp_a: + calculated_duty = 0 + except: + calculated_duty = 0 + + self._logger.debug("Calculated duty for PWM %s is %s", index_id, calculated_duty) + elif self.print_complete: + calculated_duty = self.to_int(pwm_output['duty_cycle']) + else: + calculated_duty = 0 + + self.write_pwm(gpio_pin, self.constrain(calculated_duty, 0, 100)) + + except Exception as ex: + self.log_error(ex) + + def get_linked_temp_sensor_data(self, linked_id): + try: + linked_data = [data for data in self.temperature_sensor_data if data['index_id'] == linked_id].pop() + return linked_data + except: + self._logger.warn("No linked temperature sensor found for %s", linked_id) + return None + + def handle_temp_hum_control(self): + try: + for temp_hum_control in list( + filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): + + set_temperature = self.to_float(temp_hum_control['temp_ctr_set_value']) + temp_deadband = self.to_float(temp_hum_control['temp_ctr_deadband']) + max_temp = self.to_float(temp_hum_control['temp_ctr_max_temp']) + + linked_id = temp_hum_control['linked_temp_sensor'] + + previous_status = list(filter(lambda item: item['index_id'] == temp_hum_control['index_id'], + self.temp_hum_control_status)).pop()['status'] + + if set_temperature == 0: + current_status = False + else: + linked_data = self.get_linked_temp_sensor_data(linked_id) + + control_type = str(temp_hum_control['temp_ctr_type']) + + if control_type == 'dehumidifier': + current_value = self.to_float(linked_data['humidity']) + temp_deadband = 0 + else: + current_value = self.to_float(linked_data['temperature']) + + if control_type == 'cooler' or control_type == 'dehumidifier': + if current_value <= set_temperature and current_value >= (set_temperature - temp_deadband): + current_status = previous_status + elif current_value < set_temperature: + current_status = False + else: + current_status = True + else: + if current_value <= set_temperature and current_value >= (set_temperature - temp_deadband): + current_status = previous_status + elif current_value > set_temperature: + current_status = False + else: + current_status = True + + if control_type == 'heater' and max_temp > 0.0 and max_temp < current_value: + self._logger.debug("Maximum temperature reached for temperature control %s", + temp_hum_control['index_id']) + temp_hum_control['temp_ctr_set_value'] = 0 + current_status = False + + if current_status != previous_status: + if current_status: + self._logger.info("Turning gpio to control temperature on.") + val = False if temp_hum_control['active_low'] else True + self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) + else: + index_id = temp_hum_control['index_id'] + if index_id in self.waiting_temperature: + self.waiting_temperature.remove(index_id) + + if not self.waiting_temperature and self._printer.is_paused(): + self._printer.resume_print() + + self._logger.info("Turning gpio to control temperature off.") + val = True if temp_hum_control['active_low'] else False + self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) + for control_status in self.temp_hum_control_status: + if control_status['index_id'] == temp_hum_control['index_id']: + control_status['status'] = current_status + except Exception as ex: + self.log_error(ex) + + def log_error(self, ex): + template = "An exception of type {0} occurred on {1}. Arguments:\n{2!r}" + message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, ex.args) + self._logger.warn(message) + + def setup_gpio(self): + try: + current_mode = GPIO.getmode() + set_mode = GPIO.BOARD if self._settings.get(["use_board_pin_number"]) else GPIO.BCM + if current_mode is None: + outputs = list(filter( + lambda item: item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ + 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct', + self.rpi_outputs)) + inputs = list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)) + gpios = outputs + inputs + if gpios: + GPIO.setmode(set_mode) + tempstr = "BOARD" if set_mode == GPIO.BOARD else "BCM" + self._logger.info("Setting GPIO mode to %s", tempstr) + elif current_mode != set_mode: + GPIO.setmode(current_mode) + tempstr = "BOARD" if current_mode == GPIO.BOARD else "BCM" + self._settings.set(["use_board_pin_number"], True if current_mode == GPIO.BOARD else False) + warn_msg = "GPIO mode was configured before, GPIO mode will be forced to use: " + tempstr + " as pin numbers. Please update GPIO accordingly!" + self._logger.info(warn_msg) + self._plugin_manager.send_plugin_message(self._identifier, + dict(is_msg=True, msg=warn_msg, msg_type="error")) + GPIO.setwarnings(False) + except Exception as ex: + self.log_error(ex) + + def clear_gpio(self): + try: + + for gpio_out in list(filter( + lambda item: item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ + 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct', + self.rpi_outputs)): + gpio_pin = self.to_int(gpio_out['gpio_pin']) + if gpio_pin not in self.rpi_outputs_not_changed: + GPIO.cleanup(gpio_pin) + + for gpio_in in list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)): + try: + GPIO.remove_event_detect(self.to_int(gpio_in['gpio_pin'])) + except: + pass + GPIO.cleanup(self.to_int(gpio_in['gpio_pin'])) + except Exception as ex: + self.log_error(ex) + + def clear_channel(self, channel): + try: + GPIO.cleanup(self.to_int(channel)) + self._logger.debug("Clearing channel %s", channel) + except Exception as ex: + self.log_error(ex) + + def generate_temp_hum_control_status(self): + status = [] + for temp_hum_control in list(filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): + status.append(dict(index_id=temp_hum_control['index_id'], status=False)) + self.temp_hum_control_status = status + + def configure_gpio(self): + try: + + for gpio_out in list( + filter(lambda item: item['output_type'] == 'regular' or item['output_type'] == 'temp_hum_control', + self.rpi_outputs)): + initial_value = GPIO.HIGH if gpio_out['active_low'] else GPIO.LOW + pin = self.to_int(gpio_out['gpio_pin']) + if pin not in self.rpi_outputs_not_changed: + self._logger.info("Setting GPIO pin %s as OUTPUT with initial value: %s", pin, initial_value) + GPIO.setup(pin, GPIO.OUT, initial=initial_value) + for gpio_out_pwm in list(filter(lambda item: item['output_type'] == 'pwm', self.rpi_outputs)): + pin = self.to_int(gpio_out_pwm['gpio_pin']) + self._logger.info("Setting GPIO pin %s as PWM", pin) + for pwm in (pwm_dict for pwm_dict in self.pwm_instances if pin in pwm_dict): + self.pwm_instances.remove(pwm) + self.clear_channel(pin) + GPIO.setup(pin, GPIO.OUT) + pwm_instance = GPIO.PWM(pin, self.to_int(gpio_out_pwm['pwm_frequency'])) + self._logger.info("starting PWM on pin %s", pin) + pwm_instance.start(0) + self.pwm_instances.append({pin: pwm_instance}) + for gpio_out_neopixel in list( + filter(lambda item: item['output_type'] == 'neopixel_direct', self.rpi_outputs)): + pin = self.to_int(gpio_out_neopixel['gpio_pin']) + self.clear_channel(pin) + + for rpi_input in list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)): + gpio_pin = self.to_int(rpi_input['gpio_pin']) + pull_resistor = GPIO.PUD_UP if rpi_input['input_pull_resistor'] == 'input_pull_up' else GPIO.PUD_DOWN + GPIO.setup(gpio_pin, GPIO.IN, pull_resistor) + edge = GPIO.RISING if rpi_input['edge'] == 'rise' else GPIO.FALLING + + inputs_same_gpio = list( + [r_inp for r_inp in self.rpi_inputs if self.to_int(r_inp['gpio_pin']) == gpio_pin]) + + if len(inputs_same_gpio) > 1: + GPIO.remove_event_detect(gpio_pin) + for other_input in inputs_same_gpio: + if other_input['edge'] is not edge: + edge = GPIO.BOTH + + if rpi_input['action_type'] == 'output_control': + self._logger.info("Adding GPIO event detect on pin %s with edge: %s", gpio_pin, edge) + GPIO.add_event_detect(gpio_pin, edge, callback=self.handle_gpio_control, bouncetime=200) + if (rpi_input['action_type'] == 'printer_control' and rpi_input['printer_action'] != 'filament'): + GPIO.add_event_detect(gpio_pin, edge, callback=self.handle_printer_action, bouncetime=200) + self._logger.info("Adding PRINTER CONTROL event detect on pin %s with edge: %s", gpio_pin, edge) + except Exception as ex: + self.log_error(ex) + + def handle_filamment_detection(self, channel): + try: + for filament_sensor in list(filter( + lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ + 'printer_action'] == 'filament' and self.to_int(item['gpio_pin']) == self.to_int(channel), + self.rpi_inputs)): + if ((filament_sensor['edge'] == 'fall') ^ (GPIO.input(self.to_int(filament_sensor['gpio_pin']))) and + filament_sensor['filament_sensor_enabled']): + last_detected_time = list(filter(lambda item: item['index_id'] == filament_sensor['index_id'], + self.last_filament_end_detected)).pop()['time'] + time_now = time.time() + time_difference = self.to_int(time_now - last_detected_time) + time_out_value = self.to_int(filament_sensor['filament_sensor_timeout']) + if time_difference > time_out_value: + self._logger.info("Detected end of filament.") + for item in self.last_filament_end_detected: + if item['index_id'] == filament_sensor['index_id']: + item['time'] = time_now + for line in self._settings.get(["filament_sensor_gcode"]).split('\n'): + if line: + self._printer.commands(line.strip()) + self._logger.info("Sending GCODE command: %s", line.strip()) + time.sleep(0.2) + for notification in self.notifications: + if notification['filamentChange']: + msg = "Filament change action caused by sensor: " + str(filament_sensor['label']) + self.send_notification(msg) + else: + self._logger.info("Prevented end of filament detection, filament sensor timeout not elapsed.") + except Exception as ex: + self.log_error(ex) + + def start_filament_detection(self): + self.stop_filament_detection() + try: + for filament_sensor in list(filter( + lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ + 'printer_action'] == 'filament', self.rpi_inputs)): + edge = GPIO.RISING if filament_sensor['edge'] == 'rise' else GPIO.FALLING + if GPIO.input(self.to_int(filament_sensor['gpio_pin'])) == (edge == GPIO.RISING): + self._printer.pause_print() + self._logger.info("Started printing with no filament.") + else: + self.last_filament_end_detected.append(dict(index_id=filament_sensor['index_id'], time=0)) + self._logger.info("Adding GPIO event detect on pin %s with edge: %s", filament_sensor['gpio_pin'], + edge) + GPIO.add_event_detect(self.to_int(filament_sensor['gpio_pin']), edge, + callback=self.handle_filamment_detection, bouncetime=200) + except Exception as ex: + self.log_error(ex) + + def stop_filament_detection(self): + try: + self.last_filament_end_detected = [] + for filament_sensor in list(filter( + lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ + 'printer_action'] == 'filament', self.rpi_inputs)): + GPIO.remove_event_detect(self.to_int(filament_sensor['gpio_pin'])) + except Exception as ex: + self.log_error(ex) + + def cancel_all_events_on_queue(self): + for task in self.event_queue: + try: + task['thread'].cancel() + except: + self._logger.warn("Failed to stop task %s.", task) + pass + + def handle_initial_gpio_control(self): + try: + for rpi_input in list( + filter(lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'output_control', + self.rpi_inputs)): + gpio_pin = self.to_int(rpi_input['gpio_pin']) + controlled_io = self.to_int(rpi_input['controlled_io']) + if (rpi_input['edge'] == 'fall') ^ GPIO.input(gpio_pin): + rpi_output = [r_out for r_out in self.rpi_outputs if + self.to_int(r_out['index_id']) == controlled_io].pop() + if rpi_output['output_type'] == 'regular': + val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + except Exception as ex: + self.log_error(ex) + pass + + def shell_command(self, command): + try: + stdout = (Popen(command, shell=True, stdout=PIPE).stdout).read() + self._plugin_manager.send_plugin_message(self._identifier, + dict(is_msg=True, msg=stdout, msg_type="success")) + except Exception as ex: + self.log_error(ex) + self._plugin_manager.send_plugin_message(self._identifier, + dict(is_msg=True, msg="Could not execute shell script", msg_type="error")) + + def handle_gpio_control(self, channel): + + try: + self._logger.debug("GPIO event triggered on channel %s", channel) + for rpi_input in list( + filter(lambda item: self.to_int(item['gpio_pin']) == self.to_int(channel), self.rpi_inputs)): + gpio_pin = self.to_int(rpi_input['gpio_pin']) + controlled_io = self.to_int(rpi_input['controlled_io']) + if (rpi_input['edge'] == 'fall') ^ GPIO.input(gpio_pin): + rpi_output = [r_out for r_out in self.rpi_outputs if + self.to_int(r_out['index_id']) == controlled_io].pop() + if rpi_output['output_type'] == 'regular': + if rpi_input['controlled_io_set_value'] == 'toggle': + val = GPIO.LOW if GPIO.input( + self.to_int(rpi_output['gpio_pin'])) == GPIO.HIGH else GPIO.HIGH + else: + val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + for notification in self.notifications: + if notification['gpioAction']: + msg = "GPIO control action caused by input " + str( + rpi_input['label']) + ". Setting GPIO" + str( + rpi_input['controlled_io']) + " to: " + str(rpi_input['controlled_io_set_value']) + self.send_notification(msg) + if rpi_output['output_type'] == 'gcode_output': + self.send_gcode_command(rpi_output['gcode']) + for notification in self.notifications: + if notification['gpioAction']: + msg = "GPIO control action caused by input " + str( + rpi_input['label']) + ". Sending GCODE command" + self.send_notification(msg) + if rpi_output['output_type'] == 'shell_output': + command = rpi_output['shell_script'] + self.shell_command(command) + except Exception as ex: + self.log_error(ex) + pass + + def send_gcode_command(self, command): + for line in command.split('\n'): + if line: + self._printer.commands(line.strip()) + self._logger.info("Sending GCODE command: %s", line.strip()) + time.sleep(0.2) + + def handle_printer_action(self, channel): + try: + for rpi_input in self.rpi_inputs: + if (channel == self.to_int(rpi_input['gpio_pin']) and rpi_input[ + 'action_type'] == 'printer_control' and ( + (rpi_input['edge'] == 'fall') ^ GPIO.input(self.to_int(rpi_input['gpio_pin'])))): + if rpi_input['printer_action'] == 'resume': + self._logger.info("Printer action resume.") + self._printer.resume_print() + elif rpi_input['printer_action'] == 'pause': + self._logger.info("Printer action pause.") + self._printer.pause_print() + elif rpi_input['printer_action'] == 'cancel': + self._logger.info("Printer action cancel.") + self._printer.cancel_print() + elif rpi_input['printer_action'] == 'toggle': + self._logger.info("Printer action toggle.") + if self._printer.is_operational(): + self._printer.toggle_pause_print() + else: + self._printer.connect() + elif rpi_input['printer_action'] == 'start': + self._logger.info("Printer action start.") + self._printer.start_print() + elif rpi_input['printer_action'] == 'toggle_job': + self._logger.info("Printer action toggle_job.") + if self._printer.is_operational(): + if self._printer.is_printing(): + self._printer.cancel_print() + elif self._printer.is_ready(): + self._printer.start_print() + else: + self._printer.connect() + elif rpi_input['printer_action'] == 'stop_temp_hum_control': + self._logger.info("Printer action stopping temperature control.") + for rpi_output in self.rpi_outputs: + if rpi_output['auto_shutdown'] and rpi_output['output_type'] == 'temp_hum_control': + rpi_output['temp_ctr_set_value'] = 0 + self.handle_temp_hum_control() + for notification in self.notifications: + if notification['printer_action']: + msg = "Printer action: " + rpi_input['printer_action'] + " caused by input: " + str( + rpi_input['label']) + self.send_notification(msg) + except Exception as ex: + self.log_error(ex) + pass + + def write_gpio(self, gpio, value, queue_id=None): + try: + GPIO.output(gpio, value) + if queue_id is not None: + self._logger.debug("Running scheduled queue id %s", queue_id) + self._logger.debug("Writing on GPIO: %s value %s", gpio, value) + self.update_ui() + if queue_id is not None: + self.stop_queue_item(queue_id) + except Exception as ex: + template = "An exception of type {0} occurred on {1} when writing on pin {2}. Arguments:\n{3!r}" + message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, gpio, ex.args) + self._logger.warn(message) + pass + + def write_pwm(self, gpio, pwm_value, queue_id=None): + try: + if queue_id is not None: + self._logger.debug("running scheduled queue id %s", queue_id) + for pwm in self.pwm_instances: + if gpio in pwm: + pwm_object = pwm[gpio] + old_pwm_value = pwm['duty_cycle'] if 'duty_cycle' in pwm else -1 + if not self.to_int(old_pwm_value) == self.to_int(pwm_value): + pwm['duty_cycle'] = pwm_value + pwm_object.start(pwm_value) #should be changed back to pwm_object.ChangeDutyCycle() but this + # was causing errors. + self._logger.debug("Writing PWM on gpio: %s value %s", gpio, pwm_value) + self.update_ui() + if queue_id is not None: + self.stop_queue_item(queue_id) + break + except Exception as ex: + self.log_error(ex) + pass + + def get_output_list(self): + result = [] + for rpi_output in self.rpi_outputs: + if rpi_output['output_type'] == 'regular': + result.append(self.to_int(rpi_output['gpio_pin'])) + return result + + def send_notification(self, message): + try: + provider = self._settings.get(["notification_provider"]) + if provider == 'ifttt': + event = self._settings.get(["notification_event_name"]) + api_key = self._settings.get(["notification_api_key"]) + self._logger.debug("Sending notification to: %s with msg: %s with key: %s", provider, message, + api_key) + try: + res = self.ifttt_notification(message, event, api_key) + except requests.exceptions.ConnectionError: + self._logger.info("Error: Could not connect to IFTTT") + except requests.exceptions.HTTPError: + self._logger.info("Error: Received invalid response") + except requests.exceptions.Timeout: + self._logger.info("Error: Request timed out") + except requests.exceptions.TooManyRedirects: + self._logger.info("Error: Too many redirects") + except requests.exceptions.RequestException as reqe: + self._logger.info("Error: {e}".format(e=reqe)) + if res.status_code != requests.codes['ok']: + try: + j = res.json() + except ValueError: + self._logger.info('Error: Could not parse server response. Event not sent') + for err in j['errors']: + self._logger.info('Error: {}'.format(err['message'])) + except Exception as ex: + self.log_error(ex) + pass + + def ifttt_notification(self, message, event, api_key): + url = "https://maker.ifttt.com/trigger/{e}/with/key/{k}/".format(e=event, k=api_key) + payload = {'value1': message} + return requests.post(url, data=payload) + + # ~~ EventPlugin mixin + def on_event(self, event, payload): + if event == Events.CONNECTED: + self.update_ui() + + if event == Events.CLIENT_OPENED: + self.update_ui() + + if event == Events.PRINT_RESUMED: + self.start_filament_detection() + + if event == Events.PRINT_STARTED: + self.print_complete = False + self.cancel_all_events_on_queue() + self.event_queue = [] + self.start_filament_detection() + for rpi_output in self.rpi_outputs: + if rpi_output['auto_startup']: + delay_seconds = self.get_startup_delay_from_output(rpi_output) + self.schedule_auto_startup_outputs(rpi_output, delay_seconds) + if rpi_output['toggle_timer']: + if rpi_output['output_type'] == 'regular' or rpi_output['output_type'] == 'pwm': + self.toggle_output(rpi_output['index_id'], True) + if self.is_hour(rpi_output['shutdown_time']): + shutdown_delay_seconds = self.get_shutdown_delay_from_output(rpi_output) + self.schedule_auto_shutdown_outputs(rpi_output, shutdown_delay_seconds) + self.run_tasks() + self.update_ui() + + elif event == Events.PRINT_DONE: + self.stop_filament_detection() + self.print_complete = True + for rpi_output in self.rpi_outputs: + shutdown_time = rpi_output['shutdown_time'] + if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: + rpi_output['duty_cycle'] = rpi_output['default_duty_cycle'] + if rpi_output['auto_shutdown'] and not self.is_hour(shutdown_time): + delay_seconds = self.to_float(shutdown_time) + self.schedule_auto_shutdown_outputs(rpi_output, delay_seconds) + self.run_tasks() + self.update_ui() + + elif event in (Events.PRINT_CANCELLED, Events.PRINT_FAILED): + self.stop_filament_detection() + self.cancel_all_events_on_queue() + self.event_queue = [] + for rpi_output in self.rpi_outputs: + if rpi_output['shutdown_on_failed']: + shutdown_time = rpi_output['shutdown_time'] + if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: + rpi_output['duty_cycle'] = rpi_output['default_duty_cycle'] + if rpi_output['auto_shutdown'] and not self.is_hour(shutdown_time): + delay_seconds = self.to_float(shutdown_time) + self.schedule_auto_shutdown_outputs(rpi_output, delay_seconds) + if rpi_output['output_type'] == 'temp_hum_control': + rpi_output['temp_ctr_set_value'] = 0 + self.run_tasks() + + if event == Events.PRINT_DONE: + for notification in self.notifications: + if notification['printFinish']: + file_name = os.path.basename(payload["file"]) + elapsed_time_in_seconds = payload["time"] + elapsed_time = octoprint.util.get_formatted_timedelta(timedelta(seconds=elapsed_time_in_seconds)) + msg = "Print job finished: " + file_name + "finished printing in " + file_name, elapsed_time + self.send_notification(msg) + + def run_tasks(self): + for task in self.event_queue: + if not task['thread'].is_alive(): + task['thread'].start() + + def schedule_auto_shutdown_outputs(self, rpi_output, shutdown_delay_seconds): + sufix = 'auto_shutdown' + if rpi_output['output_type'] == 'regular': + value = True if rpi_output['active_low'] else False + self.add_regular_output_to_queue(shutdown_delay_seconds, rpi_output, value, sufix) + if rpi_output['output_type'] == 'ledstrip': + self.ledstrip_set_rgb(rpi_output) + if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: + value = 0 + self.add_pwm_output_to_queue(shutdown_delay_seconds, rpi_output, value, sufix) + if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: + self.schedule_pwm_duty_on_queue(shutdown_delay_seconds, rpi_output, 0, sufix) + if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): + self.add_neopixel_output_to_queue(rpi_output, shutdown_delay_seconds, 0, 0, 0, sufix) + if rpi_output['output_type'] == 'temp_hum_control': + value = 0 + self.add_temperature_output_temperature_queue(shutdown_delay_seconds, rpi_output, value, sufix) + self._logger.debug("Events scheduled to run %s", self.event_queue) + + def ledstrip_set_rgb(self, rpi_output, rgb=None): + clk = rpi_output["ledstrip_gpio_clk"] + data = rpi_output["ledstrip_gpio_dat"] + if clk is not None and data is not None: + ledstrip = LEDStrip(self.to_int(clk), self.to_int(data)) + if rgb is None: + red, green, blue = self.get_color_from_rgb(rpi_output['default_ledstrip_color']) + else: + red, green, blue = self.get_color_from_rgb(rgb) + + self._logger.info("LEDSTRIP set rgb color: %s, %s, %s", red, green, blue) + ledstrip.setcolourrgb(self.to_int(red), self.to_int(green), self.to_int(blue)) + + def start_outpus_with_server(self): + for rpi_output in self.rpi_outputs: + if rpi_output['startup_with_server']: + gpio = self.to_int(rpi_output['gpio_pin']) + if rpi_output['output_type'] == 'regular': + value = False if rpi_output['active_low'] else True + self.write_gpio(gpio, value) + if rpi_output['output_type'] == 'ledstrip': + self.ledstrip_set_rgb(rpi_output) + if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: + value = self.to_int(rpi_output['default_duty_cycle']) + self.write_pwm(gpio, value) + if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): + red, green, blue = self.get_color_from_rgb(rpi_output['default_neopixel_color']) + led_count = rpi_output['neopixel_count'] + led_brightness = rpi_output['neopixel_brightness'] + address = rpi_output['microcontroller_address'] + index_id = self.to_int(rpi_output['index_id']) + neopixel_direct = rpi_output['output_type'] == 'neopixel_direct' + self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, + green, blue, address, neopixel_direct, index_id) + if rpi_output['output_type'] == 'temp_hum_control': + rpi_output['temp_ctr_set_value'] = rpi_output['temp_ctr_default_value'] + + def schedule_auto_startup_outputs(self, rpi_output, delay_seconds): + sufix = 'auto_startup' + if rpi_output['output_type'] == 'regular': + value = False if rpi_output['active_low'] else True + self.add_regular_output_to_queue(delay_seconds, rpi_output, value, sufix) + if rpi_output['output_type'] == 'ledstrip': + self.ledstrip_set_rgb(rpi_output) + if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: + value = self.to_int(rpi_output['default_duty_cycle']) + self.add_pwm_output_to_queue(delay_seconds, rpi_output, value, sufix) + if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): + red, green, blue = self.get_color_from_rgb(rpi_output['default_neopixel_color']) + self.add_neopixel_output_to_queue(rpi_output, delay_seconds, red, green, blue, sufix) + if rpi_output['output_type'] == 'temp_hum_control': + value = rpi_output['temp_ctr_default_value'] + self.add_temperature_output_temperature_queue(delay_seconds, rpi_output, value, sufix) + self._logger.debug("Events scheduled to run %s", self.event_queue) + + def get_color_from_rgb(self, stringColor): + stringColor = stringColor.replace('rgb(', '') + red = stringColor[:stringColor.index(',')] + stringColor = stringColor[stringColor.index(',') + 1:] + green = stringColor[:stringColor.index(',')] + stringColor = stringColor[stringColor.index(',') + 1:] + blue = stringColor[:stringColor.index(')')] + return red, green, blue + + def get_shutdown_delay_from_output(self, rpi_output): + shutdown_time = rpi_output['shutdown_time'] + + shut_down_date_time = self.create_date(shutdown_time) + + if shut_down_date_time < datetime.now(): + shut_down_date_time = shut_down_date_time + timedelta(days=1) + + delay_seconds = (shut_down_date_time - datetime.now()).total_seconds() + + return delay_seconds + + def add_neopixel_output_to_queue(self, rpi_output, delay_seconds, red, green, blue, sufix): + gpio_pin = rpi_output['gpio_pin'] + ledCount = rpi_output['neopixel_count'] + ledBrightness = rpi_output['neopixel_brightness'] + address = rpi_output['microcontroller_address'] + neopixel_direct = rpi_output['output_type'] == 'neopixel_direct' + index_id = self.to_int(rpi_output['index_id']) + + queue_id = '{0!s}_{1!s}'.format(index_id, sufix) + + self._logger.debug("Scheduling neopixel output id %s for on %s delay_seconds", queue_id, delay_seconds) + + thread = threading.Timer(delay_seconds, self.send_neopixel_command, + args=[gpio_pin, ledCount, ledBrightness, red, green, blue, address, neopixel_direct, + index_id, queue_id]) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def add_pwm_output_to_queue(self, delay_seconds, rpi_output, value, sufix): + queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) + + self._logger.debug("Scheduling pwm output id %s for on %s delay_seconds", queue_id, delay_seconds) + + thread = threading.Timer(delay_seconds, self.write_pwm, + args=[self.to_int(rpi_output['gpio_pin']), value, queue_id]) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def schedule_pwm_duty_on_queue(self, delay_seconds, rpi_output, value, sufix): + queue_id = '{0!s}_{1!s}_{2!s}'.format(rpi_output['index_id'], "pwm_linked_temp", sufix) + thread = threading.Timer(delay_seconds, self.set_pwm_duty_cycle, args=[rpi_output, value, queue_id]) + + self._logger.debug("Scheduling pwm linked temp output id %s on %s delay_seconds", queue_id, delay_seconds) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def set_pwm_duty_cycle(self, rpi_output, value, queue_id): + rpi_output['duty_cycle'] = value + if queue_id is not None: + self.stop_queue_item(queue_id) + + def add_regular_output_to_queue(self, delay_seconds, rpi_output, value, sufix): + queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) + + self._logger.debug("Scheduling regular output id %s on %s delay_seconds", queue_id, delay_seconds) + + thread = threading.Timer(delay_seconds, self.write_gpio, + args=[self.to_int(rpi_output['gpio_pin']), value, queue_id]) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def add_temperature_output_temperature_queue(self, delay_seconds, rpi_output, value, sufix): + queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) + self._logger.debug("Scheduling temperature control id %s on %s delay_seconds", queue_id, delay_seconds) + + thread = threading.Timer(delay_seconds, self.write_temperature_to_output, + args=[self.to_int(rpi_output['index_id']), value, queue_id]) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def write_temperature_to_output(self, rpi_output_index, value, queue_id=None): + try: + rpi_output = [r_out for r_out in self.rpi_outputs if + self.to_int(r_out['index_id']) == rpi_output_index].pop() + + if rpi_output['output_type'] == 'temp_hum_control': + rpi_output['temp_ctr_set_value'] = value + + if queue_id is not None: + self._logger.debug("running scheduled queue id %s", queue_id) + self._logger.debug("Setting temperature to output index: %s value %s", rpi_output['index_id'], value) + + self.update_ui() + if queue_id is not None: + self.stop_queue_item(queue_id) + + except Exception as ex: + template = "An exception of type {0} occurred on {1}. Arguments:\n{3!r}" + message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, ex.args) + self._logger.warn(message) + pass + + def get_startup_delay_from_output(self, rpi_output): + start_up_time = rpi_output['startup_time'] + if self.is_hour(start_up_time): + start_up_date_time = self.create_date(start_up_time) + if start_up_date_time < datetime.now(): + delay_seconds = 0.0 + else: + delay_seconds = (start_up_date_time - datetime.now()).total_seconds() + else: + delay_seconds = self.to_float(rpi_output['startup_time']) + return delay_seconds + + # ~~ SettingsPlugin mixin + def on_settings_save(self, data): + outputsBeforeSave = self.get_output_list() + octoprint.plugin.SettingsPlugin.on_settings_save(self, data) + self.rpi_outputs = self._settings.get(["rpi_outputs"]) + self.rpi_inputs = self._settings.get(["rpi_inputs"]) + self.notifications = self._settings.get(["notifications"]) + outputsAfterSave = self.get_output_list() + + commonPins = list(set(outputsBeforeSave) & set(outputsAfterSave)) + + for pin in (pin for pin in outputsBeforeSave if pin not in commonPins): + self.clear_channel(pin) + + self.rpi_outputs_not_changed = commonPins + self.clear_gpio() + + self._logger.debug("rpi_outputs: %s", self.rpi_outputs) + self._logger.debug("rpi_inputs: %s", self.rpi_inputs) + self.setup_gpio() + self.configure_gpio() + self.generate_temp_hum_control_status() + + def get_settings_defaults(self): + return dict(rpi_outputs=[], rpi_inputs=[], + filament_sensor_gcode="G91 ;Set Relative Mode \n" + "G1 E-5.000000 F500 ;Retract 5mm\n" + "G1 Z15 F300 ;move Z up 15mm\n" + "G90 ;Set Absolute Mode\n " + "G1 X20 Y20 F9000 ;Move to hold position\n" + "G91 ;Set Relative Mode\n" + "G1 E-40 F500 ;Retract 40mm\n" + "M0 ;Idle Hold\n" + "G90 ;Set Absolute Mode\n" + "G1 F5000 ;Set speed limits\n" + "G28 X0 Y0 ;Home X Y\n" + "M82 ;Set extruder to Absolute Mode\n" + "G92 E0 ;Set Extruder to 0", + use_sudo=True, neopixel_dma=10, debug=False, gcode_control=False, debug_temperature_log=False, + use_board_pin_number=False, notification_provider="disabled", notification_api_key="", + notification_event_name="printer_event", notifications=[{ + 'printFinish' : True, + 'filamentChange' : True, + 'printer_action' : True, + 'temperatureAction': True, 'gpioAction': True + }]) + + # ~~ TemplatePlugin + def get_template_configs(self): + return [dict(type="settings", custom_bindings=True), dict(type="tab", custom_bindings=True), + dict(type="navbar", custom_bindings=True, suffix="_1", classes=["dropdown"]), + dict(type="navbar", custom_bindings=True, template="enclosure_navbar_input.jinja2", suffix="_2", + classes=["dropdown"])] + + # ~~ AssetPlugin mixin + def get_assets(self): + return dict(js=["js/enclosure.js", "js/bootstrap-colorpicker.min.js"], + css=["css/bootstrap-colorpicker.css", "css/enclosure.css"]) + + # ~~ Softwareupdate hook + def get_update_information(self): + return dict(enclosure=dict(displayName="Enclosure Plugin", displayVersion=self._plugin_version, + # version check: github repository + type="github_release", user="vitormhenrique", repo="OctoPrint-Enclosure", current=self._plugin_version, + # update method: pip + pip="https://github.com/vitormhenrique/OctoPrint-Enclosure/archive/{target_version}.zip")) + + def hook_gcode_queuing(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs): + if self._settings.get(["gcode_control"]) is False: + return + + if cmd.strip().startswith("ENC"): + self._logger.debug("Gcode queuing: %s", cmd) + index_id = self.to_int(self.get_gcode_value(cmd, 'O')) + for output in [item for item in self.rpi_outputs if item['index_id'] == index_id]: + if output['output_type'] == 'regular': + set_value = self.to_int(self.get_gcode_value(cmd, 'S')) + set_value = self.constrain(set_value, 0, 1) + value = True if set_value == 1 else False + value = (not value) if output['active_low'] else value + self.write_gpio(self.to_int(output['gpio_pin']), value) + comm_instance._log("Setting REGULAR output %s to value %s" % (index_id, value)) + return + if output['output_type'] == 'pwm': + set_value = self.to_int(self.get_gcode_value(cmd, 'S')) + set_value = self.constrain(set_value, 0, 100) + output['duty_cycle'] = set_value + self.write_pwm(self.to_int(output['gpio_pin']), set_value) + comm_instance._log("Setting PWM output %s to value %s" % (index_id, set_value)) + return + if output['output_type'] == 'neopixel_indirect' or output['output_type'] == 'neopixel_direct': + red = self.get_gcode_value(cmd, 'R') + green = self.get_gcode_value(cmd, 'G') + blue = self.get_gcode_value(cmd, 'B') + + led_count = output['neopixel_count'] + led_brightness = output['neopixel_brightness'] + address = output['microcontroller_address'] + + index_id = self.to_int(output['index_id']) + + neopixel_direct = output['output_type'] == 'neopixel_direct' + + self.send_neopixel_command(self.to_int(output['gpio_pin']), led_count, led_brightness, red, green, + blue, address, neopixel_direct, index_id) + comm_instance._log( + "Setting NEOPIXEL output %s to red: %s green: %s blue: %s" % (index_id, red, green, blue)) + return + if output['output_type'] == 'temp_hum_control': + set_value = self.to_float(self.get_gcode_value(cmd, 'S')) + should_wait = self.to_int(self.get_gcode_value(cmd, 'W')) + if should_wait == 1 and self._printer.is_printing(): + self._printer.pause_print() + self.waiting_temperature.append(index_id) + output['temp_ctr_set_value'] = set_value + self.update_ui_set_temperature() + self.handle_temp_hum_control() + comm_instance._log("Setting TEMP/HUM control output %s to value %s" % (index_id, set_value)) + return __plugin_name__ = "Enclosure Plugin" @@ -1888,11 +1950,11 @@ __plugin_pythoncompat__ = ">=2.7,<4" def __plugin_load__(): - global __plugin_implementation__ - __plugin_implementation__ = EnclosurePlugin() + global __plugin_implementation__ + __plugin_implementation__ = EnclosurePlugin() - global __plugin_hooks__ - __plugin_hooks__ = { - "octoprint.comm.protocol.gcode.queuing" : __plugin_implementation__.hook_gcode_queuing, - "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information - } + global __plugin_hooks__ + __plugin_hooks__ = { + "octoprint.comm.protocol.gcode.queuing" : __plugin_implementation__.hook_gcode_queuing, + "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information + } -- 2.39.5 From 546d7535adfb5fdd52c81d72e3ecbe68554141c9 Mon Sep 17 00:00:00 2001 From: RFBomb Date: Sat, 24 Oct 2020 18:54:21 -0400 Subject: [PATCH 040/104] Converted from Tab Spacing to 4xSpacebar Spacing to match original --- octoprint_enclosure/__init__.py | 3836 +++++++++++++++---------------- 1 file changed, 1918 insertions(+), 1918 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index d2f1c78..ca3b983 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -25,1924 +25,1924 @@ import copy #Function that returns Boolean output state of the GPIO inputs / outputs def PinState_Boolean(pin, ActiveLow) : - try: - state = GPIO.input(pin) - if ActiveLow and not state: return True - if not ActiveLow and state: return True - return False - except: - return "ERROR: Unable to read pin" + try: + state = GPIO.input(pin) + if ActiveLow and not state: return True + if not ActiveLow and state: return True + return False + except: + return "ERROR: Unable to read pin" #Function that returns human-readable output state of the GPIO inputs / outputs def PinState_Human(pin, ActiveLow): - PinState = PinState_Boolean(pin, ActiveLow) - if PinState == True : - return " ON " - elif PinState == False: - return " OFF " - else: - return PinState + PinState = PinState_Boolean(pin, ActiveLow) + if PinState == True : + return " ON " + elif PinState == False: + return " OFF " + else: + return PinState #Translates the Pull-Up/Pull-Down GPIO resistor setting to ActiveLow/ActiveHigh boolean def CheckInputActiveLow(Input_Pull_Resistor): - #input_pull_up - #input_pull_down - if Input_Pull_Resistor == "input_pull_up": - return True - else: - return False - + #input_pull_up + #input_pull_down + if Input_Pull_Resistor == "input_pull_up": + return True + else: + return False + class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SettingsPlugin, - octoprint.plugin.AssetPlugin, octoprint.plugin.BlueprintPlugin, - octoprint.plugin.EventHandlerPlugin): - rpi_outputs = [] - rpi_inputs = [] - waiting_temperature = [] - rpi_outputs_not_changed = [] - notifications = [] - pwm_instances = [] - event_queue = [] - temp_hum_control_status = [] - temperature_sensor_data = [] - last_filament_end_detected = [] - print_complete = False - development_mode = False - dummy_value = 30.0 - dummy_delta = 0.5 - - def start_timer(self): - """ - Function to start timer that checks enclosure temperature - """ - - self._check_temp_timer = RepeatedTimer(10, self.check_enclosure_temp, None, None, True) - self._check_temp_timer.start() - - @staticmethod - def to_float(value): - """Converts value to flow - Arguments: - value {any} -- value to be - Returns: - float -- value converted - """ - - try: - val = float(value) - return val - except: - return 0 - - @staticmethod - def to_int(value): - try: - val = int(value) - return val - except: - return 0 - - @staticmethod - def is_hour(value): - try: - datetime.strptime(value, '%H:%M') - return True - except: - return False - - @staticmethod - def create_date(value): - temp_string = datetime.now().strftime('%m/%d/%Y') + " " + value - return datetime.strptime(temp_string, '%m/%d/%Y %H:%M') - - @staticmethod - def constrain(n, minn, maxn): - return max(min(maxn, n), minn) - - @staticmethod - def get_gcode_value(command_string, gcode): - semicolon = command_string.find(';') - if not semicolon == -1: - command_string = command_string[:semicolon] - - for command in command_string.split(' '): - index = command.upper().find(gcode.upper()) - if not index == -1: - return command.replace(gcode, '') - return -1 - - # ~~ StartupPlugin mixin - def on_after_startup(self): - self.pwm_instances = [] - self.event_queue = [] - self.rpi_outputs_not_changed = [] - self.rpi_outputs = self._settings.get(["rpi_outputs"]) - self.rpi_inputs = self._settings.get(["rpi_inputs"]) - self.notifications = self._settings.get(["notifications"]) - self.generate_temp_hum_control_status() - self.setup_gpio() - self.configure_gpio() - self.update_ui() - self.start_outpus_with_server() - self.handle_initial_gpio_control() - self.start_timer() - self.print_complete = False - - def get_settings_version(self): - return 6 - - def on_settings_migrate(self, target, current=None): - self._logger.warn("######### current settings version %s target settings version %s #########", current, target) - self._logger.info("######### Current settings #########") - self._logger.info("rpi_outputs: %s", self.rpi_outputs) - self._logger.info("rpi_inputs: %s", self.rpi_inputs) - self._logger.info("######### End Current Settings #########") - if current >= 4 and target == 6: - self._logger.warn("######### migrating settings to v6 #########") - old_outputs = self._settings.get(["rpi_outputs"]) - for rpi_output in old_outputs: - if 'shutdown_on_failed' not in rpi_output: - rpi_output['shutdown_on_failed'] = False - if 'shell_script' not in rpi_output: - rpi_output['shell_script'] = "" - self._settings.set(["rpi_outputs"], old_outputs) - else: - self._logger.warn("######### settings not compatible #########") - self._settings.set(["rpi_outputs"], []) - self._settings.set(["rpi_inputs"], []) - self.rpi_inputs = self._settings.get(["rpi_inputs"]) - - #Scan all configured inputs and outputs and return the pin value - @octoprint.plugin.BlueprintPlugin.route("/ReadPin/", methods=["GET"]) - def ReadSinglePin(self, identifier): - Resp = [] - MatchFound = False - for rpi_input in self.rpi_inputs: - if identifier == self.to_int(rpi_input['gpio_pin']): - MatchFound = True - ConfiguredAs = "Input" - ActiveLow = CheckInputActiveLow(rpi_input['input_pull_resistor']) - pin = self.to_int(rpi_input['gpio_pin']) - val = PinState_Human(pin,ActiveLow) - label = rpi_input['label'] - Resp.append(dict(Configured_As=ConfiguredAs, label=label, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['gpio_pin']): - MatchFound = True - ConfiguredAs = "Output" - ActiveLow = CheckInputActiveLow(rpi_output['active_low']) - pin = self.to_int(rpi_output['gpio_pin']) - val = PinState_Human(pin,ActiveLow) - label = rpi_output['label'] - Resp.append(dict(Configured_As=ConfiguredAs, label=label, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) - if MatchFound == False: - pin = int(identifier) - ConfiguredAs = "Unknown" - ActiveLow = "Unknown" - try: - val = GPIO.input(pin) - except: - val = "GPIO pin not initialized." - Resp.append(dict(Configured_As=ConfiguredAs, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) - return Response(json.dumps(Resp), mimetype='application/json') - - # ~~ Blueprintplugin mixin - @octoprint.plugin.BlueprintPlugin.route("/inputs", methods=["GET"]) - def get_inputs(self): - inputs = [] - for rpi_input in self.rpi_inputs: - index = self.to_int(rpi_input['index_id']) - label = rpi_input['label'] - ActiveLow = CheckInputActiveLow(rpi_input['input_pull_resistor']) - pin = self.to_int(rpi_input['gpio_pin']) - val = PinState_Human(pin,ActiveLow) - inputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, State=val)) - return Response(json.dumps(inputs), mimetype='application/json') - - @octoprint.plugin.BlueprintPlugin.route("/inputs/", methods=["GET"]) - def get_input_status(self, identifier): - for rpi_input in self.rpi_inputs: - if identifier == self.to_int(rpi_input['index_id']): - return Response(json.dumps(rpi_input), mimetype='application/json') - return make_response('', 404) - - - @octoprint.plugin.BlueprintPlugin.route("/temperature/", methods=["PATCH"]) - @restricted_access - def set_enclosure_temp_humidity(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'temperature' not in data: - return make_response("missing temperature attribute", 406) - - set_value = data["temperature"] - - for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == identifier]: - temp_hum_control['temp_ctr_set_value'] = set_value - - self.handle_temp_hum_control() - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/filament/", methods=["PATCH"]) - @restricted_access - def set_filament_sensor(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'status' not in data: - return make_response("missing status attribute", 406) - - value = data["status"] - - for sensor in self.rpi_inputs: - if identifier == self.to_int(sensor['index_id']): - sensor['filament_sensor_enabled'] = value - self._logger.info("Setting filament sensor for input %s to : %s", str(identifier), value) - self._settings.set(["rpi_inputs"], self.rpi_inputs) - return make_response('', 204) - - @octoprint.plugin.BlueprintPlugin.route("/outputs", methods=["GET"]) - def get_outputs(self): - outputs = [] - for rpi_output in self.rpi_outputs: - if rpi_output['output_type'] == 'regular': - index = self.to_int(rpi_output['index_id']) - label = rpi_output['label'] - pin = self.to_int(rpi_output['gpio_pin']) - ActiveLow = rpi_output['active_low'] - val = PinState_Human(pin,ActiveLow) - outputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, State=val)) - return Response(json.dumps(outputs), mimetype='application/json') - - - @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["GET"]) - def get_output_status(self, identifier): - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['index_id']): - out = copy.deepcopy(rpi_output) - pin = self.to_int(rpi_output['gpio_pin']) - out['current_value'] = PinState_Boolean(pin, rpi_output['active_low'] ) - return Response(json.dumps(out), mimetype='application/json') - return make_response('', 404) - - - @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["PATCH"]) - @restricted_access - def set_io(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'status' not in data: - return make_response("missing status attribute", 406) - - value = data["status"] - - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['index_id']): - val = (not value) if rpi_output['active_low'] else value - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-startup", methods=["PATCH"]) - @restricted_access - def set_auto_startup(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'status' not in data: - return make_response("missing status attribute", 406) - - value = data["status"] - - if not value: - suffix = 'auto_startup' - queue_id = '{0!s}_{1!s}'.format(str(identifier), suffix) - self.stop_queue_item(queue_id) - for output in self.rpi_outputs: - if identifier == self.to_int(output['index_id']): - output['auto_startup'] = value - self._logger.info("Setting auto startup for output %s to : %s", str(identifier), value) - self._settings.set(["rpi_outputs"], self.rpi_outputs) - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-shutdown", methods=["PATCH"]) - @restricted_access - def set_auto_shutdown(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'status' not in data: - return make_response("missing status attribute", 406) - - value = data["status"] - - if not value: - suffix = 'auto_shutdown' - queue_id = '{0!s}_{1!s}'.format(str(identifier), suffix) - self.stop_queue_item(queue_id) - - for output in self.rpi_outputs: - if identifier == self.to_int(output['index_id']): - output['auto_shutdown'] = value - self._logger.info("Setting auto shutdown for output %s to : %s", str(identifier), value) - self._settings.set(["rpi_outputs"], self.rpi_outputs) - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/pwm/", methods=["PATCH"]) - @restricted_access - def set_pwm(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'duty_cycle' not in data: - return make_response("missing duty_cycle attribute", 406) - - set_value = self.to_int(data['duty_cycle']) - for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == identifier]: - rpi_output['duty_cycle'] = set_value - rpi_output['new_duty_cycle'] = "" - gpio = self.to_int(rpi_output['gpio_pin']) - self.write_pwm(gpio, set_value) - return make_response('', 204) - - @octoprint.plugin.BlueprintPlugin.route("/rgb-led/", methods=["PATCH"]) - @restricted_access - def set_ledstrip_color(self, identifier): - """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'rgb' not in data: - return make_response("missing rgb attribute", 406) - rgb = data['rgb'] - - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['index_id']): - self.ledstrip_set_rgb(rpi_output, rgb) - - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/neopixel/", methods=["PATCH"]) - @restricted_access - def set_neopixel(self, identifier): - """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'red' not in data: - return make_response("missing red attribute", 406) - if 'green' not in data: - return make_response("missing green attribute", 406) - if 'blue' not in data: - return make_response("missing blue attribute", 406) - - red = data['red'] - green = data['green'] - blue = data['blue'] - - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['index_id']): - led_count = rpi_output['neopixel_count'] - led_brightness = rpi_output['neopixel_brightness'] - address = rpi_output['microcontroller_address'] - - neopixel_dirrect = rpi_output['output_type'] == 'neopixel_direct' - - self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, green, - blue, address, neopixel_dirrect, identifier) - - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/clear-gpio", methods=["POST"]) - @restricted_access - def clear_gpio_mode(self): - GPIO.cleanup() - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/update", methods=["POST"]) - @restricted_access - def update_ui_requested(self): - self.update_ui() - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/shell/", methods=["POST"]) - @restricted_access - def send_shell_command(self, identifier): - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() - - command = rpi_output['shell_script'] - self.shell_command(command) - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/gcode/", methods=["POST"]) - @restricted_access - def requested_gcode_command(self, identifier): - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() - self.send_gcode_command(rpi_output['gcode']) - return make_response('', 204) - - - - - - """ - DEPRECATION - This API will be deprecated in a future version - """ - - # ~~ Blueprintplugin mixin - @octoprint.plugin.BlueprintPlugin.route("/setEnclosureTempHum", methods=["GET"]) - def set_enclosure_temp_humidity_old(self): - set_value = self.to_float(request.values["set_temperature"]) - index_id = self.to_int(request.values["index_id"]) - - for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == index_id]: - temp_hum_control['temp_ctr_set_value'] = set_value - - self.handle_temp_hum_control() - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/clearGPIOMode", methods=["GET"]) - def clear_gpio_mode_old(self): - GPIO.cleanup() - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/updateUI", methods=["GET"]) - def update_ui_requested_old(self): - self.update_ui() - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/getOutputStatus", methods=["GET"]) - def get_output_status_old(self): - gpio_status = [] - for rpi_output in self.rpi_outputs: - if rpi_output['output_type'] == 'regular': - pin = self.to_int(rpi_output['gpio_pin']) - ActiveLow = rpi_output['active_low'] - val = PinState_Boolean(pin, ActiveLow) - val2 = PinState_Human(pin, ActiveLow) - index = self.to_int(rpi_output['index_id']) - gpio_status.append(dict(index_id=index, status=val, State=val2)) - return Response(json.dumps(gpio_status), mimetype='application/json') - - @octoprint.plugin.BlueprintPlugin.route("/setIO", methods=["GET"]) - def set_io_old(self): - index = request.values["index_id"] - value = True if request.values["status"] == 'true' else False - for rpi_output in self.rpi_outputs: - if self.to_int(index) == self.to_int(rpi_output['index_id']): - val = (not value) if rpi_output['active_low'] else value - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/sendShellCommand", methods=["GET"]) - def send_shell_command_old(self): - output_index = self.to_int(request.values["index_id"]) - - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == output_index].pop() - - command = rpi_output['shell_script'] - self.shell_command(command) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setAutoStartUp", methods=["GET"]) - def set_auto_startup_old(self): - index = request.values["index_id"] - value = True if request.values["status"] == 'true' else False - - if not value: - suffix = 'auto_startup' - queue_id = '{0!s}_{1!s}'.format(index, suffix) - self.stop_queue_item(queue_id) - for output in self.rpi_outputs: - if self.to_int(index) == self.to_int(output['index_id']): - output['auto_startup'] = value - self._logger.info("Setting auto startup for output %s to : %s", index, value) - self._settings.set(["rpi_outputs"], self.rpi_outputs) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setAutoShutdown", methods=["GET"]) - def set_auto_shutdown_old(self): - index = request.values["index_id"] - value = True if request.values["status"] == 'true' else False - - if not value: - suffix = 'auto_shutdown' - queue_id = '{0!s}_{1!s}'.format(index, suffix) - self.stop_queue_item(queue_id) - - for output in self.rpi_outputs: - if self.to_int(index) == self.to_int(output['index_id']): - output['auto_shutdown'] = value - self._logger.info("Setting auto shutdown for output %s to : %s", index, value) - self._settings.set(["rpi_outputs"], self.rpi_outputs) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setFilamentSensor", methods=["GET"]) - def set_filament_sensor_old(self): - index = request.values["index_id"] - value = True if request.values["status"] == 'true' else False - for sensor in self.rpi_inputs: - if self.to_int(index) == self.to_int(sensor['index_id']): - sensor['filament_sensor_enabled'] = value - self._logger.info("Setting filament sensor for input %s to : %s", index, value) - self._settings.set(["rpi_inputs"], self.rpi_inputs) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setPWM", methods=["GET"]) - def set_pwm_old(self): - set_value = self.to_int(request.values["new_duty_cycle"]) - index_id = self.to_int(request.values["index_id"]) - for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == index_id]: - rpi_output['duty_cycle'] = set_value - rpi_output['new_duty_cycle'] = "" - gpio = self.to_int(rpi_output['gpio_pin']) - self.write_pwm(gpio, set_value) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/sendGcodeCommand", methods=["GET"]) - def requested_gcode_command_old(self): - gpio_index = self.to_int(request.values["index_id"]) - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == gpio_index].pop() - self.send_gcode_command(rpi_output['gcode']) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setNeopixel", methods=["GET"]) - def set_neopixel_old(self): - """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" - gpio_index = self.to_int(request.values["index_id"]) - red = request.values["red"] - green = request.values["green"] - blue = request.values["blue"] - for rpi_output in self.rpi_outputs: - if gpio_index == self.to_int(rpi_output['index_id']): - led_count = rpi_output['neopixel_count'] - led_brightness = rpi_output['neopixel_brightness'] - address = rpi_output['microcontroller_address'] - - neopixel_dirrect = rpi_output['output_type'] == 'neopixel_direct' - - self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, green, - blue, address, neopixel_dirrect, gpio_index) - - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setLedstripColor", methods=["GET"]) - def set_ledstrip_color_old(self): - """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" - gpio_index = self.to_int(request.values["index_id"]) - rgb = request.values["rgb"] - for rpi_output in self.rpi_outputs: - if gpio_index == self.to_int(rpi_output['index_id']): - self.ledstrip_set_rgb(rpi_output, rgb) - - return jsonify(success=True) - - # DEPREACTION END - - - - - - def send_neopixel_command(self, led_pin, led_count, led_brightness, red, green, blue, address, neopixel_dirrect, - index_id, queue_id=None): - """Send neopixel command - Arguments: - led_pin {int} -- GPIO number - ledCount {int} -- number of LEDS - ledBrightness {int} -- brightness from 0 to 255 - red {int} -- red value from 0 to 255 - green {int} -- green value from 0 to 255 - blue {int} -- blue value from 0 to 255 - address {int} -- i2c address from microcontroller - """ - - try: - - for rpi_output in self.rpi_outputs: - if self.to_int(index_id) == self.to_int(rpi_output['index_id']): - rpi_output['neopixel_color'] = 'rgb({0!s},{1!s},{2!s})'.format(red, green, blue) - - if address == '': - address = 0 - - if neopixel_dirrect: - script = os.path.dirname(os.path.realpath(__file__)) + "/neopixel_direct.py " - else: - script = os.path.dirname(os.path.realpath(__file__)) + "/neopixel_indirect.py " - - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - - cmd = sudo_str + "python " + script + str(led_pin) + " " + str(led_count) + " " + str( - led_brightness) + " " + str(red) + " " + str(green) + " " + str(blue) + " " - - if neopixel_dirrect: - dma = self._settings.get(["neopixel_dma"]) or 10 - cmd = cmd + str(dma) - else: - cmd = cmd + str(address) - - if queue_id is not None: - self._logger.debug("running scheduled queue id %s", queue_id) - self._logger.debug("Sending neopixel cmd: %s", cmd) - Popen(cmd, shell=True) - if queue_id is not None: - self.stop_queue_item(queue_id) - except Exception as ex: - self.log_error(ex) - - def check_enclosure_temp(self): - try: - sensor_data = [] - for sensor in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): - temp, hum = self.get_sensor_data(sensor) - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Sensor %s Temperature: %s humidity %s", sensor['label'], temp, hum) - if temp is not None and hum is not None: - sensor["temp_sensor_temp"] = temp - sensor["temp_sensor_humidity"] = hum - sensor_data.append(dict(index_id=sensor['index_id'], temperature=temp, humidity=hum)) - self.temperature_sensor_data = sensor_data - self.handle_temp_hum_control() - self.handle_temperature_events() - self.handle_pwm_linked_temperature() - self.update_ui() - except Exception as ex: - self.log_error(ex) - - def toggle_output(self, output_index, first_run=False): - for output in [item for item in self.rpi_outputs if item['index_id'] == output_index]: - gpio_pin = self.to_int(output['gpio_pin']) - index_id = self.to_int(output['index_id']) - - if output['output_type'] == 'regular': - if first_run: - current_value = False - else: - current_value = (not GPIO.input(gpio_pin)) if output['active_low'] else GPIO.input(gpio_pin) - - if current_value: - time_delay = self.to_int(output['toggle_timer_off']) - else: - time_delay = self.to_int(output['toggle_timer_on']) - - if not self.print_complete: - self.write_gpio(gpio_pin, not current_value) - thread = threading.Timer(time_delay, self.toggle_output, args=[index_id]) - thread.start() - else: - off_value = True if output['active_low'] else False - self.write_gpio(gpio_pin, off_value) - self.update_ui_outputs() - return - - if output['output_type'] == 'pwm': - for pwm in self.pwm_instances: - if gpio_pin in pwm: - if first_run: - current_pwm_value = 0 - else: - if 'duty_cycle' in pwm: - current_pwm_value = pwm['duty_cycle'] - current_pwm_value = self.to_int(current_pwm_value) - else: - current_pwm_value = 0 - - if not current_pwm_value == 0: - time_delay = self.to_int(output['toggle_timer_off']) - write_value = 0 - else: - time_delay = self.to_int(output['toggle_timer_on']) - write_value = self.to_int(output['default_duty_cycle']) - - if not self.print_complete: - self.write_pwm(gpio_pin, write_value) - thread = threading.Timer(time_delay, self.toggle_output, args=[index_id]) - thread.start() - else: - self.write_pwm(self.to_int(output['gpio_pin']), 0) - self.update_ui_outputs() - return - - def update_ui(self): - self.update_ui_outputs() - self.update_ui_current_temperature() - self.update_ui_set_temperature() - self.update_ui_inputs() - - def update_ui_current_temperature(self): - self._plugin_manager.send_plugin_message(self._identifier, dict(sensor_data=self.temperature_sensor_data)) - - def update_ui_set_temperature(self): - result = [] - for temp_crt_output in list(filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): - set_temperature = self.to_float(temp_crt_output['temp_ctr_set_value']) - result.append(dict(index_id=temp_crt_output['index_id'], set_temperature=set_temperature)) - result.append(set_temperature) - self._plugin_manager.send_plugin_message(self._identifier, dict(set_temperature=result)) - - def stop_queue_item(self, queue_id): - old_list = self.event_queue - self._logger.debug("Stopping queue id %s...", queue_id) - for task in self.event_queue: - self._logger.debug("Queue id found...") - if task['queue_id'] == queue_id: - task['thread'].cancel() - self.event_queue.remove(task) - self._logger.debug("Queue id stopped and removed from list...") - self._logger.debug("Old queue list: %s", old_list) - self._logger.debug("New queue list: %s", self.event_queue) - - def update_ui_outputs(self): - try: - regular_status = [] - pwm_status = [] - neopixel_status = [] - temp_control_status = [] - for output in self.rpi_outputs: - index = self.to_int(output['index_id']) - pin = self.to_int(output['gpio_pin']) - startup = output['auto_startup'] - shutdown = output['auto_shutdown'] - - if output['output_type'] == 'regular': - val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) - regular_status.append( - dict(index_id=index, status=val, auto_startup=startup, auto_shutdown=shutdown)) - if output['output_type'] == 'temp_hum_control': - val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) - temp_control_status.append( - dict(index_id=index, status=val, auto_startup=startup, auto_shutdown=shutdown)) - if output['output_type'] == 'neopixel_indirect' or output['output_type'] == 'neopixel_direct': - val = output['neopixel_color'] - neopixel_status.append( - dict(index_id=index, color=val, auto_startup=startup, auto_shutdown=shutdown)) - if output['output_type'] == 'pwm': - for pwm in self.pwm_instances: - if pin in pwm: - if 'duty_cycle' in pwm: - pwm_val = pwm['duty_cycle'] - val = self.to_int(pwm_val) - else: - val = 0 - pwm_status.append( - dict(index_id=index, pwm_value=val, auto_startup=startup, auto_shutdown=shutdown)) - self._plugin_manager.send_plugin_message(self._identifier, - dict(rpi_output_regular=regular_status, rpi_output_pwm=pwm_status, - rpi_output_neopixel=neopixel_status, - rpi_output_temp_hum_ctrl=temp_control_status)) - except Exception as ex: - self.log_error(ex) - - def update_ui_inputs(self): - try: - sensor_status = [] - for sensor in self.rpi_inputs: - if sensor['input_type'] == 'gpio' and sensor['action_type'] == 'printer_control' and sensor[ - 'printer_action'] == 'filament': - index = self.to_int(sensor['index_id']) - value = sensor['filament_sensor_enabled'] - sensor_status.append(dict(index_id=index, filament_sensor_enabled=value)) - self._plugin_manager.send_plugin_message(self._identifier, dict(filament_sensor_status=sensor_status)) - except Exception as ex: - self.log_error(ex) - - def get_sensor_data(self, sensor): - try: - if self.development_mode: - temp, hum = self.read_dummy_temp() - else: - if sensor['temp_sensor_type'] in ["11", "22", "2302"]: - temp, hum = self.read_dht_temp(sensor['temp_sensor_type'], sensor['gpio_pin']) - elif sensor['temp_sensor_type'] == "18b20": - temp = self.read_18b20_temp(sensor['ds18b20_serial']) - hum = 0 - elif sensor['temp_sensor_type'] == "bme280": - temp, hum = self.read_bme280_temp(sensor['temp_sensor_address']) - elif sensor['temp_sensor_type'] == "am2320": - temp, hum = self.read_am2320_temp() # sensor has fixed address - elif sensor['temp_sensor_type'] == "si7021": - temp, hum = self.read_si7021_temp(sensor['temp_sensor_address'], sensor['temp_sensor_i2cbus']) - elif sensor['temp_sensor_type'] == "tmp102": - temp = self.read_tmp102_temp(sensor['temp_sensor_address']) - hum = 0 - elif sensor['temp_sensor_type'] == "max31855": - temp = self.read_max31855_temp(sensor['temp_sensor_address']) - hum = 0 - elif sensor['temp_sensor_type'] == "mcp9808": - temp = self.read_mcp_temp(sensor['temp_sensor_address']) - hum = 0 - else: - self._logger.info("temp_sensor_type no match") - temp = None - hum = None - if temp != -1 and hum != -1: - temp = round(self.to_float(temp), 1) if not sensor['use_fahrenheit'] else round( - self.to_float(temp) * 1.8 + 32, 1) - hum = round(self.to_float(hum), 1) - return temp, hum - return None, None - except Exception as ex: - self.log_error(ex) - - def handle_temperature_events(self): - for temperature_alarm in [item for item in self.rpi_outputs if item['output_type'] == 'temperature_alarm']: - set_temperature = self.to_float(temperature_alarm['alarm_set_temp']) - if int(set_temperature) is 0: - continue - linked_data = [item for item in self.temperature_sensor_data if - item['index_id'] == temperature_alarm['linked_temp_sensor']].pop() - sensor_temperature = self.to_float(linked_data['temperature']) - if set_temperature < sensor_temperature: - for rpi_controlled_output in self.rpi_outputs: - if self.to_int(temperature_alarm['controlled_io']) == self.to_int( - rpi_controlled_output['index_id']): - val = GPIO.LOW if temperature_alarm['controlled_io_set_value'] == 'low' else GPIO.HIGH - self.write_gpio(self.to_int(rpi_controlled_output['gpio_pin']), val) - for notification in self.notifications: - if notification['temperatureAction']: - msg = ("Temperature action: enclosure temperature exceed " + temperature_alarm[ - 'alarm_set_temp']) - self.send_notification(msg) - - def read_dummy_temp(self): - current_value = self.dummy_value - if current_value > 40 or current_value < 30: - self.dummy_delta = - self.dummy_delta - - return_value = current_value + self.dummy_delta - - self.dummy_value = return_value - - return return_value, return_value - - def read_mcp_temp(self, address): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/mcp9808.py" - args = ["python", script, str(address)] - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature MCP9808 cmd: %s", " ".join(args)) - proc = Popen(args, stdout=PIPE) - stdout, _ = proc.communicate() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("MCP9808 result: %s", stdout) - return self.to_float(stdout.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 - - def read_dht_temp(self, sensor, pin): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/getDHTTemp.py " - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - cmd = sudo_str + "python " + script + str(sensor) + " " + str(pin) - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature dht cmd: %s", cmd) - stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Dht result: %s", stdout) - temp, hum = stdout.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 of the plugin.") - self.log_error(ex) - return (0, 0) - - def read_bme280_temp(self, address): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/BME280.py " - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - cmd = sudo_str + "python " + script + str(address) - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature BME280 cmd: %s", cmd) - stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("BME280 result: %s", stdout) - temp, hum = stdout.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 of the plugin.") - self.log_error(ex) - return (0, 0) - - def read_am2320_temp(self): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/AM2320.py " - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - cmd = sudo_str + "python " + script # sensor has fixed address 0x5C - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature AM2320 cmd: %s", cmd) - stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("AM2320 result: %s", stdout) - temp, hum = stdout.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 of the plugin.") - self.log_error(ex) - return (0, 0) - - def read_si7021_temp(self, address, i2cbus): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/SI7021.py " - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - cmd = sudo_str + "python " + script + str(address) + " " + str(i2cbus) - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature SI7021 cmd: %s", cmd) - stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("SI7021 result: %s", stdout) - temp, hum = stdout.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 of the plugin.") - self.log_error(ex) - return (0, 0) - - def read_18b20_temp(self, serial_number): - os.system('modprobe w1-gpio') - os.system('modprobe w1-therm') - - lines = self.read_raw_18b20_temp(serial_number) - while lines[0].strip()[-3:] != 'YES': - time.sleep(0.2) - lines = self.read_raw_18b20_temp(serial_number) - equals_pos = lines[1].find('t=') - if equals_pos != -1: - temp_string = lines[1][equals_pos + 2:] - temp_c = float(temp_string) / 1000. - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("DS18B20 result: %s", temp_c) - return '{0:0.1f}'.format(temp_c) - return 0 - - def read_raw_18b20_temp(self, serial_number): - base_dir = '/sys/bus/w1/devices/' - device_folder = glob.glob(base_dir + str(serial_number) + '*')[0] - device_file = device_folder + '/w1_slave' - device_file_result = open(device_file, 'r') - lines = device_file_result.readlines() - device_file_result.close() - return lines - - def read_tmp102_temp(self, address): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/tmp102.py" - args = ["python", script, str(address)] - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature TMP102 cmd: %s", " ".join(args)) - proc = Popen(args, stdout=PIPE) - stdout, _ = proc.communicate() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("TMP102 result: %s", stdout) - return self.to_float(stdout.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 - - def read_max31855_temp(self, address): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/max31855.py" - args = ["python", script, str(address)] - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature MAX31855 cmd: %s", " ".join(args)) - proc = Popen(args, stdout=PIPE) - stdout, _ = proc.communicate() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("MAX31855 result: %s", stdout) - return self.to_float(stdout.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 - - def handle_pwm_linked_temperature(self): - try: - for pwm_output in list(filter(lambda item: item['output_type'] == 'pwm' and item['pwm_temperature_linked'], - self.rpi_outputs)): - gpio_pin = self.to_int(pwm_output['gpio_pin']) - if self._printer.is_printing(): - index_id = self.to_int(pwm_output['index_id']) - linked_id = self.to_int(pwm_output['linked_temp_sensor']) - linked_data = self.get_linked_temp_sensor_data(linked_id) - current_temp = self.to_float(linked_data['temperature']) - - duty_a = self.to_float(pwm_output['duty_a']) - duty_b = self.to_float(pwm_output['duty_b']) - temp_a = self.to_float(pwm_output['temperature_a']) - temp_b = self.to_float(pwm_output['temperature_b']) - - try: - calculated_duty = ((current_temp - temp_a) * (duty_b - duty_a) / (temp_b - temp_a)) + duty_a - - if current_temp < temp_a: - calculated_duty = 0 - except: - calculated_duty = 0 - - self._logger.debug("Calculated duty for PWM %s is %s", index_id, calculated_duty) - elif self.print_complete: - calculated_duty = self.to_int(pwm_output['duty_cycle']) - else: - calculated_duty = 0 - - self.write_pwm(gpio_pin, self.constrain(calculated_duty, 0, 100)) - - except Exception as ex: - self.log_error(ex) - - def get_linked_temp_sensor_data(self, linked_id): - try: - linked_data = [data for data in self.temperature_sensor_data if data['index_id'] == linked_id].pop() - return linked_data - except: - self._logger.warn("No linked temperature sensor found for %s", linked_id) - return None - - def handle_temp_hum_control(self): - try: - for temp_hum_control in list( - filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): - - set_temperature = self.to_float(temp_hum_control['temp_ctr_set_value']) - temp_deadband = self.to_float(temp_hum_control['temp_ctr_deadband']) - max_temp = self.to_float(temp_hum_control['temp_ctr_max_temp']) - - linked_id = temp_hum_control['linked_temp_sensor'] - - previous_status = list(filter(lambda item: item['index_id'] == temp_hum_control['index_id'], - self.temp_hum_control_status)).pop()['status'] - - if set_temperature == 0: - current_status = False - else: - linked_data = self.get_linked_temp_sensor_data(linked_id) - - control_type = str(temp_hum_control['temp_ctr_type']) - - if control_type == 'dehumidifier': - current_value = self.to_float(linked_data['humidity']) - temp_deadband = 0 - else: - current_value = self.to_float(linked_data['temperature']) - - if control_type == 'cooler' or control_type == 'dehumidifier': - if current_value <= set_temperature and current_value >= (set_temperature - temp_deadband): - current_status = previous_status - elif current_value < set_temperature: - current_status = False - else: - current_status = True - else: - if current_value <= set_temperature and current_value >= (set_temperature - temp_deadband): - current_status = previous_status - elif current_value > set_temperature: - current_status = False - else: - current_status = True - - if control_type == 'heater' and max_temp > 0.0 and max_temp < current_value: - self._logger.debug("Maximum temperature reached for temperature control %s", - temp_hum_control['index_id']) - temp_hum_control['temp_ctr_set_value'] = 0 - current_status = False - - if current_status != previous_status: - if current_status: - self._logger.info("Turning gpio to control temperature on.") - val = False if temp_hum_control['active_low'] else True - self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) - else: - index_id = temp_hum_control['index_id'] - if index_id in self.waiting_temperature: - self.waiting_temperature.remove(index_id) - - if not self.waiting_temperature and self._printer.is_paused(): - self._printer.resume_print() - - self._logger.info("Turning gpio to control temperature off.") - val = True if temp_hum_control['active_low'] else False - self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) - for control_status in self.temp_hum_control_status: - if control_status['index_id'] == temp_hum_control['index_id']: - control_status['status'] = current_status - except Exception as ex: - self.log_error(ex) - - def log_error(self, ex): - template = "An exception of type {0} occurred on {1}. Arguments:\n{2!r}" - message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, ex.args) - self._logger.warn(message) - - def setup_gpio(self): - try: - current_mode = GPIO.getmode() - set_mode = GPIO.BOARD if self._settings.get(["use_board_pin_number"]) else GPIO.BCM - if current_mode is None: - outputs = list(filter( - lambda item: item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ - 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct', - self.rpi_outputs)) - inputs = list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)) - gpios = outputs + inputs - if gpios: - GPIO.setmode(set_mode) - tempstr = "BOARD" if set_mode == GPIO.BOARD else "BCM" - self._logger.info("Setting GPIO mode to %s", tempstr) - elif current_mode != set_mode: - GPIO.setmode(current_mode) - tempstr = "BOARD" if current_mode == GPIO.BOARD else "BCM" - self._settings.set(["use_board_pin_number"], True if current_mode == GPIO.BOARD else False) - warn_msg = "GPIO mode was configured before, GPIO mode will be forced to use: " + tempstr + " as pin numbers. Please update GPIO accordingly!" - self._logger.info(warn_msg) - self._plugin_manager.send_plugin_message(self._identifier, - dict(is_msg=True, msg=warn_msg, msg_type="error")) - GPIO.setwarnings(False) - except Exception as ex: - self.log_error(ex) - - def clear_gpio(self): - try: - - for gpio_out in list(filter( - lambda item: item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ - 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct', - self.rpi_outputs)): - gpio_pin = self.to_int(gpio_out['gpio_pin']) - if gpio_pin not in self.rpi_outputs_not_changed: - GPIO.cleanup(gpio_pin) - - for gpio_in in list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)): - try: - GPIO.remove_event_detect(self.to_int(gpio_in['gpio_pin'])) - except: - pass - GPIO.cleanup(self.to_int(gpio_in['gpio_pin'])) - except Exception as ex: - self.log_error(ex) - - def clear_channel(self, channel): - try: - GPIO.cleanup(self.to_int(channel)) - self._logger.debug("Clearing channel %s", channel) - except Exception as ex: - self.log_error(ex) - - def generate_temp_hum_control_status(self): - status = [] - for temp_hum_control in list(filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): - status.append(dict(index_id=temp_hum_control['index_id'], status=False)) - self.temp_hum_control_status = status - - def configure_gpio(self): - try: - - for gpio_out in list( - filter(lambda item: item['output_type'] == 'regular' or item['output_type'] == 'temp_hum_control', - self.rpi_outputs)): - initial_value = GPIO.HIGH if gpio_out['active_low'] else GPIO.LOW - pin = self.to_int(gpio_out['gpio_pin']) - if pin not in self.rpi_outputs_not_changed: - self._logger.info("Setting GPIO pin %s as OUTPUT with initial value: %s", pin, initial_value) - GPIO.setup(pin, GPIO.OUT, initial=initial_value) - for gpio_out_pwm in list(filter(lambda item: item['output_type'] == 'pwm', self.rpi_outputs)): - pin = self.to_int(gpio_out_pwm['gpio_pin']) - self._logger.info("Setting GPIO pin %s as PWM", pin) - for pwm in (pwm_dict for pwm_dict in self.pwm_instances if pin in pwm_dict): - self.pwm_instances.remove(pwm) - self.clear_channel(pin) - GPIO.setup(pin, GPIO.OUT) - pwm_instance = GPIO.PWM(pin, self.to_int(gpio_out_pwm['pwm_frequency'])) - self._logger.info("starting PWM on pin %s", pin) - pwm_instance.start(0) - self.pwm_instances.append({pin: pwm_instance}) - for gpio_out_neopixel in list( - filter(lambda item: item['output_type'] == 'neopixel_direct', self.rpi_outputs)): - pin = self.to_int(gpio_out_neopixel['gpio_pin']) - self.clear_channel(pin) - - for rpi_input in list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)): - gpio_pin = self.to_int(rpi_input['gpio_pin']) - pull_resistor = GPIO.PUD_UP if rpi_input['input_pull_resistor'] == 'input_pull_up' else GPIO.PUD_DOWN - GPIO.setup(gpio_pin, GPIO.IN, pull_resistor) - edge = GPIO.RISING if rpi_input['edge'] == 'rise' else GPIO.FALLING - - inputs_same_gpio = list( - [r_inp for r_inp in self.rpi_inputs if self.to_int(r_inp['gpio_pin']) == gpio_pin]) - - if len(inputs_same_gpio) > 1: - GPIO.remove_event_detect(gpio_pin) - for other_input in inputs_same_gpio: - if other_input['edge'] is not edge: - edge = GPIO.BOTH - - if rpi_input['action_type'] == 'output_control': - self._logger.info("Adding GPIO event detect on pin %s with edge: %s", gpio_pin, edge) - GPIO.add_event_detect(gpio_pin, edge, callback=self.handle_gpio_control, bouncetime=200) - if (rpi_input['action_type'] == 'printer_control' and rpi_input['printer_action'] != 'filament'): - GPIO.add_event_detect(gpio_pin, edge, callback=self.handle_printer_action, bouncetime=200) - self._logger.info("Adding PRINTER CONTROL event detect on pin %s with edge: %s", gpio_pin, edge) - except Exception as ex: - self.log_error(ex) - - def handle_filamment_detection(self, channel): - try: - for filament_sensor in list(filter( - lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ - 'printer_action'] == 'filament' and self.to_int(item['gpio_pin']) == self.to_int(channel), - self.rpi_inputs)): - if ((filament_sensor['edge'] == 'fall') ^ (GPIO.input(self.to_int(filament_sensor['gpio_pin']))) and - filament_sensor['filament_sensor_enabled']): - last_detected_time = list(filter(lambda item: item['index_id'] == filament_sensor['index_id'], - self.last_filament_end_detected)).pop()['time'] - time_now = time.time() - time_difference = self.to_int(time_now - last_detected_time) - time_out_value = self.to_int(filament_sensor['filament_sensor_timeout']) - if time_difference > time_out_value: - self._logger.info("Detected end of filament.") - for item in self.last_filament_end_detected: - if item['index_id'] == filament_sensor['index_id']: - item['time'] = time_now - for line in self._settings.get(["filament_sensor_gcode"]).split('\n'): - if line: - self._printer.commands(line.strip()) - self._logger.info("Sending GCODE command: %s", line.strip()) - time.sleep(0.2) - for notification in self.notifications: - if notification['filamentChange']: - msg = "Filament change action caused by sensor: " + str(filament_sensor['label']) - self.send_notification(msg) - else: - self._logger.info("Prevented end of filament detection, filament sensor timeout not elapsed.") - except Exception as ex: - self.log_error(ex) - - def start_filament_detection(self): - self.stop_filament_detection() - try: - for filament_sensor in list(filter( - lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ - 'printer_action'] == 'filament', self.rpi_inputs)): - edge = GPIO.RISING if filament_sensor['edge'] == 'rise' else GPIO.FALLING - if GPIO.input(self.to_int(filament_sensor['gpio_pin'])) == (edge == GPIO.RISING): - self._printer.pause_print() - self._logger.info("Started printing with no filament.") - else: - self.last_filament_end_detected.append(dict(index_id=filament_sensor['index_id'], time=0)) - self._logger.info("Adding GPIO event detect on pin %s with edge: %s", filament_sensor['gpio_pin'], - edge) - GPIO.add_event_detect(self.to_int(filament_sensor['gpio_pin']), edge, - callback=self.handle_filamment_detection, bouncetime=200) - except Exception as ex: - self.log_error(ex) - - def stop_filament_detection(self): - try: - self.last_filament_end_detected = [] - for filament_sensor in list(filter( - lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ - 'printer_action'] == 'filament', self.rpi_inputs)): - GPIO.remove_event_detect(self.to_int(filament_sensor['gpio_pin'])) - except Exception as ex: - self.log_error(ex) - - def cancel_all_events_on_queue(self): - for task in self.event_queue: - try: - task['thread'].cancel() - except: - self._logger.warn("Failed to stop task %s.", task) - pass - - def handle_initial_gpio_control(self): - try: - for rpi_input in list( - filter(lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'output_control', - self.rpi_inputs)): - gpio_pin = self.to_int(rpi_input['gpio_pin']) - controlled_io = self.to_int(rpi_input['controlled_io']) - if (rpi_input['edge'] == 'fall') ^ GPIO.input(gpio_pin): - rpi_output = [r_out for r_out in self.rpi_outputs if - self.to_int(r_out['index_id']) == controlled_io].pop() - if rpi_output['output_type'] == 'regular': - val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) - except Exception as ex: - self.log_error(ex) - pass - - def shell_command(self, command): - try: - stdout = (Popen(command, shell=True, stdout=PIPE).stdout).read() - self._plugin_manager.send_plugin_message(self._identifier, - dict(is_msg=True, msg=stdout, msg_type="success")) - except Exception as ex: - self.log_error(ex) - self._plugin_manager.send_plugin_message(self._identifier, - dict(is_msg=True, msg="Could not execute shell script", msg_type="error")) - - def handle_gpio_control(self, channel): - - try: - self._logger.debug("GPIO event triggered on channel %s", channel) - for rpi_input in list( - filter(lambda item: self.to_int(item['gpio_pin']) == self.to_int(channel), self.rpi_inputs)): - gpio_pin = self.to_int(rpi_input['gpio_pin']) - controlled_io = self.to_int(rpi_input['controlled_io']) - if (rpi_input['edge'] == 'fall') ^ GPIO.input(gpio_pin): - rpi_output = [r_out for r_out in self.rpi_outputs if - self.to_int(r_out['index_id']) == controlled_io].pop() - if rpi_output['output_type'] == 'regular': - if rpi_input['controlled_io_set_value'] == 'toggle': - val = GPIO.LOW if GPIO.input( - self.to_int(rpi_output['gpio_pin'])) == GPIO.HIGH else GPIO.HIGH - else: - val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) - for notification in self.notifications: - if notification['gpioAction']: - msg = "GPIO control action caused by input " + str( - rpi_input['label']) + ". Setting GPIO" + str( - rpi_input['controlled_io']) + " to: " + str(rpi_input['controlled_io_set_value']) - self.send_notification(msg) - if rpi_output['output_type'] == 'gcode_output': - self.send_gcode_command(rpi_output['gcode']) - for notification in self.notifications: - if notification['gpioAction']: - msg = "GPIO control action caused by input " + str( - rpi_input['label']) + ". Sending GCODE command" - self.send_notification(msg) - if rpi_output['output_type'] == 'shell_output': - command = rpi_output['shell_script'] - self.shell_command(command) - except Exception as ex: - self.log_error(ex) - pass - - def send_gcode_command(self, command): - for line in command.split('\n'): - if line: - self._printer.commands(line.strip()) - self._logger.info("Sending GCODE command: %s", line.strip()) - time.sleep(0.2) - - def handle_printer_action(self, channel): - try: - for rpi_input in self.rpi_inputs: - if (channel == self.to_int(rpi_input['gpio_pin']) and rpi_input[ - 'action_type'] == 'printer_control' and ( - (rpi_input['edge'] == 'fall') ^ GPIO.input(self.to_int(rpi_input['gpio_pin'])))): - if rpi_input['printer_action'] == 'resume': - self._logger.info("Printer action resume.") - self._printer.resume_print() - elif rpi_input['printer_action'] == 'pause': - self._logger.info("Printer action pause.") - self._printer.pause_print() - elif rpi_input['printer_action'] == 'cancel': - self._logger.info("Printer action cancel.") - self._printer.cancel_print() - elif rpi_input['printer_action'] == 'toggle': - self._logger.info("Printer action toggle.") - if self._printer.is_operational(): - self._printer.toggle_pause_print() - else: - self._printer.connect() - elif rpi_input['printer_action'] == 'start': - self._logger.info("Printer action start.") - self._printer.start_print() - elif rpi_input['printer_action'] == 'toggle_job': - self._logger.info("Printer action toggle_job.") - if self._printer.is_operational(): - if self._printer.is_printing(): - self._printer.cancel_print() - elif self._printer.is_ready(): - self._printer.start_print() - else: - self._printer.connect() - elif rpi_input['printer_action'] == 'stop_temp_hum_control': - self._logger.info("Printer action stopping temperature control.") - for rpi_output in self.rpi_outputs: - if rpi_output['auto_shutdown'] and rpi_output['output_type'] == 'temp_hum_control': - rpi_output['temp_ctr_set_value'] = 0 - self.handle_temp_hum_control() - for notification in self.notifications: - if notification['printer_action']: - msg = "Printer action: " + rpi_input['printer_action'] + " caused by input: " + str( - rpi_input['label']) - self.send_notification(msg) - except Exception as ex: - self.log_error(ex) - pass - - def write_gpio(self, gpio, value, queue_id=None): - try: - GPIO.output(gpio, value) - if queue_id is not None: - self._logger.debug("Running scheduled queue id %s", queue_id) - self._logger.debug("Writing on GPIO: %s value %s", gpio, value) - self.update_ui() - if queue_id is not None: - self.stop_queue_item(queue_id) - except Exception as ex: - template = "An exception of type {0} occurred on {1} when writing on pin {2}. Arguments:\n{3!r}" - message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, gpio, ex.args) - self._logger.warn(message) - pass - - def write_pwm(self, gpio, pwm_value, queue_id=None): - try: - if queue_id is not None: - self._logger.debug("running scheduled queue id %s", queue_id) - for pwm in self.pwm_instances: - if gpio in pwm: - pwm_object = pwm[gpio] - old_pwm_value = pwm['duty_cycle'] if 'duty_cycle' in pwm else -1 - if not self.to_int(old_pwm_value) == self.to_int(pwm_value): - pwm['duty_cycle'] = pwm_value - pwm_object.start(pwm_value) #should be changed back to pwm_object.ChangeDutyCycle() but this - # was causing errors. - self._logger.debug("Writing PWM on gpio: %s value %s", gpio, pwm_value) - self.update_ui() - if queue_id is not None: - self.stop_queue_item(queue_id) - break - except Exception as ex: - self.log_error(ex) - pass - - def get_output_list(self): - result = [] - for rpi_output in self.rpi_outputs: - if rpi_output['output_type'] == 'regular': - result.append(self.to_int(rpi_output['gpio_pin'])) - return result - - def send_notification(self, message): - try: - provider = self._settings.get(["notification_provider"]) - if provider == 'ifttt': - event = self._settings.get(["notification_event_name"]) - api_key = self._settings.get(["notification_api_key"]) - self._logger.debug("Sending notification to: %s with msg: %s with key: %s", provider, message, - api_key) - try: - res = self.ifttt_notification(message, event, api_key) - except requests.exceptions.ConnectionError: - self._logger.info("Error: Could not connect to IFTTT") - except requests.exceptions.HTTPError: - self._logger.info("Error: Received invalid response") - except requests.exceptions.Timeout: - self._logger.info("Error: Request timed out") - except requests.exceptions.TooManyRedirects: - self._logger.info("Error: Too many redirects") - except requests.exceptions.RequestException as reqe: - self._logger.info("Error: {e}".format(e=reqe)) - if res.status_code != requests.codes['ok']: - try: - j = res.json() - except ValueError: - self._logger.info('Error: Could not parse server response. Event not sent') - for err in j['errors']: - self._logger.info('Error: {}'.format(err['message'])) - except Exception as ex: - self.log_error(ex) - pass - - def ifttt_notification(self, message, event, api_key): - url = "https://maker.ifttt.com/trigger/{e}/with/key/{k}/".format(e=event, k=api_key) - payload = {'value1': message} - return requests.post(url, data=payload) - - # ~~ EventPlugin mixin - def on_event(self, event, payload): - if event == Events.CONNECTED: - self.update_ui() - - if event == Events.CLIENT_OPENED: - self.update_ui() - - if event == Events.PRINT_RESUMED: - self.start_filament_detection() - - if event == Events.PRINT_STARTED: - self.print_complete = False - self.cancel_all_events_on_queue() - self.event_queue = [] - self.start_filament_detection() - for rpi_output in self.rpi_outputs: - if rpi_output['auto_startup']: - delay_seconds = self.get_startup_delay_from_output(rpi_output) - self.schedule_auto_startup_outputs(rpi_output, delay_seconds) - if rpi_output['toggle_timer']: - if rpi_output['output_type'] == 'regular' or rpi_output['output_type'] == 'pwm': - self.toggle_output(rpi_output['index_id'], True) - if self.is_hour(rpi_output['shutdown_time']): - shutdown_delay_seconds = self.get_shutdown_delay_from_output(rpi_output) - self.schedule_auto_shutdown_outputs(rpi_output, shutdown_delay_seconds) - self.run_tasks() - self.update_ui() - - elif event == Events.PRINT_DONE: - self.stop_filament_detection() - self.print_complete = True - for rpi_output in self.rpi_outputs: - shutdown_time = rpi_output['shutdown_time'] - if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: - rpi_output['duty_cycle'] = rpi_output['default_duty_cycle'] - if rpi_output['auto_shutdown'] and not self.is_hour(shutdown_time): - delay_seconds = self.to_float(shutdown_time) - self.schedule_auto_shutdown_outputs(rpi_output, delay_seconds) - self.run_tasks() - self.update_ui() - - elif event in (Events.PRINT_CANCELLED, Events.PRINT_FAILED): - self.stop_filament_detection() - self.cancel_all_events_on_queue() - self.event_queue = [] - for rpi_output in self.rpi_outputs: - if rpi_output['shutdown_on_failed']: - shutdown_time = rpi_output['shutdown_time'] - if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: - rpi_output['duty_cycle'] = rpi_output['default_duty_cycle'] - if rpi_output['auto_shutdown'] and not self.is_hour(shutdown_time): - delay_seconds = self.to_float(shutdown_time) - self.schedule_auto_shutdown_outputs(rpi_output, delay_seconds) - if rpi_output['output_type'] == 'temp_hum_control': - rpi_output['temp_ctr_set_value'] = 0 - self.run_tasks() - - if event == Events.PRINT_DONE: - for notification in self.notifications: - if notification['printFinish']: - file_name = os.path.basename(payload["file"]) - elapsed_time_in_seconds = payload["time"] - elapsed_time = octoprint.util.get_formatted_timedelta(timedelta(seconds=elapsed_time_in_seconds)) - msg = "Print job finished: " + file_name + "finished printing in " + file_name, elapsed_time - self.send_notification(msg) - - def run_tasks(self): - for task in self.event_queue: - if not task['thread'].is_alive(): - task['thread'].start() - - def schedule_auto_shutdown_outputs(self, rpi_output, shutdown_delay_seconds): - sufix = 'auto_shutdown' - if rpi_output['output_type'] == 'regular': - value = True if rpi_output['active_low'] else False - self.add_regular_output_to_queue(shutdown_delay_seconds, rpi_output, value, sufix) - if rpi_output['output_type'] == 'ledstrip': - self.ledstrip_set_rgb(rpi_output) - if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: - value = 0 - self.add_pwm_output_to_queue(shutdown_delay_seconds, rpi_output, value, sufix) - if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: - self.schedule_pwm_duty_on_queue(shutdown_delay_seconds, rpi_output, 0, sufix) - if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): - self.add_neopixel_output_to_queue(rpi_output, shutdown_delay_seconds, 0, 0, 0, sufix) - if rpi_output['output_type'] == 'temp_hum_control': - value = 0 - self.add_temperature_output_temperature_queue(shutdown_delay_seconds, rpi_output, value, sufix) - self._logger.debug("Events scheduled to run %s", self.event_queue) - - def ledstrip_set_rgb(self, rpi_output, rgb=None): - clk = rpi_output["ledstrip_gpio_clk"] - data = rpi_output["ledstrip_gpio_dat"] - if clk is not None and data is not None: - ledstrip = LEDStrip(self.to_int(clk), self.to_int(data)) - if rgb is None: - red, green, blue = self.get_color_from_rgb(rpi_output['default_ledstrip_color']) - else: - red, green, blue = self.get_color_from_rgb(rgb) - - self._logger.info("LEDSTRIP set rgb color: %s, %s, %s", red, green, blue) - ledstrip.setcolourrgb(self.to_int(red), self.to_int(green), self.to_int(blue)) - - def start_outpus_with_server(self): - for rpi_output in self.rpi_outputs: - if rpi_output['startup_with_server']: - gpio = self.to_int(rpi_output['gpio_pin']) - if rpi_output['output_type'] == 'regular': - value = False if rpi_output['active_low'] else True - self.write_gpio(gpio, value) - if rpi_output['output_type'] == 'ledstrip': - self.ledstrip_set_rgb(rpi_output) - if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: - value = self.to_int(rpi_output['default_duty_cycle']) - self.write_pwm(gpio, value) - if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): - red, green, blue = self.get_color_from_rgb(rpi_output['default_neopixel_color']) - led_count = rpi_output['neopixel_count'] - led_brightness = rpi_output['neopixel_brightness'] - address = rpi_output['microcontroller_address'] - index_id = self.to_int(rpi_output['index_id']) - neopixel_direct = rpi_output['output_type'] == 'neopixel_direct' - self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, - green, blue, address, neopixel_direct, index_id) - if rpi_output['output_type'] == 'temp_hum_control': - rpi_output['temp_ctr_set_value'] = rpi_output['temp_ctr_default_value'] - - def schedule_auto_startup_outputs(self, rpi_output, delay_seconds): - sufix = 'auto_startup' - if rpi_output['output_type'] == 'regular': - value = False if rpi_output['active_low'] else True - self.add_regular_output_to_queue(delay_seconds, rpi_output, value, sufix) - if rpi_output['output_type'] == 'ledstrip': - self.ledstrip_set_rgb(rpi_output) - if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: - value = self.to_int(rpi_output['default_duty_cycle']) - self.add_pwm_output_to_queue(delay_seconds, rpi_output, value, sufix) - if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): - red, green, blue = self.get_color_from_rgb(rpi_output['default_neopixel_color']) - self.add_neopixel_output_to_queue(rpi_output, delay_seconds, red, green, blue, sufix) - if rpi_output['output_type'] == 'temp_hum_control': - value = rpi_output['temp_ctr_default_value'] - self.add_temperature_output_temperature_queue(delay_seconds, rpi_output, value, sufix) - self._logger.debug("Events scheduled to run %s", self.event_queue) - - def get_color_from_rgb(self, stringColor): - stringColor = stringColor.replace('rgb(', '') - red = stringColor[:stringColor.index(',')] - stringColor = stringColor[stringColor.index(',') + 1:] - green = stringColor[:stringColor.index(',')] - stringColor = stringColor[stringColor.index(',') + 1:] - blue = stringColor[:stringColor.index(')')] - return red, green, blue - - def get_shutdown_delay_from_output(self, rpi_output): - shutdown_time = rpi_output['shutdown_time'] - - shut_down_date_time = self.create_date(shutdown_time) - - if shut_down_date_time < datetime.now(): - shut_down_date_time = shut_down_date_time + timedelta(days=1) - - delay_seconds = (shut_down_date_time - datetime.now()).total_seconds() - - return delay_seconds - - def add_neopixel_output_to_queue(self, rpi_output, delay_seconds, red, green, blue, sufix): - gpio_pin = rpi_output['gpio_pin'] - ledCount = rpi_output['neopixel_count'] - ledBrightness = rpi_output['neopixel_brightness'] - address = rpi_output['microcontroller_address'] - neopixel_direct = rpi_output['output_type'] == 'neopixel_direct' - index_id = self.to_int(rpi_output['index_id']) - - queue_id = '{0!s}_{1!s}'.format(index_id, sufix) - - self._logger.debug("Scheduling neopixel output id %s for on %s delay_seconds", queue_id, delay_seconds) - - thread = threading.Timer(delay_seconds, self.send_neopixel_command, - args=[gpio_pin, ledCount, ledBrightness, red, green, blue, address, neopixel_direct, - index_id, queue_id]) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def add_pwm_output_to_queue(self, delay_seconds, rpi_output, value, sufix): - queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) - - self._logger.debug("Scheduling pwm output id %s for on %s delay_seconds", queue_id, delay_seconds) - - thread = threading.Timer(delay_seconds, self.write_pwm, - args=[self.to_int(rpi_output['gpio_pin']), value, queue_id]) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def schedule_pwm_duty_on_queue(self, delay_seconds, rpi_output, value, sufix): - queue_id = '{0!s}_{1!s}_{2!s}'.format(rpi_output['index_id'], "pwm_linked_temp", sufix) - thread = threading.Timer(delay_seconds, self.set_pwm_duty_cycle, args=[rpi_output, value, queue_id]) - - self._logger.debug("Scheduling pwm linked temp output id %s on %s delay_seconds", queue_id, delay_seconds) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def set_pwm_duty_cycle(self, rpi_output, value, queue_id): - rpi_output['duty_cycle'] = value - if queue_id is not None: - self.stop_queue_item(queue_id) - - def add_regular_output_to_queue(self, delay_seconds, rpi_output, value, sufix): - queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) - - self._logger.debug("Scheduling regular output id %s on %s delay_seconds", queue_id, delay_seconds) - - thread = threading.Timer(delay_seconds, self.write_gpio, - args=[self.to_int(rpi_output['gpio_pin']), value, queue_id]) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def add_temperature_output_temperature_queue(self, delay_seconds, rpi_output, value, sufix): - queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) - self._logger.debug("Scheduling temperature control id %s on %s delay_seconds", queue_id, delay_seconds) - - thread = threading.Timer(delay_seconds, self.write_temperature_to_output, - args=[self.to_int(rpi_output['index_id']), value, queue_id]) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def write_temperature_to_output(self, rpi_output_index, value, queue_id=None): - try: - rpi_output = [r_out for r_out in self.rpi_outputs if - self.to_int(r_out['index_id']) == rpi_output_index].pop() - - if rpi_output['output_type'] == 'temp_hum_control': - rpi_output['temp_ctr_set_value'] = value - - if queue_id is not None: - self._logger.debug("running scheduled queue id %s", queue_id) - self._logger.debug("Setting temperature to output index: %s value %s", rpi_output['index_id'], value) - - self.update_ui() - if queue_id is not None: - self.stop_queue_item(queue_id) - - except Exception as ex: - template = "An exception of type {0} occurred on {1}. Arguments:\n{3!r}" - message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, ex.args) - self._logger.warn(message) - pass - - def get_startup_delay_from_output(self, rpi_output): - start_up_time = rpi_output['startup_time'] - if self.is_hour(start_up_time): - start_up_date_time = self.create_date(start_up_time) - if start_up_date_time < datetime.now(): - delay_seconds = 0.0 - else: - delay_seconds = (start_up_date_time - datetime.now()).total_seconds() - else: - delay_seconds = self.to_float(rpi_output['startup_time']) - return delay_seconds - - # ~~ SettingsPlugin mixin - def on_settings_save(self, data): - outputsBeforeSave = self.get_output_list() - octoprint.plugin.SettingsPlugin.on_settings_save(self, data) - self.rpi_outputs = self._settings.get(["rpi_outputs"]) - self.rpi_inputs = self._settings.get(["rpi_inputs"]) - self.notifications = self._settings.get(["notifications"]) - outputsAfterSave = self.get_output_list() - - commonPins = list(set(outputsBeforeSave) & set(outputsAfterSave)) - - for pin in (pin for pin in outputsBeforeSave if pin not in commonPins): - self.clear_channel(pin) - - self.rpi_outputs_not_changed = commonPins - self.clear_gpio() - - self._logger.debug("rpi_outputs: %s", self.rpi_outputs) - self._logger.debug("rpi_inputs: %s", self.rpi_inputs) - self.setup_gpio() - self.configure_gpio() - self.generate_temp_hum_control_status() - - def get_settings_defaults(self): - return dict(rpi_outputs=[], rpi_inputs=[], - filament_sensor_gcode="G91 ;Set Relative Mode \n" + "G1 E-5.000000 F500 ;Retract 5mm\n" + "G1 Z15 F300 ;move Z up 15mm\n" + "G90 ;Set Absolute Mode\n " + "G1 X20 Y20 F9000 ;Move to hold position\n" + "G91 ;Set Relative Mode\n" + "G1 E-40 F500 ;Retract 40mm\n" + "M0 ;Idle Hold\n" + "G90 ;Set Absolute Mode\n" + "G1 F5000 ;Set speed limits\n" + "G28 X0 Y0 ;Home X Y\n" + "M82 ;Set extruder to Absolute Mode\n" + "G92 E0 ;Set Extruder to 0", - use_sudo=True, neopixel_dma=10, debug=False, gcode_control=False, debug_temperature_log=False, - use_board_pin_number=False, notification_provider="disabled", notification_api_key="", - notification_event_name="printer_event", notifications=[{ - 'printFinish' : True, - 'filamentChange' : True, - 'printer_action' : True, - 'temperatureAction': True, 'gpioAction': True - }]) - - # ~~ TemplatePlugin - def get_template_configs(self): - return [dict(type="settings", custom_bindings=True), dict(type="tab", custom_bindings=True), - dict(type="navbar", custom_bindings=True, suffix="_1", classes=["dropdown"]), - dict(type="navbar", custom_bindings=True, template="enclosure_navbar_input.jinja2", suffix="_2", - classes=["dropdown"])] - - # ~~ AssetPlugin mixin - def get_assets(self): - return dict(js=["js/enclosure.js", "js/bootstrap-colorpicker.min.js"], - css=["css/bootstrap-colorpicker.css", "css/enclosure.css"]) - - # ~~ Softwareupdate hook - def get_update_information(self): - return dict(enclosure=dict(displayName="Enclosure Plugin", displayVersion=self._plugin_version, - # version check: github repository - type="github_release", user="vitormhenrique", repo="OctoPrint-Enclosure", current=self._plugin_version, - # update method: pip - pip="https://github.com/vitormhenrique/OctoPrint-Enclosure/archive/{target_version}.zip")) - - def hook_gcode_queuing(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs): - if self._settings.get(["gcode_control"]) is False: - return - - if cmd.strip().startswith("ENC"): - self._logger.debug("Gcode queuing: %s", cmd) - index_id = self.to_int(self.get_gcode_value(cmd, 'O')) - for output in [item for item in self.rpi_outputs if item['index_id'] == index_id]: - if output['output_type'] == 'regular': - set_value = self.to_int(self.get_gcode_value(cmd, 'S')) - set_value = self.constrain(set_value, 0, 1) - value = True if set_value == 1 else False - value = (not value) if output['active_low'] else value - self.write_gpio(self.to_int(output['gpio_pin']), value) - comm_instance._log("Setting REGULAR output %s to value %s" % (index_id, value)) - return - if output['output_type'] == 'pwm': - set_value = self.to_int(self.get_gcode_value(cmd, 'S')) - set_value = self.constrain(set_value, 0, 100) - output['duty_cycle'] = set_value - self.write_pwm(self.to_int(output['gpio_pin']), set_value) - comm_instance._log("Setting PWM output %s to value %s" % (index_id, set_value)) - return - if output['output_type'] == 'neopixel_indirect' or output['output_type'] == 'neopixel_direct': - red = self.get_gcode_value(cmd, 'R') - green = self.get_gcode_value(cmd, 'G') - blue = self.get_gcode_value(cmd, 'B') - - led_count = output['neopixel_count'] - led_brightness = output['neopixel_brightness'] - address = output['microcontroller_address'] - - index_id = self.to_int(output['index_id']) - - neopixel_direct = output['output_type'] == 'neopixel_direct' - - self.send_neopixel_command(self.to_int(output['gpio_pin']), led_count, led_brightness, red, green, - blue, address, neopixel_direct, index_id) - comm_instance._log( - "Setting NEOPIXEL output %s to red: %s green: %s blue: %s" % (index_id, red, green, blue)) - return - if output['output_type'] == 'temp_hum_control': - set_value = self.to_float(self.get_gcode_value(cmd, 'S')) - should_wait = self.to_int(self.get_gcode_value(cmd, 'W')) - if should_wait == 1 and self._printer.is_printing(): - self._printer.pause_print() - self.waiting_temperature.append(index_id) - output['temp_ctr_set_value'] = set_value - self.update_ui_set_temperature() - self.handle_temp_hum_control() - comm_instance._log("Setting TEMP/HUM control output %s to value %s" % (index_id, set_value)) - return + octoprint.plugin.AssetPlugin, octoprint.plugin.BlueprintPlugin, + octoprint.plugin.EventHandlerPlugin): + rpi_outputs = [] + rpi_inputs = [] + waiting_temperature = [] + rpi_outputs_not_changed = [] + notifications = [] + pwm_instances = [] + event_queue = [] + temp_hum_control_status = [] + temperature_sensor_data = [] + last_filament_end_detected = [] + print_complete = False + development_mode = False + dummy_value = 30.0 + dummy_delta = 0.5 + + def start_timer(self): + """ + Function to start timer that checks enclosure temperature + """ + + self._check_temp_timer = RepeatedTimer(10, self.check_enclosure_temp, None, None, True) + self._check_temp_timer.start() + + @staticmethod + def to_float(value): + """Converts value to flow + Arguments: + value {any} -- value to be + Returns: + float -- value converted + """ + + try: + val = float(value) + return val + except: + return 0 + + @staticmethod + def to_int(value): + try: + val = int(value) + return val + except: + return 0 + + @staticmethod + def is_hour(value): + try: + datetime.strptime(value, '%H:%M') + return True + except: + return False + + @staticmethod + def create_date(value): + temp_string = datetime.now().strftime('%m/%d/%Y') + " " + value + return datetime.strptime(temp_string, '%m/%d/%Y %H:%M') + + @staticmethod + def constrain(n, minn, maxn): + return max(min(maxn, n), minn) + + @staticmethod + def get_gcode_value(command_string, gcode): + semicolon = command_string.find(';') + if not semicolon == -1: + command_string = command_string[:semicolon] + + for command in command_string.split(' '): + index = command.upper().find(gcode.upper()) + if not index == -1: + return command.replace(gcode, '') + return -1 + + # ~~ StartupPlugin mixin + def on_after_startup(self): + self.pwm_instances = [] + self.event_queue = [] + self.rpi_outputs_not_changed = [] + self.rpi_outputs = self._settings.get(["rpi_outputs"]) + self.rpi_inputs = self._settings.get(["rpi_inputs"]) + self.notifications = self._settings.get(["notifications"]) + self.generate_temp_hum_control_status() + self.setup_gpio() + self.configure_gpio() + self.update_ui() + self.start_outpus_with_server() + self.handle_initial_gpio_control() + self.start_timer() + self.print_complete = False + + def get_settings_version(self): + return 6 + + def on_settings_migrate(self, target, current=None): + self._logger.warn("######### current settings version %s target settings version %s #########", current, target) + self._logger.info("######### Current settings #########") + self._logger.info("rpi_outputs: %s", self.rpi_outputs) + self._logger.info("rpi_inputs: %s", self.rpi_inputs) + self._logger.info("######### End Current Settings #########") + if current >= 4 and target == 6: + self._logger.warn("######### migrating settings to v6 #########") + old_outputs = self._settings.get(["rpi_outputs"]) + for rpi_output in old_outputs: + if 'shutdown_on_failed' not in rpi_output: + rpi_output['shutdown_on_failed'] = False + if 'shell_script' not in rpi_output: + rpi_output['shell_script'] = "" + self._settings.set(["rpi_outputs"], old_outputs) + else: + self._logger.warn("######### settings not compatible #########") + self._settings.set(["rpi_outputs"], []) + self._settings.set(["rpi_inputs"], []) + self.rpi_inputs = self._settings.get(["rpi_inputs"]) + + #Scan all configured inputs and outputs and return the pin value + @octoprint.plugin.BlueprintPlugin.route("/ReadPin/", methods=["GET"]) + def ReadSinglePin(self, identifier): + Resp = [] + MatchFound = False + for rpi_input in self.rpi_inputs: + if identifier == self.to_int(rpi_input['gpio_pin']): + MatchFound = True + ConfiguredAs = "Input" + ActiveLow = CheckInputActiveLow(rpi_input['input_pull_resistor']) + pin = self.to_int(rpi_input['gpio_pin']) + val = PinState_Human(pin,ActiveLow) + label = rpi_input['label'] + Resp.append(dict(Configured_As=ConfiguredAs, label=label, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['gpio_pin']): + MatchFound = True + ConfiguredAs = "Output" + ActiveLow = CheckInputActiveLow(rpi_output['active_low']) + pin = self.to_int(rpi_output['gpio_pin']) + val = PinState_Human(pin,ActiveLow) + label = rpi_output['label'] + Resp.append(dict(Configured_As=ConfiguredAs, label=label, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) + if MatchFound == False: + pin = int(identifier) + ConfiguredAs = "Unknown" + ActiveLow = "Unknown" + try: + val = GPIO.input(pin) + except: + val = "GPIO pin not initialized." + Resp.append(dict(Configured_As=ConfiguredAs, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) + return Response(json.dumps(Resp), mimetype='application/json') + + # ~~ Blueprintplugin mixin + @octoprint.plugin.BlueprintPlugin.route("/inputs", methods=["GET"]) + def get_inputs(self): + inputs = [] + for rpi_input in self.rpi_inputs: + index = self.to_int(rpi_input['index_id']) + label = rpi_input['label'] + ActiveLow = CheckInputActiveLow(rpi_input['input_pull_resistor']) + pin = self.to_int(rpi_input['gpio_pin']) + val = PinState_Human(pin,ActiveLow) + inputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, State=val)) + return Response(json.dumps(inputs), mimetype='application/json') + + @octoprint.plugin.BlueprintPlugin.route("/inputs/", methods=["GET"]) + def get_input_status(self, identifier): + for rpi_input in self.rpi_inputs: + if identifier == self.to_int(rpi_input['index_id']): + return Response(json.dumps(rpi_input), mimetype='application/json') + return make_response('', 404) + + + @octoprint.plugin.BlueprintPlugin.route("/temperature/", methods=["PATCH"]) + @restricted_access + def set_enclosure_temp_humidity(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'temperature' not in data: + return make_response("missing temperature attribute", 406) + + set_value = data["temperature"] + + for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == identifier]: + temp_hum_control['temp_ctr_set_value'] = set_value + + self.handle_temp_hum_control() + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/filament/", methods=["PATCH"]) + @restricted_access + def set_filament_sensor(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + for sensor in self.rpi_inputs: + if identifier == self.to_int(sensor['index_id']): + sensor['filament_sensor_enabled'] = value + self._logger.info("Setting filament sensor for input %s to : %s", str(identifier), value) + self._settings.set(["rpi_inputs"], self.rpi_inputs) + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/outputs", methods=["GET"]) + def get_outputs(self): + outputs = [] + for rpi_output in self.rpi_outputs: + if rpi_output['output_type'] == 'regular': + index = self.to_int(rpi_output['index_id']) + label = rpi_output['label'] + pin = self.to_int(rpi_output['gpio_pin']) + ActiveLow = rpi_output['active_low'] + val = PinState_Human(pin,ActiveLow) + outputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, State=val)) + return Response(json.dumps(outputs), mimetype='application/json') + + + @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["GET"]) + def get_output_status(self, identifier): + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + out = copy.deepcopy(rpi_output) + pin = self.to_int(rpi_output['gpio_pin']) + out['current_value'] = PinState_Boolean(pin, rpi_output['active_low'] ) + return Response(json.dumps(out), mimetype='application/json') + return make_response('', 404) + + + @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["PATCH"]) + @restricted_access + def set_io(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + val = (not value) if rpi_output['active_low'] else value + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-startup", methods=["PATCH"]) + @restricted_access + def set_auto_startup(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + if not value: + suffix = 'auto_startup' + queue_id = '{0!s}_{1!s}'.format(str(identifier), suffix) + self.stop_queue_item(queue_id) + for output in self.rpi_outputs: + if identifier == self.to_int(output['index_id']): + output['auto_startup'] = value + self._logger.info("Setting auto startup for output %s to : %s", str(identifier), value) + self._settings.set(["rpi_outputs"], self.rpi_outputs) + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-shutdown", methods=["PATCH"]) + @restricted_access + def set_auto_shutdown(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + if not value: + suffix = 'auto_shutdown' + queue_id = '{0!s}_{1!s}'.format(str(identifier), suffix) + self.stop_queue_item(queue_id) + + for output in self.rpi_outputs: + if identifier == self.to_int(output['index_id']): + output['auto_shutdown'] = value + self._logger.info("Setting auto shutdown for output %s to : %s", str(identifier), value) + self._settings.set(["rpi_outputs"], self.rpi_outputs) + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/pwm/", methods=["PATCH"]) + @restricted_access + def set_pwm(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'duty_cycle' not in data: + return make_response("missing duty_cycle attribute", 406) + + set_value = self.to_int(data['duty_cycle']) + for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == identifier]: + rpi_output['duty_cycle'] = set_value + rpi_output['new_duty_cycle'] = "" + gpio = self.to_int(rpi_output['gpio_pin']) + self.write_pwm(gpio, set_value) + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/rgb-led/", methods=["PATCH"]) + @restricted_access + def set_ledstrip_color(self, identifier): + """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'rgb' not in data: + return make_response("missing rgb attribute", 406) + rgb = data['rgb'] + + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + self.ledstrip_set_rgb(rpi_output, rgb) + + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/neopixel/", methods=["PATCH"]) + @restricted_access + def set_neopixel(self, identifier): + """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'red' not in data: + return make_response("missing red attribute", 406) + if 'green' not in data: + return make_response("missing green attribute", 406) + if 'blue' not in data: + return make_response("missing blue attribute", 406) + + red = data['red'] + green = data['green'] + blue = data['blue'] + + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + led_count = rpi_output['neopixel_count'] + led_brightness = rpi_output['neopixel_brightness'] + address = rpi_output['microcontroller_address'] + + neopixel_dirrect = rpi_output['output_type'] == 'neopixel_direct' + + self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, green, + blue, address, neopixel_dirrect, identifier) + + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/clear-gpio", methods=["POST"]) + @restricted_access + def clear_gpio_mode(self): + GPIO.cleanup() + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/update", methods=["POST"]) + @restricted_access + def update_ui_requested(self): + self.update_ui() + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/shell/", methods=["POST"]) + @restricted_access + def send_shell_command(self, identifier): + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() + + command = rpi_output['shell_script'] + self.shell_command(command) + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/gcode/", methods=["POST"]) + @restricted_access + def requested_gcode_command(self, identifier): + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() + self.send_gcode_command(rpi_output['gcode']) + return make_response('', 204) + + + + + + """ + DEPRECATION + This API will be deprecated in a future version + """ + + # ~~ Blueprintplugin mixin + @octoprint.plugin.BlueprintPlugin.route("/setEnclosureTempHum", methods=["GET"]) + def set_enclosure_temp_humidity_old(self): + set_value = self.to_float(request.values["set_temperature"]) + index_id = self.to_int(request.values["index_id"]) + + for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == index_id]: + temp_hum_control['temp_ctr_set_value'] = set_value + + self.handle_temp_hum_control() + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/clearGPIOMode", methods=["GET"]) + def clear_gpio_mode_old(self): + GPIO.cleanup() + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/updateUI", methods=["GET"]) + def update_ui_requested_old(self): + self.update_ui() + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/getOutputStatus", methods=["GET"]) + def get_output_status_old(self): + gpio_status = [] + for rpi_output in self.rpi_outputs: + if rpi_output['output_type'] == 'regular': + pin = self.to_int(rpi_output['gpio_pin']) + ActiveLow = rpi_output['active_low'] + val = PinState_Boolean(pin, ActiveLow) + val2 = PinState_Human(pin, ActiveLow) + index = self.to_int(rpi_output['index_id']) + gpio_status.append(dict(index_id=index, status=val, State=val2)) + return Response(json.dumps(gpio_status), mimetype='application/json') + + @octoprint.plugin.BlueprintPlugin.route("/setIO", methods=["GET"]) + def set_io_old(self): + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False + for rpi_output in self.rpi_outputs: + if self.to_int(index) == self.to_int(rpi_output['index_id']): + val = (not value) if rpi_output['active_low'] else value + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/sendShellCommand", methods=["GET"]) + def send_shell_command_old(self): + output_index = self.to_int(request.values["index_id"]) + + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == output_index].pop() + + command = rpi_output['shell_script'] + self.shell_command(command) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setAutoStartUp", methods=["GET"]) + def set_auto_startup_old(self): + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False + + if not value: + suffix = 'auto_startup' + queue_id = '{0!s}_{1!s}'.format(index, suffix) + self.stop_queue_item(queue_id) + for output in self.rpi_outputs: + if self.to_int(index) == self.to_int(output['index_id']): + output['auto_startup'] = value + self._logger.info("Setting auto startup for output %s to : %s", index, value) + self._settings.set(["rpi_outputs"], self.rpi_outputs) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setAutoShutdown", methods=["GET"]) + def set_auto_shutdown_old(self): + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False + + if not value: + suffix = 'auto_shutdown' + queue_id = '{0!s}_{1!s}'.format(index, suffix) + self.stop_queue_item(queue_id) + + for output in self.rpi_outputs: + if self.to_int(index) == self.to_int(output['index_id']): + output['auto_shutdown'] = value + self._logger.info("Setting auto shutdown for output %s to : %s", index, value) + self._settings.set(["rpi_outputs"], self.rpi_outputs) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setFilamentSensor", methods=["GET"]) + def set_filament_sensor_old(self): + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False + for sensor in self.rpi_inputs: + if self.to_int(index) == self.to_int(sensor['index_id']): + sensor['filament_sensor_enabled'] = value + self._logger.info("Setting filament sensor for input %s to : %s", index, value) + self._settings.set(["rpi_inputs"], self.rpi_inputs) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setPWM", methods=["GET"]) + def set_pwm_old(self): + set_value = self.to_int(request.values["new_duty_cycle"]) + index_id = self.to_int(request.values["index_id"]) + for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == index_id]: + rpi_output['duty_cycle'] = set_value + rpi_output['new_duty_cycle'] = "" + gpio = self.to_int(rpi_output['gpio_pin']) + self.write_pwm(gpio, set_value) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/sendGcodeCommand", methods=["GET"]) + def requested_gcode_command_old(self): + gpio_index = self.to_int(request.values["index_id"]) + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == gpio_index].pop() + self.send_gcode_command(rpi_output['gcode']) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setNeopixel", methods=["GET"]) + def set_neopixel_old(self): + """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" + gpio_index = self.to_int(request.values["index_id"]) + red = request.values["red"] + green = request.values["green"] + blue = request.values["blue"] + for rpi_output in self.rpi_outputs: + if gpio_index == self.to_int(rpi_output['index_id']): + led_count = rpi_output['neopixel_count'] + led_brightness = rpi_output['neopixel_brightness'] + address = rpi_output['microcontroller_address'] + + neopixel_dirrect = rpi_output['output_type'] == 'neopixel_direct' + + self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, green, + blue, address, neopixel_dirrect, gpio_index) + + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setLedstripColor", methods=["GET"]) + def set_ledstrip_color_old(self): + """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" + gpio_index = self.to_int(request.values["index_id"]) + rgb = request.values["rgb"] + for rpi_output in self.rpi_outputs: + if gpio_index == self.to_int(rpi_output['index_id']): + self.ledstrip_set_rgb(rpi_output, rgb) + + return jsonify(success=True) + + # DEPREACTION END + + + + + + def send_neopixel_command(self, led_pin, led_count, led_brightness, red, green, blue, address, neopixel_dirrect, + index_id, queue_id=None): + """Send neopixel command + Arguments: + led_pin {int} -- GPIO number + ledCount {int} -- number of LEDS + ledBrightness {int} -- brightness from 0 to 255 + red {int} -- red value from 0 to 255 + green {int} -- green value from 0 to 255 + blue {int} -- blue value from 0 to 255 + address {int} -- i2c address from microcontroller + """ + + try: + + for rpi_output in self.rpi_outputs: + if self.to_int(index_id) == self.to_int(rpi_output['index_id']): + rpi_output['neopixel_color'] = 'rgb({0!s},{1!s},{2!s})'.format(red, green, blue) + + if address == '': + address = 0 + + if neopixel_dirrect: + script = os.path.dirname(os.path.realpath(__file__)) + "/neopixel_direct.py " + else: + script = os.path.dirname(os.path.realpath(__file__)) + "/neopixel_indirect.py " + + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + + cmd = sudo_str + "python " + script + str(led_pin) + " " + str(led_count) + " " + str( + led_brightness) + " " + str(red) + " " + str(green) + " " + str(blue) + " " + + if neopixel_dirrect: + dma = self._settings.get(["neopixel_dma"]) or 10 + cmd = cmd + str(dma) + else: + cmd = cmd + str(address) + + if queue_id is not None: + self._logger.debug("running scheduled queue id %s", queue_id) + self._logger.debug("Sending neopixel cmd: %s", cmd) + Popen(cmd, shell=True) + if queue_id is not None: + self.stop_queue_item(queue_id) + except Exception as ex: + self.log_error(ex) + + def check_enclosure_temp(self): + try: + sensor_data = [] + for sensor in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): + temp, hum = self.get_sensor_data(sensor) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Sensor %s Temperature: %s humidity %s", sensor['label'], temp, hum) + if temp is not None and hum is not None: + sensor["temp_sensor_temp"] = temp + sensor["temp_sensor_humidity"] = hum + sensor_data.append(dict(index_id=sensor['index_id'], temperature=temp, humidity=hum)) + self.temperature_sensor_data = sensor_data + self.handle_temp_hum_control() + self.handle_temperature_events() + self.handle_pwm_linked_temperature() + self.update_ui() + except Exception as ex: + self.log_error(ex) + + def toggle_output(self, output_index, first_run=False): + for output in [item for item in self.rpi_outputs if item['index_id'] == output_index]: + gpio_pin = self.to_int(output['gpio_pin']) + index_id = self.to_int(output['index_id']) + + if output['output_type'] == 'regular': + if first_run: + current_value = False + else: + current_value = (not GPIO.input(gpio_pin)) if output['active_low'] else GPIO.input(gpio_pin) + + if current_value: + time_delay = self.to_int(output['toggle_timer_off']) + else: + time_delay = self.to_int(output['toggle_timer_on']) + + if not self.print_complete: + self.write_gpio(gpio_pin, not current_value) + thread = threading.Timer(time_delay, self.toggle_output, args=[index_id]) + thread.start() + else: + off_value = True if output['active_low'] else False + self.write_gpio(gpio_pin, off_value) + self.update_ui_outputs() + return + + if output['output_type'] == 'pwm': + for pwm in self.pwm_instances: + if gpio_pin in pwm: + if first_run: + current_pwm_value = 0 + else: + if 'duty_cycle' in pwm: + current_pwm_value = pwm['duty_cycle'] + current_pwm_value = self.to_int(current_pwm_value) + else: + current_pwm_value = 0 + + if not current_pwm_value == 0: + time_delay = self.to_int(output['toggle_timer_off']) + write_value = 0 + else: + time_delay = self.to_int(output['toggle_timer_on']) + write_value = self.to_int(output['default_duty_cycle']) + + if not self.print_complete: + self.write_pwm(gpio_pin, write_value) + thread = threading.Timer(time_delay, self.toggle_output, args=[index_id]) + thread.start() + else: + self.write_pwm(self.to_int(output['gpio_pin']), 0) + self.update_ui_outputs() + return + + def update_ui(self): + self.update_ui_outputs() + self.update_ui_current_temperature() + self.update_ui_set_temperature() + self.update_ui_inputs() + + def update_ui_current_temperature(self): + self._plugin_manager.send_plugin_message(self._identifier, dict(sensor_data=self.temperature_sensor_data)) + + def update_ui_set_temperature(self): + result = [] + for temp_crt_output in list(filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): + set_temperature = self.to_float(temp_crt_output['temp_ctr_set_value']) + result.append(dict(index_id=temp_crt_output['index_id'], set_temperature=set_temperature)) + result.append(set_temperature) + self._plugin_manager.send_plugin_message(self._identifier, dict(set_temperature=result)) + + def stop_queue_item(self, queue_id): + old_list = self.event_queue + self._logger.debug("Stopping queue id %s...", queue_id) + for task in self.event_queue: + self._logger.debug("Queue id found...") + if task['queue_id'] == queue_id: + task['thread'].cancel() + self.event_queue.remove(task) + self._logger.debug("Queue id stopped and removed from list...") + self._logger.debug("Old queue list: %s", old_list) + self._logger.debug("New queue list: %s", self.event_queue) + + def update_ui_outputs(self): + try: + regular_status = [] + pwm_status = [] + neopixel_status = [] + temp_control_status = [] + for output in self.rpi_outputs: + index = self.to_int(output['index_id']) + pin = self.to_int(output['gpio_pin']) + startup = output['auto_startup'] + shutdown = output['auto_shutdown'] + + if output['output_type'] == 'regular': + val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) + regular_status.append( + dict(index_id=index, status=val, auto_startup=startup, auto_shutdown=shutdown)) + if output['output_type'] == 'temp_hum_control': + val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) + temp_control_status.append( + dict(index_id=index, status=val, auto_startup=startup, auto_shutdown=shutdown)) + if output['output_type'] == 'neopixel_indirect' or output['output_type'] == 'neopixel_direct': + val = output['neopixel_color'] + neopixel_status.append( + dict(index_id=index, color=val, auto_startup=startup, auto_shutdown=shutdown)) + if output['output_type'] == 'pwm': + for pwm in self.pwm_instances: + if pin in pwm: + if 'duty_cycle' in pwm: + pwm_val = pwm['duty_cycle'] + val = self.to_int(pwm_val) + else: + val = 0 + pwm_status.append( + dict(index_id=index, pwm_value=val, auto_startup=startup, auto_shutdown=shutdown)) + self._plugin_manager.send_plugin_message(self._identifier, + dict(rpi_output_regular=regular_status, rpi_output_pwm=pwm_status, + rpi_output_neopixel=neopixel_status, + rpi_output_temp_hum_ctrl=temp_control_status)) + except Exception as ex: + self.log_error(ex) + + def update_ui_inputs(self): + try: + sensor_status = [] + for sensor in self.rpi_inputs: + if sensor['input_type'] == 'gpio' and sensor['action_type'] == 'printer_control' and sensor[ + 'printer_action'] == 'filament': + index = self.to_int(sensor['index_id']) + value = sensor['filament_sensor_enabled'] + sensor_status.append(dict(index_id=index, filament_sensor_enabled=value)) + self._plugin_manager.send_plugin_message(self._identifier, dict(filament_sensor_status=sensor_status)) + except Exception as ex: + self.log_error(ex) + + def get_sensor_data(self, sensor): + try: + if self.development_mode: + temp, hum = self.read_dummy_temp() + else: + if sensor['temp_sensor_type'] in ["11", "22", "2302"]: + temp, hum = self.read_dht_temp(sensor['temp_sensor_type'], sensor['gpio_pin']) + elif sensor['temp_sensor_type'] == "18b20": + temp = self.read_18b20_temp(sensor['ds18b20_serial']) + hum = 0 + elif sensor['temp_sensor_type'] == "bme280": + temp, hum = self.read_bme280_temp(sensor['temp_sensor_address']) + elif sensor['temp_sensor_type'] == "am2320": + temp, hum = self.read_am2320_temp() # sensor has fixed address + elif sensor['temp_sensor_type'] == "si7021": + temp, hum = self.read_si7021_temp(sensor['temp_sensor_address'], sensor['temp_sensor_i2cbus']) + elif sensor['temp_sensor_type'] == "tmp102": + temp = self.read_tmp102_temp(sensor['temp_sensor_address']) + hum = 0 + elif sensor['temp_sensor_type'] == "max31855": + temp = self.read_max31855_temp(sensor['temp_sensor_address']) + hum = 0 + elif sensor['temp_sensor_type'] == "mcp9808": + temp = self.read_mcp_temp(sensor['temp_sensor_address']) + hum = 0 + else: + self._logger.info("temp_sensor_type no match") + temp = None + hum = None + if temp != -1 and hum != -1: + temp = round(self.to_float(temp), 1) if not sensor['use_fahrenheit'] else round( + self.to_float(temp) * 1.8 + 32, 1) + hum = round(self.to_float(hum), 1) + return temp, hum + return None, None + except Exception as ex: + self.log_error(ex) + + def handle_temperature_events(self): + for temperature_alarm in [item for item in self.rpi_outputs if item['output_type'] == 'temperature_alarm']: + set_temperature = self.to_float(temperature_alarm['alarm_set_temp']) + if int(set_temperature) is 0: + continue + linked_data = [item for item in self.temperature_sensor_data if + item['index_id'] == temperature_alarm['linked_temp_sensor']].pop() + sensor_temperature = self.to_float(linked_data['temperature']) + if set_temperature < sensor_temperature: + for rpi_controlled_output in self.rpi_outputs: + if self.to_int(temperature_alarm['controlled_io']) == self.to_int( + rpi_controlled_output['index_id']): + val = GPIO.LOW if temperature_alarm['controlled_io_set_value'] == 'low' else GPIO.HIGH + self.write_gpio(self.to_int(rpi_controlled_output['gpio_pin']), val) + for notification in self.notifications: + if notification['temperatureAction']: + msg = ("Temperature action: enclosure temperature exceed " + temperature_alarm[ + 'alarm_set_temp']) + self.send_notification(msg) + + def read_dummy_temp(self): + current_value = self.dummy_value + if current_value > 40 or current_value < 30: + self.dummy_delta = - self.dummy_delta + + return_value = current_value + self.dummy_delta + + self.dummy_value = return_value + + return return_value, return_value + + def read_mcp_temp(self, address): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/mcp9808.py" + args = ["python", script, str(address)] + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature MCP9808 cmd: %s", " ".join(args)) + proc = Popen(args, stdout=PIPE) + stdout, _ = proc.communicate() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("MCP9808 result: %s", stdout) + return self.to_float(stdout.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 + + def read_dht_temp(self, sensor, pin): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/getDHTTemp.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python " + script + str(sensor) + " " + str(pin) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature dht cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Dht result: %s", stdout) + temp, hum = stdout.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 of the plugin.") + self.log_error(ex) + return (0, 0) + + def read_bme280_temp(self, address): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/BME280.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python " + script + str(address) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature BME280 cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("BME280 result: %s", stdout) + temp, hum = stdout.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 of the plugin.") + self.log_error(ex) + return (0, 0) + + def read_am2320_temp(self): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/AM2320.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python " + script # sensor has fixed address 0x5C + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature AM2320 cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("AM2320 result: %s", stdout) + temp, hum = stdout.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 of the plugin.") + self.log_error(ex) + return (0, 0) + + def read_si7021_temp(self, address, i2cbus): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/SI7021.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python " + script + str(address) + " " + str(i2cbus) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature SI7021 cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("SI7021 result: %s", stdout) + temp, hum = stdout.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 of the plugin.") + self.log_error(ex) + return (0, 0) + + def read_18b20_temp(self, serial_number): + os.system('modprobe w1-gpio') + os.system('modprobe w1-therm') + + lines = self.read_raw_18b20_temp(serial_number) + while lines[0].strip()[-3:] != 'YES': + time.sleep(0.2) + lines = self.read_raw_18b20_temp(serial_number) + equals_pos = lines[1].find('t=') + if equals_pos != -1: + temp_string = lines[1][equals_pos + 2:] + temp_c = float(temp_string) / 1000. + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("DS18B20 result: %s", temp_c) + return '{0:0.1f}'.format(temp_c) + return 0 + + def read_raw_18b20_temp(self, serial_number): + base_dir = '/sys/bus/w1/devices/' + device_folder = glob.glob(base_dir + str(serial_number) + '*')[0] + device_file = device_folder + '/w1_slave' + device_file_result = open(device_file, 'r') + lines = device_file_result.readlines() + device_file_result.close() + return lines + + def read_tmp102_temp(self, address): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/tmp102.py" + args = ["python", script, str(address)] + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature TMP102 cmd: %s", " ".join(args)) + proc = Popen(args, stdout=PIPE) + stdout, _ = proc.communicate() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("TMP102 result: %s", stdout) + return self.to_float(stdout.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 + + def read_max31855_temp(self, address): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/max31855.py" + args = ["python", script, str(address)] + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature MAX31855 cmd: %s", " ".join(args)) + proc = Popen(args, stdout=PIPE) + stdout, _ = proc.communicate() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("MAX31855 result: %s", stdout) + return self.to_float(stdout.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 + + def handle_pwm_linked_temperature(self): + try: + for pwm_output in list(filter(lambda item: item['output_type'] == 'pwm' and item['pwm_temperature_linked'], + self.rpi_outputs)): + gpio_pin = self.to_int(pwm_output['gpio_pin']) + if self._printer.is_printing(): + index_id = self.to_int(pwm_output['index_id']) + linked_id = self.to_int(pwm_output['linked_temp_sensor']) + linked_data = self.get_linked_temp_sensor_data(linked_id) + current_temp = self.to_float(linked_data['temperature']) + + duty_a = self.to_float(pwm_output['duty_a']) + duty_b = self.to_float(pwm_output['duty_b']) + temp_a = self.to_float(pwm_output['temperature_a']) + temp_b = self.to_float(pwm_output['temperature_b']) + + try: + calculated_duty = ((current_temp - temp_a) * (duty_b - duty_a) / (temp_b - temp_a)) + duty_a + + if current_temp < temp_a: + calculated_duty = 0 + except: + calculated_duty = 0 + + self._logger.debug("Calculated duty for PWM %s is %s", index_id, calculated_duty) + elif self.print_complete: + calculated_duty = self.to_int(pwm_output['duty_cycle']) + else: + calculated_duty = 0 + + self.write_pwm(gpio_pin, self.constrain(calculated_duty, 0, 100)) + + except Exception as ex: + self.log_error(ex) + + def get_linked_temp_sensor_data(self, linked_id): + try: + linked_data = [data for data in self.temperature_sensor_data if data['index_id'] == linked_id].pop() + return linked_data + except: + self._logger.warn("No linked temperature sensor found for %s", linked_id) + return None + + def handle_temp_hum_control(self): + try: + for temp_hum_control in list( + filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): + + set_temperature = self.to_float(temp_hum_control['temp_ctr_set_value']) + temp_deadband = self.to_float(temp_hum_control['temp_ctr_deadband']) + max_temp = self.to_float(temp_hum_control['temp_ctr_max_temp']) + + linked_id = temp_hum_control['linked_temp_sensor'] + + previous_status = list(filter(lambda item: item['index_id'] == temp_hum_control['index_id'], + self.temp_hum_control_status)).pop()['status'] + + if set_temperature == 0: + current_status = False + else: + linked_data = self.get_linked_temp_sensor_data(linked_id) + + control_type = str(temp_hum_control['temp_ctr_type']) + + if control_type == 'dehumidifier': + current_value = self.to_float(linked_data['humidity']) + temp_deadband = 0 + else: + current_value = self.to_float(linked_data['temperature']) + + if control_type == 'cooler' or control_type == 'dehumidifier': + if current_value <= set_temperature and current_value >= (set_temperature - temp_deadband): + current_status = previous_status + elif current_value < set_temperature: + current_status = False + else: + current_status = True + else: + if current_value <= set_temperature and current_value >= (set_temperature - temp_deadband): + current_status = previous_status + elif current_value > set_temperature: + current_status = False + else: + current_status = True + + if control_type == 'heater' and max_temp > 0.0 and max_temp < current_value: + self._logger.debug("Maximum temperature reached for temperature control %s", + temp_hum_control['index_id']) + temp_hum_control['temp_ctr_set_value'] = 0 + current_status = False + + if current_status != previous_status: + if current_status: + self._logger.info("Turning gpio to control temperature on.") + val = False if temp_hum_control['active_low'] else True + self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) + else: + index_id = temp_hum_control['index_id'] + if index_id in self.waiting_temperature: + self.waiting_temperature.remove(index_id) + + if not self.waiting_temperature and self._printer.is_paused(): + self._printer.resume_print() + + self._logger.info("Turning gpio to control temperature off.") + val = True if temp_hum_control['active_low'] else False + self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) + for control_status in self.temp_hum_control_status: + if control_status['index_id'] == temp_hum_control['index_id']: + control_status['status'] = current_status + except Exception as ex: + self.log_error(ex) + + def log_error(self, ex): + template = "An exception of type {0} occurred on {1}. Arguments:\n{2!r}" + message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, ex.args) + self._logger.warn(message) + + def setup_gpio(self): + try: + current_mode = GPIO.getmode() + set_mode = GPIO.BOARD if self._settings.get(["use_board_pin_number"]) else GPIO.BCM + if current_mode is None: + outputs = list(filter( + lambda item: item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ + 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct', + self.rpi_outputs)) + inputs = list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)) + gpios = outputs + inputs + if gpios: + GPIO.setmode(set_mode) + tempstr = "BOARD" if set_mode == GPIO.BOARD else "BCM" + self._logger.info("Setting GPIO mode to %s", tempstr) + elif current_mode != set_mode: + GPIO.setmode(current_mode) + tempstr = "BOARD" if current_mode == GPIO.BOARD else "BCM" + self._settings.set(["use_board_pin_number"], True if current_mode == GPIO.BOARD else False) + warn_msg = "GPIO mode was configured before, GPIO mode will be forced to use: " + tempstr + " as pin numbers. Please update GPIO accordingly!" + self._logger.info(warn_msg) + self._plugin_manager.send_plugin_message(self._identifier, + dict(is_msg=True, msg=warn_msg, msg_type="error")) + GPIO.setwarnings(False) + except Exception as ex: + self.log_error(ex) + + def clear_gpio(self): + try: + + for gpio_out in list(filter( + lambda item: item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ + 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct', + self.rpi_outputs)): + gpio_pin = self.to_int(gpio_out['gpio_pin']) + if gpio_pin not in self.rpi_outputs_not_changed: + GPIO.cleanup(gpio_pin) + + for gpio_in in list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)): + try: + GPIO.remove_event_detect(self.to_int(gpio_in['gpio_pin'])) + except: + pass + GPIO.cleanup(self.to_int(gpio_in['gpio_pin'])) + except Exception as ex: + self.log_error(ex) + + def clear_channel(self, channel): + try: + GPIO.cleanup(self.to_int(channel)) + self._logger.debug("Clearing channel %s", channel) + except Exception as ex: + self.log_error(ex) + + def generate_temp_hum_control_status(self): + status = [] + for temp_hum_control in list(filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): + status.append(dict(index_id=temp_hum_control['index_id'], status=False)) + self.temp_hum_control_status = status + + def configure_gpio(self): + try: + + for gpio_out in list( + filter(lambda item: item['output_type'] == 'regular' or item['output_type'] == 'temp_hum_control', + self.rpi_outputs)): + initial_value = GPIO.HIGH if gpio_out['active_low'] else GPIO.LOW + pin = self.to_int(gpio_out['gpio_pin']) + if pin not in self.rpi_outputs_not_changed: + self._logger.info("Setting GPIO pin %s as OUTPUT with initial value: %s", pin, initial_value) + GPIO.setup(pin, GPIO.OUT, initial=initial_value) + for gpio_out_pwm in list(filter(lambda item: item['output_type'] == 'pwm', self.rpi_outputs)): + pin = self.to_int(gpio_out_pwm['gpio_pin']) + self._logger.info("Setting GPIO pin %s as PWM", pin) + for pwm in (pwm_dict for pwm_dict in self.pwm_instances if pin in pwm_dict): + self.pwm_instances.remove(pwm) + self.clear_channel(pin) + GPIO.setup(pin, GPIO.OUT) + pwm_instance = GPIO.PWM(pin, self.to_int(gpio_out_pwm['pwm_frequency'])) + self._logger.info("starting PWM on pin %s", pin) + pwm_instance.start(0) + self.pwm_instances.append({pin: pwm_instance}) + for gpio_out_neopixel in list( + filter(lambda item: item['output_type'] == 'neopixel_direct', self.rpi_outputs)): + pin = self.to_int(gpio_out_neopixel['gpio_pin']) + self.clear_channel(pin) + + for rpi_input in list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)): + gpio_pin = self.to_int(rpi_input['gpio_pin']) + pull_resistor = GPIO.PUD_UP if rpi_input['input_pull_resistor'] == 'input_pull_up' else GPIO.PUD_DOWN + GPIO.setup(gpio_pin, GPIO.IN, pull_resistor) + edge = GPIO.RISING if rpi_input['edge'] == 'rise' else GPIO.FALLING + + inputs_same_gpio = list( + [r_inp for r_inp in self.rpi_inputs if self.to_int(r_inp['gpio_pin']) == gpio_pin]) + + if len(inputs_same_gpio) > 1: + GPIO.remove_event_detect(gpio_pin) + for other_input in inputs_same_gpio: + if other_input['edge'] is not edge: + edge = GPIO.BOTH + + if rpi_input['action_type'] == 'output_control': + self._logger.info("Adding GPIO event detect on pin %s with edge: %s", gpio_pin, edge) + GPIO.add_event_detect(gpio_pin, edge, callback=self.handle_gpio_control, bouncetime=200) + if (rpi_input['action_type'] == 'printer_control' and rpi_input['printer_action'] != 'filament'): + GPIO.add_event_detect(gpio_pin, edge, callback=self.handle_printer_action, bouncetime=200) + self._logger.info("Adding PRINTER CONTROL event detect on pin %s with edge: %s", gpio_pin, edge) + except Exception as ex: + self.log_error(ex) + + def handle_filamment_detection(self, channel): + try: + for filament_sensor in list(filter( + lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ + 'printer_action'] == 'filament' and self.to_int(item['gpio_pin']) == self.to_int(channel), + self.rpi_inputs)): + if ((filament_sensor['edge'] == 'fall') ^ (GPIO.input(self.to_int(filament_sensor['gpio_pin']))) and + filament_sensor['filament_sensor_enabled']): + last_detected_time = list(filter(lambda item: item['index_id'] == filament_sensor['index_id'], + self.last_filament_end_detected)).pop()['time'] + time_now = time.time() + time_difference = self.to_int(time_now - last_detected_time) + time_out_value = self.to_int(filament_sensor['filament_sensor_timeout']) + if time_difference > time_out_value: + self._logger.info("Detected end of filament.") + for item in self.last_filament_end_detected: + if item['index_id'] == filament_sensor['index_id']: + item['time'] = time_now + for line in self._settings.get(["filament_sensor_gcode"]).split('\n'): + if line: + self._printer.commands(line.strip()) + self._logger.info("Sending GCODE command: %s", line.strip()) + time.sleep(0.2) + for notification in self.notifications: + if notification['filamentChange']: + msg = "Filament change action caused by sensor: " + str(filament_sensor['label']) + self.send_notification(msg) + else: + self._logger.info("Prevented end of filament detection, filament sensor timeout not elapsed.") + except Exception as ex: + self.log_error(ex) + + def start_filament_detection(self): + self.stop_filament_detection() + try: + for filament_sensor in list(filter( + lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ + 'printer_action'] == 'filament', self.rpi_inputs)): + edge = GPIO.RISING if filament_sensor['edge'] == 'rise' else GPIO.FALLING + if GPIO.input(self.to_int(filament_sensor['gpio_pin'])) == (edge == GPIO.RISING): + self._printer.pause_print() + self._logger.info("Started printing with no filament.") + else: + self.last_filament_end_detected.append(dict(index_id=filament_sensor['index_id'], time=0)) + self._logger.info("Adding GPIO event detect on pin %s with edge: %s", filament_sensor['gpio_pin'], + edge) + GPIO.add_event_detect(self.to_int(filament_sensor['gpio_pin']), edge, + callback=self.handle_filamment_detection, bouncetime=200) + except Exception as ex: + self.log_error(ex) + + def stop_filament_detection(self): + try: + self.last_filament_end_detected = [] + for filament_sensor in list(filter( + lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ + 'printer_action'] == 'filament', self.rpi_inputs)): + GPIO.remove_event_detect(self.to_int(filament_sensor['gpio_pin'])) + except Exception as ex: + self.log_error(ex) + + def cancel_all_events_on_queue(self): + for task in self.event_queue: + try: + task['thread'].cancel() + except: + self._logger.warn("Failed to stop task %s.", task) + pass + + def handle_initial_gpio_control(self): + try: + for rpi_input in list( + filter(lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'output_control', + self.rpi_inputs)): + gpio_pin = self.to_int(rpi_input['gpio_pin']) + controlled_io = self.to_int(rpi_input['controlled_io']) + if (rpi_input['edge'] == 'fall') ^ GPIO.input(gpio_pin): + rpi_output = [r_out for r_out in self.rpi_outputs if + self.to_int(r_out['index_id']) == controlled_io].pop() + if rpi_output['output_type'] == 'regular': + val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + except Exception as ex: + self.log_error(ex) + pass + + def shell_command(self, command): + try: + stdout = (Popen(command, shell=True, stdout=PIPE).stdout).read() + self._plugin_manager.send_plugin_message(self._identifier, + dict(is_msg=True, msg=stdout, msg_type="success")) + except Exception as ex: + self.log_error(ex) + self._plugin_manager.send_plugin_message(self._identifier, + dict(is_msg=True, msg="Could not execute shell script", msg_type="error")) + + def handle_gpio_control(self, channel): + + try: + self._logger.debug("GPIO event triggered on channel %s", channel) + for rpi_input in list( + filter(lambda item: self.to_int(item['gpio_pin']) == self.to_int(channel), self.rpi_inputs)): + gpio_pin = self.to_int(rpi_input['gpio_pin']) + controlled_io = self.to_int(rpi_input['controlled_io']) + if (rpi_input['edge'] == 'fall') ^ GPIO.input(gpio_pin): + rpi_output = [r_out for r_out in self.rpi_outputs if + self.to_int(r_out['index_id']) == controlled_io].pop() + if rpi_output['output_type'] == 'regular': + if rpi_input['controlled_io_set_value'] == 'toggle': + val = GPIO.LOW if GPIO.input( + self.to_int(rpi_output['gpio_pin'])) == GPIO.HIGH else GPIO.HIGH + else: + val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + for notification in self.notifications: + if notification['gpioAction']: + msg = "GPIO control action caused by input " + str( + rpi_input['label']) + ". Setting GPIO" + str( + rpi_input['controlled_io']) + " to: " + str(rpi_input['controlled_io_set_value']) + self.send_notification(msg) + if rpi_output['output_type'] == 'gcode_output': + self.send_gcode_command(rpi_output['gcode']) + for notification in self.notifications: + if notification['gpioAction']: + msg = "GPIO control action caused by input " + str( + rpi_input['label']) + ". Sending GCODE command" + self.send_notification(msg) + if rpi_output['output_type'] == 'shell_output': + command = rpi_output['shell_script'] + self.shell_command(command) + except Exception as ex: + self.log_error(ex) + pass + + def send_gcode_command(self, command): + for line in command.split('\n'): + if line: + self._printer.commands(line.strip()) + self._logger.info("Sending GCODE command: %s", line.strip()) + time.sleep(0.2) + + def handle_printer_action(self, channel): + try: + for rpi_input in self.rpi_inputs: + if (channel == self.to_int(rpi_input['gpio_pin']) and rpi_input[ + 'action_type'] == 'printer_control' and ( + (rpi_input['edge'] == 'fall') ^ GPIO.input(self.to_int(rpi_input['gpio_pin'])))): + if rpi_input['printer_action'] == 'resume': + self._logger.info("Printer action resume.") + self._printer.resume_print() + elif rpi_input['printer_action'] == 'pause': + self._logger.info("Printer action pause.") + self._printer.pause_print() + elif rpi_input['printer_action'] == 'cancel': + self._logger.info("Printer action cancel.") + self._printer.cancel_print() + elif rpi_input['printer_action'] == 'toggle': + self._logger.info("Printer action toggle.") + if self._printer.is_operational(): + self._printer.toggle_pause_print() + else: + self._printer.connect() + elif rpi_input['printer_action'] == 'start': + self._logger.info("Printer action start.") + self._printer.start_print() + elif rpi_input['printer_action'] == 'toggle_job': + self._logger.info("Printer action toggle_job.") + if self._printer.is_operational(): + if self._printer.is_printing(): + self._printer.cancel_print() + elif self._printer.is_ready(): + self._printer.start_print() + else: + self._printer.connect() + elif rpi_input['printer_action'] == 'stop_temp_hum_control': + self._logger.info("Printer action stopping temperature control.") + for rpi_output in self.rpi_outputs: + if rpi_output['auto_shutdown'] and rpi_output['output_type'] == 'temp_hum_control': + rpi_output['temp_ctr_set_value'] = 0 + self.handle_temp_hum_control() + for notification in self.notifications: + if notification['printer_action']: + msg = "Printer action: " + rpi_input['printer_action'] + " caused by input: " + str( + rpi_input['label']) + self.send_notification(msg) + except Exception as ex: + self.log_error(ex) + pass + + def write_gpio(self, gpio, value, queue_id=None): + try: + GPIO.output(gpio, value) + if queue_id is not None: + self._logger.debug("Running scheduled queue id %s", queue_id) + self._logger.debug("Writing on GPIO: %s value %s", gpio, value) + self.update_ui() + if queue_id is not None: + self.stop_queue_item(queue_id) + except Exception as ex: + template = "An exception of type {0} occurred on {1} when writing on pin {2}. Arguments:\n{3!r}" + message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, gpio, ex.args) + self._logger.warn(message) + pass + + def write_pwm(self, gpio, pwm_value, queue_id=None): + try: + if queue_id is not None: + self._logger.debug("running scheduled queue id %s", queue_id) + for pwm in self.pwm_instances: + if gpio in pwm: + pwm_object = pwm[gpio] + old_pwm_value = pwm['duty_cycle'] if 'duty_cycle' in pwm else -1 + if not self.to_int(old_pwm_value) == self.to_int(pwm_value): + pwm['duty_cycle'] = pwm_value + pwm_object.start(pwm_value) #should be changed back to pwm_object.ChangeDutyCycle() but this + # was causing errors. + self._logger.debug("Writing PWM on gpio: %s value %s", gpio, pwm_value) + self.update_ui() + if queue_id is not None: + self.stop_queue_item(queue_id) + break + except Exception as ex: + self.log_error(ex) + pass + + def get_output_list(self): + result = [] + for rpi_output in self.rpi_outputs: + if rpi_output['output_type'] == 'regular': + result.append(self.to_int(rpi_output['gpio_pin'])) + return result + + def send_notification(self, message): + try: + provider = self._settings.get(["notification_provider"]) + if provider == 'ifttt': + event = self._settings.get(["notification_event_name"]) + api_key = self._settings.get(["notification_api_key"]) + self._logger.debug("Sending notification to: %s with msg: %s with key: %s", provider, message, + api_key) + try: + res = self.ifttt_notification(message, event, api_key) + except requests.exceptions.ConnectionError: + self._logger.info("Error: Could not connect to IFTTT") + except requests.exceptions.HTTPError: + self._logger.info("Error: Received invalid response") + except requests.exceptions.Timeout: + self._logger.info("Error: Request timed out") + except requests.exceptions.TooManyRedirects: + self._logger.info("Error: Too many redirects") + except requests.exceptions.RequestException as reqe: + self._logger.info("Error: {e}".format(e=reqe)) + if res.status_code != requests.codes['ok']: + try: + j = res.json() + except ValueError: + self._logger.info('Error: Could not parse server response. Event not sent') + for err in j['errors']: + self._logger.info('Error: {}'.format(err['message'])) + except Exception as ex: + self.log_error(ex) + pass + + def ifttt_notification(self, message, event, api_key): + url = "https://maker.ifttt.com/trigger/{e}/with/key/{k}/".format(e=event, k=api_key) + payload = {'value1': message} + return requests.post(url, data=payload) + + # ~~ EventPlugin mixin + def on_event(self, event, payload): + if event == Events.CONNECTED: + self.update_ui() + + if event == Events.CLIENT_OPENED: + self.update_ui() + + if event == Events.PRINT_RESUMED: + self.start_filament_detection() + + if event == Events.PRINT_STARTED: + self.print_complete = False + self.cancel_all_events_on_queue() + self.event_queue = [] + self.start_filament_detection() + for rpi_output in self.rpi_outputs: + if rpi_output['auto_startup']: + delay_seconds = self.get_startup_delay_from_output(rpi_output) + self.schedule_auto_startup_outputs(rpi_output, delay_seconds) + if rpi_output['toggle_timer']: + if rpi_output['output_type'] == 'regular' or rpi_output['output_type'] == 'pwm': + self.toggle_output(rpi_output['index_id'], True) + if self.is_hour(rpi_output['shutdown_time']): + shutdown_delay_seconds = self.get_shutdown_delay_from_output(rpi_output) + self.schedule_auto_shutdown_outputs(rpi_output, shutdown_delay_seconds) + self.run_tasks() + self.update_ui() + + elif event == Events.PRINT_DONE: + self.stop_filament_detection() + self.print_complete = True + for rpi_output in self.rpi_outputs: + shutdown_time = rpi_output['shutdown_time'] + if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: + rpi_output['duty_cycle'] = rpi_output['default_duty_cycle'] + if rpi_output['auto_shutdown'] and not self.is_hour(shutdown_time): + delay_seconds = self.to_float(shutdown_time) + self.schedule_auto_shutdown_outputs(rpi_output, delay_seconds) + self.run_tasks() + self.update_ui() + + elif event in (Events.PRINT_CANCELLED, Events.PRINT_FAILED): + self.stop_filament_detection() + self.cancel_all_events_on_queue() + self.event_queue = [] + for rpi_output in self.rpi_outputs: + if rpi_output['shutdown_on_failed']: + shutdown_time = rpi_output['shutdown_time'] + if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: + rpi_output['duty_cycle'] = rpi_output['default_duty_cycle'] + if rpi_output['auto_shutdown'] and not self.is_hour(shutdown_time): + delay_seconds = self.to_float(shutdown_time) + self.schedule_auto_shutdown_outputs(rpi_output, delay_seconds) + if rpi_output['output_type'] == 'temp_hum_control': + rpi_output['temp_ctr_set_value'] = 0 + self.run_tasks() + + if event == Events.PRINT_DONE: + for notification in self.notifications: + if notification['printFinish']: + file_name = os.path.basename(payload["file"]) + elapsed_time_in_seconds = payload["time"] + elapsed_time = octoprint.util.get_formatted_timedelta(timedelta(seconds=elapsed_time_in_seconds)) + msg = "Print job finished: " + file_name + "finished printing in " + file_name, elapsed_time + self.send_notification(msg) + + def run_tasks(self): + for task in self.event_queue: + if not task['thread'].is_alive(): + task['thread'].start() + + def schedule_auto_shutdown_outputs(self, rpi_output, shutdown_delay_seconds): + sufix = 'auto_shutdown' + if rpi_output['output_type'] == 'regular': + value = True if rpi_output['active_low'] else False + self.add_regular_output_to_queue(shutdown_delay_seconds, rpi_output, value, sufix) + if rpi_output['output_type'] == 'ledstrip': + self.ledstrip_set_rgb(rpi_output) + if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: + value = 0 + self.add_pwm_output_to_queue(shutdown_delay_seconds, rpi_output, value, sufix) + if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: + self.schedule_pwm_duty_on_queue(shutdown_delay_seconds, rpi_output, 0, sufix) + if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): + self.add_neopixel_output_to_queue(rpi_output, shutdown_delay_seconds, 0, 0, 0, sufix) + if rpi_output['output_type'] == 'temp_hum_control': + value = 0 + self.add_temperature_output_temperature_queue(shutdown_delay_seconds, rpi_output, value, sufix) + self._logger.debug("Events scheduled to run %s", self.event_queue) + + def ledstrip_set_rgb(self, rpi_output, rgb=None): + clk = rpi_output["ledstrip_gpio_clk"] + data = rpi_output["ledstrip_gpio_dat"] + if clk is not None and data is not None: + ledstrip = LEDStrip(self.to_int(clk), self.to_int(data)) + if rgb is None: + red, green, blue = self.get_color_from_rgb(rpi_output['default_ledstrip_color']) + else: + red, green, blue = self.get_color_from_rgb(rgb) + + self._logger.info("LEDSTRIP set rgb color: %s, %s, %s", red, green, blue) + ledstrip.setcolourrgb(self.to_int(red), self.to_int(green), self.to_int(blue)) + + def start_outpus_with_server(self): + for rpi_output in self.rpi_outputs: + if rpi_output['startup_with_server']: + gpio = self.to_int(rpi_output['gpio_pin']) + if rpi_output['output_type'] == 'regular': + value = False if rpi_output['active_low'] else True + self.write_gpio(gpio, value) + if rpi_output['output_type'] == 'ledstrip': + self.ledstrip_set_rgb(rpi_output) + if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: + value = self.to_int(rpi_output['default_duty_cycle']) + self.write_pwm(gpio, value) + if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): + red, green, blue = self.get_color_from_rgb(rpi_output['default_neopixel_color']) + led_count = rpi_output['neopixel_count'] + led_brightness = rpi_output['neopixel_brightness'] + address = rpi_output['microcontroller_address'] + index_id = self.to_int(rpi_output['index_id']) + neopixel_direct = rpi_output['output_type'] == 'neopixel_direct' + self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, + green, blue, address, neopixel_direct, index_id) + if rpi_output['output_type'] == 'temp_hum_control': + rpi_output['temp_ctr_set_value'] = rpi_output['temp_ctr_default_value'] + + def schedule_auto_startup_outputs(self, rpi_output, delay_seconds): + sufix = 'auto_startup' + if rpi_output['output_type'] == 'regular': + value = False if rpi_output['active_low'] else True + self.add_regular_output_to_queue(delay_seconds, rpi_output, value, sufix) + if rpi_output['output_type'] == 'ledstrip': + self.ledstrip_set_rgb(rpi_output) + if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: + value = self.to_int(rpi_output['default_duty_cycle']) + self.add_pwm_output_to_queue(delay_seconds, rpi_output, value, sufix) + if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): + red, green, blue = self.get_color_from_rgb(rpi_output['default_neopixel_color']) + self.add_neopixel_output_to_queue(rpi_output, delay_seconds, red, green, blue, sufix) + if rpi_output['output_type'] == 'temp_hum_control': + value = rpi_output['temp_ctr_default_value'] + self.add_temperature_output_temperature_queue(delay_seconds, rpi_output, value, sufix) + self._logger.debug("Events scheduled to run %s", self.event_queue) + + def get_color_from_rgb(self, stringColor): + stringColor = stringColor.replace('rgb(', '') + red = stringColor[:stringColor.index(',')] + stringColor = stringColor[stringColor.index(',') + 1:] + green = stringColor[:stringColor.index(',')] + stringColor = stringColor[stringColor.index(',') + 1:] + blue = stringColor[:stringColor.index(')')] + return red, green, blue + + def get_shutdown_delay_from_output(self, rpi_output): + shutdown_time = rpi_output['shutdown_time'] + + shut_down_date_time = self.create_date(shutdown_time) + + if shut_down_date_time < datetime.now(): + shut_down_date_time = shut_down_date_time + timedelta(days=1) + + delay_seconds = (shut_down_date_time - datetime.now()).total_seconds() + + return delay_seconds + + def add_neopixel_output_to_queue(self, rpi_output, delay_seconds, red, green, blue, sufix): + gpio_pin = rpi_output['gpio_pin'] + ledCount = rpi_output['neopixel_count'] + ledBrightness = rpi_output['neopixel_brightness'] + address = rpi_output['microcontroller_address'] + neopixel_direct = rpi_output['output_type'] == 'neopixel_direct' + index_id = self.to_int(rpi_output['index_id']) + + queue_id = '{0!s}_{1!s}'.format(index_id, sufix) + + self._logger.debug("Scheduling neopixel output id %s for on %s delay_seconds", queue_id, delay_seconds) + + thread = threading.Timer(delay_seconds, self.send_neopixel_command, + args=[gpio_pin, ledCount, ledBrightness, red, green, blue, address, neopixel_direct, + index_id, queue_id]) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def add_pwm_output_to_queue(self, delay_seconds, rpi_output, value, sufix): + queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) + + self._logger.debug("Scheduling pwm output id %s for on %s delay_seconds", queue_id, delay_seconds) + + thread = threading.Timer(delay_seconds, self.write_pwm, + args=[self.to_int(rpi_output['gpio_pin']), value, queue_id]) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def schedule_pwm_duty_on_queue(self, delay_seconds, rpi_output, value, sufix): + queue_id = '{0!s}_{1!s}_{2!s}'.format(rpi_output['index_id'], "pwm_linked_temp", sufix) + thread = threading.Timer(delay_seconds, self.set_pwm_duty_cycle, args=[rpi_output, value, queue_id]) + + self._logger.debug("Scheduling pwm linked temp output id %s on %s delay_seconds", queue_id, delay_seconds) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def set_pwm_duty_cycle(self, rpi_output, value, queue_id): + rpi_output['duty_cycle'] = value + if queue_id is not None: + self.stop_queue_item(queue_id) + + def add_regular_output_to_queue(self, delay_seconds, rpi_output, value, sufix): + queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) + + self._logger.debug("Scheduling regular output id %s on %s delay_seconds", queue_id, delay_seconds) + + thread = threading.Timer(delay_seconds, self.write_gpio, + args=[self.to_int(rpi_output['gpio_pin']), value, queue_id]) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def add_temperature_output_temperature_queue(self, delay_seconds, rpi_output, value, sufix): + queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) + self._logger.debug("Scheduling temperature control id %s on %s delay_seconds", queue_id, delay_seconds) + + thread = threading.Timer(delay_seconds, self.write_temperature_to_output, + args=[self.to_int(rpi_output['index_id']), value, queue_id]) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def write_temperature_to_output(self, rpi_output_index, value, queue_id=None): + try: + rpi_output = [r_out for r_out in self.rpi_outputs if + self.to_int(r_out['index_id']) == rpi_output_index].pop() + + if rpi_output['output_type'] == 'temp_hum_control': + rpi_output['temp_ctr_set_value'] = value + + if queue_id is not None: + self._logger.debug("running scheduled queue id %s", queue_id) + self._logger.debug("Setting temperature to output index: %s value %s", rpi_output['index_id'], value) + + self.update_ui() + if queue_id is not None: + self.stop_queue_item(queue_id) + + except Exception as ex: + template = "An exception of type {0} occurred on {1}. Arguments:\n{3!r}" + message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, ex.args) + self._logger.warn(message) + pass + + def get_startup_delay_from_output(self, rpi_output): + start_up_time = rpi_output['startup_time'] + if self.is_hour(start_up_time): + start_up_date_time = self.create_date(start_up_time) + if start_up_date_time < datetime.now(): + delay_seconds = 0.0 + else: + delay_seconds = (start_up_date_time - datetime.now()).total_seconds() + else: + delay_seconds = self.to_float(rpi_output['startup_time']) + return delay_seconds + + # ~~ SettingsPlugin mixin + def on_settings_save(self, data): + outputsBeforeSave = self.get_output_list() + octoprint.plugin.SettingsPlugin.on_settings_save(self, data) + self.rpi_outputs = self._settings.get(["rpi_outputs"]) + self.rpi_inputs = self._settings.get(["rpi_inputs"]) + self.notifications = self._settings.get(["notifications"]) + outputsAfterSave = self.get_output_list() + + commonPins = list(set(outputsBeforeSave) & set(outputsAfterSave)) + + for pin in (pin for pin in outputsBeforeSave if pin not in commonPins): + self.clear_channel(pin) + + self.rpi_outputs_not_changed = commonPins + self.clear_gpio() + + self._logger.debug("rpi_outputs: %s", self.rpi_outputs) + self._logger.debug("rpi_inputs: %s", self.rpi_inputs) + self.setup_gpio() + self.configure_gpio() + self.generate_temp_hum_control_status() + + def get_settings_defaults(self): + return dict(rpi_outputs=[], rpi_inputs=[], + filament_sensor_gcode="G91 ;Set Relative Mode \n" + "G1 E-5.000000 F500 ;Retract 5mm\n" + "G1 Z15 F300 ;move Z up 15mm\n" + "G90 ;Set Absolute Mode\n " + "G1 X20 Y20 F9000 ;Move to hold position\n" + "G91 ;Set Relative Mode\n" + "G1 E-40 F500 ;Retract 40mm\n" + "M0 ;Idle Hold\n" + "G90 ;Set Absolute Mode\n" + "G1 F5000 ;Set speed limits\n" + "G28 X0 Y0 ;Home X Y\n" + "M82 ;Set extruder to Absolute Mode\n" + "G92 E0 ;Set Extruder to 0", + use_sudo=True, neopixel_dma=10, debug=False, gcode_control=False, debug_temperature_log=False, + use_board_pin_number=False, notification_provider="disabled", notification_api_key="", + notification_event_name="printer_event", notifications=[{ + 'printFinish' : True, + 'filamentChange' : True, + 'printer_action' : True, + 'temperatureAction': True, 'gpioAction': True + }]) + + # ~~ TemplatePlugin + def get_template_configs(self): + return [dict(type="settings", custom_bindings=True), dict(type="tab", custom_bindings=True), + dict(type="navbar", custom_bindings=True, suffix="_1", classes=["dropdown"]), + dict(type="navbar", custom_bindings=True, template="enclosure_navbar_input.jinja2", suffix="_2", + classes=["dropdown"])] + + # ~~ AssetPlugin mixin + def get_assets(self): + return dict(js=["js/enclosure.js", "js/bootstrap-colorpicker.min.js"], + css=["css/bootstrap-colorpicker.css", "css/enclosure.css"]) + + # ~~ Softwareupdate hook + def get_update_information(self): + return dict(enclosure=dict(displayName="Enclosure Plugin", displayVersion=self._plugin_version, + # version check: github repository + type="github_release", user="vitormhenrique", repo="OctoPrint-Enclosure", current=self._plugin_version, + # update method: pip + pip="https://github.com/vitormhenrique/OctoPrint-Enclosure/archive/{target_version}.zip")) + + def hook_gcode_queuing(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs): + if self._settings.get(["gcode_control"]) is False: + return + + if cmd.strip().startswith("ENC"): + self._logger.debug("Gcode queuing: %s", cmd) + index_id = self.to_int(self.get_gcode_value(cmd, 'O')) + for output in [item for item in self.rpi_outputs if item['index_id'] == index_id]: + if output['output_type'] == 'regular': + set_value = self.to_int(self.get_gcode_value(cmd, 'S')) + set_value = self.constrain(set_value, 0, 1) + value = True if set_value == 1 else False + value = (not value) if output['active_low'] else value + self.write_gpio(self.to_int(output['gpio_pin']), value) + comm_instance._log("Setting REGULAR output %s to value %s" % (index_id, value)) + return + if output['output_type'] == 'pwm': + set_value = self.to_int(self.get_gcode_value(cmd, 'S')) + set_value = self.constrain(set_value, 0, 100) + output['duty_cycle'] = set_value + self.write_pwm(self.to_int(output['gpio_pin']), set_value) + comm_instance._log("Setting PWM output %s to value %s" % (index_id, set_value)) + return + if output['output_type'] == 'neopixel_indirect' or output['output_type'] == 'neopixel_direct': + red = self.get_gcode_value(cmd, 'R') + green = self.get_gcode_value(cmd, 'G') + blue = self.get_gcode_value(cmd, 'B') + + led_count = output['neopixel_count'] + led_brightness = output['neopixel_brightness'] + address = output['microcontroller_address'] + + index_id = self.to_int(output['index_id']) + + neopixel_direct = output['output_type'] == 'neopixel_direct' + + self.send_neopixel_command(self.to_int(output['gpio_pin']), led_count, led_brightness, red, green, + blue, address, neopixel_direct, index_id) + comm_instance._log( + "Setting NEOPIXEL output %s to red: %s green: %s blue: %s" % (index_id, red, green, blue)) + return + if output['output_type'] == 'temp_hum_control': + set_value = self.to_float(self.get_gcode_value(cmd, 'S')) + should_wait = self.to_int(self.get_gcode_value(cmd, 'W')) + if should_wait == 1 and self._printer.is_printing(): + self._printer.pause_print() + self.waiting_temperature.append(index_id) + output['temp_ctr_set_value'] = set_value + self.update_ui_set_temperature() + self.handle_temp_hum_control() + comm_instance._log("Setting TEMP/HUM control output %s to value %s" % (index_id, set_value)) + return __plugin_name__ = "Enclosure Plugin" @@ -1950,11 +1950,11 @@ __plugin_pythoncompat__ = ">=2.7,<4" def __plugin_load__(): - global __plugin_implementation__ - __plugin_implementation__ = EnclosurePlugin() + global __plugin_implementation__ + __plugin_implementation__ = EnclosurePlugin() - global __plugin_hooks__ - __plugin_hooks__ = { - "octoprint.comm.protocol.gcode.queuing" : __plugin_implementation__.hook_gcode_queuing, - "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information - } + global __plugin_hooks__ + __plugin_hooks__ = { + "octoprint.comm.protocol.gcode.queuing" : __plugin_implementation__.hook_gcode_queuing, + "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information + } -- 2.39.5 From 5b5ec7fc7541b9ef70e643be2d66889aa358d071 Mon Sep 17 00:00:00 2001 From: ShagoY Date: Wed, 18 Nov 2020 17:45:34 +0100 Subject: [PATCH 041/104] Update __init__.py --- octoprint_enclosure/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index d1681a8..36858b2 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -904,7 +904,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() if self._settings.get(["debug_temperature_log"]) is True: self._logger.debug("BME280 result: %s", stdout) - temp, hum = stdout.split("|") + temp, hum = stdout.split(b"|") return (self.to_float(temp.strip()), self.to_float(hum.strip())) except Exception as ex: self._logger.info( -- 2.39.5 From 64eecefe76b68e44a8bfc1fba195abf93062c8be Mon Sep 17 00:00:00 2001 From: Roberto Bonacina Date: Tue, 1 Dec 2020 21:24:08 +0100 Subject: [PATCH 042/104] Fix some typos in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5e75eba..e7e67fd 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Check pictures on thingiverse: http://www.thingiverse.com/thing:2245493 Install the plugin using the Plugin Manager bundled with OctoPrint, you can search for the Enclosure plugin or just use the url: https://github.com/vitormhenrique/OctoPrint-Enclosure/archive/master.zip. -To control the encosure temperature or get temperature trigged events, you need to install and configure a temperature sensor. This plugin can support DHT11, DHT22, AM2302, DS18B20, SI7021, BME280 and TMP102 temperature sensors. +To control the enclosure temperature or get temperature triggered events, you need to install and configure a temperature sensor. This plugin can support DHT11, DHT22, AM2302, DS18B20, SI7021, BME280 and TMP102 temperature sensors. * For the DHT11, DHT22 and AM2302 follow this steps: @@ -170,7 +170,7 @@ Outputs can be set to the following types: * Regular GPIO * PWM GPIO -* Neopixel Control via Microcontroler +* Neopixel Control via Microcontroller * Neopixel Control directly from raspberry pi * Temperature and Humidity Control * Temperature Alarm -- 2.39.5 From d7416837f610edddb830824add2bc6ef8fcf6c03 Mon Sep 17 00:00:00 2001 From: coliss86 Date: Wed, 23 Dec 2020 15:12:10 +0100 Subject: [PATCH 043/104] Add markdown formatting --- README.md | 108 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index e7e67fd..2601b8d 100644 --- a/README.md +++ b/README.md @@ -28,13 +28,21 @@ Here is a list of possibilities: Check pictures on thingiverse: http://www.thingiverse.com/thing:2245493 -**Software** +## Software + +### Installation Install the plugin using the Plugin Manager bundled with OctoPrint, you can search for the Enclosure plugin or just use the url: https://github.com/vitormhenrique/OctoPrint-Enclosure/archive/master.zip. +## Hardware + +This plugin support many hardware temperature sensors and led controller. Here are detailled instructions on how to setup them + +### Temperature sensor + To control the enclosure temperature or get temperature triggered events, you need to install and configure a temperature sensor. This plugin can support DHT11, DHT22, AM2302, DS18B20, SI7021, BME280 and TMP102 temperature sensors. -* For the DHT11, DHT22 and AM2302 follow this steps: +#### DHT11, DHT22 and AM2302 sensors Wire the sensor following the wiring diagram on the pictures on thingiverse, you can use any GPIO pin. @@ -42,72 +50,89 @@ For DHT11 and DHT22 sensors, don't forget to connect a 4.7K - 10K resistor from You need to install Adafruit library to use the temperature sensor on raspberry pi. -Open raspberry pi terminal and type: +Open a raspberry pi terminal and type: -
cd ~
+```
+cd ~
 git clone https://github.com/adafruit/Adafruit_Python_DHT.git
 cd Adafruit_Python_DHT
 sudo apt-get update
 sudo apt-get install build-essential python-dev python-openssl
-sudo python setup.py install
+sudo python setup.py install +``` Note: All libraries need to be installed on raspberry pi system python not octoprint virtual environment. You can test the library by using: -
cd examples
+```
+cd examples
 sudo ./AdafruitDHT.py 2302 4
- +``` Note that the first argument is the temperature sensor (11, 22, or 2302), and the second argument is the GPIO that the sensor was connected. -* For the DS18B20 sensor: +#### DS18B20 sensor Follow the wiring diagram on the pictures on thingiverse. The DS18B20 uses "1-wire" communication protocol, you need to use 4.7K to 10K resistor from the data pin to VCC, DS18B20 only works on GPIO pin number 4 by default. You also need to add OneWire support for your raspberry pi. -Start by adding the following line to /boot/config.txt +Start by adding the following line to `/boot/config.txt` -
dtoverlay=w1-gpio
+``` +dtoverlay=w1-gpio +``` After rebooting, you can check if the OneWire device was found properly with -
dmesg | grep w1-gpio
+``` +dmesg | grep w1-gpio +``` + You should see something like -
[    3.030368] w1-gpio onewire@0: gpio pin 4, external pullup pin -1, parasitic power 0
+``` +[ 3.030368] w1-gpio onewire@0: gpio pin 4, external pullup pin -1, parasitic power 0 +``` -You should be able to test your sensor by rebooting your system with sudo reboot. When the Pi is back up and you're logged in again, type the commands you see below into a terminal window. When you are in the 'devices' directory, the directory starting '28-' may have a different name, so cd to the name of whatever directory is there. +You should be able to test your sensor by rebooting your system with `sudo reboot`. When the Pi is back up and you're logged in again, type the commands you see below into a terminal window. When you are in the 'devices' directory, the directory starting '28-' may have a different name, so `cd` to the name of whatever directory is there. -
sudo modprobe w1-gpio
+```
+sudo modprobe w1-gpio
 sudo modprobe w1-therm
 cd /sys/bus/w1/devices
 ls
 cd 28-xxxx (change this to match what serial number pops up)
-cat w1_slave
+cat w1_slave +``` -The response will either have YES or NO at the end of the first line. If it is yes, then the temperature will be at the end of the second line, in 1/000 degrees C. +The response will either have `YES` or `NO` at the end of the first line. If it is `YES`, then the temperature will be at the end of the second line, in 1/000 degrees C. -Copy the serial number, you will need to configure the plugin. Note that for the serial number includes the 28-, for example 28-0000069834ff. +Copy the serial number, you will need to configure the plugin. Note that for the serial number includes the `28-`, for example `28-0000069834ff`. -* For the SI7021, BME280, TMP102 and MCP9808 sensors +#### SI7021, BME280, TMP102 and MCP9808 sensors Enable I2C on your raspberry pi, depending on raspi-config version, step by step can be different: -
Run sudo raspi-config
-Use the down arrow to select 9 Advanced Options
-Arrow down to A7 I2C
-Select yes when it asks you to enable I2C
-Also select yes when it asks about automatically loading the kernel module
-Use the right arrow to select the button
-Select yes when it asks to reboot
-
+``` +sudo raspi-config +``` +* Use the down arrow to select 9 Advanced Options +* Arrow down to A7 I2C +* Select yes when it asks you to enable I2C +* Also select yes when it asks about automatically loading the kernel module +* Use the right arrow to select the button +* Select yes when it asks to reboot Install some packages (on raspberry pi system python not octoprint virtual environment): -
sudo apt-get install i2c-tools python-pip python-smbus
+``` +sudo apt-get install i2c-tools python-pip python-smbus +``` Find the address of the sensor: -
i2cdetect -y 1
+``` +i2cdetect -y 1 +``` -* For Neopixel +### For Neopixel If your setup does not have pip install pip: `sudo apt-get install python-pip` @@ -125,22 +150,21 @@ Also backlist the audio kernel: `sudo nano /etc/modprobe.d/snd-blacklist.conf` -add the `blacklist snd_bcm2835` to the end of the file: - +add the `blacklist snd_bcm2835` to the end of the file. * GPIO This release uses RPi.GPIO to control IO of raspberry pi, it should install and work automatically. If it doesn't please update your octoprint with the latest release of octopi. -**Hardware** +### External hardware You can use relays / mosfets to control you lights, heater, lockers etc... If you want to control mains voltage I recommend using PowerSwitch Tail II. -* Relay +#### Relay The relays module that I used couple [SainSmart 2-Channel Relay Module](https://www.amazon.com/gp/product/B0057OC6D8?ie=UTF8&tag=3dpstuff-20&camp=1789&linkCode=xm2&creativeASIN=B0057OC6D8). Those relays are active low, that means that they will turn on when you put LOW on the output of your pin. In order to not fry your Raspberry Pi pay attention on your wiring connection: remove the jumper link and connect 3.3v to VCC, 5V to JD-VCC and Ground to GND. -* Heater +#### Heater For heating my enclosure I got a $15 lasko inside my enclosure. I opened it and added a relay to the mains wire. If you’re uncomfortable soldering or dealing with high voltage, please check out the [PowerSwitch Tail II](http://www.powerswitchtail.com/) . The PowerSwitch Tail II is fully enclosed, making it a lot safer. @@ -148,11 +172,11 @@ For heating my enclosure I got a $15 lasko inside my enclosure. I opened it and **CAUTION 2: THIS HEATER IS NOT INTENDED TO FUNCTION THIS WAY AND IT MIGHT BE A FIRE HAZARD. DO IT AT YOUR OWN RISK** -* Cooler +#### Cooler You can get a [USB Mini Desktop Fan](https://www.amazon.com/gp/product/B00WM7TRTY?ie=UTF8&tag=3dpstuff-20&camp=1789&linkCode=xm2&creativeASIN=B00WM7TRTY) and control it over a relay. -* Filament sensor +#### Filament sensor You have the ability to add a filament sensor to the enclosure, it will automatically pause the print and run a gcode command to change the filament if you run out of filament, I can be any type of filament sensor, the sensor should connect to ground if is set as an "active low" when the filament run out or 3.3v if the sensor is set as "active high" when detected the end of filament, it does not matter if it is normally open or closed, that will only interfere on your wiring. I'm using the following sensor: @@ -196,7 +220,7 @@ After selecting GPIO for the input type, and selecting output control on the act Selecting print control on the action type will trigger printer actions when the configured GPIO receives a signal. The actions can be Resume and Pause a print job or Change Filament. You can use the "change filament" action and set up the input GPIO according to your filament sensor, for example, if your filament sensor connects to ground when detects the end of the filament, you should choose PULL UP resistors and detect the event on the falling edge. You can also add mechanical buttons to pause, resume and change filaments near your printer for convenience. -**Advanced Area** +## Advanced Area If you want to enable notifications check the following [issue](https://github.com/vitormhenrique/OctoPrint-Enclosure/issues/36) @@ -204,16 +228,16 @@ You can control outputs using a simple [API](https://github.com/vitormhenrique/O Or use [g-code](https://github.com/vitormhenrique/OctoPrint-Enclosure/wiki/G-CODE-Control) commands -**Tab Order** +### Tab Order I often use more this plugin than the time-lapse tab, so having the plugin appear before the timelapse is better for me. -You can do this by changing the config.yaml file as instructed on [octoprint documentation ](http://docs.octoprint.org/en/master/configuration/config_yaml.html). Unless defined differently via the command line config.yaml is located at ~/.octoprint. +You can do this by changing the config.yaml file as instructed on [octoprint documentation ](http://docs.octoprint.org/en/master/configuration/config_yaml.html). Unless defined differently via the command line config.yaml is located at `~/.octoprint`. You just need to add the following section: - -
appearance:
+```
+appearance:
   components:
     order:
       tab:
@@ -222,4 +246,4 @@ You just need to add the following section:
       - gcodeviewer
       - terminal
       - plugin_enclosure
-
+``` -- 2.39.5 From 7abaae199dc6ce1819ad2dbf1f9f76b510671402 Mon Sep 17 00:00:00 2001 From: coliss86 Date: Wed, 23 Dec 2020 15:18:10 +0100 Subject: [PATCH 044/104] Update README.md --- README.md | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 2601b8d..936dd7f 100644 --- a/README.md +++ b/README.md @@ -28,17 +28,17 @@ Here is a list of possibilities: Check pictures on thingiverse: http://www.thingiverse.com/thing:2245493 -## Software - -### Installation +## Installation Install the plugin using the Plugin Manager bundled with OctoPrint, you can search for the Enclosure plugin or just use the url: https://github.com/vitormhenrique/OctoPrint-Enclosure/archive/master.zip. ## Hardware -This plugin support many hardware temperature sensors and led controller. Here are detailled instructions on how to setup them +This plugin support many hardware temperature sensors, led, relays, heater... -### Temperature sensor +Here are detailled instructions on how to setup them. + +### Temperature sensors To control the enclosure temperature or get temperature triggered events, you need to install and configure a temperature sensor. This plugin can support DHT11, DHT22, AM2302, DS18B20, SI7021, BME280 and TMP102 temperature sensors. @@ -132,7 +132,7 @@ Find the address of the sensor: i2cdetect -y 1 ``` -### For Neopixel +### Neopixel If your setup does not have pip install pip: `sudo apt-get install python-pip` @@ -152,27 +152,24 @@ Also backlist the audio kernel: add the `blacklist snd_bcm2835` to the end of the file. -* GPIO +### GPIO This release uses RPi.GPIO to control IO of raspberry pi, it should install and work automatically. If it doesn't please update your octoprint with the latest release of octopi. +You can use relays / mosfets to control you lights, heater, lockers etc... If you want to control mains voltage I recommend using [PowerSwitch Tail II](http://www.powerswitchtail.com/). -### External hardware - -You can use relays / mosfets to control you lights, heater, lockers etc... If you want to control mains voltage I recommend using PowerSwitch Tail II. - -#### Relay +### Relay The relays module that I used couple [SainSmart 2-Channel Relay Module](https://www.amazon.com/gp/product/B0057OC6D8?ie=UTF8&tag=3dpstuff-20&camp=1789&linkCode=xm2&creativeASIN=B0057OC6D8). Those relays are active low, that means that they will turn on when you put LOW on the output of your pin. In order to not fry your Raspberry Pi pay attention on your wiring connection: remove the jumper link and connect 3.3v to VCC, 5V to JD-VCC and Ground to GND. -#### Heater +### Heater -For heating my enclosure I got a $15 lasko inside my enclosure. I opened it and added a relay to the mains wire. If you’re uncomfortable soldering or dealing with high voltage, please check out the [PowerSwitch Tail II](http://www.powerswitchtail.com/) . The PowerSwitch Tail II is fully enclosed, making it a lot safer. +For heating my enclosure I got a $15 lasko inside my enclosure. I opened it and added a relay to the mains wire. If you’re uncomfortable soldering or dealing with high voltage, please check out the [PowerSwitch Tail II](http://www.powerswitchtail.com/). It is fully enclosed, making it a lot safer. **CAUTION: VOLTAGE ON MAINS WIRE CAN KILL YOU, ONLY ATTEMPT TO DO THIS IF YOU KNOW WHAT YOU ARE DOING, AND DO AT YOUR OWN RISK** **CAUTION 2: THIS HEATER IS NOT INTENDED TO FUNCTION THIS WAY AND IT MIGHT BE A FIRE HAZARD. DO IT AT YOUR OWN RISK** -#### Cooler +### Cooler You can get a [USB Mini Desktop Fan](https://www.amazon.com/gp/product/B00WM7TRTY?ie=UTF8&tag=3dpstuff-20&camp=1789&linkCode=xm2&creativeASIN=B00WM7TRTY) and control it over a relay. @@ -182,7 +179,7 @@ You have the ability to add a filament sensor to the enclosure, it will automati http://www.thingiverse.com/thing:1698397 -**Configuration** +## Plugin configuration You need to enable what do you want the plugin to control. Settings from plugin version < 3.6 are not compatible anymore, you will loose all settings after upgrading the plugin. -- 2.39.5 From 22c5f602c4d77ab8ae079dddd7746a9ee7361e4b Mon Sep 17 00:00:00 2001 From: coliss86 Date: Wed, 23 Dec 2020 15:21:39 +0100 Subject: [PATCH 045/104] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 936dd7f..8306fcd 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ For heating my enclosure I got a $15 lasko inside my enclosure. I opened it and You can get a [USB Mini Desktop Fan](https://www.amazon.com/gp/product/B00WM7TRTY?ie=UTF8&tag=3dpstuff-20&camp=1789&linkCode=xm2&creativeASIN=B00WM7TRTY) and control it over a relay. -#### Filament sensor +### Filament sensor You have the ability to add a filament sensor to the enclosure, it will automatically pause the print and run a gcode command to change the filament if you run out of filament, I can be any type of filament sensor, the sensor should connect to ground if is set as an "active low" when the filament run out or 3.3v if the sensor is set as "active high" when detected the end of filament, it does not matter if it is normally open or closed, that will only interfere on your wiring. I'm using the following sensor: -- 2.39.5 From c9103bcdf8c29bab9621ec62a68b99ed668c5485 Mon Sep 17 00:00:00 2001 From: raoulh Date: Fri, 5 Feb 2021 17:44:31 +0100 Subject: [PATCH 046/104] Add i2c gpio support (#1) Implement support for i2c gpio devices (GPIO outputs and temp/humidity inputs) --- octoprint_enclosure/__init__.py | 238 +++++++++++++++--- octoprint_enclosure/static/js/enclosure.js | 14 +- .../templates/enclosure_settings.jinja2 | 98 +++++++- setup.py | 2 +- 4 files changed, 315 insertions(+), 37 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 473321c..8116e81 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -21,6 +21,8 @@ import inspect import threading import json import copy +from smbus2 import SMBus +import struct #Function that returns Boolean output state of the GPIO inputs / outputs @@ -148,7 +150,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.print_complete = False def get_settings_version(self): - return 6 + return 8 def on_settings_migrate(self, target, current=None): self._logger.warn("######### current settings version %s target settings version %s #########", current, target) @@ -156,15 +158,39 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._logger.info("rpi_outputs: %s", self.rpi_outputs) self._logger.info("rpi_inputs: %s", self.rpi_inputs) self._logger.info("######### End Current Settings #########") - if current >= 4 and target == 6: - self._logger.warn("######### migrating settings to v6 #########") + if current >= 4 and target == 8: + self._logger.warn("######### migrating settings to v8 #########") old_outputs = self._settings.get(["rpi_outputs"]) for rpi_output in old_outputs: if 'shutdown_on_failed' not in rpi_output: rpi_output['shutdown_on_failed'] = False if 'shell_script' not in rpi_output: rpi_output['shell_script'] = "" + if 'gpio_i2c_enabled' not in rpi_output: + rpi_output['gpio_i2c_enabled'] = False + if 'gpio_i2c_bus' not in rpi_output: + rpi_output['gpio_i2c_bus'] = 1 + if 'gpio_i2c_address' not in rpi_output: + rpi_output['gpio_i2c_address'] = 1 + if 'gpio_i2c_register' not in rpi_output: + rpi_output['gpio_i2c_register'] = 1 + if 'gpio_i2c_data_on' not in rpi_output: + rpi_output['gpio_i2c_data_on'] = 1 + if 'gpio_i2c_data_off' not in rpi_output: + rpi_output['gpio_i2c_data_off'] = 0 + if 'gpio_i2c_register_status' not in rpi_output: + rpi_output['gpio_i2c_register_status'] = 1 self._settings.set(["rpi_outputs"], old_outputs) + + old_inputs = self._settings.get(["rpi_inputs"]) + for rpi_input in old_inputs: + if 'temp_i2c_bus' not in rpi_input: + rpi_input['temp_i2c_bus'] = 1 + if 'temp_i2c_address' not in rpi_input: + rpi_input['temp_i2c_address'] = 1 + if 'temp_i2c_register' not in rpi_input: + rpi_input['temp_i2c_register'] = 1 + self._settings.set(["rpi_inputs"], old_inputs) else: self._logger.warn("######### settings not compatible #########") self._settings.set(["rpi_outputs"], []) @@ -191,7 +217,11 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP ConfiguredAs = "Output" ActiveLow = CheckInputActiveLow(rpi_output['active_low']) pin = self.to_int(rpi_output['gpio_pin']) - val = PinState_Human(pin,ActiveLow) + if rpi_output['gpio_i2c_enabled']: + b = self.gpio_i2c_input(rpi_output, ActiveLow) + val = " ON " if b else " OFF " + else: + val = PinState_Human(pin,ActiveLow) label = rpi_output['label'] Resp.append(dict(Configured_As=ConfiguredAs, label=label, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) if MatchFound == False: @@ -279,7 +309,11 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP label = rpi_output['label'] pin = self.to_int(rpi_output['gpio_pin']) ActiveLow = rpi_output['active_low'] - val = PinState_Human(pin,ActiveLow) + if rpi_output['gpio_i2c_enabled']: + b = self.gpio_i2c_input(rpi_output, ActiveLow) + val = " ON " if b else " OFF " + else: + val = PinState_Human(pin,ActiveLow) outputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, State=val)) return Response(json.dumps(outputs), mimetype='application/json') @@ -290,7 +324,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP if identifier == self.to_int(rpi_output['index_id']): out = copy.deepcopy(rpi_output) pin = self.to_int(rpi_output['gpio_pin']) - out['current_value'] = PinState_Boolean(pin, rpi_output['active_low'] ) + if rpi_output['gpio_i2c_enabled']: + out['current_value'] = self.gpio_i2c_input(rpi_output, rpi_output['active_low']) + else: + out['current_value'] = PinState_Boolean(pin, rpi_output['active_low'] ) return Response(json.dumps(out), mimetype='application/json') return make_response('', 404) @@ -313,7 +350,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP for rpi_output in self.rpi_outputs: if identifier == self.to_int(rpi_output['index_id']): val = (not value) if rpi_output['active_low'] else value - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + if rpi_output['gpio_i2c_enabled']: + self.gpio_i2c_write(rpi_output, val) + else: + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) return make_response('', 204) @@ -520,7 +560,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP if rpi_output['output_type'] == 'regular': pin = self.to_int(rpi_output['gpio_pin']) ActiveLow = rpi_output['active_low'] - val = PinState_Boolean(pin, ActiveLow) + if rpi_output['gpio_i2c_enabled']: + val = self.gpio_i2c_input(rpi_output, rpi_output['active_low']) + else: + val = PinState_Boolean(pin, ActiveLow) val2 = PinState_Human(pin, ActiveLow) index = self.to_int(rpi_output['index_id']) gpio_status.append(dict(index_id=index, status=val, State=val2)) @@ -533,7 +576,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP for rpi_output in self.rpi_outputs: if self.to_int(index) == self.to_int(rpi_output['index_id']): val = (not value) if rpi_output['active_low'] else value - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + if rpi_output['gpio_i2c_enabled']: + self.gpio_i2c_write(rpi_output, val) + else: + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) return jsonify(success=True) @octoprint.plugin.BlueprintPlugin.route("/sendShellCommand", methods=["GET"]) @@ -642,7 +688,62 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP # DEPREACTION END + # GPIO over i2c + def gpio_i2c_input(self, output, active_low=None): + state = False + try: + i2cbus = self.to_int(output['gpio_i2c_bus']) + i2caddr = self.to_int(output['gpio_i2c_address']) + i2creg = self.to_int(output['gpio_i2c_register_status']) + data_on = self.to_int(output['gpio_i2c_data_on']) + + with SMBus(i2cbus) as bus: + data = bus.read_i2c_block_data(i2caddr, i2creg, 1) + if data[0] == data_on: + state = True + + self._logger.debug("gpio_i2c_input(i2cbus=%s, i2caddr=%s, i2creg=%s, data_on=%s) data == %s", + i2cbus, i2caddr, i2creg, data_on, data) + + if active_low is None and state: return state + + except Exception as ex: + self.log_error(ex) + + if active_low and not state: return True + if not active_low and state: return True + return False + + def gpio_i2c_write(self, output, state, queue_id=None): + try: + i2cbus = self.to_int(output['gpio_i2c_bus']) + i2caddr = self.to_int(output['gpio_i2c_address']) + i2creg = self.to_int(output['gpio_i2c_register']) + data_on = self.to_int(output['gpio_i2c_data_on']) + data_off = self.to_int(output['gpio_i2c_data_off']) + + with SMBus(i2cbus) as bus: + data = [] + if state: + data.append(data_on) + else: + data.append(data_off) + + bus.write_i2c_block_data(i2caddr, i2creg, data) + + if queue_id is not None: + self._logger.debug("Running scheduled queue id %s", queue_id) + self._logger.debug("Writing on GPIO (i2c): %s/%s value %s", output['gpio_i2c_address'], output['gpio_i2c_register'], state) + self.update_ui() + if queue_id is not None: + self.stop_queue_item(queue_id) + + except Exception as ex: + template = "An exception of type {0} occurred on {1} when writing on i2c address {2}, reg {3}. Arguments:\n{4!r}" + message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, output['gpio_i2c_address'], output['gpio_i2c_register'], ex.args) + self._logger.warn(message) + pass def send_neopixel_command(self, led_pin, led_count, led_brightness, red, green, blue, address, neopixel_dirrect, @@ -720,10 +821,13 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP index_id = self.to_int(output['index_id']) if output['output_type'] == 'regular': - if first_run: - current_value = False + if output['gpio_i2c_enabled']: + current_value = self.gpio_i2c_input(output) else: - current_value = (not GPIO.input(gpio_pin)) if output['active_low'] else GPIO.input(gpio_pin) + if first_run: + current_value = False + else: + current_value = (not GPIO.input(gpio_pin)) if output['active_low'] else GPIO.input(gpio_pin) if current_value: time_delay = self.to_int(output['toggle_timer_off']) @@ -731,12 +835,18 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP time_delay = self.to_int(output['toggle_timer_on']) if not self.print_complete: - self.write_gpio(gpio_pin, not current_value) + if output['gpio_i2c_enabled']: + self.gpio_i2c_write(output, not current_value) + else: + self.write_gpio(gpio_pin, not current_value) thread = threading.Timer(time_delay, self.toggle_output, args=[index_id]) thread.start() else: off_value = True if output['active_low'] else False - self.write_gpio(gpio_pin, off_value) + if output['gpio_i2c_enabled']: + self.gpio_i2c_write(output, off_value) + else: + self.write_gpio(gpio_pin, off_value) self.update_ui_outputs() return @@ -810,11 +920,17 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP shutdown = output['auto_shutdown'] if output['output_type'] == 'regular': - val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) + if output['gpio_i2c_enabled']: + val = self.gpio_i2c_input(output) + else: + val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) regular_status.append( dict(index_id=index, status=val, auto_startup=startup, auto_shutdown=shutdown)) if output['output_type'] == 'temp_hum_control': - val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) + if output['gpio_i2c_enabled']: + val = self.gpio_i2c_input(output) + else: + val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) temp_control_status.append( dict(index_id=index, status=val, auto_startup=startup, auto_shutdown=shutdown)) if output['output_type'] == 'neopixel_indirect' or output['output_type'] == 'neopixel_direct': @@ -876,6 +992,12 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP elif sensor['temp_sensor_type'] == "mcp9808": temp = self.read_mcp_temp(sensor['temp_sensor_address']) hum = 0 + elif sensor['temp_sensor_type'] == "temp_raw_i2c": + temp = self.read_raw_i2c_temp(sensor) + hum = 0 + elif sensor['temp_sensor_type'] == "hum_raw_i2c": + temp = 0 + hum = self.read_raw_i2c_temp(sensor) else: self._logger.info("temp_sensor_type no match") temp = None @@ -901,8 +1023,12 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP for rpi_controlled_output in self.rpi_outputs: if self.to_int(temperature_alarm['controlled_io']) == self.to_int( rpi_controlled_output['index_id']): - val = GPIO.LOW if temperature_alarm['controlled_io_set_value'] == 'low' else GPIO.HIGH - self.write_gpio(self.to_int(rpi_controlled_output['gpio_pin']), val) + if rpi_controlled_output['gpio_i2c_enabled']: + val = False if temperature_alarm['controlled_io_set_value'] == 'low' else True + self.gpio_i2c_write(rpi_controlled_output, val) + else: + val = GPIO.LOW if temperature_alarm['controlled_io_set_value'] == 'low' else GPIO.HIGH + self.write_gpio(self.to_int(rpi_controlled_output['gpio_pin']), val) for notification in self.notifications: if notification['temperatureAction']: msg = ("Temperature action: enclosure temperature exceed " + temperature_alarm[ @@ -920,6 +1046,28 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP return return_value, return_value + def read_raw_i2c_temp(self, sensor): + try: + i2cbus = self.to_int(sensor['temp_i2c_bus']) + i2caddr = self.to_int(sensor['temp_i2c_address']) + i2creg = self.to_int(sensor['temp_i2c_register']) + + with SMBus(i2cbus) as bus: + data = bus.read_i2c_block_data(i2caddr, i2creg, 4) + val = struct.unpack('f', bytearray(data)) + fval = val[0] + + self._logger.debug("read_raw_i2c_temp(i2cbus=%s, i2caddr=%s, i2creg=%s) data == %s (%s)", + i2cbus, i2caddr, i2creg, data, fval) + + return str(fval) + + except Exception as ex: + template = "An exception of type {0} occurred on {1} when reading on i2c address {2}, reg {3}. Arguments:\n{4!r}" + message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, i2caddr, i2creg, ex.args) + self._logger.warn(message) + return str(-1) + def read_mcp_temp(self, address): try: script = os.path.dirname(os.path.realpath(__file__)) + "/mcp9808.py" @@ -1173,7 +1321,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP if current_status: self._logger.info("Turning gpio to control temperature on.") val = False if temp_hum_control['active_low'] else True - self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) + if temp_hum_control['gpio_i2c_enabled']: + self.gpio_i2c_write(temp_hum_control, val) + else: + self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) else: index_id = temp_hum_control['index_id'] if index_id in self.waiting_temperature: @@ -1184,7 +1335,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._logger.info("Turning gpio to control temperature off.") val = True if temp_hum_control['active_low'] else False - self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) + if temp_hum_control['gpio_i2c_enabled']: + self.gpio_i2c_write(temp_hum_control, val) + else: + self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) for control_status in self.temp_hum_control_status: if control_status['index_id'] == temp_hum_control['index_id']: control_status['status'] = current_status @@ -1202,8 +1356,9 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP set_mode = GPIO.BOARD if self._settings.get(["use_board_pin_number"]) else GPIO.BCM if current_mode is None: outputs = list(filter( - lambda item: item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ - 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct', + lambda item: (item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ + 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct') and + item['gpio_i2c_enabled'] == False, self.rpi_outputs)) inputs = list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)) gpios = outputs + inputs @@ -1227,8 +1382,9 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP try: for gpio_out in list(filter( - lambda item: item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ - 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct', + lambda item: (item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ + 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct') and + item['gpio_i2c_enabled'] == False, self.rpi_outputs)): gpio_pin = self.to_int(gpio_out['gpio_pin']) if gpio_pin not in self.rpi_outputs_not_changed: @@ -1260,7 +1416,8 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP try: for gpio_out in list( - filter(lambda item: item['output_type'] == 'regular' or item['output_type'] == 'temp_hum_control', + filter(lambda item: (item['output_type'] == 'regular' or item['output_type'] == 'temp_hum_control') and + item['gpio_i2c_enabled'] == False, self.rpi_outputs)): initial_value = GPIO.HIGH if gpio_out['active_low'] else GPIO.LOW pin = self.to_int(gpio_out['gpio_pin']) @@ -1387,8 +1544,12 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == controlled_io].pop() if rpi_output['output_type'] == 'regular': - val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + if rpi_output['gpio_i2c_enabled']: + val = False if rpi_input['controlled_io_set_value'] == 'low' else True + self.gpio_i2c_write(rpi_output, val) + else: + val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) except Exception as ex: self.log_error(ex) pass @@ -1420,7 +1581,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.to_int(rpi_output['gpio_pin'])) == GPIO.HIGH else GPIO.HIGH else: val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + if rpi_output['gpio_i2c_enabled']: + self.gpio_i2c_write(rpi_output, val) + else: + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) for notification in self.notifications: if notification['gpioAction']: msg = "GPIO control action caused by input " + str( @@ -1685,7 +1849,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP gpio = self.to_int(rpi_output['gpio_pin']) if rpi_output['output_type'] == 'regular': value = False if rpi_output['active_low'] else True - self.write_gpio(gpio, value) + if rpi_output['gpio_i2c_enabled']: + self.gpio_i2c_write(rpi_output, value) + else: + self.write_gpio(gpio, value) if rpi_output['output_type'] == 'ledstrip': self.ledstrip_set_rgb(rpi_output) if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: @@ -1788,8 +1955,12 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._logger.debug("Scheduling regular output id %s on %s delay_seconds", queue_id, delay_seconds) - thread = threading.Timer(delay_seconds, self.write_gpio, - args=[self.to_int(rpi_output['gpio_pin']), value, queue_id]) + if rpi_output['gpio_i2c_enabled']: + thread = threading.Timer(delay_seconds, self.gpio_i2c_write, + args=[rpi_output, value, queue_id]) + else: + thread = threading.Timer(delay_seconds, self.write_gpio, + args=[self.to_int(rpi_output['gpio_pin']), value, queue_id]) self.event_queue.append(dict(queue_id=queue_id, thread=thread)) @@ -1904,7 +2075,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP set_value = self.constrain(set_value, 0, 1) value = True if set_value == 1 else False value = (not value) if output['active_low'] else value - self.write_gpio(self.to_int(output['gpio_pin']), value) + if output['gpio_i2c_enabled']: + self.gpio_i2c_write(output, value) + else: + self.write_gpio(self.to_int(output['gpio_pin']), value) comm_instance._log("Setting REGULAR output %s to value %s" % (index_id, value)) return if output['output_type'] == 'pwm': diff --git a/octoprint_enclosure/static/js/enclosure.js b/octoprint_enclosure/static/js/enclosure.js index e9c4847..048b45e 100644 --- a/octoprint_enclosure/static/js/enclosure.js +++ b/octoprint_enclosure/static/js/enclosure.js @@ -421,7 +421,14 @@ $(function () { ledstrip_gpio_dat: ko.observable(""), microcontroller_address: ko.observable(0), gcode: ko.observable(""), - show_on_navbar: ko.observable(false) + show_on_navbar: ko.observable(false), + gpio_i2c_enabled: ko.observable(false), + gpio_i2c_bus: ko.observable(1), + gpio_i2c_address: ko.observable(1), + gpio_i2c_register: ko.observable(1), + gpio_i2c_data_on: ko.observable(1), + gpio_i2c_data_off: ko.observable(0), + gpio_i2c_register_status: ko.observable(1) }); }; @@ -456,7 +463,10 @@ $(function () { temp_sensor_navbar: ko.observable(true), filament_sensor_timeout: ko.observable(120), filament_sensor_enabled: ko.observable(true), - temp_sensor_i2cbus: ko.observable(1) + temp_sensor_i2cbus: ko.observable(1), + temp_i2c_bus: ko.observable(1), + temp_i2c_address: ko.observable(1), + temp_i2c_register: ko.observable(1) }); }; diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index a30ca69..01ddaed 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -91,7 +91,7 @@ - +
@@ -263,6 +263,69 @@
+ +
+
+ + Active low means that the GPIO will turn on when receive a low signal (ground) from Raspberry PI +
+
+ + + +
+ +
+ + This value should remain 1 unless you've used dtoverlay=i2c-gpio or you are using another bus. + +
+
+ +
+ +
+ + I2C address in HEX value, you can find it by running + i2cdetect -y 1 on your Raspberry Pi +
+
+ +
+ +
+ + I2C register in HEX value +
+
+ +
+ +
+ + I2C data to write in HEX value when ON +
+
+ +
+ +
+ + I2C data to write in HEX value when OFF +
+
+ +
+ +
+ + I2C register for reading gpio state in HEX value +
+
+ +
@@ -540,6 +603,8 @@ + + Attention You need to install and configure the necessary libraries for the temperature sensor, check @@ -547,6 +612,35 @@ github page
+ + +
+ +
+ + This value should remain 1 unless you've used dtoverlay=i2c-gpio or you are using another bus. + +
+
+ +
+ +
+ + I2C address in HEX value, you can find it by running + i2cdetect -y 1 on your Raspberry Pi +
+
+ +
+ +
+ + I2C register in HEX value +
+
+ +
@@ -614,7 +708,7 @@
- +
diff --git a/setup.py b/setup.py index 3dd24a9..bcf8339 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ plugin_url = "https://github.com/vitormhenrique/OctoPrint-Enclosure" plugin_license = "AGPLv3" # Any additional requirements besides OctoPrint should be listed here -plugin_requires = ["RPi.GPIO>=0.6.5","requests>=2.7"] +plugin_requires = ["RPi.GPIO>=0.6.5","requests>=2.7","smbus2>=0.3.0"] additional_setup_parameters = {} -- 2.39.5 From 05cf1e9684253dae8f5941b10849ad513efd426e Mon Sep 17 00:00:00 2001 From: Daniel Korgel Date: Wed, 10 Feb 2021 21:05:43 +0100 Subject: [PATCH 047/104] Implemented temperature and humidity display in graph --- octoprint_enclosure/__init__.py | 27 ++++++++++++++++--- octoprint_enclosure/static/js/enclosure.js | 4 ++- .../templates/enclosure_settings.jinja2 | 18 +++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 473321c..2559987 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -148,7 +148,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.print_complete = False def get_settings_version(self): - return 6 + return 7 def on_settings_migrate(self, target, current=None): self._logger.warn("######### current settings version %s target settings version %s #########", current, target) @@ -157,14 +157,25 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._logger.info("rpi_inputs: %s", self.rpi_inputs) self._logger.info("######### End Current Settings #########") if current >= 4 and target == 6: - self._logger.warn("######### migrating settings to v6 #########") old_outputs = self._settings.get(["rpi_outputs"]) + old_inputs = self._settings.get(["rpi_inputs"]) + self._logger.warn("######### migrating settings to v6+ #########") for rpi_output in old_outputs: if 'shutdown_on_failed' not in rpi_output: rpi_output['shutdown_on_failed'] = False if 'shell_script' not in rpi_output: rpi_output['shell_script'] = "" + + if target >= 7: + self._logger.warn("######### migrating settings to v7+ #########") + for rpi_input in old_inputs: + if 'show_graph_temp' not in rpi_input: + rpi_input['show_graph_temp'] = False + if 'show_graph_humidity' not in rpi_input: + rpi_input['show_graph_humidity'] = False + self._settings.set(["rpi_outputs"], old_outputs) + self._settings.set(["rpi_inputs"], old_inputs) else: self._logger.warn("######### settings not compatible #########") self._settings.set(["rpi_outputs"], []) @@ -1944,6 +1955,15 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP comm_instance._log("Setting TEMP/HUM control output %s to value %s" % (index_id, set_value)) return + def get_graph_data(self, comm, parsed_temps): + for sensor in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): + if sensor["show_graph_temp"]: + parsed_temps[str(sensor["label"])] = (sensor['temp_sensor_temp'], None) + if sensor["show_graph_humidity"]: + parsed_temps[str(sensor["label"])+" Humidity"] = (sensor['temp_sensor_humidity'], None) + + return parsed_temps + __plugin_name__ = "Enclosure Plugin" __plugin_pythoncompat__ = ">=2.7,<4" @@ -1956,5 +1976,6 @@ def __plugin_load__(): global __plugin_hooks__ __plugin_hooks__ = { "octoprint.comm.protocol.gcode.queuing" : __plugin_implementation__.hook_gcode_queuing, - "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information + "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information, + "octoprint.comm.protocol.temperatures.received": (__plugin_implementation__.get_graph_data, 1) } diff --git a/octoprint_enclosure/static/js/enclosure.js b/octoprint_enclosure/static/js/enclosure.js index e9c4847..36367e8 100644 --- a/octoprint_enclosure/static/js/enclosure.js +++ b/octoprint_enclosure/static/js/enclosure.js @@ -456,7 +456,9 @@ $(function () { temp_sensor_navbar: ko.observable(true), filament_sensor_timeout: ko.observable(120), filament_sensor_enabled: ko.observable(true), - temp_sensor_i2cbus: ko.observable(1) + temp_sensor_i2cbus: ko.observable(1), + show_graph_temp: ko.observable(false), + show_graph_humidity: ko.observable(false) }); }; diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index a30ca69..ab88291 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -765,6 +765,24 @@ Enable and disable temperature on navbar
+
+ + Enable to show temperature in temperature graph, when a printer is connected. + +

Note:This feature currently requires a custom graph plugin like PlotlyTempGraph

+ +
+
+ + Enable to show humidity in temperature graph, when a printer is connected. + +

Note:This feature currently requires a custom graph plugin like PlotlyTempGraph

+ +
-- 2.39.5 From f3759a29bbd98337afb55d9377a467590d45508f Mon Sep 17 00:00:00 2001 From: Stefan Schueller Date: Sat, 20 Feb 2021 20:32:00 +0100 Subject: [PATCH 048/104] Added RPI CPU Temp --- octoprint_enclosure/__init__.py | 18 +++++++++++++++++- octoprint_enclosure/getPiTemp.py | 10 ++++++++++ .../templates/enclosure_settings.jinja2 | 3 ++- 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 octoprint_enclosure/getPiTemp.py diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 473321c..509abbe 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -21,7 +21,7 @@ import inspect import threading import json import copy - +from .getPiTemp import PiTemp #Function that returns Boolean output state of the GPIO inputs / outputs def PinState_Boolean(pin, ActiveLow) : @@ -865,6 +865,9 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP temp, hum = self.read_bme280_temp(sensor['temp_sensor_address']) elif sensor['temp_sensor_type'] == "am2320": temp, hum = self.read_am2320_temp() # sensor has fixed address + elif sensor['temp_sensor_type'] == "rpi": + temp = self.read_rpi_temp() # rpi CPU Temp + hum = 0 elif sensor['temp_sensor_type'] == "si7021": temp, hum = self.read_si7021_temp(sensor['temp_sensor_address'], sensor['temp_sensor_i2cbus']) elif sensor['temp_sensor_type'] == "tmp102": @@ -998,6 +1001,19 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP "Failed to execute python scripts, try disabling use SUDO on advanced section of the plugin.") self.log_error(ex) return (0, 0) + + def read_rpi_temp(self): + try: + pitemp = PiTemp() + temp = pitemp.getTemp() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Pi CPU result: %s", temp) + return temp + except Exception as ex: + self._logger.info( + "Failed to get pi cpu temperature") + self.log_error(ex) + return 0 def read_si7021_temp(self, address, i2cbus): try: diff --git a/octoprint_enclosure/getPiTemp.py b/octoprint_enclosure/getPiTemp.py new file mode 100644 index 0000000..ca8f137 --- /dev/null +++ b/octoprint_enclosure/getPiTemp.py @@ -0,0 +1,10 @@ +from gpiozero import CPUTemperature + +import ctypes +import struct +import sys + +class PiTemp: + def getTemp(self): + temp = CPUTemperature() + return '{0:0.1f}'.format(temp.temperature) diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index a30ca69..4a9e42b 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -539,6 +539,7 @@ + @@ -614,7 +615,7 @@
- +
-- 2.39.5 From 77ac49ecf98bfef2b3fac9ac2f7a51edf2f5276f Mon Sep 17 00:00:00 2001 From: Daniel Korgel Date: Sun, 7 Mar 2021 11:44:58 +0100 Subject: [PATCH 049/104] Fixed bug in config updater --- octoprint_enclosure/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 2559987..fd9b0f8 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -156,7 +156,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._logger.info("rpi_outputs: %s", self.rpi_outputs) self._logger.info("rpi_inputs: %s", self.rpi_inputs) self._logger.info("######### End Current Settings #########") - if current >= 4 and target == 6: + if current >= 4 and target >= 6: old_outputs = self._settings.get(["rpi_outputs"]) old_inputs = self._settings.get(["rpi_inputs"]) self._logger.warn("######### migrating settings to v6+ #########") -- 2.39.5 From 330d70e531b7dc26a04468b7eff2d95d1b3ac6a2 Mon Sep 17 00:00:00 2001 From: Vitor de Miranda Henrique Date: Thu, 18 Mar 2021 21:52:01 -0500 Subject: [PATCH 050/104] Update README.md --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e7e67fd..0224ec7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +# QUICK NOTE: I suffered an injury last Sunday (06/20/20) and broke both bones on my left forearm, it will be impossible to work on on this plugin in the near future, I'm struggling to make it work with a full time job + masters in computers science. This plugin is not abandoned but it is on vacation mode. + + Find the plugin useful? Buy me a coffee [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/VitorHenrique/2) @@ -32,7 +35,7 @@ Check pictures on thingiverse: http://www.thingiverse.com/thing:2245493 Install the plugin using the Plugin Manager bundled with OctoPrint, you can search for the Enclosure plugin or just use the url: https://github.com/vitormhenrique/OctoPrint-Enclosure/archive/master.zip. -To control the enclosure temperature or get temperature triggered events, you need to install and configure a temperature sensor. This plugin can support DHT11, DHT22, AM2302, DS18B20, SI7021, BME280 and TMP102 temperature sensors. +To control the encosure temperature or get temperature trigged events, you need to install and configure a temperature sensor. This plugin can support DHT11, DHT22, AM2302, DS18B20, SI7021, BME280 and TMP102 temperature sensors. * For the DHT11, DHT22 and AM2302 follow this steps: @@ -170,7 +173,7 @@ Outputs can be set to the following types: * Regular GPIO * PWM GPIO -* Neopixel Control via Microcontroller +* Neopixel Control via Microcontroler * Neopixel Control directly from raspberry pi * Temperature and Humidity Control * Temperature Alarm @@ -191,7 +194,7 @@ Temperature Sensors will be used to input temperature and humidity data, they ca GPIO inputs will trigger events for the plugin, this feature can be used to add buttons to the enclosure and cause pressing those buttons to act on the printer or other pre-configured outputs. -After selecting GPIO for the input type, and selecting output control on the action type, the button will be able to turn on / off or toggle linked regular outputs, basically being able to control your lights / fan using mechanical buttons instead of the octoprint interface. You can also use buttons to send g-code commands. +After selecting GPIO for the input type, and selecting output control on the action type, the button will be able to turn on / off or toggle linked regular_gpio outputs, basically being able to control your lights / fan using mechanical buttons instead of the octoprint interface. You can also use buttons to send g-code commands. Selecting print control on the action type will trigger printer actions when the configured GPIO receives a signal. The actions can be Resume and Pause a print job or Change Filament. You can use the "change filament" action and set up the input GPIO according to your filament sensor, for example, if your filament sensor connects to ground when detects the end of the filament, you should choose PULL UP resistors and detect the event on the falling edge. You can also add mechanical buttons to pause, resume and change filaments near your printer for convenience. -- 2.39.5 From a46e771f7e6065e5a5b6c1baf79f890526a8eb70 Mon Sep 17 00:00:00 2001 From: Daniel Korgel Date: Wed, 7 Apr 2021 20:18:25 +0200 Subject: [PATCH 051/104] initial version of Auto Shutdown on Error --- octoprint_enclosure/__init__.py | 25 ++++++++++++++++++- .../templates/enclosure_settings.jinja2 | 10 ++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index fd9b0f8..d30692d 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -148,7 +148,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.print_complete = False def get_settings_version(self): - return 7 + return 8 def on_settings_migrate(self, target, current=None): self._logger.warn("######### current settings version %s target settings version %s #########", current, target) @@ -165,6 +165,9 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP rpi_output['shutdown_on_failed'] = False if 'shell_script' not in rpi_output: rpi_output['shell_script'] = "" + if target >= 8: + if 'shutdown_on_error' not in rpi_output: + rpi_output['shutdown_on_error'] = False if target >= 7: self._logger.warn("######### migrating settings to v7+ #########") @@ -174,6 +177,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP if 'show_graph_humidity' not in rpi_input: rpi_input['show_graph_humidity'] = False + self._settings.set(["rpi_outputs"], old_outputs) self._settings.set(["rpi_inputs"], old_inputs) else: @@ -1588,6 +1592,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP # ~~ EventPlugin mixin def on_event(self, event, payload): + self._logger.info("Event Logging Test: %s", event) if event == Events.CONNECTED: self.update_ui() @@ -1653,6 +1658,23 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP msg = "Print job finished: " + file_name + "finished printing in " + file_name, elapsed_time self.send_notification(msg) + if event in (Events.ERROR, Events.DISCONNECTED): + self._logger.info("Detected Error or Disconnect in %s will call listeners for shutdown_on_error!", event) + for rpi_output in self.rpi_outputs: + if rpi_output['shutdown_on_error']: + self._logger.debug("Schedule shutdown for: %s", rpi_output["index_id"]) + self.schedule_auto_shutdown_outputs(rpi_output, 0) + self.run_tasks() + + if event == Events.PRINTER_STATE_CHANGED: + if "error" in payload["state_string"].lower(): + self._logger.info("Detected Error in %s id: %s state: %s will call listeners for shutdown_on_error!", event, payload["state_id"], payload["state_string"]) + for rpi_output in self.rpi_outputs: + if rpi_output['shutdown_on_error']: + self._logger.debug("Schedule shutdown for: %s", rpi_output["index_id"]) + self.schedule_auto_shutdown_outputs(rpi_output, 0) + self.run_tasks() + def run_tasks(self): for task in self.event_queue: if not task['thread'].is_alive(): @@ -1661,6 +1683,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP def schedule_auto_shutdown_outputs(self, rpi_output, shutdown_delay_seconds): sufix = 'auto_shutdown' if rpi_output['output_type'] == 'regular': + self._logger.debug("schedule_auto_shutdown_outputs output is of type regular") value = True if rpi_output['active_low'] else False self.add_regular_output_to_queue(shutdown_delay_seconds, rpi_output, value, sufix) if rpi_output['output_type'] == 'ledstrip': diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index ab88291..b415330 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -250,6 +250,16 @@
+ +
+
+ + Choose if output should turn off automatomatically when an error or disconnect was detected. +
+
+ -- 2.39.5 From 65c015243733dd09e1fb588fc8f43cb9283cc2cf Mon Sep 17 00:00:00 2001 From: Mitch Gallman Date: Mon, 3 May 2021 23:17:22 -0400 Subject: [PATCH 052/104] Add hum_raw_i2c & temp_raw_i2c as humidity capable --- octoprint_enclosure/static/js/enclosure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/octoprint_enclosure/static/js/enclosure.js b/octoprint_enclosure/static/js/enclosure.js index 048b45e..7dab1b4 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', '22', '2302', 'bme280', 'am2320', 'si7021'].indexOf(sensor) >= 0){ + if (['11', '22', '2302', 'bme280', 'am2320', 'si7021', 'hum_raw_i2c', 'temp_raw_i2c'].indexOf(sensor) >= 0){ return true; } return false; -- 2.39.5 From 04d57cc6d7691de938e4b971d0109ab8f8a0a01c Mon Sep 17 00:00:00 2001 From: Mitch Gallman Date: Mon, 3 May 2021 23:22:52 -0400 Subject: [PATCH 053/104] Change read_raw_i2c_temp to return both temp & hum Change read_raw_i2c_temp to return both temperature and humidity. Slave code concatenates the two float values into a byte array. --- octoprint_enclosure/__init__.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 8116e81..969d6a2 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -993,11 +993,9 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP temp = self.read_mcp_temp(sensor['temp_sensor_address']) hum = 0 elif sensor['temp_sensor_type'] == "temp_raw_i2c": - temp = self.read_raw_i2c_temp(sensor) - hum = 0 + temp, hum = self.read_raw_i2c_temp(sensor) elif sensor['temp_sensor_type'] == "hum_raw_i2c": - temp = 0 - hum = self.read_raw_i2c_temp(sensor) + hum, temp = self.read_raw_i2c_temp(sensor) else: self._logger.info("temp_sensor_type no match") temp = None @@ -1053,14 +1051,16 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP i2creg = self.to_int(sensor['temp_i2c_register']) with SMBus(i2cbus) as bus: - data = bus.read_i2c_block_data(i2caddr, i2creg, 4) - val = struct.unpack('f', bytearray(data)) - fval = val[0] + data = bus.read_i2c_block_data(i2caddr, i2creg, 8) + val = struct.unpack('f', bytearray(data[0:4])) + fval1 = val[0] + val = struct.unpack('f', bytearray(data[4:8])) + fval2 = val[0] - self._logger.debug("read_raw_i2c_temp(i2cbus=%s, i2caddr=%s, i2creg=%s) data == %s (%s)", - i2cbus, i2caddr, i2creg, data, fval) + self._logger.debug("read_raw_i2c_temp(i2cbus=%s, i2caddr=%s, i2creg=%s) data == %s (%s, %s)", + i2cbus, i2caddr, i2creg, data, fval1, fval2) - return str(fval) + return (fval1, fval2) except Exception as ex: template = "An exception of type {0} occurred on {1} when reading on i2c address {2}, reg {3}. Arguments:\n{4!r}" -- 2.39.5 From 0c88c333a1fe2350cabaa5edcc401727fb52db1e Mon Sep 17 00:00:00 2001 From: Mitch Gallman Date: Tue, 4 May 2021 20:11:04 -0400 Subject: [PATCH 054/104] Handle nan in read_raw_i2c_temp --- octoprint_enclosure/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 969d6a2..100908d 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -1054,9 +1054,13 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP data = bus.read_i2c_block_data(i2caddr, i2creg, 8) val = struct.unpack('f', bytearray(data[0:4])) fval1 = val[0] + if fval1 != fval1: + fval1 = 0 val = struct.unpack('f', bytearray(data[4:8])) fval2 = val[0] - + if fval2 != fval2: + fval2 = 0 + self._logger.debug("read_raw_i2c_temp(i2cbus=%s, i2caddr=%s, i2creg=%s) data == %s (%s, %s)", i2cbus, i2caddr, i2creg, data, fval1, fval2) -- 2.39.5 From f90347105bc62788584419c95f1f080a6d675085 Mon Sep 17 00:00:00 2001 From: Mitch Gallman Date: Tue, 4 May 2021 20:38:15 -0400 Subject: [PATCH 055/104] Update __init__.py --- octoprint_enclosure/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 100908d..81fa589 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -1052,12 +1052,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP with SMBus(i2cbus) as bus: data = bus.read_i2c_block_data(i2caddr, i2creg, 8) - val = struct.unpack('f', bytearray(data[0:4])) - fval1 = val[0] + fval1 = struct.unpack('f', bytearray(data[0:4]))[0] if fval1 != fval1: fval1 = 0 - val = struct.unpack('f', bytearray(data[4:8])) - fval2 = val[0] + fval2 = struct.unpack('f', bytearray(data[4:8]))[0] if fval2 != fval2: fval2 = 0 -- 2.39.5 From aa5bbe50ffcd6b34ca53ebd1a792bf687a057c7f Mon Sep 17 00:00:00 2001 From: Mitchell Way Date: Sat, 10 Jul 2021 21:33:08 -0500 Subject: [PATCH 056/104] adapt getDHTTemp.py to python3 --- octoprint_enclosure/__init__.py | 2 +- octoprint_enclosure/getDHTTemp.py | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 8116e81..0a6dfb3 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -1091,7 +1091,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP sudo_str = "sudo " else: sudo_str = "" - cmd = sudo_str + "python " + script + str(sensor) + " " + str(pin) + cmd = sudo_str + "python3 " + script + str(sensor) + " " + str(pin) if self._settings.get(["debug_temperature_log"]) is True: self._logger.debug("Temperature dht cmd: %s", cmd) stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() diff --git a/octoprint_enclosure/getDHTTemp.py b/octoprint_enclosure/getDHTTemp.py index cd6c18f..cb9bfef 100644 --- a/octoprint_enclosure/getDHTTemp.py +++ b/octoprint_enclosure/getDHTTemp.py @@ -1,19 +1,23 @@ import sys -import Adafruit_DHT +import adafruit_dht # Parse command line parameters. -sensor_args = { '11': Adafruit_DHT.DHT11, - '22': Adafruit_DHT.DHT22, - '2302': Adafruit_DHT.AM2302 } +sensor_args = { + '11': adafruit_dht.DHT11, + '22': adafruit_dht.DHT22, + '2302': adafruit_dht.DHT22 + } + if len(sys.argv) == 3 and sys.argv[1] in sensor_args: sensor = sensor_args[sys.argv[1]] pin = sys.argv[2] else: sys.exit(1) - -humidity, temperature = Adafruit_DHT.read_retry(sensor, pin,2,0.5) +dht_dev = sensor(pin) +humidity = dht_dev.humidity +temperature = dht_dev.temperature if humidity is not None and temperature is not None: print('{0:0.1f} | {1:0.1f}'.format(temperature, humidity)) -- 2.39.5 From d56eab29e9bfbf178994001876a3731ffc904c83 Mon Sep 17 00:00:00 2001 From: Pierre-Alexis Ciavaldini Date: Tue, 3 Aug 2021 19:58:21 +0200 Subject: [PATCH 057/104] add description to the output id argument After struggling a bit to find the right argument, I think this description is a little more clear to what should be passed (tried the pin number and zero-based array before finding the right way in the code) --- API.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/API.md b/API.md index dbee16d..a09d46c 100644 --- a/API.md +++ b/API.md @@ -144,7 +144,7 @@ Error Responses: Endpoint: **PATCH** `/outputs/` -*Note: id needs to be int (index_id)* +*Note: id needs to be int (index_id), one-based index in octoprint settings' enclosure section's `rpi_outputs` list* Body (Content-Type: `application/json`): ``` @@ -340,4 +340,4 @@ Body: empty Response (204): No-Content Error Responses: -- none \ No newline at end of file +- none -- 2.39.5 From 1ce086b42089d60fecb4b44b548a23884a59eb74 Mon Sep 17 00:00:00 2001 From: Chuck Bass Date: Wed, 4 Aug 2021 21:59:04 -0700 Subject: [PATCH 058/104] Update enclosure_settings.jinja2 Modified grammar to on settings page to sound correct to native English speakers. Fixed minor spelling errors. --- .../templates/enclosure_settings.jinja2 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index 01ddaed..dfdf111 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -1,10 +1,10 @@

{{ _('Raspberry Pi Outputs') }}

Configure all - outputs. Outputs can be everything that do actions based on UI button presses or automatic events or alarms. + outputs. Outputs can be everything that performs actions based on UI button presses, or automatic events or alarms.

- Example of possible outputs are gpio pins, neopixel LED's, temperature control or sending gcode to the printer. + Example of possible outputs are GPIO pins, neopixel LED's, temperature control or sending gcode to the printer.

@@ -773,7 +773,7 @@ - Choose what type of pull resistors that you want on the output. If you signal is active low, that means it should + Choose what type of pull resistors that you want on the output. If your signal is active low, that means it should run the action when receive a low signal (ground), you should choose PULL UP resistors.
@@ -784,8 +784,8 @@ - Do you want thrigger the event on the rise or falling edge? If you signal is active low, that means it should run - the action when receive a low signal (ground), you should choose FALLING EDGE. + Do you want to trigger the event on the rise or falling edge? If you signal is active low, that means it should run + the action when it receives a low signal (ground), you should choose FALLING EDGE. @@ -796,7 +796,7 @@ - When the event happen, you want control which OUTPUT? + When the event happens, do you want control of which OUTPUT? @@ -808,7 +808,7 @@ - When the event happen, you want to turn the controlled IO HIGH or LOW? + When the event happens, you do want to turn the controlled IO HIGH or LOW? @@ -947,7 +947,7 @@
GCODE that will be sent to the printer to pause and allow filament to be changed. You should add - ENTER on the end of every line sent to the printer + ENTER to the end of every line sent to the printer
-- 2.39.5 From f5ed2790c3080048c47e04a796a0f6aaea54e4e6 Mon Sep 17 00:00:00 2001 From: Chuck Bass Date: Thu, 9 Sep 2021 12:58:21 -0700 Subject: [PATCH 059/104] Update octoprint_enclosure/templates/enclosure_settings.jinja2 Co-authored-by: Mitch Gallman --- octoprint_enclosure/templates/enclosure_settings.jinja2 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index dfdf111..e79032c 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -808,7 +808,8 @@ - When the event happens, you do want to turn the controlled IO HIGH or LOW? + When the event happens, do you want to turn the controlled IO HIGH or LOW? + -- 2.39.5 From d615faef8d3d4ab521d3be07c0d55a989d24ae67 Mon Sep 17 00:00:00 2001 From: Adam Demuri Date: Wed, 6 Oct 2021 19:46:48 -0600 Subject: [PATCH 060/104] Add option for pullup for DS18B20 These sensors need a pullup resistor on the data pin. Instead of using an external pullup, this adds an option to use an internal pullup. --- README.md | 14 +++++++++++++- octoprint_enclosure/__init__.py | 12 +++++++++++- .../templates/enclosure_settings.jinja2 | 10 ++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0224ec7..1a01df6 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Note that the first argument is the temperature sensor (11, 22, or 2302), and th * For the DS18B20 sensor: -Follow the wiring diagram on the pictures on thingiverse. The DS18B20 uses "1-wire" communication protocol, you need to use 4.7K to 10K resistor from the data pin to VCC, DS18B20 only works on GPIO pin number 4 by default. You also need to add OneWire support for your raspberry pi. +Follow the wiring diagram on the pictures on thingiverse. The DS18B20 uses "1-wire" communication protocol, DS18B20 only works on GPIO pin number 4 by default. You also need to add OneWire support for your raspberry pi. Start by adding the following line to /boot/config.txt @@ -76,6 +76,16 @@ After rebooting, you can check if the OneWire device was found properly with You should see something like
[    3.030368] w1-gpio onewire@0: gpio pin 4, external pullup pin -1, parasitic power 0
+If you're using the internal pullup resistor, you'll need to enable it manually by running these Python commands. Or, you can simply configure the sensor inside of the Enclosure plugin, which will do this for you. + +```python +import RPi.GPIO as GPIO + +PIN=4 +GPIO.setmode(GPIO.BCM) +GPIO.setup(PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) +``` + You should be able to test your sensor by rebooting your system with sudo reboot. When the Pi is back up and you're logged in again, type the commands you see below into a terminal window. When you are in the 'devices' directory, the directory starting '28-' may have a different name, so cd to the name of whatever directory is there.
sudo modprobe w1-gpio
@@ -89,6 +99,8 @@ The response will either have YES or NO at the end of the first line. If it is y
 
 Copy the serial number, you will need to configure the plugin.  Note that for the serial number includes the 28-, for example 28-0000069834ff.
 
+The DS18B20 needs a pullup resistor on the data pin. On modern Pi models, you can use a resistor built into the Pi, configured in software. To do this, set the "Input Pull Resistor" option to "Input Pullup". If this doesn't work, you need to use a 4.7K to 10K resistor from the data pin to VCC.
+
 * For the SI7021, BME280, TMP102 and MCP9808 sensors
 
 Enable I2C on your raspberry pi, depending on raspi-config version, step by step can be different:
diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py
index 8116e81..0ab7c83 100644
--- a/octoprint_enclosure/__init__.py
+++ b/octoprint_enclosure/__init__.py
@@ -1348,7 +1348,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP
     def log_error(self, ex):
         template = "An exception of type {0} occurred on {1}. Arguments:\n{2!r}"
         message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, ex.args)
-        self._logger.warn(message)
+        self._logger.warn(message, exc_info = True)
 
     def setup_gpio(self):
         try:
@@ -1461,6 +1461,16 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP
                 if (rpi_input['action_type'] == 'printer_control' and rpi_input['printer_action'] != 'filament'):
                     GPIO.add_event_detect(gpio_pin, edge, callback=self.handle_printer_action, bouncetime=200)
                     self._logger.info("Adding PRINTER CONTROL event detect on pin %s with edge: %s", gpio_pin, edge)
+            
+            for rpi_input in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)):
+                gpio_pin = self.to_int(rpi_input['gpio_pin'])
+                if rpi_input['input_pull_resistor'] == 'input_pull_up':
+                    pull_resistor = GPIO.PUD_UP
+                elif rpi_input['input_pull_resistor'] == 'input_pull_down':
+                    pull_resistor = GPIO.PUD_DOWN
+                else:
+                    pull_resistor = GPIO.PUD_OFF
+                GPIO.setup(gpio_pin, GPIO.IN, pull_up_down=pull_resistor)
         except Exception as ex:
             self.log_error(ex)
 
diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2
index 01ddaed..506153f 100644
--- a/octoprint_enclosure/templates/enclosure_settings.jinja2
+++ b/octoprint_enclosure/templates/enclosure_settings.jinja2
@@ -649,6 +649,16 @@
           GPIO pin for temperature sensor, recommended to use 4 as DS18B20 has default support to pin #4 (BCM)
         
       
+      
+ +
+ + Whether to enable the built-in pullup for this temperature sensor. If set, then no external pullup is needed. +
+
-- 2.39.5 From 528e3e8a2c27a6c3209bc851bc0d6fc543de02eb Mon Sep 17 00:00:00 2001 From: jneilliii Date: Wed, 13 Oct 2021 00:48:40 -0400 Subject: [PATCH 061/104] fix deprecated payload in PrintDone event This should resolve the error associated with #432 --- octoprint_enclosure/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 8116e81..70e6404 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -1800,7 +1800,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP if event == Events.PRINT_DONE: for notification in self.notifications: if notification['printFinish']: - file_name = os.path.basename(payload["file"]) + file_name = os.path.basename(payload["path"]) elapsed_time_in_seconds = payload["time"] elapsed_time = octoprint.util.get_formatted_timedelta(timedelta(seconds=elapsed_time_in_seconds)) msg = "Print job finished: " + file_name + "finished printing in " + file_name, elapsed_time -- 2.39.5 From 21281c9711ba0f12e2655febcf4034349031d933 Mon Sep 17 00:00:00 2001 From: Daniel Korgel Date: Wed, 13 Oct 2021 14:40:11 +0200 Subject: [PATCH 062/104] incremented settings version number --- octoprint_enclosure/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 6f0d00f..5d4af89 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -150,7 +150,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.print_complete = False def get_settings_version(self): - return 8 + return 9 def on_settings_migrate(self, target, current=None): self._logger.warn("######### current settings version %s target settings version %s #########", current, target) @@ -158,8 +158,8 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._logger.info("rpi_outputs: %s", self.rpi_outputs) self._logger.info("rpi_inputs: %s", self.rpi_inputs) self._logger.info("######### End Current Settings #########") - if current >= 4 and target == 8: - self._logger.warn("######### migrating settings to v8 #########") + if current >= 4 and target == 9: + self._logger.warn("######### migrating settings to v9 #########") old_outputs = self._settings.get(["rpi_outputs"]) old_inputs = self._settings.get(["rpi_inputs"]) for rpi_output in old_outputs: -- 2.39.5 From 0d743fda49fc744a4670a85758096226128e1c93 Mon Sep 17 00:00:00 2001 From: Daniel Korgel Date: Wed, 13 Oct 2021 14:55:17 +0200 Subject: [PATCH 063/104] Updated settings description text --- octoprint_enclosure/templates/enclosure_settings.jinja2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index e422a0b..1f77a61 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -254,7 +254,7 @@
Choose if output should turn off automatomatically when an error or disconnect was detected.
-- 2.39.5 From ac8929842c654b734020d6607abb8e4622bd7f4c Mon Sep 17 00:00:00 2001 From: Vitor de Miranda Henrique Date: Wed, 13 Oct 2021 12:38:11 -0500 Subject: [PATCH 064/104] Update README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 1a01df6..982b585 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ -# QUICK NOTE: I suffered an injury last Sunday (06/20/20) and broke both bones on my left forearm, it will be impossible to work on on this plugin in the near future, I'm struggling to make it work with a full time job + masters in computers science. This plugin is not abandoned but it is on vacation mode. - - Find the plugin useful? Buy me a coffee [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/VitorHenrique/2) -- 2.39.5 From 262baca5879d6d27c5057f3ffde7fc35cc1b539e Mon Sep 17 00:00:00 2001 From: Daniel Korgel Date: Wed, 13 Oct 2021 20:34:02 +0200 Subject: [PATCH 065/104] Fixed typos in settings description --- octoprint_enclosure/__init__.py | 4364 ++++++++--------- .../templates/enclosure_settings.jinja2 | 6 +- 2 files changed, 2185 insertions(+), 2185 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index dc2aab2..01217f0 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -1,2182 +1,2182 @@ -# coding=utf-8 -from __future__ import absolute_import -from octoprint.events import eventManager, Events -from octoprint.util import RepeatedTimer -from subprocess import Popen, PIPE -from .ledstrip import LEDStrip -import octoprint.plugin -import RPi.GPIO as GPIO -from flask import jsonify, request, make_response, Response -from octoprint.server.util.flask import restricted_access -from werkzeug.exceptions import BadRequest -import time -import sys -import glob -import os -from datetime import datetime -from datetime import timedelta -import octoprint.util -import requests -import inspect -import threading -import json -import copy -from smbus2 import SMBus -import struct - - -#Function that returns Boolean output state of the GPIO inputs / outputs -def PinState_Boolean(pin, ActiveLow) : - try: - state = GPIO.input(pin) - if ActiveLow and not state: return True - if not ActiveLow and state: return True - return False - except: - return "ERROR: Unable to read pin" - -#Function that returns human-readable output state of the GPIO inputs / outputs -def PinState_Human(pin, ActiveLow): - PinState = PinState_Boolean(pin, ActiveLow) - if PinState == True : - return " ON " - elif PinState == False: - return " OFF " - else: - return PinState - -#Translates the Pull-Up/Pull-Down GPIO resistor setting to ActiveLow/ActiveHigh boolean -def CheckInputActiveLow(Input_Pull_Resistor): - #input_pull_up - #input_pull_down - if Input_Pull_Resistor == "input_pull_up": - return True - else: - return False - -class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SettingsPlugin, - octoprint.plugin.AssetPlugin, octoprint.plugin.BlueprintPlugin, - octoprint.plugin.EventHandlerPlugin): - rpi_outputs = [] - rpi_inputs = [] - waiting_temperature = [] - rpi_outputs_not_changed = [] - notifications = [] - pwm_instances = [] - event_queue = [] - temp_hum_control_status = [] - temperature_sensor_data = [] - last_filament_end_detected = [] - print_complete = False - development_mode = False - dummy_value = 30.0 - dummy_delta = 0.5 - - def start_timer(self): - """ - Function to start timer that checks enclosure temperature - """ - - self._check_temp_timer = RepeatedTimer(10, self.check_enclosure_temp, None, None, True) - self._check_temp_timer.start() - - @staticmethod - def to_float(value): - """Converts value to flow - Arguments: - value {any} -- value to be - Returns: - float -- value converted - """ - - try: - val = float(value) - return val - except: - return 0 - - @staticmethod - def to_int(value): - try: - val = int(value) - return val - except: - return 0 - - @staticmethod - def is_hour(value): - try: - datetime.strptime(value, '%H:%M') - return True - except: - return False - - @staticmethod - def create_date(value): - temp_string = datetime.now().strftime('%m/%d/%Y') + " " + value - return datetime.strptime(temp_string, '%m/%d/%Y %H:%M') - - @staticmethod - def constrain(n, minn, maxn): - return max(min(maxn, n), minn) - - @staticmethod - def get_gcode_value(command_string, gcode): - semicolon = command_string.find(';') - if not semicolon == -1: - command_string = command_string[:semicolon] - - for command in command_string.split(' '): - index = command.upper().find(gcode.upper()) - if not index == -1: - return command.replace(gcode, '') - return -1 - - # ~~ StartupPlugin mixin - def on_after_startup(self): - self.pwm_instances = [] - self.event_queue = [] - self.rpi_outputs_not_changed = [] - self.rpi_outputs = self._settings.get(["rpi_outputs"]) - self.rpi_inputs = self._settings.get(["rpi_inputs"]) - self.notifications = self._settings.get(["notifications"]) - self.generate_temp_hum_control_status() - self.setup_gpio() - self.configure_gpio() - self.update_ui() - self.start_outpus_with_server() - self.handle_initial_gpio_control() - self.start_timer() - self.print_complete = False - - def get_settings_version(self): - return 10 - - def on_settings_migrate(self, target, current=None): - self._logger.warn("######### current settings version %s target settings version %s #########", current, target) - self._logger.info("######### Current settings #########") - self._logger.info("rpi_outputs: %s", self.rpi_outputs) - self._logger.info("rpi_inputs: %s", self.rpi_inputs) - self._logger.info("######### End Current Settings #########") - if current >= 4 and target == 10: - self._logger.warn("######### migrating settings to v10 #########") - old_outputs = self._settings.get(["rpi_outputs"]) - old_inputs = self._settings.get(["rpi_inputs"]) - for rpi_output in old_outputs: - if 'shutdown_on_failed' not in rpi_output: - rpi_output['shutdown_on_failed'] = False - if 'shell_script' not in rpi_output: - rpi_output['shell_script'] = "" - if 'gpio_i2c_enabled' not in rpi_output: - rpi_output['gpio_i2c_enabled'] = False - if 'gpio_i2c_bus' not in rpi_output: - rpi_output['gpio_i2c_bus'] = 1 - if 'gpio_i2c_address' not in rpi_output: - rpi_output['gpio_i2c_address'] = 1 - if 'gpio_i2c_register' not in rpi_output: - rpi_output['gpio_i2c_register'] = 1 - if 'gpio_i2c_data_on' not in rpi_output: - rpi_output['gpio_i2c_data_on'] = 1 - if 'gpio_i2c_data_off' not in rpi_output: - rpi_output['gpio_i2c_data_off'] = 0 - if 'gpio_i2c_register_status' not in rpi_output: - rpi_output['gpio_i2c_register_status'] = 1 - if 'shutdown_on_error' not in rpi_output: - rpi_output['shutdown_on_error'] = False - self._settings.set(["rpi_outputs"], old_outputs) - - old_inputs = self._settings.get(["rpi_inputs"]) - for rpi_input in old_inputs: - if 'temp_i2c_bus' not in rpi_input: - rpi_input['temp_i2c_bus'] = 1 - if 'temp_i2c_address' not in rpi_input: - rpi_input['temp_i2c_address'] = 1 - if 'temp_i2c_register' not in rpi_input: - rpi_input['temp_i2c_register'] = 1 - if 'show_graph_temp' not in rpi_input: - rpi_input['show_graph_temp'] = False - if 'show_graph_humidity' not in rpi_input: - rpi_input['show_graph_humidity'] = False - self._settings.set(["rpi_inputs"], old_inputs) - else: - self._logger.warn("######### settings not compatible #########") - self._settings.set(["rpi_outputs"], []) - self._settings.set(["rpi_inputs"], []) - self.rpi_inputs = self._settings.get(["rpi_inputs"]) - - #Scan all configured inputs and outputs and return the pin value - @octoprint.plugin.BlueprintPlugin.route("/ReadPin/", methods=["GET"]) - def ReadSinglePin(self, identifier): - Resp = [] - MatchFound = False - for rpi_input in self.rpi_inputs: - if identifier == self.to_int(rpi_input['gpio_pin']): - MatchFound = True - ConfiguredAs = "Input" - ActiveLow = CheckInputActiveLow(rpi_input['input_pull_resistor']) - pin = self.to_int(rpi_input['gpio_pin']) - val = PinState_Human(pin,ActiveLow) - label = rpi_input['label'] - Resp.append(dict(Configured_As=ConfiguredAs, label=label, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['gpio_pin']): - MatchFound = True - ConfiguredAs = "Output" - ActiveLow = CheckInputActiveLow(rpi_output['active_low']) - pin = self.to_int(rpi_output['gpio_pin']) - if rpi_output['gpio_i2c_enabled']: - b = self.gpio_i2c_input(rpi_output, ActiveLow) - val = " ON " if b else " OFF " - else: - val = PinState_Human(pin,ActiveLow) - label = rpi_output['label'] - Resp.append(dict(Configured_As=ConfiguredAs, label=label, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) - if MatchFound == False: - pin = int(identifier) - ConfiguredAs = "Unknown" - ActiveLow = "Unknown" - try: - val = GPIO.input(pin) - except: - val = "GPIO pin not initialized." - Resp.append(dict(Configured_As=ConfiguredAs, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) - return Response(json.dumps(Resp), mimetype='application/json') - - # ~~ Blueprintplugin mixin - @octoprint.plugin.BlueprintPlugin.route("/inputs", methods=["GET"]) - def get_inputs(self): - inputs = [] - for rpi_input in self.rpi_inputs: - index = self.to_int(rpi_input['index_id']) - label = rpi_input['label'] - ActiveLow = CheckInputActiveLow(rpi_input['input_pull_resistor']) - pin = self.to_int(rpi_input['gpio_pin']) - val = PinState_Human(pin,ActiveLow) - inputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, State=val)) - return Response(json.dumps(inputs), mimetype='application/json') - - @octoprint.plugin.BlueprintPlugin.route("/inputs/", methods=["GET"]) - def get_input_status(self, identifier): - for rpi_input in self.rpi_inputs: - if identifier == self.to_int(rpi_input['index_id']): - return Response(json.dumps(rpi_input), mimetype='application/json') - return make_response('', 404) - - - @octoprint.plugin.BlueprintPlugin.route("/temperature/", methods=["PATCH"]) - @restricted_access - def set_enclosure_temp_humidity(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'temperature' not in data: - return make_response("missing temperature attribute", 406) - - set_value = data["temperature"] - - for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == identifier]: - temp_hum_control['temp_ctr_set_value'] = set_value - - self.handle_temp_hum_control() - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/filament/", methods=["PATCH"]) - @restricted_access - def set_filament_sensor(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'status' not in data: - return make_response("missing status attribute", 406) - - value = data["status"] - - for sensor in self.rpi_inputs: - if identifier == self.to_int(sensor['index_id']): - sensor['filament_sensor_enabled'] = value - self._logger.info("Setting filament sensor for input %s to : %s", str(identifier), value) - self._settings.set(["rpi_inputs"], self.rpi_inputs) - return make_response('', 204) - - @octoprint.plugin.BlueprintPlugin.route("/outputs", methods=["GET"]) - def get_outputs(self): - outputs = [] - for rpi_output in self.rpi_outputs: - if rpi_output['output_type'] == 'regular': - index = self.to_int(rpi_output['index_id']) - label = rpi_output['label'] - pin = self.to_int(rpi_output['gpio_pin']) - ActiveLow = rpi_output['active_low'] - if rpi_output['gpio_i2c_enabled']: - b = self.gpio_i2c_input(rpi_output, ActiveLow) - val = " ON " if b else " OFF " - else: - val = PinState_Human(pin,ActiveLow) - outputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, State=val)) - return Response(json.dumps(outputs), mimetype='application/json') - - - @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["GET"]) - def get_output_status(self, identifier): - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['index_id']): - out = copy.deepcopy(rpi_output) - pin = self.to_int(rpi_output['gpio_pin']) - if rpi_output['gpio_i2c_enabled']: - out['current_value'] = self.gpio_i2c_input(rpi_output, rpi_output['active_low']) - else: - out['current_value'] = PinState_Boolean(pin, rpi_output['active_low'] ) - return Response(json.dumps(out), mimetype='application/json') - return make_response('', 404) - - - @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["PATCH"]) - @restricted_access - def set_io(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'status' not in data: - return make_response("missing status attribute", 406) - - value = data["status"] - - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['index_id']): - val = (not value) if rpi_output['active_low'] else value - if rpi_output['gpio_i2c_enabled']: - self.gpio_i2c_write(rpi_output, val) - else: - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-startup", methods=["PATCH"]) - @restricted_access - def set_auto_startup(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'status' not in data: - return make_response("missing status attribute", 406) - - value = data["status"] - - if not value: - suffix = 'auto_startup' - queue_id = '{0!s}_{1!s}'.format(str(identifier), suffix) - self.stop_queue_item(queue_id) - for output in self.rpi_outputs: - if identifier == self.to_int(output['index_id']): - output['auto_startup'] = value - self._logger.info("Setting auto startup for output %s to : %s", str(identifier), value) - self._settings.set(["rpi_outputs"], self.rpi_outputs) - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-shutdown", methods=["PATCH"]) - @restricted_access - def set_auto_shutdown(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'status' not in data: - return make_response("missing status attribute", 406) - - value = data["status"] - - if not value: - suffix = 'auto_shutdown' - queue_id = '{0!s}_{1!s}'.format(str(identifier), suffix) - self.stop_queue_item(queue_id) - - for output in self.rpi_outputs: - if identifier == self.to_int(output['index_id']): - output['auto_shutdown'] = value - self._logger.info("Setting auto shutdown for output %s to : %s", str(identifier), value) - self._settings.set(["rpi_outputs"], self.rpi_outputs) - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/pwm/", methods=["PATCH"]) - @restricted_access - def set_pwm(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'duty_cycle' not in data: - return make_response("missing duty_cycle attribute", 406) - - set_value = self.to_int(data['duty_cycle']) - for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == identifier]: - rpi_output['duty_cycle'] = set_value - rpi_output['new_duty_cycle'] = "" - gpio = self.to_int(rpi_output['gpio_pin']) - self.write_pwm(gpio, set_value) - return make_response('', 204) - - @octoprint.plugin.BlueprintPlugin.route("/rgb-led/", methods=["PATCH"]) - @restricted_access - def set_ledstrip_color(self, identifier): - """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'rgb' not in data: - return make_response("missing rgb attribute", 406) - rgb = data['rgb'] - - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['index_id']): - self.ledstrip_set_rgb(rpi_output, rgb) - - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/neopixel/", methods=["PATCH"]) - @restricted_access - def set_neopixel(self, identifier): - """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'red' not in data: - return make_response("missing red attribute", 406) - if 'green' not in data: - return make_response("missing green attribute", 406) - if 'blue' not in data: - return make_response("missing blue attribute", 406) - - red = data['red'] - green = data['green'] - blue = data['blue'] - - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['index_id']): - led_count = rpi_output['neopixel_count'] - led_brightness = rpi_output['neopixel_brightness'] - address = rpi_output['microcontroller_address'] - - neopixel_dirrect = rpi_output['output_type'] == 'neopixel_direct' - - self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, green, - blue, address, neopixel_dirrect, identifier) - - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/clear-gpio", methods=["POST"]) - @restricted_access - def clear_gpio_mode(self): - GPIO.cleanup() - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/update", methods=["POST"]) - @restricted_access - def update_ui_requested(self): - self.update_ui() - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/shell/", methods=["POST"]) - @restricted_access - def send_shell_command(self, identifier): - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() - - command = rpi_output['shell_script'] - self.shell_command(command) - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/gcode/", methods=["POST"]) - @restricted_access - def requested_gcode_command(self, identifier): - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() - self.send_gcode_command(rpi_output['gcode']) - return make_response('', 204) - - - - - - """ - DEPRECATION - This API will be deprecated in a future version - """ - - # ~~ Blueprintplugin mixin - @octoprint.plugin.BlueprintPlugin.route("/setEnclosureTempHum", methods=["GET"]) - def set_enclosure_temp_humidity_old(self): - set_value = self.to_float(request.values["set_temperature"]) - index_id = self.to_int(request.values["index_id"]) - - for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == index_id]: - temp_hum_control['temp_ctr_set_value'] = set_value - - self.handle_temp_hum_control() - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/clearGPIOMode", methods=["GET"]) - def clear_gpio_mode_old(self): - GPIO.cleanup() - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/updateUI", methods=["GET"]) - def update_ui_requested_old(self): - self.update_ui() - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/getOutputStatus", methods=["GET"]) - def get_output_status_old(self): - gpio_status = [] - for rpi_output in self.rpi_outputs: - if rpi_output['output_type'] == 'regular': - pin = self.to_int(rpi_output['gpio_pin']) - ActiveLow = rpi_output['active_low'] - if rpi_output['gpio_i2c_enabled']: - val = self.gpio_i2c_input(rpi_output, rpi_output['active_low']) - else: - val = PinState_Boolean(pin, ActiveLow) - val2 = PinState_Human(pin, ActiveLow) - index = self.to_int(rpi_output['index_id']) - gpio_status.append(dict(index_id=index, status=val, State=val2)) - return Response(json.dumps(gpio_status), mimetype='application/json') - - @octoprint.plugin.BlueprintPlugin.route("/setIO", methods=["GET"]) - def set_io_old(self): - index = request.values["index_id"] - value = True if request.values["status"] == 'true' else False - for rpi_output in self.rpi_outputs: - if self.to_int(index) == self.to_int(rpi_output['index_id']): - val = (not value) if rpi_output['active_low'] else value - if rpi_output['gpio_i2c_enabled']: - self.gpio_i2c_write(rpi_output, val) - else: - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/sendShellCommand", methods=["GET"]) - def send_shell_command_old(self): - output_index = self.to_int(request.values["index_id"]) - - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == output_index].pop() - - command = rpi_output['shell_script'] - self.shell_command(command) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setAutoStartUp", methods=["GET"]) - def set_auto_startup_old(self): - index = request.values["index_id"] - value = True if request.values["status"] == 'true' else False - - if not value: - suffix = 'auto_startup' - queue_id = '{0!s}_{1!s}'.format(index, suffix) - self.stop_queue_item(queue_id) - for output in self.rpi_outputs: - if self.to_int(index) == self.to_int(output['index_id']): - output['auto_startup'] = value - self._logger.info("Setting auto startup for output %s to : %s", index, value) - self._settings.set(["rpi_outputs"], self.rpi_outputs) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setAutoShutdown", methods=["GET"]) - def set_auto_shutdown_old(self): - index = request.values["index_id"] - value = True if request.values["status"] == 'true' else False - - if not value: - suffix = 'auto_shutdown' - queue_id = '{0!s}_{1!s}'.format(index, suffix) - self.stop_queue_item(queue_id) - - for output in self.rpi_outputs: - if self.to_int(index) == self.to_int(output['index_id']): - output['auto_shutdown'] = value - self._logger.info("Setting auto shutdown for output %s to : %s", index, value) - self._settings.set(["rpi_outputs"], self.rpi_outputs) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setFilamentSensor", methods=["GET"]) - def set_filament_sensor_old(self): - index = request.values["index_id"] - value = True if request.values["status"] == 'true' else False - for sensor in self.rpi_inputs: - if self.to_int(index) == self.to_int(sensor['index_id']): - sensor['filament_sensor_enabled'] = value - self._logger.info("Setting filament sensor for input %s to : %s", index, value) - self._settings.set(["rpi_inputs"], self.rpi_inputs) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setPWM", methods=["GET"]) - def set_pwm_old(self): - set_value = self.to_int(request.values["new_duty_cycle"]) - index_id = self.to_int(request.values["index_id"]) - for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == index_id]: - rpi_output['duty_cycle'] = set_value - rpi_output['new_duty_cycle'] = "" - gpio = self.to_int(rpi_output['gpio_pin']) - self.write_pwm(gpio, set_value) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/sendGcodeCommand", methods=["GET"]) - def requested_gcode_command_old(self): - gpio_index = self.to_int(request.values["index_id"]) - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == gpio_index].pop() - self.send_gcode_command(rpi_output['gcode']) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setNeopixel", methods=["GET"]) - def set_neopixel_old(self): - """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" - gpio_index = self.to_int(request.values["index_id"]) - red = request.values["red"] - green = request.values["green"] - blue = request.values["blue"] - for rpi_output in self.rpi_outputs: - if gpio_index == self.to_int(rpi_output['index_id']): - led_count = rpi_output['neopixel_count'] - led_brightness = rpi_output['neopixel_brightness'] - address = rpi_output['microcontroller_address'] - - neopixel_dirrect = rpi_output['output_type'] == 'neopixel_direct' - - self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, green, - blue, address, neopixel_dirrect, gpio_index) - - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setLedstripColor", methods=["GET"]) - def set_ledstrip_color_old(self): - """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" - gpio_index = self.to_int(request.values["index_id"]) - rgb = request.values["rgb"] - for rpi_output in self.rpi_outputs: - if gpio_index == self.to_int(rpi_output['index_id']): - self.ledstrip_set_rgb(rpi_output, rgb) - - return jsonify(success=True) - - # DEPREACTION END - - - # GPIO over i2c - - def gpio_i2c_input(self, output, active_low=None): - state = False - try: - i2cbus = self.to_int(output['gpio_i2c_bus']) - i2caddr = self.to_int(output['gpio_i2c_address']) - i2creg = self.to_int(output['gpio_i2c_register_status']) - data_on = self.to_int(output['gpio_i2c_data_on']) - - with SMBus(i2cbus) as bus: - data = bus.read_i2c_block_data(i2caddr, i2creg, 1) - if data[0] == data_on: - state = True - - self._logger.debug("gpio_i2c_input(i2cbus=%s, i2caddr=%s, i2creg=%s, data_on=%s) data == %s", - i2cbus, i2caddr, i2creg, data_on, data) - - if active_low is None and state: return state - - except Exception as ex: - self.log_error(ex) - - if active_low and not state: return True - if not active_low and state: return True - return False - - def gpio_i2c_write(self, output, state, queue_id=None): - try: - i2cbus = self.to_int(output['gpio_i2c_bus']) - i2caddr = self.to_int(output['gpio_i2c_address']) - i2creg = self.to_int(output['gpio_i2c_register']) - data_on = self.to_int(output['gpio_i2c_data_on']) - data_off = self.to_int(output['gpio_i2c_data_off']) - - with SMBus(i2cbus) as bus: - data = [] - if state: - data.append(data_on) - else: - data.append(data_off) - - bus.write_i2c_block_data(i2caddr, i2creg, data) - - if queue_id is not None: - self._logger.debug("Running scheduled queue id %s", queue_id) - self._logger.debug("Writing on GPIO (i2c): %s/%s value %s", output['gpio_i2c_address'], output['gpio_i2c_register'], state) - self.update_ui() - if queue_id is not None: - self.stop_queue_item(queue_id) - - except Exception as ex: - template = "An exception of type {0} occurred on {1} when writing on i2c address {2}, reg {3}. Arguments:\n{4!r}" - message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, output['gpio_i2c_address'], output['gpio_i2c_register'], ex.args) - self._logger.warn(message) - pass - - - def send_neopixel_command(self, led_pin, led_count, led_brightness, red, green, blue, address, neopixel_dirrect, - index_id, queue_id=None): - """Send neopixel command - Arguments: - led_pin {int} -- GPIO number - ledCount {int} -- number of LEDS - ledBrightness {int} -- brightness from 0 to 255 - red {int} -- red value from 0 to 255 - green {int} -- green value from 0 to 255 - blue {int} -- blue value from 0 to 255 - address {int} -- i2c address from microcontroller - """ - - try: - - for rpi_output in self.rpi_outputs: - if self.to_int(index_id) == self.to_int(rpi_output['index_id']): - rpi_output['neopixel_color'] = 'rgb({0!s},{1!s},{2!s})'.format(red, green, blue) - - if address == '': - address = 0 - - if neopixel_dirrect: - script = os.path.dirname(os.path.realpath(__file__)) + "/neopixel_direct.py " - else: - script = os.path.dirname(os.path.realpath(__file__)) + "/neopixel_indirect.py " - - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - - cmd = sudo_str + "python " + script + str(led_pin) + " " + str(led_count) + " " + str( - led_brightness) + " " + str(red) + " " + str(green) + " " + str(blue) + " " - - if neopixel_dirrect: - dma = self._settings.get(["neopixel_dma"]) or 10 - cmd = cmd + str(dma) - else: - cmd = cmd + str(address) - - if queue_id is not None: - self._logger.debug("running scheduled queue id %s", queue_id) - self._logger.debug("Sending neopixel cmd: %s", cmd) - Popen(cmd, shell=True) - if queue_id is not None: - self.stop_queue_item(queue_id) - except Exception as ex: - self.log_error(ex) - - def check_enclosure_temp(self): - try: - sensor_data = [] - for sensor in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): - temp, hum = self.get_sensor_data(sensor) - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Sensor %s Temperature: %s humidity %s", sensor['label'], temp, hum) - if temp is not None and hum is not None: - sensor["temp_sensor_temp"] = temp - sensor["temp_sensor_humidity"] = hum - sensor_data.append(dict(index_id=sensor['index_id'], temperature=temp, humidity=hum)) - self.temperature_sensor_data = sensor_data - self.handle_temp_hum_control() - self.handle_temperature_events() - self.handle_pwm_linked_temperature() - self.update_ui() - except Exception as ex: - self.log_error(ex) - - def toggle_output(self, output_index, first_run=False): - for output in [item for item in self.rpi_outputs if item['index_id'] == output_index]: - gpio_pin = self.to_int(output['gpio_pin']) - index_id = self.to_int(output['index_id']) - - if output['output_type'] == 'regular': - if output['gpio_i2c_enabled']: - current_value = self.gpio_i2c_input(output) - else: - if first_run: - current_value = False - else: - current_value = (not GPIO.input(gpio_pin)) if output['active_low'] else GPIO.input(gpio_pin) - - if current_value: - time_delay = self.to_int(output['toggle_timer_off']) - else: - time_delay = self.to_int(output['toggle_timer_on']) - - if not self.print_complete: - if output['gpio_i2c_enabled']: - self.gpio_i2c_write(output, not current_value) - else: - self.write_gpio(gpio_pin, not current_value) - thread = threading.Timer(time_delay, self.toggle_output, args=[index_id]) - thread.start() - else: - off_value = True if output['active_low'] else False - if output['gpio_i2c_enabled']: - self.gpio_i2c_write(output, off_value) - else: - self.write_gpio(gpio_pin, off_value) - self.update_ui_outputs() - return - - if output['output_type'] == 'pwm': - for pwm in self.pwm_instances: - if gpio_pin in pwm: - if first_run: - current_pwm_value = 0 - else: - if 'duty_cycle' in pwm: - current_pwm_value = pwm['duty_cycle'] - current_pwm_value = self.to_int(current_pwm_value) - else: - current_pwm_value = 0 - - if not current_pwm_value == 0: - time_delay = self.to_int(output['toggle_timer_off']) - write_value = 0 - else: - time_delay = self.to_int(output['toggle_timer_on']) - write_value = self.to_int(output['default_duty_cycle']) - - if not self.print_complete: - self.write_pwm(gpio_pin, write_value) - thread = threading.Timer(time_delay, self.toggle_output, args=[index_id]) - thread.start() - else: - self.write_pwm(self.to_int(output['gpio_pin']), 0) - self.update_ui_outputs() - return - - def update_ui(self): - self.update_ui_outputs() - self.update_ui_current_temperature() - self.update_ui_set_temperature() - self.update_ui_inputs() - - def update_ui_current_temperature(self): - self._plugin_manager.send_plugin_message(self._identifier, dict(sensor_data=self.temperature_sensor_data)) - - def update_ui_set_temperature(self): - result = [] - for temp_crt_output in list(filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): - set_temperature = self.to_float(temp_crt_output['temp_ctr_set_value']) - result.append(dict(index_id=temp_crt_output['index_id'], set_temperature=set_temperature)) - result.append(set_temperature) - self._plugin_manager.send_plugin_message(self._identifier, dict(set_temperature=result)) - - def stop_queue_item(self, queue_id): - old_list = self.event_queue - self._logger.debug("Stopping queue id %s...", queue_id) - for task in self.event_queue: - self._logger.debug("Queue id found...") - if task['queue_id'] == queue_id: - task['thread'].cancel() - self.event_queue.remove(task) - self._logger.debug("Queue id stopped and removed from list...") - self._logger.debug("Old queue list: %s", old_list) - self._logger.debug("New queue list: %s", self.event_queue) - - def update_ui_outputs(self): - try: - regular_status = [] - pwm_status = [] - neopixel_status = [] - temp_control_status = [] - for output in self.rpi_outputs: - index = self.to_int(output['index_id']) - pin = self.to_int(output['gpio_pin']) - startup = output['auto_startup'] - shutdown = output['auto_shutdown'] - - if output['output_type'] == 'regular': - if output['gpio_i2c_enabled']: - val = self.gpio_i2c_input(output) - else: - val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) - regular_status.append( - dict(index_id=index, status=val, auto_startup=startup, auto_shutdown=shutdown)) - if output['output_type'] == 'temp_hum_control': - if output['gpio_i2c_enabled']: - val = self.gpio_i2c_input(output) - else: - val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) - temp_control_status.append( - dict(index_id=index, status=val, auto_startup=startup, auto_shutdown=shutdown)) - if output['output_type'] == 'neopixel_indirect' or output['output_type'] == 'neopixel_direct': - val = output['neopixel_color'] - neopixel_status.append( - dict(index_id=index, color=val, auto_startup=startup, auto_shutdown=shutdown)) - if output['output_type'] == 'pwm': - for pwm in self.pwm_instances: - if pin in pwm: - if 'duty_cycle' in pwm: - pwm_val = pwm['duty_cycle'] - val = self.to_int(pwm_val) - else: - val = 0 - pwm_status.append( - dict(index_id=index, pwm_value=val, auto_startup=startup, auto_shutdown=shutdown)) - self._plugin_manager.send_plugin_message(self._identifier, - dict(rpi_output_regular=regular_status, rpi_output_pwm=pwm_status, - rpi_output_neopixel=neopixel_status, - rpi_output_temp_hum_ctrl=temp_control_status)) - except Exception as ex: - self.log_error(ex) - - def update_ui_inputs(self): - try: - sensor_status = [] - for sensor in self.rpi_inputs: - if sensor['input_type'] == 'gpio' and sensor['action_type'] == 'printer_control' and sensor[ - 'printer_action'] == 'filament': - index = self.to_int(sensor['index_id']) - value = sensor['filament_sensor_enabled'] - sensor_status.append(dict(index_id=index, filament_sensor_enabled=value)) - self._plugin_manager.send_plugin_message(self._identifier, dict(filament_sensor_status=sensor_status)) - except Exception as ex: - self.log_error(ex) - - def get_sensor_data(self, sensor): - try: - if self.development_mode: - temp, hum = self.read_dummy_temp() - else: - if sensor['temp_sensor_type'] in ["11", "22", "2302"]: - temp, hum = self.read_dht_temp(sensor['temp_sensor_type'], sensor['gpio_pin']) - elif sensor['temp_sensor_type'] == "18b20": - temp = self.read_18b20_temp(sensor['ds18b20_serial']) - hum = 0 - elif sensor['temp_sensor_type'] == "bme280": - temp, hum = self.read_bme280_temp(sensor['temp_sensor_address']) - elif sensor['temp_sensor_type'] == "am2320": - temp, hum = self.read_am2320_temp() # sensor has fixed address - elif sensor['temp_sensor_type'] == "si7021": - temp, hum = self.read_si7021_temp(sensor['temp_sensor_address'], sensor['temp_sensor_i2cbus']) - elif sensor['temp_sensor_type'] == "tmp102": - temp = self.read_tmp102_temp(sensor['temp_sensor_address']) - hum = 0 - elif sensor['temp_sensor_type'] == "max31855": - temp = self.read_max31855_temp(sensor['temp_sensor_address']) - hum = 0 - elif sensor['temp_sensor_type'] == "mcp9808": - temp = self.read_mcp_temp(sensor['temp_sensor_address']) - hum = 0 - elif sensor['temp_sensor_type'] == "temp_raw_i2c": - temp, hum = self.read_raw_i2c_temp(sensor) - elif sensor['temp_sensor_type'] == "hum_raw_i2c": - hum, temp = self.read_raw_i2c_temp(sensor) - else: - self._logger.info("temp_sensor_type no match") - temp = None - hum = None - if temp != -1 and hum != -1: - temp = round(self.to_float(temp), 1) if not sensor['use_fahrenheit'] else round( - self.to_float(temp) * 1.8 + 32, 1) - hum = round(self.to_float(hum), 1) - return temp, hum - return None, None - except Exception as ex: - self.log_error(ex) - - def handle_temperature_events(self): - for temperature_alarm in [item for item in self.rpi_outputs if item['output_type'] == 'temperature_alarm']: - set_temperature = self.to_float(temperature_alarm['alarm_set_temp']) - if int(set_temperature) is 0: - continue - linked_data = [item for item in self.temperature_sensor_data if - item['index_id'] == temperature_alarm['linked_temp_sensor']].pop() - sensor_temperature = self.to_float(linked_data['temperature']) - if set_temperature < sensor_temperature: - for rpi_controlled_output in self.rpi_outputs: - if self.to_int(temperature_alarm['controlled_io']) == self.to_int( - rpi_controlled_output['index_id']): - if rpi_controlled_output['gpio_i2c_enabled']: - val = False if temperature_alarm['controlled_io_set_value'] == 'low' else True - self.gpio_i2c_write(rpi_controlled_output, val) - else: - val = GPIO.LOW if temperature_alarm['controlled_io_set_value'] == 'low' else GPIO.HIGH - self.write_gpio(self.to_int(rpi_controlled_output['gpio_pin']), val) - for notification in self.notifications: - if notification['temperatureAction']: - msg = ("Temperature action: enclosure temperature exceed " + temperature_alarm[ - 'alarm_set_temp']) - self.send_notification(msg) - - def read_dummy_temp(self): - current_value = self.dummy_value - if current_value > 40 or current_value < 30: - self.dummy_delta = - self.dummy_delta - - return_value = current_value + self.dummy_delta - - self.dummy_value = return_value - - return return_value, return_value - - def read_raw_i2c_temp(self, sensor): - try: - i2cbus = self.to_int(sensor['temp_i2c_bus']) - i2caddr = self.to_int(sensor['temp_i2c_address']) - i2creg = self.to_int(sensor['temp_i2c_register']) - - with SMBus(i2cbus) as bus: - data = bus.read_i2c_block_data(i2caddr, i2creg, 8) - fval1 = struct.unpack('f', bytearray(data[0:4]))[0] - if fval1 != fval1: - fval1 = 0 - fval2 = struct.unpack('f', bytearray(data[4:8]))[0] - if fval2 != fval2: - fval2 = 0 - - self._logger.debug("read_raw_i2c_temp(i2cbus=%s, i2caddr=%s, i2creg=%s) data == %s (%s, %s)", - i2cbus, i2caddr, i2creg, data, fval1, fval2) - - return (fval1, fval2) - - except Exception as ex: - template = "An exception of type {0} occurred on {1} when reading on i2c address {2}, reg {3}. Arguments:\n{4!r}" - message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, i2caddr, i2creg, ex.args) - self._logger.warn(message) - return str(-1) - - def read_mcp_temp(self, address): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/mcp9808.py" - args = ["python", script, str(address)] - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature MCP9808 cmd: %s", " ".join(args)) - proc = Popen(args, stdout=PIPE) - stdout, _ = proc.communicate() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("MCP9808 result: %s", stdout) - return self.to_float(stdout.decode("utf-8").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 - - def read_dht_temp(self, sensor, pin): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/getDHTTemp.py " - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - cmd = sudo_str + "python3 " + script + str(sensor) + " " + str(pin) - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature dht cmd: %s", cmd) - stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Dht result: %s", stdout) - temp, hum = stdout.decode("utf-8").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 of the plugin.") - self.log_error(ex) - return (0, 0) - - def read_bme280_temp(self, address): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/BME280.py " - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - cmd = sudo_str + "python " + script + str(address) - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature BME280 cmd: %s", cmd) - stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("BME280 result: %s", stdout) - temp, hum = stdout.decode("utf-8").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 of the plugin.") - self.log_error(ex) - return (0, 0) - - def read_am2320_temp(self): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/AM2320.py " - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - cmd = sudo_str + "python " + script # sensor has fixed address 0x5C - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature AM2320 cmd: %s", cmd) - stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("AM2320 result: %s", stdout) - temp, hum = stdout.decode("utf-8").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 of the plugin.") - self.log_error(ex) - return (0, 0) - - def read_si7021_temp(self, address, i2cbus): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/SI7021.py " - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - cmd = sudo_str + "python " + script + str(address) + " " + str(i2cbus) - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature SI7021 cmd: %s", cmd) - stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("SI7021 result: %s", stdout) - temp, hum = stdout.decode("utf-8").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 of the plugin.") - self.log_error(ex) - return (0, 0) - - def read_18b20_temp(self, serial_number): - os.system('modprobe w1-gpio') - os.system('modprobe w1-therm') - - lines = self.read_raw_18b20_temp(serial_number) - while lines[0].strip()[-3:] != 'YES': - time.sleep(0.2) - lines = self.read_raw_18b20_temp(serial_number) - equals_pos = lines[1].find('t=') - if equals_pos != -1: - temp_string = lines[1][equals_pos + 2:] - temp_c = float(temp_string) / 1000. - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("DS18B20 result: %s", temp_c) - return '{0:0.1f}'.format(temp_c) - return 0 - - def read_raw_18b20_temp(self, serial_number): - base_dir = '/sys/bus/w1/devices/' - device_folder = glob.glob(base_dir + str(serial_number) + '*')[0] - device_file = device_folder + '/w1_slave' - device_file_result = open(device_file, 'r') - lines = device_file_result.readlines() - device_file_result.close() - return lines - - def read_tmp102_temp(self, address): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/tmp102.py" - args = ["python", script, str(address)] - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature TMP102 cmd: %s", " ".join(args)) - proc = Popen(args, stdout=PIPE) - stdout, _ = proc.communicate() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("TMP102 result: %s", stdout) - return self.to_float(stdout.decode("utf-8").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 - - def read_max31855_temp(self, address): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/max31855.py" - args = ["python", script, str(address)] - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature MAX31855 cmd: %s", " ".join(args)) - proc = Popen(args, stdout=PIPE) - stdout, _ = proc.communicate() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("MAX31855 result: %s", stdout) - return self.to_float(stdout.decode("utf-8").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 - - def handle_pwm_linked_temperature(self): - try: - for pwm_output in list(filter(lambda item: item['output_type'] == 'pwm' and item['pwm_temperature_linked'], - self.rpi_outputs)): - gpio_pin = self.to_int(pwm_output['gpio_pin']) - if self._printer.is_printing(): - index_id = self.to_int(pwm_output['index_id']) - linked_id = self.to_int(pwm_output['linked_temp_sensor']) - linked_data = self.get_linked_temp_sensor_data(linked_id) - current_temp = self.to_float(linked_data['temperature']) - - duty_a = self.to_float(pwm_output['duty_a']) - duty_b = self.to_float(pwm_output['duty_b']) - temp_a = self.to_float(pwm_output['temperature_a']) - temp_b = self.to_float(pwm_output['temperature_b']) - - try: - calculated_duty = ((current_temp - temp_a) * (duty_b - duty_a) / (temp_b - temp_a)) + duty_a - - if current_temp < temp_a: - calculated_duty = 0 - except: - calculated_duty = 0 - - self._logger.debug("Calculated duty for PWM %s is %s", index_id, calculated_duty) - elif self.print_complete: - calculated_duty = self.to_int(pwm_output['duty_cycle']) - else: - calculated_duty = 0 - - self.write_pwm(gpio_pin, self.constrain(calculated_duty, 0, 100)) - - except Exception as ex: - self.log_error(ex) - - def get_linked_temp_sensor_data(self, linked_id): - try: - linked_data = [data for data in self.temperature_sensor_data if data['index_id'] == linked_id].pop() - return linked_data - except: - self._logger.warn("No linked temperature sensor found for %s", linked_id) - return None - - def handle_temp_hum_control(self): - try: - for temp_hum_control in list( - filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): - - set_temperature = self.to_float(temp_hum_control['temp_ctr_set_value']) - temp_deadband = self.to_float(temp_hum_control['temp_ctr_deadband']) - max_temp = self.to_float(temp_hum_control['temp_ctr_max_temp']) - - linked_id = temp_hum_control['linked_temp_sensor'] - - previous_status = list(filter(lambda item: item['index_id'] == temp_hum_control['index_id'], - self.temp_hum_control_status)).pop()['status'] - - if set_temperature == 0: - current_status = False - else: - linked_data = self.get_linked_temp_sensor_data(linked_id) - - control_type = str(temp_hum_control['temp_ctr_type']) - - if control_type == 'dehumidifier': - current_value = self.to_float(linked_data['humidity']) - temp_deadband = 0 - else: - current_value = self.to_float(linked_data['temperature']) - - if control_type == 'cooler' or control_type == 'dehumidifier': - if current_value <= set_temperature and current_value >= (set_temperature - temp_deadband): - current_status = previous_status - elif current_value < set_temperature: - current_status = False - else: - current_status = True - else: - if current_value <= set_temperature and current_value >= (set_temperature - temp_deadband): - current_status = previous_status - elif current_value > set_temperature: - current_status = False - else: - current_status = True - - if control_type == 'heater' and max_temp > 0.0 and max_temp < current_value: - self._logger.debug("Maximum temperature reached for temperature control %s", - temp_hum_control['index_id']) - temp_hum_control['temp_ctr_set_value'] = 0 - current_status = False - - if current_status != previous_status: - if current_status: - self._logger.info("Turning gpio to control temperature on.") - val = False if temp_hum_control['active_low'] else True - if temp_hum_control['gpio_i2c_enabled']: - self.gpio_i2c_write(temp_hum_control, val) - else: - self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) - else: - index_id = temp_hum_control['index_id'] - if index_id in self.waiting_temperature: - self.waiting_temperature.remove(index_id) - - if not self.waiting_temperature and self._printer.is_paused(): - self._printer.resume_print() - - self._logger.info("Turning gpio to control temperature off.") - val = True if temp_hum_control['active_low'] else False - if temp_hum_control['gpio_i2c_enabled']: - self.gpio_i2c_write(temp_hum_control, val) - else: - self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) - for control_status in self.temp_hum_control_status: - if control_status['index_id'] == temp_hum_control['index_id']: - control_status['status'] = current_status - except Exception as ex: - self.log_error(ex) - - def log_error(self, ex): - template = "An exception of type {0} occurred on {1}. Arguments:\n{2!r}" - message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, ex.args) - self._logger.warn(message, exc_info = True) - - def setup_gpio(self): - try: - current_mode = GPIO.getmode() - set_mode = GPIO.BOARD if self._settings.get(["use_board_pin_number"]) else GPIO.BCM - if current_mode is None: - outputs = list(filter( - lambda item: (item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ - 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct') and - item['gpio_i2c_enabled'] == False, - self.rpi_outputs)) - inputs = list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)) - gpios = outputs + inputs - if gpios: - GPIO.setmode(set_mode) - tempstr = "BOARD" if set_mode == GPIO.BOARD else "BCM" - self._logger.info("Setting GPIO mode to %s", tempstr) - elif current_mode != set_mode: - GPIO.setmode(current_mode) - tempstr = "BOARD" if current_mode == GPIO.BOARD else "BCM" - self._settings.set(["use_board_pin_number"], True if current_mode == GPIO.BOARD else False) - warn_msg = "GPIO mode was configured before, GPIO mode will be forced to use: " + tempstr + " as pin numbers. Please update GPIO accordingly!" - self._logger.info(warn_msg) - self._plugin_manager.send_plugin_message(self._identifier, - dict(is_msg=True, msg=warn_msg, msg_type="error")) - GPIO.setwarnings(False) - except Exception as ex: - self.log_error(ex) - - def clear_gpio(self): - try: - - for gpio_out in list(filter( - lambda item: (item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ - 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct') and - item['gpio_i2c_enabled'] == False, - self.rpi_outputs)): - gpio_pin = self.to_int(gpio_out['gpio_pin']) - if gpio_pin not in self.rpi_outputs_not_changed: - GPIO.cleanup(gpio_pin) - - for gpio_in in list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)): - try: - GPIO.remove_event_detect(self.to_int(gpio_in['gpio_pin'])) - except: - pass - GPIO.cleanup(self.to_int(gpio_in['gpio_pin'])) - except Exception as ex: - self.log_error(ex) - - def clear_channel(self, channel): - try: - GPIO.cleanup(self.to_int(channel)) - self._logger.debug("Clearing channel %s", channel) - except Exception as ex: - self.log_error(ex) - - def generate_temp_hum_control_status(self): - status = [] - for temp_hum_control in list(filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): - status.append(dict(index_id=temp_hum_control['index_id'], status=False)) - self.temp_hum_control_status = status - - def configure_gpio(self): - try: - - for gpio_out in list( - filter(lambda item: (item['output_type'] == 'regular' or item['output_type'] == 'temp_hum_control') and - item['gpio_i2c_enabled'] == False, - self.rpi_outputs)): - initial_value = GPIO.HIGH if gpio_out['active_low'] else GPIO.LOW - pin = self.to_int(gpio_out['gpio_pin']) - if pin not in self.rpi_outputs_not_changed: - self._logger.info("Setting GPIO pin %s as OUTPUT with initial value: %s", pin, initial_value) - GPIO.setup(pin, GPIO.OUT, initial=initial_value) - for gpio_out_pwm in list(filter(lambda item: item['output_type'] == 'pwm', self.rpi_outputs)): - pin = self.to_int(gpio_out_pwm['gpio_pin']) - self._logger.info("Setting GPIO pin %s as PWM", pin) - for pwm in (pwm_dict for pwm_dict in self.pwm_instances if pin in pwm_dict): - self.pwm_instances.remove(pwm) - self.clear_channel(pin) - GPIO.setup(pin, GPIO.OUT) - pwm_instance = GPIO.PWM(pin, self.to_int(gpio_out_pwm['pwm_frequency'])) - self._logger.info("starting PWM on pin %s", pin) - pwm_instance.start(0) - self.pwm_instances.append({pin: pwm_instance}) - for gpio_out_neopixel in list( - filter(lambda item: item['output_type'] == 'neopixel_direct', self.rpi_outputs)): - pin = self.to_int(gpio_out_neopixel['gpio_pin']) - self.clear_channel(pin) - - for rpi_input in list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)): - gpio_pin = self.to_int(rpi_input['gpio_pin']) - pull_resistor = GPIO.PUD_UP if rpi_input['input_pull_resistor'] == 'input_pull_up' else GPIO.PUD_DOWN - GPIO.setup(gpio_pin, GPIO.IN, pull_resistor) - edge = GPIO.RISING if rpi_input['edge'] == 'rise' else GPIO.FALLING - - inputs_same_gpio = list( - [r_inp for r_inp in self.rpi_inputs if self.to_int(r_inp['gpio_pin']) == gpio_pin]) - - if len(inputs_same_gpio) > 1: - GPIO.remove_event_detect(gpio_pin) - for other_input in inputs_same_gpio: - if other_input['edge'] is not edge: - edge = GPIO.BOTH - - if rpi_input['action_type'] == 'output_control': - self._logger.info("Adding GPIO event detect on pin %s with edge: %s", gpio_pin, edge) - GPIO.add_event_detect(gpio_pin, edge, callback=self.handle_gpio_control, bouncetime=200) - if (rpi_input['action_type'] == 'printer_control' and rpi_input['printer_action'] != 'filament'): - GPIO.add_event_detect(gpio_pin, edge, callback=self.handle_printer_action, bouncetime=200) - self._logger.info("Adding PRINTER CONTROL event detect on pin %s with edge: %s", gpio_pin, edge) - - for rpi_input in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): - gpio_pin = self.to_int(rpi_input['gpio_pin']) - if rpi_input['input_pull_resistor'] == 'input_pull_up': - pull_resistor = GPIO.PUD_UP - elif rpi_input['input_pull_resistor'] == 'input_pull_down': - pull_resistor = GPIO.PUD_DOWN - else: - pull_resistor = GPIO.PUD_OFF - GPIO.setup(gpio_pin, GPIO.IN, pull_up_down=pull_resistor) - except Exception as ex: - self.log_error(ex) - - def handle_filamment_detection(self, channel): - try: - for filament_sensor in list(filter( - lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ - 'printer_action'] == 'filament' and self.to_int(item['gpio_pin']) == self.to_int(channel), - self.rpi_inputs)): - if ((filament_sensor['edge'] == 'fall') ^ (GPIO.input(self.to_int(filament_sensor['gpio_pin']))) and - filament_sensor['filament_sensor_enabled']): - last_detected_time = list(filter(lambda item: item['index_id'] == filament_sensor['index_id'], - self.last_filament_end_detected)).pop()['time'] - time_now = time.time() - time_difference = self.to_int(time_now - last_detected_time) - time_out_value = self.to_int(filament_sensor['filament_sensor_timeout']) - if time_difference > time_out_value: - self._logger.info("Detected end of filament.") - for item in self.last_filament_end_detected: - if item['index_id'] == filament_sensor['index_id']: - item['time'] = time_now - for line in self._settings.get(["filament_sensor_gcode"]).split('\n'): - if line: - self._printer.commands(line.strip()) - self._logger.info("Sending GCODE command: %s", line.strip()) - time.sleep(0.2) - for notification in self.notifications: - if notification['filamentChange']: - msg = "Filament change action caused by sensor: " + str(filament_sensor['label']) - self.send_notification(msg) - else: - self._logger.info("Prevented end of filament detection, filament sensor timeout not elapsed.") - except Exception as ex: - self.log_error(ex) - - def start_filament_detection(self): - self.stop_filament_detection() - try: - for filament_sensor in list(filter( - lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ - 'printer_action'] == 'filament', self.rpi_inputs)): - edge = GPIO.RISING if filament_sensor['edge'] == 'rise' else GPIO.FALLING - if GPIO.input(self.to_int(filament_sensor['gpio_pin'])) == (edge == GPIO.RISING): - self._printer.pause_print() - self._logger.info("Started printing with no filament.") - else: - self.last_filament_end_detected.append(dict(index_id=filament_sensor['index_id'], time=0)) - self._logger.info("Adding GPIO event detect on pin %s with edge: %s", filament_sensor['gpio_pin'], - edge) - GPIO.add_event_detect(self.to_int(filament_sensor['gpio_pin']), edge, - callback=self.handle_filamment_detection, bouncetime=200) - except Exception as ex: - self.log_error(ex) - - def stop_filament_detection(self): - try: - self.last_filament_end_detected = [] - for filament_sensor in list(filter( - lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ - 'printer_action'] == 'filament', self.rpi_inputs)): - GPIO.remove_event_detect(self.to_int(filament_sensor['gpio_pin'])) - except Exception as ex: - self.log_error(ex) - - def cancel_all_events_on_queue(self): - for task in self.event_queue: - try: - task['thread'].cancel() - except: - self._logger.warn("Failed to stop task %s.", task) - pass - - def handle_initial_gpio_control(self): - try: - for rpi_input in list( - filter(lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'output_control', - self.rpi_inputs)): - gpio_pin = self.to_int(rpi_input['gpio_pin']) - controlled_io = self.to_int(rpi_input['controlled_io']) - if (rpi_input['edge'] == 'fall') ^ GPIO.input(gpio_pin): - rpi_output = [r_out for r_out in self.rpi_outputs if - self.to_int(r_out['index_id']) == controlled_io].pop() - if rpi_output['output_type'] == 'regular': - if rpi_output['gpio_i2c_enabled']: - val = False if rpi_input['controlled_io_set_value'] == 'low' else True - self.gpio_i2c_write(rpi_output, val) - else: - val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) - except Exception as ex: - self.log_error(ex) - pass - - def shell_command(self, command): - try: - stdout = (Popen(command, shell=True, stdout=PIPE).stdout).read() - self._plugin_manager.send_plugin_message(self._identifier, - dict(is_msg=True, msg=stdout, msg_type="success")) - except Exception as ex: - self.log_error(ex) - self._plugin_manager.send_plugin_message(self._identifier, - dict(is_msg=True, msg="Could not execute shell script", msg_type="error")) - - def handle_gpio_control(self, channel): - - try: - self._logger.debug("GPIO event triggered on channel %s", channel) - for rpi_input in list( - filter(lambda item: self.to_int(item['gpio_pin']) == self.to_int(channel), self.rpi_inputs)): - gpio_pin = self.to_int(rpi_input['gpio_pin']) - controlled_io = self.to_int(rpi_input['controlled_io']) - if (rpi_input['edge'] == 'fall') ^ GPIO.input(gpio_pin): - rpi_output = [r_out for r_out in self.rpi_outputs if - self.to_int(r_out['index_id']) == controlled_io].pop() - if rpi_output['output_type'] == 'regular': - if rpi_input['controlled_io_set_value'] == 'toggle': - val = GPIO.LOW if GPIO.input( - self.to_int(rpi_output['gpio_pin'])) == GPIO.HIGH else GPIO.HIGH - else: - val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH - if rpi_output['gpio_i2c_enabled']: - self.gpio_i2c_write(rpi_output, val) - else: - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) - for notification in self.notifications: - if notification['gpioAction']: - msg = "GPIO control action caused by input " + str( - rpi_input['label']) + ". Setting GPIO" + str( - rpi_input['controlled_io']) + " to: " + str(rpi_input['controlled_io_set_value']) - self.send_notification(msg) - if rpi_output['output_type'] == 'gcode_output': - self.send_gcode_command(rpi_output['gcode']) - for notification in self.notifications: - if notification['gpioAction']: - msg = "GPIO control action caused by input " + str( - rpi_input['label']) + ". Sending GCODE command" - self.send_notification(msg) - if rpi_output['output_type'] == 'shell_output': - command = rpi_output['shell_script'] - self.shell_command(command) - except Exception as ex: - self.log_error(ex) - pass - - def send_gcode_command(self, command): - for line in command.split('\n'): - if line: - self._printer.commands(line.strip()) - self._logger.info("Sending GCODE command: %s", line.strip()) - time.sleep(0.2) - - def handle_printer_action(self, channel): - try: - for rpi_input in self.rpi_inputs: - if (channel == self.to_int(rpi_input['gpio_pin']) and rpi_input[ - 'action_type'] == 'printer_control' and ( - (rpi_input['edge'] == 'fall') ^ GPIO.input(self.to_int(rpi_input['gpio_pin'])))): - if rpi_input['printer_action'] == 'resume': - self._logger.info("Printer action resume.") - self._printer.resume_print() - elif rpi_input['printer_action'] == 'pause': - self._logger.info("Printer action pause.") - self._printer.pause_print() - elif rpi_input['printer_action'] == 'cancel': - self._logger.info("Printer action cancel.") - self._printer.cancel_print() - elif rpi_input['printer_action'] == 'toggle': - self._logger.info("Printer action toggle.") - if self._printer.is_operational(): - self._printer.toggle_pause_print() - else: - self._printer.connect() - elif rpi_input['printer_action'] == 'start': - self._logger.info("Printer action start.") - self._printer.start_print() - elif rpi_input['printer_action'] == 'toggle_job': - self._logger.info("Printer action toggle_job.") - if self._printer.is_operational(): - if self._printer.is_printing(): - self._printer.cancel_print() - elif self._printer.is_ready(): - self._printer.start_print() - else: - self._printer.connect() - elif rpi_input['printer_action'] == 'stop_temp_hum_control': - self._logger.info("Printer action stopping temperature control.") - for rpi_output in self.rpi_outputs: - if rpi_output['auto_shutdown'] and rpi_output['output_type'] == 'temp_hum_control': - rpi_output['temp_ctr_set_value'] = 0 - self.handle_temp_hum_control() - for notification in self.notifications: - if notification['printer_action']: - msg = "Printer action: " + rpi_input['printer_action'] + " caused by input: " + str( - rpi_input['label']) - self.send_notification(msg) - except Exception as ex: - self.log_error(ex) - pass - - def write_gpio(self, gpio, value, queue_id=None): - try: - GPIO.output(gpio, value) - if queue_id is not None: - self._logger.debug("Running scheduled queue id %s", queue_id) - self._logger.debug("Writing on GPIO: %s value %s", gpio, value) - self.update_ui() - if queue_id is not None: - self.stop_queue_item(queue_id) - except Exception as ex: - template = "An exception of type {0} occurred on {1} when writing on pin {2}. Arguments:\n{3!r}" - message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, gpio, ex.args) - self._logger.warn(message) - pass - - def write_pwm(self, gpio, pwm_value, queue_id=None): - try: - if queue_id is not None: - self._logger.debug("running scheduled queue id %s", queue_id) - for pwm in self.pwm_instances: - if gpio in pwm: - pwm_object = pwm[gpio] - old_pwm_value = pwm['duty_cycle'] if 'duty_cycle' in pwm else -1 - if not self.to_int(old_pwm_value) == self.to_int(pwm_value): - pwm['duty_cycle'] = pwm_value - pwm_object.start(pwm_value) #should be changed back to pwm_object.ChangeDutyCycle() but this - # was causing errors. - self._logger.debug("Writing PWM on gpio: %s value %s", gpio, pwm_value) - self.update_ui() - if queue_id is not None: - self.stop_queue_item(queue_id) - break - except Exception as ex: - self.log_error(ex) - pass - - def get_output_list(self): - result = [] - for rpi_output in self.rpi_outputs: - if rpi_output['output_type'] == 'regular': - result.append(self.to_int(rpi_output['gpio_pin'])) - return result - - def send_notification(self, message): - try: - provider = self._settings.get(["notification_provider"]) - if provider == 'ifttt': - event = self._settings.get(["notification_event_name"]) - api_key = self._settings.get(["notification_api_key"]) - self._logger.debug("Sending notification to: %s with msg: %s with key: %s", provider, message, - api_key) - try: - res = self.ifttt_notification(message, event, api_key) - except requests.exceptions.ConnectionError: - self._logger.info("Error: Could not connect to IFTTT") - except requests.exceptions.HTTPError: - self._logger.info("Error: Received invalid response") - except requests.exceptions.Timeout: - self._logger.info("Error: Request timed out") - except requests.exceptions.TooManyRedirects: - self._logger.info("Error: Too many redirects") - except requests.exceptions.RequestException as reqe: - self._logger.info("Error: {e}".format(e=reqe)) - if res.status_code != requests.codes['ok']: - try: - j = res.json() - except ValueError: - self._logger.info('Error: Could not parse server response. Event not sent') - for err in j['errors']: - self._logger.info('Error: {}'.format(err['message'])) - except Exception as ex: - self.log_error(ex) - pass - - def ifttt_notification(self, message, event, api_key): - url = "https://maker.ifttt.com/trigger/{e}/with/key/{k}/".format(e=event, k=api_key) - payload = {'value1': message} - return requests.post(url, data=payload) - - # ~~ EventPlugin mixin - def on_event(self, event, payload): - self._logger.info("Event Logging Test: %s", event) - if event == Events.CONNECTED: - self.update_ui() - - if event == Events.CLIENT_OPENED: - self.update_ui() - - if event == Events.PRINT_RESUMED: - self.start_filament_detection() - - if event == Events.PRINT_STARTED: - self.print_complete = False - self.cancel_all_events_on_queue() - self.event_queue = [] - self.start_filament_detection() - for rpi_output in self.rpi_outputs: - if rpi_output['auto_startup']: - delay_seconds = self.get_startup_delay_from_output(rpi_output) - self.schedule_auto_startup_outputs(rpi_output, delay_seconds) - if rpi_output['toggle_timer']: - if rpi_output['output_type'] == 'regular' or rpi_output['output_type'] == 'pwm': - self.toggle_output(rpi_output['index_id'], True) - if self.is_hour(rpi_output['shutdown_time']): - shutdown_delay_seconds = self.get_shutdown_delay_from_output(rpi_output) - self.schedule_auto_shutdown_outputs(rpi_output, shutdown_delay_seconds) - self.run_tasks() - self.update_ui() - - elif event == Events.PRINT_DONE: - self.stop_filament_detection() - self.print_complete = True - for rpi_output in self.rpi_outputs: - shutdown_time = rpi_output['shutdown_time'] - if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: - rpi_output['duty_cycle'] = rpi_output['default_duty_cycle'] - if rpi_output['auto_shutdown'] and not self.is_hour(shutdown_time): - delay_seconds = self.to_float(shutdown_time) - self.schedule_auto_shutdown_outputs(rpi_output, delay_seconds) - self.run_tasks() - self.update_ui() - - elif event in (Events.PRINT_CANCELLED, Events.PRINT_FAILED): - self.stop_filament_detection() - self.cancel_all_events_on_queue() - self.event_queue = [] - for rpi_output in self.rpi_outputs: - if rpi_output['shutdown_on_failed']: - shutdown_time = rpi_output['shutdown_time'] - if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: - rpi_output['duty_cycle'] = rpi_output['default_duty_cycle'] - if rpi_output['auto_shutdown'] and not self.is_hour(shutdown_time): - delay_seconds = self.to_float(shutdown_time) - self.schedule_auto_shutdown_outputs(rpi_output, delay_seconds) - if rpi_output['output_type'] == 'temp_hum_control': - rpi_output['temp_ctr_set_value'] = 0 - self.run_tasks() - - if event == Events.PRINT_DONE: - for notification in self.notifications: - if notification['printFinish']: - file_name = os.path.basename(payload["path"]) - elapsed_time_in_seconds = payload["time"] - elapsed_time = octoprint.util.get_formatted_timedelta(timedelta(seconds=elapsed_time_in_seconds)) - msg = "Print job finished: " + file_name + "finished printing in " + file_name, elapsed_time - self.send_notification(msg) - - if event in (Events.ERROR, Events.DISCONNECTED): - self._logger.info("Detected Error or Disconnect in %s will call listeners for shutdown_on_error!", event) - for rpi_output in self.rpi_outputs: - if rpi_output['shutdown_on_error']: - self._logger.debug("Schedule shutdown for: %s", rpi_output["index_id"]) - self.schedule_auto_shutdown_outputs(rpi_output, 0) - self.run_tasks() - - if event == Events.PRINTER_STATE_CHANGED: - if "error" in payload["state_string"].lower(): - self._logger.info("Detected Error in %s id: %s state: %s will call listeners for shutdown_on_error!", event, payload["state_id"], payload["state_string"]) - for rpi_output in self.rpi_outputs: - if rpi_output['shutdown_on_error']: - self._logger.debug("Schedule shutdown for: %s", rpi_output["index_id"]) - self.schedule_auto_shutdown_outputs(rpi_output, 0) - self.run_tasks() - - def run_tasks(self): - for task in self.event_queue: - if not task['thread'].is_alive(): - task['thread'].start() - - def schedule_auto_shutdown_outputs(self, rpi_output, shutdown_delay_seconds): - sufix = 'auto_shutdown' - if rpi_output['output_type'] == 'regular': - self._logger.debug("schedule_auto_shutdown_outputs output is of type regular") - value = True if rpi_output['active_low'] else False - self.add_regular_output_to_queue(shutdown_delay_seconds, rpi_output, value, sufix) - if rpi_output['output_type'] == 'ledstrip': - self.ledstrip_set_rgb(rpi_output) - if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: - value = 0 - self.add_pwm_output_to_queue(shutdown_delay_seconds, rpi_output, value, sufix) - if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: - self.schedule_pwm_duty_on_queue(shutdown_delay_seconds, rpi_output, 0, sufix) - if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): - self.add_neopixel_output_to_queue(rpi_output, shutdown_delay_seconds, 0, 0, 0, sufix) - if rpi_output['output_type'] == 'temp_hum_control': - value = 0 - self.add_temperature_output_temperature_queue(shutdown_delay_seconds, rpi_output, value, sufix) - self._logger.debug("Events scheduled to run %s", self.event_queue) - - def ledstrip_set_rgb(self, rpi_output, rgb=None): - clk = rpi_output["ledstrip_gpio_clk"] - data = rpi_output["ledstrip_gpio_dat"] - if clk is not None and data is not None: - ledstrip = LEDStrip(self.to_int(clk), self.to_int(data)) - if rgb is None: - red, green, blue = self.get_color_from_rgb(rpi_output['default_ledstrip_color']) - else: - red, green, blue = self.get_color_from_rgb(rgb) - - self._logger.info("LEDSTRIP set rgb color: %s, %s, %s", red, green, blue) - ledstrip.setcolourrgb(self.to_int(red), self.to_int(green), self.to_int(blue)) - - def start_outpus_with_server(self): - for rpi_output in self.rpi_outputs: - if rpi_output['startup_with_server']: - gpio = self.to_int(rpi_output['gpio_pin']) - if rpi_output['output_type'] == 'regular': - value = False if rpi_output['active_low'] else True - if rpi_output['gpio_i2c_enabled']: - self.gpio_i2c_write(rpi_output, value) - else: - self.write_gpio(gpio, value) - if rpi_output['output_type'] == 'ledstrip': - self.ledstrip_set_rgb(rpi_output) - if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: - value = self.to_int(rpi_output['default_duty_cycle']) - self.write_pwm(gpio, value) - if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): - red, green, blue = self.get_color_from_rgb(rpi_output['default_neopixel_color']) - led_count = rpi_output['neopixel_count'] - led_brightness = rpi_output['neopixel_brightness'] - address = rpi_output['microcontroller_address'] - index_id = self.to_int(rpi_output['index_id']) - neopixel_direct = rpi_output['output_type'] == 'neopixel_direct' - self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, - green, blue, address, neopixel_direct, index_id) - if rpi_output['output_type'] == 'temp_hum_control': - rpi_output['temp_ctr_set_value'] = rpi_output['temp_ctr_default_value'] - - def schedule_auto_startup_outputs(self, rpi_output, delay_seconds): - sufix = 'auto_startup' - if rpi_output['output_type'] == 'regular': - value = False if rpi_output['active_low'] else True - self.add_regular_output_to_queue(delay_seconds, rpi_output, value, sufix) - if rpi_output['output_type'] == 'ledstrip': - self.ledstrip_set_rgb(rpi_output) - if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: - value = self.to_int(rpi_output['default_duty_cycle']) - self.add_pwm_output_to_queue(delay_seconds, rpi_output, value, sufix) - if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): - red, green, blue = self.get_color_from_rgb(rpi_output['default_neopixel_color']) - self.add_neopixel_output_to_queue(rpi_output, delay_seconds, red, green, blue, sufix) - if rpi_output['output_type'] == 'temp_hum_control': - value = rpi_output['temp_ctr_default_value'] - self.add_temperature_output_temperature_queue(delay_seconds, rpi_output, value, sufix) - self._logger.debug("Events scheduled to run %s", self.event_queue) - - def get_color_from_rgb(self, stringColor): - stringColor = stringColor.replace('rgb(', '') - red = stringColor[:stringColor.index(',')] - stringColor = stringColor[stringColor.index(',') + 1:] - green = stringColor[:stringColor.index(',')] - stringColor = stringColor[stringColor.index(',') + 1:] - blue = stringColor[:stringColor.index(')')] - return red, green, blue - - def get_shutdown_delay_from_output(self, rpi_output): - shutdown_time = rpi_output['shutdown_time'] - - shut_down_date_time = self.create_date(shutdown_time) - - if shut_down_date_time < datetime.now(): - shut_down_date_time = shut_down_date_time + timedelta(days=1) - - delay_seconds = (shut_down_date_time - datetime.now()).total_seconds() - - return delay_seconds - - def add_neopixel_output_to_queue(self, rpi_output, delay_seconds, red, green, blue, sufix): - gpio_pin = rpi_output['gpio_pin'] - ledCount = rpi_output['neopixel_count'] - ledBrightness = rpi_output['neopixel_brightness'] - address = rpi_output['microcontroller_address'] - neopixel_direct = rpi_output['output_type'] == 'neopixel_direct' - index_id = self.to_int(rpi_output['index_id']) - - queue_id = '{0!s}_{1!s}'.format(index_id, sufix) - - self._logger.debug("Scheduling neopixel output id %s for on %s delay_seconds", queue_id, delay_seconds) - - thread = threading.Timer(delay_seconds, self.send_neopixel_command, - args=[gpio_pin, ledCount, ledBrightness, red, green, blue, address, neopixel_direct, - index_id, queue_id]) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def add_pwm_output_to_queue(self, delay_seconds, rpi_output, value, sufix): - queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) - - self._logger.debug("Scheduling pwm output id %s for on %s delay_seconds", queue_id, delay_seconds) - - thread = threading.Timer(delay_seconds, self.write_pwm, - args=[self.to_int(rpi_output['gpio_pin']), value, queue_id]) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def schedule_pwm_duty_on_queue(self, delay_seconds, rpi_output, value, sufix): - queue_id = '{0!s}_{1!s}_{2!s}'.format(rpi_output['index_id'], "pwm_linked_temp", sufix) - thread = threading.Timer(delay_seconds, self.set_pwm_duty_cycle, args=[rpi_output, value, queue_id]) - - self._logger.debug("Scheduling pwm linked temp output id %s on %s delay_seconds", queue_id, delay_seconds) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def set_pwm_duty_cycle(self, rpi_output, value, queue_id): - rpi_output['duty_cycle'] = value - if queue_id is not None: - self.stop_queue_item(queue_id) - - def add_regular_output_to_queue(self, delay_seconds, rpi_output, value, sufix): - queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) - - self._logger.debug("Scheduling regular output id %s on %s delay_seconds", queue_id, delay_seconds) - - if rpi_output['gpio_i2c_enabled']: - thread = threading.Timer(delay_seconds, self.gpio_i2c_write, - args=[rpi_output, value, queue_id]) - else: - thread = threading.Timer(delay_seconds, self.write_gpio, - args=[self.to_int(rpi_output['gpio_pin']), value, queue_id]) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def add_temperature_output_temperature_queue(self, delay_seconds, rpi_output, value, sufix): - queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) - self._logger.debug("Scheduling temperature control id %s on %s delay_seconds", queue_id, delay_seconds) - - thread = threading.Timer(delay_seconds, self.write_temperature_to_output, - args=[self.to_int(rpi_output['index_id']), value, queue_id]) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def write_temperature_to_output(self, rpi_output_index, value, queue_id=None): - try: - rpi_output = [r_out for r_out in self.rpi_outputs if - self.to_int(r_out['index_id']) == rpi_output_index].pop() - - if rpi_output['output_type'] == 'temp_hum_control': - rpi_output['temp_ctr_set_value'] = value - - if queue_id is not None: - self._logger.debug("running scheduled queue id %s", queue_id) - self._logger.debug("Setting temperature to output index: %s value %s", rpi_output['index_id'], value) - - self.update_ui() - if queue_id is not None: - self.stop_queue_item(queue_id) - - except Exception as ex: - template = "An exception of type {0} occurred on {1}. Arguments:\n{3!r}" - message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, ex.args) - self._logger.warn(message) - pass - - def get_startup_delay_from_output(self, rpi_output): - start_up_time = rpi_output['startup_time'] - if self.is_hour(start_up_time): - start_up_date_time = self.create_date(start_up_time) - if start_up_date_time < datetime.now(): - delay_seconds = 0.0 - else: - delay_seconds = (start_up_date_time - datetime.now()).total_seconds() - else: - delay_seconds = self.to_float(rpi_output['startup_time']) - return delay_seconds - - # ~~ SettingsPlugin mixin - def on_settings_save(self, data): - outputsBeforeSave = self.get_output_list() - octoprint.plugin.SettingsPlugin.on_settings_save(self, data) - self.rpi_outputs = self._settings.get(["rpi_outputs"]) - self.rpi_inputs = self._settings.get(["rpi_inputs"]) - self.notifications = self._settings.get(["notifications"]) - outputsAfterSave = self.get_output_list() - - commonPins = list(set(outputsBeforeSave) & set(outputsAfterSave)) - - for pin in (pin for pin in outputsBeforeSave if pin not in commonPins): - self.clear_channel(pin) - - self.rpi_outputs_not_changed = commonPins - self.clear_gpio() - - self._logger.debug("rpi_outputs: %s", self.rpi_outputs) - self._logger.debug("rpi_inputs: %s", self.rpi_inputs) - self.setup_gpio() - self.configure_gpio() - self.generate_temp_hum_control_status() - - def get_settings_defaults(self): - return dict(rpi_outputs=[], rpi_inputs=[], - filament_sensor_gcode="G91 ;Set Relative Mode \n" + "G1 E-5.000000 F500 ;Retract 5mm\n" + "G1 Z15 F300 ;move Z up 15mm\n" + "G90 ;Set Absolute Mode\n " + "G1 X20 Y20 F9000 ;Move to hold position\n" + "G91 ;Set Relative Mode\n" + "G1 E-40 F500 ;Retract 40mm\n" + "M0 ;Idle Hold\n" + "G90 ;Set Absolute Mode\n" + "G1 F5000 ;Set speed limits\n" + "G28 X0 Y0 ;Home X Y\n" + "M82 ;Set extruder to Absolute Mode\n" + "G92 E0 ;Set Extruder to 0", - use_sudo=True, neopixel_dma=10, debug=False, gcode_control=False, debug_temperature_log=False, - use_board_pin_number=False, notification_provider="disabled", notification_api_key="", - notification_event_name="printer_event", notifications=[{ - 'printFinish' : True, - 'filamentChange' : True, - 'printer_action' : True, - 'temperatureAction': True, 'gpioAction': True - }]) - - # ~~ TemplatePlugin - def get_template_configs(self): - return [dict(type="settings", custom_bindings=True), dict(type="tab", custom_bindings=True), - dict(type="navbar", custom_bindings=True, suffix="_1", classes=["dropdown"]), - dict(type="navbar", custom_bindings=True, template="enclosure_navbar_input.jinja2", suffix="_2", - classes=["dropdown"])] - - # ~~ AssetPlugin mixin - def get_assets(self): - return dict(js=["js/enclosure.js", "js/bootstrap-colorpicker.min.js"], - css=["css/bootstrap-colorpicker.css", "css/enclosure.css"]) - - # ~~ Softwareupdate hook - def get_update_information(self): - return dict(enclosure=dict(displayName="Enclosure Plugin", displayVersion=self._plugin_version, - # version check: github repository - type="github_release", user="vitormhenrique", repo="OctoPrint-Enclosure", current=self._plugin_version, - # update method: pip - pip="https://github.com/vitormhenrique/OctoPrint-Enclosure/archive/{target_version}.zip")) - - def hook_gcode_queuing(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs): - if self._settings.get(["gcode_control"]) is False: - return - - if cmd.strip().startswith("ENC"): - self._logger.debug("Gcode queuing: %s", cmd) - index_id = self.to_int(self.get_gcode_value(cmd, 'O')) - for output in [item for item in self.rpi_outputs if item['index_id'] == index_id]: - if output['output_type'] == 'regular': - set_value = self.to_int(self.get_gcode_value(cmd, 'S')) - set_value = self.constrain(set_value, 0, 1) - value = True if set_value == 1 else False - value = (not value) if output['active_low'] else value - if output['gpio_i2c_enabled']: - self.gpio_i2c_write(output, value) - else: - self.write_gpio(self.to_int(output['gpio_pin']), value) - comm_instance._log("Setting REGULAR output %s to value %s" % (index_id, value)) - return - if output['output_type'] == 'pwm': - set_value = self.to_int(self.get_gcode_value(cmd, 'S')) - set_value = self.constrain(set_value, 0, 100) - output['duty_cycle'] = set_value - self.write_pwm(self.to_int(output['gpio_pin']), set_value) - comm_instance._log("Setting PWM output %s to value %s" % (index_id, set_value)) - return - if output['output_type'] == 'neopixel_indirect' or output['output_type'] == 'neopixel_direct': - red = self.get_gcode_value(cmd, 'R') - green = self.get_gcode_value(cmd, 'G') - blue = self.get_gcode_value(cmd, 'B') - - led_count = output['neopixel_count'] - led_brightness = output['neopixel_brightness'] - address = output['microcontroller_address'] - - index_id = self.to_int(output['index_id']) - - neopixel_direct = output['output_type'] == 'neopixel_direct' - - self.send_neopixel_command(self.to_int(output['gpio_pin']), led_count, led_brightness, red, green, - blue, address, neopixel_direct, index_id) - comm_instance._log( - "Setting NEOPIXEL output %s to red: %s green: %s blue: %s" % (index_id, red, green, blue)) - return - if output['output_type'] == 'temp_hum_control': - set_value = self.to_float(self.get_gcode_value(cmd, 'S')) - should_wait = self.to_int(self.get_gcode_value(cmd, 'W')) - if should_wait == 1 and self._printer.is_printing(): - self._printer.pause_print() - self.waiting_temperature.append(index_id) - output['temp_ctr_set_value'] = set_value - self.update_ui_set_temperature() - self.handle_temp_hum_control() - comm_instance._log("Setting TEMP/HUM control output %s to value %s" % (index_id, set_value)) - return - - def get_graph_data(self, comm, parsed_temps): - for sensor in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): - if sensor["show_graph_temp"]: - parsed_temps[str(sensor["label"])] = (sensor['temp_sensor_temp'], None) - if sensor["show_graph_humidity"]: - parsed_temps[str(sensor["label"])+" Humidity"] = (sensor['temp_sensor_humidity'], None) - - return parsed_temps - - -__plugin_name__ = "Enclosure Plugin" -__plugin_pythoncompat__ = ">=2.7,<4" - - -def __plugin_load__(): - global __plugin_implementation__ - __plugin_implementation__ = EnclosurePlugin() - - global __plugin_hooks__ - __plugin_hooks__ = { - "octoprint.comm.protocol.gcode.queuing" : __plugin_implementation__.hook_gcode_queuing, - "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information, - "octoprint.comm.protocol.temperatures.received": (__plugin_implementation__.get_graph_data, 1) - } +# coding=utf-8 +from __future__ import absolute_import +from octoprint.events import eventManager, Events +from octoprint.util import RepeatedTimer +from subprocess import Popen, PIPE +from .ledstrip import LEDStrip +import octoprint.plugin +import RPi.GPIO as GPIO +from flask import jsonify, request, make_response, Response +from octoprint.server.util.flask import restricted_access +from werkzeug.exceptions import BadRequest +import time +import sys +import glob +import os +from datetime import datetime +from datetime import timedelta +import octoprint.util +import requests +import inspect +import threading +import json +import copy +from smbus2 import SMBus +import struct + + +#Function that returns Boolean output state of the GPIO inputs / outputs +def PinState_Boolean(pin, ActiveLow) : + try: + state = GPIO.input(pin) + if ActiveLow and not state: return True + if not ActiveLow and state: return True + return False + except: + return "ERROR: Unable to read pin" + +#Function that returns human-readable output state of the GPIO inputs / outputs +def PinState_Human(pin, ActiveLow): + PinState = PinState_Boolean(pin, ActiveLow) + if PinState == True : + return " ON " + elif PinState == False: + return " OFF " + else: + return PinState + +#Translates the Pull-Up/Pull-Down GPIO resistor setting to ActiveLow/ActiveHigh boolean +def CheckInputActiveLow(Input_Pull_Resistor): + #input_pull_up + #input_pull_down + if Input_Pull_Resistor == "input_pull_up": + return True + else: + return False + +class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SettingsPlugin, + octoprint.plugin.AssetPlugin, octoprint.plugin.BlueprintPlugin, + octoprint.plugin.EventHandlerPlugin): + rpi_outputs = [] + rpi_inputs = [] + waiting_temperature = [] + rpi_outputs_not_changed = [] + notifications = [] + pwm_instances = [] + event_queue = [] + temp_hum_control_status = [] + temperature_sensor_data = [] + last_filament_end_detected = [] + print_complete = False + development_mode = False + dummy_value = 30.0 + dummy_delta = 0.5 + + def start_timer(self): + """ + Function to start timer that checks enclosure temperature + """ + + self._check_temp_timer = RepeatedTimer(10, self.check_enclosure_temp, None, None, True) + self._check_temp_timer.start() + + @staticmethod + def to_float(value): + """Converts value to flow + Arguments: + value {any} -- value to be + Returns: + float -- value converted + """ + + try: + val = float(value) + return val + except: + return 0 + + @staticmethod + def to_int(value): + try: + val = int(value) + return val + except: + return 0 + + @staticmethod + def is_hour(value): + try: + datetime.strptime(value, '%H:%M') + return True + except: + return False + + @staticmethod + def create_date(value): + temp_string = datetime.now().strftime('%m/%d/%Y') + " " + value + return datetime.strptime(temp_string, '%m/%d/%Y %H:%M') + + @staticmethod + def constrain(n, minn, maxn): + return max(min(maxn, n), minn) + + @staticmethod + def get_gcode_value(command_string, gcode): + semicolon = command_string.find(';') + if not semicolon == -1: + command_string = command_string[:semicolon] + + for command in command_string.split(' '): + index = command.upper().find(gcode.upper()) + if not index == -1: + return command.replace(gcode, '') + return -1 + + # ~~ StartupPlugin mixin + def on_after_startup(self): + self.pwm_instances = [] + self.event_queue = [] + self.rpi_outputs_not_changed = [] + self.rpi_outputs = self._settings.get(["rpi_outputs"]) + self.rpi_inputs = self._settings.get(["rpi_inputs"]) + self.notifications = self._settings.get(["notifications"]) + self.generate_temp_hum_control_status() + self.setup_gpio() + self.configure_gpio() + self.update_ui() + self.start_outpus_with_server() + self.handle_initial_gpio_control() + self.start_timer() + self.print_complete = False + + def get_settings_version(self): + return 10 + + def on_settings_migrate(self, target, current=None): + self._logger.warn("######### current settings version %s target settings version %s #########", current, target) + self._logger.info("######### Current settings #########") + self._logger.info("rpi_outputs: %s", self.rpi_outputs) + self._logger.info("rpi_inputs: %s", self.rpi_inputs) + self._logger.info("######### End Current Settings #########") + if current >= 4 and target == 10: + self._logger.warn("######### migrating settings to v10 #########") + old_outputs = self._settings.get(["rpi_outputs"]) + old_inputs = self._settings.get(["rpi_inputs"]) + for rpi_output in old_outputs: + if 'shutdown_on_failed' not in rpi_output: + rpi_output['shutdown_on_failed'] = False + if 'shell_script' not in rpi_output: + rpi_output['shell_script'] = "" + if 'gpio_i2c_enabled' not in rpi_output: + rpi_output['gpio_i2c_enabled'] = False + if 'gpio_i2c_bus' not in rpi_output: + rpi_output['gpio_i2c_bus'] = 1 + if 'gpio_i2c_address' not in rpi_output: + rpi_output['gpio_i2c_address'] = 1 + if 'gpio_i2c_register' not in rpi_output: + rpi_output['gpio_i2c_register'] = 1 + if 'gpio_i2c_data_on' not in rpi_output: + rpi_output['gpio_i2c_data_on'] = 1 + if 'gpio_i2c_data_off' not in rpi_output: + rpi_output['gpio_i2c_data_off'] = 0 + if 'gpio_i2c_register_status' not in rpi_output: + rpi_output['gpio_i2c_register_status'] = 1 + if 'shutdown_on_error' not in rpi_output: + rpi_output['shutdown_on_error'] = False + self._settings.set(["rpi_outputs"], old_outputs) + + old_inputs = self._settings.get(["rpi_inputs"]) + for rpi_input in old_inputs: + if 'temp_i2c_bus' not in rpi_input: + rpi_input['temp_i2c_bus'] = 1 + if 'temp_i2c_address' not in rpi_input: + rpi_input['temp_i2c_address'] = 1 + if 'temp_i2c_register' not in rpi_input: + rpi_input['temp_i2c_register'] = 1 + if 'show_graph_temp' not in rpi_input: + rpi_input['show_graph_temp'] = False + if 'show_graph_humidity' not in rpi_input: + rpi_input['show_graph_humidity'] = False + self._settings.set(["rpi_inputs"], old_inputs) + else: + self._logger.warn("######### settings not compatible #########") + self._settings.set(["rpi_outputs"], []) + self._settings.set(["rpi_inputs"], []) + self.rpi_inputs = self._settings.get(["rpi_inputs"]) + + #Scan all configured inputs and outputs and return the pin value + @octoprint.plugin.BlueprintPlugin.route("/ReadPin/", methods=["GET"]) + def ReadSinglePin(self, identifier): + Resp = [] + MatchFound = False + for rpi_input in self.rpi_inputs: + if identifier == self.to_int(rpi_input['gpio_pin']): + MatchFound = True + ConfiguredAs = "Input" + ActiveLow = CheckInputActiveLow(rpi_input['input_pull_resistor']) + pin = self.to_int(rpi_input['gpio_pin']) + val = PinState_Human(pin,ActiveLow) + label = rpi_input['label'] + Resp.append(dict(Configured_As=ConfiguredAs, label=label, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['gpio_pin']): + MatchFound = True + ConfiguredAs = "Output" + ActiveLow = CheckInputActiveLow(rpi_output['active_low']) + pin = self.to_int(rpi_output['gpio_pin']) + if rpi_output['gpio_i2c_enabled']: + b = self.gpio_i2c_input(rpi_output, ActiveLow) + val = " ON " if b else " OFF " + else: + val = PinState_Human(pin,ActiveLow) + label = rpi_output['label'] + Resp.append(dict(Configured_As=ConfiguredAs, label=label, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) + if MatchFound == False: + pin = int(identifier) + ConfiguredAs = "Unknown" + ActiveLow = "Unknown" + try: + val = GPIO.input(pin) + except: + val = "GPIO pin not initialized." + Resp.append(dict(Configured_As=ConfiguredAs, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) + return Response(json.dumps(Resp), mimetype='application/json') + + # ~~ Blueprintplugin mixin + @octoprint.plugin.BlueprintPlugin.route("/inputs", methods=["GET"]) + def get_inputs(self): + inputs = [] + for rpi_input in self.rpi_inputs: + index = self.to_int(rpi_input['index_id']) + label = rpi_input['label'] + ActiveLow = CheckInputActiveLow(rpi_input['input_pull_resistor']) + pin = self.to_int(rpi_input['gpio_pin']) + val = PinState_Human(pin,ActiveLow) + inputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, State=val)) + return Response(json.dumps(inputs), mimetype='application/json') + + @octoprint.plugin.BlueprintPlugin.route("/inputs/", methods=["GET"]) + def get_input_status(self, identifier): + for rpi_input in self.rpi_inputs: + if identifier == self.to_int(rpi_input['index_id']): + return Response(json.dumps(rpi_input), mimetype='application/json') + return make_response('', 404) + + + @octoprint.plugin.BlueprintPlugin.route("/temperature/", methods=["PATCH"]) + @restricted_access + def set_enclosure_temp_humidity(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'temperature' not in data: + return make_response("missing temperature attribute", 406) + + set_value = data["temperature"] + + for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == identifier]: + temp_hum_control['temp_ctr_set_value'] = set_value + + self.handle_temp_hum_control() + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/filament/", methods=["PATCH"]) + @restricted_access + def set_filament_sensor(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + for sensor in self.rpi_inputs: + if identifier == self.to_int(sensor['index_id']): + sensor['filament_sensor_enabled'] = value + self._logger.info("Setting filament sensor for input %s to : %s", str(identifier), value) + self._settings.set(["rpi_inputs"], self.rpi_inputs) + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/outputs", methods=["GET"]) + def get_outputs(self): + outputs = [] + for rpi_output in self.rpi_outputs: + if rpi_output['output_type'] == 'regular': + index = self.to_int(rpi_output['index_id']) + label = rpi_output['label'] + pin = self.to_int(rpi_output['gpio_pin']) + ActiveLow = rpi_output['active_low'] + if rpi_output['gpio_i2c_enabled']: + b = self.gpio_i2c_input(rpi_output, ActiveLow) + val = " ON " if b else " OFF " + else: + val = PinState_Human(pin,ActiveLow) + outputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, State=val)) + return Response(json.dumps(outputs), mimetype='application/json') + + + @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["GET"]) + def get_output_status(self, identifier): + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + out = copy.deepcopy(rpi_output) + pin = self.to_int(rpi_output['gpio_pin']) + if rpi_output['gpio_i2c_enabled']: + out['current_value'] = self.gpio_i2c_input(rpi_output, rpi_output['active_low']) + else: + out['current_value'] = PinState_Boolean(pin, rpi_output['active_low'] ) + return Response(json.dumps(out), mimetype='application/json') + return make_response('', 404) + + + @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["PATCH"]) + @restricted_access + def set_io(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + val = (not value) if rpi_output['active_low'] else value + if rpi_output['gpio_i2c_enabled']: + self.gpio_i2c_write(rpi_output, val) + else: + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-startup", methods=["PATCH"]) + @restricted_access + def set_auto_startup(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + if not value: + suffix = 'auto_startup' + queue_id = '{0!s}_{1!s}'.format(str(identifier), suffix) + self.stop_queue_item(queue_id) + for output in self.rpi_outputs: + if identifier == self.to_int(output['index_id']): + output['auto_startup'] = value + self._logger.info("Setting auto startup for output %s to : %s", str(identifier), value) + self._settings.set(["rpi_outputs"], self.rpi_outputs) + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-shutdown", methods=["PATCH"]) + @restricted_access + def set_auto_shutdown(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + if not value: + suffix = 'auto_shutdown' + queue_id = '{0!s}_{1!s}'.format(str(identifier), suffix) + self.stop_queue_item(queue_id) + + for output in self.rpi_outputs: + if identifier == self.to_int(output['index_id']): + output['auto_shutdown'] = value + self._logger.info("Setting auto shutdown for output %s to : %s", str(identifier), value) + self._settings.set(["rpi_outputs"], self.rpi_outputs) + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/pwm/", methods=["PATCH"]) + @restricted_access + def set_pwm(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'duty_cycle' not in data: + return make_response("missing duty_cycle attribute", 406) + + set_value = self.to_int(data['duty_cycle']) + for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == identifier]: + rpi_output['duty_cycle'] = set_value + rpi_output['new_duty_cycle'] = "" + gpio = self.to_int(rpi_output['gpio_pin']) + self.write_pwm(gpio, set_value) + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/rgb-led/", methods=["PATCH"]) + @restricted_access + def set_ledstrip_color(self, identifier): + """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'rgb' not in data: + return make_response("missing rgb attribute", 406) + rgb = data['rgb'] + + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + self.ledstrip_set_rgb(rpi_output, rgb) + + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/neopixel/", methods=["PATCH"]) + @restricted_access + def set_neopixel(self, identifier): + """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'red' not in data: + return make_response("missing red attribute", 406) + if 'green' not in data: + return make_response("missing green attribute", 406) + if 'blue' not in data: + return make_response("missing blue attribute", 406) + + red = data['red'] + green = data['green'] + blue = data['blue'] + + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + led_count = rpi_output['neopixel_count'] + led_brightness = rpi_output['neopixel_brightness'] + address = rpi_output['microcontroller_address'] + + neopixel_dirrect = rpi_output['output_type'] == 'neopixel_direct' + + self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, green, + blue, address, neopixel_dirrect, identifier) + + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/clear-gpio", methods=["POST"]) + @restricted_access + def clear_gpio_mode(self): + GPIO.cleanup() + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/update", methods=["POST"]) + @restricted_access + def update_ui_requested(self): + self.update_ui() + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/shell/", methods=["POST"]) + @restricted_access + def send_shell_command(self, identifier): + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() + + command = rpi_output['shell_script'] + self.shell_command(command) + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/gcode/", methods=["POST"]) + @restricted_access + def requested_gcode_command(self, identifier): + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() + self.send_gcode_command(rpi_output['gcode']) + return make_response('', 204) + + + + + + """ + DEPRECATION + This API will be deprecated in a future version + """ + + # ~~ Blueprintplugin mixin + @octoprint.plugin.BlueprintPlugin.route("/setEnclosureTempHum", methods=["GET"]) + def set_enclosure_temp_humidity_old(self): + set_value = self.to_float(request.values["set_temperature"]) + index_id = self.to_int(request.values["index_id"]) + + for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == index_id]: + temp_hum_control['temp_ctr_set_value'] = set_value + + self.handle_temp_hum_control() + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/clearGPIOMode", methods=["GET"]) + def clear_gpio_mode_old(self): + GPIO.cleanup() + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/updateUI", methods=["GET"]) + def update_ui_requested_old(self): + self.update_ui() + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/getOutputStatus", methods=["GET"]) + def get_output_status_old(self): + gpio_status = [] + for rpi_output in self.rpi_outputs: + if rpi_output['output_type'] == 'regular': + pin = self.to_int(rpi_output['gpio_pin']) + ActiveLow = rpi_output['active_low'] + if rpi_output['gpio_i2c_enabled']: + val = self.gpio_i2c_input(rpi_output, rpi_output['active_low']) + else: + val = PinState_Boolean(pin, ActiveLow) + val2 = PinState_Human(pin, ActiveLow) + index = self.to_int(rpi_output['index_id']) + gpio_status.append(dict(index_id=index, status=val, State=val2)) + return Response(json.dumps(gpio_status), mimetype='application/json') + + @octoprint.plugin.BlueprintPlugin.route("/setIO", methods=["GET"]) + def set_io_old(self): + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False + for rpi_output in self.rpi_outputs: + if self.to_int(index) == self.to_int(rpi_output['index_id']): + val = (not value) if rpi_output['active_low'] else value + if rpi_output['gpio_i2c_enabled']: + self.gpio_i2c_write(rpi_output, val) + else: + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/sendShellCommand", methods=["GET"]) + def send_shell_command_old(self): + output_index = self.to_int(request.values["index_id"]) + + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == output_index].pop() + + command = rpi_output['shell_script'] + self.shell_command(command) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setAutoStartUp", methods=["GET"]) + def set_auto_startup_old(self): + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False + + if not value: + suffix = 'auto_startup' + queue_id = '{0!s}_{1!s}'.format(index, suffix) + self.stop_queue_item(queue_id) + for output in self.rpi_outputs: + if self.to_int(index) == self.to_int(output['index_id']): + output['auto_startup'] = value + self._logger.info("Setting auto startup for output %s to : %s", index, value) + self._settings.set(["rpi_outputs"], self.rpi_outputs) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setAutoShutdown", methods=["GET"]) + def set_auto_shutdown_old(self): + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False + + if not value: + suffix = 'auto_shutdown' + queue_id = '{0!s}_{1!s}'.format(index, suffix) + self.stop_queue_item(queue_id) + + for output in self.rpi_outputs: + if self.to_int(index) == self.to_int(output['index_id']): + output['auto_shutdown'] = value + self._logger.info("Setting auto shutdown for output %s to : %s", index, value) + self._settings.set(["rpi_outputs"], self.rpi_outputs) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setFilamentSensor", methods=["GET"]) + def set_filament_sensor_old(self): + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False + for sensor in self.rpi_inputs: + if self.to_int(index) == self.to_int(sensor['index_id']): + sensor['filament_sensor_enabled'] = value + self._logger.info("Setting filament sensor for input %s to : %s", index, value) + self._settings.set(["rpi_inputs"], self.rpi_inputs) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setPWM", methods=["GET"]) + def set_pwm_old(self): + set_value = self.to_int(request.values["new_duty_cycle"]) + index_id = self.to_int(request.values["index_id"]) + for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == index_id]: + rpi_output['duty_cycle'] = set_value + rpi_output['new_duty_cycle'] = "" + gpio = self.to_int(rpi_output['gpio_pin']) + self.write_pwm(gpio, set_value) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/sendGcodeCommand", methods=["GET"]) + def requested_gcode_command_old(self): + gpio_index = self.to_int(request.values["index_id"]) + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == gpio_index].pop() + self.send_gcode_command(rpi_output['gcode']) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setNeopixel", methods=["GET"]) + def set_neopixel_old(self): + """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" + gpio_index = self.to_int(request.values["index_id"]) + red = request.values["red"] + green = request.values["green"] + blue = request.values["blue"] + for rpi_output in self.rpi_outputs: + if gpio_index == self.to_int(rpi_output['index_id']): + led_count = rpi_output['neopixel_count'] + led_brightness = rpi_output['neopixel_brightness'] + address = rpi_output['microcontroller_address'] + + neopixel_dirrect = rpi_output['output_type'] == 'neopixel_direct' + + self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, green, + blue, address, neopixel_dirrect, gpio_index) + + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setLedstripColor", methods=["GET"]) + def set_ledstrip_color_old(self): + """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" + gpio_index = self.to_int(request.values["index_id"]) + rgb = request.values["rgb"] + for rpi_output in self.rpi_outputs: + if gpio_index == self.to_int(rpi_output['index_id']): + self.ledstrip_set_rgb(rpi_output, rgb) + + return jsonify(success=True) + + # DEPREACTION END + + + # GPIO over i2c + + def gpio_i2c_input(self, output, active_low=None): + state = False + try: + i2cbus = self.to_int(output['gpio_i2c_bus']) + i2caddr = self.to_int(output['gpio_i2c_address']) + i2creg = self.to_int(output['gpio_i2c_register_status']) + data_on = self.to_int(output['gpio_i2c_data_on']) + + with SMBus(i2cbus) as bus: + data = bus.read_i2c_block_data(i2caddr, i2creg, 1) + if data[0] == data_on: + state = True + + self._logger.debug("gpio_i2c_input(i2cbus=%s, i2caddr=%s, i2creg=%s, data_on=%s) data == %s", + i2cbus, i2caddr, i2creg, data_on, data) + + if active_low is None and state: return state + + except Exception as ex: + self.log_error(ex) + + if active_low and not state: return True + if not active_low and state: return True + return False + + def gpio_i2c_write(self, output, state, queue_id=None): + try: + i2cbus = self.to_int(output['gpio_i2c_bus']) + i2caddr = self.to_int(output['gpio_i2c_address']) + i2creg = self.to_int(output['gpio_i2c_register']) + data_on = self.to_int(output['gpio_i2c_data_on']) + data_off = self.to_int(output['gpio_i2c_data_off']) + + with SMBus(i2cbus) as bus: + data = [] + if state: + data.append(data_on) + else: + data.append(data_off) + + bus.write_i2c_block_data(i2caddr, i2creg, data) + + if queue_id is not None: + self._logger.debug("Running scheduled queue id %s", queue_id) + self._logger.debug("Writing on GPIO (i2c): %s/%s value %s", output['gpio_i2c_address'], output['gpio_i2c_register'], state) + self.update_ui() + if queue_id is not None: + self.stop_queue_item(queue_id) + + except Exception as ex: + template = "An exception of type {0} occurred on {1} when writing on i2c address {2}, reg {3}. Arguments:\n{4!r}" + message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, output['gpio_i2c_address'], output['gpio_i2c_register'], ex.args) + self._logger.warn(message) + pass + + + def send_neopixel_command(self, led_pin, led_count, led_brightness, red, green, blue, address, neopixel_dirrect, + index_id, queue_id=None): + """Send neopixel command + Arguments: + led_pin {int} -- GPIO number + ledCount {int} -- number of LEDS + ledBrightness {int} -- brightness from 0 to 255 + red {int} -- red value from 0 to 255 + green {int} -- green value from 0 to 255 + blue {int} -- blue value from 0 to 255 + address {int} -- i2c address from microcontroller + """ + + try: + + for rpi_output in self.rpi_outputs: + if self.to_int(index_id) == self.to_int(rpi_output['index_id']): + rpi_output['neopixel_color'] = 'rgb({0!s},{1!s},{2!s})'.format(red, green, blue) + + if address == '': + address = 0 + + if neopixel_dirrect: + script = os.path.dirname(os.path.realpath(__file__)) + "/neopixel_direct.py " + else: + script = os.path.dirname(os.path.realpath(__file__)) + "/neopixel_indirect.py " + + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + + cmd = sudo_str + "python " + script + str(led_pin) + " " + str(led_count) + " " + str( + led_brightness) + " " + str(red) + " " + str(green) + " " + str(blue) + " " + + if neopixel_dirrect: + dma = self._settings.get(["neopixel_dma"]) or 10 + cmd = cmd + str(dma) + else: + cmd = cmd + str(address) + + if queue_id is not None: + self._logger.debug("running scheduled queue id %s", queue_id) + self._logger.debug("Sending neopixel cmd: %s", cmd) + Popen(cmd, shell=True) + if queue_id is not None: + self.stop_queue_item(queue_id) + except Exception as ex: + self.log_error(ex) + + def check_enclosure_temp(self): + try: + sensor_data = [] + for sensor in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): + temp, hum = self.get_sensor_data(sensor) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Sensor %s Temperature: %s humidity %s", sensor['label'], temp, hum) + if temp is not None and hum is not None: + sensor["temp_sensor_temp"] = temp + sensor["temp_sensor_humidity"] = hum + sensor_data.append(dict(index_id=sensor['index_id'], temperature=temp, humidity=hum)) + self.temperature_sensor_data = sensor_data + self.handle_temp_hum_control() + self.handle_temperature_events() + self.handle_pwm_linked_temperature() + self.update_ui() + except Exception as ex: + self.log_error(ex) + + def toggle_output(self, output_index, first_run=False): + for output in [item for item in self.rpi_outputs if item['index_id'] == output_index]: + gpio_pin = self.to_int(output['gpio_pin']) + index_id = self.to_int(output['index_id']) + + if output['output_type'] == 'regular': + if output['gpio_i2c_enabled']: + current_value = self.gpio_i2c_input(output) + else: + if first_run: + current_value = False + else: + current_value = (not GPIO.input(gpio_pin)) if output['active_low'] else GPIO.input(gpio_pin) + + if current_value: + time_delay = self.to_int(output['toggle_timer_off']) + else: + time_delay = self.to_int(output['toggle_timer_on']) + + if not self.print_complete: + if output['gpio_i2c_enabled']: + self.gpio_i2c_write(output, not current_value) + else: + self.write_gpio(gpio_pin, not current_value) + thread = threading.Timer(time_delay, self.toggle_output, args=[index_id]) + thread.start() + else: + off_value = True if output['active_low'] else False + if output['gpio_i2c_enabled']: + self.gpio_i2c_write(output, off_value) + else: + self.write_gpio(gpio_pin, off_value) + self.update_ui_outputs() + return + + if output['output_type'] == 'pwm': + for pwm in self.pwm_instances: + if gpio_pin in pwm: + if first_run: + current_pwm_value = 0 + else: + if 'duty_cycle' in pwm: + current_pwm_value = pwm['duty_cycle'] + current_pwm_value = self.to_int(current_pwm_value) + else: + current_pwm_value = 0 + + if not current_pwm_value == 0: + time_delay = self.to_int(output['toggle_timer_off']) + write_value = 0 + else: + time_delay = self.to_int(output['toggle_timer_on']) + write_value = self.to_int(output['default_duty_cycle']) + + if not self.print_complete: + self.write_pwm(gpio_pin, write_value) + thread = threading.Timer(time_delay, self.toggle_output, args=[index_id]) + thread.start() + else: + self.write_pwm(self.to_int(output['gpio_pin']), 0) + self.update_ui_outputs() + return + + def update_ui(self): + self.update_ui_outputs() + self.update_ui_current_temperature() + self.update_ui_set_temperature() + self.update_ui_inputs() + + def update_ui_current_temperature(self): + self._plugin_manager.send_plugin_message(self._identifier, dict(sensor_data=self.temperature_sensor_data)) + + def update_ui_set_temperature(self): + result = [] + for temp_crt_output in list(filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): + set_temperature = self.to_float(temp_crt_output['temp_ctr_set_value']) + result.append(dict(index_id=temp_crt_output['index_id'], set_temperature=set_temperature)) + result.append(set_temperature) + self._plugin_manager.send_plugin_message(self._identifier, dict(set_temperature=result)) + + def stop_queue_item(self, queue_id): + old_list = self.event_queue + self._logger.debug("Stopping queue id %s...", queue_id) + for task in self.event_queue: + self._logger.debug("Queue id found...") + if task['queue_id'] == queue_id: + task['thread'].cancel() + self.event_queue.remove(task) + self._logger.debug("Queue id stopped and removed from list...") + self._logger.debug("Old queue list: %s", old_list) + self._logger.debug("New queue list: %s", self.event_queue) + + def update_ui_outputs(self): + try: + regular_status = [] + pwm_status = [] + neopixel_status = [] + temp_control_status = [] + for output in self.rpi_outputs: + index = self.to_int(output['index_id']) + pin = self.to_int(output['gpio_pin']) + startup = output['auto_startup'] + shutdown = output['auto_shutdown'] + + if output['output_type'] == 'regular': + if output['gpio_i2c_enabled']: + val = self.gpio_i2c_input(output) + else: + val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) + regular_status.append( + dict(index_id=index, status=val, auto_startup=startup, auto_shutdown=shutdown)) + if output['output_type'] == 'temp_hum_control': + if output['gpio_i2c_enabled']: + val = self.gpio_i2c_input(output) + else: + val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) + temp_control_status.append( + dict(index_id=index, status=val, auto_startup=startup, auto_shutdown=shutdown)) + if output['output_type'] == 'neopixel_indirect' or output['output_type'] == 'neopixel_direct': + val = output['neopixel_color'] + neopixel_status.append( + dict(index_id=index, color=val, auto_startup=startup, auto_shutdown=shutdown)) + if output['output_type'] == 'pwm': + for pwm in self.pwm_instances: + if pin in pwm: + if 'duty_cycle' in pwm: + pwm_val = pwm['duty_cycle'] + val = self.to_int(pwm_val) + else: + val = 0 + pwm_status.append( + dict(index_id=index, pwm_value=val, auto_startup=startup, auto_shutdown=shutdown)) + self._plugin_manager.send_plugin_message(self._identifier, + dict(rpi_output_regular=regular_status, rpi_output_pwm=pwm_status, + rpi_output_neopixel=neopixel_status, + rpi_output_temp_hum_ctrl=temp_control_status)) + except Exception as ex: + self.log_error(ex) + + def update_ui_inputs(self): + try: + sensor_status = [] + for sensor in self.rpi_inputs: + if sensor['input_type'] == 'gpio' and sensor['action_type'] == 'printer_control' and sensor[ + 'printer_action'] == 'filament': + index = self.to_int(sensor['index_id']) + value = sensor['filament_sensor_enabled'] + sensor_status.append(dict(index_id=index, filament_sensor_enabled=value)) + self._plugin_manager.send_plugin_message(self._identifier, dict(filament_sensor_status=sensor_status)) + except Exception as ex: + self.log_error(ex) + + def get_sensor_data(self, sensor): + try: + if self.development_mode: + temp, hum = self.read_dummy_temp() + else: + if sensor['temp_sensor_type'] in ["11", "22", "2302"]: + temp, hum = self.read_dht_temp(sensor['temp_sensor_type'], sensor['gpio_pin']) + elif sensor['temp_sensor_type'] == "18b20": + temp = self.read_18b20_temp(sensor['ds18b20_serial']) + hum = 0 + elif sensor['temp_sensor_type'] == "bme280": + temp, hum = self.read_bme280_temp(sensor['temp_sensor_address']) + elif sensor['temp_sensor_type'] == "am2320": + temp, hum = self.read_am2320_temp() # sensor has fixed address + elif sensor['temp_sensor_type'] == "si7021": + temp, hum = self.read_si7021_temp(sensor['temp_sensor_address'], sensor['temp_sensor_i2cbus']) + elif sensor['temp_sensor_type'] == "tmp102": + temp = self.read_tmp102_temp(sensor['temp_sensor_address']) + hum = 0 + elif sensor['temp_sensor_type'] == "max31855": + temp = self.read_max31855_temp(sensor['temp_sensor_address']) + hum = 0 + elif sensor['temp_sensor_type'] == "mcp9808": + temp = self.read_mcp_temp(sensor['temp_sensor_address']) + hum = 0 + elif sensor['temp_sensor_type'] == "temp_raw_i2c": + temp, hum = self.read_raw_i2c_temp(sensor) + elif sensor['temp_sensor_type'] == "hum_raw_i2c": + hum, temp = self.read_raw_i2c_temp(sensor) + else: + self._logger.info("temp_sensor_type no match") + temp = None + hum = None + if temp != -1 and hum != -1: + temp = round(self.to_float(temp), 1) if not sensor['use_fahrenheit'] else round( + self.to_float(temp) * 1.8 + 32, 1) + hum = round(self.to_float(hum), 1) + return temp, hum + return None, None + except Exception as ex: + self.log_error(ex) + + def handle_temperature_events(self): + for temperature_alarm in [item for item in self.rpi_outputs if item['output_type'] == 'temperature_alarm']: + set_temperature = self.to_float(temperature_alarm['alarm_set_temp']) + if int(set_temperature) is 0: + continue + linked_data = [item for item in self.temperature_sensor_data if + item['index_id'] == temperature_alarm['linked_temp_sensor']].pop() + sensor_temperature = self.to_float(linked_data['temperature']) + if set_temperature < sensor_temperature: + for rpi_controlled_output in self.rpi_outputs: + if self.to_int(temperature_alarm['controlled_io']) == self.to_int( + rpi_controlled_output['index_id']): + if rpi_controlled_output['gpio_i2c_enabled']: + val = False if temperature_alarm['controlled_io_set_value'] == 'low' else True + self.gpio_i2c_write(rpi_controlled_output, val) + else: + val = GPIO.LOW if temperature_alarm['controlled_io_set_value'] == 'low' else GPIO.HIGH + self.write_gpio(self.to_int(rpi_controlled_output['gpio_pin']), val) + for notification in self.notifications: + if notification['temperatureAction']: + msg = ("Temperature action: enclosure temperature exceed " + temperature_alarm[ + 'alarm_set_temp']) + self.send_notification(msg) + + def read_dummy_temp(self): + current_value = self.dummy_value + if current_value > 40 or current_value < 30: + self.dummy_delta = - self.dummy_delta + + return_value = current_value + self.dummy_delta + + self.dummy_value = return_value + + return return_value, return_value + + def read_raw_i2c_temp(self, sensor): + try: + i2cbus = self.to_int(sensor['temp_i2c_bus']) + i2caddr = self.to_int(sensor['temp_i2c_address']) + i2creg = self.to_int(sensor['temp_i2c_register']) + + with SMBus(i2cbus) as bus: + data = bus.read_i2c_block_data(i2caddr, i2creg, 8) + fval1 = struct.unpack('f', bytearray(data[0:4]))[0] + if fval1 != fval1: + fval1 = 0 + fval2 = struct.unpack('f', bytearray(data[4:8]))[0] + if fval2 != fval2: + fval2 = 0 + + self._logger.debug("read_raw_i2c_temp(i2cbus=%s, i2caddr=%s, i2creg=%s) data == %s (%s, %s)", + i2cbus, i2caddr, i2creg, data, fval1, fval2) + + return (fval1, fval2) + + except Exception as ex: + template = "An exception of type {0} occurred on {1} when reading on i2c address {2}, reg {3}. Arguments:\n{4!r}" + message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, i2caddr, i2creg, ex.args) + self._logger.warn(message) + return str(-1) + + def read_mcp_temp(self, address): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/mcp9808.py" + args = ["python", script, str(address)] + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature MCP9808 cmd: %s", " ".join(args)) + proc = Popen(args, stdout=PIPE) + stdout, _ = proc.communicate() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("MCP9808 result: %s", stdout) + return self.to_float(stdout.decode("utf-8").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 + + def read_dht_temp(self, sensor, pin): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/getDHTTemp.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python3 " + script + str(sensor) + " " + str(pin) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature dht cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Dht result: %s", stdout) + temp, hum = stdout.decode("utf-8").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 of the plugin.") + self.log_error(ex) + return (0, 0) + + def read_bme280_temp(self, address): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/BME280.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python " + script + str(address) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature BME280 cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("BME280 result: %s", stdout) + temp, hum = stdout.decode("utf-8").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 of the plugin.") + self.log_error(ex) + return (0, 0) + + def read_am2320_temp(self): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/AM2320.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python " + script # sensor has fixed address 0x5C + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature AM2320 cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("AM2320 result: %s", stdout) + temp, hum = stdout.decode("utf-8").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 of the plugin.") + self.log_error(ex) + return (0, 0) + + def read_si7021_temp(self, address, i2cbus): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/SI7021.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python " + script + str(address) + " " + str(i2cbus) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature SI7021 cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("SI7021 result: %s", stdout) + temp, hum = stdout.decode("utf-8").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 of the plugin.") + self.log_error(ex) + return (0, 0) + + def read_18b20_temp(self, serial_number): + os.system('modprobe w1-gpio') + os.system('modprobe w1-therm') + + lines = self.read_raw_18b20_temp(serial_number) + while lines[0].strip()[-3:] != 'YES': + time.sleep(0.2) + lines = self.read_raw_18b20_temp(serial_number) + equals_pos = lines[1].find('t=') + if equals_pos != -1: + temp_string = lines[1][equals_pos + 2:] + temp_c = float(temp_string) / 1000. + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("DS18B20 result: %s", temp_c) + return '{0:0.1f}'.format(temp_c) + return 0 + + def read_raw_18b20_temp(self, serial_number): + base_dir = '/sys/bus/w1/devices/' + device_folder = glob.glob(base_dir + str(serial_number) + '*')[0] + device_file = device_folder + '/w1_slave' + device_file_result = open(device_file, 'r') + lines = device_file_result.readlines() + device_file_result.close() + return lines + + def read_tmp102_temp(self, address): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/tmp102.py" + args = ["python", script, str(address)] + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature TMP102 cmd: %s", " ".join(args)) + proc = Popen(args, stdout=PIPE) + stdout, _ = proc.communicate() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("TMP102 result: %s", stdout) + return self.to_float(stdout.decode("utf-8").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 + + def read_max31855_temp(self, address): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/max31855.py" + args = ["python", script, str(address)] + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature MAX31855 cmd: %s", " ".join(args)) + proc = Popen(args, stdout=PIPE) + stdout, _ = proc.communicate() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("MAX31855 result: %s", stdout) + return self.to_float(stdout.decode("utf-8").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 + + def handle_pwm_linked_temperature(self): + try: + for pwm_output in list(filter(lambda item: item['output_type'] == 'pwm' and item['pwm_temperature_linked'], + self.rpi_outputs)): + gpio_pin = self.to_int(pwm_output['gpio_pin']) + if self._printer.is_printing(): + index_id = self.to_int(pwm_output['index_id']) + linked_id = self.to_int(pwm_output['linked_temp_sensor']) + linked_data = self.get_linked_temp_sensor_data(linked_id) + current_temp = self.to_float(linked_data['temperature']) + + duty_a = self.to_float(pwm_output['duty_a']) + duty_b = self.to_float(pwm_output['duty_b']) + temp_a = self.to_float(pwm_output['temperature_a']) + temp_b = self.to_float(pwm_output['temperature_b']) + + try: + calculated_duty = ((current_temp - temp_a) * (duty_b - duty_a) / (temp_b - temp_a)) + duty_a + + if current_temp < temp_a: + calculated_duty = 0 + except: + calculated_duty = 0 + + self._logger.debug("Calculated duty for PWM %s is %s", index_id, calculated_duty) + elif self.print_complete: + calculated_duty = self.to_int(pwm_output['duty_cycle']) + else: + calculated_duty = 0 + + self.write_pwm(gpio_pin, self.constrain(calculated_duty, 0, 100)) + + except Exception as ex: + self.log_error(ex) + + def get_linked_temp_sensor_data(self, linked_id): + try: + linked_data = [data for data in self.temperature_sensor_data if data['index_id'] == linked_id].pop() + return linked_data + except: + self._logger.warn("No linked temperature sensor found for %s", linked_id) + return None + + def handle_temp_hum_control(self): + try: + for temp_hum_control in list( + filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): + + set_temperature = self.to_float(temp_hum_control['temp_ctr_set_value']) + temp_deadband = self.to_float(temp_hum_control['temp_ctr_deadband']) + max_temp = self.to_float(temp_hum_control['temp_ctr_max_temp']) + + linked_id = temp_hum_control['linked_temp_sensor'] + + previous_status = list(filter(lambda item: item['index_id'] == temp_hum_control['index_id'], + self.temp_hum_control_status)).pop()['status'] + + if set_temperature == 0: + current_status = False + else: + linked_data = self.get_linked_temp_sensor_data(linked_id) + + control_type = str(temp_hum_control['temp_ctr_type']) + + if control_type == 'dehumidifier': + current_value = self.to_float(linked_data['humidity']) + temp_deadband = 0 + else: + current_value = self.to_float(linked_data['temperature']) + + if control_type == 'cooler' or control_type == 'dehumidifier': + if current_value <= set_temperature and current_value >= (set_temperature - temp_deadband): + current_status = previous_status + elif current_value < set_temperature: + current_status = False + else: + current_status = True + else: + if current_value <= set_temperature and current_value >= (set_temperature - temp_deadband): + current_status = previous_status + elif current_value > set_temperature: + current_status = False + else: + current_status = True + + if control_type == 'heater' and max_temp > 0.0 and max_temp < current_value: + self._logger.debug("Maximum temperature reached for temperature control %s", + temp_hum_control['index_id']) + temp_hum_control['temp_ctr_set_value'] = 0 + current_status = False + + if current_status != previous_status: + if current_status: + self._logger.info("Turning gpio to control temperature on.") + val = False if temp_hum_control['active_low'] else True + if temp_hum_control['gpio_i2c_enabled']: + self.gpio_i2c_write(temp_hum_control, val) + else: + self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) + else: + index_id = temp_hum_control['index_id'] + if index_id in self.waiting_temperature: + self.waiting_temperature.remove(index_id) + + if not self.waiting_temperature and self._printer.is_paused(): + self._printer.resume_print() + + self._logger.info("Turning gpio to control temperature off.") + val = True if temp_hum_control['active_low'] else False + if temp_hum_control['gpio_i2c_enabled']: + self.gpio_i2c_write(temp_hum_control, val) + else: + self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) + for control_status in self.temp_hum_control_status: + if control_status['index_id'] == temp_hum_control['index_id']: + control_status['status'] = current_status + except Exception as ex: + self.log_error(ex) + + def log_error(self, ex): + template = "An exception of type {0} occurred on {1}. Arguments:\n{2!r}" + message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, ex.args) + self._logger.warn(message, exc_info = True) + + def setup_gpio(self): + try: + current_mode = GPIO.getmode() + set_mode = GPIO.BOARD if self._settings.get(["use_board_pin_number"]) else GPIO.BCM + if current_mode is None: + outputs = list(filter( + lambda item: (item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ + 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct') and + item['gpio_i2c_enabled'] == False, + self.rpi_outputs)) + inputs = list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)) + gpios = outputs + inputs + if gpios: + GPIO.setmode(set_mode) + tempstr = "BOARD" if set_mode == GPIO.BOARD else "BCM" + self._logger.info("Setting GPIO mode to %s", tempstr) + elif current_mode != set_mode: + GPIO.setmode(current_mode) + tempstr = "BOARD" if current_mode == GPIO.BOARD else "BCM" + self._settings.set(["use_board_pin_number"], True if current_mode == GPIO.BOARD else False) + warn_msg = "GPIO mode was configured before, GPIO mode will be forced to use: " + tempstr + " as pin numbers. Please update GPIO accordingly!" + self._logger.info(warn_msg) + self._plugin_manager.send_plugin_message(self._identifier, + dict(is_msg=True, msg=warn_msg, msg_type="error")) + GPIO.setwarnings(False) + except Exception as ex: + self.log_error(ex) + + def clear_gpio(self): + try: + + for gpio_out in list(filter( + lambda item: (item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ + 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct') and + item['gpio_i2c_enabled'] == False, + self.rpi_outputs)): + gpio_pin = self.to_int(gpio_out['gpio_pin']) + if gpio_pin not in self.rpi_outputs_not_changed: + GPIO.cleanup(gpio_pin) + + for gpio_in in list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)): + try: + GPIO.remove_event_detect(self.to_int(gpio_in['gpio_pin'])) + except: + pass + GPIO.cleanup(self.to_int(gpio_in['gpio_pin'])) + except Exception as ex: + self.log_error(ex) + + def clear_channel(self, channel): + try: + GPIO.cleanup(self.to_int(channel)) + self._logger.debug("Clearing channel %s", channel) + except Exception as ex: + self.log_error(ex) + + def generate_temp_hum_control_status(self): + status = [] + for temp_hum_control in list(filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): + status.append(dict(index_id=temp_hum_control['index_id'], status=False)) + self.temp_hum_control_status = status + + def configure_gpio(self): + try: + + for gpio_out in list( + filter(lambda item: (item['output_type'] == 'regular' or item['output_type'] == 'temp_hum_control') and + item['gpio_i2c_enabled'] == False, + self.rpi_outputs)): + initial_value = GPIO.HIGH if gpio_out['active_low'] else GPIO.LOW + pin = self.to_int(gpio_out['gpio_pin']) + if pin not in self.rpi_outputs_not_changed: + self._logger.info("Setting GPIO pin %s as OUTPUT with initial value: %s", pin, initial_value) + GPIO.setup(pin, GPIO.OUT, initial=initial_value) + for gpio_out_pwm in list(filter(lambda item: item['output_type'] == 'pwm', self.rpi_outputs)): + pin = self.to_int(gpio_out_pwm['gpio_pin']) + self._logger.info("Setting GPIO pin %s as PWM", pin) + for pwm in (pwm_dict for pwm_dict in self.pwm_instances if pin in pwm_dict): + self.pwm_instances.remove(pwm) + self.clear_channel(pin) + GPIO.setup(pin, GPIO.OUT) + pwm_instance = GPIO.PWM(pin, self.to_int(gpio_out_pwm['pwm_frequency'])) + self._logger.info("starting PWM on pin %s", pin) + pwm_instance.start(0) + self.pwm_instances.append({pin: pwm_instance}) + for gpio_out_neopixel in list( + filter(lambda item: item['output_type'] == 'neopixel_direct', self.rpi_outputs)): + pin = self.to_int(gpio_out_neopixel['gpio_pin']) + self.clear_channel(pin) + + for rpi_input in list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)): + gpio_pin = self.to_int(rpi_input['gpio_pin']) + pull_resistor = GPIO.PUD_UP if rpi_input['input_pull_resistor'] == 'input_pull_up' else GPIO.PUD_DOWN + GPIO.setup(gpio_pin, GPIO.IN, pull_resistor) + edge = GPIO.RISING if rpi_input['edge'] == 'rise' else GPIO.FALLING + + inputs_same_gpio = list( + [r_inp for r_inp in self.rpi_inputs if self.to_int(r_inp['gpio_pin']) == gpio_pin]) + + if len(inputs_same_gpio) > 1: + GPIO.remove_event_detect(gpio_pin) + for other_input in inputs_same_gpio: + if other_input['edge'] is not edge: + edge = GPIO.BOTH + + if rpi_input['action_type'] == 'output_control': + self._logger.info("Adding GPIO event detect on pin %s with edge: %s", gpio_pin, edge) + GPIO.add_event_detect(gpio_pin, edge, callback=self.handle_gpio_control, bouncetime=200) + if (rpi_input['action_type'] == 'printer_control' and rpi_input['printer_action'] != 'filament'): + GPIO.add_event_detect(gpio_pin, edge, callback=self.handle_printer_action, bouncetime=200) + self._logger.info("Adding PRINTER CONTROL event detect on pin %s with edge: %s", gpio_pin, edge) + + for rpi_input in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): + gpio_pin = self.to_int(rpi_input['gpio_pin']) + if rpi_input['input_pull_resistor'] == 'input_pull_up': + pull_resistor = GPIO.PUD_UP + elif rpi_input['input_pull_resistor'] == 'input_pull_down': + pull_resistor = GPIO.PUD_DOWN + else: + pull_resistor = GPIO.PUD_OFF + GPIO.setup(gpio_pin, GPIO.IN, pull_up_down=pull_resistor) + except Exception as ex: + self.log_error(ex) + + def handle_filamment_detection(self, channel): + try: + for filament_sensor in list(filter( + lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ + 'printer_action'] == 'filament' and self.to_int(item['gpio_pin']) == self.to_int(channel), + self.rpi_inputs)): + if ((filament_sensor['edge'] == 'fall') ^ (GPIO.input(self.to_int(filament_sensor['gpio_pin']))) and + filament_sensor['filament_sensor_enabled']): + last_detected_time = list(filter(lambda item: item['index_id'] == filament_sensor['index_id'], + self.last_filament_end_detected)).pop()['time'] + time_now = time.time() + time_difference = self.to_int(time_now - last_detected_time) + time_out_value = self.to_int(filament_sensor['filament_sensor_timeout']) + if time_difference > time_out_value: + self._logger.info("Detected end of filament.") + for item in self.last_filament_end_detected: + if item['index_id'] == filament_sensor['index_id']: + item['time'] = time_now + for line in self._settings.get(["filament_sensor_gcode"]).split('\n'): + if line: + self._printer.commands(line.strip()) + self._logger.info("Sending GCODE command: %s", line.strip()) + time.sleep(0.2) + for notification in self.notifications: + if notification['filamentChange']: + msg = "Filament change action caused by sensor: " + str(filament_sensor['label']) + self.send_notification(msg) + else: + self._logger.info("Prevented end of filament detection, filament sensor timeout not elapsed.") + except Exception as ex: + self.log_error(ex) + + def start_filament_detection(self): + self.stop_filament_detection() + try: + for filament_sensor in list(filter( + lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ + 'printer_action'] == 'filament', self.rpi_inputs)): + edge = GPIO.RISING if filament_sensor['edge'] == 'rise' else GPIO.FALLING + if GPIO.input(self.to_int(filament_sensor['gpio_pin'])) == (edge == GPIO.RISING): + self._printer.pause_print() + self._logger.info("Started printing with no filament.") + else: + self.last_filament_end_detected.append(dict(index_id=filament_sensor['index_id'], time=0)) + self._logger.info("Adding GPIO event detect on pin %s with edge: %s", filament_sensor['gpio_pin'], + edge) + GPIO.add_event_detect(self.to_int(filament_sensor['gpio_pin']), edge, + callback=self.handle_filamment_detection, bouncetime=200) + except Exception as ex: + self.log_error(ex) + + def stop_filament_detection(self): + try: + self.last_filament_end_detected = [] + for filament_sensor in list(filter( + lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ + 'printer_action'] == 'filament', self.rpi_inputs)): + GPIO.remove_event_detect(self.to_int(filament_sensor['gpio_pin'])) + except Exception as ex: + self.log_error(ex) + + def cancel_all_events_on_queue(self): + for task in self.event_queue: + try: + task['thread'].cancel() + except: + self._logger.warn("Failed to stop task %s.", task) + pass + + def handle_initial_gpio_control(self): + try: + for rpi_input in list( + filter(lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'output_control', + self.rpi_inputs)): + gpio_pin = self.to_int(rpi_input['gpio_pin']) + controlled_io = self.to_int(rpi_input['controlled_io']) + if (rpi_input['edge'] == 'fall') ^ GPIO.input(gpio_pin): + rpi_output = [r_out for r_out in self.rpi_outputs if + self.to_int(r_out['index_id']) == controlled_io].pop() + if rpi_output['output_type'] == 'regular': + if rpi_output['gpio_i2c_enabled']: + val = False if rpi_input['controlled_io_set_value'] == 'low' else True + self.gpio_i2c_write(rpi_output, val) + else: + val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + except Exception as ex: + self.log_error(ex) + pass + + def shell_command(self, command): + try: + stdout = (Popen(command, shell=True, stdout=PIPE).stdout).read() + self._plugin_manager.send_plugin_message(self._identifier, + dict(is_msg=True, msg=stdout, msg_type="success")) + except Exception as ex: + self.log_error(ex) + self._plugin_manager.send_plugin_message(self._identifier, + dict(is_msg=True, msg="Could not execute shell script", msg_type="error")) + + def handle_gpio_control(self, channel): + + try: + self._logger.debug("GPIO event triggered on channel %s", channel) + for rpi_input in list( + filter(lambda item: self.to_int(item['gpio_pin']) == self.to_int(channel), self.rpi_inputs)): + gpio_pin = self.to_int(rpi_input['gpio_pin']) + controlled_io = self.to_int(rpi_input['controlled_io']) + if (rpi_input['edge'] == 'fall') ^ GPIO.input(gpio_pin): + rpi_output = [r_out for r_out in self.rpi_outputs if + self.to_int(r_out['index_id']) == controlled_io].pop() + if rpi_output['output_type'] == 'regular': + if rpi_input['controlled_io_set_value'] == 'toggle': + val = GPIO.LOW if GPIO.input( + self.to_int(rpi_output['gpio_pin'])) == GPIO.HIGH else GPIO.HIGH + else: + val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH + if rpi_output['gpio_i2c_enabled']: + self.gpio_i2c_write(rpi_output, val) + else: + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + for notification in self.notifications: + if notification['gpioAction']: + msg = "GPIO control action caused by input " + str( + rpi_input['label']) + ". Setting GPIO" + str( + rpi_input['controlled_io']) + " to: " + str(rpi_input['controlled_io_set_value']) + self.send_notification(msg) + if rpi_output['output_type'] == 'gcode_output': + self.send_gcode_command(rpi_output['gcode']) + for notification in self.notifications: + if notification['gpioAction']: + msg = "GPIO control action caused by input " + str( + rpi_input['label']) + ". Sending GCODE command" + self.send_notification(msg) + if rpi_output['output_type'] == 'shell_output': + command = rpi_output['shell_script'] + self.shell_command(command) + except Exception as ex: + self.log_error(ex) + pass + + def send_gcode_command(self, command): + for line in command.split('\n'): + if line: + self._printer.commands(line.strip()) + self._logger.info("Sending GCODE command: %s", line.strip()) + time.sleep(0.2) + + def handle_printer_action(self, channel): + try: + for rpi_input in self.rpi_inputs: + if (channel == self.to_int(rpi_input['gpio_pin']) and rpi_input[ + 'action_type'] == 'printer_control' and ( + (rpi_input['edge'] == 'fall') ^ GPIO.input(self.to_int(rpi_input['gpio_pin'])))): + if rpi_input['printer_action'] == 'resume': + self._logger.info("Printer action resume.") + self._printer.resume_print() + elif rpi_input['printer_action'] == 'pause': + self._logger.info("Printer action pause.") + self._printer.pause_print() + elif rpi_input['printer_action'] == 'cancel': + self._logger.info("Printer action cancel.") + self._printer.cancel_print() + elif rpi_input['printer_action'] == 'toggle': + self._logger.info("Printer action toggle.") + if self._printer.is_operational(): + self._printer.toggle_pause_print() + else: + self._printer.connect() + elif rpi_input['printer_action'] == 'start': + self._logger.info("Printer action start.") + self._printer.start_print() + elif rpi_input['printer_action'] == 'toggle_job': + self._logger.info("Printer action toggle_job.") + if self._printer.is_operational(): + if self._printer.is_printing(): + self._printer.cancel_print() + elif self._printer.is_ready(): + self._printer.start_print() + else: + self._printer.connect() + elif rpi_input['printer_action'] == 'stop_temp_hum_control': + self._logger.info("Printer action stopping temperature control.") + for rpi_output in self.rpi_outputs: + if rpi_output['auto_shutdown'] and rpi_output['output_type'] == 'temp_hum_control': + rpi_output['temp_ctr_set_value'] = 0 + self.handle_temp_hum_control() + for notification in self.notifications: + if notification['printer_action']: + msg = "Printer action: " + rpi_input['printer_action'] + " caused by input: " + str( + rpi_input['label']) + self.send_notification(msg) + except Exception as ex: + self.log_error(ex) + pass + + def write_gpio(self, gpio, value, queue_id=None): + try: + GPIO.output(gpio, value) + if queue_id is not None: + self._logger.debug("Running scheduled queue id %s", queue_id) + self._logger.debug("Writing on GPIO: %s value %s", gpio, value) + self.update_ui() + if queue_id is not None: + self.stop_queue_item(queue_id) + except Exception as ex: + template = "An exception of type {0} occurred on {1} when writing on pin {2}. Arguments:\n{3!r}" + message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, gpio, ex.args) + self._logger.warn(message) + pass + + def write_pwm(self, gpio, pwm_value, queue_id=None): + try: + if queue_id is not None: + self._logger.debug("running scheduled queue id %s", queue_id) + for pwm in self.pwm_instances: + if gpio in pwm: + pwm_object = pwm[gpio] + old_pwm_value = pwm['duty_cycle'] if 'duty_cycle' in pwm else -1 + if not self.to_int(old_pwm_value) == self.to_int(pwm_value): + pwm['duty_cycle'] = pwm_value + pwm_object.start(pwm_value) #should be changed back to pwm_object.ChangeDutyCycle() but this + # was causing errors. + self._logger.debug("Writing PWM on gpio: %s value %s", gpio, pwm_value) + self.update_ui() + if queue_id is not None: + self.stop_queue_item(queue_id) + break + except Exception as ex: + self.log_error(ex) + pass + + def get_output_list(self): + result = [] + for rpi_output in self.rpi_outputs: + if rpi_output['output_type'] == 'regular': + result.append(self.to_int(rpi_output['gpio_pin'])) + return result + + def send_notification(self, message): + try: + provider = self._settings.get(["notification_provider"]) + if provider == 'ifttt': + event = self._settings.get(["notification_event_name"]) + api_key = self._settings.get(["notification_api_key"]) + self._logger.debug("Sending notification to: %s with msg: %s with key: %s", provider, message, + api_key) + try: + res = self.ifttt_notification(message, event, api_key) + except requests.exceptions.ConnectionError: + self._logger.info("Error: Could not connect to IFTTT") + except requests.exceptions.HTTPError: + self._logger.info("Error: Received invalid response") + except requests.exceptions.Timeout: + self._logger.info("Error: Request timed out") + except requests.exceptions.TooManyRedirects: + self._logger.info("Error: Too many redirects") + except requests.exceptions.RequestException as reqe: + self._logger.info("Error: {e}".format(e=reqe)) + if res.status_code != requests.codes['ok']: + try: + j = res.json() + except ValueError: + self._logger.info('Error: Could not parse server response. Event not sent') + for err in j['errors']: + self._logger.info('Error: {}'.format(err['message'])) + except Exception as ex: + self.log_error(ex) + pass + + def ifttt_notification(self, message, event, api_key): + url = "https://maker.ifttt.com/trigger/{e}/with/key/{k}/".format(e=event, k=api_key) + payload = {'value1': message} + return requests.post(url, data=payload) + + # ~~ EventPlugin mixin + def on_event(self, event, payload): + self._logger.info("Event Logging Test: %s", event) + if event == Events.CONNECTED: + self.update_ui() + + if event == Events.CLIENT_OPENED: + self.update_ui() + + if event == Events.PRINT_RESUMED: + self.start_filament_detection() + + if event == Events.PRINT_STARTED: + self.print_complete = False + self.cancel_all_events_on_queue() + self.event_queue = [] + self.start_filament_detection() + for rpi_output in self.rpi_outputs: + if rpi_output['auto_startup']: + delay_seconds = self.get_startup_delay_from_output(rpi_output) + self.schedule_auto_startup_outputs(rpi_output, delay_seconds) + if rpi_output['toggle_timer']: + if rpi_output['output_type'] == 'regular' or rpi_output['output_type'] == 'pwm': + self.toggle_output(rpi_output['index_id'], True) + if self.is_hour(rpi_output['shutdown_time']): + shutdown_delay_seconds = self.get_shutdown_delay_from_output(rpi_output) + self.schedule_auto_shutdown_outputs(rpi_output, shutdown_delay_seconds) + self.run_tasks() + self.update_ui() + + elif event == Events.PRINT_DONE: + self.stop_filament_detection() + self.print_complete = True + for rpi_output in self.rpi_outputs: + shutdown_time = rpi_output['shutdown_time'] + if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: + rpi_output['duty_cycle'] = rpi_output['default_duty_cycle'] + if rpi_output['auto_shutdown'] and not self.is_hour(shutdown_time): + delay_seconds = self.to_float(shutdown_time) + self.schedule_auto_shutdown_outputs(rpi_output, delay_seconds) + self.run_tasks() + self.update_ui() + + elif event in (Events.PRINT_CANCELLED, Events.PRINT_FAILED): + self.stop_filament_detection() + self.cancel_all_events_on_queue() + self.event_queue = [] + for rpi_output in self.rpi_outputs: + if rpi_output['shutdown_on_failed']: + shutdown_time = rpi_output['shutdown_time'] + if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: + rpi_output['duty_cycle'] = rpi_output['default_duty_cycle'] + if rpi_output['auto_shutdown'] and not self.is_hour(shutdown_time): + delay_seconds = self.to_float(shutdown_time) + self.schedule_auto_shutdown_outputs(rpi_output, delay_seconds) + if rpi_output['output_type'] == 'temp_hum_control': + rpi_output['temp_ctr_set_value'] = 0 + self.run_tasks() + + if event == Events.PRINT_DONE: + for notification in self.notifications: + if notification['printFinish']: + file_name = os.path.basename(payload["path"]) + elapsed_time_in_seconds = payload["time"] + elapsed_time = octoprint.util.get_formatted_timedelta(timedelta(seconds=elapsed_time_in_seconds)) + msg = "Print job finished: " + file_name + "finished printing in " + file_name, elapsed_time + self.send_notification(msg) + + if event in (Events.ERROR, Events.DISCONNECTED): + self._logger.info("Detected Error or Disconnect in %s will call listeners for shutdown_on_error!", event) + for rpi_output in self.rpi_outputs: + if rpi_output['shutdown_on_error']: + self._logger.debug("Schedule shutdown for: %s", rpi_output["index_id"]) + self.schedule_auto_shutdown_outputs(rpi_output, 0) + self.run_tasks() + + if event == Events.PRINTER_STATE_CHANGED: + if "error" in payload["state_string"].lower(): + self._logger.info("Detected Error in %s id: %s state: %s will call listeners for shutdown_on_error!", event, payload["state_id"], payload["state_string"]) + for rpi_output in self.rpi_outputs: + if rpi_output['shutdown_on_error']: + self._logger.debug("Schedule shutdown for: %s", rpi_output["index_id"]) + self.schedule_auto_shutdown_outputs(rpi_output, 0) + self.run_tasks() + + def run_tasks(self): + for task in self.event_queue: + if not task['thread'].is_alive(): + task['thread'].start() + + def schedule_auto_shutdown_outputs(self, rpi_output, shutdown_delay_seconds): + sufix = 'auto_shutdown' + if rpi_output['output_type'] == 'regular': + self._logger.debug("schedule_auto_shutdown_outputs output is of type regular") + value = True if rpi_output['active_low'] else False + self.add_regular_output_to_queue(shutdown_delay_seconds, rpi_output, value, sufix) + if rpi_output['output_type'] == 'ledstrip': + self.ledstrip_set_rgb(rpi_output) + if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: + value = 0 + self.add_pwm_output_to_queue(shutdown_delay_seconds, rpi_output, value, sufix) + if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: + self.schedule_pwm_duty_on_queue(shutdown_delay_seconds, rpi_output, 0, sufix) + if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): + self.add_neopixel_output_to_queue(rpi_output, shutdown_delay_seconds, 0, 0, 0, sufix) + if rpi_output['output_type'] == 'temp_hum_control': + value = 0 + self.add_temperature_output_temperature_queue(shutdown_delay_seconds, rpi_output, value, sufix) + self._logger.debug("Events scheduled to run %s", self.event_queue) + + def ledstrip_set_rgb(self, rpi_output, rgb=None): + clk = rpi_output["ledstrip_gpio_clk"] + data = rpi_output["ledstrip_gpio_dat"] + if clk is not None and data is not None: + ledstrip = LEDStrip(self.to_int(clk), self.to_int(data)) + if rgb is None: + red, green, blue = self.get_color_from_rgb(rpi_output['default_ledstrip_color']) + else: + red, green, blue = self.get_color_from_rgb(rgb) + + self._logger.info("LEDSTRIP set rgb color: %s, %s, %s", red, green, blue) + ledstrip.setcolourrgb(self.to_int(red), self.to_int(green), self.to_int(blue)) + + def start_outpus_with_server(self): + for rpi_output in self.rpi_outputs: + if rpi_output['startup_with_server']: + gpio = self.to_int(rpi_output['gpio_pin']) + if rpi_output['output_type'] == 'regular': + value = False if rpi_output['active_low'] else True + if rpi_output['gpio_i2c_enabled']: + self.gpio_i2c_write(rpi_output, value) + else: + self.write_gpio(gpio, value) + if rpi_output['output_type'] == 'ledstrip': + self.ledstrip_set_rgb(rpi_output) + if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: + value = self.to_int(rpi_output['default_duty_cycle']) + self.write_pwm(gpio, value) + if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): + red, green, blue = self.get_color_from_rgb(rpi_output['default_neopixel_color']) + led_count = rpi_output['neopixel_count'] + led_brightness = rpi_output['neopixel_brightness'] + address = rpi_output['microcontroller_address'] + index_id = self.to_int(rpi_output['index_id']) + neopixel_direct = rpi_output['output_type'] == 'neopixel_direct' + self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, + green, blue, address, neopixel_direct, index_id) + if rpi_output['output_type'] == 'temp_hum_control': + rpi_output['temp_ctr_set_value'] = rpi_output['temp_ctr_default_value'] + + def schedule_auto_startup_outputs(self, rpi_output, delay_seconds): + sufix = 'auto_startup' + if rpi_output['output_type'] == 'regular': + value = False if rpi_output['active_low'] else True + self.add_regular_output_to_queue(delay_seconds, rpi_output, value, sufix) + if rpi_output['output_type'] == 'ledstrip': + self.ledstrip_set_rgb(rpi_output) + if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: + value = self.to_int(rpi_output['default_duty_cycle']) + self.add_pwm_output_to_queue(delay_seconds, rpi_output, value, sufix) + if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): + red, green, blue = self.get_color_from_rgb(rpi_output['default_neopixel_color']) + self.add_neopixel_output_to_queue(rpi_output, delay_seconds, red, green, blue, sufix) + if rpi_output['output_type'] == 'temp_hum_control': + value = rpi_output['temp_ctr_default_value'] + self.add_temperature_output_temperature_queue(delay_seconds, rpi_output, value, sufix) + self._logger.debug("Events scheduled to run %s", self.event_queue) + + def get_color_from_rgb(self, stringColor): + stringColor = stringColor.replace('rgb(', '') + red = stringColor[:stringColor.index(',')] + stringColor = stringColor[stringColor.index(',') + 1:] + green = stringColor[:stringColor.index(',')] + stringColor = stringColor[stringColor.index(',') + 1:] + blue = stringColor[:stringColor.index(')')] + return red, green, blue + + def get_shutdown_delay_from_output(self, rpi_output): + shutdown_time = rpi_output['shutdown_time'] + + shut_down_date_time = self.create_date(shutdown_time) + + if shut_down_date_time < datetime.now(): + shut_down_date_time = shut_down_date_time + timedelta(days=1) + + delay_seconds = (shut_down_date_time - datetime.now()).total_seconds() + + return delay_seconds + + def add_neopixel_output_to_queue(self, rpi_output, delay_seconds, red, green, blue, sufix): + gpio_pin = rpi_output['gpio_pin'] + ledCount = rpi_output['neopixel_count'] + ledBrightness = rpi_output['neopixel_brightness'] + address = rpi_output['microcontroller_address'] + neopixel_direct = rpi_output['output_type'] == 'neopixel_direct' + index_id = self.to_int(rpi_output['index_id']) + + queue_id = '{0!s}_{1!s}'.format(index_id, sufix) + + self._logger.debug("Scheduling neopixel output id %s for on %s delay_seconds", queue_id, delay_seconds) + + thread = threading.Timer(delay_seconds, self.send_neopixel_command, + args=[gpio_pin, ledCount, ledBrightness, red, green, blue, address, neopixel_direct, + index_id, queue_id]) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def add_pwm_output_to_queue(self, delay_seconds, rpi_output, value, sufix): + queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) + + self._logger.debug("Scheduling pwm output id %s for on %s delay_seconds", queue_id, delay_seconds) + + thread = threading.Timer(delay_seconds, self.write_pwm, + args=[self.to_int(rpi_output['gpio_pin']), value, queue_id]) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def schedule_pwm_duty_on_queue(self, delay_seconds, rpi_output, value, sufix): + queue_id = '{0!s}_{1!s}_{2!s}'.format(rpi_output['index_id'], "pwm_linked_temp", sufix) + thread = threading.Timer(delay_seconds, self.set_pwm_duty_cycle, args=[rpi_output, value, queue_id]) + + self._logger.debug("Scheduling pwm linked temp output id %s on %s delay_seconds", queue_id, delay_seconds) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def set_pwm_duty_cycle(self, rpi_output, value, queue_id): + rpi_output['duty_cycle'] = value + if queue_id is not None: + self.stop_queue_item(queue_id) + + def add_regular_output_to_queue(self, delay_seconds, rpi_output, value, sufix): + queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) + + self._logger.debug("Scheduling regular output id %s on %s delay_seconds", queue_id, delay_seconds) + + if rpi_output['gpio_i2c_enabled']: + thread = threading.Timer(delay_seconds, self.gpio_i2c_write, + args=[rpi_output, value, queue_id]) + else: + thread = threading.Timer(delay_seconds, self.write_gpio, + args=[self.to_int(rpi_output['gpio_pin']), value, queue_id]) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def add_temperature_output_temperature_queue(self, delay_seconds, rpi_output, value, sufix): + queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) + self._logger.debug("Scheduling temperature control id %s on %s delay_seconds", queue_id, delay_seconds) + + thread = threading.Timer(delay_seconds, self.write_temperature_to_output, + args=[self.to_int(rpi_output['index_id']), value, queue_id]) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def write_temperature_to_output(self, rpi_output_index, value, queue_id=None): + try: + rpi_output = [r_out for r_out in self.rpi_outputs if + self.to_int(r_out['index_id']) == rpi_output_index].pop() + + if rpi_output['output_type'] == 'temp_hum_control': + rpi_output['temp_ctr_set_value'] = value + + if queue_id is not None: + self._logger.debug("running scheduled queue id %s", queue_id) + self._logger.debug("Setting temperature to output index: %s value %s", rpi_output['index_id'], value) + + self.update_ui() + if queue_id is not None: + self.stop_queue_item(queue_id) + + except Exception as ex: + template = "An exception of type {0} occurred on {1}. Arguments:\n{3!r}" + message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, ex.args) + self._logger.warn(message) + pass + + def get_startup_delay_from_output(self, rpi_output): + start_up_time = rpi_output['startup_time'] + if self.is_hour(start_up_time): + start_up_date_time = self.create_date(start_up_time) + if start_up_date_time < datetime.now(): + delay_seconds = 0.0 + else: + delay_seconds = (start_up_date_time - datetime.now()).total_seconds() + else: + delay_seconds = self.to_float(rpi_output['startup_time']) + return delay_seconds + + # ~~ SettingsPlugin mixin + def on_settings_save(self, data): + outputsBeforeSave = self.get_output_list() + octoprint.plugin.SettingsPlugin.on_settings_save(self, data) + self.rpi_outputs = self._settings.get(["rpi_outputs"]) + self.rpi_inputs = self._settings.get(["rpi_inputs"]) + self.notifications = self._settings.get(["notifications"]) + outputsAfterSave = self.get_output_list() + + commonPins = list(set(outputsBeforeSave) & set(outputsAfterSave)) + + for pin in (pin for pin in outputsBeforeSave if pin not in commonPins): + self.clear_channel(pin) + + self.rpi_outputs_not_changed = commonPins + self.clear_gpio() + + self._logger.debug("rpi_outputs: %s", self.rpi_outputs) + self._logger.debug("rpi_inputs: %s", self.rpi_inputs) + self.setup_gpio() + self.configure_gpio() + self.generate_temp_hum_control_status() + + def get_settings_defaults(self): + return dict(rpi_outputs=[], rpi_inputs=[], + filament_sensor_gcode="G91 ;Set Relative Mode \n" + "G1 E-5.000000 F500 ;Retract 5mm\n" + "G1 Z15 F300 ;move Z up 15mm\n" + "G90 ;Set Absolute Mode\n " + "G1 X20 Y20 F9000 ;Move to hold position\n" + "G91 ;Set Relative Mode\n" + "G1 E-40 F500 ;Retract 40mm\n" + "M0 ;Idle Hold\n" + "G90 ;Set Absolute Mode\n" + "G1 F5000 ;Set speed limits\n" + "G28 X0 Y0 ;Home X Y\n" + "M82 ;Set extruder to Absolute Mode\n" + "G92 E0 ;Set Extruder to 0", + use_sudo=True, neopixel_dma=10, debug=False, gcode_control=False, debug_temperature_log=False, + use_board_pin_number=False, notification_provider="disabled", notification_api_key="", + notification_event_name="printer_event", notifications=[{ + 'printFinish' : True, + 'filamentChange' : True, + 'printer_action' : True, + 'temperatureAction': True, 'gpioAction': True + }]) + + # ~~ TemplatePlugin + def get_template_configs(self): + return [dict(type="settings", custom_bindings=True), dict(type="tab", custom_bindings=True), + dict(type="navbar", custom_bindings=True, suffix="_1", classes=["dropdown"]), + dict(type="navbar", custom_bindings=True, template="enclosure_navbar_input.jinja2", suffix="_2", + classes=["dropdown"])] + + # ~~ AssetPlugin mixin + def get_assets(self): + return dict(js=["js/enclosure.js", "js/bootstrap-colorpicker.min.js"], + css=["css/bootstrap-colorpicker.css", "css/enclosure.css"]) + + # ~~ Softwareupdate hook + def get_update_information(self): + return dict(enclosure=dict(displayName="Enclosure Plugin", displayVersion=self._plugin_version, + # version check: github repository + type="github_release", user="vitormhenrique", repo="OctoPrint-Enclosure", current=self._plugin_version, + # update method: pip + pip="https://github.com/vitormhenrique/OctoPrint-Enclosure/archive/{target_version}.zip")) + + def hook_gcode_queuing(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs): + if self._settings.get(["gcode_control"]) is False: + return + + if cmd.strip().startswith("ENC"): + self._logger.debug("Gcode queuing: %s", cmd) + index_id = self.to_int(self.get_gcode_value(cmd, 'O')) + for output in [item for item in self.rpi_outputs if item['index_id'] == index_id]: + if output['output_type'] == 'regular': + set_value = self.to_int(self.get_gcode_value(cmd, 'S')) + set_value = self.constrain(set_value, 0, 1) + value = True if set_value == 1 else False + value = (not value) if output['active_low'] else value + if output['gpio_i2c_enabled']: + self.gpio_i2c_write(output, value) + else: + self.write_gpio(self.to_int(output['gpio_pin']), value) + comm_instance._log("Setting REGULAR output %s to value %s" % (index_id, value)) + return + if output['output_type'] == 'pwm': + set_value = self.to_int(self.get_gcode_value(cmd, 'S')) + set_value = self.constrain(set_value, 0, 100) + output['duty_cycle'] = set_value + self.write_pwm(self.to_int(output['gpio_pin']), set_value) + comm_instance._log("Setting PWM output %s to value %s" % (index_id, set_value)) + return + if output['output_type'] == 'neopixel_indirect' or output['output_type'] == 'neopixel_direct': + red = self.get_gcode_value(cmd, 'R') + green = self.get_gcode_value(cmd, 'G') + blue = self.get_gcode_value(cmd, 'B') + + led_count = output['neopixel_count'] + led_brightness = output['neopixel_brightness'] + address = output['microcontroller_address'] + + index_id = self.to_int(output['index_id']) + + neopixel_direct = output['output_type'] == 'neopixel_direct' + + self.send_neopixel_command(self.to_int(output['gpio_pin']), led_count, led_brightness, red, green, + blue, address, neopixel_direct, index_id) + comm_instance._log( + "Setting NEOPIXEL output %s to red: %s green: %s blue: %s" % (index_id, red, green, blue)) + return + if output['output_type'] == 'temp_hum_control': + set_value = self.to_float(self.get_gcode_value(cmd, 'S')) + should_wait = self.to_int(self.get_gcode_value(cmd, 'W')) + if should_wait == 1 and self._printer.is_printing(): + self._printer.pause_print() + self.waiting_temperature.append(index_id) + output['temp_ctr_set_value'] = set_value + self.update_ui_set_temperature() + self.handle_temp_hum_control() + comm_instance._log("Setting TEMP/HUM control output %s to value %s" % (index_id, set_value)) + return + + def get_graph_data(self, comm, parsed_temps): + for sensor in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): + if sensor["show_graph_temp"]: + parsed_temps[str(sensor["label"])] = (sensor['temp_sensor_temp'], None) + if sensor["show_graph_humidity"]: + parsed_temps[str(sensor["label"])+" Humidity"] = (sensor['temp_sensor_humidity'], None) + + return parsed_temps + + +__plugin_name__ = "Enclosure Plugin" +__plugin_pythoncompat__ = ">=2.7,<4" + + +def __plugin_load__(): + global __plugin_implementation__ + __plugin_implementation__ = EnclosurePlugin() + + global __plugin_hooks__ + __plugin_hooks__ = { + "octoprint.comm.protocol.gcode.queuing" : __plugin_implementation__.hook_gcode_queuing, + "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information, + "octoprint.comm.protocol.temperatures.received": (__plugin_implementation__.get_graph_data, 1) + } diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index 41b9057..b8ce97a 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -221,7 +221,7 @@ - Choose if output should turn off automatomatically when print finishes + Choose if output should turn off automatically when print finishes
@@ -231,7 +231,7 @@ - Choose if output should turn off automatomatically when print is canceled or fails + Choose if output should turn off automatically when print is canceled or fails
@@ -256,7 +256,7 @@ - Choose if output should turn off automatomatically when an error or disconnect was detected. + Choose if output should turn off automatically when an error or disconnect was detected. -- 2.39.5 From 7d40dec319b544c5629667b1722416afdb688a9a Mon Sep 17 00:00:00 2001 From: Daniel Korgel Date: Wed, 13 Oct 2021 20:37:37 +0200 Subject: [PATCH 066/104] Removed debug logs --- octoprint_enclosure/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 01217f0..08fef32 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -1760,7 +1760,6 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP # ~~ EventPlugin mixin def on_event(self, event, payload): - self._logger.info("Event Logging Test: %s", event) if event == Events.CONNECTED: self.update_ui() @@ -1851,7 +1850,6 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP def schedule_auto_shutdown_outputs(self, rpi_output, shutdown_delay_seconds): sufix = 'auto_shutdown' if rpi_output['output_type'] == 'regular': - self._logger.debug("schedule_auto_shutdown_outputs output is of type regular") value = True if rpi_output['active_low'] else False self.add_regular_output_to_queue(shutdown_delay_seconds, rpi_output, value, sufix) if rpi_output['output_type'] == 'ledstrip': -- 2.39.5 From fc66d3a92bccf72c582be1870b222d03c54b368a Mon Sep 17 00:00:00 2001 From: Daniel Korgel Date: Wed, 13 Oct 2021 20:39:45 +0200 Subject: [PATCH 067/104] Fixed Line Ending in __init__.py --- octoprint_enclosure/__init__.py | 4360 +++++++++++++++---------------- 1 file changed, 2180 insertions(+), 2180 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 08fef32..b7ce185 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -1,2180 +1,2180 @@ -# coding=utf-8 -from __future__ import absolute_import -from octoprint.events import eventManager, Events -from octoprint.util import RepeatedTimer -from subprocess import Popen, PIPE -from .ledstrip import LEDStrip -import octoprint.plugin -import RPi.GPIO as GPIO -from flask import jsonify, request, make_response, Response -from octoprint.server.util.flask import restricted_access -from werkzeug.exceptions import BadRequest -import time -import sys -import glob -import os -from datetime import datetime -from datetime import timedelta -import octoprint.util -import requests -import inspect -import threading -import json -import copy -from smbus2 import SMBus -import struct - - -#Function that returns Boolean output state of the GPIO inputs / outputs -def PinState_Boolean(pin, ActiveLow) : - try: - state = GPIO.input(pin) - if ActiveLow and not state: return True - if not ActiveLow and state: return True - return False - except: - return "ERROR: Unable to read pin" - -#Function that returns human-readable output state of the GPIO inputs / outputs -def PinState_Human(pin, ActiveLow): - PinState = PinState_Boolean(pin, ActiveLow) - if PinState == True : - return " ON " - elif PinState == False: - return " OFF " - else: - return PinState - -#Translates the Pull-Up/Pull-Down GPIO resistor setting to ActiveLow/ActiveHigh boolean -def CheckInputActiveLow(Input_Pull_Resistor): - #input_pull_up - #input_pull_down - if Input_Pull_Resistor == "input_pull_up": - return True - else: - return False - -class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SettingsPlugin, - octoprint.plugin.AssetPlugin, octoprint.plugin.BlueprintPlugin, - octoprint.plugin.EventHandlerPlugin): - rpi_outputs = [] - rpi_inputs = [] - waiting_temperature = [] - rpi_outputs_not_changed = [] - notifications = [] - pwm_instances = [] - event_queue = [] - temp_hum_control_status = [] - temperature_sensor_data = [] - last_filament_end_detected = [] - print_complete = False - development_mode = False - dummy_value = 30.0 - dummy_delta = 0.5 - - def start_timer(self): - """ - Function to start timer that checks enclosure temperature - """ - - self._check_temp_timer = RepeatedTimer(10, self.check_enclosure_temp, None, None, True) - self._check_temp_timer.start() - - @staticmethod - def to_float(value): - """Converts value to flow - Arguments: - value {any} -- value to be - Returns: - float -- value converted - """ - - try: - val = float(value) - return val - except: - return 0 - - @staticmethod - def to_int(value): - try: - val = int(value) - return val - except: - return 0 - - @staticmethod - def is_hour(value): - try: - datetime.strptime(value, '%H:%M') - return True - except: - return False - - @staticmethod - def create_date(value): - temp_string = datetime.now().strftime('%m/%d/%Y') + " " + value - return datetime.strptime(temp_string, '%m/%d/%Y %H:%M') - - @staticmethod - def constrain(n, minn, maxn): - return max(min(maxn, n), minn) - - @staticmethod - def get_gcode_value(command_string, gcode): - semicolon = command_string.find(';') - if not semicolon == -1: - command_string = command_string[:semicolon] - - for command in command_string.split(' '): - index = command.upper().find(gcode.upper()) - if not index == -1: - return command.replace(gcode, '') - return -1 - - # ~~ StartupPlugin mixin - def on_after_startup(self): - self.pwm_instances = [] - self.event_queue = [] - self.rpi_outputs_not_changed = [] - self.rpi_outputs = self._settings.get(["rpi_outputs"]) - self.rpi_inputs = self._settings.get(["rpi_inputs"]) - self.notifications = self._settings.get(["notifications"]) - self.generate_temp_hum_control_status() - self.setup_gpio() - self.configure_gpio() - self.update_ui() - self.start_outpus_with_server() - self.handle_initial_gpio_control() - self.start_timer() - self.print_complete = False - - def get_settings_version(self): - return 10 - - def on_settings_migrate(self, target, current=None): - self._logger.warn("######### current settings version %s target settings version %s #########", current, target) - self._logger.info("######### Current settings #########") - self._logger.info("rpi_outputs: %s", self.rpi_outputs) - self._logger.info("rpi_inputs: %s", self.rpi_inputs) - self._logger.info("######### End Current Settings #########") - if current >= 4 and target == 10: - self._logger.warn("######### migrating settings to v10 #########") - old_outputs = self._settings.get(["rpi_outputs"]) - old_inputs = self._settings.get(["rpi_inputs"]) - for rpi_output in old_outputs: - if 'shutdown_on_failed' not in rpi_output: - rpi_output['shutdown_on_failed'] = False - if 'shell_script' not in rpi_output: - rpi_output['shell_script'] = "" - if 'gpio_i2c_enabled' not in rpi_output: - rpi_output['gpio_i2c_enabled'] = False - if 'gpio_i2c_bus' not in rpi_output: - rpi_output['gpio_i2c_bus'] = 1 - if 'gpio_i2c_address' not in rpi_output: - rpi_output['gpio_i2c_address'] = 1 - if 'gpio_i2c_register' not in rpi_output: - rpi_output['gpio_i2c_register'] = 1 - if 'gpio_i2c_data_on' not in rpi_output: - rpi_output['gpio_i2c_data_on'] = 1 - if 'gpio_i2c_data_off' not in rpi_output: - rpi_output['gpio_i2c_data_off'] = 0 - if 'gpio_i2c_register_status' not in rpi_output: - rpi_output['gpio_i2c_register_status'] = 1 - if 'shutdown_on_error' not in rpi_output: - rpi_output['shutdown_on_error'] = False - self._settings.set(["rpi_outputs"], old_outputs) - - old_inputs = self._settings.get(["rpi_inputs"]) - for rpi_input in old_inputs: - if 'temp_i2c_bus' not in rpi_input: - rpi_input['temp_i2c_bus'] = 1 - if 'temp_i2c_address' not in rpi_input: - rpi_input['temp_i2c_address'] = 1 - if 'temp_i2c_register' not in rpi_input: - rpi_input['temp_i2c_register'] = 1 - if 'show_graph_temp' not in rpi_input: - rpi_input['show_graph_temp'] = False - if 'show_graph_humidity' not in rpi_input: - rpi_input['show_graph_humidity'] = False - self._settings.set(["rpi_inputs"], old_inputs) - else: - self._logger.warn("######### settings not compatible #########") - self._settings.set(["rpi_outputs"], []) - self._settings.set(["rpi_inputs"], []) - self.rpi_inputs = self._settings.get(["rpi_inputs"]) - - #Scan all configured inputs and outputs and return the pin value - @octoprint.plugin.BlueprintPlugin.route("/ReadPin/", methods=["GET"]) - def ReadSinglePin(self, identifier): - Resp = [] - MatchFound = False - for rpi_input in self.rpi_inputs: - if identifier == self.to_int(rpi_input['gpio_pin']): - MatchFound = True - ConfiguredAs = "Input" - ActiveLow = CheckInputActiveLow(rpi_input['input_pull_resistor']) - pin = self.to_int(rpi_input['gpio_pin']) - val = PinState_Human(pin,ActiveLow) - label = rpi_input['label'] - Resp.append(dict(Configured_As=ConfiguredAs, label=label, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['gpio_pin']): - MatchFound = True - ConfiguredAs = "Output" - ActiveLow = CheckInputActiveLow(rpi_output['active_low']) - pin = self.to_int(rpi_output['gpio_pin']) - if rpi_output['gpio_i2c_enabled']: - b = self.gpio_i2c_input(rpi_output, ActiveLow) - val = " ON " if b else " OFF " - else: - val = PinState_Human(pin,ActiveLow) - label = rpi_output['label'] - Resp.append(dict(Configured_As=ConfiguredAs, label=label, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) - if MatchFound == False: - pin = int(identifier) - ConfiguredAs = "Unknown" - ActiveLow = "Unknown" - try: - val = GPIO.input(pin) - except: - val = "GPIO pin not initialized." - Resp.append(dict(Configured_As=ConfiguredAs, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) - return Response(json.dumps(Resp), mimetype='application/json') - - # ~~ Blueprintplugin mixin - @octoprint.plugin.BlueprintPlugin.route("/inputs", methods=["GET"]) - def get_inputs(self): - inputs = [] - for rpi_input in self.rpi_inputs: - index = self.to_int(rpi_input['index_id']) - label = rpi_input['label'] - ActiveLow = CheckInputActiveLow(rpi_input['input_pull_resistor']) - pin = self.to_int(rpi_input['gpio_pin']) - val = PinState_Human(pin,ActiveLow) - inputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, State=val)) - return Response(json.dumps(inputs), mimetype='application/json') - - @octoprint.plugin.BlueprintPlugin.route("/inputs/", methods=["GET"]) - def get_input_status(self, identifier): - for rpi_input in self.rpi_inputs: - if identifier == self.to_int(rpi_input['index_id']): - return Response(json.dumps(rpi_input), mimetype='application/json') - return make_response('', 404) - - - @octoprint.plugin.BlueprintPlugin.route("/temperature/", methods=["PATCH"]) - @restricted_access - def set_enclosure_temp_humidity(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'temperature' not in data: - return make_response("missing temperature attribute", 406) - - set_value = data["temperature"] - - for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == identifier]: - temp_hum_control['temp_ctr_set_value'] = set_value - - self.handle_temp_hum_control() - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/filament/", methods=["PATCH"]) - @restricted_access - def set_filament_sensor(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'status' not in data: - return make_response("missing status attribute", 406) - - value = data["status"] - - for sensor in self.rpi_inputs: - if identifier == self.to_int(sensor['index_id']): - sensor['filament_sensor_enabled'] = value - self._logger.info("Setting filament sensor for input %s to : %s", str(identifier), value) - self._settings.set(["rpi_inputs"], self.rpi_inputs) - return make_response('', 204) - - @octoprint.plugin.BlueprintPlugin.route("/outputs", methods=["GET"]) - def get_outputs(self): - outputs = [] - for rpi_output in self.rpi_outputs: - if rpi_output['output_type'] == 'regular': - index = self.to_int(rpi_output['index_id']) - label = rpi_output['label'] - pin = self.to_int(rpi_output['gpio_pin']) - ActiveLow = rpi_output['active_low'] - if rpi_output['gpio_i2c_enabled']: - b = self.gpio_i2c_input(rpi_output, ActiveLow) - val = " ON " if b else " OFF " - else: - val = PinState_Human(pin,ActiveLow) - outputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, State=val)) - return Response(json.dumps(outputs), mimetype='application/json') - - - @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["GET"]) - def get_output_status(self, identifier): - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['index_id']): - out = copy.deepcopy(rpi_output) - pin = self.to_int(rpi_output['gpio_pin']) - if rpi_output['gpio_i2c_enabled']: - out['current_value'] = self.gpio_i2c_input(rpi_output, rpi_output['active_low']) - else: - out['current_value'] = PinState_Boolean(pin, rpi_output['active_low'] ) - return Response(json.dumps(out), mimetype='application/json') - return make_response('', 404) - - - @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["PATCH"]) - @restricted_access - def set_io(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'status' not in data: - return make_response("missing status attribute", 406) - - value = data["status"] - - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['index_id']): - val = (not value) if rpi_output['active_low'] else value - if rpi_output['gpio_i2c_enabled']: - self.gpio_i2c_write(rpi_output, val) - else: - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-startup", methods=["PATCH"]) - @restricted_access - def set_auto_startup(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'status' not in data: - return make_response("missing status attribute", 406) - - value = data["status"] - - if not value: - suffix = 'auto_startup' - queue_id = '{0!s}_{1!s}'.format(str(identifier), suffix) - self.stop_queue_item(queue_id) - for output in self.rpi_outputs: - if identifier == self.to_int(output['index_id']): - output['auto_startup'] = value - self._logger.info("Setting auto startup for output %s to : %s", str(identifier), value) - self._settings.set(["rpi_outputs"], self.rpi_outputs) - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-shutdown", methods=["PATCH"]) - @restricted_access - def set_auto_shutdown(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'status' not in data: - return make_response("missing status attribute", 406) - - value = data["status"] - - if not value: - suffix = 'auto_shutdown' - queue_id = '{0!s}_{1!s}'.format(str(identifier), suffix) - self.stop_queue_item(queue_id) - - for output in self.rpi_outputs: - if identifier == self.to_int(output['index_id']): - output['auto_shutdown'] = value - self._logger.info("Setting auto shutdown for output %s to : %s", str(identifier), value) - self._settings.set(["rpi_outputs"], self.rpi_outputs) - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/pwm/", methods=["PATCH"]) - @restricted_access - def set_pwm(self, identifier): - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'duty_cycle' not in data: - return make_response("missing duty_cycle attribute", 406) - - set_value = self.to_int(data['duty_cycle']) - for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == identifier]: - rpi_output['duty_cycle'] = set_value - rpi_output['new_duty_cycle'] = "" - gpio = self.to_int(rpi_output['gpio_pin']) - self.write_pwm(gpio, set_value) - return make_response('', 204) - - @octoprint.plugin.BlueprintPlugin.route("/rgb-led/", methods=["PATCH"]) - @restricted_access - def set_ledstrip_color(self, identifier): - """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'rgb' not in data: - return make_response("missing rgb attribute", 406) - rgb = data['rgb'] - - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['index_id']): - self.ledstrip_set_rgb(rpi_output, rgb) - - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/neopixel/", methods=["PATCH"]) - @restricted_access - def set_neopixel(self, identifier): - """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" - if "application/json" not in request.headers["Content-Type"]: - return make_response("expected json", 400) - try: - data = request.json - except BadRequest: - return make_response("malformed request", 400) - - if 'red' not in data: - return make_response("missing red attribute", 406) - if 'green' not in data: - return make_response("missing green attribute", 406) - if 'blue' not in data: - return make_response("missing blue attribute", 406) - - red = data['red'] - green = data['green'] - blue = data['blue'] - - for rpi_output in self.rpi_outputs: - if identifier == self.to_int(rpi_output['index_id']): - led_count = rpi_output['neopixel_count'] - led_brightness = rpi_output['neopixel_brightness'] - address = rpi_output['microcontroller_address'] - - neopixel_dirrect = rpi_output['output_type'] == 'neopixel_direct' - - self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, green, - blue, address, neopixel_dirrect, identifier) - - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/clear-gpio", methods=["POST"]) - @restricted_access - def clear_gpio_mode(self): - GPIO.cleanup() - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/update", methods=["POST"]) - @restricted_access - def update_ui_requested(self): - self.update_ui() - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/shell/", methods=["POST"]) - @restricted_access - def send_shell_command(self, identifier): - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() - - command = rpi_output['shell_script'] - self.shell_command(command) - return make_response('', 204) - - - @octoprint.plugin.BlueprintPlugin.route("/gcode/", methods=["POST"]) - @restricted_access - def requested_gcode_command(self, identifier): - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() - self.send_gcode_command(rpi_output['gcode']) - return make_response('', 204) - - - - - - """ - DEPRECATION - This API will be deprecated in a future version - """ - - # ~~ Blueprintplugin mixin - @octoprint.plugin.BlueprintPlugin.route("/setEnclosureTempHum", methods=["GET"]) - def set_enclosure_temp_humidity_old(self): - set_value = self.to_float(request.values["set_temperature"]) - index_id = self.to_int(request.values["index_id"]) - - for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == index_id]: - temp_hum_control['temp_ctr_set_value'] = set_value - - self.handle_temp_hum_control() - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/clearGPIOMode", methods=["GET"]) - def clear_gpio_mode_old(self): - GPIO.cleanup() - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/updateUI", methods=["GET"]) - def update_ui_requested_old(self): - self.update_ui() - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/getOutputStatus", methods=["GET"]) - def get_output_status_old(self): - gpio_status = [] - for rpi_output in self.rpi_outputs: - if rpi_output['output_type'] == 'regular': - pin = self.to_int(rpi_output['gpio_pin']) - ActiveLow = rpi_output['active_low'] - if rpi_output['gpio_i2c_enabled']: - val = self.gpio_i2c_input(rpi_output, rpi_output['active_low']) - else: - val = PinState_Boolean(pin, ActiveLow) - val2 = PinState_Human(pin, ActiveLow) - index = self.to_int(rpi_output['index_id']) - gpio_status.append(dict(index_id=index, status=val, State=val2)) - return Response(json.dumps(gpio_status), mimetype='application/json') - - @octoprint.plugin.BlueprintPlugin.route("/setIO", methods=["GET"]) - def set_io_old(self): - index = request.values["index_id"] - value = True if request.values["status"] == 'true' else False - for rpi_output in self.rpi_outputs: - if self.to_int(index) == self.to_int(rpi_output['index_id']): - val = (not value) if rpi_output['active_low'] else value - if rpi_output['gpio_i2c_enabled']: - self.gpio_i2c_write(rpi_output, val) - else: - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/sendShellCommand", methods=["GET"]) - def send_shell_command_old(self): - output_index = self.to_int(request.values["index_id"]) - - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == output_index].pop() - - command = rpi_output['shell_script'] - self.shell_command(command) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setAutoStartUp", methods=["GET"]) - def set_auto_startup_old(self): - index = request.values["index_id"] - value = True if request.values["status"] == 'true' else False - - if not value: - suffix = 'auto_startup' - queue_id = '{0!s}_{1!s}'.format(index, suffix) - self.stop_queue_item(queue_id) - for output in self.rpi_outputs: - if self.to_int(index) == self.to_int(output['index_id']): - output['auto_startup'] = value - self._logger.info("Setting auto startup for output %s to : %s", index, value) - self._settings.set(["rpi_outputs"], self.rpi_outputs) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setAutoShutdown", methods=["GET"]) - def set_auto_shutdown_old(self): - index = request.values["index_id"] - value = True if request.values["status"] == 'true' else False - - if not value: - suffix = 'auto_shutdown' - queue_id = '{0!s}_{1!s}'.format(index, suffix) - self.stop_queue_item(queue_id) - - for output in self.rpi_outputs: - if self.to_int(index) == self.to_int(output['index_id']): - output['auto_shutdown'] = value - self._logger.info("Setting auto shutdown for output %s to : %s", index, value) - self._settings.set(["rpi_outputs"], self.rpi_outputs) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setFilamentSensor", methods=["GET"]) - def set_filament_sensor_old(self): - index = request.values["index_id"] - value = True if request.values["status"] == 'true' else False - for sensor in self.rpi_inputs: - if self.to_int(index) == self.to_int(sensor['index_id']): - sensor['filament_sensor_enabled'] = value - self._logger.info("Setting filament sensor for input %s to : %s", index, value) - self._settings.set(["rpi_inputs"], self.rpi_inputs) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setPWM", methods=["GET"]) - def set_pwm_old(self): - set_value = self.to_int(request.values["new_duty_cycle"]) - index_id = self.to_int(request.values["index_id"]) - for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == index_id]: - rpi_output['duty_cycle'] = set_value - rpi_output['new_duty_cycle'] = "" - gpio = self.to_int(rpi_output['gpio_pin']) - self.write_pwm(gpio, set_value) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/sendGcodeCommand", methods=["GET"]) - def requested_gcode_command_old(self): - gpio_index = self.to_int(request.values["index_id"]) - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == gpio_index].pop() - self.send_gcode_command(rpi_output['gcode']) - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setNeopixel", methods=["GET"]) - def set_neopixel_old(self): - """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" - gpio_index = self.to_int(request.values["index_id"]) - red = request.values["red"] - green = request.values["green"] - blue = request.values["blue"] - for rpi_output in self.rpi_outputs: - if gpio_index == self.to_int(rpi_output['index_id']): - led_count = rpi_output['neopixel_count'] - led_brightness = rpi_output['neopixel_brightness'] - address = rpi_output['microcontroller_address'] - - neopixel_dirrect = rpi_output['output_type'] == 'neopixel_direct' - - self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, green, - blue, address, neopixel_dirrect, gpio_index) - - return jsonify(success=True) - - @octoprint.plugin.BlueprintPlugin.route("/setLedstripColor", methods=["GET"]) - def set_ledstrip_color_old(self): - """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" - gpio_index = self.to_int(request.values["index_id"]) - rgb = request.values["rgb"] - for rpi_output in self.rpi_outputs: - if gpio_index == self.to_int(rpi_output['index_id']): - self.ledstrip_set_rgb(rpi_output, rgb) - - return jsonify(success=True) - - # DEPREACTION END - - - # GPIO over i2c - - def gpio_i2c_input(self, output, active_low=None): - state = False - try: - i2cbus = self.to_int(output['gpio_i2c_bus']) - i2caddr = self.to_int(output['gpio_i2c_address']) - i2creg = self.to_int(output['gpio_i2c_register_status']) - data_on = self.to_int(output['gpio_i2c_data_on']) - - with SMBus(i2cbus) as bus: - data = bus.read_i2c_block_data(i2caddr, i2creg, 1) - if data[0] == data_on: - state = True - - self._logger.debug("gpio_i2c_input(i2cbus=%s, i2caddr=%s, i2creg=%s, data_on=%s) data == %s", - i2cbus, i2caddr, i2creg, data_on, data) - - if active_low is None and state: return state - - except Exception as ex: - self.log_error(ex) - - if active_low and not state: return True - if not active_low and state: return True - return False - - def gpio_i2c_write(self, output, state, queue_id=None): - try: - i2cbus = self.to_int(output['gpio_i2c_bus']) - i2caddr = self.to_int(output['gpio_i2c_address']) - i2creg = self.to_int(output['gpio_i2c_register']) - data_on = self.to_int(output['gpio_i2c_data_on']) - data_off = self.to_int(output['gpio_i2c_data_off']) - - with SMBus(i2cbus) as bus: - data = [] - if state: - data.append(data_on) - else: - data.append(data_off) - - bus.write_i2c_block_data(i2caddr, i2creg, data) - - if queue_id is not None: - self._logger.debug("Running scheduled queue id %s", queue_id) - self._logger.debug("Writing on GPIO (i2c): %s/%s value %s", output['gpio_i2c_address'], output['gpio_i2c_register'], state) - self.update_ui() - if queue_id is not None: - self.stop_queue_item(queue_id) - - except Exception as ex: - template = "An exception of type {0} occurred on {1} when writing on i2c address {2}, reg {3}. Arguments:\n{4!r}" - message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, output['gpio_i2c_address'], output['gpio_i2c_register'], ex.args) - self._logger.warn(message) - pass - - - def send_neopixel_command(self, led_pin, led_count, led_brightness, red, green, blue, address, neopixel_dirrect, - index_id, queue_id=None): - """Send neopixel command - Arguments: - led_pin {int} -- GPIO number - ledCount {int} -- number of LEDS - ledBrightness {int} -- brightness from 0 to 255 - red {int} -- red value from 0 to 255 - green {int} -- green value from 0 to 255 - blue {int} -- blue value from 0 to 255 - address {int} -- i2c address from microcontroller - """ - - try: - - for rpi_output in self.rpi_outputs: - if self.to_int(index_id) == self.to_int(rpi_output['index_id']): - rpi_output['neopixel_color'] = 'rgb({0!s},{1!s},{2!s})'.format(red, green, blue) - - if address == '': - address = 0 - - if neopixel_dirrect: - script = os.path.dirname(os.path.realpath(__file__)) + "/neopixel_direct.py " - else: - script = os.path.dirname(os.path.realpath(__file__)) + "/neopixel_indirect.py " - - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - - cmd = sudo_str + "python " + script + str(led_pin) + " " + str(led_count) + " " + str( - led_brightness) + " " + str(red) + " " + str(green) + " " + str(blue) + " " - - if neopixel_dirrect: - dma = self._settings.get(["neopixel_dma"]) or 10 - cmd = cmd + str(dma) - else: - cmd = cmd + str(address) - - if queue_id is not None: - self._logger.debug("running scheduled queue id %s", queue_id) - self._logger.debug("Sending neopixel cmd: %s", cmd) - Popen(cmd, shell=True) - if queue_id is not None: - self.stop_queue_item(queue_id) - except Exception as ex: - self.log_error(ex) - - def check_enclosure_temp(self): - try: - sensor_data = [] - for sensor in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): - temp, hum = self.get_sensor_data(sensor) - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Sensor %s Temperature: %s humidity %s", sensor['label'], temp, hum) - if temp is not None and hum is not None: - sensor["temp_sensor_temp"] = temp - sensor["temp_sensor_humidity"] = hum - sensor_data.append(dict(index_id=sensor['index_id'], temperature=temp, humidity=hum)) - self.temperature_sensor_data = sensor_data - self.handle_temp_hum_control() - self.handle_temperature_events() - self.handle_pwm_linked_temperature() - self.update_ui() - except Exception as ex: - self.log_error(ex) - - def toggle_output(self, output_index, first_run=False): - for output in [item for item in self.rpi_outputs if item['index_id'] == output_index]: - gpio_pin = self.to_int(output['gpio_pin']) - index_id = self.to_int(output['index_id']) - - if output['output_type'] == 'regular': - if output['gpio_i2c_enabled']: - current_value = self.gpio_i2c_input(output) - else: - if first_run: - current_value = False - else: - current_value = (not GPIO.input(gpio_pin)) if output['active_low'] else GPIO.input(gpio_pin) - - if current_value: - time_delay = self.to_int(output['toggle_timer_off']) - else: - time_delay = self.to_int(output['toggle_timer_on']) - - if not self.print_complete: - if output['gpio_i2c_enabled']: - self.gpio_i2c_write(output, not current_value) - else: - self.write_gpio(gpio_pin, not current_value) - thread = threading.Timer(time_delay, self.toggle_output, args=[index_id]) - thread.start() - else: - off_value = True if output['active_low'] else False - if output['gpio_i2c_enabled']: - self.gpio_i2c_write(output, off_value) - else: - self.write_gpio(gpio_pin, off_value) - self.update_ui_outputs() - return - - if output['output_type'] == 'pwm': - for pwm in self.pwm_instances: - if gpio_pin in pwm: - if first_run: - current_pwm_value = 0 - else: - if 'duty_cycle' in pwm: - current_pwm_value = pwm['duty_cycle'] - current_pwm_value = self.to_int(current_pwm_value) - else: - current_pwm_value = 0 - - if not current_pwm_value == 0: - time_delay = self.to_int(output['toggle_timer_off']) - write_value = 0 - else: - time_delay = self.to_int(output['toggle_timer_on']) - write_value = self.to_int(output['default_duty_cycle']) - - if not self.print_complete: - self.write_pwm(gpio_pin, write_value) - thread = threading.Timer(time_delay, self.toggle_output, args=[index_id]) - thread.start() - else: - self.write_pwm(self.to_int(output['gpio_pin']), 0) - self.update_ui_outputs() - return - - def update_ui(self): - self.update_ui_outputs() - self.update_ui_current_temperature() - self.update_ui_set_temperature() - self.update_ui_inputs() - - def update_ui_current_temperature(self): - self._plugin_manager.send_plugin_message(self._identifier, dict(sensor_data=self.temperature_sensor_data)) - - def update_ui_set_temperature(self): - result = [] - for temp_crt_output in list(filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): - set_temperature = self.to_float(temp_crt_output['temp_ctr_set_value']) - result.append(dict(index_id=temp_crt_output['index_id'], set_temperature=set_temperature)) - result.append(set_temperature) - self._plugin_manager.send_plugin_message(self._identifier, dict(set_temperature=result)) - - def stop_queue_item(self, queue_id): - old_list = self.event_queue - self._logger.debug("Stopping queue id %s...", queue_id) - for task in self.event_queue: - self._logger.debug("Queue id found...") - if task['queue_id'] == queue_id: - task['thread'].cancel() - self.event_queue.remove(task) - self._logger.debug("Queue id stopped and removed from list...") - self._logger.debug("Old queue list: %s", old_list) - self._logger.debug("New queue list: %s", self.event_queue) - - def update_ui_outputs(self): - try: - regular_status = [] - pwm_status = [] - neopixel_status = [] - temp_control_status = [] - for output in self.rpi_outputs: - index = self.to_int(output['index_id']) - pin = self.to_int(output['gpio_pin']) - startup = output['auto_startup'] - shutdown = output['auto_shutdown'] - - if output['output_type'] == 'regular': - if output['gpio_i2c_enabled']: - val = self.gpio_i2c_input(output) - else: - val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) - regular_status.append( - dict(index_id=index, status=val, auto_startup=startup, auto_shutdown=shutdown)) - if output['output_type'] == 'temp_hum_control': - if output['gpio_i2c_enabled']: - val = self.gpio_i2c_input(output) - else: - val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) - temp_control_status.append( - dict(index_id=index, status=val, auto_startup=startup, auto_shutdown=shutdown)) - if output['output_type'] == 'neopixel_indirect' or output['output_type'] == 'neopixel_direct': - val = output['neopixel_color'] - neopixel_status.append( - dict(index_id=index, color=val, auto_startup=startup, auto_shutdown=shutdown)) - if output['output_type'] == 'pwm': - for pwm in self.pwm_instances: - if pin in pwm: - if 'duty_cycle' in pwm: - pwm_val = pwm['duty_cycle'] - val = self.to_int(pwm_val) - else: - val = 0 - pwm_status.append( - dict(index_id=index, pwm_value=val, auto_startup=startup, auto_shutdown=shutdown)) - self._plugin_manager.send_plugin_message(self._identifier, - dict(rpi_output_regular=regular_status, rpi_output_pwm=pwm_status, - rpi_output_neopixel=neopixel_status, - rpi_output_temp_hum_ctrl=temp_control_status)) - except Exception as ex: - self.log_error(ex) - - def update_ui_inputs(self): - try: - sensor_status = [] - for sensor in self.rpi_inputs: - if sensor['input_type'] == 'gpio' and sensor['action_type'] == 'printer_control' and sensor[ - 'printer_action'] == 'filament': - index = self.to_int(sensor['index_id']) - value = sensor['filament_sensor_enabled'] - sensor_status.append(dict(index_id=index, filament_sensor_enabled=value)) - self._plugin_manager.send_plugin_message(self._identifier, dict(filament_sensor_status=sensor_status)) - except Exception as ex: - self.log_error(ex) - - def get_sensor_data(self, sensor): - try: - if self.development_mode: - temp, hum = self.read_dummy_temp() - else: - if sensor['temp_sensor_type'] in ["11", "22", "2302"]: - temp, hum = self.read_dht_temp(sensor['temp_sensor_type'], sensor['gpio_pin']) - elif sensor['temp_sensor_type'] == "18b20": - temp = self.read_18b20_temp(sensor['ds18b20_serial']) - hum = 0 - elif sensor['temp_sensor_type'] == "bme280": - temp, hum = self.read_bme280_temp(sensor['temp_sensor_address']) - elif sensor['temp_sensor_type'] == "am2320": - temp, hum = self.read_am2320_temp() # sensor has fixed address - elif sensor['temp_sensor_type'] == "si7021": - temp, hum = self.read_si7021_temp(sensor['temp_sensor_address'], sensor['temp_sensor_i2cbus']) - elif sensor['temp_sensor_type'] == "tmp102": - temp = self.read_tmp102_temp(sensor['temp_sensor_address']) - hum = 0 - elif sensor['temp_sensor_type'] == "max31855": - temp = self.read_max31855_temp(sensor['temp_sensor_address']) - hum = 0 - elif sensor['temp_sensor_type'] == "mcp9808": - temp = self.read_mcp_temp(sensor['temp_sensor_address']) - hum = 0 - elif sensor['temp_sensor_type'] == "temp_raw_i2c": - temp, hum = self.read_raw_i2c_temp(sensor) - elif sensor['temp_sensor_type'] == "hum_raw_i2c": - hum, temp = self.read_raw_i2c_temp(sensor) - else: - self._logger.info("temp_sensor_type no match") - temp = None - hum = None - if temp != -1 and hum != -1: - temp = round(self.to_float(temp), 1) if not sensor['use_fahrenheit'] else round( - self.to_float(temp) * 1.8 + 32, 1) - hum = round(self.to_float(hum), 1) - return temp, hum - return None, None - except Exception as ex: - self.log_error(ex) - - def handle_temperature_events(self): - for temperature_alarm in [item for item in self.rpi_outputs if item['output_type'] == 'temperature_alarm']: - set_temperature = self.to_float(temperature_alarm['alarm_set_temp']) - if int(set_temperature) is 0: - continue - linked_data = [item for item in self.temperature_sensor_data if - item['index_id'] == temperature_alarm['linked_temp_sensor']].pop() - sensor_temperature = self.to_float(linked_data['temperature']) - if set_temperature < sensor_temperature: - for rpi_controlled_output in self.rpi_outputs: - if self.to_int(temperature_alarm['controlled_io']) == self.to_int( - rpi_controlled_output['index_id']): - if rpi_controlled_output['gpio_i2c_enabled']: - val = False if temperature_alarm['controlled_io_set_value'] == 'low' else True - self.gpio_i2c_write(rpi_controlled_output, val) - else: - val = GPIO.LOW if temperature_alarm['controlled_io_set_value'] == 'low' else GPIO.HIGH - self.write_gpio(self.to_int(rpi_controlled_output['gpio_pin']), val) - for notification in self.notifications: - if notification['temperatureAction']: - msg = ("Temperature action: enclosure temperature exceed " + temperature_alarm[ - 'alarm_set_temp']) - self.send_notification(msg) - - def read_dummy_temp(self): - current_value = self.dummy_value - if current_value > 40 or current_value < 30: - self.dummy_delta = - self.dummy_delta - - return_value = current_value + self.dummy_delta - - self.dummy_value = return_value - - return return_value, return_value - - def read_raw_i2c_temp(self, sensor): - try: - i2cbus = self.to_int(sensor['temp_i2c_bus']) - i2caddr = self.to_int(sensor['temp_i2c_address']) - i2creg = self.to_int(sensor['temp_i2c_register']) - - with SMBus(i2cbus) as bus: - data = bus.read_i2c_block_data(i2caddr, i2creg, 8) - fval1 = struct.unpack('f', bytearray(data[0:4]))[0] - if fval1 != fval1: - fval1 = 0 - fval2 = struct.unpack('f', bytearray(data[4:8]))[0] - if fval2 != fval2: - fval2 = 0 - - self._logger.debug("read_raw_i2c_temp(i2cbus=%s, i2caddr=%s, i2creg=%s) data == %s (%s, %s)", - i2cbus, i2caddr, i2creg, data, fval1, fval2) - - return (fval1, fval2) - - except Exception as ex: - template = "An exception of type {0} occurred on {1} when reading on i2c address {2}, reg {3}. Arguments:\n{4!r}" - message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, i2caddr, i2creg, ex.args) - self._logger.warn(message) - return str(-1) - - def read_mcp_temp(self, address): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/mcp9808.py" - args = ["python", script, str(address)] - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature MCP9808 cmd: %s", " ".join(args)) - proc = Popen(args, stdout=PIPE) - stdout, _ = proc.communicate() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("MCP9808 result: %s", stdout) - return self.to_float(stdout.decode("utf-8").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 - - def read_dht_temp(self, sensor, pin): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/getDHTTemp.py " - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - cmd = sudo_str + "python3 " + script + str(sensor) + " " + str(pin) - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature dht cmd: %s", cmd) - stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Dht result: %s", stdout) - temp, hum = stdout.decode("utf-8").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 of the plugin.") - self.log_error(ex) - return (0, 0) - - def read_bme280_temp(self, address): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/BME280.py " - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - cmd = sudo_str + "python " + script + str(address) - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature BME280 cmd: %s", cmd) - stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("BME280 result: %s", stdout) - temp, hum = stdout.decode("utf-8").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 of the plugin.") - self.log_error(ex) - return (0, 0) - - def read_am2320_temp(self): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/AM2320.py " - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - cmd = sudo_str + "python " + script # sensor has fixed address 0x5C - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature AM2320 cmd: %s", cmd) - stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("AM2320 result: %s", stdout) - temp, hum = stdout.decode("utf-8").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 of the plugin.") - self.log_error(ex) - return (0, 0) - - def read_si7021_temp(self, address, i2cbus): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/SI7021.py " - if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - cmd = sudo_str + "python " + script + str(address) + " " + str(i2cbus) - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature SI7021 cmd: %s", cmd) - stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("SI7021 result: %s", stdout) - temp, hum = stdout.decode("utf-8").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 of the plugin.") - self.log_error(ex) - return (0, 0) - - def read_18b20_temp(self, serial_number): - os.system('modprobe w1-gpio') - os.system('modprobe w1-therm') - - lines = self.read_raw_18b20_temp(serial_number) - while lines[0].strip()[-3:] != 'YES': - time.sleep(0.2) - lines = self.read_raw_18b20_temp(serial_number) - equals_pos = lines[1].find('t=') - if equals_pos != -1: - temp_string = lines[1][equals_pos + 2:] - temp_c = float(temp_string) / 1000. - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("DS18B20 result: %s", temp_c) - return '{0:0.1f}'.format(temp_c) - return 0 - - def read_raw_18b20_temp(self, serial_number): - base_dir = '/sys/bus/w1/devices/' - device_folder = glob.glob(base_dir + str(serial_number) + '*')[0] - device_file = device_folder + '/w1_slave' - device_file_result = open(device_file, 'r') - lines = device_file_result.readlines() - device_file_result.close() - return lines - - def read_tmp102_temp(self, address): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/tmp102.py" - args = ["python", script, str(address)] - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature TMP102 cmd: %s", " ".join(args)) - proc = Popen(args, stdout=PIPE) - stdout, _ = proc.communicate() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("TMP102 result: %s", stdout) - return self.to_float(stdout.decode("utf-8").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 - - def read_max31855_temp(self, address): - try: - script = os.path.dirname(os.path.realpath(__file__)) + "/max31855.py" - args = ["python", script, str(address)] - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Temperature MAX31855 cmd: %s", " ".join(args)) - proc = Popen(args, stdout=PIPE) - stdout, _ = proc.communicate() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("MAX31855 result: %s", stdout) - return self.to_float(stdout.decode("utf-8").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 - - def handle_pwm_linked_temperature(self): - try: - for pwm_output in list(filter(lambda item: item['output_type'] == 'pwm' and item['pwm_temperature_linked'], - self.rpi_outputs)): - gpio_pin = self.to_int(pwm_output['gpio_pin']) - if self._printer.is_printing(): - index_id = self.to_int(pwm_output['index_id']) - linked_id = self.to_int(pwm_output['linked_temp_sensor']) - linked_data = self.get_linked_temp_sensor_data(linked_id) - current_temp = self.to_float(linked_data['temperature']) - - duty_a = self.to_float(pwm_output['duty_a']) - duty_b = self.to_float(pwm_output['duty_b']) - temp_a = self.to_float(pwm_output['temperature_a']) - temp_b = self.to_float(pwm_output['temperature_b']) - - try: - calculated_duty = ((current_temp - temp_a) * (duty_b - duty_a) / (temp_b - temp_a)) + duty_a - - if current_temp < temp_a: - calculated_duty = 0 - except: - calculated_duty = 0 - - self._logger.debug("Calculated duty for PWM %s is %s", index_id, calculated_duty) - elif self.print_complete: - calculated_duty = self.to_int(pwm_output['duty_cycle']) - else: - calculated_duty = 0 - - self.write_pwm(gpio_pin, self.constrain(calculated_duty, 0, 100)) - - except Exception as ex: - self.log_error(ex) - - def get_linked_temp_sensor_data(self, linked_id): - try: - linked_data = [data for data in self.temperature_sensor_data if data['index_id'] == linked_id].pop() - return linked_data - except: - self._logger.warn("No linked temperature sensor found for %s", linked_id) - return None - - def handle_temp_hum_control(self): - try: - for temp_hum_control in list( - filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): - - set_temperature = self.to_float(temp_hum_control['temp_ctr_set_value']) - temp_deadband = self.to_float(temp_hum_control['temp_ctr_deadband']) - max_temp = self.to_float(temp_hum_control['temp_ctr_max_temp']) - - linked_id = temp_hum_control['linked_temp_sensor'] - - previous_status = list(filter(lambda item: item['index_id'] == temp_hum_control['index_id'], - self.temp_hum_control_status)).pop()['status'] - - if set_temperature == 0: - current_status = False - else: - linked_data = self.get_linked_temp_sensor_data(linked_id) - - control_type = str(temp_hum_control['temp_ctr_type']) - - if control_type == 'dehumidifier': - current_value = self.to_float(linked_data['humidity']) - temp_deadband = 0 - else: - current_value = self.to_float(linked_data['temperature']) - - if control_type == 'cooler' or control_type == 'dehumidifier': - if current_value <= set_temperature and current_value >= (set_temperature - temp_deadband): - current_status = previous_status - elif current_value < set_temperature: - current_status = False - else: - current_status = True - else: - if current_value <= set_temperature and current_value >= (set_temperature - temp_deadband): - current_status = previous_status - elif current_value > set_temperature: - current_status = False - else: - current_status = True - - if control_type == 'heater' and max_temp > 0.0 and max_temp < current_value: - self._logger.debug("Maximum temperature reached for temperature control %s", - temp_hum_control['index_id']) - temp_hum_control['temp_ctr_set_value'] = 0 - current_status = False - - if current_status != previous_status: - if current_status: - self._logger.info("Turning gpio to control temperature on.") - val = False if temp_hum_control['active_low'] else True - if temp_hum_control['gpio_i2c_enabled']: - self.gpio_i2c_write(temp_hum_control, val) - else: - self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) - else: - index_id = temp_hum_control['index_id'] - if index_id in self.waiting_temperature: - self.waiting_temperature.remove(index_id) - - if not self.waiting_temperature and self._printer.is_paused(): - self._printer.resume_print() - - self._logger.info("Turning gpio to control temperature off.") - val = True if temp_hum_control['active_low'] else False - if temp_hum_control['gpio_i2c_enabled']: - self.gpio_i2c_write(temp_hum_control, val) - else: - self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) - for control_status in self.temp_hum_control_status: - if control_status['index_id'] == temp_hum_control['index_id']: - control_status['status'] = current_status - except Exception as ex: - self.log_error(ex) - - def log_error(self, ex): - template = "An exception of type {0} occurred on {1}. Arguments:\n{2!r}" - message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, ex.args) - self._logger.warn(message, exc_info = True) - - def setup_gpio(self): - try: - current_mode = GPIO.getmode() - set_mode = GPIO.BOARD if self._settings.get(["use_board_pin_number"]) else GPIO.BCM - if current_mode is None: - outputs = list(filter( - lambda item: (item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ - 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct') and - item['gpio_i2c_enabled'] == False, - self.rpi_outputs)) - inputs = list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)) - gpios = outputs + inputs - if gpios: - GPIO.setmode(set_mode) - tempstr = "BOARD" if set_mode == GPIO.BOARD else "BCM" - self._logger.info("Setting GPIO mode to %s", tempstr) - elif current_mode != set_mode: - GPIO.setmode(current_mode) - tempstr = "BOARD" if current_mode == GPIO.BOARD else "BCM" - self._settings.set(["use_board_pin_number"], True if current_mode == GPIO.BOARD else False) - warn_msg = "GPIO mode was configured before, GPIO mode will be forced to use: " + tempstr + " as pin numbers. Please update GPIO accordingly!" - self._logger.info(warn_msg) - self._plugin_manager.send_plugin_message(self._identifier, - dict(is_msg=True, msg=warn_msg, msg_type="error")) - GPIO.setwarnings(False) - except Exception as ex: - self.log_error(ex) - - def clear_gpio(self): - try: - - for gpio_out in list(filter( - lambda item: (item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ - 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct') and - item['gpio_i2c_enabled'] == False, - self.rpi_outputs)): - gpio_pin = self.to_int(gpio_out['gpio_pin']) - if gpio_pin not in self.rpi_outputs_not_changed: - GPIO.cleanup(gpio_pin) - - for gpio_in in list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)): - try: - GPIO.remove_event_detect(self.to_int(gpio_in['gpio_pin'])) - except: - pass - GPIO.cleanup(self.to_int(gpio_in['gpio_pin'])) - except Exception as ex: - self.log_error(ex) - - def clear_channel(self, channel): - try: - GPIO.cleanup(self.to_int(channel)) - self._logger.debug("Clearing channel %s", channel) - except Exception as ex: - self.log_error(ex) - - def generate_temp_hum_control_status(self): - status = [] - for temp_hum_control in list(filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): - status.append(dict(index_id=temp_hum_control['index_id'], status=False)) - self.temp_hum_control_status = status - - def configure_gpio(self): - try: - - for gpio_out in list( - filter(lambda item: (item['output_type'] == 'regular' or item['output_type'] == 'temp_hum_control') and - item['gpio_i2c_enabled'] == False, - self.rpi_outputs)): - initial_value = GPIO.HIGH if gpio_out['active_low'] else GPIO.LOW - pin = self.to_int(gpio_out['gpio_pin']) - if pin not in self.rpi_outputs_not_changed: - self._logger.info("Setting GPIO pin %s as OUTPUT with initial value: %s", pin, initial_value) - GPIO.setup(pin, GPIO.OUT, initial=initial_value) - for gpio_out_pwm in list(filter(lambda item: item['output_type'] == 'pwm', self.rpi_outputs)): - pin = self.to_int(gpio_out_pwm['gpio_pin']) - self._logger.info("Setting GPIO pin %s as PWM", pin) - for pwm in (pwm_dict for pwm_dict in self.pwm_instances if pin in pwm_dict): - self.pwm_instances.remove(pwm) - self.clear_channel(pin) - GPIO.setup(pin, GPIO.OUT) - pwm_instance = GPIO.PWM(pin, self.to_int(gpio_out_pwm['pwm_frequency'])) - self._logger.info("starting PWM on pin %s", pin) - pwm_instance.start(0) - self.pwm_instances.append({pin: pwm_instance}) - for gpio_out_neopixel in list( - filter(lambda item: item['output_type'] == 'neopixel_direct', self.rpi_outputs)): - pin = self.to_int(gpio_out_neopixel['gpio_pin']) - self.clear_channel(pin) - - for rpi_input in list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)): - gpio_pin = self.to_int(rpi_input['gpio_pin']) - pull_resistor = GPIO.PUD_UP if rpi_input['input_pull_resistor'] == 'input_pull_up' else GPIO.PUD_DOWN - GPIO.setup(gpio_pin, GPIO.IN, pull_resistor) - edge = GPIO.RISING if rpi_input['edge'] == 'rise' else GPIO.FALLING - - inputs_same_gpio = list( - [r_inp for r_inp in self.rpi_inputs if self.to_int(r_inp['gpio_pin']) == gpio_pin]) - - if len(inputs_same_gpio) > 1: - GPIO.remove_event_detect(gpio_pin) - for other_input in inputs_same_gpio: - if other_input['edge'] is not edge: - edge = GPIO.BOTH - - if rpi_input['action_type'] == 'output_control': - self._logger.info("Adding GPIO event detect on pin %s with edge: %s", gpio_pin, edge) - GPIO.add_event_detect(gpio_pin, edge, callback=self.handle_gpio_control, bouncetime=200) - if (rpi_input['action_type'] == 'printer_control' and rpi_input['printer_action'] != 'filament'): - GPIO.add_event_detect(gpio_pin, edge, callback=self.handle_printer_action, bouncetime=200) - self._logger.info("Adding PRINTER CONTROL event detect on pin %s with edge: %s", gpio_pin, edge) - - for rpi_input in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): - gpio_pin = self.to_int(rpi_input['gpio_pin']) - if rpi_input['input_pull_resistor'] == 'input_pull_up': - pull_resistor = GPIO.PUD_UP - elif rpi_input['input_pull_resistor'] == 'input_pull_down': - pull_resistor = GPIO.PUD_DOWN - else: - pull_resistor = GPIO.PUD_OFF - GPIO.setup(gpio_pin, GPIO.IN, pull_up_down=pull_resistor) - except Exception as ex: - self.log_error(ex) - - def handle_filamment_detection(self, channel): - try: - for filament_sensor in list(filter( - lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ - 'printer_action'] == 'filament' and self.to_int(item['gpio_pin']) == self.to_int(channel), - self.rpi_inputs)): - if ((filament_sensor['edge'] == 'fall') ^ (GPIO.input(self.to_int(filament_sensor['gpio_pin']))) and - filament_sensor['filament_sensor_enabled']): - last_detected_time = list(filter(lambda item: item['index_id'] == filament_sensor['index_id'], - self.last_filament_end_detected)).pop()['time'] - time_now = time.time() - time_difference = self.to_int(time_now - last_detected_time) - time_out_value = self.to_int(filament_sensor['filament_sensor_timeout']) - if time_difference > time_out_value: - self._logger.info("Detected end of filament.") - for item in self.last_filament_end_detected: - if item['index_id'] == filament_sensor['index_id']: - item['time'] = time_now - for line in self._settings.get(["filament_sensor_gcode"]).split('\n'): - if line: - self._printer.commands(line.strip()) - self._logger.info("Sending GCODE command: %s", line.strip()) - time.sleep(0.2) - for notification in self.notifications: - if notification['filamentChange']: - msg = "Filament change action caused by sensor: " + str(filament_sensor['label']) - self.send_notification(msg) - else: - self._logger.info("Prevented end of filament detection, filament sensor timeout not elapsed.") - except Exception as ex: - self.log_error(ex) - - def start_filament_detection(self): - self.stop_filament_detection() - try: - for filament_sensor in list(filter( - lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ - 'printer_action'] == 'filament', self.rpi_inputs)): - edge = GPIO.RISING if filament_sensor['edge'] == 'rise' else GPIO.FALLING - if GPIO.input(self.to_int(filament_sensor['gpio_pin'])) == (edge == GPIO.RISING): - self._printer.pause_print() - self._logger.info("Started printing with no filament.") - else: - self.last_filament_end_detected.append(dict(index_id=filament_sensor['index_id'], time=0)) - self._logger.info("Adding GPIO event detect on pin %s with edge: %s", filament_sensor['gpio_pin'], - edge) - GPIO.add_event_detect(self.to_int(filament_sensor['gpio_pin']), edge, - callback=self.handle_filamment_detection, bouncetime=200) - except Exception as ex: - self.log_error(ex) - - def stop_filament_detection(self): - try: - self.last_filament_end_detected = [] - for filament_sensor in list(filter( - lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ - 'printer_action'] == 'filament', self.rpi_inputs)): - GPIO.remove_event_detect(self.to_int(filament_sensor['gpio_pin'])) - except Exception as ex: - self.log_error(ex) - - def cancel_all_events_on_queue(self): - for task in self.event_queue: - try: - task['thread'].cancel() - except: - self._logger.warn("Failed to stop task %s.", task) - pass - - def handle_initial_gpio_control(self): - try: - for rpi_input in list( - filter(lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'output_control', - self.rpi_inputs)): - gpio_pin = self.to_int(rpi_input['gpio_pin']) - controlled_io = self.to_int(rpi_input['controlled_io']) - if (rpi_input['edge'] == 'fall') ^ GPIO.input(gpio_pin): - rpi_output = [r_out for r_out in self.rpi_outputs if - self.to_int(r_out['index_id']) == controlled_io].pop() - if rpi_output['output_type'] == 'regular': - if rpi_output['gpio_i2c_enabled']: - val = False if rpi_input['controlled_io_set_value'] == 'low' else True - self.gpio_i2c_write(rpi_output, val) - else: - val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) - except Exception as ex: - self.log_error(ex) - pass - - def shell_command(self, command): - try: - stdout = (Popen(command, shell=True, stdout=PIPE).stdout).read() - self._plugin_manager.send_plugin_message(self._identifier, - dict(is_msg=True, msg=stdout, msg_type="success")) - except Exception as ex: - self.log_error(ex) - self._plugin_manager.send_plugin_message(self._identifier, - dict(is_msg=True, msg="Could not execute shell script", msg_type="error")) - - def handle_gpio_control(self, channel): - - try: - self._logger.debug("GPIO event triggered on channel %s", channel) - for rpi_input in list( - filter(lambda item: self.to_int(item['gpio_pin']) == self.to_int(channel), self.rpi_inputs)): - gpio_pin = self.to_int(rpi_input['gpio_pin']) - controlled_io = self.to_int(rpi_input['controlled_io']) - if (rpi_input['edge'] == 'fall') ^ GPIO.input(gpio_pin): - rpi_output = [r_out for r_out in self.rpi_outputs if - self.to_int(r_out['index_id']) == controlled_io].pop() - if rpi_output['output_type'] == 'regular': - if rpi_input['controlled_io_set_value'] == 'toggle': - val = GPIO.LOW if GPIO.input( - self.to_int(rpi_output['gpio_pin'])) == GPIO.HIGH else GPIO.HIGH - else: - val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH - if rpi_output['gpio_i2c_enabled']: - self.gpio_i2c_write(rpi_output, val) - else: - self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) - for notification in self.notifications: - if notification['gpioAction']: - msg = "GPIO control action caused by input " + str( - rpi_input['label']) + ". Setting GPIO" + str( - rpi_input['controlled_io']) + " to: " + str(rpi_input['controlled_io_set_value']) - self.send_notification(msg) - if rpi_output['output_type'] == 'gcode_output': - self.send_gcode_command(rpi_output['gcode']) - for notification in self.notifications: - if notification['gpioAction']: - msg = "GPIO control action caused by input " + str( - rpi_input['label']) + ". Sending GCODE command" - self.send_notification(msg) - if rpi_output['output_type'] == 'shell_output': - command = rpi_output['shell_script'] - self.shell_command(command) - except Exception as ex: - self.log_error(ex) - pass - - def send_gcode_command(self, command): - for line in command.split('\n'): - if line: - self._printer.commands(line.strip()) - self._logger.info("Sending GCODE command: %s", line.strip()) - time.sleep(0.2) - - def handle_printer_action(self, channel): - try: - for rpi_input in self.rpi_inputs: - if (channel == self.to_int(rpi_input['gpio_pin']) and rpi_input[ - 'action_type'] == 'printer_control' and ( - (rpi_input['edge'] == 'fall') ^ GPIO.input(self.to_int(rpi_input['gpio_pin'])))): - if rpi_input['printer_action'] == 'resume': - self._logger.info("Printer action resume.") - self._printer.resume_print() - elif rpi_input['printer_action'] == 'pause': - self._logger.info("Printer action pause.") - self._printer.pause_print() - elif rpi_input['printer_action'] == 'cancel': - self._logger.info("Printer action cancel.") - self._printer.cancel_print() - elif rpi_input['printer_action'] == 'toggle': - self._logger.info("Printer action toggle.") - if self._printer.is_operational(): - self._printer.toggle_pause_print() - else: - self._printer.connect() - elif rpi_input['printer_action'] == 'start': - self._logger.info("Printer action start.") - self._printer.start_print() - elif rpi_input['printer_action'] == 'toggle_job': - self._logger.info("Printer action toggle_job.") - if self._printer.is_operational(): - if self._printer.is_printing(): - self._printer.cancel_print() - elif self._printer.is_ready(): - self._printer.start_print() - else: - self._printer.connect() - elif rpi_input['printer_action'] == 'stop_temp_hum_control': - self._logger.info("Printer action stopping temperature control.") - for rpi_output in self.rpi_outputs: - if rpi_output['auto_shutdown'] and rpi_output['output_type'] == 'temp_hum_control': - rpi_output['temp_ctr_set_value'] = 0 - self.handle_temp_hum_control() - for notification in self.notifications: - if notification['printer_action']: - msg = "Printer action: " + rpi_input['printer_action'] + " caused by input: " + str( - rpi_input['label']) - self.send_notification(msg) - except Exception as ex: - self.log_error(ex) - pass - - def write_gpio(self, gpio, value, queue_id=None): - try: - GPIO.output(gpio, value) - if queue_id is not None: - self._logger.debug("Running scheduled queue id %s", queue_id) - self._logger.debug("Writing on GPIO: %s value %s", gpio, value) - self.update_ui() - if queue_id is not None: - self.stop_queue_item(queue_id) - except Exception as ex: - template = "An exception of type {0} occurred on {1} when writing on pin {2}. Arguments:\n{3!r}" - message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, gpio, ex.args) - self._logger.warn(message) - pass - - def write_pwm(self, gpio, pwm_value, queue_id=None): - try: - if queue_id is not None: - self._logger.debug("running scheduled queue id %s", queue_id) - for pwm in self.pwm_instances: - if gpio in pwm: - pwm_object = pwm[gpio] - old_pwm_value = pwm['duty_cycle'] if 'duty_cycle' in pwm else -1 - if not self.to_int(old_pwm_value) == self.to_int(pwm_value): - pwm['duty_cycle'] = pwm_value - pwm_object.start(pwm_value) #should be changed back to pwm_object.ChangeDutyCycle() but this - # was causing errors. - self._logger.debug("Writing PWM on gpio: %s value %s", gpio, pwm_value) - self.update_ui() - if queue_id is not None: - self.stop_queue_item(queue_id) - break - except Exception as ex: - self.log_error(ex) - pass - - def get_output_list(self): - result = [] - for rpi_output in self.rpi_outputs: - if rpi_output['output_type'] == 'regular': - result.append(self.to_int(rpi_output['gpio_pin'])) - return result - - def send_notification(self, message): - try: - provider = self._settings.get(["notification_provider"]) - if provider == 'ifttt': - event = self._settings.get(["notification_event_name"]) - api_key = self._settings.get(["notification_api_key"]) - self._logger.debug("Sending notification to: %s with msg: %s with key: %s", provider, message, - api_key) - try: - res = self.ifttt_notification(message, event, api_key) - except requests.exceptions.ConnectionError: - self._logger.info("Error: Could not connect to IFTTT") - except requests.exceptions.HTTPError: - self._logger.info("Error: Received invalid response") - except requests.exceptions.Timeout: - self._logger.info("Error: Request timed out") - except requests.exceptions.TooManyRedirects: - self._logger.info("Error: Too many redirects") - except requests.exceptions.RequestException as reqe: - self._logger.info("Error: {e}".format(e=reqe)) - if res.status_code != requests.codes['ok']: - try: - j = res.json() - except ValueError: - self._logger.info('Error: Could not parse server response. Event not sent') - for err in j['errors']: - self._logger.info('Error: {}'.format(err['message'])) - except Exception as ex: - self.log_error(ex) - pass - - def ifttt_notification(self, message, event, api_key): - url = "https://maker.ifttt.com/trigger/{e}/with/key/{k}/".format(e=event, k=api_key) - payload = {'value1': message} - return requests.post(url, data=payload) - - # ~~ EventPlugin mixin - def on_event(self, event, payload): - if event == Events.CONNECTED: - self.update_ui() - - if event == Events.CLIENT_OPENED: - self.update_ui() - - if event == Events.PRINT_RESUMED: - self.start_filament_detection() - - if event == Events.PRINT_STARTED: - self.print_complete = False - self.cancel_all_events_on_queue() - self.event_queue = [] - self.start_filament_detection() - for rpi_output in self.rpi_outputs: - if rpi_output['auto_startup']: - delay_seconds = self.get_startup_delay_from_output(rpi_output) - self.schedule_auto_startup_outputs(rpi_output, delay_seconds) - if rpi_output['toggle_timer']: - if rpi_output['output_type'] == 'regular' or rpi_output['output_type'] == 'pwm': - self.toggle_output(rpi_output['index_id'], True) - if self.is_hour(rpi_output['shutdown_time']): - shutdown_delay_seconds = self.get_shutdown_delay_from_output(rpi_output) - self.schedule_auto_shutdown_outputs(rpi_output, shutdown_delay_seconds) - self.run_tasks() - self.update_ui() - - elif event == Events.PRINT_DONE: - self.stop_filament_detection() - self.print_complete = True - for rpi_output in self.rpi_outputs: - shutdown_time = rpi_output['shutdown_time'] - if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: - rpi_output['duty_cycle'] = rpi_output['default_duty_cycle'] - if rpi_output['auto_shutdown'] and not self.is_hour(shutdown_time): - delay_seconds = self.to_float(shutdown_time) - self.schedule_auto_shutdown_outputs(rpi_output, delay_seconds) - self.run_tasks() - self.update_ui() - - elif event in (Events.PRINT_CANCELLED, Events.PRINT_FAILED): - self.stop_filament_detection() - self.cancel_all_events_on_queue() - self.event_queue = [] - for rpi_output in self.rpi_outputs: - if rpi_output['shutdown_on_failed']: - shutdown_time = rpi_output['shutdown_time'] - if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: - rpi_output['duty_cycle'] = rpi_output['default_duty_cycle'] - if rpi_output['auto_shutdown'] and not self.is_hour(shutdown_time): - delay_seconds = self.to_float(shutdown_time) - self.schedule_auto_shutdown_outputs(rpi_output, delay_seconds) - if rpi_output['output_type'] == 'temp_hum_control': - rpi_output['temp_ctr_set_value'] = 0 - self.run_tasks() - - if event == Events.PRINT_DONE: - for notification in self.notifications: - if notification['printFinish']: - file_name = os.path.basename(payload["path"]) - elapsed_time_in_seconds = payload["time"] - elapsed_time = octoprint.util.get_formatted_timedelta(timedelta(seconds=elapsed_time_in_seconds)) - msg = "Print job finished: " + file_name + "finished printing in " + file_name, elapsed_time - self.send_notification(msg) - - if event in (Events.ERROR, Events.DISCONNECTED): - self._logger.info("Detected Error or Disconnect in %s will call listeners for shutdown_on_error!", event) - for rpi_output in self.rpi_outputs: - if rpi_output['shutdown_on_error']: - self._logger.debug("Schedule shutdown for: %s", rpi_output["index_id"]) - self.schedule_auto_shutdown_outputs(rpi_output, 0) - self.run_tasks() - - if event == Events.PRINTER_STATE_CHANGED: - if "error" in payload["state_string"].lower(): - self._logger.info("Detected Error in %s id: %s state: %s will call listeners for shutdown_on_error!", event, payload["state_id"], payload["state_string"]) - for rpi_output in self.rpi_outputs: - if rpi_output['shutdown_on_error']: - self._logger.debug("Schedule shutdown for: %s", rpi_output["index_id"]) - self.schedule_auto_shutdown_outputs(rpi_output, 0) - self.run_tasks() - - def run_tasks(self): - for task in self.event_queue: - if not task['thread'].is_alive(): - task['thread'].start() - - def schedule_auto_shutdown_outputs(self, rpi_output, shutdown_delay_seconds): - sufix = 'auto_shutdown' - if rpi_output['output_type'] == 'regular': - value = True if rpi_output['active_low'] else False - self.add_regular_output_to_queue(shutdown_delay_seconds, rpi_output, value, sufix) - if rpi_output['output_type'] == 'ledstrip': - self.ledstrip_set_rgb(rpi_output) - if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: - value = 0 - self.add_pwm_output_to_queue(shutdown_delay_seconds, rpi_output, value, sufix) - if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: - self.schedule_pwm_duty_on_queue(shutdown_delay_seconds, rpi_output, 0, sufix) - if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): - self.add_neopixel_output_to_queue(rpi_output, shutdown_delay_seconds, 0, 0, 0, sufix) - if rpi_output['output_type'] == 'temp_hum_control': - value = 0 - self.add_temperature_output_temperature_queue(shutdown_delay_seconds, rpi_output, value, sufix) - self._logger.debug("Events scheduled to run %s", self.event_queue) - - def ledstrip_set_rgb(self, rpi_output, rgb=None): - clk = rpi_output["ledstrip_gpio_clk"] - data = rpi_output["ledstrip_gpio_dat"] - if clk is not None and data is not None: - ledstrip = LEDStrip(self.to_int(clk), self.to_int(data)) - if rgb is None: - red, green, blue = self.get_color_from_rgb(rpi_output['default_ledstrip_color']) - else: - red, green, blue = self.get_color_from_rgb(rgb) - - self._logger.info("LEDSTRIP set rgb color: %s, %s, %s", red, green, blue) - ledstrip.setcolourrgb(self.to_int(red), self.to_int(green), self.to_int(blue)) - - def start_outpus_with_server(self): - for rpi_output in self.rpi_outputs: - if rpi_output['startup_with_server']: - gpio = self.to_int(rpi_output['gpio_pin']) - if rpi_output['output_type'] == 'regular': - value = False if rpi_output['active_low'] else True - if rpi_output['gpio_i2c_enabled']: - self.gpio_i2c_write(rpi_output, value) - else: - self.write_gpio(gpio, value) - if rpi_output['output_type'] == 'ledstrip': - self.ledstrip_set_rgb(rpi_output) - if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: - value = self.to_int(rpi_output['default_duty_cycle']) - self.write_pwm(gpio, value) - if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): - red, green, blue = self.get_color_from_rgb(rpi_output['default_neopixel_color']) - led_count = rpi_output['neopixel_count'] - led_brightness = rpi_output['neopixel_brightness'] - address = rpi_output['microcontroller_address'] - index_id = self.to_int(rpi_output['index_id']) - neopixel_direct = rpi_output['output_type'] == 'neopixel_direct' - self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, - green, blue, address, neopixel_direct, index_id) - if rpi_output['output_type'] == 'temp_hum_control': - rpi_output['temp_ctr_set_value'] = rpi_output['temp_ctr_default_value'] - - def schedule_auto_startup_outputs(self, rpi_output, delay_seconds): - sufix = 'auto_startup' - if rpi_output['output_type'] == 'regular': - value = False if rpi_output['active_low'] else True - self.add_regular_output_to_queue(delay_seconds, rpi_output, value, sufix) - if rpi_output['output_type'] == 'ledstrip': - self.ledstrip_set_rgb(rpi_output) - if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: - value = self.to_int(rpi_output['default_duty_cycle']) - self.add_pwm_output_to_queue(delay_seconds, rpi_output, value, sufix) - if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): - red, green, blue = self.get_color_from_rgb(rpi_output['default_neopixel_color']) - self.add_neopixel_output_to_queue(rpi_output, delay_seconds, red, green, blue, sufix) - if rpi_output['output_type'] == 'temp_hum_control': - value = rpi_output['temp_ctr_default_value'] - self.add_temperature_output_temperature_queue(delay_seconds, rpi_output, value, sufix) - self._logger.debug("Events scheduled to run %s", self.event_queue) - - def get_color_from_rgb(self, stringColor): - stringColor = stringColor.replace('rgb(', '') - red = stringColor[:stringColor.index(',')] - stringColor = stringColor[stringColor.index(',') + 1:] - green = stringColor[:stringColor.index(',')] - stringColor = stringColor[stringColor.index(',') + 1:] - blue = stringColor[:stringColor.index(')')] - return red, green, blue - - def get_shutdown_delay_from_output(self, rpi_output): - shutdown_time = rpi_output['shutdown_time'] - - shut_down_date_time = self.create_date(shutdown_time) - - if shut_down_date_time < datetime.now(): - shut_down_date_time = shut_down_date_time + timedelta(days=1) - - delay_seconds = (shut_down_date_time - datetime.now()).total_seconds() - - return delay_seconds - - def add_neopixel_output_to_queue(self, rpi_output, delay_seconds, red, green, blue, sufix): - gpio_pin = rpi_output['gpio_pin'] - ledCount = rpi_output['neopixel_count'] - ledBrightness = rpi_output['neopixel_brightness'] - address = rpi_output['microcontroller_address'] - neopixel_direct = rpi_output['output_type'] == 'neopixel_direct' - index_id = self.to_int(rpi_output['index_id']) - - queue_id = '{0!s}_{1!s}'.format(index_id, sufix) - - self._logger.debug("Scheduling neopixel output id %s for on %s delay_seconds", queue_id, delay_seconds) - - thread = threading.Timer(delay_seconds, self.send_neopixel_command, - args=[gpio_pin, ledCount, ledBrightness, red, green, blue, address, neopixel_direct, - index_id, queue_id]) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def add_pwm_output_to_queue(self, delay_seconds, rpi_output, value, sufix): - queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) - - self._logger.debug("Scheduling pwm output id %s for on %s delay_seconds", queue_id, delay_seconds) - - thread = threading.Timer(delay_seconds, self.write_pwm, - args=[self.to_int(rpi_output['gpio_pin']), value, queue_id]) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def schedule_pwm_duty_on_queue(self, delay_seconds, rpi_output, value, sufix): - queue_id = '{0!s}_{1!s}_{2!s}'.format(rpi_output['index_id'], "pwm_linked_temp", sufix) - thread = threading.Timer(delay_seconds, self.set_pwm_duty_cycle, args=[rpi_output, value, queue_id]) - - self._logger.debug("Scheduling pwm linked temp output id %s on %s delay_seconds", queue_id, delay_seconds) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def set_pwm_duty_cycle(self, rpi_output, value, queue_id): - rpi_output['duty_cycle'] = value - if queue_id is not None: - self.stop_queue_item(queue_id) - - def add_regular_output_to_queue(self, delay_seconds, rpi_output, value, sufix): - queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) - - self._logger.debug("Scheduling regular output id %s on %s delay_seconds", queue_id, delay_seconds) - - if rpi_output['gpio_i2c_enabled']: - thread = threading.Timer(delay_seconds, self.gpio_i2c_write, - args=[rpi_output, value, queue_id]) - else: - thread = threading.Timer(delay_seconds, self.write_gpio, - args=[self.to_int(rpi_output['gpio_pin']), value, queue_id]) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def add_temperature_output_temperature_queue(self, delay_seconds, rpi_output, value, sufix): - queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) - self._logger.debug("Scheduling temperature control id %s on %s delay_seconds", queue_id, delay_seconds) - - thread = threading.Timer(delay_seconds, self.write_temperature_to_output, - args=[self.to_int(rpi_output['index_id']), value, queue_id]) - - self.event_queue.append(dict(queue_id=queue_id, thread=thread)) - - def write_temperature_to_output(self, rpi_output_index, value, queue_id=None): - try: - rpi_output = [r_out for r_out in self.rpi_outputs if - self.to_int(r_out['index_id']) == rpi_output_index].pop() - - if rpi_output['output_type'] == 'temp_hum_control': - rpi_output['temp_ctr_set_value'] = value - - if queue_id is not None: - self._logger.debug("running scheduled queue id %s", queue_id) - self._logger.debug("Setting temperature to output index: %s value %s", rpi_output['index_id'], value) - - self.update_ui() - if queue_id is not None: - self.stop_queue_item(queue_id) - - except Exception as ex: - template = "An exception of type {0} occurred on {1}. Arguments:\n{3!r}" - message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, ex.args) - self._logger.warn(message) - pass - - def get_startup_delay_from_output(self, rpi_output): - start_up_time = rpi_output['startup_time'] - if self.is_hour(start_up_time): - start_up_date_time = self.create_date(start_up_time) - if start_up_date_time < datetime.now(): - delay_seconds = 0.0 - else: - delay_seconds = (start_up_date_time - datetime.now()).total_seconds() - else: - delay_seconds = self.to_float(rpi_output['startup_time']) - return delay_seconds - - # ~~ SettingsPlugin mixin - def on_settings_save(self, data): - outputsBeforeSave = self.get_output_list() - octoprint.plugin.SettingsPlugin.on_settings_save(self, data) - self.rpi_outputs = self._settings.get(["rpi_outputs"]) - self.rpi_inputs = self._settings.get(["rpi_inputs"]) - self.notifications = self._settings.get(["notifications"]) - outputsAfterSave = self.get_output_list() - - commonPins = list(set(outputsBeforeSave) & set(outputsAfterSave)) - - for pin in (pin for pin in outputsBeforeSave if pin not in commonPins): - self.clear_channel(pin) - - self.rpi_outputs_not_changed = commonPins - self.clear_gpio() - - self._logger.debug("rpi_outputs: %s", self.rpi_outputs) - self._logger.debug("rpi_inputs: %s", self.rpi_inputs) - self.setup_gpio() - self.configure_gpio() - self.generate_temp_hum_control_status() - - def get_settings_defaults(self): - return dict(rpi_outputs=[], rpi_inputs=[], - filament_sensor_gcode="G91 ;Set Relative Mode \n" + "G1 E-5.000000 F500 ;Retract 5mm\n" + "G1 Z15 F300 ;move Z up 15mm\n" + "G90 ;Set Absolute Mode\n " + "G1 X20 Y20 F9000 ;Move to hold position\n" + "G91 ;Set Relative Mode\n" + "G1 E-40 F500 ;Retract 40mm\n" + "M0 ;Idle Hold\n" + "G90 ;Set Absolute Mode\n" + "G1 F5000 ;Set speed limits\n" + "G28 X0 Y0 ;Home X Y\n" + "M82 ;Set extruder to Absolute Mode\n" + "G92 E0 ;Set Extruder to 0", - use_sudo=True, neopixel_dma=10, debug=False, gcode_control=False, debug_temperature_log=False, - use_board_pin_number=False, notification_provider="disabled", notification_api_key="", - notification_event_name="printer_event", notifications=[{ - 'printFinish' : True, - 'filamentChange' : True, - 'printer_action' : True, - 'temperatureAction': True, 'gpioAction': True - }]) - - # ~~ TemplatePlugin - def get_template_configs(self): - return [dict(type="settings", custom_bindings=True), dict(type="tab", custom_bindings=True), - dict(type="navbar", custom_bindings=True, suffix="_1", classes=["dropdown"]), - dict(type="navbar", custom_bindings=True, template="enclosure_navbar_input.jinja2", suffix="_2", - classes=["dropdown"])] - - # ~~ AssetPlugin mixin - def get_assets(self): - return dict(js=["js/enclosure.js", "js/bootstrap-colorpicker.min.js"], - css=["css/bootstrap-colorpicker.css", "css/enclosure.css"]) - - # ~~ Softwareupdate hook - def get_update_information(self): - return dict(enclosure=dict(displayName="Enclosure Plugin", displayVersion=self._plugin_version, - # version check: github repository - type="github_release", user="vitormhenrique", repo="OctoPrint-Enclosure", current=self._plugin_version, - # update method: pip - pip="https://github.com/vitormhenrique/OctoPrint-Enclosure/archive/{target_version}.zip")) - - def hook_gcode_queuing(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs): - if self._settings.get(["gcode_control"]) is False: - return - - if cmd.strip().startswith("ENC"): - self._logger.debug("Gcode queuing: %s", cmd) - index_id = self.to_int(self.get_gcode_value(cmd, 'O')) - for output in [item for item in self.rpi_outputs if item['index_id'] == index_id]: - if output['output_type'] == 'regular': - set_value = self.to_int(self.get_gcode_value(cmd, 'S')) - set_value = self.constrain(set_value, 0, 1) - value = True if set_value == 1 else False - value = (not value) if output['active_low'] else value - if output['gpio_i2c_enabled']: - self.gpio_i2c_write(output, value) - else: - self.write_gpio(self.to_int(output['gpio_pin']), value) - comm_instance._log("Setting REGULAR output %s to value %s" % (index_id, value)) - return - if output['output_type'] == 'pwm': - set_value = self.to_int(self.get_gcode_value(cmd, 'S')) - set_value = self.constrain(set_value, 0, 100) - output['duty_cycle'] = set_value - self.write_pwm(self.to_int(output['gpio_pin']), set_value) - comm_instance._log("Setting PWM output %s to value %s" % (index_id, set_value)) - return - if output['output_type'] == 'neopixel_indirect' or output['output_type'] == 'neopixel_direct': - red = self.get_gcode_value(cmd, 'R') - green = self.get_gcode_value(cmd, 'G') - blue = self.get_gcode_value(cmd, 'B') - - led_count = output['neopixel_count'] - led_brightness = output['neopixel_brightness'] - address = output['microcontroller_address'] - - index_id = self.to_int(output['index_id']) - - neopixel_direct = output['output_type'] == 'neopixel_direct' - - self.send_neopixel_command(self.to_int(output['gpio_pin']), led_count, led_brightness, red, green, - blue, address, neopixel_direct, index_id) - comm_instance._log( - "Setting NEOPIXEL output %s to red: %s green: %s blue: %s" % (index_id, red, green, blue)) - return - if output['output_type'] == 'temp_hum_control': - set_value = self.to_float(self.get_gcode_value(cmd, 'S')) - should_wait = self.to_int(self.get_gcode_value(cmd, 'W')) - if should_wait == 1 and self._printer.is_printing(): - self._printer.pause_print() - self.waiting_temperature.append(index_id) - output['temp_ctr_set_value'] = set_value - self.update_ui_set_temperature() - self.handle_temp_hum_control() - comm_instance._log("Setting TEMP/HUM control output %s to value %s" % (index_id, set_value)) - return - - def get_graph_data(self, comm, parsed_temps): - for sensor in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): - if sensor["show_graph_temp"]: - parsed_temps[str(sensor["label"])] = (sensor['temp_sensor_temp'], None) - if sensor["show_graph_humidity"]: - parsed_temps[str(sensor["label"])+" Humidity"] = (sensor['temp_sensor_humidity'], None) - - return parsed_temps - - -__plugin_name__ = "Enclosure Plugin" -__plugin_pythoncompat__ = ">=2.7,<4" - - -def __plugin_load__(): - global __plugin_implementation__ - __plugin_implementation__ = EnclosurePlugin() - - global __plugin_hooks__ - __plugin_hooks__ = { - "octoprint.comm.protocol.gcode.queuing" : __plugin_implementation__.hook_gcode_queuing, - "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information, - "octoprint.comm.protocol.temperatures.received": (__plugin_implementation__.get_graph_data, 1) - } +# coding=utf-8 +from __future__ import absolute_import +from octoprint.events import eventManager, Events +from octoprint.util import RepeatedTimer +from subprocess import Popen, PIPE +from .ledstrip import LEDStrip +import octoprint.plugin +import RPi.GPIO as GPIO +from flask import jsonify, request, make_response, Response +from octoprint.server.util.flask import restricted_access +from werkzeug.exceptions import BadRequest +import time +import sys +import glob +import os +from datetime import datetime +from datetime import timedelta +import octoprint.util +import requests +import inspect +import threading +import json +import copy +from smbus2 import SMBus +import struct + + +#Function that returns Boolean output state of the GPIO inputs / outputs +def PinState_Boolean(pin, ActiveLow) : + try: + state = GPIO.input(pin) + if ActiveLow and not state: return True + if not ActiveLow and state: return True + return False + except: + return "ERROR: Unable to read pin" + +#Function that returns human-readable output state of the GPIO inputs / outputs +def PinState_Human(pin, ActiveLow): + PinState = PinState_Boolean(pin, ActiveLow) + if PinState == True : + return " ON " + elif PinState == False: + return " OFF " + else: + return PinState + +#Translates the Pull-Up/Pull-Down GPIO resistor setting to ActiveLow/ActiveHigh boolean +def CheckInputActiveLow(Input_Pull_Resistor): + #input_pull_up + #input_pull_down + if Input_Pull_Resistor == "input_pull_up": + return True + else: + return False + +class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SettingsPlugin, + octoprint.plugin.AssetPlugin, octoprint.plugin.BlueprintPlugin, + octoprint.plugin.EventHandlerPlugin): + rpi_outputs = [] + rpi_inputs = [] + waiting_temperature = [] + rpi_outputs_not_changed = [] + notifications = [] + pwm_instances = [] + event_queue = [] + temp_hum_control_status = [] + temperature_sensor_data = [] + last_filament_end_detected = [] + print_complete = False + development_mode = False + dummy_value = 30.0 + dummy_delta = 0.5 + + def start_timer(self): + """ + Function to start timer that checks enclosure temperature + """ + + self._check_temp_timer = RepeatedTimer(10, self.check_enclosure_temp, None, None, True) + self._check_temp_timer.start() + + @staticmethod + def to_float(value): + """Converts value to flow + Arguments: + value {any} -- value to be + Returns: + float -- value converted + """ + + try: + val = float(value) + return val + except: + return 0 + + @staticmethod + def to_int(value): + try: + val = int(value) + return val + except: + return 0 + + @staticmethod + def is_hour(value): + try: + datetime.strptime(value, '%H:%M') + return True + except: + return False + + @staticmethod + def create_date(value): + temp_string = datetime.now().strftime('%m/%d/%Y') + " " + value + return datetime.strptime(temp_string, '%m/%d/%Y %H:%M') + + @staticmethod + def constrain(n, minn, maxn): + return max(min(maxn, n), minn) + + @staticmethod + def get_gcode_value(command_string, gcode): + semicolon = command_string.find(';') + if not semicolon == -1: + command_string = command_string[:semicolon] + + for command in command_string.split(' '): + index = command.upper().find(gcode.upper()) + if not index == -1: + return command.replace(gcode, '') + return -1 + + # ~~ StartupPlugin mixin + def on_after_startup(self): + self.pwm_instances = [] + self.event_queue = [] + self.rpi_outputs_not_changed = [] + self.rpi_outputs = self._settings.get(["rpi_outputs"]) + self.rpi_inputs = self._settings.get(["rpi_inputs"]) + self.notifications = self._settings.get(["notifications"]) + self.generate_temp_hum_control_status() + self.setup_gpio() + self.configure_gpio() + self.update_ui() + self.start_outpus_with_server() + self.handle_initial_gpio_control() + self.start_timer() + self.print_complete = False + + def get_settings_version(self): + return 10 + + def on_settings_migrate(self, target, current=None): + self._logger.warn("######### current settings version %s target settings version %s #########", current, target) + self._logger.info("######### Current settings #########") + self._logger.info("rpi_outputs: %s", self.rpi_outputs) + self._logger.info("rpi_inputs: %s", self.rpi_inputs) + self._logger.info("######### End Current Settings #########") + if current >= 4 and target == 10: + self._logger.warn("######### migrating settings to v10 #########") + old_outputs = self._settings.get(["rpi_outputs"]) + old_inputs = self._settings.get(["rpi_inputs"]) + for rpi_output in old_outputs: + if 'shutdown_on_failed' not in rpi_output: + rpi_output['shutdown_on_failed'] = False + if 'shell_script' not in rpi_output: + rpi_output['shell_script'] = "" + if 'gpio_i2c_enabled' not in rpi_output: + rpi_output['gpio_i2c_enabled'] = False + if 'gpio_i2c_bus' not in rpi_output: + rpi_output['gpio_i2c_bus'] = 1 + if 'gpio_i2c_address' not in rpi_output: + rpi_output['gpio_i2c_address'] = 1 + if 'gpio_i2c_register' not in rpi_output: + rpi_output['gpio_i2c_register'] = 1 + if 'gpio_i2c_data_on' not in rpi_output: + rpi_output['gpio_i2c_data_on'] = 1 + if 'gpio_i2c_data_off' not in rpi_output: + rpi_output['gpio_i2c_data_off'] = 0 + if 'gpio_i2c_register_status' not in rpi_output: + rpi_output['gpio_i2c_register_status'] = 1 + if 'shutdown_on_error' not in rpi_output: + rpi_output['shutdown_on_error'] = False + self._settings.set(["rpi_outputs"], old_outputs) + + old_inputs = self._settings.get(["rpi_inputs"]) + for rpi_input in old_inputs: + if 'temp_i2c_bus' not in rpi_input: + rpi_input['temp_i2c_bus'] = 1 + if 'temp_i2c_address' not in rpi_input: + rpi_input['temp_i2c_address'] = 1 + if 'temp_i2c_register' not in rpi_input: + rpi_input['temp_i2c_register'] = 1 + if 'show_graph_temp' not in rpi_input: + rpi_input['show_graph_temp'] = False + if 'show_graph_humidity' not in rpi_input: + rpi_input['show_graph_humidity'] = False + self._settings.set(["rpi_inputs"], old_inputs) + else: + self._logger.warn("######### settings not compatible #########") + self._settings.set(["rpi_outputs"], []) + self._settings.set(["rpi_inputs"], []) + self.rpi_inputs = self._settings.get(["rpi_inputs"]) + + #Scan all configured inputs and outputs and return the pin value + @octoprint.plugin.BlueprintPlugin.route("/ReadPin/", methods=["GET"]) + def ReadSinglePin(self, identifier): + Resp = [] + MatchFound = False + for rpi_input in self.rpi_inputs: + if identifier == self.to_int(rpi_input['gpio_pin']): + MatchFound = True + ConfiguredAs = "Input" + ActiveLow = CheckInputActiveLow(rpi_input['input_pull_resistor']) + pin = self.to_int(rpi_input['gpio_pin']) + val = PinState_Human(pin,ActiveLow) + label = rpi_input['label'] + Resp.append(dict(Configured_As=ConfiguredAs, label=label, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['gpio_pin']): + MatchFound = True + ConfiguredAs = "Output" + ActiveLow = CheckInputActiveLow(rpi_output['active_low']) + pin = self.to_int(rpi_output['gpio_pin']) + if rpi_output['gpio_i2c_enabled']: + b = self.gpio_i2c_input(rpi_output, ActiveLow) + val = " ON " if b else " OFF " + else: + val = PinState_Human(pin,ActiveLow) + label = rpi_output['label'] + Resp.append(dict(Configured_As=ConfiguredAs, label=label, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) + if MatchFound == False: + pin = int(identifier) + ConfiguredAs = "Unknown" + ActiveLow = "Unknown" + try: + val = GPIO.input(pin) + except: + val = "GPIO pin not initialized." + Resp.append(dict(Configured_As=ConfiguredAs, GPIO_Pin=pin, Active_Low=ActiveLow, State=val)) + return Response(json.dumps(Resp), mimetype='application/json') + + # ~~ Blueprintplugin mixin + @octoprint.plugin.BlueprintPlugin.route("/inputs", methods=["GET"]) + def get_inputs(self): + inputs = [] + for rpi_input in self.rpi_inputs: + index = self.to_int(rpi_input['index_id']) + label = rpi_input['label'] + ActiveLow = CheckInputActiveLow(rpi_input['input_pull_resistor']) + pin = self.to_int(rpi_input['gpio_pin']) + val = PinState_Human(pin,ActiveLow) + inputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, State=val)) + return Response(json.dumps(inputs), mimetype='application/json') + + @octoprint.plugin.BlueprintPlugin.route("/inputs/", methods=["GET"]) + def get_input_status(self, identifier): + for rpi_input in self.rpi_inputs: + if identifier == self.to_int(rpi_input['index_id']): + return Response(json.dumps(rpi_input), mimetype='application/json') + return make_response('', 404) + + + @octoprint.plugin.BlueprintPlugin.route("/temperature/", methods=["PATCH"]) + @restricted_access + def set_enclosure_temp_humidity(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'temperature' not in data: + return make_response("missing temperature attribute", 406) + + set_value = data["temperature"] + + for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == identifier]: + temp_hum_control['temp_ctr_set_value'] = set_value + + self.handle_temp_hum_control() + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/filament/", methods=["PATCH"]) + @restricted_access + def set_filament_sensor(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + for sensor in self.rpi_inputs: + if identifier == self.to_int(sensor['index_id']): + sensor['filament_sensor_enabled'] = value + self._logger.info("Setting filament sensor for input %s to : %s", str(identifier), value) + self._settings.set(["rpi_inputs"], self.rpi_inputs) + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/outputs", methods=["GET"]) + def get_outputs(self): + outputs = [] + for rpi_output in self.rpi_outputs: + if rpi_output['output_type'] == 'regular': + index = self.to_int(rpi_output['index_id']) + label = rpi_output['label'] + pin = self.to_int(rpi_output['gpio_pin']) + ActiveLow = rpi_output['active_low'] + if rpi_output['gpio_i2c_enabled']: + b = self.gpio_i2c_input(rpi_output, ActiveLow) + val = " ON " if b else " OFF " + else: + val = PinState_Human(pin,ActiveLow) + outputs.append(dict(index_id=index, label=label, GPIO_Pin=pin, State=val)) + return Response(json.dumps(outputs), mimetype='application/json') + + + @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["GET"]) + def get_output_status(self, identifier): + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + out = copy.deepcopy(rpi_output) + pin = self.to_int(rpi_output['gpio_pin']) + if rpi_output['gpio_i2c_enabled']: + out['current_value'] = self.gpio_i2c_input(rpi_output, rpi_output['active_low']) + else: + out['current_value'] = PinState_Boolean(pin, rpi_output['active_low'] ) + return Response(json.dumps(out), mimetype='application/json') + return make_response('', 404) + + + @octoprint.plugin.BlueprintPlugin.route("/outputs/", methods=["PATCH"]) + @restricted_access + def set_io(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + val = (not value) if rpi_output['active_low'] else value + if rpi_output['gpio_i2c_enabled']: + self.gpio_i2c_write(rpi_output, val) + else: + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-startup", methods=["PATCH"]) + @restricted_access + def set_auto_startup(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + if not value: + suffix = 'auto_startup' + queue_id = '{0!s}_{1!s}'.format(str(identifier), suffix) + self.stop_queue_item(queue_id) + for output in self.rpi_outputs: + if identifier == self.to_int(output['index_id']): + output['auto_startup'] = value + self._logger.info("Setting auto startup for output %s to : %s", str(identifier), value) + self._settings.set(["rpi_outputs"], self.rpi_outputs) + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/outputs//auto-shutdown", methods=["PATCH"]) + @restricted_access + def set_auto_shutdown(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'status' not in data: + return make_response("missing status attribute", 406) + + value = data["status"] + + if not value: + suffix = 'auto_shutdown' + queue_id = '{0!s}_{1!s}'.format(str(identifier), suffix) + self.stop_queue_item(queue_id) + + for output in self.rpi_outputs: + if identifier == self.to_int(output['index_id']): + output['auto_shutdown'] = value + self._logger.info("Setting auto shutdown for output %s to : %s", str(identifier), value) + self._settings.set(["rpi_outputs"], self.rpi_outputs) + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/pwm/", methods=["PATCH"]) + @restricted_access + def set_pwm(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'duty_cycle' not in data: + return make_response("missing duty_cycle attribute", 406) + + set_value = self.to_int(data['duty_cycle']) + for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == identifier]: + rpi_output['duty_cycle'] = set_value + rpi_output['new_duty_cycle'] = "" + gpio = self.to_int(rpi_output['gpio_pin']) + self.write_pwm(gpio, set_value) + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/rgb-led/", methods=["PATCH"]) + @restricted_access + def set_ledstrip_color(self, identifier): + """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'rgb' not in data: + return make_response("missing rgb attribute", 406) + rgb = data['rgb'] + + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + self.ledstrip_set_rgb(rpi_output, rgb) + + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/neopixel/", methods=["PATCH"]) + @restricted_access + def set_neopixel(self, identifier): + """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + + if 'red' not in data: + return make_response("missing red attribute", 406) + if 'green' not in data: + return make_response("missing green attribute", 406) + if 'blue' not in data: + return make_response("missing blue attribute", 406) + + red = data['red'] + green = data['green'] + blue = data['blue'] + + for rpi_output in self.rpi_outputs: + if identifier == self.to_int(rpi_output['index_id']): + led_count = rpi_output['neopixel_count'] + led_brightness = rpi_output['neopixel_brightness'] + address = rpi_output['microcontroller_address'] + + neopixel_dirrect = rpi_output['output_type'] == 'neopixel_direct' + + self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, green, + blue, address, neopixel_dirrect, identifier) + + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/clear-gpio", methods=["POST"]) + @restricted_access + def clear_gpio_mode(self): + GPIO.cleanup() + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/update", methods=["POST"]) + @restricted_access + def update_ui_requested(self): + self.update_ui() + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/shell/", methods=["POST"]) + @restricted_access + def send_shell_command(self, identifier): + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() + + command = rpi_output['shell_script'] + self.shell_command(command) + return make_response('', 204) + + + @octoprint.plugin.BlueprintPlugin.route("/gcode/", methods=["POST"]) + @restricted_access + def requested_gcode_command(self, identifier): + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == identifier].pop() + self.send_gcode_command(rpi_output['gcode']) + return make_response('', 204) + + + + + + """ + DEPRECATION + This API will be deprecated in a future version + """ + + # ~~ Blueprintplugin mixin + @octoprint.plugin.BlueprintPlugin.route("/setEnclosureTempHum", methods=["GET"]) + def set_enclosure_temp_humidity_old(self): + set_value = self.to_float(request.values["set_temperature"]) + index_id = self.to_int(request.values["index_id"]) + + for temp_hum_control in [item for item in self.rpi_outputs if item['index_id'] == index_id]: + temp_hum_control['temp_ctr_set_value'] = set_value + + self.handle_temp_hum_control() + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/clearGPIOMode", methods=["GET"]) + def clear_gpio_mode_old(self): + GPIO.cleanup() + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/updateUI", methods=["GET"]) + def update_ui_requested_old(self): + self.update_ui() + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/getOutputStatus", methods=["GET"]) + def get_output_status_old(self): + gpio_status = [] + for rpi_output in self.rpi_outputs: + if rpi_output['output_type'] == 'regular': + pin = self.to_int(rpi_output['gpio_pin']) + ActiveLow = rpi_output['active_low'] + if rpi_output['gpio_i2c_enabled']: + val = self.gpio_i2c_input(rpi_output, rpi_output['active_low']) + else: + val = PinState_Boolean(pin, ActiveLow) + val2 = PinState_Human(pin, ActiveLow) + index = self.to_int(rpi_output['index_id']) + gpio_status.append(dict(index_id=index, status=val, State=val2)) + return Response(json.dumps(gpio_status), mimetype='application/json') + + @octoprint.plugin.BlueprintPlugin.route("/setIO", methods=["GET"]) + def set_io_old(self): + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False + for rpi_output in self.rpi_outputs: + if self.to_int(index) == self.to_int(rpi_output['index_id']): + val = (not value) if rpi_output['active_low'] else value + if rpi_output['gpio_i2c_enabled']: + self.gpio_i2c_write(rpi_output, val) + else: + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/sendShellCommand", methods=["GET"]) + def send_shell_command_old(self): + output_index = self.to_int(request.values["index_id"]) + + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == output_index].pop() + + command = rpi_output['shell_script'] + self.shell_command(command) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setAutoStartUp", methods=["GET"]) + def set_auto_startup_old(self): + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False + + if not value: + suffix = 'auto_startup' + queue_id = '{0!s}_{1!s}'.format(index, suffix) + self.stop_queue_item(queue_id) + for output in self.rpi_outputs: + if self.to_int(index) == self.to_int(output['index_id']): + output['auto_startup'] = value + self._logger.info("Setting auto startup for output %s to : %s", index, value) + self._settings.set(["rpi_outputs"], self.rpi_outputs) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setAutoShutdown", methods=["GET"]) + def set_auto_shutdown_old(self): + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False + + if not value: + suffix = 'auto_shutdown' + queue_id = '{0!s}_{1!s}'.format(index, suffix) + self.stop_queue_item(queue_id) + + for output in self.rpi_outputs: + if self.to_int(index) == self.to_int(output['index_id']): + output['auto_shutdown'] = value + self._logger.info("Setting auto shutdown for output %s to : %s", index, value) + self._settings.set(["rpi_outputs"], self.rpi_outputs) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setFilamentSensor", methods=["GET"]) + def set_filament_sensor_old(self): + index = request.values["index_id"] + value = True if request.values["status"] == 'true' else False + for sensor in self.rpi_inputs: + if self.to_int(index) == self.to_int(sensor['index_id']): + sensor['filament_sensor_enabled'] = value + self._logger.info("Setting filament sensor for input %s to : %s", index, value) + self._settings.set(["rpi_inputs"], self.rpi_inputs) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setPWM", methods=["GET"]) + def set_pwm_old(self): + set_value = self.to_int(request.values["new_duty_cycle"]) + index_id = self.to_int(request.values["index_id"]) + for rpi_output in [item for item in self.rpi_outputs if item['index_id'] == index_id]: + rpi_output['duty_cycle'] = set_value + rpi_output['new_duty_cycle'] = "" + gpio = self.to_int(rpi_output['gpio_pin']) + self.write_pwm(gpio, set_value) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/sendGcodeCommand", methods=["GET"]) + def requested_gcode_command_old(self): + gpio_index = self.to_int(request.values["index_id"]) + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int(r_out['index_id']) == gpio_index].pop() + self.send_gcode_command(rpi_output['gcode']) + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setNeopixel", methods=["GET"]) + def set_neopixel_old(self): + """ set_neopixel method get request from octoprint and send the command to arduino or neopixel""" + gpio_index = self.to_int(request.values["index_id"]) + red = request.values["red"] + green = request.values["green"] + blue = request.values["blue"] + for rpi_output in self.rpi_outputs: + if gpio_index == self.to_int(rpi_output['index_id']): + led_count = rpi_output['neopixel_count'] + led_brightness = rpi_output['neopixel_brightness'] + address = rpi_output['microcontroller_address'] + + neopixel_dirrect = rpi_output['output_type'] == 'neopixel_direct' + + self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, green, + blue, address, neopixel_dirrect, gpio_index) + + return jsonify(success=True) + + @octoprint.plugin.BlueprintPlugin.route("/setLedstripColor", methods=["GET"]) + def set_ledstrip_color_old(self): + """ set_ledstrip_color method get request from octoprint and send the command to Open-Smart RGB LED Strip""" + gpio_index = self.to_int(request.values["index_id"]) + rgb = request.values["rgb"] + for rpi_output in self.rpi_outputs: + if gpio_index == self.to_int(rpi_output['index_id']): + self.ledstrip_set_rgb(rpi_output, rgb) + + return jsonify(success=True) + + # DEPREACTION END + + + # GPIO over i2c + + def gpio_i2c_input(self, output, active_low=None): + state = False + try: + i2cbus = self.to_int(output['gpio_i2c_bus']) + i2caddr = self.to_int(output['gpio_i2c_address']) + i2creg = self.to_int(output['gpio_i2c_register_status']) + data_on = self.to_int(output['gpio_i2c_data_on']) + + with SMBus(i2cbus) as bus: + data = bus.read_i2c_block_data(i2caddr, i2creg, 1) + if data[0] == data_on: + state = True + + self._logger.debug("gpio_i2c_input(i2cbus=%s, i2caddr=%s, i2creg=%s, data_on=%s) data == %s", + i2cbus, i2caddr, i2creg, data_on, data) + + if active_low is None and state: return state + + except Exception as ex: + self.log_error(ex) + + if active_low and not state: return True + if not active_low and state: return True + return False + + def gpio_i2c_write(self, output, state, queue_id=None): + try: + i2cbus = self.to_int(output['gpio_i2c_bus']) + i2caddr = self.to_int(output['gpio_i2c_address']) + i2creg = self.to_int(output['gpio_i2c_register']) + data_on = self.to_int(output['gpio_i2c_data_on']) + data_off = self.to_int(output['gpio_i2c_data_off']) + + with SMBus(i2cbus) as bus: + data = [] + if state: + data.append(data_on) + else: + data.append(data_off) + + bus.write_i2c_block_data(i2caddr, i2creg, data) + + if queue_id is not None: + self._logger.debug("Running scheduled queue id %s", queue_id) + self._logger.debug("Writing on GPIO (i2c): %s/%s value %s", output['gpio_i2c_address'], output['gpio_i2c_register'], state) + self.update_ui() + if queue_id is not None: + self.stop_queue_item(queue_id) + + except Exception as ex: + template = "An exception of type {0} occurred on {1} when writing on i2c address {2}, reg {3}. Arguments:\n{4!r}" + message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, output['gpio_i2c_address'], output['gpio_i2c_register'], ex.args) + self._logger.warn(message) + pass + + + def send_neopixel_command(self, led_pin, led_count, led_brightness, red, green, blue, address, neopixel_dirrect, + index_id, queue_id=None): + """Send neopixel command + Arguments: + led_pin {int} -- GPIO number + ledCount {int} -- number of LEDS + ledBrightness {int} -- brightness from 0 to 255 + red {int} -- red value from 0 to 255 + green {int} -- green value from 0 to 255 + blue {int} -- blue value from 0 to 255 + address {int} -- i2c address from microcontroller + """ + + try: + + for rpi_output in self.rpi_outputs: + if self.to_int(index_id) == self.to_int(rpi_output['index_id']): + rpi_output['neopixel_color'] = 'rgb({0!s},{1!s},{2!s})'.format(red, green, blue) + + if address == '': + address = 0 + + if neopixel_dirrect: + script = os.path.dirname(os.path.realpath(__file__)) + "/neopixel_direct.py " + else: + script = os.path.dirname(os.path.realpath(__file__)) + "/neopixel_indirect.py " + + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + + cmd = sudo_str + "python " + script + str(led_pin) + " " + str(led_count) + " " + str( + led_brightness) + " " + str(red) + " " + str(green) + " " + str(blue) + " " + + if neopixel_dirrect: + dma = self._settings.get(["neopixel_dma"]) or 10 + cmd = cmd + str(dma) + else: + cmd = cmd + str(address) + + if queue_id is not None: + self._logger.debug("running scheduled queue id %s", queue_id) + self._logger.debug("Sending neopixel cmd: %s", cmd) + Popen(cmd, shell=True) + if queue_id is not None: + self.stop_queue_item(queue_id) + except Exception as ex: + self.log_error(ex) + + def check_enclosure_temp(self): + try: + sensor_data = [] + for sensor in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): + temp, hum = self.get_sensor_data(sensor) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Sensor %s Temperature: %s humidity %s", sensor['label'], temp, hum) + if temp is not None and hum is not None: + sensor["temp_sensor_temp"] = temp + sensor["temp_sensor_humidity"] = hum + sensor_data.append(dict(index_id=sensor['index_id'], temperature=temp, humidity=hum)) + self.temperature_sensor_data = sensor_data + self.handle_temp_hum_control() + self.handle_temperature_events() + self.handle_pwm_linked_temperature() + self.update_ui() + except Exception as ex: + self.log_error(ex) + + def toggle_output(self, output_index, first_run=False): + for output in [item for item in self.rpi_outputs if item['index_id'] == output_index]: + gpio_pin = self.to_int(output['gpio_pin']) + index_id = self.to_int(output['index_id']) + + if output['output_type'] == 'regular': + if output['gpio_i2c_enabled']: + current_value = self.gpio_i2c_input(output) + else: + if first_run: + current_value = False + else: + current_value = (not GPIO.input(gpio_pin)) if output['active_low'] else GPIO.input(gpio_pin) + + if current_value: + time_delay = self.to_int(output['toggle_timer_off']) + else: + time_delay = self.to_int(output['toggle_timer_on']) + + if not self.print_complete: + if output['gpio_i2c_enabled']: + self.gpio_i2c_write(output, not current_value) + else: + self.write_gpio(gpio_pin, not current_value) + thread = threading.Timer(time_delay, self.toggle_output, args=[index_id]) + thread.start() + else: + off_value = True if output['active_low'] else False + if output['gpio_i2c_enabled']: + self.gpio_i2c_write(output, off_value) + else: + self.write_gpio(gpio_pin, off_value) + self.update_ui_outputs() + return + + if output['output_type'] == 'pwm': + for pwm in self.pwm_instances: + if gpio_pin in pwm: + if first_run: + current_pwm_value = 0 + else: + if 'duty_cycle' in pwm: + current_pwm_value = pwm['duty_cycle'] + current_pwm_value = self.to_int(current_pwm_value) + else: + current_pwm_value = 0 + + if not current_pwm_value == 0: + time_delay = self.to_int(output['toggle_timer_off']) + write_value = 0 + else: + time_delay = self.to_int(output['toggle_timer_on']) + write_value = self.to_int(output['default_duty_cycle']) + + if not self.print_complete: + self.write_pwm(gpio_pin, write_value) + thread = threading.Timer(time_delay, self.toggle_output, args=[index_id]) + thread.start() + else: + self.write_pwm(self.to_int(output['gpio_pin']), 0) + self.update_ui_outputs() + return + + def update_ui(self): + self.update_ui_outputs() + self.update_ui_current_temperature() + self.update_ui_set_temperature() + self.update_ui_inputs() + + def update_ui_current_temperature(self): + self._plugin_manager.send_plugin_message(self._identifier, dict(sensor_data=self.temperature_sensor_data)) + + def update_ui_set_temperature(self): + result = [] + for temp_crt_output in list(filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): + set_temperature = self.to_float(temp_crt_output['temp_ctr_set_value']) + result.append(dict(index_id=temp_crt_output['index_id'], set_temperature=set_temperature)) + result.append(set_temperature) + self._plugin_manager.send_plugin_message(self._identifier, dict(set_temperature=result)) + + def stop_queue_item(self, queue_id): + old_list = self.event_queue + self._logger.debug("Stopping queue id %s...", queue_id) + for task in self.event_queue: + self._logger.debug("Queue id found...") + if task['queue_id'] == queue_id: + task['thread'].cancel() + self.event_queue.remove(task) + self._logger.debug("Queue id stopped and removed from list...") + self._logger.debug("Old queue list: %s", old_list) + self._logger.debug("New queue list: %s", self.event_queue) + + def update_ui_outputs(self): + try: + regular_status = [] + pwm_status = [] + neopixel_status = [] + temp_control_status = [] + for output in self.rpi_outputs: + index = self.to_int(output['index_id']) + pin = self.to_int(output['gpio_pin']) + startup = output['auto_startup'] + shutdown = output['auto_shutdown'] + + if output['output_type'] == 'regular': + if output['gpio_i2c_enabled']: + val = self.gpio_i2c_input(output) + else: + val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) + regular_status.append( + dict(index_id=index, status=val, auto_startup=startup, auto_shutdown=shutdown)) + if output['output_type'] == 'temp_hum_control': + if output['gpio_i2c_enabled']: + val = self.gpio_i2c_input(output) + else: + val = GPIO.input(pin) if not output['active_low'] else (not GPIO.input(pin)) + temp_control_status.append( + dict(index_id=index, status=val, auto_startup=startup, auto_shutdown=shutdown)) + if output['output_type'] == 'neopixel_indirect' or output['output_type'] == 'neopixel_direct': + val = output['neopixel_color'] + neopixel_status.append( + dict(index_id=index, color=val, auto_startup=startup, auto_shutdown=shutdown)) + if output['output_type'] == 'pwm': + for pwm in self.pwm_instances: + if pin in pwm: + if 'duty_cycle' in pwm: + pwm_val = pwm['duty_cycle'] + val = self.to_int(pwm_val) + else: + val = 0 + pwm_status.append( + dict(index_id=index, pwm_value=val, auto_startup=startup, auto_shutdown=shutdown)) + self._plugin_manager.send_plugin_message(self._identifier, + dict(rpi_output_regular=regular_status, rpi_output_pwm=pwm_status, + rpi_output_neopixel=neopixel_status, + rpi_output_temp_hum_ctrl=temp_control_status)) + except Exception as ex: + self.log_error(ex) + + def update_ui_inputs(self): + try: + sensor_status = [] + for sensor in self.rpi_inputs: + if sensor['input_type'] == 'gpio' and sensor['action_type'] == 'printer_control' and sensor[ + 'printer_action'] == 'filament': + index = self.to_int(sensor['index_id']) + value = sensor['filament_sensor_enabled'] + sensor_status.append(dict(index_id=index, filament_sensor_enabled=value)) + self._plugin_manager.send_plugin_message(self._identifier, dict(filament_sensor_status=sensor_status)) + except Exception as ex: + self.log_error(ex) + + def get_sensor_data(self, sensor): + try: + if self.development_mode: + temp, hum = self.read_dummy_temp() + else: + if sensor['temp_sensor_type'] in ["11", "22", "2302"]: + temp, hum = self.read_dht_temp(sensor['temp_sensor_type'], sensor['gpio_pin']) + elif sensor['temp_sensor_type'] == "18b20": + temp = self.read_18b20_temp(sensor['ds18b20_serial']) + hum = 0 + elif sensor['temp_sensor_type'] == "bme280": + temp, hum = self.read_bme280_temp(sensor['temp_sensor_address']) + elif sensor['temp_sensor_type'] == "am2320": + temp, hum = self.read_am2320_temp() # sensor has fixed address + elif sensor['temp_sensor_type'] == "si7021": + temp, hum = self.read_si7021_temp(sensor['temp_sensor_address'], sensor['temp_sensor_i2cbus']) + elif sensor['temp_sensor_type'] == "tmp102": + temp = self.read_tmp102_temp(sensor['temp_sensor_address']) + hum = 0 + elif sensor['temp_sensor_type'] == "max31855": + temp = self.read_max31855_temp(sensor['temp_sensor_address']) + hum = 0 + elif sensor['temp_sensor_type'] == "mcp9808": + temp = self.read_mcp_temp(sensor['temp_sensor_address']) + hum = 0 + elif sensor['temp_sensor_type'] == "temp_raw_i2c": + temp, hum = self.read_raw_i2c_temp(sensor) + elif sensor['temp_sensor_type'] == "hum_raw_i2c": + hum, temp = self.read_raw_i2c_temp(sensor) + else: + self._logger.info("temp_sensor_type no match") + temp = None + hum = None + if temp != -1 and hum != -1: + temp = round(self.to_float(temp), 1) if not sensor['use_fahrenheit'] else round( + self.to_float(temp) * 1.8 + 32, 1) + hum = round(self.to_float(hum), 1) + return temp, hum + return None, None + except Exception as ex: + self.log_error(ex) + + def handle_temperature_events(self): + for temperature_alarm in [item for item in self.rpi_outputs if item['output_type'] == 'temperature_alarm']: + set_temperature = self.to_float(temperature_alarm['alarm_set_temp']) + if int(set_temperature) is 0: + continue + linked_data = [item for item in self.temperature_sensor_data if + item['index_id'] == temperature_alarm['linked_temp_sensor']].pop() + sensor_temperature = self.to_float(linked_data['temperature']) + if set_temperature < sensor_temperature: + for rpi_controlled_output in self.rpi_outputs: + if self.to_int(temperature_alarm['controlled_io']) == self.to_int( + rpi_controlled_output['index_id']): + if rpi_controlled_output['gpio_i2c_enabled']: + val = False if temperature_alarm['controlled_io_set_value'] == 'low' else True + self.gpio_i2c_write(rpi_controlled_output, val) + else: + val = GPIO.LOW if temperature_alarm['controlled_io_set_value'] == 'low' else GPIO.HIGH + self.write_gpio(self.to_int(rpi_controlled_output['gpio_pin']), val) + for notification in self.notifications: + if notification['temperatureAction']: + msg = ("Temperature action: enclosure temperature exceed " + temperature_alarm[ + 'alarm_set_temp']) + self.send_notification(msg) + + def read_dummy_temp(self): + current_value = self.dummy_value + if current_value > 40 or current_value < 30: + self.dummy_delta = - self.dummy_delta + + return_value = current_value + self.dummy_delta + + self.dummy_value = return_value + + return return_value, return_value + + def read_raw_i2c_temp(self, sensor): + try: + i2cbus = self.to_int(sensor['temp_i2c_bus']) + i2caddr = self.to_int(sensor['temp_i2c_address']) + i2creg = self.to_int(sensor['temp_i2c_register']) + + with SMBus(i2cbus) as bus: + data = bus.read_i2c_block_data(i2caddr, i2creg, 8) + fval1 = struct.unpack('f', bytearray(data[0:4]))[0] + if fval1 != fval1: + fval1 = 0 + fval2 = struct.unpack('f', bytearray(data[4:8]))[0] + if fval2 != fval2: + fval2 = 0 + + self._logger.debug("read_raw_i2c_temp(i2cbus=%s, i2caddr=%s, i2creg=%s) data == %s (%s, %s)", + i2cbus, i2caddr, i2creg, data, fval1, fval2) + + return (fval1, fval2) + + except Exception as ex: + template = "An exception of type {0} occurred on {1} when reading on i2c address {2}, reg {3}. Arguments:\n{4!r}" + message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, i2caddr, i2creg, ex.args) + self._logger.warn(message) + return str(-1) + + def read_mcp_temp(self, address): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/mcp9808.py" + args = ["python", script, str(address)] + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature MCP9808 cmd: %s", " ".join(args)) + proc = Popen(args, stdout=PIPE) + stdout, _ = proc.communicate() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("MCP9808 result: %s", stdout) + return self.to_float(stdout.decode("utf-8").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 + + def read_dht_temp(self, sensor, pin): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/getDHTTemp.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python3 " + script + str(sensor) + " " + str(pin) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature dht cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Dht result: %s", stdout) + temp, hum = stdout.decode("utf-8").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 of the plugin.") + self.log_error(ex) + return (0, 0) + + def read_bme280_temp(self, address): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/BME280.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python " + script + str(address) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature BME280 cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("BME280 result: %s", stdout) + temp, hum = stdout.decode("utf-8").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 of the plugin.") + self.log_error(ex) + return (0, 0) + + def read_am2320_temp(self): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/AM2320.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python " + script # sensor has fixed address 0x5C + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature AM2320 cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("AM2320 result: %s", stdout) + temp, hum = stdout.decode("utf-8").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 of the plugin.") + self.log_error(ex) + return (0, 0) + + def read_si7021_temp(self, address, i2cbus): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/SI7021.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python " + script + str(address) + " " + str(i2cbus) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature SI7021 cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("SI7021 result: %s", stdout) + temp, hum = stdout.decode("utf-8").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 of the plugin.") + self.log_error(ex) + return (0, 0) + + def read_18b20_temp(self, serial_number): + os.system('modprobe w1-gpio') + os.system('modprobe w1-therm') + + lines = self.read_raw_18b20_temp(serial_number) + while lines[0].strip()[-3:] != 'YES': + time.sleep(0.2) + lines = self.read_raw_18b20_temp(serial_number) + equals_pos = lines[1].find('t=') + if equals_pos != -1: + temp_string = lines[1][equals_pos + 2:] + temp_c = float(temp_string) / 1000. + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("DS18B20 result: %s", temp_c) + return '{0:0.1f}'.format(temp_c) + return 0 + + def read_raw_18b20_temp(self, serial_number): + base_dir = '/sys/bus/w1/devices/' + device_folder = glob.glob(base_dir + str(serial_number) + '*')[0] + device_file = device_folder + '/w1_slave' + device_file_result = open(device_file, 'r') + lines = device_file_result.readlines() + device_file_result.close() + return lines + + def read_tmp102_temp(self, address): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/tmp102.py" + args = ["python", script, str(address)] + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature TMP102 cmd: %s", " ".join(args)) + proc = Popen(args, stdout=PIPE) + stdout, _ = proc.communicate() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("TMP102 result: %s", stdout) + return self.to_float(stdout.decode("utf-8").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 + + def read_max31855_temp(self, address): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/max31855.py" + args = ["python", script, str(address)] + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature MAX31855 cmd: %s", " ".join(args)) + proc = Popen(args, stdout=PIPE) + stdout, _ = proc.communicate() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("MAX31855 result: %s", stdout) + return self.to_float(stdout.decode("utf-8").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 + + def handle_pwm_linked_temperature(self): + try: + for pwm_output in list(filter(lambda item: item['output_type'] == 'pwm' and item['pwm_temperature_linked'], + self.rpi_outputs)): + gpio_pin = self.to_int(pwm_output['gpio_pin']) + if self._printer.is_printing(): + index_id = self.to_int(pwm_output['index_id']) + linked_id = self.to_int(pwm_output['linked_temp_sensor']) + linked_data = self.get_linked_temp_sensor_data(linked_id) + current_temp = self.to_float(linked_data['temperature']) + + duty_a = self.to_float(pwm_output['duty_a']) + duty_b = self.to_float(pwm_output['duty_b']) + temp_a = self.to_float(pwm_output['temperature_a']) + temp_b = self.to_float(pwm_output['temperature_b']) + + try: + calculated_duty = ((current_temp - temp_a) * (duty_b - duty_a) / (temp_b - temp_a)) + duty_a + + if current_temp < temp_a: + calculated_duty = 0 + except: + calculated_duty = 0 + + self._logger.debug("Calculated duty for PWM %s is %s", index_id, calculated_duty) + elif self.print_complete: + calculated_duty = self.to_int(pwm_output['duty_cycle']) + else: + calculated_duty = 0 + + self.write_pwm(gpio_pin, self.constrain(calculated_duty, 0, 100)) + + except Exception as ex: + self.log_error(ex) + + def get_linked_temp_sensor_data(self, linked_id): + try: + linked_data = [data for data in self.temperature_sensor_data if data['index_id'] == linked_id].pop() + return linked_data + except: + self._logger.warn("No linked temperature sensor found for %s", linked_id) + return None + + def handle_temp_hum_control(self): + try: + for temp_hum_control in list( + filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): + + set_temperature = self.to_float(temp_hum_control['temp_ctr_set_value']) + temp_deadband = self.to_float(temp_hum_control['temp_ctr_deadband']) + max_temp = self.to_float(temp_hum_control['temp_ctr_max_temp']) + + linked_id = temp_hum_control['linked_temp_sensor'] + + previous_status = list(filter(lambda item: item['index_id'] == temp_hum_control['index_id'], + self.temp_hum_control_status)).pop()['status'] + + if set_temperature == 0: + current_status = False + else: + linked_data = self.get_linked_temp_sensor_data(linked_id) + + control_type = str(temp_hum_control['temp_ctr_type']) + + if control_type == 'dehumidifier': + current_value = self.to_float(linked_data['humidity']) + temp_deadband = 0 + else: + current_value = self.to_float(linked_data['temperature']) + + if control_type == 'cooler' or control_type == 'dehumidifier': + if current_value <= set_temperature and current_value >= (set_temperature - temp_deadband): + current_status = previous_status + elif current_value < set_temperature: + current_status = False + else: + current_status = True + else: + if current_value <= set_temperature and current_value >= (set_temperature - temp_deadband): + current_status = previous_status + elif current_value > set_temperature: + current_status = False + else: + current_status = True + + if control_type == 'heater' and max_temp > 0.0 and max_temp < current_value: + self._logger.debug("Maximum temperature reached for temperature control %s", + temp_hum_control['index_id']) + temp_hum_control['temp_ctr_set_value'] = 0 + current_status = False + + if current_status != previous_status: + if current_status: + self._logger.info("Turning gpio to control temperature on.") + val = False if temp_hum_control['active_low'] else True + if temp_hum_control['gpio_i2c_enabled']: + self.gpio_i2c_write(temp_hum_control, val) + else: + self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) + else: + index_id = temp_hum_control['index_id'] + if index_id in self.waiting_temperature: + self.waiting_temperature.remove(index_id) + + if not self.waiting_temperature and self._printer.is_paused(): + self._printer.resume_print() + + self._logger.info("Turning gpio to control temperature off.") + val = True if temp_hum_control['active_low'] else False + if temp_hum_control['gpio_i2c_enabled']: + self.gpio_i2c_write(temp_hum_control, val) + else: + self.write_gpio(self.to_int(temp_hum_control['gpio_pin']), val) + for control_status in self.temp_hum_control_status: + if control_status['index_id'] == temp_hum_control['index_id']: + control_status['status'] = current_status + except Exception as ex: + self.log_error(ex) + + def log_error(self, ex): + template = "An exception of type {0} occurred on {1}. Arguments:\n{2!r}" + message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, ex.args) + self._logger.warn(message, exc_info = True) + + def setup_gpio(self): + try: + current_mode = GPIO.getmode() + set_mode = GPIO.BOARD if self._settings.get(["use_board_pin_number"]) else GPIO.BCM + if current_mode is None: + outputs = list(filter( + lambda item: (item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ + 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct') and + item['gpio_i2c_enabled'] == False, + self.rpi_outputs)) + inputs = list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)) + gpios = outputs + inputs + if gpios: + GPIO.setmode(set_mode) + tempstr = "BOARD" if set_mode == GPIO.BOARD else "BCM" + self._logger.info("Setting GPIO mode to %s", tempstr) + elif current_mode != set_mode: + GPIO.setmode(current_mode) + tempstr = "BOARD" if current_mode == GPIO.BOARD else "BCM" + self._settings.set(["use_board_pin_number"], True if current_mode == GPIO.BOARD else False) + warn_msg = "GPIO mode was configured before, GPIO mode will be forced to use: " + tempstr + " as pin numbers. Please update GPIO accordingly!" + self._logger.info(warn_msg) + self._plugin_manager.send_plugin_message(self._identifier, + dict(is_msg=True, msg=warn_msg, msg_type="error")) + GPIO.setwarnings(False) + except Exception as ex: + self.log_error(ex) + + def clear_gpio(self): + try: + + for gpio_out in list(filter( + lambda item: (item['output_type'] == 'regular' or item['output_type'] == 'pwm' or item[ + 'output_type'] == 'temp_hum_control' or item['output_type'] == 'neopixel_direct') and + item['gpio_i2c_enabled'] == False, + self.rpi_outputs)): + gpio_pin = self.to_int(gpio_out['gpio_pin']) + if gpio_pin not in self.rpi_outputs_not_changed: + GPIO.cleanup(gpio_pin) + + for gpio_in in list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)): + try: + GPIO.remove_event_detect(self.to_int(gpio_in['gpio_pin'])) + except: + pass + GPIO.cleanup(self.to_int(gpio_in['gpio_pin'])) + except Exception as ex: + self.log_error(ex) + + def clear_channel(self, channel): + try: + GPIO.cleanup(self.to_int(channel)) + self._logger.debug("Clearing channel %s", channel) + except Exception as ex: + self.log_error(ex) + + def generate_temp_hum_control_status(self): + status = [] + for temp_hum_control in list(filter(lambda item: item['output_type'] == 'temp_hum_control', self.rpi_outputs)): + status.append(dict(index_id=temp_hum_control['index_id'], status=False)) + self.temp_hum_control_status = status + + def configure_gpio(self): + try: + + for gpio_out in list( + filter(lambda item: (item['output_type'] == 'regular' or item['output_type'] == 'temp_hum_control') and + item['gpio_i2c_enabled'] == False, + self.rpi_outputs)): + initial_value = GPIO.HIGH if gpio_out['active_low'] else GPIO.LOW + pin = self.to_int(gpio_out['gpio_pin']) + if pin not in self.rpi_outputs_not_changed: + self._logger.info("Setting GPIO pin %s as OUTPUT with initial value: %s", pin, initial_value) + GPIO.setup(pin, GPIO.OUT, initial=initial_value) + for gpio_out_pwm in list(filter(lambda item: item['output_type'] == 'pwm', self.rpi_outputs)): + pin = self.to_int(gpio_out_pwm['gpio_pin']) + self._logger.info("Setting GPIO pin %s as PWM", pin) + for pwm in (pwm_dict for pwm_dict in self.pwm_instances if pin in pwm_dict): + self.pwm_instances.remove(pwm) + self.clear_channel(pin) + GPIO.setup(pin, GPIO.OUT) + pwm_instance = GPIO.PWM(pin, self.to_int(gpio_out_pwm['pwm_frequency'])) + self._logger.info("starting PWM on pin %s", pin) + pwm_instance.start(0) + self.pwm_instances.append({pin: pwm_instance}) + for gpio_out_neopixel in list( + filter(lambda item: item['output_type'] == 'neopixel_direct', self.rpi_outputs)): + pin = self.to_int(gpio_out_neopixel['gpio_pin']) + self.clear_channel(pin) + + for rpi_input in list(filter(lambda item: item['input_type'] == 'gpio', self.rpi_inputs)): + gpio_pin = self.to_int(rpi_input['gpio_pin']) + pull_resistor = GPIO.PUD_UP if rpi_input['input_pull_resistor'] == 'input_pull_up' else GPIO.PUD_DOWN + GPIO.setup(gpio_pin, GPIO.IN, pull_resistor) + edge = GPIO.RISING if rpi_input['edge'] == 'rise' else GPIO.FALLING + + inputs_same_gpio = list( + [r_inp for r_inp in self.rpi_inputs if self.to_int(r_inp['gpio_pin']) == gpio_pin]) + + if len(inputs_same_gpio) > 1: + GPIO.remove_event_detect(gpio_pin) + for other_input in inputs_same_gpio: + if other_input['edge'] is not edge: + edge = GPIO.BOTH + + if rpi_input['action_type'] == 'output_control': + self._logger.info("Adding GPIO event detect on pin %s with edge: %s", gpio_pin, edge) + GPIO.add_event_detect(gpio_pin, edge, callback=self.handle_gpio_control, bouncetime=200) + if (rpi_input['action_type'] == 'printer_control' and rpi_input['printer_action'] != 'filament'): + GPIO.add_event_detect(gpio_pin, edge, callback=self.handle_printer_action, bouncetime=200) + self._logger.info("Adding PRINTER CONTROL event detect on pin %s with edge: %s", gpio_pin, edge) + + for rpi_input in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): + gpio_pin = self.to_int(rpi_input['gpio_pin']) + if rpi_input['input_pull_resistor'] == 'input_pull_up': + pull_resistor = GPIO.PUD_UP + elif rpi_input['input_pull_resistor'] == 'input_pull_down': + pull_resistor = GPIO.PUD_DOWN + else: + pull_resistor = GPIO.PUD_OFF + GPIO.setup(gpio_pin, GPIO.IN, pull_up_down=pull_resistor) + except Exception as ex: + self.log_error(ex) + + def handle_filamment_detection(self, channel): + try: + for filament_sensor in list(filter( + lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ + 'printer_action'] == 'filament' and self.to_int(item['gpio_pin']) == self.to_int(channel), + self.rpi_inputs)): + if ((filament_sensor['edge'] == 'fall') ^ (GPIO.input(self.to_int(filament_sensor['gpio_pin']))) and + filament_sensor['filament_sensor_enabled']): + last_detected_time = list(filter(lambda item: item['index_id'] == filament_sensor['index_id'], + self.last_filament_end_detected)).pop()['time'] + time_now = time.time() + time_difference = self.to_int(time_now - last_detected_time) + time_out_value = self.to_int(filament_sensor['filament_sensor_timeout']) + if time_difference > time_out_value: + self._logger.info("Detected end of filament.") + for item in self.last_filament_end_detected: + if item['index_id'] == filament_sensor['index_id']: + item['time'] = time_now + for line in self._settings.get(["filament_sensor_gcode"]).split('\n'): + if line: + self._printer.commands(line.strip()) + self._logger.info("Sending GCODE command: %s", line.strip()) + time.sleep(0.2) + for notification in self.notifications: + if notification['filamentChange']: + msg = "Filament change action caused by sensor: " + str(filament_sensor['label']) + self.send_notification(msg) + else: + self._logger.info("Prevented end of filament detection, filament sensor timeout not elapsed.") + except Exception as ex: + self.log_error(ex) + + def start_filament_detection(self): + self.stop_filament_detection() + try: + for filament_sensor in list(filter( + lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ + 'printer_action'] == 'filament', self.rpi_inputs)): + edge = GPIO.RISING if filament_sensor['edge'] == 'rise' else GPIO.FALLING + if GPIO.input(self.to_int(filament_sensor['gpio_pin'])) == (edge == GPIO.RISING): + self._printer.pause_print() + self._logger.info("Started printing with no filament.") + else: + self.last_filament_end_detected.append(dict(index_id=filament_sensor['index_id'], time=0)) + self._logger.info("Adding GPIO event detect on pin %s with edge: %s", filament_sensor['gpio_pin'], + edge) + GPIO.add_event_detect(self.to_int(filament_sensor['gpio_pin']), edge, + callback=self.handle_filamment_detection, bouncetime=200) + except Exception as ex: + self.log_error(ex) + + def stop_filament_detection(self): + try: + self.last_filament_end_detected = [] + for filament_sensor in list(filter( + lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'printer_control' and item[ + 'printer_action'] == 'filament', self.rpi_inputs)): + GPIO.remove_event_detect(self.to_int(filament_sensor['gpio_pin'])) + except Exception as ex: + self.log_error(ex) + + def cancel_all_events_on_queue(self): + for task in self.event_queue: + try: + task['thread'].cancel() + except: + self._logger.warn("Failed to stop task %s.", task) + pass + + def handle_initial_gpio_control(self): + try: + for rpi_input in list( + filter(lambda item: item['input_type'] == 'gpio' and item['action_type'] == 'output_control', + self.rpi_inputs)): + gpio_pin = self.to_int(rpi_input['gpio_pin']) + controlled_io = self.to_int(rpi_input['controlled_io']) + if (rpi_input['edge'] == 'fall') ^ GPIO.input(gpio_pin): + rpi_output = [r_out for r_out in self.rpi_outputs if + self.to_int(r_out['index_id']) == controlled_io].pop() + if rpi_output['output_type'] == 'regular': + if rpi_output['gpio_i2c_enabled']: + val = False if rpi_input['controlled_io_set_value'] == 'low' else True + self.gpio_i2c_write(rpi_output, val) + else: + val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + except Exception as ex: + self.log_error(ex) + pass + + def shell_command(self, command): + try: + stdout = (Popen(command, shell=True, stdout=PIPE).stdout).read() + self._plugin_manager.send_plugin_message(self._identifier, + dict(is_msg=True, msg=stdout, msg_type="success")) + except Exception as ex: + self.log_error(ex) + self._plugin_manager.send_plugin_message(self._identifier, + dict(is_msg=True, msg="Could not execute shell script", msg_type="error")) + + def handle_gpio_control(self, channel): + + try: + self._logger.debug("GPIO event triggered on channel %s", channel) + for rpi_input in list( + filter(lambda item: self.to_int(item['gpio_pin']) == self.to_int(channel), self.rpi_inputs)): + gpio_pin = self.to_int(rpi_input['gpio_pin']) + controlled_io = self.to_int(rpi_input['controlled_io']) + if (rpi_input['edge'] == 'fall') ^ GPIO.input(gpio_pin): + rpi_output = [r_out for r_out in self.rpi_outputs if + self.to_int(r_out['index_id']) == controlled_io].pop() + if rpi_output['output_type'] == 'regular': + if rpi_input['controlled_io_set_value'] == 'toggle': + val = GPIO.LOW if GPIO.input( + self.to_int(rpi_output['gpio_pin'])) == GPIO.HIGH else GPIO.HIGH + else: + val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH + if rpi_output['gpio_i2c_enabled']: + self.gpio_i2c_write(rpi_output, val) + else: + self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) + for notification in self.notifications: + if notification['gpioAction']: + msg = "GPIO control action caused by input " + str( + rpi_input['label']) + ". Setting GPIO" + str( + rpi_input['controlled_io']) + " to: " + str(rpi_input['controlled_io_set_value']) + self.send_notification(msg) + if rpi_output['output_type'] == 'gcode_output': + self.send_gcode_command(rpi_output['gcode']) + for notification in self.notifications: + if notification['gpioAction']: + msg = "GPIO control action caused by input " + str( + rpi_input['label']) + ". Sending GCODE command" + self.send_notification(msg) + if rpi_output['output_type'] == 'shell_output': + command = rpi_output['shell_script'] + self.shell_command(command) + except Exception as ex: + self.log_error(ex) + pass + + def send_gcode_command(self, command): + for line in command.split('\n'): + if line: + self._printer.commands(line.strip()) + self._logger.info("Sending GCODE command: %s", line.strip()) + time.sleep(0.2) + + def handle_printer_action(self, channel): + try: + for rpi_input in self.rpi_inputs: + if (channel == self.to_int(rpi_input['gpio_pin']) and rpi_input[ + 'action_type'] == 'printer_control' and ( + (rpi_input['edge'] == 'fall') ^ GPIO.input(self.to_int(rpi_input['gpio_pin'])))): + if rpi_input['printer_action'] == 'resume': + self._logger.info("Printer action resume.") + self._printer.resume_print() + elif rpi_input['printer_action'] == 'pause': + self._logger.info("Printer action pause.") + self._printer.pause_print() + elif rpi_input['printer_action'] == 'cancel': + self._logger.info("Printer action cancel.") + self._printer.cancel_print() + elif rpi_input['printer_action'] == 'toggle': + self._logger.info("Printer action toggle.") + if self._printer.is_operational(): + self._printer.toggle_pause_print() + else: + self._printer.connect() + elif rpi_input['printer_action'] == 'start': + self._logger.info("Printer action start.") + self._printer.start_print() + elif rpi_input['printer_action'] == 'toggle_job': + self._logger.info("Printer action toggle_job.") + if self._printer.is_operational(): + if self._printer.is_printing(): + self._printer.cancel_print() + elif self._printer.is_ready(): + self._printer.start_print() + else: + self._printer.connect() + elif rpi_input['printer_action'] == 'stop_temp_hum_control': + self._logger.info("Printer action stopping temperature control.") + for rpi_output in self.rpi_outputs: + if rpi_output['auto_shutdown'] and rpi_output['output_type'] == 'temp_hum_control': + rpi_output['temp_ctr_set_value'] = 0 + self.handle_temp_hum_control() + for notification in self.notifications: + if notification['printer_action']: + msg = "Printer action: " + rpi_input['printer_action'] + " caused by input: " + str( + rpi_input['label']) + self.send_notification(msg) + except Exception as ex: + self.log_error(ex) + pass + + def write_gpio(self, gpio, value, queue_id=None): + try: + GPIO.output(gpio, value) + if queue_id is not None: + self._logger.debug("Running scheduled queue id %s", queue_id) + self._logger.debug("Writing on GPIO: %s value %s", gpio, value) + self.update_ui() + if queue_id is not None: + self.stop_queue_item(queue_id) + except Exception as ex: + template = "An exception of type {0} occurred on {1} when writing on pin {2}. Arguments:\n{3!r}" + message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, gpio, ex.args) + self._logger.warn(message) + pass + + def write_pwm(self, gpio, pwm_value, queue_id=None): + try: + if queue_id is not None: + self._logger.debug("running scheduled queue id %s", queue_id) + for pwm in self.pwm_instances: + if gpio in pwm: + pwm_object = pwm[gpio] + old_pwm_value = pwm['duty_cycle'] if 'duty_cycle' in pwm else -1 + if not self.to_int(old_pwm_value) == self.to_int(pwm_value): + pwm['duty_cycle'] = pwm_value + pwm_object.start(pwm_value) #should be changed back to pwm_object.ChangeDutyCycle() but this + # was causing errors. + self._logger.debug("Writing PWM on gpio: %s value %s", gpio, pwm_value) + self.update_ui() + if queue_id is not None: + self.stop_queue_item(queue_id) + break + except Exception as ex: + self.log_error(ex) + pass + + def get_output_list(self): + result = [] + for rpi_output in self.rpi_outputs: + if rpi_output['output_type'] == 'regular': + result.append(self.to_int(rpi_output['gpio_pin'])) + return result + + def send_notification(self, message): + try: + provider = self._settings.get(["notification_provider"]) + if provider == 'ifttt': + event = self._settings.get(["notification_event_name"]) + api_key = self._settings.get(["notification_api_key"]) + self._logger.debug("Sending notification to: %s with msg: %s with key: %s", provider, message, + api_key) + try: + res = self.ifttt_notification(message, event, api_key) + except requests.exceptions.ConnectionError: + self._logger.info("Error: Could not connect to IFTTT") + except requests.exceptions.HTTPError: + self._logger.info("Error: Received invalid response") + except requests.exceptions.Timeout: + self._logger.info("Error: Request timed out") + except requests.exceptions.TooManyRedirects: + self._logger.info("Error: Too many redirects") + except requests.exceptions.RequestException as reqe: + self._logger.info("Error: {e}".format(e=reqe)) + if res.status_code != requests.codes['ok']: + try: + j = res.json() + except ValueError: + self._logger.info('Error: Could not parse server response. Event not sent') + for err in j['errors']: + self._logger.info('Error: {}'.format(err['message'])) + except Exception as ex: + self.log_error(ex) + pass + + def ifttt_notification(self, message, event, api_key): + url = "https://maker.ifttt.com/trigger/{e}/with/key/{k}/".format(e=event, k=api_key) + payload = {'value1': message} + return requests.post(url, data=payload) + + # ~~ EventPlugin mixin + def on_event(self, event, payload): + if event == Events.CONNECTED: + self.update_ui() + + if event == Events.CLIENT_OPENED: + self.update_ui() + + if event == Events.PRINT_RESUMED: + self.start_filament_detection() + + if event == Events.PRINT_STARTED: + self.print_complete = False + self.cancel_all_events_on_queue() + self.event_queue = [] + self.start_filament_detection() + for rpi_output in self.rpi_outputs: + if rpi_output['auto_startup']: + delay_seconds = self.get_startup_delay_from_output(rpi_output) + self.schedule_auto_startup_outputs(rpi_output, delay_seconds) + if rpi_output['toggle_timer']: + if rpi_output['output_type'] == 'regular' or rpi_output['output_type'] == 'pwm': + self.toggle_output(rpi_output['index_id'], True) + if self.is_hour(rpi_output['shutdown_time']): + shutdown_delay_seconds = self.get_shutdown_delay_from_output(rpi_output) + self.schedule_auto_shutdown_outputs(rpi_output, shutdown_delay_seconds) + self.run_tasks() + self.update_ui() + + elif event == Events.PRINT_DONE: + self.stop_filament_detection() + self.print_complete = True + for rpi_output in self.rpi_outputs: + shutdown_time = rpi_output['shutdown_time'] + if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: + rpi_output['duty_cycle'] = rpi_output['default_duty_cycle'] + if rpi_output['auto_shutdown'] and not self.is_hour(shutdown_time): + delay_seconds = self.to_float(shutdown_time) + self.schedule_auto_shutdown_outputs(rpi_output, delay_seconds) + self.run_tasks() + self.update_ui() + + elif event in (Events.PRINT_CANCELLED, Events.PRINT_FAILED): + self.stop_filament_detection() + self.cancel_all_events_on_queue() + self.event_queue = [] + for rpi_output in self.rpi_outputs: + if rpi_output['shutdown_on_failed']: + shutdown_time = rpi_output['shutdown_time'] + if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: + rpi_output['duty_cycle'] = rpi_output['default_duty_cycle'] + if rpi_output['auto_shutdown'] and not self.is_hour(shutdown_time): + delay_seconds = self.to_float(shutdown_time) + self.schedule_auto_shutdown_outputs(rpi_output, delay_seconds) + if rpi_output['output_type'] == 'temp_hum_control': + rpi_output['temp_ctr_set_value'] = 0 + self.run_tasks() + + if event == Events.PRINT_DONE: + for notification in self.notifications: + if notification['printFinish']: + file_name = os.path.basename(payload["path"]) + elapsed_time_in_seconds = payload["time"] + elapsed_time = octoprint.util.get_formatted_timedelta(timedelta(seconds=elapsed_time_in_seconds)) + msg = "Print job finished: " + file_name + "finished printing in " + file_name, elapsed_time + self.send_notification(msg) + + if event in (Events.ERROR, Events.DISCONNECTED): + self._logger.info("Detected Error or Disconnect in %s will call listeners for shutdown_on_error!", event) + for rpi_output in self.rpi_outputs: + if rpi_output['shutdown_on_error']: + self._logger.debug("Schedule shutdown for: %s", rpi_output["index_id"]) + self.schedule_auto_shutdown_outputs(rpi_output, 0) + self.run_tasks() + + if event == Events.PRINTER_STATE_CHANGED: + if "error" in payload["state_string"].lower(): + self._logger.info("Detected Error in %s id: %s state: %s will call listeners for shutdown_on_error!", event, payload["state_id"], payload["state_string"]) + for rpi_output in self.rpi_outputs: + if rpi_output['shutdown_on_error']: + self._logger.debug("Schedule shutdown for: %s", rpi_output["index_id"]) + self.schedule_auto_shutdown_outputs(rpi_output, 0) + self.run_tasks() + + def run_tasks(self): + for task in self.event_queue: + if not task['thread'].is_alive(): + task['thread'].start() + + def schedule_auto_shutdown_outputs(self, rpi_output, shutdown_delay_seconds): + sufix = 'auto_shutdown' + if rpi_output['output_type'] == 'regular': + value = True if rpi_output['active_low'] else False + self.add_regular_output_to_queue(shutdown_delay_seconds, rpi_output, value, sufix) + if rpi_output['output_type'] == 'ledstrip': + self.ledstrip_set_rgb(rpi_output) + if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: + value = 0 + self.add_pwm_output_to_queue(shutdown_delay_seconds, rpi_output, value, sufix) + if rpi_output['output_type'] == 'pwm' and rpi_output['pwm_temperature_linked']: + self.schedule_pwm_duty_on_queue(shutdown_delay_seconds, rpi_output, 0, sufix) + if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): + self.add_neopixel_output_to_queue(rpi_output, shutdown_delay_seconds, 0, 0, 0, sufix) + if rpi_output['output_type'] == 'temp_hum_control': + value = 0 + self.add_temperature_output_temperature_queue(shutdown_delay_seconds, rpi_output, value, sufix) + self._logger.debug("Events scheduled to run %s", self.event_queue) + + def ledstrip_set_rgb(self, rpi_output, rgb=None): + clk = rpi_output["ledstrip_gpio_clk"] + data = rpi_output["ledstrip_gpio_dat"] + if clk is not None and data is not None: + ledstrip = LEDStrip(self.to_int(clk), self.to_int(data)) + if rgb is None: + red, green, blue = self.get_color_from_rgb(rpi_output['default_ledstrip_color']) + else: + red, green, blue = self.get_color_from_rgb(rgb) + + self._logger.info("LEDSTRIP set rgb color: %s, %s, %s", red, green, blue) + ledstrip.setcolourrgb(self.to_int(red), self.to_int(green), self.to_int(blue)) + + def start_outpus_with_server(self): + for rpi_output in self.rpi_outputs: + if rpi_output['startup_with_server']: + gpio = self.to_int(rpi_output['gpio_pin']) + if rpi_output['output_type'] == 'regular': + value = False if rpi_output['active_low'] else True + if rpi_output['gpio_i2c_enabled']: + self.gpio_i2c_write(rpi_output, value) + else: + self.write_gpio(gpio, value) + if rpi_output['output_type'] == 'ledstrip': + self.ledstrip_set_rgb(rpi_output) + if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: + value = self.to_int(rpi_output['default_duty_cycle']) + self.write_pwm(gpio, value) + if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): + red, green, blue = self.get_color_from_rgb(rpi_output['default_neopixel_color']) + led_count = rpi_output['neopixel_count'] + led_brightness = rpi_output['neopixel_brightness'] + address = rpi_output['microcontroller_address'] + index_id = self.to_int(rpi_output['index_id']) + neopixel_direct = rpi_output['output_type'] == 'neopixel_direct' + self.send_neopixel_command(self.to_int(rpi_output['gpio_pin']), led_count, led_brightness, red, + green, blue, address, neopixel_direct, index_id) + if rpi_output['output_type'] == 'temp_hum_control': + rpi_output['temp_ctr_set_value'] = rpi_output['temp_ctr_default_value'] + + def schedule_auto_startup_outputs(self, rpi_output, delay_seconds): + sufix = 'auto_startup' + if rpi_output['output_type'] == 'regular': + value = False if rpi_output['active_low'] else True + self.add_regular_output_to_queue(delay_seconds, rpi_output, value, sufix) + if rpi_output['output_type'] == 'ledstrip': + self.ledstrip_set_rgb(rpi_output) + if rpi_output['output_type'] == 'pwm' and not rpi_output['pwm_temperature_linked']: + value = self.to_int(rpi_output['default_duty_cycle']) + self.add_pwm_output_to_queue(delay_seconds, rpi_output, value, sufix) + if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): + red, green, blue = self.get_color_from_rgb(rpi_output['default_neopixel_color']) + self.add_neopixel_output_to_queue(rpi_output, delay_seconds, red, green, blue, sufix) + if rpi_output['output_type'] == 'temp_hum_control': + value = rpi_output['temp_ctr_default_value'] + self.add_temperature_output_temperature_queue(delay_seconds, rpi_output, value, sufix) + self._logger.debug("Events scheduled to run %s", self.event_queue) + + def get_color_from_rgb(self, stringColor): + stringColor = stringColor.replace('rgb(', '') + red = stringColor[:stringColor.index(',')] + stringColor = stringColor[stringColor.index(',') + 1:] + green = stringColor[:stringColor.index(',')] + stringColor = stringColor[stringColor.index(',') + 1:] + blue = stringColor[:stringColor.index(')')] + return red, green, blue + + def get_shutdown_delay_from_output(self, rpi_output): + shutdown_time = rpi_output['shutdown_time'] + + shut_down_date_time = self.create_date(shutdown_time) + + if shut_down_date_time < datetime.now(): + shut_down_date_time = shut_down_date_time + timedelta(days=1) + + delay_seconds = (shut_down_date_time - datetime.now()).total_seconds() + + return delay_seconds + + def add_neopixel_output_to_queue(self, rpi_output, delay_seconds, red, green, blue, sufix): + gpio_pin = rpi_output['gpio_pin'] + ledCount = rpi_output['neopixel_count'] + ledBrightness = rpi_output['neopixel_brightness'] + address = rpi_output['microcontroller_address'] + neopixel_direct = rpi_output['output_type'] == 'neopixel_direct' + index_id = self.to_int(rpi_output['index_id']) + + queue_id = '{0!s}_{1!s}'.format(index_id, sufix) + + self._logger.debug("Scheduling neopixel output id %s for on %s delay_seconds", queue_id, delay_seconds) + + thread = threading.Timer(delay_seconds, self.send_neopixel_command, + args=[gpio_pin, ledCount, ledBrightness, red, green, blue, address, neopixel_direct, + index_id, queue_id]) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def add_pwm_output_to_queue(self, delay_seconds, rpi_output, value, sufix): + queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) + + self._logger.debug("Scheduling pwm output id %s for on %s delay_seconds", queue_id, delay_seconds) + + thread = threading.Timer(delay_seconds, self.write_pwm, + args=[self.to_int(rpi_output['gpio_pin']), value, queue_id]) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def schedule_pwm_duty_on_queue(self, delay_seconds, rpi_output, value, sufix): + queue_id = '{0!s}_{1!s}_{2!s}'.format(rpi_output['index_id'], "pwm_linked_temp", sufix) + thread = threading.Timer(delay_seconds, self.set_pwm_duty_cycle, args=[rpi_output, value, queue_id]) + + self._logger.debug("Scheduling pwm linked temp output id %s on %s delay_seconds", queue_id, delay_seconds) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def set_pwm_duty_cycle(self, rpi_output, value, queue_id): + rpi_output['duty_cycle'] = value + if queue_id is not None: + self.stop_queue_item(queue_id) + + def add_regular_output_to_queue(self, delay_seconds, rpi_output, value, sufix): + queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) + + self._logger.debug("Scheduling regular output id %s on %s delay_seconds", queue_id, delay_seconds) + + if rpi_output['gpio_i2c_enabled']: + thread = threading.Timer(delay_seconds, self.gpio_i2c_write, + args=[rpi_output, value, queue_id]) + else: + thread = threading.Timer(delay_seconds, self.write_gpio, + args=[self.to_int(rpi_output['gpio_pin']), value, queue_id]) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def add_temperature_output_temperature_queue(self, delay_seconds, rpi_output, value, sufix): + queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) + self._logger.debug("Scheduling temperature control id %s on %s delay_seconds", queue_id, delay_seconds) + + thread = threading.Timer(delay_seconds, self.write_temperature_to_output, + args=[self.to_int(rpi_output['index_id']), value, queue_id]) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def write_temperature_to_output(self, rpi_output_index, value, queue_id=None): + try: + rpi_output = [r_out for r_out in self.rpi_outputs if + self.to_int(r_out['index_id']) == rpi_output_index].pop() + + if rpi_output['output_type'] == 'temp_hum_control': + rpi_output['temp_ctr_set_value'] = value + + if queue_id is not None: + self._logger.debug("running scheduled queue id %s", queue_id) + self._logger.debug("Setting temperature to output index: %s value %s", rpi_output['index_id'], value) + + self.update_ui() + if queue_id is not None: + self.stop_queue_item(queue_id) + + except Exception as ex: + template = "An exception of type {0} occurred on {1}. Arguments:\n{3!r}" + message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, ex.args) + self._logger.warn(message) + pass + + def get_startup_delay_from_output(self, rpi_output): + start_up_time = rpi_output['startup_time'] + if self.is_hour(start_up_time): + start_up_date_time = self.create_date(start_up_time) + if start_up_date_time < datetime.now(): + delay_seconds = 0.0 + else: + delay_seconds = (start_up_date_time - datetime.now()).total_seconds() + else: + delay_seconds = self.to_float(rpi_output['startup_time']) + return delay_seconds + + # ~~ SettingsPlugin mixin + def on_settings_save(self, data): + outputsBeforeSave = self.get_output_list() + octoprint.plugin.SettingsPlugin.on_settings_save(self, data) + self.rpi_outputs = self._settings.get(["rpi_outputs"]) + self.rpi_inputs = self._settings.get(["rpi_inputs"]) + self.notifications = self._settings.get(["notifications"]) + outputsAfterSave = self.get_output_list() + + commonPins = list(set(outputsBeforeSave) & set(outputsAfterSave)) + + for pin in (pin for pin in outputsBeforeSave if pin not in commonPins): + self.clear_channel(pin) + + self.rpi_outputs_not_changed = commonPins + self.clear_gpio() + + self._logger.debug("rpi_outputs: %s", self.rpi_outputs) + self._logger.debug("rpi_inputs: %s", self.rpi_inputs) + self.setup_gpio() + self.configure_gpio() + self.generate_temp_hum_control_status() + + def get_settings_defaults(self): + return dict(rpi_outputs=[], rpi_inputs=[], + filament_sensor_gcode="G91 ;Set Relative Mode \n" + "G1 E-5.000000 F500 ;Retract 5mm\n" + "G1 Z15 F300 ;move Z up 15mm\n" + "G90 ;Set Absolute Mode\n " + "G1 X20 Y20 F9000 ;Move to hold position\n" + "G91 ;Set Relative Mode\n" + "G1 E-40 F500 ;Retract 40mm\n" + "M0 ;Idle Hold\n" + "G90 ;Set Absolute Mode\n" + "G1 F5000 ;Set speed limits\n" + "G28 X0 Y0 ;Home X Y\n" + "M82 ;Set extruder to Absolute Mode\n" + "G92 E0 ;Set Extruder to 0", + use_sudo=True, neopixel_dma=10, debug=False, gcode_control=False, debug_temperature_log=False, + use_board_pin_number=False, notification_provider="disabled", notification_api_key="", + notification_event_name="printer_event", notifications=[{ + 'printFinish' : True, + 'filamentChange' : True, + 'printer_action' : True, + 'temperatureAction': True, 'gpioAction': True + }]) + + # ~~ TemplatePlugin + def get_template_configs(self): + return [dict(type="settings", custom_bindings=True), dict(type="tab", custom_bindings=True), + dict(type="navbar", custom_bindings=True, suffix="_1", classes=["dropdown"]), + dict(type="navbar", custom_bindings=True, template="enclosure_navbar_input.jinja2", suffix="_2", + classes=["dropdown"])] + + # ~~ AssetPlugin mixin + def get_assets(self): + return dict(js=["js/enclosure.js", "js/bootstrap-colorpicker.min.js"], + css=["css/bootstrap-colorpicker.css", "css/enclosure.css"]) + + # ~~ Softwareupdate hook + def get_update_information(self): + return dict(enclosure=dict(displayName="Enclosure Plugin", displayVersion=self._plugin_version, + # version check: github repository + type="github_release", user="vitormhenrique", repo="OctoPrint-Enclosure", current=self._plugin_version, + # update method: pip + pip="https://github.com/vitormhenrique/OctoPrint-Enclosure/archive/{target_version}.zip")) + + def hook_gcode_queuing(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs): + if self._settings.get(["gcode_control"]) is False: + return + + if cmd.strip().startswith("ENC"): + self._logger.debug("Gcode queuing: %s", cmd) + index_id = self.to_int(self.get_gcode_value(cmd, 'O')) + for output in [item for item in self.rpi_outputs if item['index_id'] == index_id]: + if output['output_type'] == 'regular': + set_value = self.to_int(self.get_gcode_value(cmd, 'S')) + set_value = self.constrain(set_value, 0, 1) + value = True if set_value == 1 else False + value = (not value) if output['active_low'] else value + if output['gpio_i2c_enabled']: + self.gpio_i2c_write(output, value) + else: + self.write_gpio(self.to_int(output['gpio_pin']), value) + comm_instance._log("Setting REGULAR output %s to value %s" % (index_id, value)) + return + if output['output_type'] == 'pwm': + set_value = self.to_int(self.get_gcode_value(cmd, 'S')) + set_value = self.constrain(set_value, 0, 100) + output['duty_cycle'] = set_value + self.write_pwm(self.to_int(output['gpio_pin']), set_value) + comm_instance._log("Setting PWM output %s to value %s" % (index_id, set_value)) + return + if output['output_type'] == 'neopixel_indirect' or output['output_type'] == 'neopixel_direct': + red = self.get_gcode_value(cmd, 'R') + green = self.get_gcode_value(cmd, 'G') + blue = self.get_gcode_value(cmd, 'B') + + led_count = output['neopixel_count'] + led_brightness = output['neopixel_brightness'] + address = output['microcontroller_address'] + + index_id = self.to_int(output['index_id']) + + neopixel_direct = output['output_type'] == 'neopixel_direct' + + self.send_neopixel_command(self.to_int(output['gpio_pin']), led_count, led_brightness, red, green, + blue, address, neopixel_direct, index_id) + comm_instance._log( + "Setting NEOPIXEL output %s to red: %s green: %s blue: %s" % (index_id, red, green, blue)) + return + if output['output_type'] == 'temp_hum_control': + set_value = self.to_float(self.get_gcode_value(cmd, 'S')) + should_wait = self.to_int(self.get_gcode_value(cmd, 'W')) + if should_wait == 1 and self._printer.is_printing(): + self._printer.pause_print() + self.waiting_temperature.append(index_id) + output['temp_ctr_set_value'] = set_value + self.update_ui_set_temperature() + self.handle_temp_hum_control() + comm_instance._log("Setting TEMP/HUM control output %s to value %s" % (index_id, set_value)) + return + + def get_graph_data(self, comm, parsed_temps): + for sensor in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): + if sensor["show_graph_temp"]: + parsed_temps[str(sensor["label"])] = (sensor['temp_sensor_temp'], None) + if sensor["show_graph_humidity"]: + parsed_temps[str(sensor["label"])+" Humidity"] = (sensor['temp_sensor_humidity'], None) + + return parsed_temps + + +__plugin_name__ = "Enclosure Plugin" +__plugin_pythoncompat__ = ">=2.7,<4" + + +def __plugin_load__(): + global __plugin_implementation__ + __plugin_implementation__ = EnclosurePlugin() + + global __plugin_hooks__ + __plugin_hooks__ = { + "octoprint.comm.protocol.gcode.queuing" : __plugin_implementation__.hook_gcode_queuing, + "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information, + "octoprint.comm.protocol.temperatures.received": (__plugin_implementation__.get_graph_data, 1) + } -- 2.39.5 From d30a3d4afdbc89a1ee12083125753183080bd05d Mon Sep 17 00:00:00 2001 From: Vitor de Miranda Henrique Date: Sun, 17 Oct 2021 16:43:12 -0500 Subject: [PATCH 068/104] fixing shutdown_on_error --- octoprint_enclosure/static/js/enclosure.js | 3 ++- octoprint_enclosure/templates/enclosure_settings.jinja2 | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/octoprint_enclosure/static/js/enclosure.js b/octoprint_enclosure/static/js/enclosure.js index 6f8031d..b5375ea 100644 --- a/octoprint_enclosure/static/js/enclosure.js +++ b/octoprint_enclosure/static/js/enclosure.js @@ -428,7 +428,8 @@ $(function () { gpio_i2c_register: ko.observable(1), gpio_i2c_data_on: ko.observable(1), gpio_i2c_data_off: ko.observable(0), - gpio_i2c_register_status: ko.observable(1) + gpio_i2c_register_status: ko.observable(1), + shutdown_on_error:ko.observable(false), }); }; diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index b8ce97a..6adab6c 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -91,7 +91,7 @@ - +
@@ -284,7 +284,7 @@
- +
-- 2.39.5 From 42448f810266a7ddd5422ea97a0ecb86804815f3 Mon Sep 17 00:00:00 2001 From: Vitor de Miranda Henrique Date: Sun, 17 Oct 2021 20:44:03 -0500 Subject: [PATCH 069/104] gitignore update --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index e75ccaa..1a6205c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ venv .vscode +*egg-info* +*.pyc -- 2.39.5 From c970fff801c7817ecdbaeb8bf3cfa2356e36cb0c Mon Sep 17 00:00:00 2001 From: Vitor de Miranda Henrique Date: Mon, 18 Oct 2021 08:58:05 -0500 Subject: [PATCH 070/104] gpiozero missing --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bcf8339..76de66d 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ plugin_url = "https://github.com/vitormhenrique/OctoPrint-Enclosure" 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"] +plugin_requires = ["RPi.GPIO>=0.6.5","requests>=2.7","smbus2>=0.3.0","gpiozero==1.6.2"] additional_setup_parameters = {} -- 2.39.5 From c9f16bf33f96e0d9cb9192807756bf5f00e65a7b Mon Sep 17 00:00:00 2001 From: Vitor de Miranda Henrique Date: Mon, 18 Oct 2021 09:00:50 -0500 Subject: [PATCH 071/104] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 76de66d..f2ccc04 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ plugin_package = "octoprint_enclosure" plugin_name = "OctoPrint-Enclosure" # The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module -plugin_version = "4.13.1" +plugin_version = "4.13.2" # The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin # module -- 2.39.5 From 3bdbd12d7d0bd3515e189bc7011be9bb85dedabf Mon Sep 17 00:00:00 2001 From: buchmann Date: Tue, 19 Oct 2021 09:44:06 +0200 Subject: [PATCH 072/104] Resolve merge conflicts for adding BME680 --- octoprint_enclosure/BME680.py | 130 ++++++++++++++++++ octoprint_enclosure/__init__.py | 52 +++++-- .../templates/enclosure_settings.jinja2 | 1 + 3 files changed, 174 insertions(+), 9 deletions(-) create mode 100644 octoprint_enclosure/BME680.py diff --git a/octoprint_enclosure/BME680.py b/octoprint_enclosure/BME680.py new file mode 100644 index 0000000..231c12c --- /dev/null +++ b/octoprint_enclosure/BME680.py @@ -0,0 +1,130 @@ +import bme680 +import time + + +try: + sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY) +except IOError: + sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY) + +# These calibration data can safely be commented +# out, if desired. + + +# These oversampling settings can be tweaked to +# change the balance between accuracy and noise in +# the data. + +hum_weighting = float(0.25) # so hum effect is 25% of the total air quality score +gas_weighting = float(0.75) # so gas effect is 75% of the total air quality score + +sensor.set_humidity_oversample(bme680.OS_2X) +sensor.set_pressure_oversample(bme680.OS_2X) +sensor.set_temperature_oversample(bme680.OS_2X) +sensor.set_filter(bme680.FILTER_SIZE_3) + +sensor.set_gas_heater_temperature(320) +sensor.set_gas_heater_duration(150) +sensor.select_gas_heater_profile(0) +sensor.set_gas_status(bme680.ENABLE_GAS_MEAS) + +gas_reference = float(250000) +hum_reference = float(40) +getgasreference_count = int(0) + + +def GetGasReference(gas_reference): + # Now run the sensor for a burn-in period, then use combination of relative humidity and gas resistance to estimate indoor air quality as a percentage. + # print("Getting a new gas reference value") + readings = int(10) + while True: + sensor.get_sensor_data() + if sensor.data.heat_stable: + for i in range(1, readings): # // read gas for 10 x 0.150mS = 1.5secs + sensor.get_sensor_data() + gas_reference = gas_reference + sensor.data.gas_resistance + # print(sensor.data.gas_resistance) + gas_reference = gas_reference / readings + return + + + + + # for i in range(1, readings): # // read gas for 10 x 0.150mS = 1.5secs + # gas_reference = gas_reference + sensor.data.gas_resistance + # print(sensor.data.gas_resistance) + # gas_reference = gas_reference / readings + + +def CalculateIAQ(score): + IAQ_text = "Air quality is " + score = float((100 - score) * 5) + if score >= 301: + IAQ_text = IAQ_text + "Hazardous" + elif score >= 201 and score <= 300: + IAQ_text = IAQ_text + "Very Unhealthy" + elif score >= 176 and score <= 200: + IAQ_text = IAQ_text + "Unhealthy" + elif score >= 151 and score <= 175: + IAQ_text = IAQ_text + "Unhealthy for Sensitive Groups" + elif score >= 51 and score <= 150: + IAQ_text = IAQ_text + "Moderate" + elif score >= 00 and score <= 50: + IAQ_text = IAQ_text + "Good" + return IAQ_text + + + +#Calculate humidity contribution to IAQ index +current_humidity = float(sensor.data.humidity) +if (current_humidity >= 38 and current_humidity <= 42): + hum_score = float(0.25*100) # Humidity +/-5% around optimum +else: + #sub-optimal + if (current_humidity < 38): + hum_score = float(0.25/hum_reference*current_humidity*100) + else: + hum_score = ((-0.25/(100-hum_reference)*current_humidity)+0.416666)*100 + + +#Calculate gas contribution to IAQ index +gas_lower_limit = float(5000) # Bad air quality limit +gas_upper_limit = float(50000) # Good air quality limit + +if (gas_reference > gas_upper_limit): + gas_reference = gas_upper_limit #gas_reference = 250000 see above +if (gas_reference < gas_lower_limit): + gas_reference = gas_lower_limit + +gas_score = float((0.75/(gas_upper_limit-gas_lower_limit)*gas_reference -(gas_lower_limit*(0.75/(gas_upper_limit-gas_lower_limit))))*100) + +# print('gas_upper_limit--------') +# print(gas_upper_limit) +# +# print('gas_lower_limit--------') +# print(gas_lower_limit) +# +# +# print('gas score---------') +# print(gas_score) +#Combine results for the final IAQ index value (0-100% where 100% is good quality air) +air_quality_score = float(hum_score + gas_score) + +# print('Air Quality = {0:.2f} % derived from 25% of Humidity reading and 75% of Gas reading - 100% is good quality air'.format( air_quality_score)) + +# print('Humidity element was : {0:.2f} of 0.25'.format( hum_score/100)) +# print(' Gas element was : {0:.2f} of 0.25'.format( gas_score/100)) + + + + +# if (sensor.data.gas_resistance < 120000): +# print('Poor air qulity *****') + +GetGasReference(gas_reference) + +# print(CalculateIAQ(air_quality_score)) +# print("------------------------------------------------") +# time.sleep(2) +print('{0:0.1f}'.format(air_quality_score)) + diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 86c1077..2e1cb32 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -808,13 +808,13 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP try: sensor_data = [] for sensor in list(filter(lambda item: item['input_type'] == 'temperature_sensor', self.rpi_inputs)): - temp, hum = self.get_sensor_data(sensor) + temp, hum, airquality = self.get_sensor_data(sensor) if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("Sensor %s Temperature: %s humidity %s", sensor['label'], temp, hum) - if temp is not None and hum is not None: + self._logger.debug("Sensor %s Temperature: %s humidity %s Airquality %s", sensor['label'], temp, hum, airquality) + if temp is not None and hum is not None and airquality is not None: sensor["temp_sensor_temp"] = temp sensor["temp_sensor_humidity"] = hum - sensor_data.append(dict(index_id=sensor['index_id'], temperature=temp, humidity=hum)) + sensor_data.append(dict(index_id=sensor['index_id'], temperature=temp, humidity=hum, airquality=airquality)) self.temperature_sensor_data = sensor_data self.handle_temp_hum_control() self.handle_temperature_events() @@ -978,45 +978,58 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP def get_sensor_data(self, sensor): try: if self.development_mode: - temp, hum = self.read_dummy_temp() + temp, hum, airquality = self.read_dummy_temp() else: if sensor['temp_sensor_type'] in ["11", "22", "2302"]: temp, hum = self.read_dht_temp(sensor['temp_sensor_type'], sensor['gpio_pin']) elif sensor['temp_sensor_type'] == "18b20": temp = self.read_18b20_temp(sensor['ds18b20_serial']) hum = 0 + airquality = 0 elif sensor['temp_sensor_type'] == "bme280": temp, hum = self.read_bme280_temp(sensor['temp_sensor_address']) + airquality = 0 + elif sensor['temp_sensor_type'] == "bme680": + temp, hum, airquality = self.read_bme680_temp(sensor['temp_sensor_address']) elif sensor['temp_sensor_type'] == "am2320": temp, hum = self.read_am2320_temp() # sensor has fixed address + airquality = 0 elif sensor['temp_sensor_type'] == "rpi": temp = self.read_rpi_temp() # rpi CPU Temp hum = 0 + airquality = 0 elif sensor['temp_sensor_type'] == "si7021": temp, hum = self.read_si7021_temp(sensor['temp_sensor_address'], sensor['temp_sensor_i2cbus']) + airquality = 0 elif sensor['temp_sensor_type'] == "tmp102": temp = self.read_tmp102_temp(sensor['temp_sensor_address']) hum = 0 + airquality = 0 elif sensor['temp_sensor_type'] == "max31855": temp = self.read_max31855_temp(sensor['temp_sensor_address']) hum = 0 + airquality = 0 elif sensor['temp_sensor_type'] == "mcp9808": temp = self.read_mcp_temp(sensor['temp_sensor_address']) hum = 0 + airquality = 0 elif sensor['temp_sensor_type'] == "temp_raw_i2c": temp, hum = self.read_raw_i2c_temp(sensor) + airquality = 0 elif sensor['temp_sensor_type'] == "hum_raw_i2c": hum, temp = self.read_raw_i2c_temp(sensor) + airquality = 0 else: self._logger.info("temp_sensor_type no match") temp = None hum = None - if temp != -1 and hum != -1: + if temp != -1 and hum != -1 and airquality != -1: temp = round(self.to_float(temp), 1) if not sensor['use_fahrenheit'] else round( self.to_float(temp) * 1.8 + 32, 1) hum = round(self.to_float(hum), 1) - return temp, hum - return None, None + airquality = round(self.to_float(airquality), 1) + return temp, hum, airquality + return None, None, None except Exception as ex: self.log_error(ex) @@ -1053,7 +1066,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.dummy_value = return_value - return return_value, return_value + return return_value, return_value, return_value def read_raw_i2c_temp(self, sensor): try: @@ -1140,6 +1153,27 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP "Failed to execute python scripts, try disabling use SUDO on advanced section of the plugin.") self.log_error(ex) return (0, 0) + + def read_bme680_temp(self, address): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/BME680.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python " + script + str(address) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature BME680 cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("BME680 result: %s", stdout) + temp, hum, airq = stdout.split("|") + return (self.to_float(temp.strip()), self.to_float(hum.strip()), self.to_float(airq.strip())) + except Exception as ex: + self._logger.info( + "Failed to execute python scripts, try disabling use SUDO on advanced section of the plugin.") + self.log_error(ex) + return (0, 0) def read_am2320_temp(self): try: diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index 9744548..f4f5c4b 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -609,6 +609,7 @@ + -- 2.39.5 From ea393e7ee1f7a27207e1ba10fd2e763f110130da Mon Sep 17 00:00:00 2001 From: Thijs Triemstra Date: Tue, 26 Oct 2021 21:37:55 +0200 Subject: [PATCH 073/104] use RPi.bme280 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f2ccc04..13655f7 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ plugin_url = "https://github.com/vitormhenrique/OctoPrint-Enclosure" 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"] +plugin_requires = ["RPi.GPIO>=0.6.5", "requests>=2.7", "smbus2>=0.3.0", "gpiozero==1.6.2", "RPi.bme280"] additional_setup_parameters = {} -- 2.39.5 From f06e1e6d57f75d7e61e05278c079bef4ea4a306c Mon Sep 17 00:00:00 2001 From: Thijs Triemstra Date: Tue, 26 Oct 2021 21:40:51 +0200 Subject: [PATCH 074/104] use RPi.bme280 --- octoprint_enclosure/BME280.py | 157 ++++------------------------------ 1 file changed, 16 insertions(+), 141 deletions(-) diff --git a/octoprint_enclosure/BME280.py b/octoprint_enclosure/BME280.py index fa5f7c0..e6710ed 100644 --- a/octoprint_enclosure/BME280.py +++ b/octoprint_enclosure/BME280.py @@ -1,151 +1,26 @@ -import smbus import sys -import time -from ctypes import c_short -from ctypes import c_byte -from ctypes import c_ubyte +import smbus2 +import bme280 + +# Rev 2 Pi, Pi 2 & Pi 3 uses bus 1 +# Rev 1 Pi uses bus 0 +port = 1 +bus = smbus2.SMBus(port) if len(sys.argv) == 2: - DEVICE = int(sys.argv[1],16) + DEVICE = int(sys.argv[1], 16) else: print('-1 | -1') sys.exit(1) -bus = smbus.SMBus(1) # Rev 2 Pi, Pi 2 & Pi 3 uses bus 1 - # Rev 1 Pi uses bus 0 - -def getShort(data, index): - # return two bytes from data as a signed 16-bit value - return c_short((data[index+1] << 8) + data[index]).value - -def getUShort(data, index): - # return two bytes from data as an unsigned 16-bit value - return (data[index+1] << 8) + data[index] - -def getChar(data,index): - # return one byte from data as a signed char - result = data[index] - if result > 127: - result -= 256 - return result - -def getUChar(data,index): - # return one byte from data as an unsigned char - result = data[index] & 0xFF - return result - -def readBME280ID(addr=DEVICE): - # Chip ID Register Address - REG_ID = 0xD0 - (chip_id, chip_version) = bus.read_i2c_block_data(addr, REG_ID, 2) - return (chip_id, chip_version) - -def readBME280All(addr=DEVICE): - # Register Addresses - REG_DATA = 0xF7 - REG_CONTROL = 0xF4 - REG_CONFIG = 0xF5 - - REG_CONTROL_HUM = 0xF2 - REG_HUM_MSB = 0xFD - REG_HUM_LSB = 0xFE - - # Oversample setting - page 27 - OVERSAMPLE_TEMP = 2 - OVERSAMPLE_PRES = 2 - MODE = 1 - - # Oversample setting for humidity register - page 26 - OVERSAMPLE_HUM = 2 - bus.write_byte_data(addr, REG_CONTROL_HUM, OVERSAMPLE_HUM) - - control = OVERSAMPLE_TEMP<<5 | OVERSAMPLE_PRES<<2 | MODE - bus.write_byte_data(addr, REG_CONTROL, control) - - # Read blocks of calibration data from EEPROM - # See Page 22 data sheet - cal1 = bus.read_i2c_block_data(addr, 0x88, 24) - cal2 = bus.read_i2c_block_data(addr, 0xA1, 1) - cal3 = bus.read_i2c_block_data(addr, 0xE1, 7) - - # Convert byte data to word values - dig_T1 = getUShort(cal1, 0) - dig_T2 = getShort(cal1, 2) - dig_T3 = getShort(cal1, 4) - - dig_P1 = getUShort(cal1, 6) - dig_P2 = getShort(cal1, 8) - dig_P3 = getShort(cal1, 10) - dig_P4 = getShort(cal1, 12) - dig_P5 = getShort(cal1, 14) - dig_P6 = getShort(cal1, 16) - dig_P7 = getShort(cal1, 18) - dig_P8 = getShort(cal1, 20) - dig_P9 = getShort(cal1, 22) - - dig_H1 = getUChar(cal2, 0) - dig_H2 = getShort(cal3, 0) - dig_H3 = getUChar(cal3, 2) - - dig_H4 = getChar(cal3, 3) - dig_H4 = (dig_H4 << 24) >> 20 - dig_H4 = dig_H4 | (getChar(cal3, 4) & 0x0F) - - dig_H5 = getChar(cal3, 5) - dig_H5 = (dig_H5 << 24) >> 20 - dig_H5 = dig_H5 | (getUChar(cal3, 4) >> 4 & 0x0F) - - dig_H6 = getChar(cal3, 6) - - # Wait in ms (Datasheet Appendix B: Measurement time and current calculation) - wait_time = 1.25 + (2.3 * OVERSAMPLE_TEMP) + ((2.3 * OVERSAMPLE_PRES) + 0.575) + ((2.3 * OVERSAMPLE_HUM)+0.575) - time.sleep(wait_time/1000) # Wait the required time - - # Read temperature/pressure/humidity - data = bus.read_i2c_block_data(addr, REG_DATA, 8) - pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4) - temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4) - hum_raw = (data[6] << 8) | data[7] - - #Refine temperature - var1 = ((((temp_raw>>3)-(dig_T1<<1)))*(dig_T2)) >> 11 - var2 = (((((temp_raw>>4) - (dig_T1)) * ((temp_raw>>4) - (dig_T1))) >> 12) * (dig_T3)) >> 14 - t_fine = var1+var2 - temperature = float(((t_fine * 5) + 128) >> 8); - - # Refine pressure and adjust for temperature - var1 = t_fine / 2.0 - 64000.0 - var2 = var1 * var1 * dig_P6 / 32768.0 - var2 = var2 + var1 * dig_P5 * 2.0 - var2 = var2 / 4.0 + dig_P4 * 65536.0 - var1 = (dig_P3 * var1 * var1 / 524288.0 + dig_P2 * var1) / 524288.0 - var1 = (1.0 + var1 / 32768.0) * dig_P1 - if var1 == 0: - pressure=0 - else: - pressure = 1048576.0 - pres_raw - pressure = ((pressure - var2 / 4096.0) * 6250.0) / var1 - var1 = dig_P9 * pressure * pressure / 2147483648.0 - var2 = pressure * dig_P8 / 32768.0 - pressure = pressure + (var1 + var2 + dig_P7) / 16.0 - - # Refine humidity - humidity = t_fine - 76800.0 - humidity = (hum_raw - (dig_H4 * 64.0 + dig_H5 / 16384.0 * humidity)) * (dig_H2 / 65536.0 * (1.0 + dig_H6 / 67108864.0 * humidity * (1.0 + dig_H3 / 67108864.0 * humidity))) - humidity = humidity * (1.0 - dig_H1 * humidity / 524288.0) - if humidity > 100: - humidity = 100 - elif humidity < 0: - humidity = 0 - - return temperature/100.0,pressure/100.0,humidity - def main(): try: - temperature,pressure,humidity = readBME280All() - print('{0:0.1f} | {1:0.1f}'.format(temperature, humidity)) - except: - print('-1 | -1') + calibration_params = bme280.load_calibration_params(bus, DEVICE) -if __name__=="__main__": - main() + # the sample method will take a single reading and return a + # compensated_reading object + data = bme280.sample(bus, DEVICE, calibration_params) + + print('{0:0.1f} | {1:0.1f}'.format(data.temperature, data.humidity)) + except Exception: + print('-1 | -1') -- 2.39.5 From 5798c8e313f6af15b615ef350b55e4e7306c931c Mon Sep 17 00:00:00 2001 From: Thijs Triemstra Date: Tue, 26 Oct 2021 21:43:17 +0200 Subject: [PATCH 075/104] use sys.executable --- octoprint_enclosure/__init__.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 86c1077..63e3b5d 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -1120,20 +1120,23 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP def read_bme280_temp(self, address): try: - script = os.path.dirname(os.path.realpath(__file__)) + "/BME280.py " + script = os.path.dirname(os.path.realpath(__file__)) + "/BME280.py" + cmd = [sys.executable, script, str(address)] if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - cmd = sudo_str + "python " + script + str(address) + cmd.insert(0, "sudo") if self._settings.get(["debug_temperature_log"]) is True: self._logger.debug("Temperature BME280 cmd: %s", cmd) - stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() - if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("BME280 result: %s", stdout) - temp, hum = stdout.decode("utf-8").split("|") - + 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("BME280 error: %s", errors) + else: + self._logger.debug("BME280 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( -- 2.39.5 From 8d1e339086f520a5e3b3f4a5975094ab60fbcee3 Mon Sep 17 00:00:00 2001 From: Thijs Triemstra Date: Wed, 27 Oct 2021 02:03:53 +0200 Subject: [PATCH 076/104] move bus declaration --- octoprint_enclosure/BME280.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/octoprint_enclosure/BME280.py b/octoprint_enclosure/BME280.py index e6710ed..d38f16a 100644 --- a/octoprint_enclosure/BME280.py +++ b/octoprint_enclosure/BME280.py @@ -2,17 +2,16 @@ import sys import smbus2 import bme280 -# Rev 2 Pi, Pi 2 & Pi 3 uses bus 1 -# Rev 1 Pi uses bus 0 -port = 1 -bus = smbus2.SMBus(port) - if len(sys.argv) == 2: DEVICE = int(sys.argv[1], 16) else: print('-1 | -1') sys.exit(1) +# Rev 2 Pi, Pi 2 & Pi 3 & Pi 4 use bus 1 +# Rev 1 Pi uses bus 0 +bus = smbus2.SMBus(1) + def main(): try: calibration_params = bme280.load_calibration_params(bus, DEVICE) -- 2.39.5 From c32b20832c961f52cea7e97f3da1b687a46529e1 Mon Sep 17 00:00:00 2001 From: Thijs Triemstra Date: Wed, 27 Oct 2021 02:12:18 +0200 Subject: [PATCH 077/104] doc update --- octoprint_enclosure/BME280.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/octoprint_enclosure/BME280.py b/octoprint_enclosure/BME280.py index d38f16a..3fd387a 100644 --- a/octoprint_enclosure/BME280.py +++ b/octoprint_enclosure/BME280.py @@ -15,11 +15,8 @@ bus = smbus2.SMBus(1) def main(): try: calibration_params = bme280.load_calibration_params(bus, DEVICE) - - # the sample method will take a single reading and return a - # compensated_reading object data = bme280.sample(bus, DEVICE, calibration_params) print('{0:0.1f} | {1:0.1f}'.format(data.temperature, data.humidity)) - except Exception: + except: print('-1 | -1') -- 2.39.5 From 20312381d55ebcd4e8697256e4894526e4062584 Mon Sep 17 00:00:00 2001 From: Thijs Triemstra Date: Wed, 27 Oct 2021 02:15:32 +0200 Subject: [PATCH 078/104] restore __main__ --- octoprint_enclosure/BME280.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/octoprint_enclosure/BME280.py b/octoprint_enclosure/BME280.py index 3fd387a..c5c93dc 100644 --- a/octoprint_enclosure/BME280.py +++ b/octoprint_enclosure/BME280.py @@ -20,3 +20,6 @@ def main(): print('{0:0.1f} | {1:0.1f}'.format(data.temperature, data.humidity)) except: print('-1 | -1') + +if __name__== "__main__": + main() -- 2.39.5 From 4ca82c1036936b4ada93b0845123973b4cb27a02 Mon Sep 17 00:00:00 2001 From: David Ross Smith <5095074+DragRedSim@users.noreply.github.com> Date: Mon, 1 Nov 2021 18:36:05 +1100 Subject: [PATCH 079/104] Changed README.md to new Adafruit_DHT library The library used has changed, but the instructions were never updated to explain how to install the new library. This corrects that oversight. --- README.md | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f6f4270..825bd8e 100644 --- a/README.md +++ b/README.md @@ -45,32 +45,23 @@ To control the enclosure temperature or get temperature triggered events, you ne #### DHT11, DHT22 and AM2302 sensors -Wire the sensor following the wiring diagram on the pictures on thingiverse, you can use any GPIO pin. +Wire the sensor following the wiring diagram on the pictures on thingiverse; you can use any GPIO pin. For DHT11 and DHT22 sensors, don't forget to connect a 4.7K - 10K resistor from the data pin to VCC. Also, be aware that DHT sensors some times can not work reliably on linux, this is a limitation of reading DHT sensors from Linux--there's no guarantee the program will be given enough priority and time by the Linux kernel to reliably read the sensor. Another common issue is the power supply. you need a constant and good 3.3V, sometimes a underpowered raspberry pi will not have a solid 3.3V power supply, so you could try powering the sensor with 5V and using a level shifter on the read pin. -You need to install Adafruit library to use the temperature sensor on raspberry pi. +You need to install the Adafruit library to use the temperature sensor on Raspberry Pi. Recent versions of this plugin have moved to supporting the CircuitPython-based library on Python3 installed below. Open a raspberry pi terminal and type: ``` -cd ~ -git clone https://github.com/adafruit/Adafruit_Python_DHT.git -cd Adafruit_Python_DHT +pip3 install adafruit-circuitpython-dht sudo apt-get update -sudo apt-get install build-essential python-dev python-openssl -sudo python setup.py install +sudo apt-get install libgpiod2 -y ``` -Note: All libraries need to be installed on raspberry pi system python not octoprint virtual environment. +This will install all requirements of the library. -You can test the library by using: - -``` -cd examples -sudo ./AdafruitDHT.py 2302 4
-``` -Note that the first argument is the temperature sensor (11, 22, or 2302), and the second argument is the GPIO that the sensor was connected. +You can test the library by using the sample code from https://learn.adafruit.com/dht-humidity-sensing-on-raspberry-pi-with-gdocs-logging/python-setup. #### DS18B20 sensor -- 2.39.5 From a630111ff1b51e9fe5e28b0e38912077124957d8 Mon Sep 17 00:00:00 2001 From: d-buchmann Date: Wed, 17 Nov 2021 15:27:11 +0100 Subject: [PATCH 080/104] Remove dead code --- octoprint_enclosure/BME680.py | 46 +---------------------------------- 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/octoprint_enclosure/BME680.py b/octoprint_enclosure/BME680.py index 231c12c..c969018 100644 --- a/octoprint_enclosure/BME680.py +++ b/octoprint_enclosure/BME680.py @@ -7,14 +7,6 @@ try: except IOError: sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY) -# These calibration data can safely be commented -# out, if desired. - - -# These oversampling settings can be tweaked to -# change the balance between accuracy and noise in -# the data. - hum_weighting = float(0.25) # so hum effect is 25% of the total air quality score gas_weighting = float(0.75) # so gas effect is 75% of the total air quality score @@ -43,19 +35,9 @@ def GetGasReference(gas_reference): for i in range(1, readings): # // read gas for 10 x 0.150mS = 1.5secs sensor.get_sensor_data() gas_reference = gas_reference + sensor.data.gas_resistance - # print(sensor.data.gas_resistance) gas_reference = gas_reference / readings return - - - - # for i in range(1, readings): # // read gas for 10 x 0.150mS = 1.5secs - # gas_reference = gas_reference + sensor.data.gas_resistance - # print(sensor.data.gas_resistance) - # gas_reference = gas_reference / readings - - def CalculateIAQ(score): IAQ_text = "Air quality is " score = float((100 - score) * 5) @@ -73,8 +55,6 @@ def CalculateIAQ(score): IAQ_text = IAQ_text + "Good" return IAQ_text - - #Calculate humidity contribution to IAQ index current_humidity = float(sensor.data.humidity) if (current_humidity >= 38 and current_humidity <= 42): @@ -86,45 +66,21 @@ else: else: hum_score = ((-0.25/(100-hum_reference)*current_humidity)+0.416666)*100 - #Calculate gas contribution to IAQ index gas_lower_limit = float(5000) # Bad air quality limit gas_upper_limit = float(50000) # Good air quality limit if (gas_reference > gas_upper_limit): - gas_reference = gas_upper_limit #gas_reference = 250000 see above + gas_reference = gas_upper_limit if (gas_reference < gas_lower_limit): gas_reference = gas_lower_limit gas_score = float((0.75/(gas_upper_limit-gas_lower_limit)*gas_reference -(gas_lower_limit*(0.75/(gas_upper_limit-gas_lower_limit))))*100) -# print('gas_upper_limit--------') -# print(gas_upper_limit) -# -# print('gas_lower_limit--------') -# print(gas_lower_limit) -# -# -# print('gas score---------') -# print(gas_score) #Combine results for the final IAQ index value (0-100% where 100% is good quality air) air_quality_score = float(hum_score + gas_score) -# print('Air Quality = {0:.2f} % derived from 25% of Humidity reading and 75% of Gas reading - 100% is good quality air'.format( air_quality_score)) - -# print('Humidity element was : {0:.2f} of 0.25'.format( hum_score/100)) -# print(' Gas element was : {0:.2f} of 0.25'.format( gas_score/100)) - - - - -# if (sensor.data.gas_resistance < 120000): -# print('Poor air qulity *****') - GetGasReference(gas_reference) -# print(CalculateIAQ(air_quality_score)) -# print("------------------------------------------------") -# time.sleep(2) print('{0:0.1f}'.format(air_quality_score)) -- 2.39.5 From 7170ef2ecb64a033ef3f234a2103b722e3b134c5 Mon Sep 17 00:00:00 2001 From: d-buchmann Date: Wed, 17 Nov 2021 15:30:35 +0100 Subject: [PATCH 081/104] Add bme680 lib as requirement --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f2ccc04..e09b7ff 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ plugin_url = "https://github.com/vitormhenrique/OctoPrint-Enclosure" 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"] +plugin_requires = ["RPi.GPIO>=0.6.5","requests>=2.7","smbus2>=0.3.0","gpiozero==1.6.2","bme680>=1.1.0"] additional_setup_parameters = {} -- 2.39.5 From b4cb5a82a4358fba3028685c09b80b4cd17fd4fc Mon Sep 17 00:00:00 2001 From: d-buchmann Date: Wed, 17 Nov 2021 20:20:05 +0100 Subject: [PATCH 082/104] fix indentation in bme680.py --- octoprint_enclosure/BME680.py | 64 +++++++++++++++++------------------ 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/octoprint_enclosure/BME680.py b/octoprint_enclosure/BME680.py index c969018..e5eb39f 100644 --- a/octoprint_enclosure/BME680.py +++ b/octoprint_enclosure/BME680.py @@ -26,54 +26,54 @@ getgasreference_count = int(0) def GetGasReference(gas_reference): - # Now run the sensor for a burn-in period, then use combination of relative humidity and gas resistance to estimate indoor air quality as a percentage. - # print("Getting a new gas reference value") - readings = int(10) - while True: - sensor.get_sensor_data() - if sensor.data.heat_stable: - for i in range(1, readings): # // read gas for 10 x 0.150mS = 1.5secs + # Now run the sensor for a burn-in period, then use combination of relative humidity and gas resistance to estimate indoor air quality as a percentage. + # print("Getting a new gas reference value") + readings = int(10) + while True: sensor.get_sensor_data() - gas_reference = gas_reference + sensor.data.gas_resistance - gas_reference = gas_reference / readings - return + if sensor.data.heat_stable: + for i in range(1, readings): # // read gas for 10 x 0.150mS = 1.5secs + sensor.get_sensor_data() + gas_reference = gas_reference + sensor.data.gas_resistance + gas_reference = gas_reference / readings + return def CalculateIAQ(score): - IAQ_text = "Air quality is " - score = float((100 - score) * 5) - if score >= 301: - IAQ_text = IAQ_text + "Hazardous" - elif score >= 201 and score <= 300: - IAQ_text = IAQ_text + "Very Unhealthy" - elif score >= 176 and score <= 200: - IAQ_text = IAQ_text + "Unhealthy" - elif score >= 151 and score <= 175: - IAQ_text = IAQ_text + "Unhealthy for Sensitive Groups" - elif score >= 51 and score <= 150: - IAQ_text = IAQ_text + "Moderate" - elif score >= 00 and score <= 50: - IAQ_text = IAQ_text + "Good" - return IAQ_text + IAQ_text = "Air quality is " + score = float((100 - score) * 5) + if score >= 301: + IAQ_text = IAQ_text + "Hazardous" + elif score >= 201 and score <= 300: + IAQ_text = IAQ_text + "Very Unhealthy" + elif score >= 176 and score <= 200: + IAQ_text = IAQ_text + "Unhealthy" + elif score >= 151 and score <= 175: + IAQ_text = IAQ_text + "Unhealthy for Sensitive Groups" + elif score >= 51 and score <= 150: + IAQ_text = IAQ_text + "Moderate" + elif score >= 00 and score <= 50: + IAQ_text = IAQ_text + "Good" + return IAQ_text #Calculate humidity contribution to IAQ index current_humidity = float(sensor.data.humidity) if (current_humidity >= 38 and current_humidity <= 42): - hum_score = float(0.25*100) # Humidity +/-5% around optimum + hum_score = float(0.25*100) # Humidity +/-5% around optimum else: #sub-optimal - if (current_humidity < 38): - hum_score = float(0.25/hum_reference*current_humidity*100) - else: - hum_score = ((-0.25/(100-hum_reference)*current_humidity)+0.416666)*100 + if (current_humidity < 38): + hum_score = float(0.25/hum_reference*current_humidity*100) + else: + hum_score = ((-0.25/(100-hum_reference)*current_humidity)+0.416666)*100 #Calculate gas contribution to IAQ index gas_lower_limit = float(5000) # Bad air quality limit gas_upper_limit = float(50000) # Good air quality limit if (gas_reference > gas_upper_limit): - gas_reference = gas_upper_limit + gas_reference = gas_upper_limit if (gas_reference < gas_lower_limit): - gas_reference = gas_lower_limit + gas_reference = gas_lower_limit gas_score = float((0.75/(gas_upper_limit-gas_lower_limit)*gas_reference -(gas_lower_limit*(0.75/(gas_upper_limit-gas_lower_limit))))*100) -- 2.39.5 From 33338d5ec8cb2cf0166bfc8535cac3343f99280b Mon Sep 17 00:00:00 2001 From: d-buchmann Date: Wed, 17 Nov 2021 20:27:22 +0100 Subject: [PATCH 083/104] fix indentation in __init__.py --- octoprint_enclosure/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 42ed6a4..9091e32 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -1027,7 +1027,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP temp = round(self.to_float(temp), 1) if not sensor['use_fahrenheit'] else round( self.to_float(temp) * 1.8 + 32, 1) hum = round(self.to_float(hum), 1) - airquality = round(self.to_float(airquality), 1) + airquality = round(self.to_float(airquality), 1) return temp, hum, airquality return None, None, None except Exception as ex: -- 2.39.5 From 40248545bba01b4ed6c725846b5460e83e73002a Mon Sep 17 00:00:00 2001 From: d-buchmann Date: Thu, 18 Nov 2021 15:07:20 +0100 Subject: [PATCH 084/104] change tab indent size to 4 --- octoprint_enclosure/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 9091e32..e2297e5 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -985,10 +985,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP elif sensor['temp_sensor_type'] == "18b20": temp = self.read_18b20_temp(sensor['ds18b20_serial']) hum = 0 - airquality = 0 + airquality = 0 elif sensor['temp_sensor_type'] == "bme280": temp, hum = self.read_bme280_temp(sensor['temp_sensor_address']) - airquality = 0 + airquality = 0 elif sensor['temp_sensor_type'] == "bme680": temp, hum, airquality = self.read_bme680_temp(sensor['temp_sensor_address']) elif sensor['temp_sensor_type'] == "am2320": -- 2.39.5 From e8dd4184e25821c1a2d9572ef9ee987c24eb8ade Mon Sep 17 00:00:00 2001 From: d-buchmann Date: Thu, 18 Nov 2021 15:09:30 +0100 Subject: [PATCH 085/104] tabs to spaces --- octoprint_enclosure/__init__.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index e2297e5..3c753ad 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -985,40 +985,40 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP elif sensor['temp_sensor_type'] == "18b20": temp = self.read_18b20_temp(sensor['ds18b20_serial']) hum = 0 - airquality = 0 + airquality = 0 elif sensor['temp_sensor_type'] == "bme280": temp, hum = self.read_bme280_temp(sensor['temp_sensor_address']) - airquality = 0 + airquality = 0 elif sensor['temp_sensor_type'] == "bme680": temp, hum, airquality = self.read_bme680_temp(sensor['temp_sensor_address']) elif sensor['temp_sensor_type'] == "am2320": temp, hum = self.read_am2320_temp() # sensor has fixed address - airquality = 0 + airquality = 0 elif sensor['temp_sensor_type'] == "rpi": temp = self.read_rpi_temp() # rpi CPU Temp hum = 0 - airquality = 0 + airquality = 0 elif sensor['temp_sensor_type'] == "si7021": temp, hum = self.read_si7021_temp(sensor['temp_sensor_address'], sensor['temp_sensor_i2cbus']) - airquality = 0 + airquality = 0 elif sensor['temp_sensor_type'] == "tmp102": temp = self.read_tmp102_temp(sensor['temp_sensor_address']) hum = 0 - airquality = 0 + airquality = 0 elif sensor['temp_sensor_type'] == "max31855": temp = self.read_max31855_temp(sensor['temp_sensor_address']) hum = 0 - airquality = 0 + airquality = 0 elif sensor['temp_sensor_type'] == "mcp9808": temp = self.read_mcp_temp(sensor['temp_sensor_address']) hum = 0 - airquality = 0 + airquality = 0 elif sensor['temp_sensor_type'] == "temp_raw_i2c": temp, hum = self.read_raw_i2c_temp(sensor) - airquality = 0 + airquality = 0 elif sensor['temp_sensor_type'] == "hum_raw_i2c": hum, temp = self.read_raw_i2c_temp(sensor) - airquality = 0 + airquality = 0 else: self._logger.info("temp_sensor_type no match") temp = None -- 2.39.5 From 190b11b41dceb8fbb565e72ab0cd66c8dd99e9d1 Mon Sep 17 00:00:00 2001 From: d-buchmann Date: Thu, 18 Nov 2021 17:02:50 +0100 Subject: [PATCH 086/104] copy&paste from read_bme280_temp() --- octoprint_enclosure/__init__.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 3c753ad..bfedb92 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -1160,17 +1160,21 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP def read_bme680_temp(self, address): try: script = os.path.dirname(os.path.realpath(__file__)) + "/BME680.py " + cmd = [sys.executable, script, str(address)] if self._settings.get(["use_sudo"]): - sudo_str = "sudo " - else: - sudo_str = "" - cmd = sudo_str + "python " + script + str(address) + cmd.insert(0, "sudo") if self._settings.get(["debug_temperature_log"]) is True: self._logger.debug("Temperature BME680 cmd: %s", cmd) - stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + + stdout = Popen(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True) + output, errors = stdout.communicate() + if self._settings.get(["debug_temperature_log"]) is True: - self._logger.debug("BME680 result: %s", stdout) - temp, hum, airq = stdout.split("|") + if len(errors) > 0: + self._logger.error("BME680 error: %s", errors) + else: + self._logger.debug("BME680 result: %s", output) + temp, hum, airq = output.split("|") return (self.to_float(temp.strip()), self.to_float(hum.strip()), self.to_float(airq.strip())) except Exception as ex: self._logger.info( -- 2.39.5 From cbc90487996b00e1cde49fab5e75dc195cd5692b Mon Sep 17 00:00:00 2001 From: d-buchmann Date: Thu, 2 Dec 2021 10:49:52 +0100 Subject: [PATCH 087/104] remove unneeded parens --- octoprint_enclosure/BME680.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/octoprint_enclosure/BME680.py b/octoprint_enclosure/BME680.py index e5eb39f..3f21d22 100644 --- a/octoprint_enclosure/BME680.py +++ b/octoprint_enclosure/BME680.py @@ -70,9 +70,9 @@ else: gas_lower_limit = float(5000) # Bad air quality limit gas_upper_limit = float(50000) # Good air quality limit -if (gas_reference > gas_upper_limit): +if gas_reference > gas_upper_limit: gas_reference = gas_upper_limit -if (gas_reference < gas_lower_limit): +if gas_reference < gas_lower_limit: gas_reference = gas_lower_limit gas_score = float((0.75/(gas_upper_limit-gas_lower_limit)*gas_reference -(gas_lower_limit*(0.75/(gas_upper_limit-gas_lower_limit))))*100) -- 2.39.5 From e551edeed18afec0178924e2a430e154b4a2c22b Mon Sep 17 00:00:00 2001 From: LoPromise Date: Fri, 10 Dec 2021 19:16:05 +0000 Subject: [PATCH 088/104] fixing Head and adding AHT10 Sensor --- octoprint_enclosure/AHT10.py | 40 +++++++++++++++++++ octoprint_enclosure/static/js/enclosure.js | 2 +- .../templates/enclosure_settings.jinja2 | 3 +- 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 octoprint_enclosure/AHT10.py diff --git a/octoprint_enclosure/AHT10.py b/octoprint_enclosure/AHT10.py new file mode 100644 index 0000000..23d8619 --- /dev/null +++ b/octoprint_enclosure/AHT10.py @@ -0,0 +1,40 @@ +#!/usr/bin/python3 +#i2cdetect -y 0 +import smbus +import time +import sys + +if len(sys.argv) == 3: + DEVICE = int(sys.argv[1],16) + bus = smbus.SMBus(int(sys.argv[2],16)) +else: + print('-1 | -1') + sys.exit(1) + + +def getAll(bus,addr=DEVICE): + #Set config + config = [0x08, 0x00] + bus.write_i2c_block_data(addr, 0xE1, config) + #time.sleep(0.1) + byt = bus.read_byte(addr) + #Send MeasureCMD and read data + MeasureCmd = [0x33, 0x00] + bus.write_i2c_block_data(addr, 0xAC, MeasureCmd) + #time.sleep(0.1) + data = bus.read_i2c_block_data(addr,0x00) + temp = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5] + ctemp = ((temp*200) / 1048576) - 50 + + hum = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4 + chum = int(hum * 100 / 1048576) + return ctemp,chum +def main(): + try: + temperature,humidity=getAll(bus) + print('{0:0.1f} | {1:0.1f}'.format(temperature, humidity)) + except: + print('-1 | -1') + +if __name__=="__main__": + main() diff --git a/octoprint_enclosure/static/js/enclosure.js b/octoprint_enclosure/static/js/enclosure.js index b5375ea..d2a0093 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', '22', '2302', 'bme280', 'am2320', 'si7021', 'hum_raw_i2c', 'temp_raw_i2c'].indexOf(sensor) >= 0){ + if (['11', '22', '2302', 'bme280', 'am2320', 'aht10' , 'si7021', 'hum_raw_i2c', 'temp_raw_i2c'].indexOf(sensor) >= 0){ return true; } return false; diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index f4f5c4b..394f8fc 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -611,6 +611,7 @@ + @@ -680,7 +681,7 @@ - +
-- 2.39.5 From 7b4b208c8579ede963f2c4ecd2543ca0900e2210 Mon Sep 17 00:00:00 2001 From: Mark Leary Date: Wed, 15 Dec 2021 10:43:19 -0500 Subject: [PATCH 089/104] Retry when DHT temp sensor read fails --- octoprint_enclosure/getDHTTemp.py | 34 +++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/octoprint_enclosure/getDHTTemp.py b/octoprint_enclosure/getDHTTemp.py index cb9bfef..e898977 100644 --- a/octoprint_enclosure/getDHTTemp.py +++ b/octoprint_enclosure/getDHTTemp.py @@ -1,9 +1,10 @@ import sys +import time import adafruit_dht # Parse command line parameters. -sensor_args = { +sensor_args = { '11': adafruit_dht.DHT11, '22': adafruit_dht.DHT22, '2302': adafruit_dht.DHT22 @@ -16,12 +17,29 @@ else: sys.exit(1) dht_dev = sensor(pin) -humidity = dht_dev.humidity -temperature = dht_dev.temperature -if humidity is not None and temperature is not None: - print('{0:0.1f} | {1:0.1f}'.format(temperature, humidity)) -else: - print('-1 | -1') +# DHT sensor read fails quite often, causing enclosure plugin to report value of 0. +# If this happens, retry as suggested in the adafruit_dht docs. +max_retries = 3 +retry_count = 0 +while retry_count <= max_retries: + try: + humidity = dht_dev.humidity + temperature = dht_dev.temperature -sys.exit(1) + if humidity is not None and temperature is not None: + print('{0:0.1f} | {1:0.1f}'.format(temperature, humidity)) + sys.exit(1) + except RuntimeError as e: + time.sleep(2) + retry_count += 1 + continue + except Exception as e: + dht_dev.exit() + raise e + + time.sleep(1) + retry_count += 1 + +print('-1 | -1') +sys.exit(1) \ No newline at end of file -- 2.39.5 From e1f0372a63905f5f9de89b68c689aa5be6b209c5 Mon Sep 17 00:00:00 2001 From: Dracrius Date: Thu, 30 Dec 2021 14:26:34 -0500 Subject: [PATCH 090/104] Fixed DHT11,22,2302 Not Reading Many open issues are just because airquality was added but the if for DHT11,22,2302 was missing the "airquality = 0" it would need to read. --- octoprint_enclosure/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index bfedb92..b580b96 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -982,6 +982,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP else: if sensor['temp_sensor_type'] in ["11", "22", "2302"]: temp, hum = self.read_dht_temp(sensor['temp_sensor_type'], sensor['gpio_pin']) + airquality = 0 elif sensor['temp_sensor_type'] == "18b20": temp = self.read_18b20_temp(sensor['ds18b20_serial']) hum = 0 -- 2.39.5 From 43901015c95b9e5c4d13c4166c3ed4c1d312034e Mon Sep 17 00:00:00 2001 From: Dracrius Date: Fri, 31 Dec 2021 02:44:41 -0500 Subject: [PATCH 091/104] Added Basic MQTT Publishing Support using OctoPrint/OctoPrint-MQTT's Helper --- octoprint_enclosure/__init__.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index b580b96..6472e1b 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -72,7 +72,17 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP development_mode = False dummy_value = 30.0 dummy_delta = 0.5 - + + def __init__(self): + # mqtt helpers + self.mqtt_publish = lambda *args, **kwargs: None + self.mqtt_subscribe = lambda *args, **kwargs: None + self.mqtt_unsubscribe = lambda *args, **kwargs: None + # hardcoded + self.mqtt_root_topic = "Monolith/temperature/enclosure" + self.mqtt_sensor_topic = self.mqtt_root_topic + "/" + "enclosure" + self.mqtt_message = "{\"temperature\": 0, \"humidity\": 0}" + def start_timer(self): """ Function to start timer that checks enclosure temperature @@ -134,7 +144,22 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP return -1 # ~~ StartupPlugin mixin - def on_after_startup(self): + def on_after_startup(self): + helpers = self._plugin_manager.get_helpers("mqtt", "mqtt_publish", "mqtt_subscribe", "mqtt_unsubscribe") + + if helpers: + if "mqtt_publish" in helpers: + self.mqtt_publish = helpers["mqtt_publish"] + if "mqtt_subscribe" in helpers: + self.mqtt_subscribe = helpers["mqtt_subscribe"] + if "mqtt_unsubscribe" in helpers: + self.mqtt_unsubscribe = helpers["mqtt_unsubscribe"] + else: + self._logger.info("mqtt helpers not found. mqtt functions won't work") + + # Test MQTT + #self.mqtt_publish(self.mqtt_sensor_topic, self.mqtt_message) + self.pwm_instances = [] self.event_queue = [] self.rpi_outputs_not_changed = [] @@ -820,6 +845,9 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.handle_temperature_events() self.handle_pwm_linked_temperature() self.update_ui() + self.mqtt_sensor_topic = self.mqtt_root_topic + "/" + sensor['label'] + self.mqtt_message = {"temperature": temp, "humidity": hum} + self.mqtt_publish(self.mqtt_sensor_topic, self.mqtt_message) except Exception as ex: self.log_error(ex) -- 2.39.5 From b636429c0dde08b4f58b4d34dc06f51125b8447d Mon Sep 17 00:00:00 2001 From: Dracrius Date: Fri, 31 Dec 2021 02:53:40 -0500 Subject: [PATCH 092/104] Cleaned up unneeded MQTT code --- octoprint_enclosure/__init__.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 6472e1b..2bb8d7b 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -74,10 +74,8 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP dummy_delta = 0.5 def __init__(self): - # mqtt helpers + # mqtt helper self.mqtt_publish = lambda *args, **kwargs: None - self.mqtt_subscribe = lambda *args, **kwargs: None - self.mqtt_unsubscribe = lambda *args, **kwargs: None # hardcoded self.mqtt_root_topic = "Monolith/temperature/enclosure" self.mqtt_sensor_topic = self.mqtt_root_topic + "/" + "enclosure" @@ -150,15 +148,8 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP if helpers: if "mqtt_publish" in helpers: self.mqtt_publish = helpers["mqtt_publish"] - if "mqtt_subscribe" in helpers: - self.mqtt_subscribe = helpers["mqtt_subscribe"] - if "mqtt_unsubscribe" in helpers: - self.mqtt_unsubscribe = helpers["mqtt_unsubscribe"] else: - self._logger.info("mqtt helpers not found. mqtt functions won't work") - - # Test MQTT - #self.mqtt_publish(self.mqtt_sensor_topic, self.mqtt_message) + self._logger.info("mqtt helpers not found. mqtt functions won't work") self.pwm_instances = [] self.event_queue = [] -- 2.39.5 From 599d7460aeabbdb433d9cebacb00e82ca022c2c6 Mon Sep 17 00:00:00 2001 From: Dracrius Date: Fri, 31 Dec 2021 02:58:41 -0500 Subject: [PATCH 093/104] Changed Root Topic to a more standard hardcoded one "octoprint/plugins/enclosure" may add config field later if I see the need. Those who really want to change it can do so here for now. --- octoprint_enclosure/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 2bb8d7b..f07ea9c 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -77,7 +77,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP # mqtt helper self.mqtt_publish = lambda *args, **kwargs: None # hardcoded - self.mqtt_root_topic = "Monolith/temperature/enclosure" + self.mqtt_root_topic = "octoprint/plugins/enclosure" self.mqtt_sensor_topic = self.mqtt_root_topic + "/" + "enclosure" self.mqtt_message = "{\"temperature\": 0, \"humidity\": 0}" -- 2.39.5 From 6a4e9b3e2129b5f760b75e5823a152de88f4cf07 Mon Sep 17 00:00:00 2001 From: LoPromise Date: Wed, 5 Jan 2022 09:30:19 +0000 Subject: [PATCH 094/104] adding modified init --- octoprint_enclosure/__init__.py | 48 +++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index bfedb92..58010da 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -31,7 +31,7 @@ def PinState_Boolean(pin, ActiveLow) : try: state = GPIO.input(pin) if ActiveLow and not state: return True - if not ActiveLow and state: return True + if not ActiveLow and state: return True return False except: return "ERROR: Unable to read pin" @@ -41,7 +41,7 @@ def PinState_Human(pin, ActiveLow): PinState = PinState_Boolean(pin, ActiveLow) if PinState == True : return " ON " - elif PinState == False: + elif PinState == False: return " OFF " else: return PinState @@ -54,7 +54,7 @@ def CheckInputActiveLow(Input_Pull_Resistor): return True else: return False - + class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SettingsPlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.BlueprintPlugin, octoprint.plugin.EventHandlerPlugin): @@ -982,6 +982,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP else: if sensor['temp_sensor_type'] in ["11", "22", "2302"]: temp, hum = self.read_dht_temp(sensor['temp_sensor_type'], sensor['gpio_pin']) + airquaility = 0 elif sensor['temp_sensor_type'] == "18b20": temp = self.read_18b20_temp(sensor['ds18b20_serial']) hum = 0 @@ -994,6 +995,9 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP elif sensor['temp_sensor_type'] == "am2320": temp, hum = self.read_am2320_temp() # sensor has fixed address airquality = 0 + elif sensor['temp_sensor_type'] == "aht10": + temp, hum = self.read_aht10_temp(sensor['temp_sensor_address'], sensor['temp_sensor_i2cbus']) + airquality = 0 elif sensor['temp_sensor_type'] == "rpi": temp = self.read_rpi_temp() # rpi CPU Temp hum = 0 @@ -1023,6 +1027,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._logger.info("temp_sensor_type no match") temp = None hum = None + airquality = 0 if temp != -1 and hum != -1 and airquality != -1: temp = round(self.to_float(temp), 1) if not sensor['use_fahrenheit'] else round( self.to_float(temp) * 1.8 + 32, 1) @@ -1156,7 +1161,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP "Failed to execute python scripts, try disabling use SUDO on advanced section of the plugin.") self.log_error(ex) return (0, 0) - + def read_bme680_temp(self, address): try: script = os.path.dirname(os.path.realpath(__file__)) + "/BME680.py " @@ -1165,10 +1170,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP cmd.insert(0, "sudo") if self._settings.get(["debug_temperature_log"]) is True: self._logger.debug("Temperature BME680 cmd: %s", 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("BME680 error: %s", errors) @@ -1202,7 +1207,34 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP "Failed to execute python scripts, try disabling use SUDO on advanced section of the plugin.") self.log_error(ex) return (0, 0) - + + def read_aht10_temp(self, address, i2cbus): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/AHT10.py" + cmd = [sys.executable, script, str(address), str(i2cbus)] + if self._settings.get(["use_sudo"]): + cmd.insert(0, "sudo") + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature AHT10 cmd: %s", cmd) + self._logger.debug(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("AHT10 error: %s", errors) + else: + self._logger.debug("AHT10 result: %s", output) + self._logger.debug(output + " " + errors) + temp, hum = output.split("|") + print (temp + " , " + hum ) + return (self.to_float(temp.strip()), self.to_float(hum.strip())) + except Exception as ex: + print(ex) + self._logger.info( + "Failed to execute python scripts, try disabling use SUDO on advanced section of the plugin.") + self.log_error(ex) + return (0, 0) + def read_rpi_temp(self): try: pitemp = PiTemp() @@ -1214,7 +1246,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._logger.info( "Failed to get pi cpu temperature") self.log_error(ex) - return 0 + return 0 def read_si7021_temp(self, address, i2cbus): try: -- 2.39.5 From e3aee07b46b1ad76b55051ffc821ba8f5bf231d3 Mon Sep 17 00:00:00 2001 From: deafloo Date: Sun, 16 Jan 2022 13:16:37 +0100 Subject: [PATCH 095/104] add DHT20 support --- octoprint_enclosure/DHT20.py | 43 +++++++++++++++++++ octoprint_enclosure/__init__.py | 24 +++++++++++ octoprint_enclosure/static/js/enclosure.js | 2 +- .../templates/enclosure_settings.jinja2 | 3 +- 4 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 octoprint_enclosure/DHT20.py diff --git a/octoprint_enclosure/DHT20.py b/octoprint_enclosure/DHT20.py new file mode 100644 index 0000000..9ba45ea --- /dev/null +++ b/octoprint_enclosure/DHT20.py @@ -0,0 +1,43 @@ +import time +import smbus +import sys + +class DHT20Error(Exception): + """ Bast class for exception """ + +if len(sys.argv) == 2 or len(sys.argv) == 3: + address = int(sys.argv[1],16) + if len(sys.argv) == 3: + busNum = int(sys.argv[2],16) + else: + busNum = 1 +else: + print('-1 | -1') + sys.exit(1) + + +sensor = smbus.SMBus(busNum) + +data = sensor.read_i2c_block_data(address,0x71,1) +if(data[0] | 0x08) == 0: + raise DHT20Error('Initialization error') + + +def getValue(bus): + bus.write_i2c_block_data(address,0xac,[0x33,0x00]) + data = bus.read_i2c_block_data(address,0x71,7) + Traw = ((data[3] & 0xf) << 16) + (data[4] << 8) + data[5] + Hraw = ((data[3] & 0xf0) << 4) + (data[1] << 12) + (data[2] << 4) + temp = 200*float(Traw)/2**20 - 50 + humi = 100*float(Hraw)/2**20 + return temp,humi + +def main(): + try: + temperature,humidity = getValue(sensor) + print('{0:0.1f} | {1:0.1f}'.format(temperature, humidity)) + except: + print('-1 | -1') + +if __name__ == "__main__": + main() diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 441bc71..301e904 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -1002,6 +1002,9 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP if sensor['temp_sensor_type'] in ["11", "22", "2302"]: temp, hum = self.read_dht_temp(sensor['temp_sensor_type'], sensor['gpio_pin']) airquality = 0 + elif sensor['temp_sensor_type'] == "20": + temp, hum = self.read_dht20_temp(sensor['temp_sensor_address'], sensor['temp_sensor_i2cbus']) + airquality = 0 elif sensor['temp_sensor_type'] == "18b20": temp = self.read_18b20_temp(sensor['ds18b20_serial']) hum = 0 @@ -1155,6 +1158,27 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.log_error(ex) return (0, 0) + def read_dht20_temp(self, address, i2cbus): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/DHT20.py " + if self._settings.get(["use_sudo"]): + sudo_str = "sudo " + else: + sudo_str = "" + cmd = sudo_str + "python " + script + str(address) + " " + str(i2cbus) + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature DHT20 cmd: %s", cmd) + stdout = (Popen(cmd, shell=True, stdout=PIPE).stdout).read() + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("DHT20 result: %s", stdout) + temp, hum = stdout.decode("utf-8").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 of the plugin.") + self.log_error(ex) + return (0, 0) + def read_bme280_temp(self, address): try: script = os.path.dirname(os.path.realpath(__file__)) + "/BME280.py" diff --git a/octoprint_enclosure/static/js/enclosure.js b/octoprint_enclosure/static/js/enclosure.js index d2a0093..9bbb5db 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', '22', '2302', 'bme280', 'am2320', 'aht10' , 'si7021', 'hum_raw_i2c', 'temp_raw_i2c'].indexOf(sensor) >= 0){ + if (['11', '20', '22', '2302', 'bme280', 'am2320', 'aht10' , 'si7021', 'hum_raw_i2c', 'temp_raw_i2c'].indexOf(sensor) >= 0){ return true; } return false; diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index 394f8fc..adc7f7b 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -604,6 +604,7 @@ - Time in secconds that the GPIO will remain when OFF + Time in seconds that the GPIO will remain when OFF
@@ -240,7 +240,7 @@
- Time delay in secconds to turn on GPIO when print starts OR exact time that the event should happen, note that + Time delay in seconds to turn off GPIO when print starts OR exact time that the event should happen, note that events will only be scheduled after a print starts, time should be formated as HH:MM on a 24 hours format, don't forget to check timezone of your raspberry pi. Attention @@ -256,7 +256,7 @@ - Choose if output should turn off automatically when an error or disconnect was detected. + Choose if output should turn off automatically when an error or disconnect was detected. Shutdown Delay will be ignored in this case
@@ -279,7 +279,7 @@ - Active low means that the GPIO will turn on when receive a low signal (ground) from Raspberry PI + Allows multiple devices to be connected to the same GPIO pins -- 2.39.5 From 3a7a12819b8244c10773584411d1d130dc9fbaa8 Mon Sep 17 00:00:00 2001 From: JeremyLaurenson Date: Wed, 16 Feb 2022 18:06:08 -0500 Subject: [PATCH 097/104] Added support for EMC2101 PWM/Temp board Added support for the Adafruit EMC2101 --- octoprint_enclosure/EMC2101.py | 26 ++++++++ octoprint_enclosure/SETEMC2101.py | 23 +++++++ octoprint_enclosure/__init__.py | 106 ++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 octoprint_enclosure/EMC2101.py create mode 100644 octoprint_enclosure/SETEMC2101.py diff --git a/octoprint_enclosure/EMC2101.py b/octoprint_enclosure/EMC2101.py new file mode 100644 index 0000000..8a373d1 --- /dev/null +++ b/octoprint_enclosure/EMC2101.py @@ -0,0 +1,26 @@ +import time +import board +from adafruit_emc2101.emc2101_lut import EMC2101_LUT as EMC2101 + +i2c = board.I2C() # uses board.SCL and board.SDA +FAN_MAX_RPM = 1700 +emc = EMC2101(i2c) + + +def getTemp(): + return(emc.internal_temperature) + +def getSpeed(): + return(emc.fan_speed) + +def main(): + + try: + temperature = getTemp() + fanspeed = getSpeed() + print('{0:0.1f} | {1:0.1f}'.format(temperature, fanspeed)) + except: + print('-1 | -1') + +if __name__ == "__main__": + main() diff --git a/octoprint_enclosure/SETEMC2101.py b/octoprint_enclosure/SETEMC2101.py new file mode 100644 index 0000000..688212f --- /dev/null +++ b/octoprint_enclosure/SETEMC2101.py @@ -0,0 +1,23 @@ +import sys +import board +from adafruit_emc2101.emc2101_lut import EMC2101_LUT as EMC2101 + +i2c = board.I2C() # uses board.SCL and board.SDA +FAN_MAX_RPM = 1700 +emc = EMC2101(i2c) + + + + +def main(): + # total arguments + n = len(sys.argv) + if n != 2: + print("No_duty_cycle_specified") + sys.exit(2) + + dutyCycle=int(sys.argv[1]) + emc.manual_fan_speed = dutyCycle + +if __name__ == "__main__": + main() diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 441bc71..63dd42f 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -457,6 +457,33 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.write_pwm(gpio, set_value) return make_response('', 204) + @octoprint.plugin.BlueprintPlugin.route("/emc/", methods=["PATCH"]) + @restricted_access + def set_emc2101(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + if 'duty_cycle' not in data: + return make_response("missing duty_cycle attribute", 406) + set_value = self.to_int(data['duty_cycle']) + script = os.path.dirname(os.path.realpath(__file__)) + "/SETEMC2101.py" + cmd = [sys.executable, script, str(set_value)] + if self._settings.get(["use_sudo"]): + cmd.insert(0, "sudo") + 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("EMC2101 error: %s", errors) + else: + self._logger.debug("EMC2101 result: %s", output) + self._logger.debug(output + " " + errors) + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/rgb-led/", methods=["PATCH"]) @restricted_access def set_ledstrip_color(self, identifier): @@ -835,6 +862,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.handle_temp_hum_control() self.handle_temperature_events() self.handle_pwm_linked_temperature() + self.handle_emc_linked_temperature() self.update_ui() self.mqtt_sensor_topic = self.mqtt_root_topic + "/" + sensor['label'] self.mqtt_message = {"temperature": temp, "humidity": hum} @@ -1006,6 +1034,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP temp = self.read_18b20_temp(sensor['ds18b20_serial']) hum = 0 airquality = 0 + elif sensor['temp_sensor_type'] == "emc2101": + temp, hum = self.read_emc2101_temp(sensor['temp_sensor_address'], sensor['temp_sensor_i2cbus']) + hum =0 + airquality = 0 elif sensor['temp_sensor_type'] == "bme280": temp, hum = self.read_bme280_temp(sensor['temp_sensor_address']) airquality = 0 @@ -1226,6 +1258,31 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP "Failed to execute python scripts, try disabling use SUDO on advanced section of the plugin.") self.log_error(ex) return (0, 0) + + def read_emc2101_temp(self, address, i2cbus): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/EMC2101.py" + cmd = [sys.executable, script, str(address), str(i2cbus)] + if self._settings.get(["use_sudo"]): + cmd.insert(0, "sudo") + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature EMC2101 cmd: %s", 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("EMC2101 error: %s", errors) + else: + self._logger.debug("EMC2101 result: %s", output) + temp, fanspeed = output.split("|") + print (temp + " , " + fanspeed ) + return (self.to_float(temp.strip()), 0.0 ) + except Exception as ex: + print(ex) + self._logger.info( + "Failed to execute python scripts, try disabling use SUDO on advanced section of the plugin.") + self.log_error(ex) + return (0, 0) def read_aht10_temp(self, address, i2cbus): try: @@ -1346,6 +1403,55 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.log_error(ex) return 0 + + + def handle_emc_linked_temperature(self): + try: + for pwm_output in list(filter(lambda item: item['output_type'] == 'emc', + self.rpi_outputs)): + if self._printer.is_printing(): + index_id = self.to_int(pwm_output['index_id']) + linked_id = self.to_int(pwm_output['linked_temp_sensor']) + linked_data = self.get_linked_temp_sensor_data(linked_id) + current_temp = self.to_float(linked_data['temperature']) + + duty_a = self.to_float(pwm_output['duty_a']) + duty_b = self.to_float(pwm_output['duty_b']) + temp_a = self.to_float(pwm_output['temperature_a']) + temp_b = self.to_float(pwm_output['temperature_b']) + + try: + calculated_duty = ((current_temp - temp_a) * (duty_b - duty_a) / (temp_b - temp_a)) + duty_a + + if current_temp < temp_a: + calculated_duty = 0 + except: + calculated_duty = 0 + + self._logger.debug("Calculated duty for EMC %s is %s", index_id, calculated_duty) + elif self.print_complete: + calculated_duty = self.to_int(pwm_output['default_duty_cycle']) + else: + calculated_duty = self.to_int(pwm_output['default_duty_cycle']) + script = os.path.dirname(os.path.realpath(__file__)) + "/SETEMC2101.py" + cmd = [sys.executable, script, str(int(calculated_duty))] + if self._settings.get(["use_sudo"]): + cmd.insert(0, "sudo") + self._logger.info("Calculated fan speed is ", calculated_duty) + 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("EMC2101 error: %s", errors) + else: + self._logger.debug("EMC2101 result: %s", output) + self._logger.debug(output + " " + errors) + + + except Exception as ex: + self.log_error(ex) + + def handle_pwm_linked_temperature(self): try: for pwm_output in list(filter(lambda item: item['output_type'] == 'pwm' and item['pwm_temperature_linked'], -- 2.39.5 From 4aac5d2719432fec4f853aa5b2fdcfef9b84b4e7 Mon Sep 17 00:00:00 2001 From: JeremyLaurenson Date: Thu, 17 Feb 2022 10:26:58 -0500 Subject: [PATCH 098/104] Added support for Adafruit EMC2101 Added support for Adafruit EMC2101 --- octoprint_enclosure/EMC2101.py | 26 +++++ octoprint_enclosure/SETEMC2101.py | 23 ++++ octoprint_enclosure/__init__.py | 106 ++++++++++++++++++ .../templates/enclosure_settings.jinja2 | 33 ++++-- 4 files changed, 180 insertions(+), 8 deletions(-) create mode 100644 octoprint_enclosure/EMC2101.py create mode 100644 octoprint_enclosure/SETEMC2101.py diff --git a/octoprint_enclosure/EMC2101.py b/octoprint_enclosure/EMC2101.py new file mode 100644 index 0000000..8a373d1 --- /dev/null +++ b/octoprint_enclosure/EMC2101.py @@ -0,0 +1,26 @@ +import time +import board +from adafruit_emc2101.emc2101_lut import EMC2101_LUT as EMC2101 + +i2c = board.I2C() # uses board.SCL and board.SDA +FAN_MAX_RPM = 1700 +emc = EMC2101(i2c) + + +def getTemp(): + return(emc.internal_temperature) + +def getSpeed(): + return(emc.fan_speed) + +def main(): + + try: + temperature = getTemp() + fanspeed = getSpeed() + print('{0:0.1f} | {1:0.1f}'.format(temperature, fanspeed)) + except: + print('-1 | -1') + +if __name__ == "__main__": + main() diff --git a/octoprint_enclosure/SETEMC2101.py b/octoprint_enclosure/SETEMC2101.py new file mode 100644 index 0000000..688212f --- /dev/null +++ b/octoprint_enclosure/SETEMC2101.py @@ -0,0 +1,23 @@ +import sys +import board +from adafruit_emc2101.emc2101_lut import EMC2101_LUT as EMC2101 + +i2c = board.I2C() # uses board.SCL and board.SDA +FAN_MAX_RPM = 1700 +emc = EMC2101(i2c) + + + + +def main(): + # total arguments + n = len(sys.argv) + if n != 2: + print("No_duty_cycle_specified") + sys.exit(2) + + dutyCycle=int(sys.argv[1]) + emc.manual_fan_speed = dutyCycle + +if __name__ == "__main__": + main() diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 441bc71..63dd42f 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -457,6 +457,33 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.write_pwm(gpio, set_value) return make_response('', 204) + @octoprint.plugin.BlueprintPlugin.route("/emc/", methods=["PATCH"]) + @restricted_access + def set_emc2101(self, identifier): + if "application/json" not in request.headers["Content-Type"]: + return make_response("expected json", 400) + try: + data = request.json + except BadRequest: + return make_response("malformed request", 400) + if 'duty_cycle' not in data: + return make_response("missing duty_cycle attribute", 406) + set_value = self.to_int(data['duty_cycle']) + script = os.path.dirname(os.path.realpath(__file__)) + "/SETEMC2101.py" + cmd = [sys.executable, script, str(set_value)] + if self._settings.get(["use_sudo"]): + cmd.insert(0, "sudo") + 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("EMC2101 error: %s", errors) + else: + self._logger.debug("EMC2101 result: %s", output) + self._logger.debug(output + " " + errors) + return make_response('', 204) + + @octoprint.plugin.BlueprintPlugin.route("/rgb-led/", methods=["PATCH"]) @restricted_access def set_ledstrip_color(self, identifier): @@ -835,6 +862,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.handle_temp_hum_control() self.handle_temperature_events() self.handle_pwm_linked_temperature() + self.handle_emc_linked_temperature() self.update_ui() self.mqtt_sensor_topic = self.mqtt_root_topic + "/" + sensor['label'] self.mqtt_message = {"temperature": temp, "humidity": hum} @@ -1006,6 +1034,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP temp = self.read_18b20_temp(sensor['ds18b20_serial']) hum = 0 airquality = 0 + elif sensor['temp_sensor_type'] == "emc2101": + temp, hum = self.read_emc2101_temp(sensor['temp_sensor_address'], sensor['temp_sensor_i2cbus']) + hum =0 + airquality = 0 elif sensor['temp_sensor_type'] == "bme280": temp, hum = self.read_bme280_temp(sensor['temp_sensor_address']) airquality = 0 @@ -1226,6 +1258,31 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP "Failed to execute python scripts, try disabling use SUDO on advanced section of the plugin.") self.log_error(ex) return (0, 0) + + def read_emc2101_temp(self, address, i2cbus): + try: + script = os.path.dirname(os.path.realpath(__file__)) + "/EMC2101.py" + cmd = [sys.executable, script, str(address), str(i2cbus)] + if self._settings.get(["use_sudo"]): + cmd.insert(0, "sudo") + if self._settings.get(["debug_temperature_log"]) is True: + self._logger.debug("Temperature EMC2101 cmd: %s", 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("EMC2101 error: %s", errors) + else: + self._logger.debug("EMC2101 result: %s", output) + temp, fanspeed = output.split("|") + print (temp + " , " + fanspeed ) + return (self.to_float(temp.strip()), 0.0 ) + except Exception as ex: + print(ex) + self._logger.info( + "Failed to execute python scripts, try disabling use SUDO on advanced section of the plugin.") + self.log_error(ex) + return (0, 0) def read_aht10_temp(self, address, i2cbus): try: @@ -1346,6 +1403,55 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self.log_error(ex) return 0 + + + def handle_emc_linked_temperature(self): + try: + for pwm_output in list(filter(lambda item: item['output_type'] == 'emc', + self.rpi_outputs)): + if self._printer.is_printing(): + index_id = self.to_int(pwm_output['index_id']) + linked_id = self.to_int(pwm_output['linked_temp_sensor']) + linked_data = self.get_linked_temp_sensor_data(linked_id) + current_temp = self.to_float(linked_data['temperature']) + + duty_a = self.to_float(pwm_output['duty_a']) + duty_b = self.to_float(pwm_output['duty_b']) + temp_a = self.to_float(pwm_output['temperature_a']) + temp_b = self.to_float(pwm_output['temperature_b']) + + try: + calculated_duty = ((current_temp - temp_a) * (duty_b - duty_a) / (temp_b - temp_a)) + duty_a + + if current_temp < temp_a: + calculated_duty = 0 + except: + calculated_duty = 0 + + self._logger.debug("Calculated duty for EMC %s is %s", index_id, calculated_duty) + elif self.print_complete: + calculated_duty = self.to_int(pwm_output['default_duty_cycle']) + else: + calculated_duty = self.to_int(pwm_output['default_duty_cycle']) + script = os.path.dirname(os.path.realpath(__file__)) + "/SETEMC2101.py" + cmd = [sys.executable, script, str(int(calculated_duty))] + if self._settings.get(["use_sudo"]): + cmd.insert(0, "sudo") + self._logger.info("Calculated fan speed is ", calculated_duty) + 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("EMC2101 error: %s", errors) + else: + self._logger.debug("EMC2101 result: %s", output) + self._logger.debug(output + " " + errors) + + + except Exception as ex: + self.log_error(ex) + + def handle_pwm_linked_temperature(self): try: for pwm_output in list(filter(lambda item: item['output_type'] == 'pwm' and item['pwm_temperature_linked'], diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index 034747e..4e31ca9 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -22,6 +22,11 @@ + +
+
- +
@@ -110,13 +115,13 @@
- +
- Link PWM ouput to temperature. PWM output will be interpolated between the point from duty A, temperature A -> duty + Link PWM/EMC2101 ouput to temperature. PWM/EMC2101 output will be interpolated between the point from duty A, temperature A -> duty B, temperature B. This output will automatically start when a print starts and will default to the default duty cycle when print is complete. @@ -124,7 +129,7 @@
- +
@@ -184,6 +189,7 @@ +
- + +
- +
+ +
+ +
+ + Value is in percentage, between 0 and 100 +
+
+
@@ -612,6 +628,7 @@ + @@ -731,7 +748,7 @@
- +
-- 2.39.5 From 92f2740c26559ec6a3e9cd7bc7c21814e5c6bd5d Mon Sep 17 00:00:00 2001 From: n8many Date: Tue, 22 Feb 2022 19:46:27 -0700 Subject: [PATCH 099/104] Fixed implementation for BME680 sensor --- octoprint_enclosure/BME680.py | 106 ++++++++++++--------- octoprint_enclosure/__init__.py | 4 +- octoprint_enclosure/static/js/enclosure.js | 2 +- 3 files changed, 62 insertions(+), 50 deletions(-) diff --git a/octoprint_enclosure/BME680.py b/octoprint_enclosure/BME680.py index 3f21d22..79325d5 100644 --- a/octoprint_enclosure/BME680.py +++ b/octoprint_enclosure/BME680.py @@ -2,33 +2,11 @@ import bme680 import time -try: - sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY) -except IOError: - sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY) - -hum_weighting = float(0.25) # so hum effect is 25% of the total air quality score -gas_weighting = float(0.75) # so gas effect is 75% of the total air quality score - -sensor.set_humidity_oversample(bme680.OS_2X) -sensor.set_pressure_oversample(bme680.OS_2X) -sensor.set_temperature_oversample(bme680.OS_2X) -sensor.set_filter(bme680.FILTER_SIZE_3) - -sensor.set_gas_heater_temperature(320) -sensor.set_gas_heater_duration(150) -sensor.select_gas_heater_profile(0) -sensor.set_gas_status(bme680.ENABLE_GAS_MEAS) - -gas_reference = float(250000) -hum_reference = float(40) -getgasreference_count = int(0) - - -def GetGasReference(gas_reference): +def GetGasReference(): # Now run the sensor for a burn-in period, then use combination of relative humidity and gas resistance to estimate indoor air quality as a percentage. # print("Getting a new gas reference value") readings = int(10) + gas_reference = 0 while True: sensor.get_sensor_data() if sensor.data.heat_stable: @@ -36,7 +14,8 @@ def GetGasReference(gas_reference): sensor.get_sensor_data() gas_reference = gas_reference + sensor.data.gas_resistance gas_reference = gas_reference / readings - return + return gas_reference + def CalculateIAQ(score): IAQ_text = "Air quality is " @@ -55,32 +34,65 @@ def CalculateIAQ(score): IAQ_text = IAQ_text + "Good" return IAQ_text -#Calculate humidity contribution to IAQ index -current_humidity = float(sensor.data.humidity) -if (current_humidity >= 38 and current_humidity <= 42): - hum_score = float(0.25*100) # Humidity +/-5% around optimum -else: - #sub-optimal - if (current_humidity < 38): - hum_score = float(0.25/hum_reference*current_humidity*100) + +if __name__ == "__main__": + + try: + sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY) + except RuntimeError: + try: + sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY) + except Exception as ex: + print(ex) + quit(-1) + + hum_weighting = float(0.25) # so hum effect is 25% of the total air quality score + gas_weighting = float(0.75) # so gas effect is 75% of the total air quality score + + sensor.set_humidity_oversample(bme680.OS_2X) + sensor.set_pressure_oversample(bme680.OS_2X) + sensor.set_temperature_oversample(bme680.OS_2X) + sensor.set_filter(bme680.FILTER_SIZE_3) + + sensor.get_sensor_data() + temperature = sensor.data.temperature + humidity = sensor.data.humidity + + sensor.set_gas_heater_temperature(320) + sensor.set_gas_heater_duration(150) + sensor.select_gas_heater_profile(0) + sensor.set_gas_status(bme680.ENABLE_GAS_MEAS) + + gas_reference = float(250000) + hum_reference = float(40) + getgasreference_count = int(0) + + # Calculate humidity contribution to IAQ index + current_humidity = float(humidity) + if (current_humidity >= 38 and current_humidity <= 42): + hum_score = float(0.25 * 100) # Humidity +/-5% around optimum else: - hum_score = ((-0.25/(100-hum_reference)*current_humidity)+0.416666)*100 + # sub-optimal + if (current_humidity < 38): + hum_score = float(0.25 / hum_reference * current_humidity * 100) + else: + hum_score = ((-0.25 / (100 - hum_reference) * current_humidity) + 0.416666) * 100 -#Calculate gas contribution to IAQ index -gas_lower_limit = float(5000) # Bad air quality limit -gas_upper_limit = float(50000) # Good air quality limit + # Calculate gas contribution to IAQ index + gas_lower_limit = float(5000) # Bad air quality limit + gas_upper_limit = float(50000) # Good air quality limit -if gas_reference > gas_upper_limit: - gas_reference = gas_upper_limit -if gas_reference < gas_lower_limit: - gas_reference = gas_lower_limit + gas_reference = GetGasReference() -gas_score = float((0.75/(gas_upper_limit-gas_lower_limit)*gas_reference -(gas_lower_limit*(0.75/(gas_upper_limit-gas_lower_limit))))*100) + if gas_reference > gas_upper_limit: + gas_reference = gas_upper_limit + if gas_reference < gas_lower_limit: + gas_reference = gas_lower_limit -#Combine results for the final IAQ index value (0-100% where 100% is good quality air) -air_quality_score = float(hum_score + gas_score) + gas_score = float((0.75 / (gas_upper_limit - gas_lower_limit) * gas_reference - ( + gas_lower_limit * (0.75 / (gas_upper_limit - gas_lower_limit)))) * 100) -GetGasReference(gas_reference) - -print('{0:0.1f}'.format(air_quality_score)) + # Combine results for the final IAQ index value (0-100% where 100% is good quality air) + air_quality_score = float(hum_score + gas_score) + print('{:0.1f}|{:0.1f}|{:0.1f}'.format(temperature, humidity, air_quality_score)) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 441bc71..75f4e78 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -1183,7 +1183,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP def read_bme680_temp(self, address): try: - script = os.path.dirname(os.path.realpath(__file__)) + "/BME680.py " + script = os.path.dirname(os.path.realpath(__file__)) + "/BME680.py" cmd = [sys.executable, script, str(address)] if self._settings.get(["use_sudo"]): cmd.insert(0, "sudo") @@ -1204,7 +1204,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._logger.info( "Failed to execute python scripts, try disabling use SUDO on advanced section of the plugin.") self.log_error(ex) - return (0, 0) + return (0, 0, 0) def read_am2320_temp(self): try: diff --git a/octoprint_enclosure/static/js/enclosure.js b/octoprint_enclosure/static/js/enclosure.js index d2a0093..51077ba 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', '22', '2302', 'bme280', 'am2320', 'aht10' , 'si7021', 'hum_raw_i2c', 'temp_raw_i2c'].indexOf(sensor) >= 0){ + if (['11', '22', '2302', 'bme280', 'bme680', 'am2320', 'aht10' , 'si7021', 'hum_raw_i2c', 'temp_raw_i2c'].indexOf(sensor) >= 0){ return true; } return false; -- 2.39.5 From 6e9879a9391aa7728dab08995ffa32a4bcc7d101 Mon Sep 17 00:00:00 2001 From: Vitor de Miranda Henrique Date: Fri, 25 Feb 2022 18:09:35 -0700 Subject: [PATCH 100/104] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 825bd8e..20cc0a0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ Find the plugin useful? Buy me a coffee [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/VitorHenrique/2) +# THIS PLUGIN IS DEPRACATED! +I'm moving away from it, and when ready the replacement plugin would be here: https://github.com/vitormhenrique/OctoPrint-Enclosure-V2 + # Before opening an issue... Check the [troubleshooting guide](https://github.com/vitormhenrique/OctoPrint-Enclosure/wiki/Troubleshooting-Guide). Issues with no log, no print screen *will be closed* until the necessary documentation is available. -- 2.39.5 From 1d493fa522db9689f9cbf115db786b48c15d962c Mon Sep 17 00:00:00 2001 From: IhatemyISP Date: Sat, 12 Mar 2022 13:46:31 -0500 Subject: [PATCH 101/104] Obey bus setting for MCP9808 Bus setting for MCP9808 sensors is now obeyed. --- octoprint_enclosure/__init__.py | 6 +++--- octoprint_enclosure/mcp9808.py | 9 ++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index a03cc69..72d8376 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -1036,7 +1036,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP hum = 0 airquality = 0 elif sensor['temp_sensor_type'] == "mcp9808": - temp = self.read_mcp_temp(sensor['temp_sensor_address']) + temp = self.read_mcp_temp(sensor['temp_sensor_address'], sensor['temp_sensor_i2cbus']) hum = 0 airquality = 0 elif sensor['temp_sensor_type'] == "temp_raw_i2c": @@ -1121,10 +1121,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP self._logger.warn(message) return str(-1) - def read_mcp_temp(self, address): + def read_mcp_temp(self, address, i2cbus): try: script = os.path.dirname(os.path.realpath(__file__)) + "/mcp9808.py" - args = ["python", script, str(address)] + args = ["python", script, str(i2cbus), str(address)] if self._settings.get(["debug_temperature_log"]) is True: self._logger.debug("Temperature MCP9808 cmd: %s", " ".join(args)) proc = Popen(args, stdout=PIPE) diff --git a/octoprint_enclosure/mcp9808.py b/octoprint_enclosure/mcp9808.py index 672045b..6d9f707 100644 --- a/octoprint_enclosure/mcp9808.py +++ b/octoprint_enclosure/mcp9808.py @@ -28,13 +28,12 @@ MCP9808_REG_CONFIG_ALERTMODE = 0x0001 def main(): - # get bus address if provided or use default address + # get i2c bus and bus address if provided or use defaults address = MCP9808_I2CADDR_DEFAULT - if len(sys.argv) == 2: - address = int(sys.argv[1], 16) - - # get I2C bus bus = smbus.SMBus(1) + if len(sys.argv) > 1: + bus = smbus.SMBus(int(sys.argv[1])) + address = int(sys.argv[2], 16) # MCP9808 address, default 0x18(24) # configuration register, 0x01(1) -- 2.39.5 From cfbcf41f16fbc4de528474df153d881c31e8cb9a Mon Sep 17 00:00:00 2001 From: Tony Brobston Date: Thu, 8 Sep 2022 13:30:20 -0500 Subject: [PATCH 102/104] Update documentation --- README.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 20cc0a0..1f809ec 100644 --- a/README.md +++ b/README.md @@ -70,20 +70,16 @@ You can test the library by using the sample code from https://learn.adafruit.co Follow the wiring diagram on the pictures on thingiverse. The DS18B20 uses "1-wire" communication protocol, DS18B20 only works on GPIO pin number 4 by default. You also need to add OneWire support for your raspberry pi. -Start by adding the following line to `/boot/config.txt` - -``` -dtoverlay=w1-gpio -``` +Start by enabling 1-wire support, [follow this tutorial](https://learn.adafruit.com/adafruits-raspberry-pi-lesson-11-ds18b20-temperature-sensing/ds18b20). After rebooting, you can check if the OneWire device was found properly with ``` -dmesg | grep w1-gpio +dmesg | grep onewire ``` You should see something like ``` -[ 3.030368] w1-gpio onewire@0: gpio pin 4, external pullup pin -1, parasitic power 0 +[ 5.216899] gpio-4 (onewire@0): enforced open drain please flag it properly in DT/ACPI DSDT/board file ``` If you're using the internal pullup resistor, you'll need to enable it manually by running these Python commands. Or, you can simply configure the sensor inside of the Enclosure plugin, which will do this for you. -- 2.39.5 From 9a8bb382f0f8ab8665ad7d6c107141984036c077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Nardi?= Date: Wed, 1 May 2024 14:20:07 +0200 Subject: [PATCH 103/104] Fix DHT11/DHT22/AM2302 temperature retrieval with recent versions of adafruit_dht library --- octoprint_enclosure/getDHTTemp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/octoprint_enclosure/getDHTTemp.py b/octoprint_enclosure/getDHTTemp.py index e898977..92960e5 100644 --- a/octoprint_enclosure/getDHTTemp.py +++ b/octoprint_enclosure/getDHTTemp.py @@ -1,7 +1,7 @@ import sys import time import adafruit_dht - +from microcontroller import Pin # Parse command line parameters. sensor_args = { @@ -16,7 +16,7 @@ if len(sys.argv) == 3 and sys.argv[1] in sensor_args: else: sys.exit(1) -dht_dev = sensor(pin) +dht_dev = sensor(Pin(pin)) # DHT sensor read fails quite often, causing enclosure plugin to report value of 0. # If this happens, retry as suggested in the adafruit_dht docs. @@ -42,4 +42,4 @@ while retry_count <= max_retries: retry_count += 1 print('-1 | -1') -sys.exit(1) \ No newline at end of file +sys.exit(1) -- 2.39.5 From 76f6795e2e172c67f031a4ff6334278bda831e1c Mon Sep 17 00:00:00 2001 From: Jacopo Tediosi Date: Fri, 24 May 2024 19:06:17 +0200 Subject: [PATCH 104/104] Fix 'A PWM object already exists for this GPIO channel' exception --- octoprint_enclosure/__init__.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 72d8376..b5f1125 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -1571,13 +1571,28 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplateP for gpio_out_pwm in list(filter(lambda item: item['output_type'] == 'pwm', self.rpi_outputs)): pin = self.to_int(gpio_out_pwm['gpio_pin']) self._logger.info("Setting GPIO pin %s as PWM", pin) - for pwm in (pwm_dict for pwm_dict in self.pwm_instances if pin in pwm_dict): - self.pwm_instances.remove(pwm) + + # Stop and clear any other pwm instances on that pin + pwm_instances_to_remove = [] + for pwm_instance in self.pwm_instances: + if pin in pwm_instance: + pwm_instance[pin].stop() + pwm_instances_to_remove.append(pwm_instance) + for pwm_instance in pwm_instances_to_remove: + self.pwm_instances.remove(pwm_instance) + + # Clear the pin self.clear_channel(pin) + + # Setup new pwm on that pin GPIO.setup(pin, GPIO.OUT) pwm_instance = GPIO.PWM(pin, self.to_int(gpio_out_pwm['pwm_frequency'])) + + # Start the pwm self._logger.info("starting PWM on pin %s", pin) - pwm_instance.start(0) + pwm_instance.start(self.to_int(gpio_out_pwm['default_duty_cycle'])) + + # Add the pwm to pwm_instances list self.pwm_instances.append({pin: pwm_instance}) for gpio_out_neopixel in list( filter(lambda item: item['output_type'] == 'neopixel_direct', self.rpi_outputs)): -- 2.39.5