A Common Lisp library for interfacing with PJLink enabled devices
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.
LICENSE.txt Initial commit. Sep 15, 2018



A Common Lisp library for interfacing with PJLink standard compliant devices.

PJLink projectors operate over TCP/IP.

Table of Contents


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 *projector-ip* bound to the IP of the projector, eg ""

;; Get power status
(pjlink:get-power-status *projector-ip*)
;; =>

;; Turn projector on
(pjlink:turn-on *projector-ip*)

(pjlink:get-power-status *projector-ip*)
;; =>

;; After some time
(pjlink:get-power-status *projector-ip*)
;; =>

;; Query available inputs
(pjlink:get-inputs *projector-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) *projector-ip*)

;; Get the currently active input
(pjlink:get-input *projector-ip*)
;; =>
(:VIDEO . 6)


All pjlink operations accept a :password parameter:

(pjlink:set-av-mute :avmt-on *projector-ip*" :password "JBMIAProjectorLink")

Alternatively, all operations support taking in an object of class pjlink:pjlink-config:

(let ((config (make-instance 'pjlink:pjlink-config
                             :host *projector-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))

This prevents having to pass around connection information such as host and password information.

Class 2

Class 2 adds functionality in the form of being able to query LAN projectors and receive notifications.

Search Procedure

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

Status Notification

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*)