# # # 02_FRAMEBUFFER.pm # written by Kai Stuke # based on 02_RSS.pm # ############################################## # $Id$ package main; use strict; use warnings; use GD; use feature qw/switch/; use vars qw(%data); use Scalar::Util qw(looks_like_number); require "02_RSS.pm"; # enable use of layout files and image creation my %sets = ( 'updateDisplay' => "", 'relLayoutNo' => "", 'absLayoutNo' => "", 'layoutFilename' => "", ); ################################################## # Forward declarations # ################## sub FRAMEBUFFER_Initialize($); sub FRAMEBUFFER_rewindCounter($); sub FRAMEBUFFER_readLayout($); sub FRAMEBUFFER_Define($$); sub FRAMEBUFFER_updateDisplay($$); sub FRAMEBUFFER_Set($@); sub FRAMEBUFFER_Attr(@); sub FRAMEBUFFER_returnPNG($); sub FRAMEBUFFER_Initialize($) { my ($hash) = @_; $hash->{DefFn} = "FRAMEBUFFER_Define"; $hash->{AttrFn} = "FRAMEBUFFER_Attr"; $hash->{AttrList} = 'loglevel:0,1,2,3,4,5,6 update_interval:1,2,5,10,20,30 ' . 'size layoutBasedir layoutList startLayoutNo debugFile bgcolor enableCmd disableCmd ' . $readingFnAttributes; $hash->{SetFn} = "FRAMEBUFFER_Set"; $hash->{UndefFn} = 'FRAMEBUFFER_Undef'; } sub FRAMEBUFFER_Undef($$) { my ($hash, $arg) = @_; RemoveInternalTimer($hash); return undef; } ################## sub FRAMEBUFFER_rewindCounter($) { my ($hash) = @_; my $name= $hash->{NAME}; my $updateInterval = AttrVal($hash->{NAME}, 'update_interval', 0); Log3 $name, 5, "rewindCounter $updateInterval"; if ($updateInterval > 0) { # round to the begin of the next minute to get a more accurate time display my $currentTime = time(); my $triggerTime = int(($currentTime + ($updateInterval * 60))/60)*60; Log3 $name, 5, "current $currentTime next trigger at $triggerTime"; InternalTimer($triggerTime, 'FRAMEBUFFER_rewindCounter', $hash, 0); } FRAMEBUFFER_updateDisplay($hash, 0); } ################## sub FRAMEBUFFER_readLayout($) { my ($hash) = @_; my $name= $hash->{NAME}; my $filename= $hash->{fhem}{filename}; if (!defined $filename) { return 0; } if (defined $hash->{layoutBasedir} && substr($filename,0,1) ne '/') { $filename = $hash->{layoutBasedir} . '/' . $filename; } if(open(LAYOUT, $filename)) { my @layout= ; $hash->{fhem}{layout}= join("", @layout); close(LAYOUT); return 1; } else { $hash->{fhem}{layout}= (); Log3 $name, 1, "Cannot open $filename"; return 0; } } ################## sub FRAMEBUFFER_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t]+", $def); return "Usage: define FRAMEBUFFER framebuffer_device" if(int(@a) != 3); my $name= $a[0]; my $fb_device= $a[2]; if (! (-r $fb_device && -w $fb_device)) { return "$fb_device isn't readable and writable"; } $hash->{fhem}{fb_device}= $fb_device; eval "use GD::Text::Align"; $hash->{fhem}{useTextAlign} = ($@ ? 0 : 1 ); if(!($hash->{fhem}{useTextAlign})) { Log3 $hash, 2, "$name: Cannot use text alignment: $@"; } eval "use GD::Text::Wrap"; $hash->{fhem}{useTextWrap} = ($@ ? 0 : 1 ); if(!($hash->{fhem}{useTextWrap})) { Log3 $hash, 2, "$name: Cannot use text wrapping: $@"; } readingsSingleUpdate($hash, 'state', 'Initialized',1); return undef; } ################## sub FRAMEBUFFER_updateDisplay($$) { my ($hash, $timeout) = @_; my $name = $hash->{NAME}; my $fbv = '/usr/local/bin/fbvs'; my $fd = $hash->{fd}; if (defined $fd) { close $fd; } if (-x $fbv) { if (defined $hash->{debugFile}) { use File::Spec; my $dfile = $hash->{debugFile}; my($vol,$dir,$file) = File::Spec->splitpath($dfile); if ((-e $dfile && -w $dfile) || -w $dir) { $fbv = "tee $dfile | $fbv"; } } # check if this is a display with a timeout if ($timeout) { # yes, execute enable command (e.g. enable backlight) fhem($hash->{enableCmd}) if $hash->{enableCmd}; } else { # yes, execute enable command (e.g. enable backlight) fhem($hash->{disableCmd}) if $hash->{disableCmd}; } if (FRAMEBUFFER_readLayout($hash)) { open($fd, "|".$fbv . ' -d '. $hash->{fhem}{fb_device}); binmode $fd; print $fd FRAMEBUFFER_returnPNG($name); # don't close the file immediately, as this will wait # for the fbv process to terminate which may take some time #close FBV; } } else { Log3 $name, 1, "$fbv doesn't exist or isn't executable, please install it"; } } ################## sub FRAMEBUFFER_Set($@) { my ($hash, @a) = @_; my $name =$a[0]; my $cmd = $a[1]; my $val = $a[2]; my $val2 = $a[3]; # usage check my $usage= "Unknown argument, choose one of " . join(' ', keys %sets); if (@a == 2) { if ($cmd eq "updateDisplay") { # just display the current layout again FRAMEBUFFER_updateDisplay($hash, 0); $usage = undef; } } elsif (@a == 3 || @a == 4) { if ($cmd eq "absLayoutNo") { my $layoutNo = (defined($val) && looks_like_number($val) && $val >= 0) ? $val : 0; my @layoutList = split(/ /,$hash->{layoutList}); my $noOfLayouts = @layoutList; if ($val < $noOfLayouts) { $hash->{fhem}{filename} = $layoutList[$layoutNo]; $hash->{fhem}{absLayoutNo} = $layoutNo; FRAMEBUFFER_updateDisplay($hash, 0); $usage = undef; } else { $usage = "absLayoutNo out of bounds, must be between 0 and $noOfLayouts"; } } elsif ($cmd eq "relLayoutNo") { my $relLayoutNo = (defined($val) && looks_like_number($val)) ? $val : 0; my @layoutList = split(/ /,$hash->{layoutList}); my $noOfLayouts = @layoutList; if ($noOfLayouts > 0) { $hash->{fhem}{absLayoutNo} += $relLayoutNo; if ($hash->{fhem}{absLayoutNo} > $noOfLayouts-1) { $hash->{fhem}{absLayoutNo} = $noOfLayouts-1; } elsif ($hash->{fhem}{absLayoutNo} < 0) { $hash->{fhem}{absLayoutNo} = 0; } $hash->{fhem}{filename} = $layoutList[$hash->{fhem}{absLayoutNo}]; FRAMEBUFFER_updateDisplay($hash, 0); $usage = undef; } else { $usage = "layoutList is empty, please set that attribute first"; } } elsif ($cmd eq "layoutFilename") { my $timeout = (defined($val2) && looks_like_number($val2) && $val2 >= 0) ? $val2 : 0; my $prevFilename = $hash->{fhem}{filename}; $hash->{fhem}{filename} = $val; FRAMEBUFFER_updateDisplay($hash, $timeout); if ($timeout > 0) { # nach timeout Sekunden wieder das aktuelle Layout anzeigen RemoveInternalTimer($hash); $hash->{fhem}{filename} = $prevFilename; InternalTimer(time() + $timeout, 'FRAMEBUFFER_rewindCounter', $hash, 0); } $usage = undef; } readingsBeginUpdate($hash); readingsBulkUpdate($hash,"absLayoutNo", $hash->{fhem}{absLayoutNo}); readingsBulkUpdate($hash,"layoutFilename", $hash->{fhem}{filename}); readingsEndUpdate($hash,1); } return $usage; } ################### sub FRAMEBUFFER_Attr(@) { my (undef, $name, $attr, $val) = @_; my $hash = $defs{$name}; my $msg = ''; Log3 $name, 5, "attr " . $attr . " val " . $val; if ($attr eq 'debugFile') { $hash->{debugFile} = $val; } elsif ($attr eq 'update_interval') { my $updateInterval = (defined($val) && looks_like_number($val) && $val >= 0) ? $val : -1; if ($updateInterval >= 0) { if ($updateInterval != AttrVal($hash->{NAME}, 'update_interval', 0)) { RemoveInternalTimer($hash); $hash->{updateInterval} = $updateInterval; if ($val > 0) { InternalTimer(1, 'FRAMEBUFFER_rewindCounter', $hash, 0); } } } else { $msg = 'Wrong update_interval defined. update_interval must be a number >= 0'; } } elsif ($attr eq 'layoutBasedir') { my $layoutBasedir = $val; if (-d $val && -r $val) { $hash->{layoutBasedir} = $val; } else { $msg = "$val is not a readable directory"; } } elsif ($attr eq 'layoutList') { $hash->{layoutList} = $val; } elsif ($attr eq 'startLayoutNo') { # Beim start des Moduls das anzuzeigenden Layout aus diesem Attribut nehmen if (!defined $hash->{fhem}{absLayoutNo}) { fhem "set $name absLayoutNo $val" ; } } elsif ($attr eq 'bgcolor') { } elsif ($attr eq 'enableCmd') { $hash->{enableCmd} = $val; } elsif ($attr eq 'disableCmd') { $hash->{disableCmd} = $val; } return ($msg) ? $msg : undef; } ################## sub FRAMEBUFFER_returnPNG($) { my ($name)= @_; my ($width,$height)= split(/x/, AttrVal($name,"size","128x160")); # # increase counter # if(defined($defs{$name}{fhem}) && defined($defs{$name}{fhem}{counter})) { $defs{$name}{fhem}{counter}++; } else { $defs{$name}{fhem}{counter}= 1; } # true color GD::Image->trueColor(1); # # create the image # my $S; # let's create a blank image, we will need it in most cases. $S= GD::Image->newTrueColor($width,$height); my $bgcolor = AttrVal($name,'bgcolor','000000'); #default bg color = black $bgcolor = RSS_color($S, $bgcolor); # $S->colorAllocate(0,0,0); # other colors seem not to work (issue with GD) $S->fill(0,0,$bgcolor); # wrap to make problems with GD non-lethal # # evaluate layout # eval { RSS_evalLayout($S, $name, $defs{$name}{fhem}{layout}) }; Log3 $name, 1, "Problem with layout " . $defs{$name}{fhem}{layout} . ", maybe wrong syntax or included images don't exist: $@" if $@ ne ""; # # return png image # return $S->png(0); } 1; =pod =item device =item summary Graphical display on a Linux framebuffer =begin html

FRAMEBUFFER

=end html =cut