Ken Roberts has proposed merging lp:~alisonken1/openlp/test-projector into lp:openlp.
Requested reviews: OpenLP Core (openlp-core) For more details, see: https://code.launchpad.net/~alisonken1/openlp/test-projector/+merge/270902 Added test server code that emulates a PJLink projector for live testing of projector controller without an available network projector. NOTE: Python 2 code at this time. -- Your team OpenLP Core is requested to review the proposed merge of lp:~alisonken1/openlp/test-projector into lp:openlp.
=== added directory 'scripts/pyProjector' === added file 'scripts/pyProjector/LICENSE' --- scripts/pyProjector/LICENSE 1970-01-01 00:00:00 +0000 +++ scripts/pyProjector/LICENSE 2015-09-13 16:44:10 +0000 @@ -0,0 +1,339 @@ +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. \ No newline at end of file === added file 'scripts/pyProjector/README.md' --- scripts/pyProjector/README.md 1970-01-01 00:00:00 +0000 +++ scripts/pyProjector/README.md 2015-09-13 16:44:10 +0000 @@ -0,0 +1,41 @@ +Test Servers - Remote control for projectors + +- Currently requires Python v2.5+ - will not work in Python 3 +- Initialy, only network-connected PJLink enabled projectors can be controlled. + +Roadmap: + + - Connection handler + - PJLink module + - Other projectors as needed + - Possibly add other connection methods (i.e., serial port) + +Modularization: + Projector emulators are modularized to make it easer to test different + types of projector interfaces. + + Projector personalities are modules in the servers/ directory + +Projector personalities are threaded, so ensure your new server modules are +thread safe. + +projector/server-net.py [options] + Network connection handler. Once a new server module is added, update the + argparse section with the proper code to load your new module. + Default module will be the PJLink protocol server. + + Example: + + log.debug("Setting projector type") + if args.type.upper() == 'PJLINK': + log.info("Loading PJLink projector settings") + from servers.PJLink import * + projector = Projector() + projector.start() + + elif args.type.upper() == 'YOURNEWSERVER': + log.info("Loading YourNewServer projector settings") + from servers.YourNewServer import * + # Don't forget to include any (opts), if needed, here + projector = Projector(opts) + projector.start() === added directory 'scripts/pyProjector/projector' === added file 'scripts/pyProjector/projector/example-server.py' --- scripts/pyProjector/projector/example-server.py 1970-01-01 00:00:00 +0000 +++ scripts/pyProjector/projector/example-server.py 2015-09-13 16:44:10 +0000 @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + + """example-server.py: Example skeleton module for creating a new server""" + + +__name__ = "example-server" +__version__ = "0.0.1" +_v = __version__.split(".") +__version_hex__ = int(_v[0]) << 24 | \ + int(_v[1]) << 16 | \ + int(_v[2]) << 8 +__all__ = ['BUFFER', 'HEADER', 'PORT', 'SUFFIX', 'TIMEOUT_MAX', 'Projector'] + +import logging as log +import threading +import time +import random +from time import sleep +from hashlib import md5 +from socket import timeout +log.info("example-server %s module loading" % __version__) + +log.info("example-server: Setting required variables") +PORT = 4352 +BUFFER = 140 +TIMEOUT_MAX = 30.0 + +log.info("example: Setting local constants") +CR = chr(0x0D) # \r +LF = chr(0x0A) # \n +SUFFIX = CR # Suffix for your emulated server - default PJLink +PREFIX = '%' # Prefix character(s) for emulated server - default PJLink +CLASS = '1' # Command class if needed - default PJLink +HEADER = PREFIX + CLASS # Modify for your server + +log.info("example-server: Creating Projector() class") +class Projector(threading.Thread): + def __init__(self, + group=None, + target=None, + name='example-server', + user=None, + password = None + args=[], + kwargs={}): + super(Projector, self).__init__(group, target, name, args, kwargs) + self.name = name + # Fill in self.info with proper keys for information based on + # the emulated projector being coded for + self.info = { } + self.running = False + self.user = user + self.passwd = password + self.hashes = {} # Dictionary to hold multiple login hashes + self.hashstring = None + + def stop(self): + """stop() Clears running flag so thread can safely stop""" + log.debug('example-server: stop() called') + self.running = False + return + + def run(self): + """run() Runs while thread is alive""" + log.debug('example-server: run() called') + self.running = True + while self.running: + # Add code to handle runtime settings + sleep(10) + return + + def client_connect(self, client, addr): + """client_connect() Handle connection initialization with client""" + log.debug("example-server: client_connect(addr='%s')" % repr(addr)) + # This check should be for user and/or password options + if self.passwd is None: + """Add code for non-authenticated connection request""" + pass + else: + """Add code for authenticated connection request""" + pass + return + + def client_disconnect(self, addr): + """client_disconnect() Cleans up from client disconnect""" + log.debug("example-server: client_disconnect(addr=%s)" % repr(addr)) + _ip, _port = addr + return + + def handle_request(self, opts): + log.debug("example-server: handle_request(opts='%s')" % opts) + return === added file 'scripts/pyProjector/projector/server-net.py' --- scripts/pyProjector/projector/server-net.py 1970-01-01 00:00:00 +0000 +++ scripts/pyProjector/projector/server-net.py 2015-09-13 16:44:10 +0000 @@ -0,0 +1,203 @@ +#!/usr/bin/env python +"""server-net.py + +Network server to handle network connections and pass commands +to projector emulators +""" + +__version__ = '0.0.2' +_v = __version__.split(".") +__version_hex__ = int(_v[0]) << 24 | \ + int(_v[1]) << 16 | \ + int(_v[2]) << 8 + +import thread +import threading +import time +import logging +import argparse +import os +import sys +from time import sleep +from socket import * + +if os.geteuid() < 1000: + print "Best to run this as a normal user" + sys.exit() + +# Setup logging to console +FORMAT = '%(asctime)-15s [%(name)s]: %(message)s' +logging.basicConfig(format=FORMAT, datefmt='%Y-%m-%d %H:%M:%S') +log = logging.getLogger() +log.setLevel(logging.WARN) + +log.info("Initializing net server") + +# Get arguments +use_text = """server-net.py [options] + +Network server to emulate a projector for testing programs +that control projectors. + +Currently only PJLink is supported. +""" +parser = argparse.ArgumentParser(prog="server-net.py", + usage=use_text) +parser.add_argument('-d', action='count', + help="Increase debugging for each -d - max 2") +parser.add_argument('--debug', action='store', + help='Set debugging level: DEBUG INFO WARN ERROR CRITICAL') +parser.add_argument('-f', '--fail', action='store', default=None, + help='Set random fail check in minutes') +parser.add_argument('--host', action='store', + help='Set interface to listen to - default all') +parser.add_argument('--lamps', action="store", default=1, + help='Specify number of lamps installed') +parser.add_argument('-p', '--password', action='store', + help='Set password for projector authorization. Set to "TESTME" to use test algorithm') +parser.add_argument('--port', action='store', + help='Set a different port to use other than default') +parser.add_argument('-u', '--user', action='store', + help="Set username for projector authorization") +parser.add_argument('-t', '--type', + default='PJLINK', + help='type of projector to emulate - default="PJLINK"') +parser.add_argument('-v', '--version', + action='version', + version='%(prog)s ' + str( __version__)) +args = parser.parse_args() + +# Time to process +if args.debug is not None: + if args.debug.upper() not in "DEBUG INFO WARN ERROR CRITICAL": + print "--debug: invalid level: %s" % args.debug.upper() + print "Valid levels are DEBUG INFO WARN ERROR CRITICAL" + else: + log.setLevel(logging._levelNames[args.debug.upper()]) +elif args.d is not None: + d = log.getEffectiveLevel() - (args.d * 10) + if d < logging.DEBUG: d = logging.DEBUG + log.setLevel(logging._levelNames[d]) + +print "Log level set to %s" % logging.getLevelName(log.getEffectiveLevel()) + +log.debug('Parsing arguments') +log.debug("args: %s" % args) + +log.debug("Setting projector type") +# Only pass the arguments that your type can use. For example, if your +# projector does not need a username, then don't use the 'user' option. +if args.type.upper() == 'PJLINK': + log.info("Loading PJLink projector settings") + from servers.PJLink import * + projector = Projector(user=args.user, + password=args.password, + failcheck=args.fail, + lamps=args.lamps) + +if args.host is None: + HOST = '' # Listen on all interfaces + log.info("Listening on all interfaces") +else: + try: + HOST = gethostbyname(args.host) + log.info("Listening on interface %s" % HOST) + except: + HOST = '127.0.0.1' + log.warn("Unkown host: %s" % args.host) + log.warn("Setting to localhost (127.0.0.1)") + +if args.port is not None: + PORT = int(args.port) + if PORT < 1024 and os.geteuid() >= 1000: + print "Cannot use ports below 1024 as non-root user" + sys.exit(1) + +log.info("Setting constants") +CR = chr(0x0D) # \r +LF = chr(0x0A) # \n +# SUFFIX needs to be defined in emulator - normally CR but can be CR+LF +log.info("Setting main variables") +ADDR = (HOST, PORT) +serversock = None # Holds port binding +main_running = False # Keep track of main service running + +log.info("Creating server functions") +def send(s, d): + """send(s,d): Sends data d to socket s""" + log.debug("%s : Sending '%s'" % (repr(addr), d)) + c.send("%s%s" % (d, SUFFIX)) + +def handler(clientsock, addr): + """handler(clientsock, addr) + + Handler for new connection. Sends socket/address to Projector() class + for connection setup. + """ + log.info("New socket created for %s" % repr(addr)) + log.debug("handler: Setting %s timeout to %s" % (repr(addr), TIMEOUT_MAX)) + clientsock.settimeout(TIMEOUT_MAX) + log.info("handler: Initializing projector connection") + v, data = projector.client_connect(clientsock, addr) + log.debug("handler(): Connection validated: %s - Initial data: '%s'" % \ + (v, data)) + if not v: + log.error("Authentication failure - closing connection to %s" % \ + repr(addr)) + projector.client_disconnect(addr) + clientsock.close() + return + if data is None: + log.debug("handler(): Authenticated request with no command") + else: + _d = projector.handle_request(data) + log.debug("handler(): Athenticated connection - sending %s" % _d) + clientsock.send("%s%s" % (_d, SUFFIX)) + handler_running = True + while handler_running: + try: + data = clientsock.recv(BUFFER).strip() + if not data: + break + elif '\xff\xf4\xff\xfd\x06' in data: + # ^C pressed from telnet + log.info("%s: Received ^C - closing connection" % repr(addr)) + break + else: + log.debug("handler(): Calling projector.handle_request(%s)" % data) + _d = projector.handle_request(data) + log.debug("handler(): Sending '%s'" % _d) + clientsock.send("%s%s" % (_d, SUFFIX)) + + except timeout, e: + log.info("Timeout - closing connection to %s" % repr(addr)) + break + + except error, e: + log.info("Socket error - closing connection to %s" % repr(addr)) + break + projector.client_disconnect(addr) + log.debug("handler() closing connection to %s" % repr(addr)) + clientsock.close() + handler_running = False + +if __name__ == "__main__": + log.info("Starting main connection service address: %s" % repr(ADDR)) + serversock = socket(AF_INET, SOCK_STREAM) + serversock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) + serversock.bind(ADDR) + log.debug("Socket bound to address %s" % repr(serversock.getsockname())) + serversock.listen(5) + main_running = True + projector.start() + while main_running: + try: + log.info("MAIN: Waiting for connection - listening on port %s" % \ + PORT) + clientsock, addr = serversock.accept() + thread.start_new_thread(handler, (clientsock, addr)) + except KeyboardInterrupt: + log.warn("Keyboard interrupt - stopping") + projector.stop() + handler_running = False + main_running = False === added directory 'scripts/pyProjector/projector/servers' === added file 'scripts/pyProjector/projector/servers/CHANGELOG' --- scripts/pyProjector/projector/servers/CHANGELOG 1970-01-01 00:00:00 +0000 +++ scripts/pyProjector/projector/servers/CHANGELOG 2015-09-13 16:44:10 +0000 @@ -0,0 +1,1 @@ + === added file 'scripts/pyProjector/projector/servers/Eiki.py' --- scripts/pyProjector/projector/servers/Eiki.py 1970-01-01 00:00:00 +0000 +++ scripts/pyProjector/projector/servers/Eiki.py 2015-09-13 16:44:10 +0000 @@ -0,0 +1,107 @@ + """example-server.py: Example skeleton module for creating a new server""" + +""" +Eiki XL200 Projector Video Inputs: + INF1='EIKI' + INF2='XL200' + INST='11 12 31 32 21 22 13 23 24 25' + Param PJLink Projector + 11 RGB 1 Input 1 - RGB (PC analog) + 12 RGB 2 Input 1 - RGB (Scart) + 13 RGB 3 Input 2 - RGB + 21 VIDEO 1 Input 2 - Video + 22 VIDEO 2 Input 2 - Y, Pb/Cb, Pr/Cr + 23 VIDEO 3 Inout 3 - Video + 24 VIDEO 4 Input 3 - Y, Pb/Cb, Pr/Cr + 25 VIDEO 5 Input 3 - S-video + 31 DIGITAL 1 Input 1 - rgb (PC digital) + 32 DIGITAL 2 Input 1 - RGB (AV HDCP) + 51 NETWORK 1 Input 4 - Network +""" + +__version__ = "0.0.1" +_v = __version__.split(".") +__version_hex__ = int(_v[0]) << 24 | \ + int(_v[1]) << 16 | \ + int(_v[2]) << 8 +__all__ = ['BUFFER', 'PORT', 'SUFFIX', 'TIMEOUT_MAX', 'Projector'] + +import logging as log +import threading +import time +import random +from time import sleep +from hashlib import md5 +from socket import timeout +log.info("example-server %s module loading" % __version__) + +log.info("example-server: Setting required variables") +PORT = 4352 +BUFFER = 140 +TIMEOUT_MAX = 30.0 + +log.info("example: Setting local constants") +CR = chr(0x0D) # \r +LF = chr(0x0A) # \n +SUFFIX = CR+LF +PREFIX = '%' # Prefix character(s) for emulated server +CLASS = '1' # Command class if needed +HEADER = PREFIX + CLASS # Modify for your server + +log.info("example-server: Creating Projector() class") +class Projector(threading.Thread): + def __init__(self, + group=None, + target=None, + name='example-server', + user=None, + password = None + args=[], + kwargs={}): + super(Projector, self).__init__(group, target, name, args, kwargs) + self.name = name + # Fill in self.info with proper keys for information based on + # the emulated projector being coded for + self.info = { } + self.running = False + self.user = user + self.passwd = password + self.hashes = {} # Dictionary to hold multiple login hashes + self.hashstring = None + + def stop(self): + """stop() Clears running flag so thread can safely stop""" + log.debug('example-server: stop() called') + self.running = False + return + + def run(self): + """run() Runs while thread is alive""" + log.debug('example-server: run() called') + self.running = True + while self.running: + # Add code to handle runtime settings + sleep(2) + return + + def client_connect(self, client, addr): + """client_connect() Handle connection initialization with client""" + log.debug("example-server: client_connect(addr='%s')" % repr(addr)) + # This check should be for user and/or password options + if self.passwd is None: + """Add code for non-authenticated connection request""" + pass + else: + """Add code for authenticated connection request""" + pass + return + + def client_disconnect(self, addr): + """client_disconnect() Cleans up from client disconnect""" + log.debug("example-server: client_disconnect(addr=%s)" % repr(addr)) + _ip, _port = addr + return + + def handle_request(self, opts): + log.debug("example-server: handle_request(opts='%s')" % opts) + return === added file 'scripts/pyProjector/projector/servers/PJLink.py' --- scripts/pyProjector/projector/servers/PJLink.py 1970-01-01 00:00:00 +0000 +++ scripts/pyProjector/projector/servers/PJLink.py 2015-09-13 16:44:10 +0000 @@ -0,0 +1,756 @@ +"""PJLink.py: Module to emulate a PJLink server""" + +__version__ = "0.1.0" +_v = __version__.split(".") +__version_hex__ = int(_v[0]) << 24 | \ + int(_v[1]) << 16 | \ + int(_v[2]) << 8 + +__all__ = ['BUFFER', 'HEADER', 'PORT', 'SUFFIX', 'TIMEOUT_MAX', 'Projector'] + +import logging +import threading +import time +import random +from time import sleep +from hashlib import md5 +from socket import timeout + +from projectorbase import * + +log = logging.getLogger(__name__) + +log.info("Module loading version %s" % __version__) + +log.info("PJLink: Setting local constants") +PJLINK_PREFIX = '%' +PJLINK_CLASS = '1' + +PJLINK_INFO = { 'NAME': "PJLinkProjector", # Can be changed" + 'INF1': "OpenLP", + 'INF2': "PythonProjector", + 'INFO': "Model PS01 Serial P274001" + } + +LAMP_DICT = { "STATUS": 0, + "NUMBER": 0, + "LAMP_ON": False, + "FAN": 0, + "FAN_ON": False, + "TEMP": 0, + "FILTER": 0, # random.randint(0, 6000), # Minutes + "COVER": 0, + "TIME": 0, #random.randint(0, 300000), # Minutes + "CLOCK": None, + "WARN": False, + } + +log.info("Setting required variables") +BUFFER = 140 +HEADER = PJLINK_PREFIX + PJLINK_CLASS +PORT = 4352 +SUFFIX = CR +SUFFIX = CR+LF # Telnet needs LF otherwise change back to CR only +TIMEOUT_MAX = 30.0 + +log.info("Creating PJLinkStatus() class") +class PJLinkStatus(Status): + """PJLinkStatus(Status) + + Extends basic error code mappings with PJLink code mappings + """ + # Map some error codes to PJLink physical parts. + names = { E_NETWORK: "Network", + E_FAN: "Fan", + E_LAMP: "Lamp", + E_TEMP: "Temperature", + E_COVER: "Cover", + E_FILTER: "Filter", + E_PROJECTOR: "Projector" + } + + # PJLink power status mapping + power = { S_STANDBY: '0', + S_ON: '1', + S_COOLDOWN: '2', + S_WARMUP: '3', + '0': S_STANDBY, + '1': S_ON, + '2': S_COOLDOWN, + '3': S_WARMUP + } + # PJLink fan/lamp/temp/cover/filter/other mapping + erst = { E_OK: '0', + E_WARN: '1', + E_ERROR: '2', + '0': E_OK, + '1': E_WARN, + '2': E_ERROR + } + + errors = { E_OK: 'OK', + E_UNDEFINED: 'ERR1', + E_PARAMETER: 'ERR2', + E_UNAVAILABLE: 'ERR3', + E_PROJECTOR: 'ERR4', + E_AUTHENTICATION: 'ERRA', + 'OK': E_OK, + 'ERR1': E_UNDEFINED, + 'ERR2': E_PARAMETER, + 'ERR3': E_UNAVAILABLE, + 'ERR4': E_PROJECTOR, + 'ERRA': E_AUTHENTICATION + } + + # Define inputs - This lists all valid PJLink inputs + inputs = { '11': "RGB 1", + '12': "RGB 2", + '13': "RGB 3", + '21': "VIDEO 1", + '22': "VIDEO 2", + '23': "VIDEO 3", + '31': "DIGITAL 1", + '32': "DIGITAL 2", + '33': "DIGITAL 3", + '41': "STORAGE 1", + '42': "STORAGE 2", + '51': "NETWORK 1", + '52': "NETWORK 2" + } + +log.debug("Setting non-standard help") +_CMDHELP = """ + +All command require a prefix. + PREFIX = %s + +Example command to request power status: + %sPOWR ?<return> + +PJLink options are: + POWR [? 0 1] + INPT [? %s] + AVMT [? 10 11 20 21 30 31] + LAMP ? + INST ? + NAME ? + INF1 ? + INF2 ? + INFO ? + CLSS ? + + Non-error returns will be: + OK - Command executed + <data> - Some string based on the command given + + Error returns will be: + ERR1 - Invalid command + ERR2 - Invalid option + ERR3 - Unavailable time + ERR4 - Projector/Display error + ERRA - Authentication error + +""" % (PJLINK_PREFIX+PJLINK_CLASS, PJLINK_PREFIX+PJLINK_CLASS, PJLinkStatus.inputs.keys()) + +log.info("Creating Projector() class") +class Projector(ProjectorBase): + def __init__(self, + group=None, # Thread required + target=None, # Thread required + name='PJLink', + user=None, + password=None, + failcheck=None, + lamps=None, + args=[], # Thread required + kwargs={}): # Thread required + log.info("Projector.__init__(user='%s' password='%s')" % \ + (user, password if password is None else 'password')) + super(Projector, self).__init__(group, target, name, args, kwargs) + self.client_ip = None + self.name = name + self.running = False + self.user = user + if password == 'TESTME': + log.debug('Projector.__init__(): Setting self.passwd to JBMIAProjectorLink') + self.passwd = 'JBMIAProjectorLink' + else: + log.debug('Projector.__init__(): Using password option') + self.passwd = password + self.hashes = {} # Dictionary to hold multiple login hashes + self.shutter = False # True=shutter close=False shutter open + self.audio = False # True=audio muted False=audio normal + self.source = '11' # Default to first analog input + self.PROJ_ERR = None + self.PROJ_PART = [] # May have multiple failures + self.PROJ_ERRSTAT = { 1: 0, # Fan + 2: 0, # Lamp + 3: 0, # Temperature + 4: 0, # Cover open + 5: 0, # Filter + 6: 0 # Other + } + # Time between random failure check (minutes) + if failcheck is None or failcheck.isdigit(): + self.failcheck = failcheck + else: + self.failcheck = None + log.warn("Projector.__init__(): Invalid failcheck option - disabling failcheck") + self.failclock = None # Clock for next random failures + self.timer = None + """ self.powerstat + S_STANDBY: 0 + S_ON: 1 + S_COOLDOWN: 2 + S_WARMUP: 3 + """ + self.powerstat = S_STANDBY # Initial condition is standby + """ self.lamp + + Dictionary of lamps in projector. + Each lamp is another dictionary of status + STATUS: See PJLinkStatus.[erst | power] + FAN: Fan state S_OFF, S_ON, E_ERR + TIME: Time in minutes lamp has been in the ON state. + Initial lamp time is a random int from 0 (new) + to 300000 minutes (approx. 5000 hours) + CLOCK: time.time() when lamp was initially turned ON. + This includes warmup/cooldown times. + None when lamp is off. + FILTER: Time since last filter cleaning + COVER: Whether the lamp access cover is open + WARN: Warning indicator for lamp time or filter time + """ + # First define number of lamps, then fill out in next step + + self.lamp = {} + if lamps is None: + _lamps = 1 + elif (type(lamps) is type(str)) and not lamps.isdigit(): + _lamps = 1 + log.warn("Projector.__init__(): Invalid option for lamps - defaulting to 1") + else: + _lamps = int(lamps) + for i in range(1,_lamps+1): # Taken from initialization + self.lamp[i] = None + for i in self.lamp: + self.lamp[i] = LAMP_DICT.copy() + self.lamp[i]['FILTER'] = random.randint(0,6000) + self.lamp[i]['TIME'] = random.randint(0,300000) + self.lamp[i]['LAMP_ON'] = False + if self.lamp[i]['TIME'] >= 240000 or self.lamp[i]['FILTER'] > 6000: + # Lamp has > 4000 hours runtime + # Filter has > 100 hours since last cleaning + self.lamp[i]['WARN'] = True + log.debug("Projector.__init__(): Lamp %s TIME=%s" % (i, self.lamp[i]['TIME'])) + log.debug("Projector.__init__(): Lamp %s FILTER=%s" % (i, self.lamp[i]['FILTER'])) + log.debug("Projector.__init__(): Lamp %s WARN=%s" % (i, self.lamp[i]['WARN'])) + + ###################################################################### + # Required functions # + ###################################################################### + def client_disconnect(self, addr): + """client_disconnect(addr) + + Removes connection cached hash key. + """ + log.info("Projector.client_disconnect(addr=%s)" % repr(addr)) + _ip, _port = addr + _key = '%s:%s' % (_ip, _port) + if self.hashes.has_key(_key): + self.hashes.pop(_key) + return + + def client_connect(self, client, addr): + """client_connect(client, addr) + + Handle connection initialization with client and + adds new client to self.hashes. + """ + log.info("Projector.client_connect(addr='%s')" % repr(addr)) + self.client_ip, self.client_port = addr + if self.passwd is None: + log.debug("Projector.client_connect(%s) Unauthenticated connection" % self.client_ip) + client.send("PJLINK 0" + SUFFIX) + return (True, 'OK') + + else: + # For testing purposes: + # _rand = '498e4a67' + # Password is 'JBMIAProjectorLink' + # Return hash should be '5d8409bc1c3fa39749434aa3a5c38682' + if self.passwd == 'JBMIAProjectorLink': + log.debug("Projector(%s): self.passwd == 'JBMIAProjectorLink'" % self.client_ip) + log.debug("Projector(%s): setting _rand='498e4a67'" % self.client_ip) + _rand = '498e4a67' + else: + _rand = str(hex(random.randint(268435456, 4294967295L)))[2:-1] + log.debug("Projector(%s): Setting _rand to '%s'" % (self.client_ip, _rand)) + _hashed = md5() + _hashed.update(_rand) + _hashed.update(self.passwd) + _digest = str(_hashed.hexdigest())[:32] + _ip, _port = addr + _key = "%s:%s" % (_ip, _port) + self.hashes[_key] = _digest + try: + log.debug('Projector(%s).client_connect(): Sending "PJLINK 1 randomkey"' % self.client_ip) + client.send("PJLINK 1 %s%s" % (_rand, SUFFIX)) + data = client.recv(BUFFER).strip() + log.debug("Projector.server_connect(%s) Received %s" % (self.client_ip, data)) + except timeout, e: + log.debug("Projector.server_connect(%s): Timeout" % self.client_ip) + return (False, None) + + if len(data) > 32 and data[32:34] == HEADER: + log.debug("Projector.server_connect(%s): Authentication with command" % self.client_ip) + _a = data[:32] + _d = data[32:].strip() + else: + log.debug("Projector.server_connect(%s): Authentication only" % self.client_ip) + _a = data[:32] + _d = None + + log.debug("Projector.server_connect(%s): %s Received" % (self.client_ip, _a)) + log.debug("Projector.server_connect(%s): %s Expected" % \ + (self.client_ip, self.hashes[_key])) + if _a == self.hashes[_key]: + log.debug('Projector.server_connect(%s): Validated login data="%s"' % (self.client_ip, _d)) + return (True, _d) + else: + log.debug("Projector.server_connect(%s): Invalid login" % self.client_ip) + client.send('PJLINK=%s%s' % (PJLinkStatus.errors[E_AUTHENTICATION],SUFFIX)) + return (False, None) + + return (False, None) # Catchall failed + + def stop(self): + """stop() + + Clears running flag so thread can safely stop + """ + log.info('Projector.stop() called') + self.running = False + return + + def run(self): + """run() + + Called when thread is started, then checks status of projector + for any changes. + """ + log.info('Projector.run() called') + self.running = True + self._power_change(S_STANDBY) + while self.running: + sleep(2.0) # 2 second intervals between checks + if self.timer is None: continue + if time.time() < self.timer: continue + # Time to do something + # The only changes should be warmup -> on/cooldown -> standby + log.debug("Projector.run() Time to change something") + if self.powerstat == S_WARMUP: + self._power_change(S_ON) + elif self.powerstat == S_COOLDOWN: + self._power_change(S_STANDBY) + + if self.failclock is not None and self.failclock < time.time(): + _random_fail() + + continue # Not required, but explicit > implicit + return + + def handle_request(self, data): + """handle_request(data) + + Called from main thread to handle projector requests. + """ + log.info("Projector(%s).handle_request(data='%s')" % (self.client_ip, data)) + if data is None: + return + if data.upper() == '--HELPME--': + return _CMDHELP + if len(data) < 2: + log.debug("Projector(%s).handle_request(): Packet length < 2" % self.client_ip) + return '' + elif data[0] != PJLINK_PREFIX: + log.debug("Projector(%s).handle_request(): data[0] != %s" % (self.client_ip, PJLINK_PREFIX)) + return '' + elif data[1] != PJLINK_CLASS: + log.debug("Projector(%s).handle_request(): Invalid class %s != %s" % (self.client_ip, data[1], PJLINK_CLASS)) + return '' + + _r = None + if len(data) < 8: + log.debug("Projector(%s).handle_request(): Packet length < 8" % self.client_ip) + log.debug("Projector(%s).handle_request(): Packet length: %s" % (self.client_ip, len(data))) + # Minimum packet length not met + return "%s=%s" % (data, PJLinkStatus.errors[E_UNDEFINED]) + elif data[6] != ' ': + log.debug("Projector(%s).handle_request(): char 7 not a space: %s" % (self.client_ip, data[6])) + return "%s=%s" % (data, PJLinkStatus.errors[E_UNDEFINED]) + else: + _hdr = data[:2] + _cmd, _param = data[2:].split(' ') + + if _r is not None: + pass # Already have a reply + else: + # Commands with mutliple options + if _cmd.upper() == 'POWR': + _d = self.power(_param) + if _d == S_OK: + _r = PJLinkStatus.errors[S_OK] + else: + _r = _d + elif _cmd.upper() == 'INPT': + _d = self.input_select(_param) + if _d == S_OK: + _r = PJLinkStatus.errors[S_OK] + else: + _r = _d + elif _cmd.upper() == 'AVMT': + _d = self.avmute(_param) + if _d == S_OK: + _r = PJLinkStatus.errors[S_OK] + else: + _r = _d + + # The rest require ? as parameter and nothing else + elif _param != '?': + _r = PJLinkStatus[E_PARAMETER] + elif _cmd.upper() == 'ERST': + _r = self.error_status(_param) + elif _cmd.upper() == 'LAMP': + _r = self.lamp_status(_param) + elif _cmd.upper() == 'INST': + _r = self.input_list(_param) + elif _cmd.upper() == 'NAME': + _r = PJLINK_INFO['NAME'] + elif _cmd.upper() == 'INF1': + _r = PJLINK_INFO['INF1'] + elif _cmd.upper() == 'INF2': + _r = PJLINK_INFO['INF2'] + elif _cmd.upper() == 'INFO': + _r = PJLINK_INFO['INFO'] + elif _cmd.upper() == 'CLSS': + _r = PJLINK_CLASS + else: + # Unknown command + _r = PJLinkStatus.errors[E_UNDEFINED] + _d = "%s%s=%s" % (_hdr, _cmd, _r) + log.debug("Projector(%s).handle_request() returning '%s')" % (self.client_ip, _d)) + return _d + + ###################################################################### + # Local functions # + ###################################################################### + def _random_fail(self): + """_random_fail() + + Random check if a part failed and sets self.PROJ_ERRSTAT[] + list to which part(s) are failed. + """ + log.info("Projector(%s)._random_fail() called", self.client_ip) + if int(random.random()) == 1: + _list = [ 'POWER', 'LENS' ] + for i in self.lamp: + _list.append('LAMP%s' % self.lamp[i]['NUMBER']) + _list.append('FAN%s' % self.lamp[i]['NUMBER']) + _list.append('COVER%s' % self.lamp[i]['NUMBER']) + _list.append('FILTER%s' % self.lamp[i]['NUMBER']) + if self.lamp[i]['TIME'] > 30000: + # Lamp exceeds rated time - extra chance of failure + _list.append('LAMP%s' % self.lamp[i]['NUMBER']) + failed = random.choice(_list) + self.PROJ_PART = failed + if 'FAN' in failed: + if failed not in self.PROJ_PART: + self.PROJ_ERRSTAT[1] = 2 + elif 'LAMP' in failed: + if failed not in self.PROJ_PART: + self.PROJ_ERRSTAT[2] = 2 + elif 'TEMP' in failed: + # Probably fan failed or filter clogged + if failed not in self.PROJ_PART: + self.PROJ_ERRSTAT[3] = 2 + elif 'COVER' in failed: + # Cover open + if failed not in self.PROJ_PART: + self.PROJ_ERRSTAT[4] = 2 + elif 'FILTER' in failed: + # Filter clogged + if failed not in self.PROJ_PART: + self.PROJ_ERRSTAT[5] = 2 + else: + self.PROJ_ERRSTAT[6] = 2 + + def _lamp_clock_update(self, lamp, stop=False): + """ + Update the clock time for the lamp + """ + if lamp['CLOCK'] is not None: + _t = int(time.time() - lamp['CLOCK']) + lamp['TIME'] = lamp['TIME'] + (_t / 60) + if stop: + lamp['CLOCK'] = None + + def _lamp_change(self, lamp, opt, failed='LAMP'): + """_lamp_change(lamp, opt) + + Handle lamp changes. Must be called with a dict of a single lamp. + """ + log.info("Projector(%s)._lamp_change(opt='%s')" % (self.client_ip, Status.keys[opt])) + + if not type(lamp) == type({}): + log.warn('Projector(%s)._lamp_change() lamp not a dict' % self.client_ip) + log.warn('Projector(%s)._lamp_change(lamp): %s' % (self.client_ip, lamp)) + return E_PROJECTOR + elif opt is None: + # Update clock below + pass + elif opt != S_OFF and \ + opt not in PJLinkStatus.power and \ + opt not in PJLinkStatus.erst: + log.warn('Projector(%s)._lamp_change() invalid option %s' % (self.client_ip, opt)) + return E_PARAMETER + + _r = S_OK + if opt is None: + self._lamp_clock_update(lamp) + elif opt == E_ERROR: + _r = PJLinkStatus.errors[E_PROJECTOR] + if failed == 'LAMP': + self._lamp_clock_update(lamp, True) + lamp['STATUS'] = PJLinkStatus.erst[E_ERROR] + PROJ_ERRSTAT[0] = PJLinkStatus.erst[E_ERROR] + elif failed == 'FAN': + lamp['FAN'] = PJLinkStatus.erst[E_ERROR] + lamp['FAN_ON'] = False + lamp['TEMP'] = PJLinkStatus.erst[E_WARN] + elif failed == 'COVER': + lamp['COVER'] = PJLinkStatus.erst[E_ERROR] + lamp['TEMP'] = PJLinkStatus.erst[E_WARN] + elif opt == S_ON: + lamp['FAN_ON'] = True + lamp['LAMP_ON'] = True + elif opt == S_COOLDOWN: + self._lamp_clock_update(lamp, True) + lamp['STATUS'] = S_OFF + lamp['LAMP_ON'] = False + elif opt == S_WARMUP: + lamp['STATUS'] = S_ON + lamp['FAN_ON'] = True + lamp['CLOCK'] = time.time() + lamp['LAMP_ON'] = True + return _r + + def _power_change(self, opt): + """_power_change(opt) + + Handle power setting changes. + """ + log.info("Projector(%s)._power_change(opt='%s')" % (self.client_ip, Status.keys[opt])) + log.info("Projector(%s)._power_change() self.timer= %s" % (self.client_ip, self.timer)) + if self.PROJ_ERR is not None: + return self.PROJ_ERR + elif self.timer is not None and time.time() < self.timer: + return PJLinkStatus.errors[E_UNAVAILABLE] + elif not opt in PJLinkStatus.power: + return PJLinkStatus.errors[E_PARAMETER] + + # Error checks passed - time to change something + lampset = None + _r = S_OK + self.powerstat = opt + if opt == S_WARMUP: + self.timer = time.time() + 20.0 + lampset = S_ON + elif opt == S_STANDBY: + self.timer = None + lampset = S_OFF + elif self.powerstat == S_COOLDOWN: + self.timer = time.time() + 20.0 + lampset = S_COOLDOWN + elif opt == S_ON: + self.timer = None + # No lamp change + + _dd = S_OK + if lampset is not None: + for i in self.lamp: + _d = self._lamp_change(lamp=self.lamp[i], opt=lampset) + if _d != S_OK: + _dd = _d + log.debug("PJLink(%s)._power_change(lamp=%s) received lamp status %s" % (self.client_ip, i, _d)) + + if _dd != S_OK or _r != S_OK: + _r = PJLinkStatus.errors[E_PROJECTOR] + + log.debug("Projector(%s)._power_change() returning %s" % (self.client_ip, PJLinkStatus.keys[_r])) + return _r + + def power(self, opt): + """power(opt) + + Handle POWR command requests. + """ + log.info("Projector(%s).power(opt='%s')" % (self.client_ip, opt)) + log.debug("Projector(%s).powerstat = %s" % \ + (self.client_ip, PJLinkStatus.keys[self.powerstat])) + if opt not in '?01': + log.debug('Projector(%s).power(): opt %s not in "?01"' % (self.client_ip, opt)) + return PJLinkStatus.errors[E_PARAMETER] + elif self.PROJ_ERR is not None: + log.debug('Projector(%s).power() returning self.PROJ_ERR' % self.client_ip) + return self.PROJ_ERR + + log.debug('Projector(%s).power(): Checking opt %s' % (self.client_ip, opt)) + if opt == '?': + if self.powerstat in PJLinkStatus.power: + return PJLinkStatus.power[self.powerstat] + else: + return PJLinkStatus.errors[E_PROJECTOR] + + log.debug('Projector(%s).power(): Checking for unavailable time' % self.client_ip) + if self.powerstat == S_WARMUP or self.powerstat == S_COOLDOWN: + return PJLinkStatus.errors[E_UNAVAILABLE] + + log.debug('Projector(%s).power(): Checking for set on/off' % self.client_ip) + _r = S_OK + if opt == '1': + if self.powerstat == S_ON: + return S_OK + else: + _d = self._power_change(S_WARMUP) + elif opt == '0': + if self.powerstat == S_STANDBY: + return S_OK + else: + _d = self._power_change(S_COOLDOWN) + + if _d != S_OK: + return _d + else: + return _r + + def error_status(self, opt): + """error_status(opt) + + Returns the current status of: + fan[s], + lamp[s], + temperature, + cover open, + filter[s], + other + """ + log.info("Projector(%s).error_status(opt=%s)" % (self.client_ip, opt)) + if opt != "?": + return PJLinkStatus.errors[E_PARAMETER] + elif self.PROJ_ERR is not None: + return self.PROJ_ERR + + return '%s%s%s%s%s%s' % ( self.PROJ_ERRSTAT[1], + self.PROJ_ERRSTAT[2], + self.PROJ_ERRSTAT[3], + self.PROJ_ERRSTAT[4], + self.PROJ_ERRSTAT[5], + self.PROJ_ERRSTAT[6] + ) + + def avmute(self, opt): + """avmute(opt) + + Handles AVMT requests. + """ + log.info("Projector(%s).avmute(opt=%s)" % (self.client_ip, opt)) + if self.PROJ_ERR is not None: return self.PROJ_ERR + if opt == '?': + if self.powerstat == 2: + return PJLinkStatus.errors[3] + elif not self.audio and not self.shutter: + return '30' + elif self.audio and self.shutter: + return '31' + elif self.shutter: + # video mute (shutter closed) + return '11' + else: + # Audio muted only + return '21' + elif opt == '11': + self.shutter = True + elif opt == '10': + self.shutter = False + elif opt == '21': + self.audio = True + elif opt == '20': + self.audio = False + elif opt == '31': + self.shutter = True + self.audio = True + elif opt == '30': + self.shutter = False + self.audio = False + else: + return PJLinkStatus.errors[E_PARAMETER] + return PJLinkStatus.errors[S_OK] + + def lamp_status(self, opt): + """lamp_status(opt) + + Returns the lamp hours and current on/off status of + available lamps. + """ + log.info("Projector(%s).lamp_status(): called" % self.client_ip) + if self.PROJ_ERR is not None: + return self.PROJ_ERR + if opt != '?': return PJLinkStatus.errors[E_PARAMETER] + _r = '' + for i in self.lamp: + _r = "%s %s %s" % ( _r, self.lamp[i]['TIME'], \ + ("1" if self.lamp[i]['LAMP_ON'] else "0")) + return _r.strip() + + def input_list(self, opt): + """"input_list(opt) + + Handles INST request. + Returns a string of the available source inputs. + """ + log.info("Projector(%s).input_list(opt='%s')" % (self.client_ip, opt)) + if self.PROJ_ERR is not None: + return self.PROJ_ERR + if opt != '?': + log.debug("Projector(%s).input_list(opt='%s')" % (self.client_ip, opt)) + return PJLinkStatus.errors[E_PARAMETER] + if self.powerstat == S_STANDBY: + log.debug("Projector(%s).input_list() self.powerstat=%s" % (self.client_ip, PJLinkStatus.codes[self.powerstat])) + return PJLinkStatus.errors[E_UNAVAILABLE] + _r = [] + for key in PJLinkStatus.inputs.keys(): + _r.append(key) + log.debug('Projector(%s).input_list(): %s' % (self.client_ip, _r)) + _r.sort() + return ' '.join(_r).strip() + + def input_select(self, opt): + """input_select(opt) + + Handles INPT requests. + """ + log.info("Projector(%s).input_select(opt='%s')" % (self.client_ip, opt)) + if self.PROJ_ERR is not None: + return self.PROJ_ERR + if opt == '?': + return self.source + elif self.powerstat != S_ON and self.powerstat != S_WARMUP: + log.debug("Projector(%s).input_select(opt=%s) Power state %s != %s" % \ + (self.client_ip, opt, PJLinkStatus.codes[self.powerstat], PJLinkStatus.codes[S_ON])) + return PJLinkStatus.errors[E_UNAVAILABLE] + elif opt not in PJLinkStatus.inputs: + log.debug("Projector(%s).input_select(opt=%s) Invalid input" % (self.client_ip, opt)) + return PJLinkStatus.errors[E_PARAMETER] + self.source = opt + return PJLinkStatus.errors[S_OK] === added file 'scripts/pyProjector/projector/servers/README.md' --- scripts/pyProjector/projector/servers/README.md 1970-01-01 00:00:00 +0000 +++ scripts/pyProjector/projector/servers/README.md 2015-09-13 16:44:10 +0000 @@ -0,0 +1,73 @@ +This is the test server suite. New server personalities can be added here. + +Create a new server module in the test/servers directory. + + - create/edit Your-New-Server.py + +Add the following minimum to your new Server.py file: + * Make sure __all__ is properly set with the following + values: + __all__ = [ 'Projector', 'BUFFER', 'PORT', 'TIMEOUT_MAX' ] + + * Make sure you have the following variables available: + BUFFER = <size of input/output buffer> + PORT = <tcp port to use> + TIMEOUT_MAX = <float of maximum idle time for connection> + + * Make sure you have the following base class: + class Projector(telnetlib.Telnet) + +In the Projector class, add the following minimum: + + class Projector(telnetlib.Telnet) + def __init__(<base args>, user=None, password=None) + def client_connect(self, client, addr) + def client_disconnect(self, addr) + def stop(self) + def run(self) + def handle_request(self, data) + +client_connect(self, client, addr): + + Used for initial authentication. + client: socket of new connection + addr: tuple of [ipaddr, port] + + < Code for new connection. If needed, both authenticated + and non-authenticated connection requests> + + Returns a tuple of + (Bool, String), where: + Bool: + True - Authorized connection + False - Failed authorization + + String: + Either a string with initial command or None if no + initial command requested. + +client_disconnect(self, addr): + Cleanups when client disconnects + addr: tuple of [ipaddr, port] + +stop(): + Cleanups when stop is requested and sets a notification that + a thread stop has been requested. + +run(): + Called when thread starts. Make this a loop with a check + so cleanups can be done when system is shutdown. Typically + a while loop is used with a self.running variable check. + You can name your variable whatever you want, it's just best + to use a True/False variable so looping can halt when it's set + to False. Allows for cleanups when system is stopped. + +handle_request(data): + Called whenever data is received + data = command received from client + +Once those minimums are added, fill in the rest of the code to make +your new projector emulator work. + +Be sure to add the proper import option in server-net.py file so your +new module will be available. === added file 'scripts/pyProjector/projector/servers/__init__.py' --- scripts/pyProjector/projector/servers/__init__.py 1970-01-01 00:00:00 +0000 +++ scripts/pyProjector/projector/servers/__init__.py 2015-09-13 16:44:10 +0000 @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 === added file 'scripts/pyProjector/projector/servers/projectorbase.py' --- scripts/pyProjector/projector/servers/projectorbase.py 1970-01-01 00:00:00 +0000 +++ scripts/pyProjector/projector/servers/projectorbase.py 2015-09-13 16:44:10 +0000 @@ -0,0 +1,362 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +"""projectorbase.py: Base module for projector emulators""" + +__version__ = "0.0.1" +_v = __version__.split(".") +__version_hex__ = int(_v[0]) << 24 | \ + int(_v[1]) << 16 | \ + int(_v[2]) << 8 + +# Define exportables for * +__all__ = ['CR', 'LF', 'BUFFER', 'HEADER', 'PORT', 'SUFFIX', 'TIMEOUT_MAX', + 'S_OK', 'E_OK', 'E_GENERAL', 'E_NOTCONNECTED', 'E_NETWORK', 'E_FAN', + 'E_LAMP', 'E_TEMP', 'E_COVER', 'E_FILTER', 'E_AUTHENTICATION', + 'E_UNDEFINED', 'E_PARAMETER', 'E_UNAVAILABLE', 'E_PROJECTOR', + 'E_INVALID_DATA', 'E_WARN', 'E_ERROR', 'S_STANDBY', 'S_ON', + 'S_WARMUP', 'S_COOLDOWN', 'S_NOT_CONNECTED', 'S_CONNECTING', + 'S_STATUS', 'S_OFF', 'S_INITIALIZE', 'S_INFO', 'Status', + 'ProjectorError', 'ProjectorNotConnectedError', + 'ProjectorLampError', 'ProjectorFailedError', + 'ProjectorNetworkError', 'ProjectorBase'] + +import os +import errno +import logging +import socket +import sys +import threading +import time + +# Log should already be setup before getting here +log = logging.getLogger(__name__) + +# Common codes +log.debug('projectorbase: Setting constants') +CR = chr(0x0D) # *nix \r +LF = chr(0x0A) # *nix \n +BUFFER = 140 # Default PJLink + 4 +HEADER = '%1' # Default PJLink class 1 header +PORT = 4352 # Default PJLink +SUFFIX = CR # Default PJLink +TIMEOUT_MAX = 30.0 # Default PJLink + +# Enumeration of status and error codes used +S_OK = 0 +E_OK = 0 +# Error codes. Start at 200 so we don't duplicate system error codes. +E_GENERAL = 200 # Unknown error +E_NOTCONNECTED = 201 +E_NETWORK = 202 +E_FAN = 203 +E_LAMP = 204 +E_TEMP = 205 +E_COVER = 206 +E_FILTER = 207 +E_AUTHENTICATION = 208 # PJLink ERRA +E_UNDEFINED = 209 # PJLink ERR1 +E_PARAMETER = 210 # PJLink ERR2 +E_UNAVAILABLE = 211 # PJLink ERR3 +E_PROJECTOR = 212 # PJLink ERR4 +E_INVALID_DATA = 213 +E_WARN = 214 +E_ERROR = 215 + +# Status codes. Start with 300 so we don't duplicate system error codes. +S_STANDBY = 300 # PJLink power 0 +S_ON = 301 # PJLink power 1 +S_WARMUP = 302 # PJlink power 2 +S_COOLDOWN = 303 # PJLink power 3 +S_NOT_CONNECTED = 304 +S_CONNECTING = 305 +S_STATUS = 306 +S_OFF = 307 +S_INITIALIZE = 308 +S_INFO = 309 + +# Enumeration classes +class Status(object): + + # Start at 200 so we don't cover system error codes + keys = { S_OK: 'S_OK', + E_GENERAL: 'E_GENERAL', + E_NOTCONNECTED: 'E_NOTCONNECTED', + E_NETWORK: 'E_NETWORK', + E_FAN: 'E_FAN', + E_LAMP: 'E_LAMP', + E_TEMP: 'E_TEMP', + E_COVER: 'E_COVER', + E_FILTER: 'E_FILTER', + E_AUTHENTICATION: 'E_AUTHENTICATION', + E_UNDEFINED: 'E_UNDEFINED', + E_PARAMETER: 'E_PARAMETER', + E_UNAVAILABLE: 'E_UNAVAILABLE', + E_PROJECTOR: 'E_PROJECTOR', + E_INVALID_DATA: 'E_INVALID_DATA', + E_WARN: 'E_WARN', + E_ERROR: 'E_ERROR', + S_STANDBY: 'S_STANDBY', + S_ON: 'S_ON', + S_WARMUP: 'S_WARMUP', + S_COOLDOWN: 'S_COOLDOWN', + S_NOT_CONNECTED: 'S_NOT_CONNECTED', + S_CONNECTING: 'S_CONNECTING', + S_STATUS: 'S_STATUS', + S_OFF: 'S_OFF', + S_INITIALIZE: 'S_INITIALIZE', + S_INFO: 'S_INFO' + } + + codes = { S_OK: 'OK', + E_OK: 'OK', + E_GENERAL: "General projector error", + E_NOTCONNECTED: "Not connected error", + E_NETWORK: "Network error", + E_LAMP: "Lamp error", + E_FAN: "Fan error", + E_TEMP: "High temperature detected", + E_COVER: "Cover open detected", + E_FILTER: "Check filter", + E_AUTHENTICATION: "Authentication Error", + E_UNDEFINED: "Undefined Command", + E_PARAMETER: "Invalid Parameter", + E_UNAVAILABLE: "Projector Busy", + E_PROJECTOR: "Projector/Display Error", + E_INVALID_DATA: "Invald packet received", + E_WARN: "Warning condition detected", + E_ERROR: "Error condition detected", + S_NOT_CONNECTED: "Not connected", + S_CONNECTING: "Connecting", + S_STATUS: "Getting status", + S_OFF: "Off", + S_INITIALIZE: "Initialize in progress", + S_STANDBY: "Power standby", + S_WARMUP: "Warmup in progress", + S_ON: "Power on", + S_COOLDOWN: "Cooldown in progress", + S_INFO: "Projector Information availble" + } + +# Error classes +class ProjectorError(Exception): + """ + Base projector error class + """ + def __init__(self, errno=None, message=None): + self.errno = errno if errno is not None else E_GENERAL + self.message = message if message is not None else 'General projector error' + def __repr__(self): + return ((self.errno, self.message)) + def __str__(self): + return self.message + +class ProjectorNotConnectedError(ProjectorError): + """ + Projector not connected + """ + def __init__(self, errno=E_NOTCONNECTED, + message=u'Projector Not Connected'): + super(ProjectorNotConnectedError, self).__init__(errno, message) + +class ProjectorLampError(ProjectorError): + """ + Lamp failure + """ + def __init__(self, errno=E_LAMP, + message=u'Lamp Failure'): + super(ProjectorLampError, self).__init__(errno, message) + +class ProjectorFailedError(ProjectorError): + """ + General projector failure + """ + def __init__(self, errno=E_GENERAL, + message=u'Unknown projector failure'): + super(ProjectorFailedError, self).__init__(errno, message) + +class ProjectorNetworkError(ProjectorError): + """ + Network failure + """ + def __init__(self, errno=E_NETWORK, + message=u'Projector network error'): + super(ProjectorNetworkError, self).__init__(errno, message) + +class ProjectorFanError(ProjectorError): + """ + Fan failure + """ + def __init__(self, errno=E_FAN, + message=u'Projector fan failure'): + super(ProjectorFanError, self).__init__(errno, message) + +log.info('Creating base projector class') +class ProjectorBase(threading.Thread): + def __init__(self, + group=None, # Thread required - defined as None at this time + target=None, # Thread required + name='ProjectorBase', + user=None, + password=None, + host=None, + port=PORT, + args=[], + kwargs={} + ): + super(ProjectorBase, self).__init__(group, target, name, args, kwargs) + self.class_ = '1' # Current PJLink class defined + self.connected = False + self.connecting = False + self.host = host + self.name = name + self.password = password + self.port = port + self.prefix = '%' # PJlink command prefix + self.running = False # Keep track of thread running status + self.status = 0 + self.status_error = None + self.status_message = '' + self.status_time = time.time() + self.suffix = CR # Default PJLink suffix + self.user = user + self.header = self.prefix + self.class_ + + # Required methods to override + def run(self): + """ProjectorBase.run() + + Method used to initialize and run thread. + """ + self.__run = False + assert self.__run, "You must override this method." + + def client_connect(self): + """ProjectorBase.sock_connect() + + Method used to connect to projector. + You must define steps for authenticated and unauthenticated connections. + """ + self.__connect = False + assert self.__connect, "You must override this method." + + # Methods that should not need to be overridden + + def _set_status(self, code=None, message=None): + if self.status_error is not None: + log.debug('%s(%s): _set_status(self.status_error)' % (self.name, self.host)) + _code = self.status_error.errno + _msg = self.status_error.message + elif code == self.status: + # No status change + return + else: + log.debug("%s(%s): _set_status(code=%s, message='%s')" % \ + (self.name, self.host, code, message)) + _code = code + if code in Status.codes: + _msg = Status.codes[code] + else: + _msg = message + + self.status = _code + self.status_message = _msg + + """ + If a callback function is registered, then send status update. + """ + if self.callback is None: + return + + if self.callback_code == self.status and time.time() < self.status_time: + return + + self.callback((self.status, self.status_message)) + self.callback_code = self.status + self.status_time = time.time() # Last time callback was called + return + + def send(self, data, timeout=5.0): + """ProjectorBase.send() + + Send a packet of data. Assumes telnet protocol. + """ + log.debug('%s(%s).send() called' % (self.name, self.host)) + if self.status_error is not None: + log.debug("%s(%s): Error status code %s" % (self.host, self.port, + self.status_error.errno)) + return + + if self.socket is None and not self.connecting: + log.debug("%s(%s).send(): Reconnecting" % (self.name, self.host)) + self.sock_connect() + + try: + log.debug("%s(%s).send(data='%s')" % (self.name, self.host, data)) + self.socket.write("%s%s%s" % (self.header, data, self.SUFFIX)) + except socket.error, e: + try: + _errno = e.errno + except AttributeError: + _errno = None + + log.debug('%s(%s).send(): Socket error (%s) %s' % \ + (self.name, self.host, e.errno, e.message)) + self.status_error = ProjectorNetworkError(errno=_errno, + message=e.message) + + def recv(self, search, timeout=5.0): + """ProjectorBase.recv() + + Receive data. Assumes telnet protocol. + """ + log.debug('%s(%s).recv(): Fetching data' % (self.name, self.host)) + if self.status_error is not None: + return None + + if self.socket is None: + log.debug('ProjectorBase.recv(%s): No socket open' % self.host) + self.status_error = ProjectorNotConnectedError( + errno=E_NOTCONNECTED, + message=u'Socket not open') + self._set_status() + + try: + for i in range(5): + # Clean out empty lines + data = self.socket.read_until(search, timeout) + if len(data) < 3: + continue + else: + break + if data is None: + self.socket.close() + self.socket=None + self.status_error = ProjectorNetworkError(errno=E_NOTCONNECTED, + message=u'%s(%s): Socket closed by other side' % (self.name, self.host)) + except socket.timeout, e: + log.debug("%s(%s)_recv(): Timeout error: %s" % (self.name, self.host, e.message)) + self.socket.close() + self.socket = None + self.status_error = ProjectorNetworkError(errno=e.errno, + message=u"%s(%s): Timeout error: %s" % (self.name, self.host, e.message)) + except socket.error, e: + log.debug("%s(%s)_recv(): Socket error: %s" % (self.name, self.host, e.message)) + self.socket.close() + self.socket = None + self.status_error = ProjectorNetworkError(errno=e.errno, + message=u"%s(%s): Socket error: %s" % \ + (self.name, self.host, e.message)) + except EOFError, e: + log.debug("%s(%s)_recv(): Socket closed by other side: %s" % (self.name, self.host, e.message)) + self.socket.close() + self.socket=None + self.status_error = ProjectorNetworkError(errno=E_NOTCONNECTED, + message=u'%s(%s): Socket closed by other side' % (self.name, self.host)) + + if self.status_error is not None: + self._set_status() + return None + else: + log.debug("%s(%s).recv() returning '%s'" % (self.name, self.host, data.strip())) + return data.strip() === added file 'scripts/pyProjector/projector/test_server.old.py' --- scripts/pyProjector/projector/test_server.old.py 1970-01-01 00:00:00 +0000 +++ scripts/pyProjector/projector/test_server.old.py 2015-09-13 16:44:10 +0000 @@ -0,0 +1,392 @@ +#!/usr/bin/env python +"""test_server.py + +Test server for checking PJLink controllers. +Warning: Not multi-connection safe - use only one instance at a time +""" + +import thread +import threading +import random +import time +import sys +from time import sleep +from socket import * +from hashlib import md5 + +# Static defines +BUFFER = 136 # Maximum packet size including \n +HOST = '127.0.0.1' +SERVICE = { 'PORT': 4352, + 'NAME': 'pjlink' + } +PJLINK_PREFIX = "%" +PJLINK = { "CLASS": '1', + "COMMANDS": { '1': ['POWR', + 'INPT', + 'AVMT', + 'ERST', + 'LAMP', + 'INST', + 'NAME', + 'INF1', + 'INF2', + 'INFO', + 'CLSS' + ] + } + } + +PJLINK_ERR = { 'OK': "Success", + 'ERR1': "Undefined command", + 'ERR2': "Out of parameter", + 'ERR3': "Unavailable time", + 'ERR4': "Projector/Display error", + 0: 'OK', + 1: 'ERR1', + 2: 'ERR2', + 3: 'ERR3', + 4: 'ERR4', + 10: "Empty packet", + 11: "Invalid prefix", + 12: "Invalid class", + 13: "Invalid packet" + } + +TIMEOUT_MAX = 30.0 # Number of idle seconds before forced close +CR = chr(0x0D) # \r +LF = chr(0x0A) # \n +CRLF = CR + LF + +# Authentication purposes +PASSWD = None + +# Dictionary of commands mapped to functions +PJLINK_CMD = {} + +projector = None # used for class Projector +class Projector(threading.Thread): + """Threaded projector class + + Acts as a single projector that will update itself outside of the socket + thread. + """ + def __init__(self, group=None, + target=None, + name="ProjectorThread", + args=[], kwargs={}): + super(Projector, self).__init__(group, target, name, args, kwargs) + self.name = name + self.info = { 'NAME': "DefaultProjector", # Can be changed" + 'INF1': "OpenLP", + 'INF2': "PythonProjector", + 'INFO': "Model PS01 Serial P274001" + } + self.model = 'PS01' # Python single lamp model 1 + self.other = "Serial number P274001" + self.lockedPower = threading.Lock() # Power changes + self.lockedVideo = threading.Lock() # Video changes + self.lockedLamp = threading.Lock() # Lamp changes + self.PROJECTOR_ERROR = None # ERR3 or ERR4 + # See self.powerstat for status,fan values + self.lamp = { 0: {"status": '0', + "time": random.randint(0, 30000), # time in minutes + "clock": 0, # Clock time when lamp was turned on + "fan": '0' + } + } + self.time = 0 + for i in self.lamp: + # Random failure of lamp on startup + self.lamp[i]['status'] = 4 if int(random.random()) == 1 \ + else 0 + if self.lamp[i]['status'] > 3: self.PROJECTOR_ERROR = PJLINK_ERR[4] + self.time = self.lamp[i]['time'] \ + if self.time < self.lamp[i]['time'] \ + else self.time + self.time = self.time + 60 # 1 hour longer than longest lamp time + # self.powerstat + # 0 = off + # 1 = startup + # 2 = standby + # 3 = warmup - check self.lamp + # 4 = on + # 5 = cooldown check self.lamp + # 6 = warning + # 7 = error + self.powerstat = 0 + self.powerup = 0 # Timer startup/lamp on/lamp off + self.audio_mute = False # False=aduio out True=audio mute + self.video_mute = False # False=open True=closed + self.video = '11' + self.video_select = { 1: 4, # 1=vga, 2=rgb1, 3=rgb2, 4=dvi-a + 2: 3, # 1=composite, 2=svideo, 3=SCART + 3: 3, # 1=dvi-i, 2=dvi-d, 3=hdmi + 4: 0, # 1=usb input, 2=internal storage input + 5: 0, # 1=network input + '11': "Computer RGB", + '12': "Analog RGB", + '13': "SCART1", + '14': "DVI-A", + '21': "Composite Video", + '22': "S-Video", + '23': "SCART2", + '31': "DVI-I", + '32': "DVI-D", + '33': "HDMI" + } + self.temp = 0 # 0=ok, 1=warning 2=error + self._filter = 0 # 0=ok, 2=warning, 3=error + + def stop(self): + self._running = False + + def run(self): + """Start projector and maintain projector status""" + self._running = True + print "%s Projector class run called" % self.name + self.PROJECTOR_ERROR = PJLINK_ERR[3] + print "%s Projector status 1: startup" % self.name + self._powerchange('1') + while self._running: + sleep(2) # 2 second sleep interval between updates + # Check for powerup status + if self.powerup is not None and self.powerup < time.time(): + # Time to change status + print "Projector.run(): Changing powerup state" + if self.powerstat == '5': + self._powerchange(2) + else: + self._powerchange(str(int(self.powerstat) + 1)) + + def handle_request(self, cmd, opt): + print "Projector.handle_request(cmd='%s' opt='%s'" % (cmd, opt) + if self.PROJECTOR_ERROR is not None: + return self.PROJECTOR_ERROR + if not cmd in PJLINK["COMMANDS"]['1']: + return PJLINK_ERR[1] + + elif cmd == "POWR": + return self.power(opt) + + def _lampchange(self, lamp, opt): + """Projector._lampchange(): Change lamp and fan state""" + # Called by self._powerchange + # See 234567 of self.powerstat + print "Projector._lampchange(opt='%s')" % opt + if lamp['status'] == '7' or lamp['fan'] > '7': + # Error condition + return PJLINK_ERR[4] + + with self.lockedLamp: + if opt == '2': + # Lamp off + if lamp['clock'] is not None: + _c = int(time.time() - lamp['clock']) / 60 + lamp['time'] = lamp['time'] + _c + lamp['clock'] = None + lamp['fan'] = 2 + + elif opt == '3': + # Lamp warmup to on + lamp['clock'] = time.time() + lamp['fan'] = 4 + + lamp['status'] = opt + + return PJLINK_ERR[0] + + def _powerchange(self, opt): + """Projector._poewrchange(): Change power state""" + print "Projector._powerchange(opt='%s')" % opt + # Called by + if not opt in '012345': return + with self.lockedPower: + if opt == '1': + self.PROJECTOR_ERROR = PJLINK_ERR[3] \ + if self.PROJECTOR_ERROR != PJLINK_ERR[4] \ + else self.PROJECTOR_ERROR + if opt in '135': + self.powerup = time.time() + 30.0 \ + if opt == 1 \ + else time.time() + 60 + elif opt in '024': + self.powerup = None + self.PROJECTOR_ERROR = None \ + if self.PROJECTOR_ERROR != PJLINK_ERR[3] \ + or self.PROJECTOR_ERROR != PJLINK_ERR[4] \ + else self.PROJECTOR_ERROR + self.powerstat = opt + + r = PJLINK_ERR[0] + for i in self.lamp: + j = self._lampchange(self.lamp[i], opt) + r = j if r != PJLINK_ERR[4] else r + return r + + def power(self, opt=None): + """Set/get power status - Called from self.handle_request()""" + print "Projector.power(opt='%s')" % opt + # Initial error checks + try: + if opt not in '01?': + return PJLINK_ERR[2] + except TypeError: + return PJLINK_ERR[2] + # Status query + if self.PROJECTOR_ERROR is not None: + return self.PROJECTOR_ERROR + elif opt == '?': + print 'Projector.power() self.powerstat = %s' % self.powerstat + if self.powerstat == 2: return '0' + elif self.powerstat == 4: return '1' + elif self.powerstat == 5: return '2' + elif self.powerstat == 3: return '3' + elif self.powerstat >= 6: return PJLINK_ERR[4] + + # Time to change power + if self.powerstat == 1 and self.powerup is not None: return PJLINK_ERR[3] + elif self.powerstat in '35' and self.powerup is not None: return PJLINK_ERR[0] + + if opt == '1': + if self.powerstat == 4: + # Already on + return PJLINK_ERR[0] + elif self.powerstat == 5: + # Power up during cooldown + return PJLINK_ERR[3] + elif self.powerstat == 2: + # Set to warmup + return self._powerchange('3') + elif opt == '0': + if self.powerstat == 2: + # Aldready in standby + return PJLINK_ERR[0] + elif self.powerstat == 3: + # Power off during warmup + return PJLINK_ERR[3] + elif self.powerstat == 4: + # Set to cooldown + return self._powerchange('5') + +def logme(data): + """Print socket logging info to console""" + print repr(addr) + " " + data + +def getRandom(): + """Return an 8-char random string""" + return str(hex(random.randint(268435456, 4294967295L)))[2:-1] + +def send(data): + """Send data to client socket""" + clientsock.send(data + CRLF) + +def handler(clientsock, addr, seed=None, hashed=None): + """Socket handler""" + print "New thread addr='%s' seed='%s' hashed='%s" % \ + (repr(addr), seed, hashed) + clientsock.settimeout(TIMEOUT_MAX) + handler_running = True + while handler_running: + RESPONSE = None + ERR = 0 # > 10 non-standard error for internal use + try: + d = clientsock.recv(BUFFER + 1) # Allow for braindamaged \r + data = d.strip((CR)) + if not data: break + if '\xff\xf4\xff\xfd\x06' in data: + # ^C pressed - close connection + print repr(addr) + " Received ^C" + break + logme("Received: '%s' " % repr(data)) + except timeout, e: + break + # OK - got data, time to verify if proper format + # If invalid prefix - ignore + if len(data) <= 1: + ERR = 10 + elif len(data) <= 8 or len(data) > BUFFER: + # Invalid packet + logme("Invalid packet length: %s data: '%s' " % \ + (len(data), repr(data))) + elif data[0] != PJLINK_PREFIX: + # Invalid start character - ignore + logme("Invalid prefix: %s" % data[0]) + ERR = 11 # Invalid prefix + elif not data[1] in PJLINK['CLASS']: + # invalid class = send ERR1 + logme("Invalid class: %s " % data[1]) + ERR = 12 # Invalid class + elif data[6] != ' ': + ERR = 1 + else: + # Basic packet checks OK - process rest of command + pass + + # End of checks - time to send back + if ERR == 0: + # packet checks OK + pass + elif ERR == 10 and RESPONSE is None: + # Invalid packet - ignore + logme("Blank packet received") + send('') + continue + elif ERR > 10: + send("%s=%s" % (data[:6].strip(), PJLINK_ERR[1])) + continue + elif ERR >= 1 and ERR in PJLINK_ERR: + send("%s=%s" % (data[:6].strip(), PJLINK_ERR[ERR])) + continue + else: + logme("Unknown error %s" % ERR) + continue + + cmd = data[2:6].upper().strip() + cls = data[1] + response = projector.handle_request(cmd, data[7:].strip()) + data = data[:2].strip() + cmd + "=" +response + CRLF + logme("Sending %s" % data) + send(data) + # Done with the socket - time to cleanup + clientsock.close() + print "Connection closed addr=" + repr(addr) + if not handler_running: + print "handler terminated" + +if __name__ == '__main__': + # Start the projector + if projector is None: + projector = Projector() + projector.start() + # Start the socket service + ADDR = (HOST, SERVICE['PORT']) + serversock = socket(AF_INET, SOCK_STREAM) + serversock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) + serversock.bind(ADDR) + serversock.listen(1) + main_running = True + while main_running: + try: + print "Waiting for connection ... listening on port" + \ + str(SERVICE["PORT"]) + clientsock, addr = serversock.accept() + print "Connected from:" + repr(addr) + if PASSWD is None: + mainthread = thread.start_new_thread(handler, (clientsock, addr)) + else: + rand = getRandom() + hashed = md5() + hashed.update(rand) + hashed.update(PASSWD) + thread.start_new_thread(handler, + (clientsock, + addr, + rand, + hashed.hexdigest()) + ) + except KeyboardInterrupt: + print "Keyboard interrupt - stopping" + projector.stop() + handler_running = False + main_running = False
_______________________________________________ Mailing list: https://launchpad.net/~openlp-core Post to : [email protected] Unsubscribe : https://launchpad.net/~openlp-core More help : https://help.launchpad.net/ListHelp

