Aug 7, 2009
The UnExpressiveBrush
The UnExpressiveBrush was built to test the capabilities of the ultrasound and acceleration sensors by Parallax. The intention was to build a system where one could simulate painting with a very wet brush. With it one can paint by sprinkling with vigorous brush movements in front of a canvas. However, we cut a few corners and ended up with a system that was significantly less usable than the original plan. First of all, we did not build a paint sprinkling simulation. Instead we used GIMP and its ready-made brushes for the painting. We also did not use the acceleration information from the brush for anything else except sending a mouse down event whenever a certain threshold was exceeded and a mouse-up when acceleration returned to lower values. As a result we had the ability to sprinkle paint with high acceleration movements along one axis and to spread paint by tilting the brush to one direction. The same movements were used to select colors from the palettes available in GIMP. The laptop keyboard was needed for switching windows.
The UnExpressiveBrush from Poika Isokoski and Harri Rantala.
The system consisted of three pieces of software. One worked in BS2 to get the x and y coordinates from the ultrasound sensors and send that together with the acceleration data to a PC through a serial connection. The second part of software received this data and translated it into mouse events that were sent to Windows to deliver to whichever application happened to own the screenspace. The third piece of software was GIMP that received mouse movement and button up and button down events just like it normally does.
Below you can find two potentially useful snippets from the code. The first is the C++ class that we used to read the serial port in Windows XP. The second is the code that sent the mouse events. Putting the whole Visual C++ project online would not be much more helpful that these snippets since most people will not be using Visual C++ anyway. Finally as the last item below you can find the BS2 code that we used to read the sensors.
First the serial connection class:
#include #include #include #include class BS2Connection { public: BS2Connection(){ hCom=INVALID_HANDLE_VALUE; nread=0; } public: ~BS2Connection(void){ closePort(); } private: HANDLE hCom; char ch; char line[1024]; unsigned long nread; public: char getchar(int i){ return line[i]; } void getLine(char* str){ strcpy(str,line); } // returns 0 on success !=0 on error int openPort(LPCSTR portname){ if(hCom!=INVALID_HANDLE_VALUE) closePort(); DCB dcb; BOOL fSuccess; hCom = CreateFile( portname, GENERIC_READ, 0, // comm devices must be opened w/exclusive-access NULL, // no security attributes OPEN_EXISTING, // comm devices must use OPEN_EXISTING 0, // not overlapped I/O NULL); // hTemplate must be NULL for comm devices if (hCom == INVALID_HANDLE_VALUE) { // Handle the error. printf ("CreateFile failed with error %d.\n", GetLastError()); return 1; } // We will build on the current configuration, and skip setting the size // of the input and output buffers with SetupComm. fSuccess = GetCommState(hCom, &dcb); if (!fSuccess) { // Handle the error. printf ("GetCommState failed with error %d.\n", GetLastError()); return 2; } // Fill in the DCB: baud=57,600 bps, 8 data bits, no parity, and 1 stop bit. dcb.BaudRate = CBR_9600; // set the baud rate dcb.ByteSize = 8; // data size, xmit, and rcv dcb.Parity = NOPARITY; // no parity bit dcb.StopBits = ONESTOPBIT; // one stop bit fSuccess = SetCommState(hCom, &dcb); if (!fSuccess) { // Handle the error. printf ("SetCommState failed with error %d.\n", GetLastError()); return 3; } return 0; } int closePort(){ CloseHandle(hCom); hCom=INVALID_HANDLE_VALUE; return 0; } int next(){ return ReadFile(hCom,&ch,1,&nread,NULL); } int nextLine(){ int status=0; memset(line,0,1024); int i=0; status=next(); while(ch!='\r' && i<1023){ if(status==0) status; line[i]=ch; status=next(); i++; } return status; } };
This is the main program that uses the class above to read the data from the serial port and sends mouse events appropriately.
#include #include #include void sendMouseEvent(int x, int y, int click){ mouse_event(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_MOVE,x,y,0,0); if (click>0){ mouse_event(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_LEFTDOWN,x,y,0,0); } if (click<0){ mouse_event(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_LEFTUP,x,y,0,0); } } int _tmain(int argc, _TCHAR* argv[]) { float oldx,oldy=0; BS2Connection c; c.openPort("COM9"); char line[1024]; while(true){ int result=c.nextLine(); if(result==0){ DWORD error=GetLastError(); std::cout << "error: " << error << std::endl; } else{ c.getLine(line); std::cout << "\"" <<line <<"\""<> y >> x >> click; float xmin=1100; float ymin=3200; float xmax=3300; float ymax=4200; if(xxmax) x=oldx; if(yymax) y=oldy; if(click-500)click=-1; else click=0; sendMouseEvent((int)(x-xmin)/(xmax-xmin)*65535.0, (int)(ymax-ymin-(y-ymin))/(ymax-ymin)*65535.0,click); oldx=x; oldy=y; } } return 0; }
Finally the BS2 Basic code:
' 2009 mlab.taik.fi/paja & Harri Rantala (UCIT DIE09) ' Using PING))) Ultrasonic sensor and Accelerometer ' measures distances in 2D space and X axis from an accelerometer ' ---[ I/O Definitions ]--- pX PIN 15 pY PIN 0 gravX PIN 2 ' ---[ Variables ]--- wTime VAR Word ping VAR Byte wXPulse VAR WORD ' raw data from accelerometer wXGravity VAR WORD ' X axis gravity ' ---[ Constants ]--- cPulseState CON 1 cScale CON $200 ' 2.0 us per unit ' ---[ Constants ]--- cTrigger CON 5 ' trigger pulse = 10 uS for BS2 ' ---[ Initialization ]--- 'DEBUG CLS, "2D distances plus X axis", CR ' ---[ Main Code ]--- Main: DO GOSUB Read_Accelerometer ping = 0 GOSUB ReadPing DEBUG DEC5 wTime, " " ping = 15 GOSUB ReadPing DEBUG DEC5 wTime, " " DEBUG SDEC5 wXGravity, CR LOOP END ' ---[ Subroutines ]--- ReadPing: ' PULSOUT Pin, Duration ' pin: Pin number (0-15) ' Duration: specifies the duration of the pulse. (BS2: a unit = 2us) PULSOUT ping, cTrigger ' PULSIN Pin, State, Variable ' Pin: Pin number (0 - 15) ' State: Specifies whether the puse to be mesured is low(0) or high(1) ' Variable: the measured pulse duration will be stored. PULSIN ping, 1, wTime RETURN Read_Accelerometer: ' PULSIN Pin, State, Variable ' Pin: Pin number (0 - 15) ' State: Specifies whether the puse to be mesured is low(0) or high(1) ' Variable: the measured pulse duration will be stored. PULSIN gravX, cPulseState, wXPulse ' read pulse output wXPulse = wXPulse */ cScale ' convert to uSecs for BS2 wXGravity = ((wXPulse / 10) - 500) * 8 ' calculating the pulse to 1/1000 g ' PULSIN pYin, cPulseState, wYPulse ' wYPulse = wYPulse */ cScale ' wYGravity = ((wYPulse / 10) - 500) * 8 RETURN