Eng:Communicating to Scratch via Mac Application

The Remote Sensors Protocol can be used to connect Objective-C (that is, a Mac OS X or iOS application) to Scratch. This has many practical applications:
 * Using the WebKit library to allow JavaScript processing
 * This can be used to evaluate mathematical expressions like 5*(3-2/5)
 * Also, quick string processing like String.replace is possible
 * RegExp can be used to test strings
 * OpenGL and Quartz 2D graphics calls
 * Native actions like creating/reading files/urls and copying text to the clipboard
 * Accelerated calculations (e.g. a factorial function)
 * Using an iPhone or iPad to control a Scratch project

This tutorial assumes basic knowledge of: and will focus on:
 * Xcode
 * Interface Builder
 * Cocoa
 * Objective-C (especially memory management)
 * Using streams to connect to Scratch
 * Manipulating bytes and NSData
 * Using the remote sensors protocol

Set up the interface
First, open up Xcode and create an new Mac application. Call it ScratchConnect. Open the file  (under classes) and replace the code with this:
 * 1) import 

@interface ScratchConnectAppDelegate : NSObject  { NSWindow *window;//Window NSOutputStream *outputstr;// Output stream NSInputStream *inputstr;// Input stream IBOutlet id ip;// Ip address text field IBOutlet id message;// Message text field IBOutlet id status;// Status bar } -(IBAction)connect:(id)sender;// Connect to Scratch -(IBAction)broadcast:(id)sender;// Send a message @property (assign) IBOutlet NSWindow *window; @end The above code declares the global variables and functions we will be using in our application.

Save it, then open the file  under Resources.

Create the following interface with the appropriate objects (2 text boxes, 2 buttons, 1 label): Under the send button, add a label with no text.

Now bind the top text box to "ip", bottom text box to "message", and bottom label to "status". Then bind the "Connect" button to "connect:" and "Message" button to "broadcast".

At this point you can save and quit interface builder. Now, in Xcode, open.

Add the code and try it out
Now, add replace the code in the open file with this:

 // If something goes wrong while using this code, // such as system crashes and/or memory leaks, // the Scratch Wiki is not responsible. // Use this code at your own risk.


 * 1) import "ScratchConnectAppDelegate.h"

@implementation ScratchConnectAppDelegate

@synthesize window;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { outputstr = NSOutputStream alloc] initToMemory];// Output stream	inputstr = [NSInputStream alloc];// Input stream } -(IBAction)connect:(id)sender {// Connect to Scratch	NSHost *host = [NSHost hostWithAddress:[ip stringValue;// Get a host from our given IP address NSLog(@"Connecting..."); [NSStream getStreamsToHost:host port:42001 inputStream:&inputstr outputStream:&outputstr];// Set up streams to port 42001 (Scratch) on our IP	[outputstr scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];// Initialize output stream [outputstr setDelegate:self]; [outputstr open]; [inputstr scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];// Initialize input stream [inputstr setDelegate:self]; [inputstr open]; [status setStringValue:@"Connected!"];// Set status to connected } -(IBAction)broadcast:(id)sender {// Send a string message to Scratch if ([outputstr streamStatus] == 2) {// If our stream is open... NSData *myData = NSString stringWithString:[message stringValue dataUsingEncoding:NSASCIIStringEncoding];// Get NSData from message NSMutableData *toSend;// What we will transfer Byte *toAppend = (Byte*)malloc(4);// Size of message toAppend[0]=(([myData length] >> 24) & 0xFF);// Construct size from myData's size toAppend[1]=(([myData length] >> 16) & 0xFF); toAppend[2]=(([myData length] >> 8) & 0xFF); toAppend[3]=([myData length] & 0xFF); toSend = [NSMutableData dataWithBytes:toAppend length:4];// Append size to data [toSend appendData:myData];// Append string to data const uint8_t *bytes = (const uint8_t*)[toSend bytes];// Get bytes NSLog(@"%d bytes were sent.", [outputstr write:bytes maxLength:[toSend length]]);//Send it! }	else {// Shut stream, error occurs NSBeep; [status setStringValue:@"Oops! Not connected."]; } } -(void)dealloc {// Free up stream memory [outputstr close]; [outputstr release]; outputstr = nil; [inputstr close]; [inputstr release]; inputstr = nil; [super dealloc]; }

- (void) stream: (NSStream *) stream handleEvent: (NSStreamEvent) eventCode {// Event handler NSLog(@"Event %d occurred:", eventCode); if (eventCode == NSStreamEventErrorOccurred) {// Error! NSLog(@"Error!"); [status setStringValue:@"Oops! A connection error!"]; }	if (eventCode == NSStreamEventEndEncountered) {// Data transfer complete NSLog(@"End of transfer..."); }	if (eventCode == NSStreamEventHasSpaceAvailable) {// Space available NSLog(@"Space left..."); }	if (eventCode == NSStreamEventOpenCompleted) {// Stream opened NSLog(@"Opened..."); }	if (eventCode == NSStreamEventHasBytesAvailable) {// Message received [status setStringValue:@"Message received!"]; uint8_t buffer[1024];// To read into uint8_t rec[1024];// Message received NSMutableData *data = [[NSMutableData alloc] init];		int length = [stream read:buffer maxLength:1024];// Read data received into buffer		if (!length) {// Error!			NSLog(@"No data");			[status setStringValue:@"Message received, but could not be read."];		}		else {			[data appendBytes:buffer length:length];// Append received bytes to data		}		[data getBytes:rec range:NSMakeRange(3, [data length]-3)];// Read bytes into rec (ie get rid of size prefix)		data = [NSData dataWithBytes:rec length:length-3];// Read rec back into data		NSString *messRec = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];// Get string from Data		[status setStringValue:messRec];// Set status value		[data release];// Free up memory		[messRec release];		data = nil;		messRec = nil;	} } @end

This code will be explained later.

Now open up Scratch. Open the "Sensing" palette and scroll down. Open the drop-down menu of the ([] sensor value) block and click on "enable remote sensor connections". After noting down the IP address given, click on OK.

Now create these scripts in Scratch: When I receive [hi v] broadcast [bye v] Say [bye] wait (1) secs change [meetings v] by (1)

When gf clicked set [meetings v] to (0) Save the project.

Now build and run the Xcode project. In the IP text box, type the IP given by Scratch. Then click connect. The status label should read connected. Now type "broadcast hi" in the message text box and click send. Scratch should say "Bye". The status should read "broadcast "bye"". One second later, the status should change to 'sensor-update "meetings" 1'.

How it works
Let's examine it method-by-method (all lines are commented heavily so taking it apart line-by-line should not be too hard): All we do here is allocate memory for our streams. Here, we connect our two streams to Scratch.

First, we set up an  with our IP address. Then we use the  convenience method to connect the streams. Finally we schedule and open the streams. Here we broadcast the message to Scratch. First, we check if the stream is open. If it is, we create an  with our string. We also make one empty data for our final message. We create a byte array containing the size of the message, then construct the toSend data with a concatenation of the byte array and string's data.

Finally, we send this data with the  message. Here we just free up memory. This event is triggered by a stream. First, we take care of some events like errors, which need status bar updates. Then we take care of the  event. This event is triggered by an incoming message. In this event, we use the inputstream's  message to read the given data, then cut off the first bytes (containing size), then finally display it.