#!/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 lets you use your joystick to manually control a CNC milling machine (i.e. plotter), like an MDX-20 When first run, it will attempt to auto-find your plotter and joystick, and issue a reset to your plotter. Use the following joystick movements to control your machine: =begin html

Controller button layout

=end html Left Thumbstick: (Pan tool) (Y direction) inwards (X direction) left (X direction) right (Y direction) outwards Directional Pad (D-Pad) Same as Left Thumstick (Pan X/Y), in small steps Right Thumbstick: (Raise/Lower tool) Tool Up (Z direction) Tool Down (Z dir.) Button Functions: Y: fine-steps mode X: Motor Off B: Reset (motor off+up) A: Motor On Back: exit Start: playback a recording Left Shoulder button: activate keyboard control =head1 README Allows you to move, cut, and measure using a Windows Joystick and your Roland MDX-20 (or other RML-1 compatible milling machine) =head1 PREREQUISITES Getopt::Long; Pod::Usage; Win32::API; =head1 SCRIPT CATEGORIES Win32 Win32/Utilities =head1 AUTHOR Chris Drake, Ecdrake@cpan.orgE =head1 COPYRIGHT AND LICENSE Copyright (C) 2013 by Chris Drake This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.10.1 or, at your option, any later version of Perl 5 you may have available. =cut ##### =for RML-1 Notes Mode 1 ; [Delimiter] command separator \s [special delimiter] space or tab only D@!^_ [Command] (D=any letter) 000 [Paramater] , [Delimiter for paramaters] cr+lf [Terminator - optional usually] Mode 2 (similar) Mode 1+2 ! preceeds a mode-2 command ^ call mode 2 IN; Initialize, stop tool, move tool up, clear errors !MC0; prohibit rotation (waits too I think) V15.0; Velocity Z-Axis (mm/sec) ^PR; Change to relative paramater mode Z0,0,2420; move to x,y,z (at speed set by V) in whatever coordinate mode was selected ^PA; Change to absolute paramater mode !MC1; makes motor rotate when the tool gets moved ;;^IN;!MC0; V15.0;^PR;Z0,0,2420;^PA; !MC1; Z-40,-40,2420; Z-40,-40,40; V1.0;Z-40,-40,-120; Z1421,-40,-120; Z1421,551,-120; Z-40,551,-120; Z-40,0,-120; Z251,0,-120; Z251,0,-119; Z252,0,-117; Z254,0,-107; Z254,0,-106; Z262,0,-77; Z263,0,-75; Z265,0,-72; Z266,0,-71; Z269,0,-70; Z276,0,-67; Z398,0,-89; Z398,0,-91; Z398,0,-92; Z403,0,-120; Z1381,0,-120; Z1381,511,-120; Z0,511,-120; Z0,164,-120; Z0,162,-103; Z0,161,-92; Z0,40,-120; Z81,40,-120; Z283,271,-71; Z283,271,-73; Z281,271,-94; Z281,271,-95; Z276,271,-120; Z240,271,-120; V15.0;Z240,271,2420; !MC0;^IN; =cut # The end.