#!/usr/bin/perl
our $VERSION='0.20130119'; # Please use format: major_revision.YYYYMMDD[hh24mi]
=head1 NAME
Roland_MDX-20-Manual-commander.pl - Allows you to move, cut, and measure using a Windows Joystick and your Roland MDX-20 (or other RML-1 compatible milling machine)
=head1 SYNOPSIS
bfxgen.pl - Puts a CSV file worth of text into graphics.
Options:
-c specify the com port the milling maching is connected to. eg: com1
if omitted, the first com port that can be sucessfully opened is assumed to be your plotter
-j specify which joystick to use (in case you've got more than 1)
if omitted, the first joystick found (number zero) is assumed to be the one to use
-r max step rate (eg: 100) - max number of steps to move at full joystick swing
-d delay milliseconds (eg: 100) - number of miliseconds to wait after sending a move command
-help brief help message
-man full documentation
=cut
######################################################################
use strict;
#use warnings; # same as -w switch above
use Getopt::Long; # Commandline argument parsing
use Pod::Usage; # Inbuilt documentation helper
use Win32::API; # for joystick control
#use Win32::SerialPort; # to write to the plotter
my %arg;&GetOptions( # Parse options and store copies into the %arg hash
'help|?' => \$arg{'help'}, # breif instructions
'man' => \$arg{'man'}, # complete manual
'c=s' => \$arg{'c'}, # COM port to use
'j=s' => \$arg{'j'}, # joystick to use
'r=i' => \$arg{'r'}, # rate
'd=i' => \$arg{'d'}, # delay
) or &pod2usage(2);
no warnings;
&pod2usage(1) if ($arg{'help'});
&pod2usage(-exitstatus => 0, -verbose => 2) if $arg{'man'};
#use warnings;
my $lastp='';
my ($joyinfoex,$joyGetPosEx); # joystick structure and data call
my ($midl,$midh)=(32768-7500,32768+7500); # joystick center limits
my ($range)=$midl + (65535-$midh);
my $rate=$arg{'r'} || 45; # max number of steps to move at full joystick swing
my $delay=$arg{'d'}/1000||0.02; # number of miliseconds to wait after sending a move command - .02 is smallest sensible amount @ 9600baud
my $port; # serial port handle.
my %state; # current(last) button state
my %buthead=qw( 1 A 2 B 4 X 8 Y 16 left 32 right 64 back 128 start);
my($x,$y,$z)=(0,0,0); # Starting plotter coordinates
# $arg{'compress'}=4 unless($arg{'compress'});
print "Use joysticks & dpad to move. A=cut, X=off, B=reset, 'back'=exit\n";
######################################################################
$port=&setup_serial2(); # Find the plotter
die "Could not open serial port" unless($port);
&countdevs(); # check the joystick support
&setupjoystick(); # load $joyinfoex,$joyGetPosEx
&showret($joyinfoex,&getjoy($joyinfoex)); # Show a demo return value;
&waitcenter(); # pause until they center the joystick - don't want the tool going nuts immediately!
# print "joyGetPosEx="; my $ret=$joyGetPosEx->Call($arg{'j'} || 0,$joyinfoex); print "$ret\n";
while(1) {
my $rc=&getjoy($joyinfoex); # 0 means OK.
if($rc!=0) {
&mdxreset();
die "Joystick problem: $rc";
}
my ($d,$e)=&showret($joyinfoex,$rc);
# what new things just came in from the joystick?
my($m,$v,$in,$c);
foreach my $event (@{$e}) {
if($event eq 'back') { # exit
$port->close if(ref $port);
print "You hit the 'back' button, which ends this program now\n";
&write_serial2(";;^IN;!MC0;\r\n"); # reset
exit(0);
}
if($event eq 'B') { # reset
&write_serial2(";;^IN;!MC0;\r\nV15.0;\r\n"); # 15 is full speed
}
if($event eq 'A') { # motor on
&write_serial2("^PR;^PA;\r\n!MC1;\r\nZ$x,$y,$z;\r\n"); # Z-40,-40,40;\r\n");
# &write_serial2("^PR;Z$x,$y,$z;^PA;\r\n!MC1;\r\nZ$x,$y,$z;\r\n"); # Z-40,-40,40;\r\n");
}
if($event eq 'X') { # motor off
&write_serial2("!MC0;\r\n");
}
if($event eq 'Y') { # keyboard mode
print "Not implimented yet\n";
}
if($event eq 'start') { # recording playback
print "Not implimented yet\n";
}
if($event eq 'dwPOV') { # Micro movement
$in=$d->{$event};
$y++ if($in==0);
$y-- if($in==18000);
$x++ if($in==9000);
$x-- if($in==27000);
$c++;
}
} # events
$v='dwXpos'; $m=0; $in=$d->{$v}; $m=$in-$midh+$midl if($in>$midh); $m=$in+1 if($in<$midl); if($m){ $m-=($range/2); $m=$m/$range*$rate; $x+=$m; $c++; print "$v $m\n"}
$v='dwYpos'; $m=0; $in=$d->{$v}; $m=$in-$midh+$midl if($in>$midh); $m=$in+1 if($in<$midl); if($m){ $m-=($range/2); $m=$m/$range*$rate; $y-=$m; $c++; print "$v $m\n"}
$v='dwRpos'; $m=0; $in=$d->{$v}; $m=$in-$midh+$midl if($in>$midh); $m=$in+1 if($in<$midl); if($m){ $m-=($range/2); $m=$m/$range*$rate; $z-=$m; $c++; print "$v $m\n"}
if($c) {
$x=int($x); $y=int($y); $z=int($z);
$x=0 if($x<0); $y=0 if($y<0);
print "x=$x; y=$y, z=$z;\n";
&write_serial2("Z$x,$y,$z;\r\n");
if($delay>0) { select(undef, undef, undef, $delay); }
}
} # main loop
=for code
dwSize dwFlags dwXpos dwYpos dwZpos dwRpos dwUpos dwVpos dwButtons dwButtonNumber dwPOV dwReserved1 dwReserved2
0 1 2 3 4 5 6 7 8 9
Left Thumbstick: (Pan tool)
(Y direction) inwards 4/0=up, 65535=down, 30000-40000=mid
(X direction) left 3/0=left
(X direction) right 3/65536
(Y direction) outwards 4/65536
Directional Pad (D-Pad) 65535(none), 0,4500 9000,13500 18000,22500 27000,31500
Same as Left Thumstick
(Pan X/Y), in small steps
Right Thumbstick: (Raise/Lower tool)
Tool Up (Z direction) 6/0=up 28000-35000=mid
Tool Down (Z dir.) 6/65535=down
Button Functions:
Y: fine-steps mode 9/8
X: Motor Off 9/4
B: Reset (motor off+up) 9/2
A: Motor On 9/1
Back: exit 9/64 & 10=1
Start: playback a recording 9/128 & 10=1
Left Shoulder button:
activate keyboard control 9/16
(right=32)
=cut
######################################################################
=for fails
sub setup_serial {
my $portno=($arg{'c'}=~/(\d+)/) if(defined $arg{'c'});
my @portlist=($portno);
@portlist=(0..33) unless(defined $portno);
foreach my $p (@portlist) {
my $port = Win32::SerialPort->new("COM$p"); # this fails sometimes, when plain "open" works
if($port) {
if(0) { # this mucks up the port:-
$port->baudrate(9600);
$port->parity("none");
$port->handshake("rts");
$port->databits(8);
$port->stopbits(1);
$port->read_char_time(0);
$port->read_const_time(1);
}
print "Opened COM$p sucessfully.\n";
return $port;
} else {
print "Cannot open COM$p: $!\n";
}
}
return $port;
} # setup_serial
sub write_serial {
my($what)=@_;
$port->write($what);
print "MDX-20: $what\n";
# $port->close;
}
=cut
sub setup_serial2 {
my $portno=($arg{'c'}=~/(\d+)/) if(defined $arg{'c'});
my @portlist=($portno);
@portlist=(0..33) unless(defined $portno);
foreach my $p (@portlist) {
my $rc=open(SER,'>',"COM$p:");
if($rc) {
print "Opened COM$p sucessfully.\n";
my $old_fh = select(SER);
$| = 1; # set autoflush
select($old_fh);
return $rc;
} else {
print "Cannot open COM$p: $!\n";
}
} # portlist
return undef;
} # setup_serial2
sub write_serial2 {
my($what)=@_;
print SER $what;
print "MDX-20: $what\n";
}
# pause until they center the joystick - don't want the tool going nuts immediately!
sub waitcenter {
my $centered=0;
my $p='';
RECENTER:
while(!$centered) {
my $rc=&getjoy($joyinfoex); # 0 means OK.
my ($d,$e)=&showret($joyinfoex,$rc,$p);
if($rc==0) { # OK
$centered=1 if(
( $d->{'dwButtonNumber'}==0 ) &&
( $d->{'dwButtons'}==0 ) &&
( $d->{'dwPOV'}==65535 ) &&
( $d->{'dwXpos'}>$midl ) &&
( $d->{'dwXpos'}<$midh ) &&
( $d->{'dwYpos'}>$midl ) &&
( $d->{'dwYpos'}<$midh ) &&
( $d->{'dwRpos'}>$midl ) &&
( $d->{'dwRpos'}<$midh )
);
if(!$centered) {
$p="Please center all your joystick controls to continue...";
} else { $p=''; }
}
} # centered
&debounce(8,'orange Y','Press the %s button to begin...');
}
sub debounce {
my($butn,$butdesc,$msg)=@_;
my $p='';
my $continue=0;
# wait for press
while(!$continue) {
my $rc=&getjoy($joyinfoex); # 0 means OK.
my ($d,$e)=&showret($joyinfoex,$rc,$p);
if($rc==0) { # OK
$continue=1 if(
( $d->{'dwButtonNumber'}==1 ) &&
( $d->{'dwButtons'}==$butn )
);
}
$p=sprintf($msg,$butdesc);
} # continue
my $ret=0; $p="To continue, now release $butdesc";
# wait for release
while(!$ret) {
my $rc=&getjoy($joyinfoex); # 0 means OK.
my ($d,$e)=&showret($joyinfoex,$rc,$p);
if($rc==0) { # OK
$ret=1 if(
( $d->{'dwButtonNumber'}==0 ) &&
( $d->{'dwButtons'}==0 )
);
}
} # return now
} # debounce
sub getjoy {
my($joyinfoex)=@_;
my $ret=$joyGetPosEx->Call($arg{'j'} || 0,$joyinfoex) . ' ';
# my $ret=$joyGetPosEx->Call($arg{'j'} || 0,$joyinfoex) . ' ';
return $ret;
}
sub countdevs {
# How many joysticks does the driver support?
my $joyGetNumDevs=Win32::API->new('WinMM', 'int joyGetNumDevs()');
die "Win32 problem - cannot call 'joyGetNumDevs'" unless(ref $joyGetNumDevs);
print "joyGetNumDevs="; my $numj=$joyGetNumDevs->Call(); print "$numj\n";
}
sub showret {
my($joyinfoex,$rc,$msg)=@_;
my $p=''; my %d; my @e;
if($rc!=0) {
$p.="Error code: $rc encountered. ";
$p.='(This means: joystick not connected) ' if($rc==165);
} else {
foreach my $i (qw( dwSize dwFlags dwXpos dwYpos dwZpos dwRpos dwUpos dwVpos dwButtons dwButtonNumber dwPOV dwReserved1 dwReserved2 )) {
my $j=$joyinfoex->{$i};
push @e,$i if($state{$i}!=$j);
$d{$i}=$j;
$p.=$j . ' ';
}
foreach my $k (keys %buthead) {
if($d{'dwButtons'}&$k) {
$p.=$buthead{$k} . ' ';
push @e,$buthead{$k} unless($state{'dwButtons'}&$k);
}
}
}
$p.="\n$msg" if((defined $msg)&&($msg ne ''));
print "$p\n" unless($p eq $lastp);
#print "dwB=$d{'dwButtonNumber'} p=$p\n" unless($p eq $lastp);
$lastp=$p;
%state=%d; # remember last state
return \%d,\@e;
} # showret
sub setupjoystick {
# Define the structure we need to get all this info (might already be defined in Win32::API - I didn't look)
typedef Win32::API::Struct JOYINFOEX => (
'LONG', 'dwSize', # size of structure
'LONG', 'dwFlags', # flags to indicate what to return
'LONG', 'dwXpos', # x position L/R of LHS joystick
'LONG', 'dwYpos', # y position U/D of LHS joystick
'LONG', 'dwZpos', # z position L-Trigger(32767=>65408). R-Trigger (32767=>128)
'LONG', 'dwRpos', # rudder/4th axis position L/R RHS joystick
'LONG', 'dwUpos', # 5th axis position U/D RHS joystick
'LONG', 'dwVpos', # 6th axis position ?
'LONG', 'dwButtons', # button states
'LONG', 'dwButtonNumber', # current button number pressed
'LONG', 'dwPOV', # point of view state
'LONG', 'dwReserved1', # reserved for communication between winmm driver
'LONG', 'dwReserved2', # reserved for future expansion
);
$joyinfoex=Win32::API::Struct->new( 'JOYINFOEX' ); # Register the structure
# Windows wants us to fill in some parts of the structure before we use it:
$joyinfoex->{dwSize}=Win32::API::Struct::sizeof($joyinfoex);
$joyinfoex->{dwFlags}= 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80; # JOY_RETURNX JOY_RETURNY JOY_RETURNZ JOY_RETURNR JOY_RETURNU JOY_RETURNV JOY_RETURNPOV JOY_RETURNBUTTONS
# "import" the call we need to get the joystick data.
$joyGetPosEx=Win32::API->new('WinMM', 'int joyGetPosEx(int a, JOYINFOEX *p)');
} # setupjoystick
sub details {
my($joyinfoex)=@_;
#if($ret!=0) {
# print "non-zero return code means some kind of error.\n";
# print "if it's 165 - your joystick is probably off or not connected/detected?\n";
# print "sleeping 5 seonnds...\n";sleep(5);
#}
# Show demo results
print "dwSize=" . $joyinfoex->{ 'dwSize' } . "\n";
print "dwFlags=" . $joyinfoex->{ 'dwFlags' } . "\n";
print "dwXpos=" . $joyinfoex->{ 'dwXpos' } . "\n";
print "dwYpos=" . $joyinfoex->{ 'dwYpos' } . "\n";
print "dwZpos=" . $joyinfoex->{ 'dwZpos' } . "\n";
print "dwRpos=" . $joyinfoex->{ 'dwRpos' } . "\n";
print "dwUpos=" . $joyinfoex->{ 'dwUpos' } . "\n";
print "dwVpos=" . $joyinfoex->{ 'dwVpos' } . "\n";
print "dwButtons=" . $joyinfoex->{ 'dwButtons' } . "\n";
print "dwButtonNumber=" . $joyinfoex->{ 'dwButtonNumber' } . "\n";
print "dwPOV=" . $joyinfoex->{ 'dwPOV' } . "\n";
print "dwReserved1=" . $joyinfoex->{ 'dwReserved1' } . "\n";
print "dwReserved2=" . $joyinfoex->{ 'dwReserved2' } . "\n";
print "If you don't see numbers above, your joystick isn't connected or turned on...\n";
print "looping in 2 seconds...\n";
sleep(2);
}
=pod here
=head1 DESCRIPTION
B