/*
 * Copyright 2007-2008 (c) Erik Sjodin, eriksjodin.net
 *
 * Devloped at: The Interactive Institutet / Art and Technology, 
 * OF Lab / Ars Electronica
 *
 * 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.
 */
 
#ifndef OF_STANDARD_FIRMATA_H
#define OF_STANDARD_FIRMATA_H

using namespace std;
#include <list>
#include <vector>
#include <string>
#include <iostream>

#include "Poco/BasicEvent.h"
#include "Poco/EventArgs.h"

#include "ofSerial.h"

                /*
                 * Version numbers for the protocol. The protocol is still changing, so these
                 * version numbers are important. This number can be queried so that host
                 * software can test whether it will be compatible with the currently installed firmware.
                 */

                #define FIRMATA_MAJOR_VERSION                           2 // for non-compatible changes
                #define FIRMATA_MINOR_VERSION                           0 // for backwards compatible changes

                #define FIRMATA_MAX_DATA_BYTES                          32 // max number of data bytes in non-Sysex messages

                // message command bytes (128-255/0x80-0xFF)
                #define FIRMATA_DIGITAL_MESSAGE                         0x90 // send data for a digital pin
                #define FIRMATA_ANALOG_MESSAGE                          0xE0 // send data for an analog pin (or PWM)
                #define FIRMATA_REPORT_ANALOG                           0xC0 // enable analog input by pin #
                #define FIRMATA_REPORT_DIGITAL                          0xD0 // enable digital input by port pair
                //
                #define FIRMATA_SET_PIN_MODE                            0xF4 // set a pin to INPUT/OUTPUT/PWM/etc
                //
                #define FIRMATA_REPORT_VERSION                          0xF9 // report protocol version
                #define FIRMATA_SYSTEM_RESET                            0xFF // reset from MIDI
                //
                #define FIRMATA_START_SYSEX                             0xF0 // start a MIDI Sysex message
                #define FIRMATA_END_SYSEX                               0xF7 // end a MIDI Sysex message
               
                // pin modes
                #define FIRMATA_INPUT                                   0x00
                #define FIRMATA_OUTPUT                                  0x01
                #define FIRMATA_ANALOG                                  0x02 // analog pin in analogInput mode
                #define FIRMATA_PWM                                     0x03 // digital pin in PWM output mode
                #define FIRMATA_SERVO                                   0x04 // digital pin in Servo output mode
               
                // extended command set using SysEx (0-127/0x00-0x7F)
                /* 0x00-0x0F reserved for custom commands */
				#define FIRMATA_SYSEX_SERVO_CONFIG                      0x70
                #define FIRMATA_SYSEX_FIRMATA_STRING					0x71 // a string message with 14-bits per char
                #define FIRMATA_SYSEX_REPORT_FIRMWARE					0x79 // report name and version of the firmware
                #define FIRMATA_SYSEX_NON_REALTIME                      0x7E // MIDI Reserved for non-realtime messages
                #define FIRMATA_SYSEX_REALTIME                          0x7F // MIDI Reserved for realtime messages
                //#define FIRMATA_SYSEX_SHIFTOUT_MESSAGE				0xB0 // proposed shiftOut message (SysEx)
               
                // ---- arduino constants (for Arduino NG and Diecimila)
               
                // board settings 
                #define ARD_TOTAL_DIGITAL_PINS							22 // total number of pins currently supported
                #define ARD_TOTAL_ANALOG_PINS							8
                #define ARD_TOTAL_PORTS                                 3 // total number of ports for the board
               
                // pin modes
                #define ARD_INPUT                                       0x00
                #define ARD_OUTPUT                                      0x01
                #define ARD_ANALOG                                      0x02 // analog pin in analogInput mode
                #define ARD_PWM                                         0x03 // digital pin in PWM output mode
                #define ARD_SERVO                                       0x04 // digital pin in Servo output mode
               
                #define ARD_HIGH                                        1
                #define ARD_LOW                                         0
                #define ARD_ON                                          1
                #define ARD_OFF                                         0
               
                /*
                #if defined(__AVR_ATmega168__)  // Arduino NG and Diecimila
                #define ARD_TOTAL_ANALOG_PINS       8
                #define ARD_TOTAL_DIGITAL_PINS      22 // 14 digital + 8 analog
                #define ARD_TOTAL_PORTS             3 // total number of ports for the board
                #define ARD_ANALOG_PORT             2 // port# of analog used as digital
                #elif defined(__AVR_ATmega8__)  // old Arduinos
                #define ARD_TOTAL_ANALOG_PINS       6
                #define ARD_TOTAL_DIGITAL_PINS      20 // 14 digital + 6 analog
                #define ARD_TOTAL_PORTS             3  // total number of ports for the board
                #define ARD_ANALOG_PORT             2  // port# of analog used as digital
                #elif defined(__AVR_ATmega128__)// Wiring
                #define ARD_TOTAL_ANALOG_PINS       8
                #define ARD_TOTAL_DIGITAL_PINS      43
                #define ARD_TOTAL_PORTS             5 // total number of ports for the board
                #define ARD_ANALOG_PORT             2 // port# of analog used as digital
                #else // anything else
                #define ARD_TOTAL_ANALOG_PINS       6
                #define ARD_TOTAL_DIGITAL_PINS      14
                #define ARD_TOTAL_PORTS             3 // total number of ports for the board
                #define ARD_ANALOG_PORT             2 // port# of analog used as digital
                #endif
                */
               


/**
		Notes: 
		
		Communication problems could be caused by high baud rates, if you are using ofStandardFirmata then try to turn down the baud rate to 57600. 
		I.e. Change Firmata.begin() in setup() in ofStandardFirmata on the Arduino to Firmata.begin(57600) and connect using baud=57600;
		
        ofStandardFirmata on the Arduino will get caught in a while loop if data is sent to it continuosly at high speeds (for example on each update at 100FPS)
        As a consequence data will not be sent from the Arduino to the computer. This is why most of the sender functions only transmit data
        if it has been changed. This behavior can be overriden by setting force=true in the senders.
       
        This version of the Arduino class is intended to be used with the Firmata v2 protocol and the ofStandardFirmata firmware, see http://www.arduino.cc/playground/Interfacing/Firmata.  
        It currently supports the Arduino NG and Diecimila boards and other ATmega168 based Arduino compatible boards.  
		The class can be extended to accomodate modified versions of ofStandardFirmata.  
**/
class ofStandardFirmata{

        public:
                ofStandardFirmata();

                ~ofStandardFirmata();

                // --- setup functions
                int connect(string device, int baud=115200);
                // opens a serial port connection to the arduino
				
                void disconnect();
                // closes the serial port connection
               
				void update();
                // polls data from the serial port, this has to be called periodically
				
				bool isInitialized();
                // returns true if a succesfull connection has been established and the Arduino has reported a firmware
				
                void setDigitalHistoryLength(uint length);
                void setAnalogHistoryLength(uint length);
                void setStringHistoryLength(uint length);
                void setSysExHistoryLength(uint nSysEx);
               
                // --- senders
				
                void sendDigitalPinMode(int pin, int mode);
				// pin: 2-13
				// mode: ARD_INPUT, ARD_OUTPUT, ARD_PWM
				// setting a pins mode to ARD_INPUT turns on reporting for the port the pin is on 
				// Note: analog pins 0-5 can be used as digitial pins 16-21 but if the mode of _one_ of these pins is set to ARD_INPUT then _all_ analog pin reporting will be turned off
				
                void sendAnalogPinReporting(int pin, int mode);
				// pin: 0-5
                // mode: ARD_ON or ARD_OFF
				// Note: analog pins 0-5 can be used as digitial pins 16-21 but if reporting for _one_ analog pin is enabled then reporting for _all_ of digital pin 16-21 will be turned off

                void sendDigital(int pin, int value, bool force=false);
				// pin: 2-13
				// value: ARD_LOW or ARD_HIGH
				// the pins mode has to be set to ARD_OUTPUT or ARD_INPUT (in the latter mode pull-up resistors are enabled/disabled)
				// Note: pin 16-21 can also be used if analog inputs 0-5 are used as digital pins
					
                void sendPwm(int pin, int value, bool force=false);
				// pin: 3, 5, 6, 9, 10 and 11
				// value: 0 (always off) to 255 (always on).
				// the pins mode has to be set to ARD_PWM
				// TODO check if the PWM bug still is there causing frequent digital port reporting...
				
                void sendSysEx(int command, vector<unsigned char> data);
               
                void sendString(string str);
				// firmata can not handle strings longer than 12 characters.
               
                void sendProtocolVersionRequest();
				
                void sendFirmwareVersionRequest();
               
                void sendReset();
				
				// --- senders for SysEx communication
				
				void sendSysExBegin();
				// sends the FIRMATA_START_SYSEX command
				
				void sendSysExEnd();
				// sends the FIRMATA_END_SYSEX command

				void sendByte(unsigned char byte);
				// sends a byte without wrapping it in a firmata message, data has to be in the 0-127 range,
				// values > 127 will be interpreted as commands.
				
				void sendValueAsTwo7bitBytes(int value);
				// sends a value as two 7-bit bytes without wrapping it in a firmata message
				// values in the range 0 - 16384 will be sent as two bytes within the 0-127 data range. 
				
                // --- getters
				
                int getPwm(int pin);
				// pin: 3, 5, 6, 9, 10 and 11 
                // returns the last set PWM value (0-255) for the given pin
				// the pins mode has to be ARD_PWM 
				// Note: pin 16-21 can also be used if analog inputs 0-5 are used as digital pins
				
				int getDigital(int pin);
				// pin: 2-13 
                // returns the last received value (if the pin mode is ARD_INPUT) or the last set value (if the pin mode is ARD_OUTPUT) for the given pin
				// Note: pin 16-21 can also be used if analog inputs 0-5 are used as digital pins
				
                int getAnalog(int pin);
				// pin: 0-5
                // returns the last received analog value (0-1023) for the given pin 

                vector<unsigned char> getSysEx();
                // returns the last received SysEx message
               
                string getString();
                // returns the last received string
               
                int getMajorProtocolVersion();
                // returns the major firmware version
               
                int getMinorProtocolVersion();
                // returns the minor firmware version
               
                int getMajorFirmwareVersion();
                // returns the major firmware version
               
                int getMinorFirmwareVersion();
                // returns the minor firmware version
               
                string getFirmwareName();
                // returns the name of the firmware
               
                list<int>* getDigitalHistory(int pin);
				// pin: 2-13
                // returns a pointer to the digital data history list for the given pin
				// Note: pin 16-21 can also be used if analog inputs 0-5 are used as digital pins

                list<int>* getAnalogHistory(int pin);
				// pin: 0-5
                // returns a pointer to the analog data history list for the given pin

                list<vector<unsigned char> >* getSysExHistory();
                // returns a pointer to the SysEx history
               
                list<string>* getStringHistory();
                // returns a pointer to the string history
               
                int getDigitalPinMode(int pin);
                // returns ARD_INPUT, ARD_OUTPUT, ARD_PWM, ARD_SERVO, ARD_ANALOG
				
                int getAnalogPinReporting(int pin);
                // returns ARD_ON, ARD_OFF
				
				int getValueFromTwo7bitBytes(unsigned char lsb, unsigned char msb);
				// useful for parsing SysEx messages
				
                // --- events
               
                Poco::BasicEvent<const int> EDigitalPinChanged;
                // triggered when a digital pin changes value, the pin that changed is passed as an argument
               
                Poco::BasicEvent<const int> EAnalogPinChanged;
                // triggered when an analog pin changes value, the pin that changed is passed as an argument
               
                Poco::BasicEvent<const vector<unsigned char> > ESysExReceived;
                // triggered when a SysEx message that isn't in the extended command set is received, the SysEx message is passed as an argument
               
                Poco::BasicEvent<const int> EProtocolVersionReceived;
                // triggered when a protocol version is received, the major version is passed as an argument
               
                Poco::BasicEvent<const int> EFirmwareVersionReceived;
                // triggered when a firmware version is received, the major version is passed as an argument
               
			    Poco::BasicEvent<const int> EInitialized;
                // triggered when the firmware version is received upon connect, the major firmware version is passed as an argument
				// from this point it's safe to send to the Arduino.
				
                Poco::BasicEvent<const string> EStringReceived;
                // triggered when a string is received, the string is passed as an argument
               
        protected:
       
				bool _initialized;
				
				void sendDigitalPinReporting(int pin, int mode);
                // sets pin reporting to ARD_ON or ARD_OFF
				// enables / disables reporting for the pins port
				
				void sendDigitalPortReporting(int port, int mode);
				// sets port reporting to ARD_ON or ARD_OFF
                // enables / disables reporting for ports 0-2
                // port 0: pins 2-7  (0,1 are serial RX/TX)
                // 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 processData(unsigned char inputData);
                void processDigitalPort(int port, unsigned char value);
                virtual void processSysExData(vector<unsigned char> data);
								
                ofSerial _port;
                int _portStatus;
               
                // --- history variables
                int _analogHistoryLength;
                int _digitalHistoryLength;
                int _stringHistoryLength;
                int _sysExHistoryLength;
               
                // --- data processing variables
                int _waitForData;
                int _executeMultiByteCommand;
                int _multiByteChannel; // indicates which pin data came from
               
                // --- data holders
                unsigned char _storedInputData[FIRMATA_MAX_DATA_BYTES];
				vector<unsigned char> _sysExData;
                int _majorProtocolVersion;
                int _minorProtocolVersion;
                int _majorFirmwareVersion;
                int _minorFirmwareVersion;
                string _firmwareName;
               
                list<vector<unsigned char> > _sysExHistory;
                // maintains a history of received sysEx messages (excluding SysEx messages in the extended command set)
               
                list<string> _stringHistory;
                // maintains a history of received strings
               
                list<int> _analogHistory[ARD_TOTAL_ANALOG_PINS];
                // a history of received data for each analog pin
               
                list<int> _digitalHistory[ARD_TOTAL_DIGITAL_PINS];
                // a history of received data for each digital pin
               
                int _digitalPinMode[ARD_TOTAL_DIGITAL_PINS];
                // the modes for all digital pins
               
                int _digitalPinValue[ARD_TOTAL_DIGITAL_PINS];
                // the last set values (DIGITAL/PWM) on all digital pins

                int _digitalPortValue[ARD_TOTAL_PORTS];
                // the last set values on all ports

                int _digitalPortReporting[ARD_TOTAL_PORTS];
                // whether pin reporting is enabled / disabled
               
                int _digitalPinReporting[ARD_TOTAL_DIGITAL_PINS];
                // whether pin reporting is enabled / disabled
               
                int _analogPinReporting[ARD_TOTAL_ANALOG_PINS];
                // whether pin reporting is enabled / disabled
               
};

#endif