
#ifndef firmatalib_h
#define firmatalib_h

#include "serial.h"

// Constants from the FIRMATA PROTOCOL:
// https://github.com/firmata/arduino

#define MODE_INPUT              0x00
#define MODE_OUTPUT             0x01
#define MODE_ANALOG             0x02
#define MODE_PWM                0x03
#define MODE_SERVO              0x04
#define MODE_SHIFT              0x05
#define MODE_I2C                0x06
#define MODE_ONEWIRE            0x07
#define MODE_STEPPER            0x08
#define MODE_ENCODER            0x09
#define MODE_SERIAL             0x0A
#define MODE_PULLUP             0x0B
#define MODE_IGNORE             0x7F

#define START_SYSEX             0xF0 // start a MIDI Sysex message
#define END_SYSEX               0xF7 // end a MIDI Sysex message
#define PIN_MODE_QUERY          0x72 // ask for current and supported pin modes
#define PIN_MODE_RESPONSE       0x73 // reply with current and supported pin modes
#define DIGITAL_MESSAGE         0x90 // send data for a digital port
#define ANALOG_MESSAGE          0xE0 // send data for an analog pin (or PWM)
#define REPORT_ANALOG           0xC0 // enable analog input by pin #
#define REPORT_DIGITAL          0xD0 // enable digital input by port
#define SET_PIN_MODE            0xF4 // set a pin to INPUT/OUTPUT/PWM/etc
#define REPORT_VERSION          0xF9 // report firmware version
#define SYSTEM_RESET            0xFF // reset from MIDI

#define SERVO_CONFIG            0x70 // set max angle, minPulse, maxPulse, freq
#define STRING_DATA             0x71 // a string message with 14-bits per char
#define PULSE_IN                0x74 // send a pulse in command (34 bits)
#define SHIFT_DATA              0x75 // a bitstream to/from a shift register
#define I2C_REQUEST             0x76 // send an I2C read/write request
#define I2C_REPLY               0x77 // a reply to an I2C read request
#define I2C_CONFIG              0x78 // config I2C settings such as delay times and power pins
#define EXTENDED_ANALOG         0x6F // analog write (PWM, Servo, etc) to any pin
#define PIN_STATE_QUERY         0x6D // ask for a pin's current mode and value
#define PIN_STATE_RESPONSE      0x6E // reply with pin's current mode and value
#define CAPABILITY_QUERY        0x6B // ask for supported modes and resolution of all pins
#define CAPABILITY_RESPONSE     0x6C // reply with supported modes and resolution
#define ANALOG_MAPPING_QUERY    0x69 // ask for mapping of analog to pin numbers
#define ANALOG_MAPPING_RESPONSE 0x6A // reply with mapping info
#define REPORT_FIRMWARE         0x79 // report name and version of the firmware
#define SAMPLING_INTERVAL       0x7A // set the poll rate of the main loop
#define SYSEX_NON_REALTIME      0x7E // MIDI Reserved for non-realtime messages
#define SYSEX_REALTIME          0x7F // MIDI Reserved for realtime messages

// Constants for the client
#define TOTAL_PINS              128
#define BUF_SIZE                4096
#define LED_BUILTIN             13
#define MODE_OFF                255


// this struct follows the firmata transmitted data protocol
typedef struct {
    uint8_t mode;               // current pin mode
    uint8_t analog_channel;     // corresponding analog channel on board
    uint64_t supported_modes;   // all possible modes for the pin
    uint32_t value;             // pin value
} pin_t;


// this struct supports the client library
typedef struct {

    // serial communication with the board
    serial_t *port;

    // board pins information
    pin_t pin_info[TOTAL_PINS];
    
    // firmware name
    char firmata_name[140];
    
    // read and write counters (in bytes)
    unsigned int rx_count, tx_count;

    // parser utilities
    int parse_count;
    int parse_command_len;
    uint8_t parse_buf[BUF_SIZE];

    // board state (for serial communication)
    bool isOK;
    bool isBoardReady;

    // POSIX thread - infinite listener
    pthread_attr_t attr;
    bool threadlistening;
    
    // latest error message
    char err_msg[4000];
    
} board_t;

// Blink the on-board LED 3 times.
int  firmataclient_blink(board_t *board);

// Sends a firmware query to the board connected.
void firmataclient_requestfirmware(board_t *board);

// Opens a communiction port to the board. Returns 0 on success, -1 otherwise.
int  firmataclient_initport(board_t *board, char *name, int baud);

// Sends a new value pin update to the board.
void firmataclient_sendnewvalue(board_t *board, int pin, int newValue);

// Sends a new mode pin update to the board.
void firmataclient_changemode(board_t *board, int pin, int newMode);

// Sends a new digital value pin update to the board.
void firmataclient_senddigitalvalue(board_t *board, int pin, int newValue);

// Returns true if the board is ready for communication.
bool firmataclient_isboardready(board_t *board);

// Disconnects the board.
int  firmataclient_closeport(board_t *board);

// Waits (blocks) until the board is ready for communication.
int  firmataclient_waitForBoardReady(board_t *board);

// Reads pin values from the board.
void firmataclient_getinputvalues(board_t *board, long *indexArray, long *valuesArray, int pinsCount);

// Returns the Firmata version uploaded to the board.
char *firmataclient_getfirmware(board_t *board);

// Starts listening for incoming board messages (launch an infinite listening thread).
int  firmataclient_launchtlisteninghread(board_t *board);

// Listen for incoming bytes from serial.
void firmataclient_onidle(board_t *board);

// Stops listening for incoming board messages (kill the listening thread).
int  firmataclient_destroylisteningthread(board_t *board);

#endif /* firmataclient_h */
