openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #27351
[Merge] lp:~alisonken1/openlp/test-projector into lp:openlp
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
Follow ups