From 5df8f0e63ec326cdd60f72f442a15cadf69b4ce6 Mon Sep 17 00:00:00 2001 From: Ben Kools Date: Thu, 29 Mar 2018 11:37:06 -0500 Subject: [PATCH 1/3] IP connection --- 8x8test.py | 51 +++++++++++++++++++++ pyblackbird/__init__.py | 61 +++++++++++++------------ pyblackbird/devices/__init__.py | 3 ++ pyblackbird/devices/serial_blackbird.py | 46 +++++++++++++++++++ test.py | 15 ++++++ 5 files changed, 146 insertions(+), 30 deletions(-) create mode 100755 8x8test.py create mode 100644 pyblackbird/devices/__init__.py create mode 100644 pyblackbird/devices/serial_blackbird.py create mode 100644 test.py diff --git a/8x8test.py b/8x8test.py new file mode 100755 index 0000000..e5eea2e --- /dev/null +++ b/8x8test.py @@ -0,0 +1,51 @@ +from pyblackbird import get_blackbird + +blackbird = get_blackbird('/home/localadmin/dev/ttyV0') +# Valid zones are 11-16 for main monoprice amplifier +zone_status = blackbird.zone_status(1) +#system_power_status = blackbird.system_power_status() +#blackbird.set_system_power(4) +blackbird.set_zone_source(2,2) +blackbird.set_zone_power(1,0) + +# Print system power status +#print('System Power = {}'.format(system_power_status)) + +# Print zone status +print('Zone Number = {}'.format(zone_status.zone)) +print('AV Source = {}'.format(zone_status.av)) +print('IR Source = {}'.format(zone_status.ir)) +print('Zone Power is {}'.format('On' if zone_status.power else 'Off')) +#print('Mute is {}'.format('On' if zone_status.mute else 'Off')) +#print('Public Anouncement Mode is {}'.format('On' if zone_status.pa else 'Off')) +#print('Do Not Disturb Mode is {}'.format('On' if zone_status.do_not_disturb else 'Off')) +#print('Volume = {}'.format(zone_status.volume)) +#print('Treble = {}'.format(zone_status.treble)) +#print('Bass = {}'.format(zone_status.bass)) +#print('Balance = {}'.format(zone_status.balance)) +#print('Source = {}'.format(zone_status.source)) +#print('Keypad is {}'.format('connected' if zone_status.keypad else 'disconnected')) +print('System Lock is {}'.format('On' if blackbird.lock_status() else 'Off')) +# Turn off zone #11 +#monoprice.set_power(11, False) + +# Mute zone #12 +#monoprice.set_mute(12, True) + +# Set volume for zone #13 +#monoprice.set_volume(13, 15) + +# Set source 1 for zone #14 +#monoprice.set_source(14, 1) + +# Set treble for zone #15 +#monoprice.set_treble(15, 10) + +# Set bass for zone #16 +#monoprice.set_bass(16, 7) + +# Set balance for zone #11 +#monoprice.set_balance(11, 3) + +# Restore zone #11 to it's original state +#monoprice.restore_zone(zone_status) diff --git a/pyblackbird/__init__.py b/pyblackbird/__init__.py index 434265f..e1f8ec1 100644 --- a/pyblackbird/__init__.py +++ b/pyblackbird/__init__.py @@ -3,6 +3,7 @@ import logging import re import serial +import socket from functools import wraps from serial_asyncio import create_serial_connection from threading import RLock @@ -14,6 +15,8 @@ LEN_EOL = len(EOL) TIMEOUT = 2 # Number of seconds before serial operation timeout SYSTEM_POWER = None +PORT = 4001 +SOCKET_RECV = 2048 class ZoneStatus(object): def __init__(self, @@ -53,6 +56,7 @@ def from_string(cls, string: str): else: return False + class Blackbird(object): """ Monoprice blackbird amplifier interface @@ -134,7 +138,7 @@ def _format_lock_status() -> bytes: return '%9961.\r'.encode() -def get_blackbird(port_url): +def get_blackbird(host): """ Return synchronous version of Blackbird interface :param port_url: serial port, i.e. '/dev/ttyUSB0' @@ -150,44 +154,41 @@ def wrapper(*args, **kwargs): return wrapper class BlackbirdSync(Blackbird): - def __init__(self, port_url): - self._port = serial.serial_for_url(port_url, do_not_open=True) - self._port.baudrate = 9600 - self._port.stopbits = serial.STOPBITS_ONE - self._port.bytesize = serial.EIGHTBITS - self._port.parity = serial.PARITY_NONE - self._port.timeout = TIMEOUT - self._port.write_timeout = TIMEOUT - self._port.open() + def __init__(self, host): + """ + Initialize the socket client. + """ + self.host = host + self.port = PORT + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.settimeout(TIMEOUT) + self.socket.connect((self.host, self.port)) + + # Clear login message + self.socket.recv(SOCKET_RECV) def _process_request(self, request: bytes, skip=0): """ + Send data to socket :param request: request that is sent to the blackbird :param skip: number of bytes to skip for end of transmission decoding :return: ascii string returned by blackbird """ _LOGGER.debug('Sending "%s"', request) - # clear - self._port.reset_output_buffer() - self._port.reset_input_buffer() - # send - self._port.write(request) - self._port.flush() - # receive - result = bytearray() + + self.socket.send(request) + + response = "" + while True: - c = self._port.read(1) - if c is None: - break - if not c: - raise serial.SerialTimeoutException( - 'Connection timed out! Last received bytes {}'.format([hex(a) for a in result])) - result += c - if len(result) > skip and result [-LEN_EOL:] == EOL: + + data = self.socket.recv(SOCKET_RECV) + response += data.decode('ascii') + + if EOL in data and len(response) > skip: break - ret = bytes(result) - _LOGGER.debug('Received "%s"', ret) - return ret.decode('ascii') + + return response @synchronized def zone_status(self, zone: int): @@ -224,7 +225,7 @@ def lock_status(self): # Report system locking status return LockStatus.from_string(self._process_request(_format_lock_status())) - return BlackbirdSync(port_url) + return BlackbirdSync(host) @asyncio.coroutine diff --git a/pyblackbird/devices/__init__.py b/pyblackbird/devices/__init__.py new file mode 100644 index 0000000..e2854f0 --- /dev/null +++ b/pyblackbird/devices/__init__.py @@ -0,0 +1,3 @@ +from .serial_blackbird import SerialBlackbird + +__all__ = ['SerialBlackbird'] \ No newline at end of file diff --git a/pyblackbird/devices/serial_blackbird.py b/pyblackbird/devices/serial_blackbird.py new file mode 100644 index 0000000..9a251bf --- /dev/null +++ b/pyblackbird/devices/serial_blackbird.py @@ -0,0 +1,46 @@ +import serial + +TIMEOUT = 2 # Number of seconds before serial operation timeout + +class SerialBlackbird(object): + """ + Serial interface for Monoprice Blackbird + """ + def __init__(self, port_url): + self._port = serial.serial_for_url(port_url, do_not_open=True) + self._port.baudrate = 9600 + self._port.stopbits = serial.STOPBITS_ONE + self._port.bytesize = serial.EIGHTBITS + self._port.parity = serial.PARITY_NONE + self._port.timeout = TIMEOUT + self._port.write_timeout = TIMEOUT + self._port.open() + + def _process_request(self, request: bytes, skip=0): + """ + :param request: request that is sent to the blackbird + :param skip: number of bytes to skip for end of transmission decoding + :return: ascii string returned by blackbird + """ + _LOGGER.debug('Sending "%s"', request) + # clear + self._port.reset_output_buffer() + self._port.reset_input_buffer() + # send + self._port.write(request) + self._port.flush() + # receive + result = bytearray() + while True: + c = self._port.read(1) + if c is None: + break + if not c: + raise serial.SerialTimeoutException( + 'Connection timed out! Last received bytes {}'.format([hex(a) for a in result])) + result += c + if len(result) > skip and result [-LEN_EOL:] == EOL: + break + ret = bytes(result) + _LOGGER.debug('Received "%s"', ret) + return ret.decode('ascii') \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..a9f0d30 --- /dev/null +++ b/test.py @@ -0,0 +1,15 @@ +from pyblackbird import get_blackbird +#from pyblackbird.devices import SerialBlackbird + +#serial = SerialBlackbird('/home/localadmin/dev/ttyV0') +host = '172.30.1.7' + +blackbird = get_blackbird(host) + +zone_status = blackbird.zone_status(1) + + +print('Zone Number = {}'.format(zone_status.zone)) +print('AV Source = {}'.format(zone_status.av)) +print('IR Source = {}'.format(zone_status.ir)) +print('Zone Power is {}'.format('On' if zone_status.power else 'Off')) \ No newline at end of file From 10be83085c05272d73b4e7c8af2121640f53345f Mon Sep 17 00:00:00 2001 From: Ben Kools Date: Thu, 29 Mar 2018 11:39:45 -0500 Subject: [PATCH 2/3] cleanup --- pyblackbird/devices/__init__.py | 3 -- pyblackbird/devices/serial_blackbird.py | 46 ------------------------- 2 files changed, 49 deletions(-) delete mode 100644 pyblackbird/devices/__init__.py delete mode 100644 pyblackbird/devices/serial_blackbird.py diff --git a/pyblackbird/devices/__init__.py b/pyblackbird/devices/__init__.py deleted file mode 100644 index e2854f0..0000000 --- a/pyblackbird/devices/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .serial_blackbird import SerialBlackbird - -__all__ = ['SerialBlackbird'] \ No newline at end of file diff --git a/pyblackbird/devices/serial_blackbird.py b/pyblackbird/devices/serial_blackbird.py deleted file mode 100644 index 9a251bf..0000000 --- a/pyblackbird/devices/serial_blackbird.py +++ /dev/null @@ -1,46 +0,0 @@ -import serial - -TIMEOUT = 2 # Number of seconds before serial operation timeout - -class SerialBlackbird(object): - """ - Serial interface for Monoprice Blackbird - """ - def __init__(self, port_url): - self._port = serial.serial_for_url(port_url, do_not_open=True) - self._port.baudrate = 9600 - self._port.stopbits = serial.STOPBITS_ONE - self._port.bytesize = serial.EIGHTBITS - self._port.parity = serial.PARITY_NONE - self._port.timeout = TIMEOUT - self._port.write_timeout = TIMEOUT - self._port.open() - - def _process_request(self, request: bytes, skip=0): - """ - :param request: request that is sent to the blackbird - :param skip: number of bytes to skip for end of transmission decoding - :return: ascii string returned by blackbird - """ - _LOGGER.debug('Sending "%s"', request) - # clear - self._port.reset_output_buffer() - self._port.reset_input_buffer() - # send - self._port.write(request) - self._port.flush() - # receive - result = bytearray() - while True: - c = self._port.read(1) - if c is None: - break - if not c: - raise serial.SerialTimeoutException( - 'Connection timed out! Last received bytes {}'.format([hex(a) for a in result])) - result += c - if len(result) > skip and result [-LEN_EOL:] == EOL: - break - ret = bytes(result) - _LOGGER.debug('Received "%s"', ret) - return ret.decode('ascii') \ No newline at end of file From c763655916b456b96f6c6acba99e7b3e3b0fda7d Mon Sep 17 00:00:00 2001 From: Ben Kools Date: Thu, 29 Mar 2018 14:08:17 -0500 Subject: [PATCH 3/3] Add support for IP connections --- 8x8test.py | 51 ------------------------ README.md | 4 ++ pyblackbird/__init__.py | 87 +++++++++++++++++++++++++++++------------ setup.py | 2 +- test.py | 15 ------- tests/__init__.py | 79 ++++++++++++++++++++++++++++--------- tests/test_blackbird.py | 4 +- 7 files changed, 128 insertions(+), 114 deletions(-) delete mode 100755 8x8test.py delete mode 100644 test.py diff --git a/8x8test.py b/8x8test.py deleted file mode 100755 index e5eea2e..0000000 --- a/8x8test.py +++ /dev/null @@ -1,51 +0,0 @@ -from pyblackbird import get_blackbird - -blackbird = get_blackbird('/home/localadmin/dev/ttyV0') -# Valid zones are 11-16 for main monoprice amplifier -zone_status = blackbird.zone_status(1) -#system_power_status = blackbird.system_power_status() -#blackbird.set_system_power(4) -blackbird.set_zone_source(2,2) -blackbird.set_zone_power(1,0) - -# Print system power status -#print('System Power = {}'.format(system_power_status)) - -# Print zone status -print('Zone Number = {}'.format(zone_status.zone)) -print('AV Source = {}'.format(zone_status.av)) -print('IR Source = {}'.format(zone_status.ir)) -print('Zone Power is {}'.format('On' if zone_status.power else 'Off')) -#print('Mute is {}'.format('On' if zone_status.mute else 'Off')) -#print('Public Anouncement Mode is {}'.format('On' if zone_status.pa else 'Off')) -#print('Do Not Disturb Mode is {}'.format('On' if zone_status.do_not_disturb else 'Off')) -#print('Volume = {}'.format(zone_status.volume)) -#print('Treble = {}'.format(zone_status.treble)) -#print('Bass = {}'.format(zone_status.bass)) -#print('Balance = {}'.format(zone_status.balance)) -#print('Source = {}'.format(zone_status.source)) -#print('Keypad is {}'.format('connected' if zone_status.keypad else 'disconnected')) -print('System Lock is {}'.format('On' if blackbird.lock_status() else 'Off')) -# Turn off zone #11 -#monoprice.set_power(11, False) - -# Mute zone #12 -#monoprice.set_mute(12, True) - -# Set volume for zone #13 -#monoprice.set_volume(13, 15) - -# Set source 1 for zone #14 -#monoprice.set_source(14, 1) - -# Set treble for zone #15 -#monoprice.set_treble(15, 10) - -# Set bass for zone #16 -#monoprice.set_bass(16, 7) - -# Set balance for zone #11 -#monoprice.set_balance(11, 3) - -# Restore zone #11 to it's original state -#monoprice.restore_zone(zone_status) diff --git a/README.md b/README.md index 7699d12..d837c75 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,12 @@ This is for use with [Home-Assistant](http://home-assistant.io) ```python from pyblackbird import get_blackbird +# Connect via serial port blackbird = get_blackbird('/dev/ttyUSB0') +# Connect via IP +blackbird = get_blackbird('192.168.1.50', use_serial=False) + # Print system lock status print('System Lock is {}'.format('On' if blackbird.lock_status() else 'Off')) diff --git a/pyblackbird/__init__.py b/pyblackbird/__init__.py index e1f8ec1..194f69e 100644 --- a/pyblackbird/__init__.py +++ b/pyblackbird/__init__.py @@ -14,7 +14,6 @@ EOL = b'\r' LEN_EOL = len(EOL) TIMEOUT = 2 # Number of seconds before serial operation timeout -SYSTEM_POWER = None PORT = 4001 SOCKET_RECV = 2048 @@ -138,13 +137,14 @@ def _format_lock_status() -> bytes: return '%9961.\r'.encode() -def get_blackbird(host): +def get_blackbird(url, use_serial=True): """ Return synchronous version of Blackbird interface :param port_url: serial port, i.e. '/dev/ttyUSB0' :return: synchronous implementation of Blackbird interface """ lock = RLock() + print(serial) def synchronized(func): @wraps(func) @@ -154,18 +154,29 @@ def wrapper(*args, **kwargs): return wrapper class BlackbirdSync(Blackbird): - def __init__(self, host): + def __init__(self, url): """ - Initialize the socket client. + Initialize the client. """ - self.host = host - self.port = PORT - self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.socket.settimeout(TIMEOUT) - self.socket.connect((self.host, self.port)) - - # Clear login message - self.socket.recv(SOCKET_RECV) + if use_serial: + self._port = serial.serial_for_url(url, do_not_open=True) + self._port.baudrate = 9600 + self._port.stopbits = serial.STOPBITS_ONE + self._port.bytesize = serial.EIGHTBITS + self._port.parity = serial.PARITY_NONE + self._port.timeout = TIMEOUT + self._port.write_timeout = TIMEOUT + self._port.open() + + else: + self.host = url + self.port = PORT + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.settimeout(TIMEOUT) + self.socket.connect((self.host, self.port)) + + # Clear login message + self.socket.recv(SOCKET_RECV) def _process_request(self, request: bytes, skip=0): """ @@ -176,19 +187,43 @@ def _process_request(self, request: bytes, skip=0): """ _LOGGER.debug('Sending "%s"', request) - self.socket.send(request) - - response = "" - - while True: - - data = self.socket.recv(SOCKET_RECV) - response += data.decode('ascii') - - if EOL in data and len(response) > skip: - break - - return response + if use_serial: + # clear + self._port.reset_output_buffer() + self._port.reset_input_buffer() + # send + self._port.write(request) + self._port.flush() + # receive + result = bytearray() + while True: + c = self._port.read(1) + if c is None: + break + if not c: + raise serial.SerialTimeoutException( + 'Connection timed out! Last received bytes {}'.format([hex(a) for a in result])) + result += c + if len(result) > skip and result [-LEN_EOL:] == EOL: + break + ret = bytes(result) + _LOGGER.debug('Received "%s"', ret) + return ret.decode('ascii') + + else: + self.socket.send(request) + + response = '' + + while True: + + data = self.socket.recv(SOCKET_RECV) + response += data.decode('ascii') + + if EOL in data and len(response) > skip: + break + + return response @synchronized def zone_status(self, zone: int): @@ -225,7 +260,7 @@ def lock_status(self): # Report system locking status return LockStatus.from_string(self._process_request(_format_lock_status())) - return BlackbirdSync(host) + return BlackbirdSync(url) @asyncio.coroutine diff --git a/setup.py b/setup.py index 2fa0189..f54bbbf 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import os import sys -VERSION = '0.4' +VERSION = '0.5' try: from setuptools import setup diff --git a/test.py b/test.py deleted file mode 100644 index a9f0d30..0000000 --- a/test.py +++ /dev/null @@ -1,15 +0,0 @@ -from pyblackbird import get_blackbird -#from pyblackbird.devices import SerialBlackbird - -#serial = SerialBlackbird('/home/localadmin/dev/ttyV0') -host = '172.30.1.7' - -blackbird = get_blackbird(host) - -zone_status = blackbird.zone_status(1) - - -print('Zone Number = {}'.format(zone_status.zone)) -print('AV Source = {}'.format(zone_status.av)) -print('IR Source = {}'.format(zone_status.ir)) -print('Zone Power is {}'.format('On' if zone_status.power else 'Off')) \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py index 81c207c..a300beb 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,25 +1,66 @@ import threading import os import pty +import socket def create_dummy_port(responses): - def listener(port): - # continuously listen to commands on the master device - while 1: - res = b'' - while not res.endswith(b"\r"): - # keep reading one byte at a time until we have a full line - res += os.read(port, 1) - print("command: %s" % res) - - # write back the response - if res in responses: - resp = responses[res] - del responses[res] - os.write(port, resp) - - master, slave = pty.openpty() - thread = threading.Thread(target=listener, args=[master], daemon=True) - thread.start() - return os.ttyname(slave) \ No newline at end of file + def listener(port): + # continuously listen to commands on the master device + while 1: + res = b'' + while not res.endswith(b"\r"): + # keep reading one byte at a time until we have a full line + res += os.read(port, 1) + print("command: %s" % res) + + # write back the response + if res in responses: + resp = responses[res] + del responses[res] + os.write(port, resp) + + master, slave = pty.openpty() + thread = threading.Thread(target=listener, args=[master], daemon=True) + thread.start() + return os.ttyname(slave) + +def create_dummy_socket(responses): + HOST = '127.0.0.1' + PORT = 4001 + + + + def listener(): + + conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + conn.bind((HOST, PORT)) + conn.listen(10) + + while 1: + conn, addr = s.accept() + + conn.send('Please Input Your Command :\r') + + while True: + # Receive data + res = conn.recv(1024) + print("command: %s" % res) + + # write back the response + if res in responses: + resp = responses[res] + del responses[res] + conn.sendall(resp) + + if not res: + break + + #while 1: + + # start_new_thread(listener ,(conn,)) + + thread = threading.Thread(target=listener, daemon=True) + thread.start() + + return HOST \ No newline at end of file diff --git a/tests/test_blackbird.py b/tests/test_blackbird.py index 0fd47fe..ce13558 100644 --- a/tests/test_blackbird.py +++ b/tests/test_blackbird.py @@ -3,7 +3,7 @@ import serial from pyblackbird import (get_blackbird, get_async_blackbird, ZoneStatus) -from tests import create_dummy_port +from tests import (create_dummy_port, create_dummy_socket) import asyncio @@ -108,4 +108,4 @@ def test_timeout(self): self.blackbird.set_zone_source(6, 6) if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main()