A Common Lisp library for interfacing with PJLink standard compliant devices.
PJLink projectors operate over TCP/IP.
This library implements both class 1 and class 2 support, in which you can:
Class 1:
- Query power status and power projector on/off
- Query current and available inputs, and set active input
- Query and set audio-video mute status
- Query error status, for projector components (fan, lamp, etc)
- Get lamp information (usage time, on/off)
- Query projector, manufacturer, and product names.
Class 2:
- LAN Projector discovery.
- Projector status notifications.
- Serial version query.
- Resolution query.
- Lamp and filter model numbers.
- Microphone and speaker control.
- Projector "freeze" control
- Software version information.
Code examples have *ip*
bound to the IP of the projector, eg "192.168.1.2"
;; Get power status
(pjlink:get-power-status *ip*)
;; =>
:STANDBY
;; Turn projector on
(pjlink:power-on *ip*)
(pjlink:get-power-status *ip*)
;; =>
:WARM-UP
;; After some time
(pjlink:get-power-status *ip*)
;; =>
:LAMP-ON
;; Query available inputs
(pjlink:get-inputs *ip*)
;; =>
((:RGB . 1) (:VIDEO . 2) (:DIGITAL . 3) (:STORAGE . 4) (:RGB . 2) (:VIDEO . 6) (:DIGITAL . 7))
;; Set the input to one of them
(pjlink:set-input '(:video . 6) *ip*)
;; Get the currently active input
(pjlink:get-input *ip*)
;; =>
(:VIDEO . 6)
In addition to parameters for the commands themselves and the host designator, all of the PJlink commands accept a 'host designator' and the following keyword parameters:
- port - Port to connect to. Defaults to PJLink port (4352)
- password - Password to use if authentication is required
- local-hort - Local interface to use for the connection
- local-port - Local port to use for the connection
Each of these parameters has a corresponding generic function you may specialize in order to make it easier to pass along similar parameters, or to customize eg password retrieval:
(defclass my-pjlink-host ()
())
(defmethod pjlink:host ((obj my-pjlink-host))
*ip*)
(defmethod pjlink:password ((obj my-pjlink-host))
"Get the password by reading it from the user"
(read-password-from-user))
;; ...
(pjlink:power-on (make-instance 'my-pjlink-host))
The class pjlink-config
is provided as a simple container:
(let ((config (make-instance 'pjlink:pjlink-config
:host *ip*
:password "JBMIAProjectorLink)))
;;Set the input to one of the available ones
(let ((inputs (pjlink:get-inputs config)))
(pjlink:set-input* (first inputs) config))
;;Power off the projector
(pjlink:power-off config))
Class 2 the ability to query the LAN for projectors and receive notifications.
Projectors on the LAN can be queried by using the class 2 search procedure.
Note: It is highly recommend to specify an interface (network card) to conduct the search, as it is based on UDP broadcast and will not route properly otherwise.
Note: This is a synchronous, 30 second call as per the spec
Note: Because the search procedure uses the same port as status notification, currently there's no way to conduct a search while a status listener is active.
(pjlink:search-projectors :local-host *interface-address*)
;; 30 seconds later
;; =>
((#(192 168 2 5) . #(188 95 244 185 102 171))
(#(192 168 2 5) . #(10 0 39 0 0 14)))
The return value here is a list of projector IP's and mac-addresses
A status-listener
can be created to asynchronously monitor projector status. These updates have to be configured on the projector in a model-specific fashion so that they notify a host of status updates via UDP.
Creating and starting a listener will create a background worker thread to monitor this socket and signal an event when a status update occurs:
Note: It is highly recommended to specify an interface (network card) to listen on, rather than using the 'default' network interface.
(defun print-pjlink-event (remote-host event-type args)
(format t "Received notification from ~A: ~A = ~A" remote-host event-type args))
;; When 'handlers' is provided, listener is started automatically
(defparameter *listener* (pjlink:make-status-listener :local-host *interface-address* :handlers #'print-pjlink-event))
;; Otherwise call (pjlink:start-listener *listener*)
;; When finished listening for events:
(pjlink:stop-listener *listener*)
;; Can always resume listening later:
(pjlink:start-listening *listener*)
See LICENSE