/* ModelPlug: Modelica connection to Firmata * Author: Leonardo Laguna Ruiz * Based on Firmata GUI-friendly queries test by Paul Stoffregen (paul@pjrc.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include "serial.h" #include "modelPlugFirmata.h" typedef struct { uint8_t mode; uint8_t analog_channel; uint64_t supported_modes; uint32_t value; uint32_t next_value; uint8_t ready; } pin_t; #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 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 RESERVED_COMMAND 0x00 // 2nd SysEx data byte is a chip-specific command (AVR, PIC, TI, etc). #define ANALOG_MAPPING_QUERY 0x69 // ask for mapping of analog to pin numbers #define ANALOG_MAPPING_RESPONSE 0x6A // reply with mapping info #define CAPABILITY_QUERY 0x6B // ask for supported modes and resolution of all pins #define CAPABILITY_RESPONSE 0x6C // reply with supported modes and resolution #define PIN_STATE_QUERY 0x6D // ask for a pin's current mode and value #define PIN_STATE_RESPONSE 0x6E // reply with a pin's current mode and value #define EXTENDED_ANALOG 0x6F // analog write (PWM, Servo, etc) to any pin #define SERVO_CONFIG 0x70 // set max angle, minPulse, maxPulse, freq #define STRING_DATA 0x71 // a string message with 14-bits per char #define SHIFT_DATA 0x75 // shiftOut config/data message (34 bits) #define I2C_REQUEST 0x76 // I2C request messages from a host to an I/O board #define I2C_REPLY 0x77 // I2C reply messages from an I/O board to a host #define I2C_CONFIG 0x78 // Configure special I2C settings such as power pins and delay times #define REPORT_FIRMWARE 0x79 // report name and version of the firmware #define SAMPLING_INTERVAL 0x7A // sampling interval #define SYSEX_NON_REALTIME 0x7E // MIDI Reserved for non-realtime messages #define SYSEX_REALTIME 0x7F // MIDI Reserved for realtime messages class firmataBoard { public: firmataBoard(std::string port = "dummy",bool showCapabilitites=false,int samplingMs=10,int baudRate=57600,bool fake=true); ~firmataBoard(){}; std::string getPortName(); int openPort(); void closePort(); std::vector getPortList(); void setPinMode(uint32_t pin, uint32_t mode); void writeDigitalPin(uint32_t pin,uint32_t value); void writeAnalogPin(uint32_t pin, uint32_t value); void writeServoPin(uint32_t pin, uint32_t value, int min, int max); double readAnalogPin(uint32_t pin, double min, double max, double init); uint32_t readDigitalPin(uint32_t pin, int init); void setServoConfig(uint32_t pin,uint32_t min,uint32_t max); public: void initialize(bool dtr); void updateBoard(int timeout); void reportFirmware(); void showPinCapabilities(); void setSamplingInterval(); void setAsFakePort(); private: int write(const void *ptr, int len); int read(void *ptr, int count,int timeout); void Parse(const uint8_t *buf, int len); void DoMessage(void); pin_t pin_info[128]; std::string port_name; bool show_capabilitites; int sampling; int baudrate; Serial serial_port; bool ready; bool is_fake_port; std::string firmata_name; int parse_count; int parse_command_len; uint8_t parse_buf[4096]; }; // This is an external object that we want to use in our modelica code. class firmataBoardHandler { firmataBoardHandler(){ try { std::vector available_ports = dummyPort.getPortList(); printf("[ModelPlug.Firmata]: Available ports:\n"); fflush(stdout); for (std::vector::iterator it = available_ports.begin() ; it != available_ports.end(); ++it){ printf("- %s\n",(*it).c_str());fflush(stdout); } } catch(std::exception&) { printf("[ModelPlug.Firmata]: Failed to show the port names\n");fflush(stdout); } } public: static firmataBoardHandler *instance() { if (!s_instance) s_instance = new firmataBoardHandler; return s_instance; } firmataBoard* registerBoard(char* port,bool showCapabilitites,int samplingMs,int baudRate,bool dtr); int getPortIndex(firmataBoard* elem); firmataBoard* getBoard(uint32_t id); private: std::vector ports; firmataBoard dummyPort; static firmataBoardHandler *s_instance; }; firmataBoardHandler *firmataBoardHandler::s_instance = 0; int firmataBoard::write(const void *ptr, int len){ if(!is_fake_port) return serial_port.Write(ptr,len); return 0; } int firmataBoard::read(void *ptr, int count, int timeout){ if(!is_fake_port){ if (serial_port.Is_open() && (serial_port.Input_wait(timeout)>0)) return serial_port.Read(ptr, count); return 0; } return 0; } void firmataBoard::setAsFakePort(){ is_fake_port = true; } firmataBoard::firmataBoard(std::string port, bool showCapabilitites, int samplingMs, int baudRate, bool fake) :port_name(port),show_capabilitites(showCapabilitites),sampling(samplingMs),baudrate(baudRate),is_fake_port(fake){ // Initialize the pin information for (int i=0; i < 128; i++) { pin_info[i].mode = 255; pin_info[i].analog_channel = 127; pin_info[i].supported_modes = 0; pin_info[i].value = 0; pin_info[i].next_value = 0; pin_info[i].ready = 0; } ready = false; } void firmataBoard::setPinMode(uint32_t pin, uint32_t mode){ if (mode != pin_info[pin].mode && ready) { // send the mode change message uint8_t buf[4]; buf[0] = 0xF4; buf[1] = pin; buf[2] = mode; write(buf, 3); pin_info[pin].mode = mode; pin_info[pin].value = 0; pin_info[pin].next_value = 0; printf("[ModelPlug.Firmata]: %s setting pin %i to mode %i\n",port_name.c_str(),pin,mode); } } void firmataBoard::setServoConfig(uint32_t pin,uint32_t min,uint32_t max){ if(ready){ uint8_t buf[8]; buf[0] = START_SYSEX; buf[1] = SERVO_CONFIG; buf[2] = pin; buf[3] = min & 0x7F; buf[4] = (min >> 7) & 0x7F; buf[5] = max & 0x7F; buf[6] = (max >> 7) & 0x7F; buf[7] = END_SYSEX; write(buf, 8); } } void firmataBoard::writeDigitalPin(uint32_t pin,uint32_t value){ if(ready){ if(pin_info[pin].mode!=MODE_OUTPUT) setPinMode(pin,MODE_OUTPUT); pin_info[pin].next_value = value; } } void firmataBoard::writeAnalogPin(uint32_t pin, uint32_t value){ if(ready){ if(pin_info[pin].mode!=MODE_PWM) setPinMode(pin,MODE_PWM); pin_info[pin].next_value = value; } } void firmataBoard::writeServoPin(uint32_t pin, uint32_t value, int min, int max){ if(ready){ if(pin_info[pin].mode!=MODE_SERVO){ setPinMode(pin,MODE_SERVO); setServoConfig(pin,min,max); } pin_info[pin].next_value = value; } } double firmataBoard::readAnalogPin(uint32_t pin,double min, double max, double init){ if(ready){ if(pin_info[pin].mode!=MODE_ANALOG) setPinMode(pin,MODE_ANALOG); if(pin_info[pin].ready) return min+((pin_info[pin].value/4095.0)*(max-min)); else return init; } return init; } uint32_t firmataBoard::readDigitalPin(uint32_t pin, int init){ if(ready){ if(pin_info[pin].mode!=MODE_INPUT) setPinMode(pin,MODE_INPUT); if(pin_info[pin].ready) return pin_info[pin].value; else return init; } return init; } void firmataBoard::reportFirmware(){ uint8_t buf[3]; buf[0] = START_SYSEX; buf[1] = REPORT_FIRMWARE; // read firmata name & version buf[2] = END_SYSEX; write(buf, 3); } void firmataBoard::setSamplingInterval(){ uint8_t buf[5]; buf[0] = START_SYSEX; buf[1] = SAMPLING_INTERVAL; // read firmata name & version buf[2] = sampling & 0x7F; buf[3] = (sampling >> 7) & 0x7F; buf[4] = END_SYSEX; write(buf, 5); printf("[ModelPlug.Firmata]: Setting sampling interval to %i ms for board %s\n",sampling,port_name.c_str()); fflush(stdout); } void firmataBoard::showPinCapabilities(){ printf("[ModelPlug.Firmata]: Board %s capabilities\n", port_name.c_str()); for (int pin=0; pin<128; pin++) { if(pin_info[pin].supported_modes!=0){ printf("- Pin %i supports: ",pin); if (pin_info[pin].supported_modes & (1<> 7) & 0x7F; write(buf, 3); } // Analog output or servo if(pin_info[pin].mode==MODE_PWM || pin_info[pin].mode==MODE_SERVO){ uint32_t value = pin_info[pin].value; if (pin <= 15 && value <= 16383) { uint8_t buf[3]; buf[0] = 0xE0 | pin; buf[1] = value & 0x7F; buf[2] = (value >> 7) & 0x7F; write(buf, 3); } else { uint8_t buf[12]; int len=4; buf[0] = 0xF0; buf[1] = 0x6F; buf[2] = pin; buf[3] = value & 0x7F; if (value > 0x00000080) buf[len++] = (value >> 7) & 0x7F; if (value > 0x00004000) buf[len++] = (value >> 14) & 0x7F; if (value > 0x00200000) buf[len++] = (value >> 21) & 0x7F; if (value > 0x10000000) buf[len++] = (value >> 28) & 0x7F; buf[len++] = 0xF7; write(buf, len); } } } } // receieve bytes from the serial port r = read(buf, sizeof(buf),timeout); if (r) { Parse(buf, r); } } void firmataBoard::Parse(const uint8_t *buf, int len) { const uint8_t *p, *end; p = buf; end = p + len; for (p = buf; p < end; p++) { uint8_t msn = *p & 0xF0; if (msn == 0xE0 || msn == 0x90 || *p == 0xF9) { parse_command_len = 3; parse_count = 0; } else if (msn == 0xC0 || msn == 0xD0) { parse_command_len = 2; parse_count = 0; } else if (*p == START_SYSEX) { parse_count = 0; parse_command_len = sizeof(parse_buf); } else if (*p == END_SYSEX) { parse_command_len = parse_count + 1; } else if (*p & 0x80) { parse_command_len = 1; parse_count = 0; } if (parse_count < (int)sizeof(parse_buf)) { parse_buf[parse_count++] = *p; } if (parse_count == parse_command_len) { DoMessage(); parse_count = parse_command_len = 0; } } } void firmataBoard::DoMessage(void) { uint8_t cmd = (parse_buf[0] & 0xF0); if (cmd == 0xE0 && parse_count == 3) { int analog_ch = (parse_buf[0] & 0x0F); int analog_val = parse_buf[1] | (parse_buf[2] << 7); for (int pin=0; pin<128; pin++) { if (pin_info[pin].analog_channel == analog_ch) { pin_info[pin].value = analog_val; pin_info[pin].ready = 1; return; } } return; } if (cmd == 0x90 && parse_count == 3) { int port_num = (parse_buf[0] & 0x0F); int port_val = parse_buf[1] | (parse_buf[2] << 7); int pin = port_num * 8; //printf("port_num = %d, port_val = %d\n", port_num, port_val); for (int mask=1; mask & 0xFF; mask <<= 1, pin++) { if (pin_info[pin].mode == MODE_INPUT) { uint32_t val = (port_val & mask) ? 1 : 0; if (pin_info[pin].value != val) { pin_info[pin].value = val; pin_info[pin].ready = 1; } } } return; } if (parse_buf[0] == START_SYSEX && parse_buf[parse_count-1] == END_SYSEX) { // Sysex message if (parse_buf[1] == REPORT_FIRMWARE) { char name[140]; int len=0; for (int i=4; i < parse_count-2; i+=2) { name[len++] = (parse_buf[i] & 0x7F) | ((parse_buf[i+1] & 0x7F) << 7); } name[len++] = '-'; name[len++] = parse_buf[2] + '0'; name[len++] = '.'; name[len++] = parse_buf[3] + '0'; name[len++] = 0; firmata_name = name; printf("[ModelPlug.Firmata]: %s %s\n",port_name.c_str(),name); ready = true; // query the board's capabilities only after hearing the // REPORT_FIRMWARE message. For boards that reset when // the port open (eg, Arduino with reset=DTR), they are // not ready to communicate for some time, so the only // way to reliably query their capabilities is to wait // until the REPORT_FIRMWARE message is heard. uint8_t buf[80]; len=0; buf[len++] = START_SYSEX; buf[len++] = ANALOG_MAPPING_QUERY; // read analog to pin # info buf[len++] = END_SYSEX; buf[len++] = START_SYSEX; buf[len++] = CAPABILITY_QUERY; // read capabilities buf[len++] = END_SYSEX; for (int i=0; i<16; i++) { buf[len++] = 0xC0 | i; // report analog buf[len++] = 1; buf[len++] = 0xD0 | i; // report digital buf[len++] = 1; } write(buf, len); setSamplingInterval(); } else if (parse_buf[1] == CAPABILITY_RESPONSE) { int pin, i, n; for (pin=0; pin < 128; pin++) { pin_info[pin].supported_modes = 0; } for (i=2, n=0, pin=0; i= 6) { int pin = parse_buf[2]; pin_info[pin].mode = parse_buf[3]; pin_info[pin].value = parse_buf[4]; if (parse_count > 6) pin_info[pin].value |= (parse_buf[5] << 7); if (parse_count > 7) pin_info[pin].value |= (parse_buf[6] << 14); //add_pin(pin); } return; } } std::vector firmataBoard::getPortList(){ return serial_port.port_list(); } firmataBoard* firmataBoardHandler::getBoard(uint32_t id){ if(id<0) return &dummyPort; else return ports[id]; } firmataBoard* firmataBoardHandler::registerBoard(char* port,bool showCapabilitites,int samplingMs,int baudRate,bool dtr){ // Check if the port exists bool port_exists = false; bool fake_port = false; std::string port_name(port); std::vector available_ports = dummyPort.getPortList(); for (std::vector::iterator it = available_ports.begin() ; it != available_ports.end(); ++it){ if((*it).compare(port_name)==0){ port_exists = true; break; } } if(!port_exists) { printf("[ModelPlug.Firmata]: ERROR The port %s does not exist\n",port); fflush(stdout); fake_port |= true; } // Check if the port is already used for (std::vector::iterator it = ports.begin() ; it != ports.end(); ++it){ if ((*it)->getPortName().compare(port_name)==0) { // Print duplicated port, report error printf("[ModelPlug.Firmata]: ERROR: Trying to use two boards with the port %s\n",port);fflush(stdout); fake_port |= true; } } firmataBoard* new_port = new firmataBoard(port_name,showCapabilitites,samplingMs,baudRate,fake_port); if(new_port->openPort()==0){ ports.push_back(new_port); new_port->initialize(dtr); return new_port; } else{ // Could not open the port return error printf("[ModelPlug.Firmata]: ERROR Failed to open the port %s\n",port); fflush(stdout); new_port->setAsFakePort(); return new_port; } return new_port; } int firmataBoardHandler::getPortIndex(firmataBoard* elem) { unsigned pos = std::find(ports.begin(), ports.end(), elem) - ports.begin(); if(pos >= ports.size()){ // not found return -1; } return pos; } std::string firmataBoard::getPortName(){ return port_name; } int firmataBoard::openPort(){ if(is_fake_port){ return 0; } else { serial_port.Open(port_name); serial_port.Set_baud(baudrate); if(serial_port.Is_open()){ printf("[ModelPlug.Firmata]: Using port %s with baud rate %i\n",port_name.c_str(),baudrate); fflush(stdout); return 0; } else return -1; } return -1; } void firmataBoard::initialize(bool dtr){ serial_port.Set_control(dtr?1:0,-1); updateBoard(5); reportFirmware(); updateBoard(5); } void firmataBoard::closePort(){ if(is_fake_port) return; else if(serial_port.Is_open()) serial_port.Close(); } extern "C" { EXPORT void* boardConstructor(char* port,bool showCapabilitites,int samplingMs,int baudRate,bool dtr){ firmataBoardHandler* boards = firmataBoardHandler::instance(); void* object = (void*)boards->registerBoard(port,showCapabilitites,samplingMs,baudRate,dtr); return object; } EXPORT void boardDestructor(void* object){ if(object!=NULL){ firmataBoard* board = (firmataBoard*)object; board->closePort(); delete board; } } EXPORT void updateBoard(int id){ firmataBoard* board = firmataBoardHandler::instance()->getBoard(id); board->updateBoard(0); } EXPORT int getBoardId(void* object){ if(object!=NULL){ return firmataBoardHandler::instance()->getPortIndex((firmataBoard*)object); } else return -1; } EXPORT double readAnalogPin(int pin, double min, double max, double init, int id){ firmataBoard* board = firmataBoardHandler::instance()->getBoard(id); double value = board->readAnalogPin(pin,min,max,init); return value; } EXPORT int readDigitalPin(int pin, int init, int id){ firmataBoard* board = firmataBoardHandler::instance()->getBoard(id); int value = board->readDigitalPin(pin,init); return value; } EXPORT void writeAnalogPin(int pin, int id,double value){ double croped = value>1.0?1.0:(value<0.0?0.0:value); firmataBoard* board = firmataBoardHandler::instance()->getBoard(id); board->writeAnalogPin(pin,(uint32_t)(croped*1023)); } EXPORT void writeDigitalPin(int pin, int id,int value){ firmataBoard* board = firmataBoardHandler::instance()->getBoard(id); board->writeDigitalPin(pin,value); } EXPORT void writeServoPin(int pin, int id,double value,int min,int max){ double croped = value>1.0?1.0:(value<0.0?0.0:value); firmataBoard* board = firmataBoardHandler::instance()->getBoard(id); board->writeServoPin(pin,(int)(croped*180),min,max); } } // end extern "C"