K8055 mh interface

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

K8055 mh interface

Matthew Williams-2
Enclosed is the first release of my K8055 interface code for mh.  It comes
in two parts, the mh stuff and a standalone daemon.

The daemon source is in the .tar.bz2 file.  Two files will be created, the
daemon and a client.  The daemon can be run on any unix-like box that has
libusb.  The client, and mh parts too, can be run on any machine with TCP
connectivity to the daemon.  The client-server protocol is plain text, so
you can send commands through a standard telnet prompt.  Look at the daemon
code if you are interested in the protocol itself.  Multiple clients can
access the daemon at the same time.  Port settings are on a last set wins
basis if there are conflicting settings.

The mh part is composed of two files.  The .pm is the interface code and the
.pl is meant to be placed in mh/web/bin.  It is a crude, but complete
interface to the main code.  Type perldoc k8055.pm to get detailed
instructions.  You can read any port and write to any port.

The code is designed to automatically (every 10 seconds by default) read a
subset of all the input ports.  In other words, asking for the current state
of digital port 1 actually returns the last retrieved value of digital port
1, it doesn't ask the board at that point in time.  You can call
K8055->update() to get the code to immediately request new values, however
this will still happen asynchronously, so it is not guaranteed that the next
call to read values includes the updated value.

You need to tell K8055 which ports to get values for.  If you don't, no
values will be retrieved, either on the 10 second schedule or on a manual
update call.  If the value of a monitored port changes, the value of
K8055->state_now will be set to the name of the port (i.e. "digital 1" if
digital port 1 goes from 0 to 1 or from 1 to 0).

Sample code is below.  In order to use the web interface as written, you
need to create $k8055 somewhere in your code at a minimum.  As well, there
are mh parameters k8055_host and k8055_port that should point to the daemon.

Matt



use K8055;
$k8055 = new K8055;

$k8055->tie_event("&k8055_updated($state)");

if ($Reload) {
        # we want to retrieve values for digital port 1 and analogue port 2
        $k8055->doUpdateDigital(1);
        $k8055->doUpdateAnalogue(2);
}

sub k8055_updated {
        my ($state)=@_;

        speak ("port $state just changed");
}

$v_show_k8055 = new Voice_Cmd("show k8055");

if ($state = $v_show_k8055->said) {
        my $response;
        $response = "digital 1 is ".$k8055->readDigital(1)."\n";
        $response .= "analogue 2 is ".$k8055->readAnalogue(2)."\n";
        respond(text=>$response);
}

my $numDigitalInputPorts=5;
my $numAnalogueInputPorts=2;
my $numCounters=2;
my $numMaxInputPorts=5;
my $numDigitalOutputPorts=8;
my $numAnalogueOutputPorts=2;
my $numMaxInputPorts=5;

my $myURL='/bin/k8055.pl';

my $i;

my %param;
foreach my $param (@ARGV) {
        $param =~ /^(.+)=(.+)$/ && do { $param{$1}=$2 };
}

my $html=qq[
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<title>K8055 Interface</title>
];

if ($param{autorefresh} > 0) {
        $html.=qq[<meta http-equiv="refresh" content="$param{autorefresh}; URL=$myURL?autorefresh=$param{autorefresh}">\n];
}

$html.=qq[</head>
<h1>K8055 Interface</h1>
];

my @result;
if ($param{action} =~ /d(\d)-(.+)/) {
        my $value=$2 eq 'on' ? 1 : 0;
        $k8055->writeDigital($1,$value);
        push(@result,"Setting Digital Port $1 to $2");
} elsif ($param{action} eq 'analogue') {
        for ($i=1; $i <= $numAnalogueOutputPorts; $i++) {
                if ($param{"a$i"} ne '') {
                        $k8055->writeAnalogue($i,$param{"a$i"});
                        push(@result,"Setting Analogue Port $i to ".$param{"a$i"});
                }
        }
} elsif ($param{action} eq 'counter') {
        for ($i=1; $i <= $numCounters; $i++) {
                if ($param{"c$i"} ne '') {
                        $k8055->setDebounce($i,$param{"c$i"});
                        push(@result,"Setting Counter $i Debounce to ".$param{"c$i"}." ms");
                }
        }
} elsif ($param{action} =~ /reset-(\d)/) {
        $k8055->resetCounter($1);
        push(@result,"Resetting Counter $1");
} elsif ($param{action} =~ /update-(.)(\d)/) {
        if ($1 eq 'd') {
                $k8055->doUpdateDigital($2);
                push(@result,"Now updating digital port $2");
        } elsif ($1 eq 'a') {
                $k8055->doUpdateAnalogue($2);
                push (@result,"Now updating analogue port $2");
        }
        if ($1 eq 'c') {
                $k8055->doUpdateCounter($2);
                push(@result,"Now updating counter $2");
        }
} elsif ($param{action} eq 'autoupdate') {
        $k8055->setUpdatePeriod($param{autoupdatetime});
        push(@result,"Setting auto-update period to $param{autoupdatetime} seconds");
} elsif ($param{action} eq 'update') {
        $k8055->update();
        push(@result,"Requesting manual update");
}

if ($#result >= 0) {
        $html.="<h2>Results</h2>\n";
        foreach my $result (@result) {
                $html .= "<p>$result</p>\n";
        }
}

$html.=qq[<h2>Inputs</h2>
<table border="1"><tr><th></th>];
for ($i=1; $i <= $numMaxInputPorts; $i++) {
        $html .= qq[<th>$i</th>];
}
$html.=qq[</tr><tr><th>Digital</th>];

for ($i=1; $i <= $numDigitalInputPorts; $i++) {
        $html .= qq[<td align="center">].$k8055->readDigital($i)."</td>\n";
}

$html.=qq[</tr><tr><th>Analogue</th>];

for ($i=1; $i <= $numAnalogueInputPorts; $i++) {
        $html .= qq[<td align="center">].$k8055->readAnalogue($i)."</td>\n";
}

$html.=qq[</tr><th>Counters</th>];

for ($i=1; $i <= $numCounters; $i++) {
        $html .= qq[<td align="center">].$k8055->readCounter($i)."</td>\n";
}

$html .= qq[</table>];

$html.=qq[<form name="main" action="$myURL" method="post">
<p><input type="submit" name="action" value="refresh">
<input type="submit" name="action" value="update"></p>
<p><input type="text" name="autorefresh" value="$param{autorefresh}" maxlength="2"><input type="submit" name="action" value="autorefresh"></p>
];

$html .= qq[<h2>Port Updates</h2>
<p>];

for ($i=1; $i <= $numDigitalInputPorts; $i++) {
        $html.=qq[<input type="submit" name="action" value="update-d$i">\n];
}
for ($i=1; $i <= $numAnalogueInputPorts; $i++) {
        $html.=qq[<input type="submit" name="action" value="update-a$i">\n];
}
for ($i=1; $i <= $numCounters; $i++) {
        $html.=qq[<input type="submit" name="action" value="update-c$i">\n];
}

$html.= qq[</p>
<h2>Digital Outputs</h2>
<p>];

for ($i=1; $i <= $numDigitalOutputPorts; $i++) {
        $html.=qq[<input type="submit" name="action" value="d$i-off">];
        $html.=qq[<input type="submit" name="action" value="d$i-on">];
}

$html.=qq[</p>
<h2>Analogue Outputs</h2>
];

for ($i=1; $i <= $numAnalogueOutputPorts; $i++) {
        $html.=qq[<p>Port $i: <input type="text" name="a$i" value="" maxlength=3></p>];
}
$html .= qq[<p><input type="submit" name="action" value="analogue"></p>\n];

$html.=qq[<h2>Counters</h2>
<p>];

for ($i=1; $i <= $numCounters; $i++) {
        $html.=qq[<input type="submit" name="action" value="reset-$i">\n];
}

$html.="</p>";
for ($i=1; $i <= $numCounters; $i++) {
        $html.=qq[<p>Counter $i: <input type="text" name="c$i" value="" maxlength=4></p>];
}
$html .= qq[<p><input type="submit" name="action" value="counter"></p>\n];

$html.= qq[<h2>Miscellaneous</h2>
<p>Update Time: <input type="text" name="autoupdatetime" value="" maxlength=3></p>
<p><input type="submit" name="action" value="autoupdate"></p>
</form>];

$html.="</body></html>";
return $html;

# K8055.pm
# $Revision: 20 $
# $Date: 2006-01-17 17:14:55 -0500 (Tue, 17 Jan 2006) $
#

=head1 Name

K8055 daemon MisterHouse Interface

=head1 Credit

Written by Matthew Williams

Released under the GPL.

=head1 Instructions

This is the mh client interface for my K8055 interface board daemon.
Once you have created a K8055 object, you must tell it which input ports you are interested in using the doUpdate... methods.

  $k8055 = new K8055;
  if ($Reload) {
    $k8055->doUpdateDigital(4);
    $k8055->doUpdateAnalogue(2);
  }

Whenever the value of these ports change, $k8055->state_now will return the name of the port that has changed (i.e. 'digital 4').  The state of each port is checked every 10 seconds (by default, can be changed).

Use the write... methods to change the value of the output ports.

=head1 mh.ini parameters

=over

=item B<k8055_host>

Hostname that is running k8055d.

=item B<k8055_port>

Port on hostname to which daemon is listening.

=back

=head1 Methods

=over

=item B<state_now>

For one pass, will return the name of the input port that has just changed.  

  if ($state=$k8055->state_now()) {
    if ($state eq 'digital 5') {
      speak "The digital 5 port just changed value";
    }
    if ($state eq 'counter 1') {
      speak "Counter number 1 just changed";
    }
    if ($state eq 'analogue 2') {
      speak ("Analogue port 2 now reads ".$k8055->readAnalogue(2));
    }
  }

=cut


use strict;
use Socket_Item;

package K8055;

@K8055::ISA = ('Generic_Item');

my $numDigitalInputPorts=5;
my $numAnalogueInputPorts=2;
my $numCounters=2;
my $idNumber=0;

sub new {
        my ($class,$hostname,$port)=@_;
        my $self={};
        bless $self, $class;

        $self->{state}=undef;
        $self->{state_now}=undef;
        $self->{said}=undef;
        $self->{state_changed}=undef;
        $self->{last_event}=undef;
        $self->{digital}=[-1,-1,-1,-1,-1];
        $self->{analogue}=[-1,-1];
        $self->{counter}=[-1,-1];
        $self->{updateDigital}=[0,0,0,0,0];
        $self->{updateAnalogue}=[0,0];
        $self->{updateCounter}=[0,0];
        $self->{updatePeriod}=10;
        $self->{idNumber}=$idNumber;
        $idNumber++;

        $self->initialize($hostname,$port);

        return $self;
}

sub printDebug {
        my ($self,$message)=@_;

        if (! $main::Debug{k8055}) {
                return;
        }

        $self->printMessage($message);
}

sub printMessage {
        my ($self, $message)=@_;

        &main::print_log("K8055 (".$self->{idNumber}."): $message");
}

sub initialize {
        my ($self,$hostname,$port) = @_;

        $self->{startUpRun}=1;
        $self->printMessage("initializing");

        $hostname=$::config_parms{k8055_host} unless $hostname;
        $port=$::config_parms{k8055_port} unless $port;

        $self->{socket} = new Socket_Item(undef, undef, "$hostname:$port", 'k8055-'.$self->{idNumber}, 'tcp', 'record', "\n");
        $self->{socket}->start();
        &main::MainLoop_pre_add_hook(\&checkForData,undef,$self); # not persistent
        }

sub checkForData {
        my ($self)=@_;
        my $data;

        if (&::new_second($self->{updatePeriod})) {
                if ($self->{socket}->active()) {
                        $self->requestUpdates();
                } else {
                        $self->{socket}->start();
                }
        }

        if ($self->{socket}->inactive_now()) {
                $self->printMessage("can't talk to daemon");
        }

        if ($data = $self->{socket}->said()) {
                $self->parseResponse ($data);
        }
}

sub parseResponse {
        my ($self,$data)=@_;

        $data =~ s/\x00//g;
        $data =~ s/\n//g;
        my ($success, $command, $porttype, $portnumber, $value) = split(/\s+/,$data);
        if ($success eq 'error') {
                $self->printDebug("received error message ($data)");
                return;
        }
        if ($success ne 'ok') {
                $self->printMessage("received unknown message success=$success ($data)");
                return;
        }
        if ($command eq 'value') {
                $self->printDebug("received value update ($data)");
                if ($porttype eq 'digital') {
                        if ($self->{digital}[$portnumber-1] != $value) {
                                $self->set("digital $portnumber");
                        }
                        $self->{digital}[$portnumber-1]=$value;
                        return;
                }
                if ($porttype eq 'analogue') {
                        if ($self->{analogue}[$portnumber-1] != $value) {
                                $self->set("analogue $portnumber");
                        }
                        $self->{analogue}[$portnumber-1]=$value;
                        return;
                }
                if ($porttype eq 'counter') {
                        if ($self->{counter}[$portnumber-1] != $value) {
                                $self->set("counter $portnumber");
                        }
                        $self->{counter}[$portnumber-1]=$value;
                        return;
                }
        }
        if ($command eq 'confirmation') {
                $self->printDebug("confirmation message received ($data)");
                return;
        }
        $self->printMessage("received unknown command=$command ($data)");
}

sub requestUpdates {
        my ($self)=@_;

        $self->printDebug("requesting updated values");
        $self->sendCommand('update');
        for (my $i=1; $i <= $numDigitalInputPorts; $i++) {
                if ($self->{updateDigital}[$i-1] == 1) {
                        $self->sendCommand("read digital $i");
                }
        }
        for (my $i=1; $i <= $numAnalogueInputPorts; $i++) {
                if ($self->{updateAnalogue}[$i-1] == 1) {
                        $self->sendCommand("read analogue $i");
                }
        }
        for (my $i=1; $i <= $numCounters; $i++) {
                if ($self->{updateCounter}[$i-1] == 1) {
                        $self->sendCommand("read counter $i");
                }
        }
}

sub sendCommand {
        my ($self,$command)=@_;

        $self->printDebug("sending command $command");

        if ($self->{socket}->active()) {
                $self->{socket}->set($command);
        }
}

=item B<readAnalogue, readDigital, readCounter>

Returns the value of the given port as read on the last check.

  # returns the last read value of digital port 4
  $k8055->readDigital(4);

=cut

sub readDigital {
        my ($self,$portNum) = @_;

        return $self->{digital}[$portNum-1];
}

sub readAnalogue {
        my ($self,$portNum) = @_;

        return $self->{analogue}[$portNum-1];
}

sub readCounter {
        my ($self,$portNum) = @_;

        return $self->{counter}[$portNum-1];
}

=item B<writeAnalogue, writeDigital>

Sets the value of the output ports.

  # sets analogue port 2 to 143/255 x 5V (i.e. 0=0 V, 255 = 5 V).
  $k8055->writeAnalogue(2,143);

=cut

sub writeDigital {
        my ($self,$portNum,$value) = @_;

        $self->writePort('digital',$portNum,$value);
}

sub writeAnalogue {
        my ($self,$portNum,$value) = @_;

        $self->writePort('analogue',$portNum,$value);
}

sub writeCounter {
        my ($self,$portNum,$value) = @_;

        $self->writePort('counter',$portNum,$value);
}

sub writePort {
        my ($self, $portType, $portNum, $value)=@_;

        $self->sendCommand("write $portType $portNum, $value");
}

=item B<resetCounter>

Resets the given counter.

  # resets counter 2
  $k8055->resetCounter(2);

=cut

sub resetCounter {
        my ($self, $portNum) = @_;

        $self->sendCommand("reset $portNum");
}

=item B<setDebounce>

Sets the debounce of each counter in milliseconds.

  # sets the debounce of timer 1 to 350ms
  $k8055->setDebounce(1,350);

=cut

sub setDebounce {
        my ($self, $portNum, $debounce) = @_;

        $self->sendCommand("debounce $portNum $debounce");
}

=item B<doUpdateAnalogue, doUpdateDigital, doUpdateCounter>

Tells the object to care about the given port(s).  If this command isn't called, then the corresponding read... method will always return -1 and the state variable will never be set.

  # read and monitor digital ports 2, 3 and 5
  $k8055->doUpdateDigital(2,5,3);

=cut

sub doUpdateDigital {
        my ($self, @ports) =@_;

        foreach my $portNum (@ports) {
                $self->{updateDigital}[$portNum-1]=1;
        }
}

sub doUpdateAnalogue {
        my ($self, @ports) =@_;

        foreach my $portNum (@ports) {
                $self->{updateAnalogue}[$portNum-1]=1;
        }
}

sub doUpdateCounter {
        my ($self, @ports) =@_;

        foreach my $portNum (@ports) {
                $self->{updateCounter}[$portNum-1]=1;
        }
}

=item B<setUpdatePeriod>

Sets how often we update the input port readings.  Defaults to 10 seconds.

  # input ports will be read every 2 seconds.
  $k8055->setUpdatePeriod(2);

=back

=cut

sub setUpdatePeriod {
        my ($self, $period)=@_;

        $self->{updatePeriod}=$period;
}

=item B<getUpdatePeriod>

Gets the current auto update period in seconds.

  print "current update period is ".$k8055->getUpdatePeriod()." seconds";

=cut

sub getUpdatePeriod {
        my ($self)=@_;

        return $self->{updatePeriod};
}

=item B<update>

Immediate requests updated values from the ports that we are interested in.  Note that the data will not immediately be available on return from this method as updates are asynchronous.

  $k8055->update();

=cut

sub update {
        my ($self)=@_;

        $self->requestUpdates();
}

1;

=head1 Version Info

The following are automated subversion version strings:
  $Revision: 20 $
  $Date: 2006-01-17 17:14:55 -0500 (Tue, 17 Jan 2006) $

=cut

k8055d.tar.bz2 (12K) Download Attachment