-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b645e07
commit 830b105
Showing
8 changed files
with
213 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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?? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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": {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |