From 6d95b7e021efad822a6e8b0d5af5638acf7d7993 Mon Sep 17 00:00:00 2001 From: foxy82 Date: Fri, 24 Jul 2020 15:49:11 +0100 Subject: [PATCH] Create pypi release --- LICENSE.md | 400 ++++++++++++++++++++-------------------- README.md => README | 196 ++++++++++---------- example.py | 88 ++++----- main.py | 218 +++++++++++----------- pyblustream/listener.py | 134 +++++++------- pyblustream/matrix.py | 72 ++++---- pyblustream/protocol.py | 252 ++++++++++++------------- setup.cfg | 4 +- 8 files changed, 682 insertions(+), 682 deletions(-) rename README.md => README (97%) diff --git a/LICENSE.md b/LICENSE.md index 0176cc9..035fac7 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,201 +1,201 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2020 foxy82 - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 foxy82 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and limitations under the License. \ No newline at end of file diff --git a/README.md b/README similarity index 97% rename from README.md rename to README index c899d45..57f4150 100644 --- a/README.md +++ b/README @@ -1,99 +1,99 @@ -pyblustream -=========== - -pyblustream is a Python library to connect to an ELAN or Blustream HDBaseT Matrix. You can see the current mapping of inputs to outputs as well as -request to change the input that an output is using. - -It is primarily being developed with the intent of supporting [home-assistant](https://github.com/home-assistant/home-assistant) - - -Installation ------------- - - # Installing from PyPI - $ pip install pyblustream - -Using on the command line as a script -=========================== - - main.py -h [-p -s -o -i -a -l] - -h hostname or ip Hostname or IP of the matrix - required - -p port Port of the matrix to connect to - defaults to 23 - -o output_id -i input_id Set output ID to use input ID - specified as an int e.g. -i 2 -o 4 - both must be specified - -s output_id Display the input for this output ID must be specified as a string zero starting e.g. 02 - -a Display the input for all outputs - -l Continue running and listen for source changes - -Log out the input used on a specific output and exit - - python3 main.py -h 127.0.0.1 -s 02 - -Log out all input/output mappings and exit - - python3 main.py -h 127.0.0.1 -a - -Change display id 2 to source 3 and exit - - python3 main.py -h 127.0.0.1 -o 2 -i 3 - -Run forever logging status changes - - python3 main.py -h 127.0.0.1 -l - -Change a source then run forever logging status changes - - python3 main.py -h 127.0.0.1 -o 2 -i 3 -l - - -Using in an application -======================= - - import asyncio - from pyblustream.listener import SourceChangeListener - from pyblustream.matrix import Matrix - - class MyListener(SourceChangeListener): - - def source_changed(self, output_id, input_id): - # Your code to run when the source changes - pass - - def connected(self): - # Your code to run on a successful connection to the matrix - pass - - def disconnected(self): - # Your code to run when disconnected from the matrix - # Note: the library will try to reconnect so you don't need to - pass - - # Set to your details - ip = "127.0.0.1" - port = 23 - # Use an asyncio event loop - you will need to make sure this runs - my_loop = asyncio.get_event_loop() - # Create a matrix - matrix = Matrix(ip, port, loop=my_loop) - # Register a listenerer so you can handle state changes - matrix.register_listener(MyListener()) - # You always need to connect to the matrix - best to do this after - # adding your listener to avoid missing the inital status that is returned on start up - matrix.connect() - # Programmatically change the source for output 2 to input 3. - matrix.change_source(2, 3) - - all_outputs = matrix.status_of_all_outputs() - input_for_zone_one = matrix.status_of_output("01") - # Force the matrix to refresh it's status - # This is done automatically on startup/reconnect so you shouldn't need to do this - matrix.update_status() - - -TODO -======================= - -* Get names of inputs and outputs from the matrix -* Implement turn on / turn off -* Make the input for change_source and status_of_output to be consistent +pyblustream +=========== + +pyblustream is a Python library to connect to an ELAN or Blustream HDBaseT Matrix. You can see the current mapping of inputs to outputs as well as +request to change the input that an output is using. + +It is primarily being developed with the intent of supporting [home-assistant](https://github.com/home-assistant/home-assistant) + + +Installation +------------ + + # Installing from PyPI + $ pip install pyblustream + +Using on the command line as a script +=========================== + + main.py -h [-p -s -o -i -a -l] + -h hostname or ip Hostname or IP of the matrix - required + -p port Port of the matrix to connect to - defaults to 23 + -o output_id -i input_id Set output ID to use input ID - specified as an int e.g. -i 2 -o 4 + both must be specified + -s output_id Display the input for this output ID must be specified as a string zero starting e.g. 02 + -a Display the input for all outputs + -l Continue running and listen for source changes + +Log out the input used on a specific output and exit + + python3 main.py -h 127.0.0.1 -s 02 + +Log out all input/output mappings and exit + + python3 main.py -h 127.0.0.1 -a + +Change display id 2 to source 3 and exit + + python3 main.py -h 127.0.0.1 -o 2 -i 3 + +Run forever logging status changes + + python3 main.py -h 127.0.0.1 -l + +Change a source then run forever logging status changes + + python3 main.py -h 127.0.0.1 -o 2 -i 3 -l + + +Using in an application +======================= + + import asyncio + from pyblustream.listener import SourceChangeListener + from pyblustream.matrix import Matrix + + class MyListener(SourceChangeListener): + + def source_changed(self, output_id, input_id): + # Your code to run when the source changes + pass + + def connected(self): + # Your code to run on a successful connection to the matrix + pass + + def disconnected(self): + # Your code to run when disconnected from the matrix + # Note: the library will try to reconnect so you don't need to + pass + + # Set to your details + ip = "127.0.0.1" + port = 23 + # Use an asyncio event loop - you will need to make sure this runs + my_loop = asyncio.get_event_loop() + # Create a matrix + matrix = Matrix(ip, port, loop=my_loop) + # Register a listenerer so you can handle state changes + matrix.register_listener(MyListener()) + # You always need to connect to the matrix - best to do this after + # adding your listener to avoid missing the inital status that is returned on start up + matrix.connect() + # Programmatically change the source for output 2 to input 3. + matrix.change_source(2, 3) + + all_outputs = matrix.status_of_all_outputs() + input_for_zone_one = matrix.status_of_output("01") + # Force the matrix to refresh it's status + # This is done automatically on startup/reconnect so you shouldn't need to do this + matrix.update_status() + + +TODO +======================= + +* Get names of inputs and outputs from the matrix +* Implement turn on / turn off +* Make the input for change_source and status_of_output to be consistent \ No newline at end of file diff --git a/example.py b/example.py index 11862a7..8500523 100644 --- a/example.py +++ b/example.py @@ -1,44 +1,44 @@ -import asyncio - -from pyblustream.listener import SourceChangeListener -from pyblustream.matrix import Matrix - - -class MyListener(SourceChangeListener): - - def source_changed(self, output_id, input_id): - # Your code to run when the source changes - pass - - def connected(self): - # Your code to run on a successful connection to the matrix - pass - - def disconnected(self): - # Your code to run when disconnected from the matrix - # Note: the library will try to reconnect so you don't need to - pass - - -# Set to your details -ip = "127.0.0.1" -port = 23 -# Use an asyncio event loop - you will need to make sure this runs -my_loop = asyncio.get_event_loop() -# Create a matrix -matrix = Matrix(ip, port, loop=my_loop) -# Register a listenerer so you can handle state changes -matrix.register_listener(MyListener()) -# You always need to connect to the matrix - best to do this after -# adding your listener to avoid missing the inital status that is returned on start up -matrix.connect() - -# Programmatically change the source for output 2 to input 3. -matrix.change_source(2, 3) - -all_outputs = matrix.status_of_all_outputs() -input_for_zone_one = matrix.status_of_output("01") -# Force the matrix to refresh it's status -# This is done automatically on startup/reconnect so you shouldn't need to do this -matrix.update_status() -my_loop.run_forever() +import asyncio + +from pyblustream.listener import SourceChangeListener +from pyblustream.matrix import Matrix + + +class MyListener(SourceChangeListener): + + def source_changed(self, output_id, input_id): + # Your code to run when the source changes + pass + + def connected(self): + # Your code to run on a successful connection to the matrix + pass + + def disconnected(self): + # Your code to run when disconnected from the matrix + # Note: the library will try to reconnect so you don't need to + pass + + +# Set to your details +ip = "127.0.0.1" +port = 23 +# Use an asyncio event loop - you will need to make sure this runs +my_loop = asyncio.get_event_loop() +# Create a matrix +matrix = Matrix(ip, port, loop=my_loop) +# Register a listenerer so you can handle state changes +matrix.register_listener(MyListener()) +# You always need to connect to the matrix - best to do this after +# adding your listener to avoid missing the inital status that is returned on start up +matrix.connect() + +# Programmatically change the source for output 2 to input 3. +matrix.change_source(2, 3) + +all_outputs = matrix.status_of_all_outputs() +input_for_zone_one = matrix.status_of_output("01") +# Force the matrix to refresh it's status +# This is done automatically on startup/reconnect so you shouldn't need to do this +matrix.update_status() +my_loop.run_forever() diff --git a/main.py b/main.py index f4797d4..b689272 100644 --- a/main.py +++ b/main.py @@ -1,109 +1,109 @@ -import asyncio -import getopt -import logging -import sys - -from pyblustream.listener import LoggingListener -from pyblustream.matrix import Matrix - - -def print_usage(): - print('main.py -h [-p -s -o -i -a -l]') - print('\t-h hostname or ip\t\t\tHostname or IP of the matrix - required') - print('\t-p port\t\t\t\t\t\tPort of the matrix to connect to - defaults to 23') - print('\t-o output_id -i input_id\tSet output ID to use input ID - both must be specified') - print('\t-s output_id\t\t\t\tDisplay the input for this output ID must be specified as a string e.g. 02') - print('\t-a\t\t\t\t\t\t\tDisplay the input for all outputs') - print('\t-l\t\t\t\t\t\t\tContinue running and listen for source changes') - - -def main(argv): - ip = None - port = 23 - input_id = None - output_id = None - status_output_id = None - get_statuses = False - listen = False - try: - opts, args = getopt.getopt(argv, "h:p:i:o:s:al", ["ip=", "port="]) - except getopt.GetoptError: - print_usage() - sys.exit(2) - for opt, arg in opts: - if opt in ("-h", "--ip"): - ip = arg - elif opt in ("-p", "--port"): - port = arg - elif opt in ("-i", "--input"): - input_id = int(arg) - elif opt in ("-o", "--output"): - output_id = int(arg) - elif opt in ("-s", "--status"): - status_output_id = arg - elif opt in ("-a", "--statuses"): - get_statuses = True - elif opt in ("-l", "--listen"): - listen = True - if ip is None: - print_usage() - sys.exit(2) - elif input_id is not None and output_id is None: - print("If you set input_id you must specify an output_id") - sys.exit(2) - elif output_id is not None and input_id is None: - print("If you set output_id you must specify an input_id") - sys.exit(2) - - logging.info("starting up..") - - my_loop = asyncio.get_event_loop() - - matrix = Matrix(ip, port, loop=my_loop) - if listen: - matrix.register_listener(LoggingListener()) - matrix.connect() - - delay = 1 - if output_id is not None and input_id is not None: - my_loop.create_task(change_source(delay, output_id, input_id, matrix)) - delay += 1 - - if status_output_id is not None: - my_loop.create_task(status(delay, status_output_id, matrix)) - delay += 1 - - if get_statuses: - my_loop.create_task(statuses(delay, matrix)) - delay += 1 - - if listen: - my_loop.run_forever() - else: - my_loop.run_until_complete(done(delay)) - - -async def done(delay): - await asyncio.sleep(delay) - - -async def change_source(delay, output_id, input_id, matrix): - await asyncio.sleep(delay) - matrix.change_source(output_id, input_id) - - -async def status(delay, output_id, matrix): - await asyncio.sleep(delay) - current_status = matrix.status_of_output(output_id) - logging.info(current_status) - - -async def statuses(delay, matrix): - await asyncio.sleep(delay) - current_statuses = matrix.status_of_all_outputs() - logging.info(current_statuses) - - -if __name__ == '__main__': - logging.basicConfig(level=logging.INFO) - main(sys.argv[1:]) +import asyncio +import getopt +import logging +import sys + +from pyblustream.listener import LoggingListener +from pyblustream.matrix import Matrix + + +def print_usage(): + print('main.py -h [-p -s -o -i -a -l]') + print('\t-h hostname or ip\t\t\tHostname or IP of the matrix - required') + print('\t-p port\t\t\t\t\t\tPort of the matrix to connect to - defaults to 23') + print('\t-o output_id -i input_id\tSet output ID to use input ID - both must be specified') + print('\t-s output_id\t\t\t\tDisplay the input for this output ID must be specified as a string e.g. 02') + print('\t-a\t\t\t\t\t\t\tDisplay the input for all outputs') + print('\t-l\t\t\t\t\t\t\tContinue running and listen for source changes') + + +def main(argv): + ip = None + port = 23 + input_id = None + output_id = None + status_output_id = None + get_statuses = False + listen = False + try: + opts, args = getopt.getopt(argv, "h:p:i:o:s:al", ["ip=", "port="]) + except getopt.GetoptError: + print_usage() + sys.exit(2) + for opt, arg in opts: + if opt in ("-h", "--ip"): + ip = arg + elif opt in ("-p", "--port"): + port = arg + elif opt in ("-i", "--input"): + input_id = int(arg) + elif opt in ("-o", "--output"): + output_id = int(arg) + elif opt in ("-s", "--status"): + status_output_id = arg + elif opt in ("-a", "--statuses"): + get_statuses = True + elif opt in ("-l", "--listen"): + listen = True + if ip is None: + print_usage() + sys.exit(2) + elif input_id is not None and output_id is None: + print("If you set input_id you must specify an output_id") + sys.exit(2) + elif output_id is not None and input_id is None: + print("If you set output_id you must specify an input_id") + sys.exit(2) + + logging.info("starting up..") + + my_loop = asyncio.get_event_loop() + + matrix = Matrix(ip, port, loop=my_loop) + if listen: + matrix.register_listener(LoggingListener()) + matrix.connect() + + delay = 1 + if output_id is not None and input_id is not None: + my_loop.create_task(change_source(delay, output_id, input_id, matrix)) + delay += 1 + + if status_output_id is not None: + my_loop.create_task(status(delay, status_output_id, matrix)) + delay += 1 + + if get_statuses: + my_loop.create_task(statuses(delay, matrix)) + delay += 1 + + if listen: + my_loop.run_forever() + else: + my_loop.run_until_complete(done(delay)) + + +async def done(delay): + await asyncio.sleep(delay) + + +async def change_source(delay, output_id, input_id, matrix): + await asyncio.sleep(delay) + matrix.change_source(output_id, input_id) + + +async def status(delay, output_id, matrix): + await asyncio.sleep(delay) + current_status = matrix.status_of_output(output_id) + logging.info(current_status) + + +async def statuses(delay, matrix): + await asyncio.sleep(delay) + current_statuses = matrix.status_of_all_outputs() + logging.info(current_statuses) + + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + main(sys.argv[1:]) diff --git a/pyblustream/listener.py b/pyblustream/listener.py index 0f2ed68..64c9778 100644 --- a/pyblustream/listener.py +++ b/pyblustream/listener.py @@ -1,68 +1,68 @@ -from abc import ABC, abstractmethod -from typing import List, Any -import logging - - -class SourceChangeListener(ABC): - - @abstractmethod - def source_changed(self, output_id, input_id): - pass - - @abstractmethod - def connected(self): - pass - - @abstractmethod - def disconnected(self): - pass - - -class MultiplexingListener(SourceChangeListener): - - _listeners: List[SourceChangeListener] - - def __init__(self): - self._listeners = [] - - def source_changed(self, output_id, input_id): - for listener in self._listeners: - listener.source_changed(output_id, input_id) - - def connected(self): - for listener in self._listeners: - listener.connected() - - def disconnected(self): - for listener in self._listeners: - listener.disconnected() - - def register_listener(self, listener): - self._listeners.append(listener) - - def unregister_listener(self, listener): - self._listeners.remove(listener) - - -class LoggingListener(SourceChangeListener): - - def connected(self): - logging.info(f"Connected") - - def disconnected(self): - logging.info(f"Disconnected") - - def source_changed(self, output_id, input_id): - logging.info(f"{output_id} changed to input: {input_id}") - - -class PrintingListener(SourceChangeListener): - - def connected(self): - print("Connected") - - def disconnected(self): - print("Disconnected") - - def source_changed(self, output_id, input_id): +from abc import ABC, abstractmethod +from typing import List, Any +import logging + + +class SourceChangeListener(ABC): + + @abstractmethod + def source_changed(self, output_id, input_id): + pass + + @abstractmethod + def connected(self): + pass + + @abstractmethod + def disconnected(self): + pass + + +class MultiplexingListener(SourceChangeListener): + + _listeners: List[SourceChangeListener] + + def __init__(self): + self._listeners = [] + + def source_changed(self, output_id, input_id): + for listener in self._listeners: + listener.source_changed(output_id, input_id) + + def connected(self): + for listener in self._listeners: + listener.connected() + + def disconnected(self): + for listener in self._listeners: + listener.disconnected() + + def register_listener(self, listener): + self._listeners.append(listener) + + def unregister_listener(self, listener): + self._listeners.remove(listener) + + +class LoggingListener(SourceChangeListener): + + def connected(self): + logging.info(f"Connected") + + def disconnected(self): + logging.info(f"Disconnected") + + def source_changed(self, output_id, input_id): + logging.info(f"{output_id} changed to input: {input_id}") + + +class PrintingListener(SourceChangeListener): + + def connected(self): + print("Connected") + + def disconnected(self): + print("Disconnected") + + def source_changed(self, output_id, input_id): print(f"{output_id} changed to input: {input_id}") \ No newline at end of file diff --git a/pyblustream/matrix.py b/pyblustream/matrix.py index e9118ef..437f713 100644 --- a/pyblustream/matrix.py +++ b/pyblustream/matrix.py @@ -1,36 +1,36 @@ -from pyblustream.listener import MultiplexingListener -from pyblustream.protocol import MatrixProtocol - - -class Matrix: - - def __init__(self, hostname, port, loop=None): - self._multiplex_callback = MultiplexingListener() - self._protocol = MatrixProtocol(hostname, port, self._multiplex_callback, loop=loop) - - def connect(self): - self._protocol.connect() - - def change_source(self, input_id, output_id): - self._protocol.send_change_source(input_id, output_id) - - def update_status(self): - self._protocol.send_status_message() - - def status_of_output(self, output_id): - return self._protocol.get_status_of_output(output_id) - - def status_of_all_outputs(self): - return self._protocol.get_status_of_all_outputs() - - def turn_on(self): - self._protocol.send_turn_on_message() - - def turn_off(self): - self._protocol.send_turn_off_message() - - def register_listener(self, listener): - self._multiplex_callback.register_listener(listener) - - def unregister_listener(self, listener): - self._multiplex_callback.unregister_listener(listener) +from pyblustream.listener import MultiplexingListener +from pyblustream.protocol import MatrixProtocol + + +class Matrix: + + def __init__(self, hostname, port, loop=None): + self._multiplex_callback = MultiplexingListener() + self._protocol = MatrixProtocol(hostname, port, self._multiplex_callback, loop=loop) + + def connect(self): + self._protocol.connect() + + def change_source(self, input_id, output_id): + self._protocol.send_change_source(input_id, output_id) + + def update_status(self): + self._protocol.send_status_message() + + def status_of_output(self, output_id): + return self._protocol.get_status_of_output(output_id) + + def status_of_all_outputs(self): + return self._protocol.get_status_of_all_outputs() + + def turn_on(self): + self._protocol.send_turn_on_message() + + def turn_off(self): + self._protocol.send_turn_off_message() + + def register_listener(self, listener): + self._multiplex_callback.register_listener(listener) + + def unregister_listener(self, listener): + self._multiplex_callback.unregister_listener(listener) diff --git a/pyblustream/protocol.py b/pyblustream/protocol.py index 93b862a..6784d39 100644 --- a/pyblustream/protocol.py +++ b/pyblustream/protocol.py @@ -1,126 +1,126 @@ -import asyncio -import logging -import re - -from pyblustream.listener import SourceChangeListener - -SUCCESS_CHANGE = re.compile('.*SUCCESS.*output\\s*([0-9]+)\\sconnect from input\\s*([0-9]+).*') -OUTPUT_CHANGE = re.compile('.*OUT\\s*([0-9]+)\\s*FR\\s*([0-9]+).*', re.IGNORECASE) -STATUS_LINE = re.compile('([0-9][0-9])\\s+([0-9][0-9])[^:].*') - - -class MatrixProtocol(asyncio.Protocol): - - _source_change_callback: SourceChangeListener - - def __init__(self, hostname, port, callback: SourceChangeListener, loop=None, heartbeat_time=5, reconnect_time=10): - self._logger = logging.getLogger(__name__) - self._heartbeat_time = heartbeat_time - self._reconnect_time = reconnect_time - self._hostname = hostname - self._port = port - self._source_change_callback = callback - self._loop = loop - if self._loop is None: - self._loop = asyncio.get_event_loop() - - self._connected = False - self._transport = None - self.peer_name = None - self._received_message = "" - self._output_to_input_map = {} - self._heartbeat_task = None - - def connect(self): - connection_task = self._loop.create_connection(lambda: self, host=self._hostname, port=self._port) - self._loop.create_task(connection_task) - - def connection_made(self, transport): - self._connected = True - self._transport = transport - self.peer_name = transport.get_extra_info("peername") - self._logger.info("Connection Made: {}".format(self.peer_name)) - self._logger.info("Requesting current status") - self._source_change_callback.connected() - self.send_status_message() - self._heartbeat_task = self._loop.create_task(self._heartbeat()) - - async def _heartbeat(self): - while True: - await asyncio.sleep(self._heartbeat_time) - self._logger.debug('heartbeat') - self._data_send("\n") - - async def wait_to_reconnect(self): - while not self._connected: - await asyncio.sleep(self._reconnect_time) - self.connect() - - def connection_lost(self, exc): - self._connected = False - self._heartbeat_task.cancel() - self._logger.error("Disconnected from {} will try to reconnect in {} seconds".format(self._hostname, self._reconnect_time)) - self._source_change_callback.disconnected() - self._loop.create_task(self.wait_to_reconnect()) - pass - - def data_received(self, data): - self._logger.debug("data_received client: {}".format(data)) - - for letter in data: - # Don't add these to the message as we don't need them. - if letter != ord('\r') and letter != ord('\n'): - self._received_message += chr(letter) - if letter == ord('\n'): - self._logger.debug("Whole message: {}".format(self._received_message)) - self._process_received_packet(self._received_message) - self._received_message = '' - - def _data_send(self, message): - self._logger.debug("data_send client: {}".format(message.encode())) - self._transport.write(message.encode()) - - def _process_received_packet(self, message): - match = SUCCESS_CHANGE.match(message) - if match: - self._logger.debug("Input change message received: {}".format(message)) - output_id = match.group(1) - input_id = match.group(2) - self._process_input_changed(input_id, output_id) - else: - match = STATUS_LINE.match(message) - if match: - self._logger.debug("Status Input change message received: {}".format(message)) - output_id = match.group(1) - input_id = match.group(2) - self._process_input_changed(input_id, output_id) - else: - self._logger.debug("Not an input change message received: {}".format(message)) - - def _process_input_changed(self, input_id, output_id): - self._logger.debug("Input ID [{}] Output id [{}]".format(input_id, output_id)) - self._output_to_input_map[output_id] = input_id - self._source_change_callback.source_changed(output_id, input_id) - - def send_change_source(self, input_id, output_id): - self._logger.info(f"Sending Output source change message - Output: {output_id} changed to input: {input_id}") - self._data_send("out{}fr{}\r".format(output_id, input_id)) - - def send_status_message(self): - self._logger.info(f"Sending status change message") - self._data_send("STATUS\r") - - def get_status_of_output(self, output_id): - return self._output_to_input_map.get(output_id, None) - - def get_status_of_all_outputs(self): - return_list = [] - for output_id in self._output_to_input_map: - return_list.append((output_id, self._output_to_input_map.get(output_id, None))) - return return_list - - def send_turn_on_message(self): - pass - - def send_turn_off_message(self): - pass +import asyncio +import logging +import re + +from pyblustream.listener import SourceChangeListener + +SUCCESS_CHANGE = re.compile('.*SUCCESS.*output\\s*([0-9]+)\\sconnect from input\\s*([0-9]+).*') +OUTPUT_CHANGE = re.compile('.*OUT\\s*([0-9]+)\\s*FR\\s*([0-9]+).*', re.IGNORECASE) +STATUS_LINE = re.compile('([0-9][0-9])\\s+([0-9][0-9])[^:].*') + + +class MatrixProtocol(asyncio.Protocol): + + _source_change_callback: SourceChangeListener + + def __init__(self, hostname, port, callback: SourceChangeListener, loop=None, heartbeat_time=5, reconnect_time=10): + self._logger = logging.getLogger(__name__) + self._heartbeat_time = heartbeat_time + self._reconnect_time = reconnect_time + self._hostname = hostname + self._port = port + self._source_change_callback = callback + self._loop = loop + if self._loop is None: + self._loop = asyncio.get_event_loop() + + self._connected = False + self._transport = None + self.peer_name = None + self._received_message = "" + self._output_to_input_map = {} + self._heartbeat_task = None + + def connect(self): + connection_task = self._loop.create_connection(lambda: self, host=self._hostname, port=self._port) + self._loop.create_task(connection_task) + + def connection_made(self, transport): + self._connected = True + self._transport = transport + self.peer_name = transport.get_extra_info("peername") + self._logger.info("Connection Made: {}".format(self.peer_name)) + self._logger.info("Requesting current status") + self._source_change_callback.connected() + self.send_status_message() + self._heartbeat_task = self._loop.create_task(self._heartbeat()) + + async def _heartbeat(self): + while True: + await asyncio.sleep(self._heartbeat_time) + self._logger.debug('heartbeat') + self._data_send("\n") + + async def wait_to_reconnect(self): + while not self._connected: + await asyncio.sleep(self._reconnect_time) + self.connect() + + def connection_lost(self, exc): + self._connected = False + self._heartbeat_task.cancel() + self._logger.error("Disconnected from {} will try to reconnect in {} seconds".format(self._hostname, self._reconnect_time)) + self._source_change_callback.disconnected() + self._loop.create_task(self.wait_to_reconnect()) + pass + + def data_received(self, data): + self._logger.debug("data_received client: {}".format(data)) + + for letter in data: + # Don't add these to the message as we don't need them. + if letter != ord('\r') and letter != ord('\n'): + self._received_message += chr(letter) + if letter == ord('\n'): + self._logger.debug("Whole message: {}".format(self._received_message)) + self._process_received_packet(self._received_message) + self._received_message = '' + + def _data_send(self, message): + self._logger.debug("data_send client: {}".format(message.encode())) + self._transport.write(message.encode()) + + def _process_received_packet(self, message): + match = SUCCESS_CHANGE.match(message) + if match: + self._logger.debug("Input change message received: {}".format(message)) + output_id = match.group(1) + input_id = match.group(2) + self._process_input_changed(input_id, output_id) + else: + match = STATUS_LINE.match(message) + if match: + self._logger.debug("Status Input change message received: {}".format(message)) + output_id = match.group(1) + input_id = match.group(2) + self._process_input_changed(input_id, output_id) + else: + self._logger.debug("Not an input change message received: {}".format(message)) + + def _process_input_changed(self, input_id, output_id): + self._logger.debug("Input ID [{}] Output id [{}]".format(input_id, output_id)) + self._output_to_input_map[output_id] = input_id + self._source_change_callback.source_changed(output_id, input_id) + + def send_change_source(self, input_id, output_id): + self._logger.info(f"Sending Output source change message - Output: {output_id} changed to input: {input_id}") + self._data_send("out{}fr{}\r".format(output_id, input_id)) + + def send_status_message(self): + self._logger.info(f"Sending status change message") + self._data_send("STATUS\r") + + def get_status_of_output(self, output_id): + return self._output_to_input_map.get(output_id, None) + + def get_status_of_all_outputs(self): + return_list = [] + for output_id in self._output_to_input_map: + return_list.append((output_id, self._output_to_input_map.get(output_id, None))) + return return_list + + def send_turn_on_message(self): + pass + + def send_turn_off_message(self): + pass diff --git a/setup.cfg b/setup.cfg index 9d5f797..badc95f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ -# Inside of setup.cfg -[metadata] +# Inside of setup.cfg +[metadata] description-file = README.md \ No newline at end of file