import atexit import time import warnings from sys import version_info try: import RPi.GPIO as GPIO except ImportError: raise ImportError("This library requires the RPi.GPIO module\nInstall with: sudo pip install RPi.GPIO") from .ads1015 import ads1015 from .pins import ObjectCollection, AsyncWorker, StoppableThread __version__ = '0.2.2' RELAY_1 = 13 RELAY_2 = 19 RELAY_3 = 16 INPUT_1 = 26 INPUT_2 = 20 INPUT_3 = 21 OUTPUT_1 = 5 OUTPUT_2 = 12 OUTPUT_3 = 6 UPDATES_PER_SECOND = 30 i2c = None sn3218 = None automation_hat = False automation_phat = True _led_states = [0] * 18 _lights_need_updating = False _is_setup = False _t_update_lights = None class SNLight(object): def __init__(self, index): self.index = index self._max_brightness = float(128) def toggle(self): """Toggle the light from on to off or off to on""" self.write(1 - self.read()) def on(self): """Turn the light on""" self.write(1) def off(self): """Turn the light off""" self.write(0) def read(self): """Read the current status of the light""" if self.index is None: return return _led_states[self.index] / self._max_brightness def write(self, value): """Write a specific value to the light :param value: Brightness of the light, from 0.0 to 1.0 """ global _lights_need_updating if self.index is None: return if type(value) is not int and type(value) is not float: raise TypeError("Value must be int or float") if value >= 0 and value <= 1.0: _led_states[self.index] = int(self._max_brightness * value) if _t_update_lights is None and sn3218 is not None: sn3218.output(_led_states) else: _lights_need_updating = True else: raise ValueError("Value must be between 0.0 and 1.0") class AnalogInput(object): type = 'Analog Input' def __init__(self, channel, max_voltage, led): self._en_auto_lights = True self.channel = channel self.value = 0 self.max_voltage = float(max_voltage) self.light = SNLight(led) self._is_setup = False def setup(self): if self._is_setup: return setup() self._is_setup = True def auto_light(self, value=None): if value is not None: self._en_auto_lights = value return self._en_auto_lights def read(self): """Return the read voltage of the analog input""" if self.name == "four" and is_automation_phat(): warnings.warn("Analog Four is not supported on Automation pHAT") self._update() return round(self.value * self.max_voltage, 3) def _update(self): self.setup() self.value = _ads1015.read(self.channel) if self._en_auto_lights: adc = self.value self.light.write(max(0.0, min(1.0, adc))) class Pin(object): type = 'Pin' def __init__(self, pin): self.pin = pin self._last_value = None self._is_setup = False def __call__(self): return filter(lambda x: x[0] != '_', dir(self)) def read(self): self.setup() return GPIO.input(self.pin) def setup(self): pass def has_changed(self): value = self.read() if self._last_value is None: self._last_value = value if value is not self._last_value: self._last_value = value return True return False def is_on(self): return self.read() == 1 def is_off(self): return self.read() == 0 class Input(Pin): type = 'Digital Input' def __init__(self, pin, led): self._en_auto_lights = True Pin.__init__(self, pin) self.light = SNLight(led) def setup(self): if self._is_setup: return False setup() GPIO.setup(self.pin, GPIO.IN) self._is_setup = True def auto_light(self, value=None): if value is not None: self._en_auto_lights = value return self._en_auto_lights def read(self): value = Pin.read(self) if self._en_auto_lights: self.light.write(value) return value class Output(Pin): type = 'Digital Output' def __init__(self, pin, led): self._en_auto_lights = True Pin.__init__(self, pin) self.light = SNLight(led) def setup(self): if self._is_setup: return False setup() GPIO.setup(self.pin, GPIO.OUT, initial=0) self._is_setup = True return True def auto_light(self, value=None): if value is not None: self._en_auto_lights = value return self._en_auto_lights def write(self, value): """Write a value to the output. :param value: Value to write, either 1 for HIGH or 0 for LOW """ self.setup() GPIO.output(self.pin, value) if self._en_auto_lights: self.light.write(1 if value else 0) def on(self): """Turn the output on/HIGH""" self.write(1) def off(self): """Turn the output off/LOW""" self.write(0) def toggle(self): """Toggle the output.""" self.write(not self.read()) class Relay(Output): type = 'Relay' def __init__(self, pin, led_no, led_nc): Pin.__init__(self, pin) self.light_no = SNLight(led_no) self.light_nc = SNLight(led_nc) self._en_auto_lights = True def auto_light(self, value=None): if value is not None: self._en_auto_lights = value return self._en_auto_lights def setup(self): if self._is_setup: return False setup() if is_automation_phat() and self.name == "one": self.pin = RELAY_3 GPIO.setup(self.pin, GPIO.OUT, initial=0) self._is_setup = True return True def write(self, value): """Write a value to the relay. :param value: Value to write, either 0 for LOW or 1 for HIGH """ self.setup() if is_automation_phat() and self.name in ["two", "three"]: warnings.warn("Relay '{}' is not supported on Automation pHAT".format(self.name)) GPIO.output(self.pin, value) if self._en_auto_lights: if value: self.light_no.write(1) self.light_nc.write(0) else: self.light_no.write(0) self.light_nc.write(1) def _update_lights(): global _lights_need_updating analog.read() input.read() if _lights_need_updating: sn3218.output(_led_states) _lights_need_updating = False time.sleep(1.0 / UPDATES_PER_SECOND) def is_automation_hat(): setup() return sn3218 is not None def is_automation_phat(): setup() return sn3218 is None def enable_auto_lights(state): global _t_update_lights setup() if sn3218 is None: return input.auto_light(state) output.auto_light(state) relay.auto_light(state) analog.auto_light(state) if state and _t_update_lights is None: _t_update_lights = AsyncWorker(_update_lights) _t_update_lights.start() if not state and _t_update_lights is not None: _t_update_lights.stop() _t_update_lights.join() _t_update_lights = None def setup(): global automation_hat, automation_phat, sn3218, _ads1015, _is_setup, _t_update_lights if _is_setup: return True _is_setup = True GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) try: import smbus except ImportError: if version_info[0] < 3: raise ImportError("This library requires python-smbus\nInstall with: sudo apt install python-smbus") elif version_info[0] == 3: raise ImportError("This library requires python3-smbus\nInstall with: sudo apt install python3-smbus") _ads1015 = ads1015(smbus.SMBus(1)) if _ads1015.available() is False: raise RuntimeError("No ADC detected, check your connections") try: import sn3218 except ImportError: raise ImportError("This library requires sn3218\nInstall with: sudo pip install sn3218") except IOError: pass if sn3218 is not None: sn3218.enable() sn3218.enable_leds(0b111111111111111111) automation_hat = True automation_phat = False _t_update_lights = AsyncWorker(_update_lights) _t_update_lights.start() atexit.register(_exit) def _exit(): if _t_update_lights: _t_update_lights.stop() _t_update_lights.join() if sn3218 is not None: sn3218.output([0] * 18) GPIO.cleanup() analog = ObjectCollection() analog._add(one=AnalogInput(0, 25.85, 0)) analog._add(two=AnalogInput(1, 25.85, 1)) analog._add(three=AnalogInput(2, 25.85, 2)) analog._add(four=AnalogInput(3, 3.3, None)) input = ObjectCollection() input._add(one=Input(INPUT_1, 14)) input._add(two=Input(INPUT_2, 13)) input._add(three=Input(INPUT_3, 12)) output = ObjectCollection() output._add(one=Output(OUTPUT_1, 3)) output._add(two=Output(OUTPUT_2, 4)) output._add(three=Output(OUTPUT_3, 5)) relay = ObjectCollection() relay._add(one=Relay(RELAY_1, 6, 7)) relay._add(two=Relay(RELAY_2, 8, 9)) relay._add(three=Relay(RELAY_3, 10, 11)) light = ObjectCollection() light._add(power=SNLight(17)) light._add(comms=SNLight(16)) light._add(warn=SNLight(15))