topical media & game development

talk show tell print

lib-of-vs-libs-openFrameworks-communication-ofStandardFirmata.cpp / cpp



  /*
   * Copyright 2007-2008 (c) Erik Sjodin, eriksjodin.net
   *
   * Permission is hereby granted, free of charge, to any person
   * obtaining a copy of this software and associated documentation
   * files (the "Software"), to deal in the Software without
   * restriction, including without limitation the rights to use,
   * copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the
   * Software is furnished to do so, subject to the following
   * conditions:
   *
   * The above copyright notice and this permission notice shall be
   * included in all copies or substantial _portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
   * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
   * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
   * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
   * OTHER DEALINGS IN THE SOFTWARE.
   */
  
  include <ofStandardFirmata.h>
  
  // TODO thread it?
  // TODO throw event or exception if the serial port goes down...
  //---------------------------------------------------------------------------
  ofStandardFirmata::ofStandardFirmata(){
          _portStatus=-1;
          _waitForData=0;
          _analogHistoryLength = 2;
          _digitalHistoryLength = 2;
          _stringHistoryLength = 1;
          _sysExHistoryLength = 1;
  
          _majorProtocolVersion = 0;
          _minorProtocolVersion = 0;
          _majorFirmwareVersion = 0;
          _minorFirmwareVersion = 0;
          _firmwareName = "Unknown";
  
      // ports
          for(int i=0; i<ARD_TOTAL_PORTS; ++i) {
                  _digitalPortValue[i]=0;
                  _digitalPortReporting[i] = ARD_OFF;
          }
  
      // digital pins
          for(int i=0; i<16; ++i) {
                  _digitalPinValue[i] = -1;
                  _digitalPinMode[i] = ARD_OUTPUT;
                  _digitalPinReporting[i] = ARD_OFF;
          }
  
          // analog in pins
          for (int i=0; i<ARD_TOTAL_ANALOG_PINS; ++i) {
                  _analogPinReporting[i] = ARD_OFF;
                  // analog pins used as digital
                  _digitalPinMode[i]=ARD_ANALOG;
                  _digitalPinValue[i] = -1;
          }
  }
  
  ofStandardFirmata::~ofStandardFirmata() {
          _port.close();
  }
  
  int ofStandardFirmata::connect(string device, int baud){
          _initialized = false;
          _port.enumerateDevices();
          return _port.setup(device.c_str(), baud);
  }
  
  void ofStandardFirmata::setDigitalHistoryLength(int length){
          if(length>=2)
                  _digitalHistoryLength=length;
  }
  
  void ofStandardFirmata::setAnalogHistoryLength(int length){
          if(length>=2)
                  _analogHistoryLength=length;
  }
  
  void ofStandardFirmata::setSysExHistoryLength(int length){
          if(length>=1)
                  _sysExHistoryLength=length;
  }
  
  void ofStandardFirmata::setStringHistoryLength(int length){
          if(length>=1)
                  _stringHistoryLength=length;
  }
  
  void ofStandardFirmata::disconnect(){
          _port.close();
  }
  
  void ofStandardFirmata::update(){
          int dataRead=0;
          // try to empty the _port buffer
          while (dataRead<512) {
  
                  int byte = _port.readByte();
  
                  // process data....
                  if (byte!=-1) {
                          processData((char)(byte));
                          dataRead++;
                  }
                  // _port buffer is empty
                  else{
                          break;
                  }
          }
  }
  
  int ofStandardFirmata::getAnalog(int pin){
          if(_analogHistory[pin].size()>0)
                  return _analogHistory[pin].front();
          else
                  return -1;
  }
  
  int ofStandardFirmata::getDigital(int pin){
          if(_digitalPinMode[pin]==ARD_INPUT && _digitalHistory[pin].size()>0)
                  return _digitalHistory[pin].front();
          else if (_digitalPinMode[pin]==ARD_OUTPUT)
                  return _digitalPinValue[pin];
          else
                  return -1;
  }
  
  int ofStandardFirmata::getPwm(int pin){
          if(_digitalPinMode[pin]==ARD_PWM)
                  return _digitalPinValue[pin];
          else
                  return -1;
  }
  
  vector<unsigned char> ofStandardFirmata::getSysEx(){
          return _sysExHistory.front();
  }
  
  string ofStandardFirmata::getString(){
          return _stringHistory.front();
  }
  
  int ofStandardFirmata::getDigitalPinMode(int pin){
          return _digitalPinMode[pin];
  }
  
  void ofStandardFirmata::sendDigital(int pin, int value, bool force){
          if((_digitalPinMode[pin]==ARD_INPUT || _digitalPinMode[pin]==ARD_OUTPUT) && (_digitalPinValue[pin]!=value || force)){
  
                  _digitalPinValue[pin] = value;
  
                  int port=0;
                  int bit=0;
  
                  if(pin < 8 && pin >1){
                          port=0;
                          bit = pin;
                  }
                  else if(pin>7 && pin <14){
                          port = 1;
                          bit = pin-8;
                  }
                  else if(pin>15 && pin <22){
                          port = 2;
                          bit = pin-16;
                  }
  
                  // set the bit
                  if(value==1)
                          _digitalPortValue[port] |= (1 << bit);
  
                  // clear the bit
                  if(value==0)
                          _digitalPortValue[port] &= ~(1 << bit);
  
                  sendByte(FIRMATA_DIGITAL_MESSAGE+port);
                  sendValueAsTwo7bitBytes(_digitalPortValue[port]);
  
          }
  }
  
  void ofStandardFirmata::sendPwm(int pin, int value, bool force){
          if(_digitalPinMode[pin]==ARD_PWM && (_digitalPinValue[pin]!=value || force)){
                  sendByte(FIRMATA_ANALOG_MESSAGE+pin);
                  sendValueAsTwo7bitBytes(value);
                  _digitalPinValue[pin] = value;
          }
  }
  
  void ofStandardFirmata::sendSysEx(int command, vector<unsigned char> data){
          sendByte(FIRMATA_START_SYSEX);
          sendByte(command);
          vector<unsigned char>::iterator it = data.begin();
          while( it != data.end() ) {
                  sendByte(*it);
                  it++;
          }
          sendByte(FIRMATA_END_SYSEX);
  }
  
  void ofStandardFirmata::sendSysExBegin(){
          sendByte(FIRMATA_START_SYSEX);
  }
  
  void ofStandardFirmata::sendSysExEnd(){
          sendByte(FIRMATA_END_SYSEX);
  }
  
  void ofStandardFirmata::sendString(string str){
          sendByte(FIRMATA_START_SYSEX);
          sendByte(FIRMATA_SYSEX_FIRMATA_STRING);
          string::iterator it = str.begin();
          while( it != str.end() ) {
                  sendValueAsTwo7bitBytes(*it);
                  it++;
          }
          sendByte(FIRMATA_END_SYSEX);
  }
  
  void ofStandardFirmata::sendProtocolVersionRequest(){
          sendByte(FIRMATA_REPORT_VERSION);
  }
  
  void ofStandardFirmata::sendFirmwareVersionRequest(){
          sendByte(FIRMATA_START_SYSEX);
          sendByte(FIRMATA_SYSEX_REPORT_FIRMWARE);
          sendByte(FIRMATA_END_SYSEX);
  }
  
  void ofStandardFirmata::sendReset(){
          sendByte(FIRMATA_SYSTEM_RESET);
  }
  
  void ofStandardFirmata::sendAnalogPinReporting(int pin, int mode){
          int i;
          //bool send;
  
      // disable reporting for all pins on port 2
          for(i=16; i<22; ++i) {
                  if(_digitalPinReporting[i]==ARD_ON)
                          sendDigitalPinReporting(i, ARD_OFF);
          }
  
          _digitalPinMode[16+pin]=ARD_ANALOG;
  
          sendByte(FIRMATA_REPORT_ANALOG+pin);
          sendByte(mode);
          _analogPinReporting[pin] = mode;
  }
  
  void ofStandardFirmata::sendDigitalPinMode(int pin, int mode){
          sendByte(FIRMATA_SET_PIN_MODE);
          sendByte(pin); // Tx pins 0-6
          sendByte(mode);
          _digitalPinMode[pin]=mode;
  
          // turn on or off reporting on the port
          if(mode==ARD_INPUT){
                  sendDigitalPinReporting(pin, ARD_ON);
          }
          else {
                  sendDigitalPinReporting(pin, ARD_OFF);
          }
  }
  
  int ofStandardFirmata::getAnalogPinReporting(int pin){
          return _analogPinReporting[pin];
  }
  
  list<int>* ofStandardFirmata::getAnalogHistory(int pin){
          return &_analogHistory[pin];
  }
  
  list<int>* ofStandardFirmata::getDigitalHistory(int pin){
          return &_digitalHistory[pin];
  }
  
  list<vector<unsigned char> >* ofStandardFirmata::getSysExHistory(){
          return &_sysExHistory;
  }
  
  list<string>* ofStandardFirmata::getStringHistory(){
          return &_stringHistory;
  }
  
  int ofStandardFirmata::getMajorProtocolVersion(){
          return _majorFirmwareVersion;
  }
  
  int ofStandardFirmata::getMinorProtocolVersion(){
          return _minorFirmwareVersion;
  }
  
  int ofStandardFirmata::getMajorFirmwareVersion(){
          return _majorFirmwareVersion;
  }
  
  int ofStandardFirmata::getMinorFirmwareVersion(){
          return _minorFirmwareVersion;
  }
  
  string ofStandardFirmata::getFirmwareName(){
          return _firmwareName;
  }
  
  bool ofStandardFirmata::isInitialized(){
          return _initialized;
  }
  
  // ------------------------------ private functions
  
  void ofStandardFirmata::processData(unsigned char inputData){
  
          char msg[100];
          sprintf(msg, "Received Byte: \%i", inputData);
          //Logger::get("Application").information(msg);
  
          // we have command data
          if(_waitForData>0 && inputData<128) {
                  _waitForData--;
  
                  // collect the data
                  _storedInputData [waitForData] = inputData;
  
                  // we have all data executeMultiByteCommand
                  if(_waitForData==0) {
                          switch (_executeMultiByteCommand) {
                                  case FIRMATA_DIGITAL_MESSAGE:
                                          processDigitalPort(_multiByteChannel, (_storedInputData[0] << 7) | _storedInputData[1]);
                                  break;
                                  case FIRMATA_REPORT_VERSION: // report version
                                          _majorProtocolVersion = _storedInputData[1];
                                          _minorProtocolVersion = _storedInputData[0];
                                          ofNotifyEvent(EProtocolVersionReceived, _majorProtocolVersion, this);
                                  break;
                                  case FIRMATA_ANALOG_MESSAGE:
                                          int previous = _analogHistory [multiByteChannel].front();
  
                                          _analogHistory [multiByteChannel].push_front((_storedInputData[0] << 7) | _storedInputData[1]);
                                          if((int)_analogHistory [multiByteChannel].size()>_analogHistoryLength)
                                                  _analogHistory [multiByteChannel].pop_back();
  
                                          // trigger an event if the pin has changed value
                                          if(_analogHistory [multiByteChannel].front()!=previous)
                                                  ofNotifyEvent(EAnalogPinChanged, _multiByteChannel, this);
                                  break;
                          }
  
                  }
          }
          // we have SysEx command data
          else if(_waitForData<0){
                  // we have all sysex data
                  if(inputData==FIRMATA_END_SYSEX){
                          _waitForData=0;
                          processSysExData(_sysExData);
                          _sysExData.clear();
                  }
                  // still have data, collect it
                  else {
                          _sysExData.push_back((unsigned char)inputData);
                  }
          }
          // we have a command
          else{
  
                  int command;
  
                  // extract the command and channel info from a byte if it is less than 0xF0
                  if(inputData < 0xF0) {
                    command = inputData & 0xF0;
                    _multiByteChannel = inputData & 0x0F;
                  }
                  else {
                    // commands in the 0xF* range don't use channel data
                    command = inputData;
                  }
  
                  switch (command) {
                          case FIRMATA_REPORT_VERSION:
                          case FIRMATA_DIGITAL_MESSAGE:
                          case FIRMATA_ANALOG_MESSAGE:
                                  _waitForData = 2;  // 2 bytes needed
                                  _executeMultiByteCommand = command;
                          break;
                          case FIRMATA_START_SYSEX:
                                  _sysExData.clear();
                                  _waitForData = -1;  // n bytes needed, -1 is used to indicate sysex message
                                  _executeMultiByteCommand = command;
                          break;
                  }
  
          }
  }
  
  // sysex data is assumed to be 8-bit bytes split into two 7-bit bytes.
  void ofStandardFirmata::processSysExData(vector<unsigned char> data){
  
          string str;
  
          vector<unsigned char>::iterator it;
          unsigned char buffer;
          //int i = 1;
  
          // act on reserved sysEx messages (extended commands) or trigger SysEx event...
          switch(data.front()) { //first byte in buffer is command
                  case FIRMATA_SYSEX_REPORT_FIRMWARE:
                          it = data.begin();
                          it++; // skip the first byte, which is the firmware version command
                          _majorFirmwareVersion = *it;
                          it++;
                          _minorFirmwareVersion = *it;
                          it++;
  
                          while( it != data.end() ) {
                                          buffer = *it;
                                          it++;
                                          buffer += *it << 7;
                                          it++;
                                          str+=buffer;
                          }
                          _firmwareName = str;
  
                          ofNotifyEvent(EFirmwareVersionReceived, _majorFirmwareVersion, this);
  
                          // trigger the initialization event
                          ofNotifyEvent(EInitialized, _majorFirmwareVersion, this);
                          _initialized = true;
  
                  break;
                  case FIRMATA_SYSEX_FIRMATA_STRING:
                          it = data.begin();
                          it++; // skip the first byte, which is the string command
                          while( it != data.end() ) {
                                          buffer = *it;
                                          it++;
                                          buffer += *it << 7;
                                          it++;
                                          str+=buffer;
                          }
  
                          _stringHistory.push_front(str);
                          if((int)_stringHistory.size()>_stringHistoryLength)
                                          _stringHistory.pop_back();
  
                          ofNotifyEvent(EStringReceived, str, this);
                  break;
                  default: // the message isn't in Firmatas extended command set
                          _sysExHistory.push_front(data);
                          if((int)_sysExHistory.size()>_sysExHistoryLength)
                                          _sysExHistory.pop_back();
                          ofNotifyEvent(ESysExReceived, data, this);
                  break;
  
          }
  }
  
  void ofStandardFirmata::processDigitalPort(int port, unsigned char value){
  
          unsigned char mask;
          int previous;
          int i;
          int pin;
          switch(port) {
                  case 0: // pins 2-7  (0,1 are ignored as serial RX/TX)
                          for(i=2; i<8; ++i) {
                                  pin = i;
                                  if(_digitalPinMode[pin]==ARD_INPUT){
                                          previous = _digitalHistory[pin].front();
  
                                          mask = 1 << i;
                                          _digitalHistory[pin].push_front((value & mask)>>i);
  
                                          if((int)_digitalHistory[pin].size()>_digitalHistoryLength)
                                                          _digitalHistory[pin].pop_back();
  
                                          // trigger an event if the pin has changed value
                                          if(_digitalHistory[pin].front()!=previous){
                                                  ofNotifyEvent(EDigitalPinChanged, pin, this);
                                          }
                                  }
                          }
          break;
                  case 1: // pins 8-13 (14,15 are disabled for the crystal)
                   for(i=0; i<6; ++i) {
                          pin = i+8;
  
                          if(_digitalPinMode[pin]==ARD_INPUT){
                                  previous = _digitalHistory[pin].front();
  
                                  mask = 1 << i;
                                  _digitalHistory[pin].push_front((value & mask)>>i);
  
                                  if((int)_digitalHistory[pin].size()>_digitalHistoryLength)
                                          _digitalHistory[pin].pop_back();
  
                                  // trigger an event if the pin has changed value
                                  if(_digitalHistory[pin].front()!=previous){
                                          ofNotifyEvent(EDigitalPinChanged, pin, this);
                                  }
                          }
                   }
          break;
          case 2: // analog pins used as digital pins 16-21
                  for(i=0; i<6; ++i) {
                          pin = i+16;
                          if(_digitalPinMode[pin]==ARD_INPUT){
                                  previous = _digitalHistory[pin].front();
  
                                  mask = 1 << i;
                                  _digitalHistory[pin].push_front((value & mask)>>i);
  
                                  if((int)_digitalHistory[pin].size()>_digitalHistoryLength)
                                          _digitalHistory[pin].pop_back();
  
                                  // trigger an event if the pin has changed value
                                  if(_digitalHistory[pin].front()!=previous){
                                          ofNotifyEvent(EDigitalPinChanged, pin, this);
                                  }
                          }
                  }
          break;
          }
  }
  
  // port 0: pins 2-7  (0,1 are serial RX/TX, don't change their values)
  // port 1: pins 8-13 (14,15 are disabled for the crystal)
  // port 2: pins 16-21 analog pins used as digital, all analog reporting will be turned off if this is set to ARD_ON
  void ofStandardFirmata::sendDigitalPortReporting(int port, int mode){
          sendByte(FIRMATA_REPORT_DIGITAL+port);
          sendByte(mode);
          _digitalPortReporting[port] = mode;
          if(port==2 && mode==ARD_ON){ // if reporting is turned on on port 2 then ofStandardFirmata on the Arduino disables all analog reporting
  
                  for (int i=0; i<ARD_TOTAL_ANALOG_PINS; i++) {
                                  _analogPinReporting[i] = ARD_OFF;
                  }
          }
  }
  
  void ofStandardFirmata::sendDigitalPinReporting(int pin, int mode){
          _digitalPinReporting[pin] = mode;
          if(mode==ARD_ON){        // enable reporting for the port
                  if(pin<=7 && pin>=2)
                          sendDigitalPortReporting(0, ARD_ON);
                  if(pin<=13 && pin>=8)
                          sendDigitalPortReporting(1, ARD_ON);
                  if(pin<=21 && pin>=16)
                          sendDigitalPortReporting(2, ARD_ON);
          }
          else if(mode==ARD_OFF){
                  int i;
                  bool send=true;
                  if(pin<8 && pin>=2){    // check if all pins on the port are off, if so set port reporting to off..
                          for(i=2; i<8; ++i) {
                                  if(_digitalPinReporting[i]==ARD_ON)
                                                  send=false;
                          }
                          if(send)
                                  sendDigitalPortReporting(0, ARD_OFF);
                  }
                  if(pin<14 && pin>=8){
                          for(i=8; i<14; ++i) {
                                  if(_digitalPinReporting[i]==ARD_ON)
                                                  send=false;
                          }
                          if(send)
                                  sendDigitalPortReporting(1, ARD_OFF);
                  }
                  if(pin<22 && pin>=16){
                          for(i=16; i<22; ++i) {
                                  if(_digitalPinReporting[i]==ARD_ON)
                                                  send=false;
                          }
                          if(send)
                                  sendDigitalPortReporting(2, ARD_OFF);
                  }
  
          }
  }
  
  void ofStandardFirmata::sendByte(unsigned char byte){
          //char msg[100];
          //sprintf(msg, "Sending Byte: \%i", byte);
          //Logger::get("Application").information(msg);
          _port.writeByte(byte);
  }
  
  // in Firmata (and MIDI) data bytes are 7-bits. The 8th bit serves as a flag to mark a byte as either command or data.
  // therefore you need two data bytes to send 8-bits (a char).
  void ofStandardFirmata::sendValueAsTwo7bitBytes(int value)
  {
          sendByte(value & 127); // LSB
          sendByte(value >> 7 & 127); // MSB
  }
  
  // SysEx data is sent as 8-bit bytes split into two 7-bit bytes, this function merges two 7-bit bytes back into one 8-bit byte.
  int ofStandardFirmata::getValueFromTwo7bitBytes(unsigned char lsb, unsigned char msb){
     return (msb << 7) | lsb;
  }
  


(C) Æliens 04/09/2009

You may not copy or print any of this material without explicit permission of the author or the publisher. In case of other copyright issues, contact the author.