CAJUN devices can be one of 4 types: input, output, audio, and utility. Each of these types uses the same basic template, with only minor modifications.
To start, here's what the basic template should look like:
package DEVICEPACKAGENAME; use strict; use lib '/usr/share/cajun/lib'; use PARENTDEVICE; our @ISA=qw(PARENTDEVICE); # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # CAJUN version 4.0, Copyright (C) 2001. # CAJUN comes with ABSOLUTELY NO WARRANTY; for details see the LICENSE file. # This is free software, and you are welcome to redistribute it # under certain conditions; also see the LICENSE file for details. # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= #!!! Gui parameters#!!! Gui parameters # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # onLoad gets called once when the object is created (optional). # you'll have the following data at hand, once your SUPER's onLoad # returns: # # $self->{db}: a connection to the sql database # $self->{config}{devparam}: a hash ref to this device's paramters # $self->{config}{global}: a hash ref to CAJUN's global params # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= sub onLoad { my($self)=@_; $self->SUPER::onLoad(); # *** MUST call this first! # add your code here. $self->logmsg("Device initialized and ready to go."); } # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # onUnLoad gets called if CAJUN ever exits. You don't need it, but if # you have one, be sure to call the SUPER's onUnLoad first. # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= sub onUnLoad { my($self)=@_; $self->SUPER::onUnLoad(); # *** MUST call this first! $self->logmsg("We're outta here!"); }
You'll want to replace DEVICEPACKAGENAME with the name of the device package you'll place the object into, and PARENTDEVICE as one of InputDevice,OutputDevice,AudioDevice, or UtilityDevice as appropriate. Note that you have a logging method, "logmsg", to log arbitrary strings formatted nicely. Output will be prepended with the device name and number for easy identification.
Now, for the driver type specific changes:
Input drivers only need one method. It's called when CAJUN needs a keypress from the input device, and is allowed to block until that keypress is received. Simply return the ascii bytes in string form to the caller:
sub readMsgFromInputDev {
my ($self)=@_;
my $buf=somehowGetThisDevicesChars();
return $buf;
};
Somewhere in the onLoad code, the output driver is required to call one of two methods:
Full UpdatesFull updates are requested by calling $self->requestFullDisplayUpdates in the onLoad method. It declares that the physical device is incapable of cursor positioning, and the screen must be completely redrawn every time.
Partial UpdatesPartial updates are requested by calling $self->requestPartialDisplayUpdates in the onLoad method. This indicates that the device is capable of cursor positioning, and that only the areas of the display that need updating can be delivered.
The Output driver needs to declare two methods:
sub display {
my($self,@work)=@_;
# do stuff with @work
}
sub sendRaw {
my($self,$str)=@_;
my $portfh=$self->{portfh};
print $portfh $str;
}
In the case of full updates, each entry in @work will be a full line, exactly the width of the display. The number of entries in @work will correspond with the display height. If partial updates are requested, each entry in @work is a hash reference with the following keys:
In this case, you must iterate through the @work array and place output strings as appropriate.
The 'sendRaw' routine is used when a user wants to send a byte stream to the display during initalization.
Optionally, you can declare a few other methods:
sub backlight {
my($self,$state)=@_;
if ($state eq "on") {
# turn light on
} else {
# turn light off
}
}
to control the backlight. CAJUN will turn the light on and off as required.
Also, for misc. string display (such as startup/shutdown messages), implement the optional 'displayString' method similar to:
sub displayString {
my($self,$str)=@_;
# position cursor, check length, center string
$str=substr($str,0,$self->{width}-1) if length($str)>=$self->{width};
my $x=int($self->{width}/2)-(length($str)/2);
my $y=int($self->{height}/2)-1;
print $lcdport $self->{clear} . sprintf("%c%c%c",27,$x,$y,$str);
}
If you have some background work to do, simply implement the following method, and it will be called periodically for you. You cannot control the rate at which you'll be called, but it will usually be at least once a second. Implement a timer check if you can't be called too often:
sub backgroundThread {
my($self)=@_;
# do stuff
}
Audio drivers have a little more work to do. First, they must provide activate() and deactivate() methods, which are called by the parent when an audio device is activated/deactivated by the CAJUN core. Upon deactivation, the device should immediately silence all output, and cease communication with the core (more in a moment) until activated again. It should also be noted that while activated, all audio methods should return as quickly as possible. Any blocking will cause poor input device response, and could potentially overflow socket queues.
Next, the driver can have a polling method if it would like. Wherever desired, call $self->setBackgroundThreadTimer(1), where 1 is the number of seconds between polls (partial seconds are acceptable). Set it to 0 to turn off polling. The callback occurs in a method "backgroundThread". Remember to minimize time spent in this method. Also, try not to ask for too short a polling interval; anything less than .10 seconds may be too little for slower machines to handle. If you don't ask for polling, the callback method is not needed.
Also, CAJUN can watch sockets for you, and alert you when input is available on them. Simply call addMonitorFds/removeMonitorFds to add or remove file descriptors (not file handles) to the watch list, and define a 'monitorFd' method to be called when something happens:
sub onLoad {
...
$self->addMonitorFd(fileno($mysocket));
}
sub montorFd {
my($self)=@_;
sysread($mysocket,$buf,1024);
...
}
Last, a "processAction" method needs to be declared, taking one parameter, an action name. This action will have been delivered from the input device through the core.
An audio driver needs to track which state it is currently in. A default state method is already present in the audio device; it takes either 0 or one arguments. If a state name is passed in, the module changes to that state, alerts the core, and logs a message. Either way, the (possibly now current) state name is returned. If you want to implement your own state method, make sure you call your SUPER's state() method first, and let it maintain your audio object's {state} variable for you.
It's common to also provide the following methods in your driver (for your use only; they're not expected by the parent driver):
Here's what the missing pieces from a sample audio driver would look like:
sub processAction {
my($self,$action)=@_;
# decide what to do with this action
$self->updateDisplay; # since most actions result in display changes
}
sub activate {
my($self)=@_;
# initialize and get ready for action
$self->logmsg("activating");
}
sub deactivate {
my($self)=@_;
# shut us down and be quiet
$self->logmsg("deactivating");
}
sub backgroundThread {
my($self)=@_;
# do something here? update a clock? whatever?
$self->updateDisplay;
}
sub state {
my($self,$state)=@_;
$self->SUPER::state($state);
# any additional processing here.
if ($self->{state} eq "stopped") {
# for example, silence everybody...
}
return $state;
}
sub updateDisplay {
my($self)=@_;
return unless $self->{activated};
$self->sendMsg(
_msgtype => "display",
_state => $self->state,
artist => $self->{current}{artist},
disc => $self->{current}{disc},
song => $self->{current}{song},
);
}
Now, a little discussion about sending back display data. (See the CAJUN documentation on "Configuration: Output Drivers" for more help). The output driver is going to expect you to send the information about screen data in a single call to $self->sendMsg, which formats a message and sends it to the core (who will translate it and forward it to the output driver). The only two required fields are "_msgtype", which is always "display", and "_state", which is the current state of your application. The output driver will use the "_state" field to determine which screen to display. It can be helpful to send fields that don't exist in the default display - for example, in a MP3 player, you can send the "bitrate" and "genre" fields, but they don't necessarily have to be used by the display filter and sent to the display. Users with larger displays will want to customize their display filters to display the additional data you provide.
For additional clarity, go to your CAJUN's internal web site configuration and examine the displayFilters for the output devices you have configured.