Skip to content

Commit

Permalink
Do proper argparsing
Browse files Browse the repository at this point in the history
  • Loading branch information
fredley committed Mar 7, 2018
1 parent 411ef61 commit 9f71941
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 81 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
config.yml
__pycache__
.*
55 changes: 11 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,54 +28,21 @@ Launch the listener with:
It should connect, and once it has you should be able to press buttons on your Turn Touch and see the output of your commands.


## Hue Controller
## Controllers

In order to connect to your Hue Bridge, it's easest to run setup separately first:
To see what controllers are available, run

```python3 controllers/hue_controller.py```
```sudo python3 monitor.py -l```

This will prompt you to press the button on your bridge, then list out your lights, by id, type and room.
You can then configure button presses in `config.yml` as follows:
To see help for setting up commands for a controller, run, e.g.

```yaml
north_double:
type: hue
action: set_light
id: 1 # From output of python3 hue.py
bri: # 1-254
hue: 9000 # From 0 to 65535, hardware dependent
```
Setting scenes, or rooms is not possible, yet!
## Nest Controller
In order to connect to your Nest, you need to register for a Nest Developer account, and get a product ID and secret, which you must insert into `controllers/nest_controller.py` manually.
```sudo python3 monitor.py -c hue```

Then, you can authenticate yourself by running:
and then to set them up, run, e.g.

```python3 controllers/nest_controller.py```

This will give you a URL, which will give you a PIN which you will be prompted for. If successful, the controller will then print out all homes and devices. You can then configure your remote using the following:

```yaml
east_press:
type: nest
structure: My Home # As in listed values. Can be omitted if only one structure
device: Kitchen # As in listed values. Can be omitted if only one device
action: adjust_temp
temperature: 18 # will be ºF or ºC as per the settings on your nest. Just put in the right number.
east_double:
type: nest
action: adjust_temp
direction: up # will add 1º to the target temp, or use 'down' for -1º
east_hold:
type: nest
action: set_away
away: true # Sets your device to away, or use false to set home
```
```sudo python3 monitor.py -s hue```

Currently, only nest thermostats are supported, and again no room support.
Most controllers that connect to a device will need some kind of setup. Controllers will often print out helpful information (such as available bulbs, thermostats) after setup.

## Battery Status

Expand Down Expand Up @@ -104,10 +71,10 @@ Make sure you have set up credentials for services such as Nest and Hue before d

## Writing your own controller

* Decide on a name for your controller. In this example it is `custom`. This key must be used in the filename and `config.yml`
* Decide on a name for your controller. In this example it is `custom`. This key must be used in the filename and `config.yml` entries.
* Create `controllers/custom_controller.py`
* Create a class called, e.b. `CustomController`. It *must* contain the word 'Controller'.
* Implement `perform(self, action)`, where action is a dict as passed from `config.yml`
* Create a class called, e.b. `CustomController`. It *must* inherit from `BaseController`.
* Implement `perform(self, action)`, where action is a dict as passed from `config.yml`, and `init` for setup.
* In `config.yml`, simply address your controller as follows:

```yaml
Expand Down
9 changes: 8 additions & 1 deletion controllers/base_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def __init__(self, *args, **kwargs):
self.print_log = True
else:
self.logger = logging.getLogger(self.get_class_name())
self.init()
self.init(*args, **kwargs)

@classmethod
def get_class_name(cls):
Expand All @@ -27,3 +27,10 @@ def log(self, msg, level=logging.INFO):

def perform(self, action):
print("Unimplemented!")

@classmethod
def help(cls):
return "There is no help for this module :-("

def print_all(self):
pass
13 changes: 13 additions & 0 deletions controllers/bash_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,16 @@ def perform(self, action):
).decode('utf-8').strip())
except Exception as e:
self.log("Something went wrong: {}".format(e), logging.ERROR)

@classmethod
def help(cls):
return """
Bash Module - Run commands in the shell
Usage:
north_press:
type: bash
command: echo "Hello North"
"""

35 changes: 32 additions & 3 deletions controllers/hue_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,36 @@ def perform(self, action):
elif act == 'set_scene':
self.set_scene(id, **kwargs)

@classmethod
def help(cls):
return """
Hue Module - Control Hue Lights
if __name__ == '__main__':
c = HueController(print=True)
c.print_all()
Usage:
north_press:
type: hue
action: set_light
id: 1 # See below for IDs
bri: 254 # From 1 to 254
hue: 9000 # From 0 to 65535, hardware dependent
north_press:
type: hue
action: set_room
id: 1 # See below for IDs
bri: 254 # From 1 to 254
hue: 9000 # From 0 to 65535, hardware dependent
north_press:
type: hue
action: adjust_brightness
id: 1 # Light ID. See below for IDs
direction: up # or down
north_press:
type: hue
action: set_scene
id: 1 # Room ID. See below for IDs
scene: Scene Name # see below
"""
27 changes: 23 additions & 4 deletions controllers/nest_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
# You must register for a Nest Developer account to obtain these
# Instructions are at https://github.com/jkoelker/python-nest

# Remember to set them in turntouch.service!

client_id = os.environ.get("NEST_CLIENT_ID")
client_secret = os.environ.get("NEST_CLIENT_SECRET")


class NestController(BaseController):

def init(self):
def init(self, *args, **kwargs):
if not client_secret or not client_id:
self.log("Nest Developer Account required: see https://console.developers.nest.com/developer/new", logging.ERROR)
self.log("Update this file with client_id and client_secret to connect to Nest", logging.ERROR)
Expand Down Expand Up @@ -67,7 +69,24 @@ def perform(self, action):
device.target = device.target + (1 if action['direction'] == 'up' else -1)
self.log("Set temperature to {}".format(device.target))

@classmethod
def help(cls):
return """
Hue Module - Control Nest Thermostats
Usage:
north_press:
type: nest
action: set_temp
structure: Home # Run setup to see names. Optional if only one structure
device: Kitchen # Run setup to see names. Optional if only one device
temperature: 21 # In ºF or ºC, as your thermostat is set
if __name__ == '__main__':
n = NestController(print=True)
n.print_all()
north_press:
type: nest
action: adjust_temp
structure: Home # Run setup to see names. Optional if only one structure
device: Kitchen # Run setup to see names. Optional if only one device
direction: up # or down
"""
101 changes: 72 additions & 29 deletions monitor.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from apscheduler.schedulers.background import BackgroundScheduler
import argparse
import datetime
import importlib
import gatt
import logging
import os
import yaml
import subprocess
import sys

from controllers.base_controller import BaseController

Expand All @@ -17,7 +18,7 @@

logger = logging.getLogger('monitor')

manager = gatt.DeviceManager(adapter_name='hci0')
# manager = gatt.DeviceManager(adapter_name='hci0')

print_log = False

Expand Down Expand Up @@ -137,32 +138,74 @@ def perform(self, direction, action):
else:
log("No controller found for action {}".format(action['type']))


def get_controllers():
res = []
for f in os.listdir("./controllers"):
if 'controller' in f and f != 'base_controller.py':
m = importlib.import_module('controllers.{}'.format(os.path.splitext(f)[0]))
controller = [c for _, c in m.__dict__.items() if (type(c) == type) and c != BaseController and issubclass(c, BaseController)][0]
res.append((f.split('_')[0], controller))
return res


if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == 'print':
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--print", help="print output, rather than logging",
action="store_true")
parser.add_argument("-l", "--list", help="list available controllers",
action="store_true")
parser.add_argument("-c", "--controller", type=str, help="show help for a controller")
parser.add_argument("-s", "--setup", type=str, help="run setup for a controller")
args = parser.parse_args()

if args.print:
print_log = True
print("Printing not logging!")

try:
with open('config.yml') as f:
config = yaml.load(f)
log('Config loaded: {}'.format(config))
except Exception as e:
config = []
log("Error loading config: {}".format(e))
for c in config:
controllers = {}
for t in set([b['type'] for _, b in c['buttons'].items()]):
log("Found command of type {}, trying to load controller".format(t))
m = importlib.import_module('controllers.{}_controller'.format(t))
controller = [c for _, c in m.__dict__.items() if (type(c) == type) and c != BaseController and issubclass(c, BaseController)][0]
controllers[t] = controller(print=print_log)
device = TurnTouch(
mac_address=c['mac'],
manager=manager,
buttons=c['buttons'],
name=c['name'],
controllers=controllers
)
log("Trying to connect to {} at {}...".format(c['name'], c['mac']))
device.connect()
manager.run()

if args.list:
print("Controllers:")
for controller in get_controllers():
print(" - [{}]: {}".format(controller[0], controller[1].get_class_name()))
elif args.controller:
controllers = get_controllers()
try:
controller = [m for m in controllers if m[0] == args.controller][0]
print(controller[1].help())
except:
print("A controller called {} was not found. The available controllers are:".format(args.controller))
for controller in controllers:
print(" - {}".format(controller[0]))
elif args.setup:
controllers = get_controllers()
try:
controller = [m for m in controllers if m[0] == args.setup][0]
except:
print("A controller called {} was not found. The available controllers are:".format(args.controller))
for controller in controllers:
print(" - {}".format(controller[0]))
controller[1](print=True).print_all()
else:
try:
with open('config.yml') as f:
config = yaml.load(f)
log('Config loaded: {}'.format(config))
except Exception as e:
config = []
log("Error loading config: {}".format(e))
for c in config:
controllers = {}
for t in set([b['type'] for _, b in c['buttons'].items()]):
log("Found command of type {}, trying to load controller".format(t))
m = importlib.import_module('controllers.{}_controller'.format(t))
controller = [c for _, c in m.__dict__.items() if (type(c) == type) and c != BaseController and issubclass(c, BaseController)][0]
controllers[t] = controller(print=print_log)
device = TurnTouch(
mac_address=c['mac'],
manager=manager,
buttons=c['buttons'],
name=c['name'],
controllers=controllers
)
log("Trying to connect to {} at {}...".format(c['name'], c['mac']))
device.connect()
manager.run()
2 changes: 2 additions & 0 deletions turntouch.service
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ After=network.target
[Service]
Type=simple
User=root
Environment="NEST_CLIENT_ID="
Environment="NEST_CLIENT_SECRET="
WorkingDirectory=/home/pi/raspi-turntouch
ExecStart=/usr/bin/python3 /home/pi/raspi-turntouch/monitor.py

Expand Down

0 comments on commit 9f71941

Please sign in to comment.