Skip to content

Commit

Permalink
add button control
Browse files Browse the repository at this point in the history
  • Loading branch information
chris72205 committed May 16, 2021
1 parent b645e07 commit 830b105
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
service/config.json

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
# iot-raspberry-pi-controller

Control relay(s) with a Raspberry Pi. Should run as a system service and update the relay state(s) based on messages via MQTT or physical momentary switch(es).
Control relay(s) with a Raspberry Pi. Should run as a system service and update the relay state(s) based on messages via MQTT/AMQP or physical momentary switch(es).

## Setup

### Configuration

Place populated `config.json` next to `main.py` with configured values. See `config.sample.json` for what this should look like.

### System service

Copy `iot-raspberry-pi-controller.service` to `/etc/systemd/system` and then run `sudo systemctl start iot-raspberry-pi-controller.service`. If satisfied, enable it with `sudo systemctl enable iot-raspberry-pi-controller.service`. The service can be stopped at any time by running `sudo systemctl stop iot-raspberry-pi-controller.service`.

## Todo

- set up messaging via RabbitMQ
- consume messages to toggle relay channels
- publish messages with latest status of relay channels
- add output logging of switch state changes and around RabbitMQ messages
- look into how to properly package this for easier installation/deployment (thinking about installing python dependencies)
- tests??
39 changes: 39 additions & 0 deletions config.sample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"relay": {
"channels": [
{
"name": "Outdoor Light",
"key": "outdoor-light",
"pin": 13
},
{
"name": "East Wall Switch",
"key": "east-wall-switch",
"pin": 26
},
{
"name": "West Wall Switch",
"key": "west-wall-switch",
"pin": 6
},
{
"name": "3D Printer",
"key": "3d-printer",
"pin": 5
}
]
},
"buttons": [
{
"name": "Outdoor Light",
"key": "outdoor-light",
"pin": 0
},
{
"name": "3D Printer",
"key": "3d-printer",
"pin": 0
}
],
"rabbitmq": {}
}
14 changes: 14 additions & 0 deletions raspberry-pi-relay-controller.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[Unit]
Description=Raspberry Pi Relay Controller
After=network.target

[Service]
ExecStart=/usr/bin/python3 -u service/main.py
WorkingDirectory=/home/pi/iot-raspberry-pi-controller
StandardOutput=inherit
StandardError=inherit
Restart=always
User=pi

[Install]
WantedBy=multi-user.target
37 changes: 37 additions & 0 deletions service/button.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from gpiozero import Button as GPIOButton
import time

class Button:
_gpio_button = None
_debounce_time = 500
_last_press = 0
_channel = None


def __init__(self, name, key, pin):
self.name = name
self.key = key
self._pin = pin

self._setup()

def set_relay_channel(self, channel):
self._channel = channel

def _pressed(self):
press_time = self._current_millis()

if(self._is_outside_of_debounce(press_time)):
if(self._channel is not None):
self._channel.toggle()
self._last_press = press_time

def _setup(self):
self._gpio_button = GPIOButton(self._pin)
self._gpio_button.when_released = self._pressed

def _is_outside_of_debounce(self, press_time):
return press_time - self._last_press > self._debounce_time

def _current_millis(self):
return round(time.time() * 1000)
33 changes: 33 additions & 0 deletions service/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import json
import signal

from relay import Relay
from button import Button

def load_config():
with open('config.json') as f:
return json.load(f)

# parse config
config = load_config()


# set up relay
relay = Relay()
for val in config['relay']['channels']:
relay.add_channel(val['name'], val['key'], val['pin'])


# set up buttons
buttons = []
for button_config in config['buttons']:
button = Button(button_config['name'], button_config['key'], button_config['pin'])
button.set_relay_channel(relay.get_channel(button_config['key']))

buttons.append(button)


# todo(chrisjacob): set up RabbitMQ
# set up RabbitMQ

signal.pause()
5 changes: 5 additions & 0 deletions service/rabbitmq.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import pika

class RabbitMQ:
def __init__(self, connection_string, relay):
# todo (chrisjacob): build this out
63 changes: 63 additions & 0 deletions service/relay.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from gpiozero import LED
import time
import logging

class Channel:
_pin = None
_output = None

_debounce_time = 500
_last_press = 0

def __init__(self, name, pin):
self.name = name
self.pin = pin

self._output = LED(pin)

def get_state(self):
self._output.is_active

def turn_on(self):
if(not self.safety_enabled() and self._is_outside_of_debounce(self._current_millis())):
self._last_press = self._current_millis()
self._output.on()

def turn_off(self):
# no debounce here, we can always turn it off
self._output.off()

def toggle(self):
if(not self.safety_enabled() and self._is_outside_of_debounce(self._current_millis())):
self._last_press = self._current_millis()
self._output.toggle()

def _is_outside_of_debounce(self, press_time):
return press_time - self._last_press > self._debounce_time

def _current_millis(self):
return round(time.time() * 1000)

def _safety_enabled(self):
# todo(chrisjacob): return true if channel has changed state more than _x_ times in _n_ seconds

class Relay:
_channels = {}

def add_channel(self, name, key, pin):
self._channels[key] = Channel(name, pin)

def get_channel(self, key):
return self._channels[key]

def turn_on(self, key):
if(self._channels.has_key(key)):
self._channels[key].turn_on()

def turn_off(self, key):
if(self._channels.has_key(key)):
self._channels[key].turn_off()

def toggle(self, key):
if(self._channels.has_key(key)):
self._channels[key].toggle()

0 comments on commit 830b105

Please sign in to comment.