-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' of github.com:CarlFK/voctomix-outcasts
- Loading branch information
Showing
3 changed files
with
234 additions
and
2 deletions.
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 |
---|---|---|
@@ -0,0 +1,8 @@ | ||
[server] | ||
host=localhost | ||
|
||
[light] | ||
driver=rpi | ||
cam=cam2 | ||
gpio_red=11 | ||
gpios=11,12,13 |
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
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,226 @@ | ||
#!/usr/bin/env python3 | ||
# Based upon: | ||
# https://github.com/voc/voctomix/tree/voctopanel/example-scripts/voctolight | ||
|
||
from enum import Enum | ||
import argparse | ||
import asyncio | ||
import configparser | ||
import json | ||
import os.path | ||
|
||
|
||
class Config(configparser.ConfigParser, object): | ||
config_files = [ | ||
os.path.join(os.path.dirname(os.path.realpath(__file__)), | ||
'default-config.ini'), | ||
os.path.join(os.path.dirname(os.path.realpath(__file__)), | ||
'config.ini'), | ||
'/etc/voctomix/voctolight.ini', | ||
'/etc/voctolight.ini', | ||
os.path.expanduser('~/.voctolight.ini'), | ||
] | ||
|
||
def __init__(self, cmd_line_config=None): | ||
super().__init__() | ||
self.cmd_line_config = cmd_line_config | ||
self._read_config() | ||
|
||
def _read_config(self): | ||
self.read(self.config_files) | ||
if self.cmd_line_config: | ||
self.read_file(self.cmd_line_config) | ||
self.cmd_line_config.seek(0) | ||
|
||
def setup_with_server_config(self, server_config): | ||
self.clear() | ||
self._read_config() | ||
self.read_dict(server_config) | ||
|
||
def getlist(self, section, option): | ||
return [x.strip() for x in self.get(section, option).split(',')] | ||
|
||
|
||
class Connection(asyncio.Protocol): | ||
def __init__(self, interpreter): | ||
self.interpreter = interpreter | ||
|
||
def connect(self, host, port=9999): | ||
self.loop = asyncio.get_event_loop() | ||
self.conn = self.loop.create_connection(lambda: self, host, port) | ||
self.loop.run_until_complete(conn.conn) | ||
|
||
def send(self, message): | ||
self.transport.write(message.encode()) | ||
self.transport.write('\n'.encode()) | ||
|
||
def connection_made(self, transport): | ||
self.transport = transport | ||
|
||
def data_received(self, data): | ||
# print('data received: {}'.format(data.decode())) | ||
lines = data.decode().rstrip('\n').split('\n') | ||
for line in lines: | ||
interpreter.handler(line) | ||
|
||
def connection_lost(self, exc): | ||
print('server closed the connection') | ||
asyncio.get_event_loop().stop() | ||
|
||
|
||
# FIXME: Duplicate from videomix.py | ||
class CompositeModes(Enum): | ||
fullscreen = 0 | ||
side_by_side_equal = 1 | ||
side_by_side_preview = 2 | ||
picture_in_picture = 3 | ||
|
||
|
||
class Interpreter(object): | ||
a_or_b = False | ||
primary = False | ||
composite_mode = CompositeModes.fullscreen | ||
|
||
def __init__(self, actor, config, debug=False): | ||
self.config = config | ||
self.actor = actor | ||
self.debug = debug | ||
actor.reset_led() | ||
if self.debug: | ||
print('LED has been reset to off') | ||
|
||
def compute_state(self): | ||
if self.composite_mode == CompositeModes.fullscreen: | ||
return self.primary | ||
else: | ||
return self.a_or_b | ||
|
||
def handler(self, response): | ||
words = response.split() | ||
signal = words[0] | ||
args = words[1:] | ||
try: | ||
handler = getattr(self, 'handle_{}'.format(signal)) | ||
except AttributeError: | ||
print('Ignoring signal', signal) | ||
else: | ||
handler(args) | ||
enable = interpreter.compute_state() | ||
actor.enable_tally(enable) | ||
if self.debug: | ||
print('LED has been set to {}.'.format( | ||
'on' if enable else 'off')) | ||
|
||
def handle_video_status(self, cams): | ||
mycam = self.config.get('light', 'cam') | ||
if mycam in cams: | ||
self.a_or_b = True | ||
else: | ||
self.a_or_b = False | ||
|
||
self.primary = (cams[0] == mycam) | ||
# print ('Is primary?', self.primary) | ||
|
||
def handle_composite_mode(self, mode_list): | ||
mode = mode_list[0] | ||
if mode == 'fullscreen': | ||
self.composite_mode = CompositeModes.fullscreen | ||
elif mode == 'side_by_side_equal': | ||
self.composite_mode = CompositeModes.side_by_side_equal | ||
elif mode == 'side_by_side_preview': | ||
self.composite_mode = CompositeModes.side_by_side_preview | ||
elif mode == 'picture_in_picture': | ||
self.composite_mode = CompositeModes.picture_in_picture | ||
else: | ||
print('Cannot handle', mode, 'of type', type(mode)) | ||
|
||
def handle_server_config(self, args): | ||
server_config_json = ' '.join(args) | ||
server_config = json.loads(server_config_json) | ||
self.config.setup_with_server_config(server_config) | ||
|
||
|
||
class RPiGPIODriver: | ||
def __init__(self, config): | ||
self._import() | ||
self.gpios = config.get('light', 'gpios').split(',') | ||
self.gpio_red = int(config.get('light', 'gpio_red')) | ||
self.reset_led() | ||
|
||
def _import(self): | ||
global GPIO | ||
import RPi.GPIO as GPIO | ||
|
||
def reset_led(self): | ||
GPIO.setmode(GPIO.BOARD) | ||
GPIO.setwarnings(False) | ||
for gpio in self.gpios: | ||
gpio = int(gpio) | ||
GPIO.setup(gpio, GPIO.OUT) | ||
GPIO.output(gpio, GPIO.HIGH) | ||
|
||
def enable_tally(self, enable): | ||
if enable: | ||
GPIO.output(self.gpio_red, GPIO.LOW) | ||
else: | ||
GPIO.output(self.gpio_red, GPIO.HIGH) | ||
|
||
|
||
class DummyDriver: | ||
def __init__(self, config): | ||
pass | ||
|
||
def reset_led(self): | ||
pass | ||
|
||
def enable_tally(self, enable): | ||
pass | ||
|
||
|
||
class SerialDTRDriver: | ||
def __init__(self, config): | ||
self.fn = config.get('light', 'port') | ||
self.fd = None | ||
|
||
def reset_led(self): | ||
if self.fd: | ||
self.fd.close() | ||
self.fd = None | ||
|
||
def enable_tally(self, enable): | ||
if enable: | ||
self.fd = open(self.fn) | ||
else: | ||
self.reset_led() | ||
|
||
|
||
if __name__ == '__main__': | ||
parser = argparse.ArgumentParser( | ||
description='Tallylight controlling daemon for voctomix.') | ||
parser.add_argument( | ||
'--config', type=open, help='Use a specific config file') | ||
parser.add_argument( | ||
'--debug', action='store_true', | ||
help='Show what would be done instead of toggling lights') | ||
args = parser.parse_args() | ||
config = Config(cmd_line_config=args.config) | ||
driver = config.get('light', 'driver') | ||
if driver == 'dummy': | ||
actor = DummyDriver(config) | ||
elif driver == 'rpi': | ||
actor = RPiGPIODriver(config) | ||
elif driver == 'serial': | ||
actor = SerialDTRDriver(config) | ||
else: | ||
raise Exception('Unknown driver: ' + driver) | ||
interpreter = Interpreter(actor, config, debug=args.debug) | ||
conn = Connection(interpreter) | ||
conn.connect(config.get('server', 'host')) | ||
conn.send('get_config') | ||
conn.send('get_composite_mode') | ||
conn.send('get_video') | ||
try: | ||
conn.loop.run_forever() | ||
except KeyboardInterrupt: | ||
print('Quitting (Ctrl-C)') | ||
actor.reset_led() |