├── keywords.txt ├── LICENSE ├── CmdMessenger.h ├── README.markdown ├── CmdMessenger.cpp ├── CmdMessengerExample └── CmdMessengerExample.pde └── Max5 ├── cmdmessenger.maxhelp └── cmdmessenger.maxpat /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For Messenger 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | Messenger KEYWORD1 10 | 11 | ####################################### 12 | # Methods and Functions (KEYWORD2) 13 | ####################################### 14 | 15 | process KEYWORD2 16 | readInt KEYWORD2 17 | readChar KEYWORD2 18 | copyString KEYWORD2 19 | checkString KEYWORD2 20 | 21 | ####################################### 22 | # Instances (KEYWORD2) 23 | ####################################### 24 | 25 | ####################################### 26 | # Constants (LITERAL1) 27 | ####################################### 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | = MIT License 2 | 3 | Copyright (c) 2013 Dreamcat4, Neil Dudman, Thomas Ouellet Fredericks. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /CmdMessenger.h: -------------------------------------------------------------------------------- 1 | #ifndef CmdMessenger_h 2 | #define CmdMessenger_h 3 | 4 | #include 5 | #if defined(ARDUINO) && ARDUINO >= 100 6 | #include "Arduino.h" 7 | #else 8 | #include "WProgram.h" 9 | #endif 10 | 11 | 12 | #include "Stream.h" 13 | 14 | extern "C" { 15 | // Our callbacks are always method signature: void cmd(void); 16 | typedef void (*messengerCallbackFunction)(void); 17 | } 18 | 19 | #define MAXCALLBACKS 50 // The maximum number of unique commands 20 | #define MESSENGERBUFFERSIZE 64 // The maximum length of the buffer (defaults to 64) 21 | #define DEFAULT_TIMEOUT 5000 // Abandon incomplete messages if nothing heard after 5 seconds 22 | 23 | class CmdMessenger 24 | { 25 | 26 | protected: 27 | uint8_t bufferIndex; // Index where to write the data 28 | uint8_t bufferLength; // Is set to MESSENGERBUFFERSIZE 29 | uint8_t bufferLastIndex; // The last index of the buffer 30 | 31 | messengerCallbackFunction default_callback; 32 | messengerCallbackFunction callbackList[MAXCALLBACKS]; 33 | 34 | // (not implemented, generally not needed) 35 | // when we are sending a message and requre answer or acknowledgement 36 | // suspend any processing (process()) when serial intterupt is recieved 37 | // Even though we usually only have single processing thread we still need 38 | // this i think because Serial interrupts. 39 | // Could also be usefull when we want data larger than MESSENGERBUFFERSIZE 40 | // we could send a startCmd, which could pauseProcessing and read directly 41 | // from serial all the data, send acknowledge etc and then resume processing 42 | boolean pauseProcessing; 43 | 44 | void handleMessage(); 45 | void init(Stream &comms, char field_separator, char command_separator); 46 | uint8_t process(int serialByte); 47 | void reset(); 48 | 49 | char buffer[MESSENGERBUFFERSIZE]; // Buffer that holds the data 50 | uint8_t messageState; 51 | uint8_t dumped; 52 | char* current; // Pointer to current data 53 | char* last; 54 | 55 | public: 56 | CmdMessenger(Stream &comms); 57 | CmdMessenger(Stream &comms, char fld_separator); 58 | CmdMessenger(Stream &comms, char fld_separator, char cmd_separator); 59 | 60 | void attach(messengerCallbackFunction newFunction); 61 | void discard_LF_CR(); 62 | void print_LF_CR(); 63 | 64 | uint8_t next(); 65 | uint8_t available(); 66 | 67 | int readInt(); 68 | char readChar(); 69 | void copyString(char *string, uint8_t size); 70 | uint8_t checkString(char *string); 71 | 72 | // Polymorphism used to interact with serial class 73 | // Stream is an abstract base class which defines a base set 74 | // of functionality used by the serial classes. 75 | Stream *comms; 76 | 77 | void attach(byte msgId, messengerCallbackFunction newFunction); 78 | 79 | char* sendCmd(int cmdId, char *msg, boolean reqAc = false, 80 | char *replyBuff = NULL, int butSize = 0, int timeout = DEFAULT_TIMEOUT, 81 | int retryCount = 10); 82 | 83 | void feedinSerialData(); 84 | 85 | char command_separator; 86 | char field_separator; 87 | 88 | boolean discard_newlines; 89 | boolean print_newlines; 90 | 91 | boolean blockedTillReply(int timeout = DEFAULT_TIMEOUT); 92 | }; 93 | #endif -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | ### CmdMessenger v2 2 | 3 | A Serial Messaging system for the Arduino Platform. 4 | 5 | ### Intoduction 6 | 7 | CmdMessenger is a command-orientated version of Messenger for serial comms. To use CmdMessenger, we define a list of command identifiers. And attach callback / handler functions for recieved messages. The first sub-string (part) in a message is always the command identifier. If a message is received and there is no corresponding known command (and command handler function), then the message is passed to the default message handler. 8 | 9 | This new version of the library (version 2.0) contains many changes to both the CmdMessenger class and also the example program. 10 | 11 | ### Improvements Over CmdMessenger v1 (v0.1.0) 12 | 13 | * Updated to work with Arduino IDE 022 14 | * Enable / disable newline (print and ignore) 15 | * New generic example (works with all Arduinos) 16 | * More reliable process() loop. 17 | * User can set their own cmd and field seperator 18 | (defaults to ';' and ',') 19 | * Base-64 encoded data to avoid collisions with ^^ 20 | * Works with Arduino Serial Monitor for easy debugging 21 | 22 | ### Requirements 23 | 24 | * [Arduino IDE Version 022 or later](http://www.arduino.cc/en/Main/Software)* 25 | * [Ardunio Streaming Library](http://arduiniana.org/libraries/streaming/) 26 | * [Arduino Base64 Library](https://github.com/adamvr/arduino-base64) 27 | 28 | \* Earlier versions of Arduino IDE might work but wernt tested. 29 | 30 | ### Getting Started 31 | 32 | Pretty much everything is explained within the example sketch file. Open CmdMessengerExample.pde in Arduino IDE and upload it to your microcontroller device. Check the baud rate is OK for your model. Try it out by typing commands into the Arduino Serial Monitor. 33 | 34 | Once its been established what is happening on the Arduino side by following the interactive example, the next step is to work on with the program running on the other side of the serial interface which will be communicating with the Arduino. There are a wide variety of platforms and programming languages which can be used. C, Python, Ruby, Linux, Windows, and other embedded devices. The possibilities are endless. A principal benefit of CmdMessenger is that the format of messages are both flexible and simple enough to be programmed in nearly any suitable language or programming environment that supports a serial (comm port) interface. 35 | 36 | The following C program `arduino-serial` is recommended for this task. It can be modified to suit your needs, or compiled "as-is" to provide an appropriate command line tool for scripting. 37 | 38 | * [arduino-serial.c](http://todbot.com/blog/2006/12/06/arduino-serial-c-code-to-talk-to-arduino) 39 | 40 | NOTE: Don't put `arduino-serial.c` into your `Arduino/libraries/CmdMessenger/` directory or CmdMessenger won't compile anymore. 41 | 42 | We provide a Max5 / MaxMSP example (`/Max5` folder) because historically that has been included with previous distributions of Messenger. The inclusion of a MaxMSP sample is not any kind of recommendation over other languages however. 43 | 44 | Dreamcat4 45 | 46 | 47 | ### Credit 48 | 49 | * Initial Messenger Library - By Thomas Ouellet Fredericks. 50 | * CmdMessenger Version 1 - By Neil Dudman. 51 | * CmdMessenger Version 2 - By Dreamcat4. 52 | 53 | ### Copyright 54 | 55 | CmdMessenger is provided Copyright © 2013 under MIT License. 56 | 57 | -------------------------------------------------------------------------------- /CmdMessenger.cpp: -------------------------------------------------------------------------------- 1 | 2 | // ADDED FOR COMPATIBILITY WITH WIRING ?? 3 | extern "C" { 4 | #include 5 | } 6 | 7 | #include "CmdMessenger.h" 8 | #include 9 | 10 | //////////////////// Cmd Messenger imp //////////////// 11 | CmdMessenger::CmdMessenger(Stream &ccomms) 12 | { 13 | init(ccomms,' ',';'); 14 | } 15 | 16 | CmdMessenger::CmdMessenger(Stream &ccomms, char fld_separator) 17 | { 18 | init(ccomms,fld_separator,';'); 19 | } 20 | 21 | CmdMessenger::CmdMessenger(Stream &ccomms, char fld_separator, char cmd_separator) 22 | { 23 | init(ccomms,fld_separator,cmd_separator); 24 | } 25 | 26 | void CmdMessenger::attach(messengerCallbackFunction newFunction) { 27 | default_callback = newFunction; 28 | } 29 | 30 | void CmdMessenger::attach(byte msgId, messengerCallbackFunction newFunction) { 31 | if (msgId > 0 && msgId <= MAXCALLBACKS) // <= ? I think its ok ? 32 | callbackList[msgId-1] = newFunction; 33 | } 34 | 35 | void CmdMessenger::discard_LF_CR() 36 | { 37 | discard_newlines = true; 38 | } 39 | 40 | void CmdMessenger::print_LF_CR() 41 | { 42 | print_newlines = true; 43 | } 44 | 45 | void CmdMessenger::init(Stream &ccomms, char fld_separator, char cmd_separator) 46 | { 47 | comms = &ccomms; 48 | 49 | discard_newlines = false; 50 | print_newlines = false; 51 | 52 | field_separator = fld_separator; 53 | command_separator = cmd_separator; 54 | 55 | bufferLength = MESSENGERBUFFERSIZE; 56 | bufferLastIndex = MESSENGERBUFFERSIZE -1; 57 | reset(); 58 | 59 | default_callback = NULL; 60 | for (int i = 0; i < MAXCALLBACKS; i++) 61 | callbackList[i] = NULL; 62 | 63 | pauseProcessing = false; 64 | } 65 | 66 | void CmdMessenger::reset() { 67 | bufferIndex = 0; 68 | current = NULL; 69 | last = NULL; 70 | dumped = 1; 71 | } 72 | 73 | uint8_t CmdMessenger::next() 74 | { 75 | char * temppointer= NULL; 76 | // Currently, cmd messenger only supports 1 char for the field seperator 77 | const char seperator_tokens[] = { field_separator,'\0' }; 78 | switch (messageState) 79 | { 80 | case 0: 81 | return 0; 82 | case 1: 83 | temppointer = buffer; 84 | messageState = 2; 85 | default: 86 | if (dumped) 87 | current = strtok_r(temppointer,seperator_tokens,&last); 88 | if (current != NULL) 89 | { 90 | dumped = 0; 91 | return 1; 92 | } 93 | } 94 | return 0; 95 | } 96 | 97 | uint8_t CmdMessenger::available() 98 | { 99 | return next(); 100 | } 101 | 102 | uint8_t CmdMessenger::process(int serialByte) { 103 | messageState = 0; 104 | char serialChar = (char)serialByte; 105 | 106 | if (serialByte > 0) { 107 | 108 | // Currently, cmd messenger only supports 1 char for the command seperator 109 | if(serialChar == command_separator) 110 | { 111 | buffer[bufferIndex]=0; 112 | if(bufferIndex > 0) 113 | { 114 | messageState = 1; 115 | current = buffer; 116 | } 117 | reset(); 118 | } 119 | else 120 | { 121 | buffer[bufferIndex]=serialByte; 122 | bufferIndex++; 123 | if (bufferIndex >= bufferLastIndex) reset(); 124 | 125 | if(discard_newlines && (serialChar != field_separator)) 126 | if((serialChar == '\n') || (serialChar == '\r')) 127 | reset(); 128 | } 129 | } 130 | 131 | if ( messageState == 1 ) { 132 | handleMessage(); 133 | } 134 | return messageState; 135 | } 136 | 137 | void CmdMessenger::handleMessage() 138 | { 139 | // If we didnt want to use ASCII integer... 140 | // we would change the line below vv 141 | int id = readInt(); 142 | 143 | //Serial << "ID+" << id << endl; 144 | // Because readInt() can fail and return a 0 we can't 145 | // start our array index at that number 146 | if (id > 0 && id <= MAXCALLBACKS && callbackList[id-1] != NULL) 147 | (*callbackList[id-1])(); 148 | else // Cmd not registered default callback 149 | (*default_callback)(); 150 | } 151 | 152 | void CmdMessenger::feedinSerialData() 153 | { 154 | while ( !pauseProcessing && comms->available( ) ) 155 | process(comms->read( ) ); 156 | } 157 | 158 | boolean CmdMessenger::blockedTillReply(int timeout) 159 | { 160 | unsigned long start = millis(); 161 | unsigned long time = start; 162 | while(!comms->available() || (start - time) > timeout ) 163 | time = millis(); 164 | } 165 | 166 | // if the arguments in the future could be passed in as int/long/float etc 167 | // then it might make sense to use the above writeReal????() methods 168 | // I've removed them for now. 169 | char* CmdMessenger::sendCmd(int cmdId, char *msg, boolean reqAc, 170 | char *replyBuff, int butSize, int timeout, 171 | int retryCount) 172 | { 173 | int tryCount = 0; 174 | pauseProcessing = true; 175 | //*comms << cmdId << field_separator << msg << endl; 176 | comms->print(cmdId); 177 | comms->print(field_separator); 178 | comms->print(msg); 179 | comms->print(command_separator); 180 | if(print_newlines) 181 | comms->println(); // should append BOTH \r\n 182 | if (reqAc) { 183 | do { 184 | blockedTillReply(timeout); 185 | //strcpy(replyBuff, buf; 186 | } while( tryCount < retryCount); 187 | } 188 | 189 | pauseProcessing = false; 190 | return NULL; 191 | } 192 | 193 | 194 | // Not sure if it will work for signed.. check it out 195 | /*unsigned char *CmdMessenger::writeRealInt(int val, unsigned char buff[2]) 196 | { 197 | buff[1] = (unsigned char)val; 198 | buff[0] = (unsigned char)(val >> 8); 199 | buff[2] = 0; 200 | return buff; 201 | } 202 | 203 | char* CmdMessenger::writeRealLong(long val, char buff[4]) 204 | { 205 | //buff[1] = (unsigned char)val; 206 | //buff[0] = (unsigned char)(val >> 8); 207 | return buff; 208 | } 209 | 210 | char* CmdMessenger::writeRealFloat(float val, char buff[4]) 211 | { 212 | //buff[1] = (unsigned char)val; 213 | //buff[0] = (unsigned char)(val >> 8); 214 | return buff; 215 | } 216 | */ 217 | 218 | int CmdMessenger::readInt() 219 | { 220 | if (next()) 221 | { 222 | dumped = 1; 223 | return atoi(current); 224 | } 225 | return 0; 226 | } 227 | 228 | char CmdMessenger::readChar() 229 | { 230 | if (next()) 231 | { 232 | dumped = 1; 233 | return current[0]; 234 | } 235 | return 0; 236 | } 237 | 238 | void CmdMessenger::copyString(char *string, uint8_t size) 239 | { 240 | if (next()) 241 | { 242 | dumped = 1; 243 | strlcpy(string,current,size); 244 | } 245 | else 246 | { 247 | if ( size ) string[0] = '\0'; 248 | } 249 | } 250 | 251 | uint8_t CmdMessenger::checkString(char *string) 252 | { 253 | if (next()) 254 | { 255 | if ( strcmp(string,current) == 0 ) 256 | { 257 | dumped = 1; 258 | return 1; 259 | } 260 | else 261 | { 262 | return 0; 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /CmdMessengerExample/CmdMessengerExample.pde: -------------------------------------------------------------------------------- 1 | // This example demonstrates CmdMessenger's callback & attach methods 2 | // For Arduino Uno and Arduino Duemilanove board (may work with other) 3 | 4 | // Download these into your Sketches/libraries/ folder... 5 | 6 | // CmdMessenger library available from https://github.com/dreamcat4/cmdmessenger 7 | #include 8 | 9 | // Base64 library available from https://github.com/adamvr/arduino-base64 10 | #include 11 | 12 | // Streaming4 library available from http://arduiniana.org/libraries/streaming/ 13 | #include 14 | 15 | // Mustnt conflict / collide with our message payload data. Fine if we use base64 library ^^ above 16 | char field_separator = ','; 17 | char command_separator = ';'; 18 | 19 | // Attach a new CmdMessenger object to the default Serial port 20 | CmdMessenger cmdMessenger = CmdMessenger(Serial, field_separator, command_separator); 21 | 22 | 23 | // ------------------ S E R I A L M O N I T O R ----------------------------- 24 | // 25 | // Try typing these command messages in the serial monitor! 26 | // 27 | // 4,hi,heh,ho!; 28 | // 5; 29 | // 5,dGhlIGJlYXJzIGFyZSBhbGxyaWdodA==; 30 | // 5,dGhvc2UgbmFzdHkgY29udHJvbCA7OyBjaGFyYWN0ZXJzICws==; 31 | // 2; 32 | // 6; 33 | // 34 | // 35 | // Expected output: 36 | // 37 | // 1,Arduino ready; 38 | // 1,bens cmd recieved; 39 | // 1,hi; 40 | // 1,heh; 41 | // 1,ho!; 42 | // 1,jerrys cmd recieved; 43 | // 1,"the bears are allright" encoded in base64...; 44 | // 1,dGhlIGJlYXJzIGFyZSBhbGxyaWdodA==; 45 | // 1,jerrys cmd recieved; 46 | // 1,what you send me, decoded base64...; 47 | // 1,the bears are allright; 48 | // 1,jerrys cmd recieved; 49 | // 1,what you send me, decoded base64...; 50 | // 1,those nasty control ;; characters ,,; 51 | // 1,Arduino ready; 52 | // 3,Unknown command; 53 | // 54 | 55 | 56 | // ------------------ C M D L I S T I N G ( T X / R X ) --------------------- 57 | 58 | // We can define up to a default of 50 cmds total, including both directions (send + recieve) 59 | // and including also the first 4 default command codes for the generic error handling. 60 | // If you run out of message slots, then just increase the value of MAXCALLBACKS in CmdMessenger.h 61 | 62 | // Commands we send from the Arduino to be received on the PC 63 | enum 64 | { 65 | kCOMM_ERROR = 000, // Lets Arduino report serial port comm error back to the PC (only works for some comm errors) 66 | kACK = 001, // Arduino acknowledges cmd was received 67 | kARDUINO_READY = 002, // After opening the comm port, send this cmd 02 from PC to check arduino is ready 68 | kERR = 003, // Arduino reports badly formatted cmd, or cmd not recognised 69 | 70 | // Now we can define many more 'send' commands, coming from the arduino -> the PC, eg 71 | // kICE_CREAM_READY, 72 | // kICE_CREAM_PRICE, 73 | // For the above commands, we just call cmdMessenger.sendCmd() anywhere we want in our Arduino program. 74 | 75 | kSEND_CMDS_END, // Mustnt delete this line 76 | }; 77 | 78 | // Commands we send from the PC and want to recieve on the Arduino. 79 | // We must define a callback function in our Arduino program for each entry in the list below vv. 80 | // They start at the address kSEND_CMDS_END defined ^^ above as 004 81 | messengerCallbackFunction messengerCallbacks[] = 82 | { 83 | bens_msg, // 004 in this example 84 | jerrys_base64_data, // 005 85 | NULL 86 | }; 87 | // Its also possible (above ^^) to implement some symetric commands, when both the Arduino and 88 | // PC / host are using each other's same command numbers. However we recommend only to do this if you 89 | // really have the exact same messages going in both directions. Then specify the integers (with '=') 90 | 91 | 92 | // ------------------ C A L L B A C K M E T H O D S ------------------------- 93 | 94 | void bens_msg() 95 | { 96 | // Message data is any ASCII bytes (0-255 value). But can't contain the field 97 | // separator, command separator chars you decide (eg ',' and ';') 98 | cmdMessenger.sendCmd(kACK,"bens cmd recieved"); 99 | while ( cmdMessenger.available() ) 100 | { 101 | char buf[350] = { '\0' }; 102 | cmdMessenger.copyString(buf, 350); 103 | if(buf[0]) 104 | cmdMessenger.sendCmd(kACK, buf); 105 | } 106 | } 107 | 108 | void jerrys_base64_data() 109 | { 110 | // To avoid conflicts with the control characters and any newline characters 111 | // Message length increases about 30%-40% 112 | 113 | // Afer base64_decode(), we just parse the buffer and unpack it into your 114 | // target / desination data type eg bitmask, float, double, whatever. 115 | char buf[350] = { '\0' }; 116 | boolean data_msg_printed = false; 117 | cmdMessenger.sendCmd(kACK,"jerrys cmd recieved"); 118 | 119 | // base64 decode 120 | while ( cmdMessenger.available() ) 121 | { 122 | if(!data_msg_printed) 123 | { 124 | cmdMessenger.sendCmd(kACK, "what you send me, decoded base64..."); 125 | data_msg_printed = true; 126 | } 127 | char buf[350] = { '\0' }; 128 | cmdMessenger.copyString(buf, 350); 129 | if(buf[0]) 130 | { 131 | char decode_buf[350] = { '\0' }; 132 | base64_decode(decode_buf, buf, 350); 133 | cmdMessenger.sendCmd(kACK, decode_buf); 134 | } 135 | } 136 | 137 | // base64 encode 138 | if(!data_msg_printed) 139 | { 140 | cmdMessenger.sendCmd(kACK, "\"the bears are allright\" encoded in base64..."); 141 | char base64_msg[350] = { '\0' }; 142 | base64_encode(base64_msg, "the bears are allright", 22); 143 | cmdMessenger.sendCmd(kACK, base64_msg); 144 | } 145 | } 146 | 147 | // ------------------ D E F A U L T C A L L B A C K S ----------------------- 148 | 149 | void arduino_ready() 150 | { 151 | // In response to ping. We just send a throw-away Acknowledgement to say "im alive" 152 | cmdMessenger.sendCmd(kACK,"Arduino ready"); 153 | } 154 | 155 | void unknownCmd() 156 | { 157 | // Default response for unknown commands and corrupt messages 158 | cmdMessenger.sendCmd(kERR,"Unknown command"); 159 | } 160 | 161 | // ------------------ E N D C A L L B A C K M E T H O D S ------------------ 162 | 163 | 164 | 165 | // ------------------ S E T U P ---------------------------------------------- 166 | 167 | void attach_callbacks(messengerCallbackFunction* callbacks) 168 | { 169 | int i = 0; 170 | int offset = kSEND_CMDS_END; 171 | while(callbacks[i]) 172 | { 173 | cmdMessenger.attach(offset+i, callbacks[i]); 174 | i++; 175 | } 176 | } 177 | 178 | void setup() 179 | { 180 | // Listen on serial connection for messages from the pc 181 | // Serial.begin(57600); // Arduino Duemilanove, FTDI Serial 182 | Serial.begin(115200); // Arduino Uno, Mega, with AT8u2 USB 183 | 184 | // cmdMessenger.discard_LF_CR(); // Useful if your terminal appends CR/LF, and you wish to remove them 185 | cmdMessenger.print_LF_CR(); // Make output more readable whilst debugging in Arduino Serial Monitor 186 | 187 | // Attach default / generic callback methods 188 | cmdMessenger.attach(kARDUINO_READY, arduino_ready); 189 | cmdMessenger.attach(unknownCmd); 190 | 191 | // Attach my application's user-defined callback methods 192 | attach_callbacks(messengerCallbacks); 193 | 194 | arduino_ready(); 195 | 196 | // blink 197 | pinMode(13, OUTPUT); 198 | } 199 | 200 | 201 | // ------------------ M A I N ( ) -------------------------------------------- 202 | 203 | // Timeout handling 204 | long timeoutInterval = 2000; // 2 seconds 205 | long previousMillis = 0; 206 | int counter = 0; 207 | 208 | void timeout() 209 | { 210 | // blink 211 | if (counter % 2) 212 | digitalWrite(13, HIGH); 213 | else 214 | digitalWrite(13, LOW); 215 | counter ++; 216 | } 217 | 218 | void loop() 219 | { 220 | // Process incoming serial data, if any 221 | cmdMessenger.feedinSerialData(); 222 | 223 | // handle timeout function, if any 224 | if ( millis() - previousMillis > timeoutInterval ) 225 | { 226 | timeout(); 227 | previousMillis = millis(); 228 | } 229 | 230 | // Loop. 231 | } 232 | 233 | -------------------------------------------------------------------------------- /Max5/cmdmessenger.maxhelp: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "rect" : [ 614.0, 82.0, 792.0, 692.0 ], 5 | "bglocked" : 0, 6 | "defrect" : [ 614.0, 82.0, 792.0, 692.0 ], 7 | "openrect" : [ 0.0, 0.0, 0.0, 0.0 ], 8 | "openinpresentation" : 0, 9 | "default_fontsize" : 12.0, 10 | "default_fontface" : 0, 11 | "default_fontname" : "Arial", 12 | "gridonopen" : 0, 13 | "gridsize" : [ 15.0, 15.0 ], 14 | "gridsnaponopen" : 0, 15 | "toolbarvisible" : 1, 16 | "boxanimatetime" : 200, 17 | "imprint" : 0, 18 | "enablehscroll" : 1, 19 | "enablevscroll" : 1, 20 | "devicewidth" : 0.0, 21 | "boxes" : [ { 22 | "box" : { 23 | "maxclass" : "comment", 24 | "text" : "<-- Note: You will want to create a patch to recognise the commands coming from the Arduino. There is a numerical (enum) mapping in the .pde Sketch file. Which is the command listing. Identify commands based on the ASCII integer in the first field and branch out. Split on the field seperator with select and zi group (as explained in other patch file, \"cmdmessenger.maxpat\").", 25 | "linecount" : 4, 26 | "fontsize" : 12.0, 27 | "numoutlets" : 0, 28 | "patching_rect" : [ 230.0, 542.0, 522.0, 62.0 ], 29 | "id" : "obj-7", 30 | "fontname" : "Arial", 31 | "numinlets" : 1 32 | } 33 | 34 | } 35 | , { 36 | "box" : { 37 | "maxclass" : "comment", 38 | "text" : "<-- Normally, you'll create a patch to construct similar messages from other appropriate MaxMSP inputs.", 39 | "linecount" : 3, 40 | "fontsize" : 12.0, 41 | "numoutlets" : 0, 42 | "patching_rect" : [ 344.0, 381.0, 203.0, 48.0 ], 43 | "id" : "obj-6", 44 | "fontname" : "Arial", 45 | "numinlets" : 1 46 | } 47 | 48 | } 49 | , { 50 | "box" : { 51 | "maxclass" : "comment", 52 | "text" : "These are (literally) our formatted ASCII messages. We backslash escape the comma '\\,' and the semi-colon '\\;' as they also have certain special meanings in MaxMSP \"message\" objects.", 53 | "linecount" : 4, 54 | "fontsize" : 12.0, 55 | "numoutlets" : 0, 56 | "patching_rect" : [ 341.0, 220.0, 306.0, 62.0 ], 57 | "id" : "obj-3", 58 | "fontname" : "Arial", 59 | "numinlets" : 1 60 | } 61 | 62 | } 63 | , { 64 | "box" : { 65 | "maxclass" : "message", 66 | "text" : "6\\;", 67 | "fontsize" : 12.0, 68 | "numoutlets" : 1, 69 | "outlettype" : [ "" ], 70 | "patching_rect" : [ 163.0, 402.0, 32.5, 18.0 ], 71 | "id" : "obj-24", 72 | "fontname" : "Arial", 73 | "numinlets" : 2 74 | } 75 | 76 | } 77 | , { 78 | "box" : { 79 | "maxclass" : "message", 80 | "text" : "2\\;", 81 | "fontsize" : 12.0, 82 | "numoutlets" : 1, 83 | "outlettype" : [ "" ], 84 | "patching_rect" : [ 164.0, 375.0, 32.5, 18.0 ], 85 | "id" : "obj-22", 86 | "fontname" : "Arial", 87 | "numinlets" : 2 88 | } 89 | 90 | } 91 | , { 92 | "box" : { 93 | "maxclass" : "comment", 94 | "text" : "Send some messages!", 95 | "fontsize" : 12.0, 96 | "numoutlets" : 0, 97 | "patching_rect" : [ 162.0, 220.0, 150.0, 20.0 ], 98 | "id" : "obj-20", 99 | "fontname" : "Arial", 100 | "numinlets" : 1 101 | } 102 | 103 | } 104 | , { 105 | "box" : { 106 | "maxclass" : "message", 107 | "text" : "5\\,dGhvc2UgbmFzdHkgY29udHJvbCA7OyBjaGFyYWN0ZXJzICws==\\;", 108 | "linecount" : 2, 109 | "fontsize" : 12.0, 110 | "numoutlets" : 1, 111 | "outlettype" : [ "" ], 112 | "patching_rect" : [ 163.0, 334.0, 287.0, 32.0 ], 113 | "id" : "obj-17", 114 | "fontname" : "Arial", 115 | "numinlets" : 2 116 | } 117 | 118 | } 119 | , { 120 | "box" : { 121 | "maxclass" : "message", 122 | "text" : "5\\,dGhlIGJlYXJzIGFyZSBhbGxyaWdodA==\\;", 123 | "fontsize" : 12.0, 124 | "numoutlets" : 1, 125 | "outlettype" : [ "" ], 126 | "patching_rect" : [ 163.0, 306.0, 249.0, 18.0 ], 127 | "id" : "obj-16", 128 | "fontname" : "Arial", 129 | "numinlets" : 2 130 | } 131 | 132 | } 133 | , { 134 | "box" : { 135 | "maxclass" : "message", 136 | "text" : "baud 115200", 137 | "fontsize" : 12.0, 138 | "numoutlets" : 1, 139 | "outlettype" : [ "" ], 140 | "patching_rect" : [ 59.0, 131.0, 80.0, 18.0 ], 141 | "id" : "obj-15", 142 | "fontname" : "Arial", 143 | "numinlets" : 2 144 | } 145 | 146 | } 147 | , { 148 | "box" : { 149 | "maxclass" : "comment", 150 | "text" : "Set baud rate", 151 | "fontsize" : 12.0, 152 | "numoutlets" : 0, 153 | "patching_rect" : [ 145.0, 130.0, 150.0, 20.0 ], 154 | "id" : "obj-9", 155 | "fontname" : "Arial", 156 | "numinlets" : 1 157 | } 158 | 159 | } 160 | , { 161 | "box" : { 162 | "maxclass" : "comment", 163 | "text" : "This works with CmdMessengerExample.pde", 164 | "fontsize" : 12.0, 165 | "numoutlets" : 0, 166 | "patching_rect" : [ 20.0, 27.0, 412.0, 20.0 ], 167 | "id" : "obj-23", 168 | "fontname" : "Arial", 169 | "numinlets" : 1 170 | } 171 | 172 | } 173 | , { 174 | "box" : { 175 | "maxclass" : "message", 176 | "text" : "5\\;", 177 | "fontsize" : 12.0, 178 | "numoutlets" : 1, 179 | "outlettype" : [ "" ], 180 | "patching_rect" : [ 164.0, 278.0, 32.5, 18.0 ], 181 | "id" : "obj-21", 182 | "fontname" : "Arial", 183 | "numinlets" : 2 184 | } 185 | 186 | } 187 | , { 188 | "box" : { 189 | "maxclass" : "message", 190 | "text" : "4\\,hi\\,heh\\,ho!\\;", 191 | "fontsize" : 12.0, 192 | "numoutlets" : 1, 193 | "outlettype" : [ "" ], 194 | "patching_rect" : [ 165.0, 249.0, 90.0, 18.0 ], 195 | "id" : "obj-18", 196 | "fontname" : "Arial", 197 | "numinlets" : 2 198 | } 199 | 200 | } 201 | , { 202 | "box" : { 203 | "maxclass" : "comment", 204 | "text" : "Arguments are recognised as either \"print\" (which prints the available serial ports), \"port\" (sets the port and opens the serial connection), \"baud\" (sets the baud rate). All other messages are interpreted as ASCII text to be passed over the serial line.", 205 | "linecount" : 4, 206 | "fontsize" : 12.0, 207 | "numoutlets" : 0, 208 | "patching_rect" : [ 334.0, 75.0, 346.0, 62.0 ], 209 | "id" : "obj-5", 210 | "fontname" : "Arial", 211 | "numinlets" : 1 212 | } 213 | 214 | } 215 | , { 216 | "box" : { 217 | "maxclass" : "comment", 218 | "text" : "Response Message coming back from Arduino", 219 | "linecount" : 2, 220 | "fontsize" : 12.0, 221 | "numoutlets" : 0, 222 | "patching_rect" : [ 62.0, 543.0, 150.0, 34.0 ], 223 | "id" : "obj-19", 224 | "fontname" : "Arial", 225 | "numinlets" : 1 226 | } 227 | 228 | } 229 | , { 230 | "box" : { 231 | "maxclass" : "comment", 232 | "text" : "Select port", 233 | "fontsize" : 12.0, 234 | "numoutlets" : 0, 235 | "patching_rect" : [ 85.0, 101.0, 150.0, 20.0 ], 236 | "id" : "obj-13", 237 | "fontname" : "Arial", 238 | "numinlets" : 1 239 | } 240 | 241 | } 242 | , { 243 | "box" : { 244 | "maxclass" : "message", 245 | "text" : "port a", 246 | "fontsize" : 12.0, 247 | "numoutlets" : 1, 248 | "outlettype" : [ "" ], 249 | "patching_rect" : [ 37.0, 103.0, 41.0, 18.0 ], 250 | "id" : "obj-12", 251 | "fontname" : "Arial", 252 | "numinlets" : 2 253 | } 254 | 255 | } 256 | , { 257 | "box" : { 258 | "maxclass" : "comment", 259 | "text" : "Print available ports", 260 | "fontsize" : 12.0, 261 | "numoutlets" : 0, 262 | "patching_rect" : [ 62.0, 73.0, 150.0, 20.0 ], 263 | "id" : "obj-10", 264 | "fontname" : "Arial", 265 | "numinlets" : 1 266 | } 267 | 268 | } 269 | , { 270 | "box" : { 271 | "maxclass" : "message", 272 | "text" : "print", 273 | "fontsize" : 12.0, 274 | "numoutlets" : 1, 275 | "outlettype" : [ "" ], 276 | "patching_rect" : [ 17.0, 74.0, 34.0, 18.0 ], 277 | "id" : "obj-4", 278 | "fontname" : "Arial", 279 | "numinlets" : 2 280 | } 281 | 282 | } 283 | , { 284 | "box" : { 285 | "maxclass" : "newobj", 286 | "text" : "print", 287 | "fontsize" : 12.0, 288 | "numoutlets" : 0, 289 | "patching_rect" : [ 17.0, 542.0, 34.0, 20.0 ], 290 | "id" : "obj-2", 291 | "fontname" : "Arial", 292 | "numinlets" : 1 293 | } 294 | 295 | } 296 | , { 297 | "box" : { 298 | "maxclass" : "newobj", 299 | "text" : "cmdmessenger c", 300 | "fontsize" : 12.0, 301 | "numoutlets" : 1, 302 | "outlettype" : [ "" ], 303 | "patching_rect" : [ 17.0, 431.0, 102.0, 20.0 ], 304 | "id" : "obj-1", 305 | "fontname" : "Arial", 306 | "numinlets" : 1 307 | } 308 | 309 | } 310 | ], 311 | "lines" : [ { 312 | "patchline" : { 313 | "source" : [ "obj-24", 0 ], 314 | "destination" : [ "obj-1", 0 ], 315 | "hidden" : 0, 316 | "midpoints" : [ ] 317 | } 318 | 319 | } 320 | , { 321 | "patchline" : { 322 | "source" : [ "obj-22", 0 ], 323 | "destination" : [ "obj-1", 0 ], 324 | "hidden" : 0, 325 | "midpoints" : [ ] 326 | } 327 | 328 | } 329 | , { 330 | "patchline" : { 331 | "source" : [ "obj-12", 0 ], 332 | "destination" : [ "obj-1", 0 ], 333 | "hidden" : 0, 334 | "midpoints" : [ 46.5, 418.0, 26.5, 418.0 ] 335 | } 336 | 337 | } 338 | , { 339 | "patchline" : { 340 | "source" : [ "obj-15", 0 ], 341 | "destination" : [ "obj-1", 0 ], 342 | "hidden" : 0, 343 | "midpoints" : [ 68.5, 418.0, 26.5, 418.0 ] 344 | } 345 | 346 | } 347 | , { 348 | "patchline" : { 349 | "source" : [ "obj-17", 0 ], 350 | "destination" : [ "obj-1", 0 ], 351 | "hidden" : 0, 352 | "midpoints" : [ ] 353 | } 354 | 355 | } 356 | , { 357 | "patchline" : { 358 | "source" : [ "obj-16", 0 ], 359 | "destination" : [ "obj-1", 0 ], 360 | "hidden" : 0, 361 | "midpoints" : [ ] 362 | } 363 | 364 | } 365 | , { 366 | "patchline" : { 367 | "source" : [ "obj-1", 0 ], 368 | "destination" : [ "obj-2", 0 ], 369 | "hidden" : 0, 370 | "midpoints" : [ ] 371 | } 372 | 373 | } 374 | , { 375 | "patchline" : { 376 | "source" : [ "obj-4", 0 ], 377 | "destination" : [ "obj-1", 0 ], 378 | "hidden" : 0, 379 | "midpoints" : [ 26.5, 166.0, 26.0, 166.0, 26.0, 232.0, 26.5, 232.0 ] 380 | } 381 | 382 | } 383 | , { 384 | "patchline" : { 385 | "source" : [ "obj-18", 0 ], 386 | "destination" : [ "obj-1", 0 ], 387 | "hidden" : 0, 388 | "midpoints" : [ ] 389 | } 390 | 391 | } 392 | , { 393 | "patchline" : { 394 | "source" : [ "obj-21", 0 ], 395 | "destination" : [ "obj-1", 0 ], 396 | "hidden" : 0, 397 | "midpoints" : [ ] 398 | } 399 | 400 | } 401 | ] 402 | } 403 | 404 | } 405 | -------------------------------------------------------------------------------- /Max5/cmdmessenger.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "rect" : [ 387.0, 44.0, 1053.0, 830.0 ], 5 | "bglocked" : 0, 6 | "defrect" : [ 387.0, 44.0, 1053.0, 830.0 ], 7 | "openrect" : [ 0.0, 0.0, 0.0, 0.0 ], 8 | "openinpresentation" : 0, 9 | "default_fontsize" : 12.0, 10 | "default_fontface" : 0, 11 | "default_fontname" : "Arial", 12 | "gridonopen" : 0, 13 | "gridsize" : [ 15.0, 15.0 ], 14 | "gridsnaponopen" : 0, 15 | "toolbarvisible" : 1, 16 | "boxanimatetime" : 200, 17 | "imprint" : 0, 18 | "enablehscroll" : 1, 19 | "enablevscroll" : 1, 20 | "devicewidth" : 0.0, 21 | "boxes" : [ { 22 | "box" : { 23 | "maxclass" : "comment", 24 | "text" : "<-- After zi group we're outputting our ASCII stream as a MaxMSP lists. For further processing. Generally lists seem to be the most useful data representation here.", 25 | "linecount" : 2, 26 | "fontsize" : 12.0, 27 | "numoutlets" : 0, 28 | "patching_rect" : [ 383.0, 699.0, 492.0, 34.0 ], 29 | "id" : "obj-32", 30 | "fontname" : "Arial", 31 | "numinlets" : 1 32 | } 33 | 34 | } 35 | , { 36 | "box" : { 37 | "maxclass" : "comment", 38 | "text" : "<-- Last param here is \"any other data\" = our comm port transmit data", 39 | "fontsize" : 12.0, 40 | "numoutlets" : 0, 41 | "patching_rect" : [ 401.0, 182.0, 421.0, 20.0 ], 42 | "id" : "obj-13", 43 | "fontname" : "Arial", 44 | "numinlets" : 1 45 | } 46 | 47 | } 48 | , { 49 | "box" : { 50 | "maxclass" : "comment", 51 | "text" : "<-- The MaxMSP Serial Comm port object", 52 | "fontsize" : 12.0, 53 | "numoutlets" : 0, 54 | "patching_rect" : [ 346.0, 307.0, 246.0, 20.0 ], 55 | "id" : "obj-12", 56 | "fontname" : "Arial", 57 | "numinlets" : 1 58 | } 59 | 60 | } 61 | , { 62 | "box" : { 63 | "maxclass" : "comment", 64 | "text" : "<-- Here, integers 10 and 13 correspond to CR/LF (\\r\\n). We use select to swallow those characters if the Arduino is printing carriage return / newline. If we had this in our Arduino code:\ncmdMessenger.print_LF_CR();", 65 | "linecount" : 3, 66 | "fontsize" : 12.0, 67 | "numoutlets" : 0, 68 | "patching_rect" : [ 348.0, 359.0, 515.0, 48.0 ], 69 | "id" : "obj-11", 70 | "fontname" : "Arial", 71 | "numinlets" : 1 72 | } 73 | 74 | } 75 | , { 76 | "box" : { 77 | "maxclass" : "newobj", 78 | "text" : "select 10 13", 79 | "fontsize" : 12.0, 80 | "numoutlets" : 3, 81 | "outlettype" : [ "bang", "bang", "" ], 82 | "patching_rect" : [ 260.0, 357.0, 75.0, 20.0 ], 83 | "id" : "obj-3", 84 | "fontname" : "Arial", 85 | "numinlets" : 1 86 | } 87 | 88 | } 89 | , { 90 | "box" : { 91 | "maxclass" : "comment", 92 | "text" : "<-- Note: For splitting up multi-part messages (eg \"6,part1,part2,part3;\"). Use this same trick. But break on field seperator instead", 93 | "linecount" : 2, 94 | "fontsize" : 12.0, 95 | "numoutlets" : 0, 96 | "patching_rect" : [ 548.0, 519.0, 407.0, 34.0 ], 97 | "id" : "obj-10", 98 | "fontname" : "Arial", 99 | "numinlets" : 1 100 | } 101 | 102 | } 103 | , { 104 | "box" : { 105 | "maxclass" : "comment", 106 | "text" : "<-- Generate bangs on the command seperator", 107 | "linecount" : 2, 108 | "fontsize" : 12.0, 109 | "numoutlets" : 0, 110 | "patching_rect" : [ 385.0, 517.0, 153.0, 34.0 ], 111 | "id" : "obj-4", 112 | "fontname" : "Arial", 113 | "numinlets" : 1 114 | } 115 | 116 | } 117 | , { 118 | "box" : { 119 | "maxclass" : "newobj", 120 | "text" : "select \\;", 121 | "fontsize" : 12.0, 122 | "numoutlets" : 2, 123 | "outlettype" : [ "bang", "" ], 124 | "patching_rect" : [ 317.0, 518.0, 52.0, 20.0 ], 125 | "id" : "obj-15", 126 | "fontname" : "Arial", 127 | "numinlets" : 1 128 | } 129 | 130 | } 131 | , { 132 | "box" : { 133 | "maxclass" : "comment", 134 | "text" : "<-- collect the characters between bangs. zi group takes the bangs as the first argument and the recieved ASCII stream as the second argument. When paired with the select statement (above), it generates the bangs we need for zi group, and select also swallows the \\; command character.", 135 | "linecount" : 3, 136 | "fontsize" : 12.0, 137 | "numoutlets" : 0, 138 | "patching_rect" : [ 384.0, 595.0, 534.0, 48.0 ], 139 | "id" : "obj-28", 140 | "fontname" : "Arial", 141 | "numinlets" : 1 142 | } 143 | 144 | } 145 | , { 146 | "box" : { 147 | "maxclass" : "comment", 148 | "text" : "<-- atoi converts our formatted ASCII input string into an integer list, which is what the MaxMSP serial object will accept", 149 | "linecount" : 2, 150 | "fontsize" : 12.0, 151 | "numoutlets" : 0, 152 | "patching_rect" : [ 463.0, 216.0, 348.0, 34.0 ], 153 | "id" : "obj-26", 154 | "fontname" : "Arial", 155 | "numinlets" : 1 156 | } 157 | 158 | } 159 | , { 160 | "box" : { 161 | "maxclass" : "comment", 162 | "text" : "<-- translate the received stream of bytes from integers back into ascii", 163 | "fontsize" : 12.0, 164 | "numoutlets" : 0, 165 | "patching_rect" : [ 386.0, 449.0, 383.0, 20.0 ], 166 | "id" : "obj-25", 167 | "fontname" : "Arial", 168 | "numinlets" : 1 169 | } 170 | 171 | } 172 | , { 173 | "box" : { 174 | "maxclass" : "comment", 175 | "text" : "Flush our serial TX transmit buffer every 1 millisecond vv", 176 | "linecount" : 2, 177 | "fontsize" : 12.0, 178 | "numoutlets" : 0, 179 | "patching_rect" : [ 44.0, 170.0, 184.0, 34.0 ], 180 | "id" : "obj-24", 181 | "fontname" : "Arial", 182 | "numinlets" : 1 183 | } 184 | 185 | } 186 | , { 187 | "box" : { 188 | "maxclass" : "newobj", 189 | "text" : "route print port baud", 190 | "fontsize" : 12.0, 191 | "numoutlets" : 4, 192 | "outlettype" : [ "", "", "", "" ], 193 | "patching_rect" : [ 243.0, 157.0, 119.0, 20.0 ], 194 | "id" : "obj-7", 195 | "fontname" : "Arial", 196 | "numinlets" : 1 197 | } 198 | 199 | } 200 | , { 201 | "box" : { 202 | "maxclass" : "inlet", 203 | "numoutlets" : 1, 204 | "outlettype" : [ "" ], 205 | "patching_rect" : [ 242.0, 77.0, 25.0, 25.0 ], 206 | "id" : "obj-6", 207 | "numinlets" : 0, 208 | "comment" : "" 209 | } 210 | 211 | } 212 | , { 213 | "box" : { 214 | "maxclass" : "newobj", 215 | "text" : "atoi", 216 | "fontsize" : 12.0, 217 | "numoutlets" : 1, 218 | "outlettype" : [ "list" ], 219 | "patching_rect" : [ 405.0, 214.0, 44.0, 20.0 ], 220 | "id" : "obj-2", 221 | "fontname" : "Arial", 222 | "numinlets" : 3 223 | } 224 | 225 | } 226 | , { 227 | "box" : { 228 | "maxclass" : "message", 229 | "text" : "baud $1", 230 | "fontsize" : 12.0, 231 | "numoutlets" : 1, 232 | "outlettype" : [ "" ], 233 | "patching_rect" : [ 340.0, 215.0, 54.0, 18.0 ], 234 | "id" : "obj-23", 235 | "fontname" : "Arial", 236 | "numinlets" : 2 237 | } 238 | 239 | } 240 | , { 241 | "box" : { 242 | "maxclass" : "newobj", 243 | "text" : "loadbang", 244 | "fontsize" : 12.0, 245 | "numoutlets" : 1, 246 | "outlettype" : [ "bang" ], 247 | "patching_rect" : [ 132.0, 208.0, 60.0, 20.0 ], 248 | "id" : "obj-18", 249 | "fontname" : "Arial", 250 | "numinlets" : 1 251 | } 252 | 253 | } 254 | , { 255 | "box" : { 256 | "maxclass" : "newobj", 257 | "text" : "itoa", 258 | "fontsize" : 12.0, 259 | "numoutlets" : 1, 260 | "outlettype" : [ "int" ], 261 | "patching_rect" : [ 317.0, 449.0, 46.0, 20.0 ], 262 | "id" : "obj-17", 263 | "fontname" : "Arial", 264 | "numinlets" : 3 265 | } 266 | 267 | } 268 | , { 269 | "box" : { 270 | "maxclass" : "outlet", 271 | "numoutlets" : 0, 272 | "patching_rect" : [ 317.0, 698.0, 25.0, 25.0 ], 273 | "id" : "obj-16", 274 | "numinlets" : 1, 275 | "comment" : "" 276 | } 277 | 278 | } 279 | , { 280 | "box" : { 281 | "maxclass" : "newobj", 282 | "text" : "zl group", 283 | "fontsize" : 12.0, 284 | "numoutlets" : 2, 285 | "outlettype" : [ "", "" ], 286 | "patching_rect" : [ 317.0, 597.0, 53.0, 20.0 ], 287 | "id" : "obj-14", 288 | "fontname" : "Arial", 289 | "numinlets" : 2 290 | } 291 | 292 | } 293 | , { 294 | "box" : { 295 | "maxclass" : "newobj", 296 | "text" : "qmetro 1", 297 | "fontsize" : 12.0, 298 | "numoutlets" : 1, 299 | "outlettype" : [ "bang" ], 300 | "patching_rect" : [ 132.0, 237.0, 58.0, 20.0 ], 301 | "id" : "obj-9", 302 | "fontname" : "Arial", 303 | "numinlets" : 2 304 | } 305 | 306 | } 307 | , { 308 | "box" : { 309 | "maxclass" : "message", 310 | "text" : "port $1", 311 | "fontsize" : 12.0, 312 | "numoutlets" : 1, 313 | "outlettype" : [ "" ], 314 | "patching_rect" : [ 284.0, 215.0, 48.0, 18.0 ], 315 | "id" : "obj-8", 316 | "fontname" : "Arial", 317 | "numinlets" : 2 318 | } 319 | 320 | } 321 | , { 322 | "box" : { 323 | "maxclass" : "message", 324 | "text" : "print", 325 | "fontsize" : 12.0, 326 | "numoutlets" : 1, 327 | "outlettype" : [ "" ], 328 | "patching_rect" : [ 243.0, 215.0, 34.0, 18.0 ], 329 | "id" : "obj-5", 330 | "fontname" : "Arial", 331 | "numinlets" : 2 332 | } 333 | 334 | } 335 | , { 336 | "box" : { 337 | "maxclass" : "newobj", 338 | "text" : "serial #1", 339 | "fontsize" : 12.0, 340 | "numoutlets" : 2, 341 | "outlettype" : [ "int", "" ], 342 | "patching_rect" : [ 260.0, 304.0, 56.0, 20.0 ], 343 | "id" : "obj-1", 344 | "fontname" : "Arial", 345 | "numinlets" : 1 346 | } 347 | 348 | } 349 | ], 350 | "lines" : [ { 351 | "patchline" : { 352 | "source" : [ "obj-14", 0 ], 353 | "destination" : [ "obj-16", 0 ], 354 | "hidden" : 0, 355 | "midpoints" : [ ] 356 | } 357 | 358 | } 359 | , { 360 | "patchline" : { 361 | "source" : [ "obj-15", 1 ], 362 | "destination" : [ "obj-14", 0 ], 363 | "hidden" : 0, 364 | "midpoints" : [ ] 365 | } 366 | 367 | } 368 | , { 369 | "patchline" : { 370 | "source" : [ "obj-15", 0 ], 371 | "destination" : [ "obj-14", 0 ], 372 | "hidden" : 0, 373 | "midpoints" : [ ] 374 | } 375 | 376 | } 377 | , { 378 | "patchline" : { 379 | "source" : [ "obj-18", 0 ], 380 | "destination" : [ "obj-9", 0 ], 381 | "hidden" : 0, 382 | "midpoints" : [ ] 383 | } 384 | 385 | } 386 | , { 387 | "patchline" : { 388 | "source" : [ "obj-9", 0 ], 389 | "destination" : [ "obj-1", 0 ], 390 | "hidden" : 0, 391 | "midpoints" : [ ] 392 | } 393 | 394 | } 395 | , { 396 | "patchline" : { 397 | "source" : [ "obj-17", 0 ], 398 | "destination" : [ "obj-15", 0 ], 399 | "hidden" : 0, 400 | "midpoints" : [ ] 401 | } 402 | 403 | } 404 | , { 405 | "patchline" : { 406 | "source" : [ "obj-8", 0 ], 407 | "destination" : [ "obj-1", 0 ], 408 | "hidden" : 0, 409 | "midpoints" : [ ] 410 | } 411 | 412 | } 413 | , { 414 | "patchline" : { 415 | "source" : [ "obj-5", 0 ], 416 | "destination" : [ "obj-1", 0 ], 417 | "hidden" : 0, 418 | "midpoints" : [ ] 419 | } 420 | 421 | } 422 | , { 423 | "patchline" : { 424 | "source" : [ "obj-6", 0 ], 425 | "destination" : [ "obj-7", 0 ], 426 | "hidden" : 0, 427 | "midpoints" : [ ] 428 | } 429 | 430 | } 431 | , { 432 | "patchline" : { 433 | "source" : [ "obj-7", 1 ], 434 | "destination" : [ "obj-8", 0 ], 435 | "hidden" : 0, 436 | "midpoints" : [ ] 437 | } 438 | 439 | } 440 | , { 441 | "patchline" : { 442 | "source" : [ "obj-7", 0 ], 443 | "destination" : [ "obj-5", 0 ], 444 | "hidden" : 0, 445 | "midpoints" : [ ] 446 | } 447 | 448 | } 449 | , { 450 | "patchline" : { 451 | "source" : [ "obj-2", 0 ], 452 | "destination" : [ "obj-1", 0 ], 453 | "hidden" : 0, 454 | "midpoints" : [ ] 455 | } 456 | 457 | } 458 | , { 459 | "patchline" : { 460 | "source" : [ "obj-7", 2 ], 461 | "destination" : [ "obj-23", 0 ], 462 | "hidden" : 0, 463 | "midpoints" : [ ] 464 | } 465 | 466 | } 467 | , { 468 | "patchline" : { 469 | "source" : [ "obj-23", 0 ], 470 | "destination" : [ "obj-1", 0 ], 471 | "hidden" : 0, 472 | "midpoints" : [ ] 473 | } 474 | 475 | } 476 | , { 477 | "patchline" : { 478 | "source" : [ "obj-7", 3 ], 479 | "destination" : [ "obj-2", 0 ], 480 | "hidden" : 0, 481 | "midpoints" : [ ] 482 | } 483 | 484 | } 485 | , { 486 | "patchline" : { 487 | "source" : [ "obj-1", 0 ], 488 | "destination" : [ "obj-3", 0 ], 489 | "hidden" : 0, 490 | "midpoints" : [ ] 491 | } 492 | 493 | } 494 | , { 495 | "patchline" : { 496 | "source" : [ "obj-3", 2 ], 497 | "destination" : [ "obj-17", 0 ], 498 | "hidden" : 0, 499 | "midpoints" : [ ] 500 | } 501 | 502 | } 503 | ] 504 | } 505 | 506 | } 507 | --------------------------------------------------------------------------------