#!/usr/bin/perl -w # # Copyright (c) 2007 Oleksandr Tymoshenko # Copyright (c) 2014 Sulev-Madis Silber # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # use strict; use warnings FATAL => 'all'; use Time::HiRes qw(usleep); use GPIO; $| = 1; my (@pins, @pinlist, $pinlist); my $pinstatus = {}; my $ws2801_elements_count_max = -1; my ($mode, $pins, $delay_us) = @ARGV; die 'invalid mode or pins' unless $mode && $mode =~ /^(?:poll|change|ws2801)$/i && $pins; $mode = lc $mode; @pins = sort { $a <=> $b } split ',', $pins; my $gpio = GPIO->new; $gpio->open || die(sprintf "Can't open GPIO controller dev: %s", $!); sub flags2str ($) { my ($flags) = @_; my @config; push @config, 'INPUT' if $flags & GPIO::PIN_INPUT; push @config, 'OUTPUT' if $flags & GPIO::PIN_OUTPUT; push @config, 'OPENDRAIN' if $flags & GPIO::PIN_OPENDRAIN; push @config, 'PUSHPULL' if $flags & GPIO::PIN_PUSHPULL; push @config, 'TRISTATE' if $flags & GPIO::PIN_TRISTATE; push @config, 'PULLUP' if $flags & GPIO::PIN_PULLUP; push @config, 'PULLDOWN' if $flags & GPIO::PIN_PULLDOWN; push @config, 'INVIN' if $flags & GPIO::PIN_INVIN; push @config, 'INVOUT' if $flags & GPIO::PIN_INVOUT; push @config, 'PULSATE' if $flags & GPIO::PIN_PULSATE; return join ',', @config; } sub pins_config ($) { my ($flags) = @_; foreach my $pin (@pins) { next unless defined $pin && $pin =~ /^\d+$/; next unless $gpio->is_pin_valid($pin); my $name = $gpio->get_pin_name($pin); my $caps = $gpio->get_pin_caps($pin); my $config_old = $gpio->get_pin_config($pin); my $value_old = $gpio->get_pin_value($pin); next unless $caps & $flags; $gpio->set_pin_config($pin, $flags); my $config_new = $gpio->get_pin_config($pin); $gpio->set_pin_value($pin, GPIO::LOW) if $config_new & GPIO::PIN_OUTPUT; my $value_new = $gpio->get_pin_value($pin); push @pinlist, $pin; $pinlist->{$pin} = 1; printf STDERR "[gpiocfg] %s %s %s: %s %s -> %s %s\n", $pin, $name, flags2str($caps), flags2str($config_old), $value_old, flags2str($config_new), $value_new; } } sub pins_poll () { foreach my $pin (@pinlist) { my $value = $gpio->get_pin_value($pin); my $prev_value = $pinstatus->{$pin}; next if defined $prev_value && $prev_value == $value; $pinstatus->{$pin} = $value; #next unless defined $prev_value; printf "%s=%s\n", $pin, $value; } } sub ws2801_write ($) { my ($input) = @_; my ($clock, $data) = @pinlist; $gpio->set_pin_value($data, GPIO::LOW); $gpio->set_pin_value($clock, GPIO::LOW); usleep 500; my @rgb_array = split /\s+/, $input; $ws2801_elements_count_max = $#rgb_array if $ws2801_elements_count_max < $#rgb_array; foreach my $rgb (@rgb_array) { my $bits; if ($rgb =~ /^\d+$/) { $bits = sprintf "%024b", $rgb; } elsif ($rgb =~ /^(\d+),(\d+),(\d+)$/) { $bits = sprintf "%08b%08b%08b", $1, $2, $3; } else { next; } next unless length $bits == 24; foreach my $bit (split //, $bits) { $gpio->set_pin_value($data, $bit ? GPIO::HIGH : GPIO::LOW); $gpio->set_pin_value($clock, GPIO::HIGH); $gpio->set_pin_value($clock, GPIO::LOW); } } $gpio->set_pin_value($data, GPIO::LOW); $gpio->set_pin_value($clock, GPIO::LOW); } sub proctitle { my ($suffix) = @_; $0 = sprintf "gpio %s pins=%s%s", $mode, join('|', @pins), (defined $suffix ? sprintf " %s", $suffix : ''); } sub custom_exit () { exit unless $gpio; foreach my $pin (@pinlist) { my $name = $gpio->get_pin_name($pin); my $caps = $gpio->get_pin_caps($pin); my $config = $gpio->get_pin_config($pin); next unless $config & GPIO::PIN_OUTPUT; if ($mode eq 'ws2801' && $ws2801_elements_count_max > -1) { ws2801_write join ' ', map '0,0,0', 0 .. $ws2801_elements_count_max; } my $value_old = $gpio->get_pin_value($pin); $gpio->set_pin_value($pin, GPIO::LOW); my $value_new = $gpio->get_pin_value($pin); printf STDERR "[gpiocfg] %s %s %s %s: %s -> %s\n", $pin, $name, flags2str($caps), flags2str($config), $value_old, $value_new; } $gpio->close; exit; } $SIG{$_} = \&custom_exit foreach (qw(TERM INT HUP)); if ($mode eq 'poll') { $delay_us = 1000000 unless $delay_us; proctitle sprintf "delay=%sus", $delay_us; pins_config(GPIO::PIN_INPUT|GPIO::PIN_PULLDOWN); print STDERR "READY\n"; while (1) { pins_poll; usleep $delay_us; } } elsif ($mode eq 'change') { proctitle; pins_config(GPIO::PIN_OUTPUT); print STDERR "READY\n"; while () { if (/^\s*(\d+)\s*=\s*(\d+)\s*$/) { my $pin = $1; my $value = $2; if (exists $pinlist->{$pin} && ($value == GPIO::HIGH || $value == GPIO::LOW)) { $gpio->set_pin_value($pin, $value); } } } } elsif ($mode eq 'ws2801') { proctitle; die(sprintf "mode '%s' needs 2 pins", $mode) unless $#pins == 1; pins_config(GPIO::PIN_OUTPUT|GPIO::PIN_PULLDOWN); print STDERR "READY\n"; while () { if (/^\s*all\s*off\s*$/i) { if ($ws2801_elements_count_max > -1) { ws2801_write join ' ', map '0,0,0', 0 .. $ws2801_elements_count_max; } } else { ws2801_write $_; } } } custom_exit;