diff --git a/README.md b/README.md index 825bd8e..2a3ba9a 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Install the plugin using the Plugin Manager bundled with OctoPrint, you can sear This plugin support many hardware temperature sensors, led, relays, heater... -Here are detailled instructions on how to setup them. +Here are detailed instructions on how to setup them. ### Temperature sensors @@ -136,7 +136,7 @@ If your setup does not have pip install pip: Install the required library: `sudo pip install rpi_ws281x` -rpi_ws281x really needs sudo, and you need to setup up so your rpi does not ask for a password when runing a python script, so run: +rpi_ws281x really needs sudo, and you need to setup up so your rpi does not ask for a password when running a python script, so run: `sudo visudo` @@ -148,6 +148,20 @@ Also backlist the audio kernel: add the `blacklist snd_bcm2835` to the end of the file. +### Hardware PWM + +Hardware PWM is required in order to drive higher frequency signals, such as the 25000 Hz needed by Noctua PWM fans. The rpi-hardware-pwm module is used as a lightweight alternative to pigpio. Install the required library: `sudo pip install rpi-hardware-pwm` + +Enable PWM0 (GPIO 18) by adding the following line to `/boot/config.txt` + +``` +dtoverlay=pwm +``` + +If you wish to use both PWM0 and PWM1 then use `dtoverlay=pwm-2chan` instead. If you wish to use the alternate PWM pin numbers, then read the [rpi-hardware-pwm module readme](https://github.com/Pioreactor/rpi_hardware_pwm) for how this can be configured. + +After editing `boot/config.text` reboot your Raspberry Pi. + ### 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. @@ -186,8 +200,8 @@ Outputs are meant to control THINGS (temperature, lights, locker, extra enclosur Outputs can be set to the following types: * Regular GPIO -* PWM GPIO -* Neopixel Control via Microcontroler +* PWM GPIO (Software or Hardware controlled PWM) +* Neopixel Control via Microcontroller * Neopixel Control directly from raspberry pi * Temperature and Humidity Control * Temperature Alarm diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 441bc71..de78d67 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -55,6 +55,17 @@ def CheckInputActiveLow(Input_Pull_Resistor): else: return False +#Function that returns the hardware PWM channel or raises ValueError otherwise +def Pwm_Channel(pin): + pwm0_pins = [12, 18] + pwm1_pins = [13, 19] + if pin in pwm0_pins: + return 0 + elif pin in pwm1_pins: + return 1 + else: + raise ValueError("Not a Hardware PWM pin") + class EnclosurePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SettingsPlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.BlueprintPlugin, octoprint.plugin.EventHandlerPlugin): @@ -167,7 +178,7 @@ def on_after_startup(self): self.print_complete = False def get_settings_version(self): - return 10 + return 11 def on_settings_migrate(self, target, current=None): self._logger.warn("######### current settings version %s target settings version %s #########", current, target) @@ -175,8 +186,8 @@ def on_settings_migrate(self, target, current=None): 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 #########") + if current >= 4 and target == 11: + self._logger.warn("######### migrating settings to v11 #########") old_outputs = self._settings.get(["rpi_outputs"]) old_inputs = self._settings.get(["rpi_inputs"]) for rpi_output in old_outputs: @@ -199,7 +210,9 @@ def on_settings_migrate(self, target, current=None): 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 + rpi_output['shutdown_on_error'] = False + if 'hw_pwm' not in rpi_output: + rpi_output['hw_pwm'] = False self._settings.set(["rpi_outputs"], old_outputs) old_inputs = self._settings.get(["rpi_inputs"]) @@ -1546,15 +1559,27 @@ def configure_gpio(self): 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) + pwm_freqency = self.to_int(gpio_out_pwm["pwm_frequency"]) 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}) + try: + if "hw_pwm" in gpio_out_pwm and gpio_out_pwm["hw_pwm"] is True: + from rpi_hardware_pwm import HardwarePWM + pwm_channel_number = Pwm_Channel(pin) # Raises valueError if not a hardware PWM pin + self._logger.info("starting Hardware PWM on pin %i channel %i at %i Hz", pin, pwm_channel_number, pwm_freqency) + # If RPi dtoverlay has not been configured this will throw rpi_hardware_pwm.HardwarePWMException with information on what to do. + pwm_instance = HardwarePWM(pwm_channel=pwm_channel_number, hz=pwm_freqency) + else: + self._logger.info("starting PWM on pin %s at %i Hz", pin, pwm_freqency) + GPIO.setup(pin, GPIO.OUT) + pwm_instance = GPIO.PWM(pin, pwm_freqency) + pwm_instance.start(0) + self.pwm_instances.append({pin: pwm_instance}) + except ImportError as error: + self._logger.error("HardwarePWM module not installed. Install using pip install rpi-hardware-pwm") + except ValueError as error: + self._logger.error("Invalid Hardware PMW pin. pwm0 is GPIO pin 18 is physical pin 12 and pwm1 is GPIO pin 19 is physical pin 13") 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']) diff --git a/octoprint_enclosure/static/js/enclosure.js b/octoprint_enclosure/static/js/enclosure.js index d2a0093..f3d5179 100644 --- a/octoprint_enclosure/static/js/enclosure.js +++ b/octoprint_enclosure/static/js/enclosure.js @@ -380,6 +380,7 @@ $(function () { gpio_status: ko.observable(false), hide_btn_ui: ko.observable(false), active_low: ko.observable(true), + hw_pwm: ko.observable(false), pwm_temperature_linked: ko.observable(false), toggle_timer: ko.observable(false), toggle_timer_on: ko.observable(0), diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index 034747e..e5871ed 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -110,6 +110,21 @@ + +
+
+ + + Attention You need to install and configure the rpi-hardware-pwm module to utilise the hardware PWM on the Pi, check + the documentation on the plugin + github page. Note: Hardware PWM is only available on GPIO 18 (PWM0) and GPIO 19 (PWM1). + +
+
+ +